feat: 前端显示 mdns 搜索到的板子连接信息。

This commit is contained in:
Ivan Li 2023-04-29 15:09:45 +08:00
parent e5527ce3c3
commit f6e3257670
9 changed files with 164 additions and 12 deletions

View File

@ -13,6 +13,7 @@
"dependencies": { "dependencies": {
"@solidjs/router": "^0.8.2", "@solidjs/router": "^0.8.2",
"@tauri-apps/api": "^1.2.0", "@tauri-apps/api": "^1.2.0",
"debug": "^4.3.4",
"solid-icons": "^1.0.4", "solid-icons": "^1.0.4",
"solid-js": "^1.4.7", "solid-js": "^1.4.7",
"solid-tippy": "^0.2.1", "solid-tippy": "^0.2.1",
@ -20,6 +21,7 @@
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "^1.2.2", "@tauri-apps/cli": "^1.2.2",
"@types/debug": "^4.1.7",
"@types/node": "^18.7.10", "@types/node": "^18.7.10",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"postcss": "^8.4.21", "postcss": "^8.4.21",

View File

@ -7,6 +7,9 @@ dependencies:
'@tauri-apps/api': '@tauri-apps/api':
specifier: ^1.2.0 specifier: ^1.2.0
version: 1.2.0 version: 1.2.0
debug:
specifier: ^4.3.4
version: 4.3.4
solid-icons: solid-icons:
specifier: ^1.0.4 specifier: ^1.0.4
version: 1.0.4(solid-js@1.6.14) version: 1.0.4(solid-js@1.6.14)
@ -24,6 +27,9 @@ devDependencies:
'@tauri-apps/cli': '@tauri-apps/cli':
specifier: ^1.2.2 specifier: ^1.2.2
version: 1.2.3 version: 1.2.3
'@types/debug':
specifier: ^4.1.7
version: 4.1.7
'@types/node': '@types/node':
specifier: ^18.7.10 specifier: ^18.7.10
version: 18.15.3 version: 18.15.3
@ -766,6 +772,16 @@ packages:
'@babel/types': 7.21.3 '@babel/types': 7.21.3
dev: true dev: true
/@types/debug@4.1.7:
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
dependencies:
'@types/ms': 0.7.31
dev: true
/@types/ms@0.7.31:
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
dev: true
/@types/node@18.15.3: /@types/node@18.15.3:
resolution: {integrity: sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==} resolution: {integrity: sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==}
dev: true dev: true
@ -939,7 +955,6 @@ packages:
optional: true optional: true
dependencies: dependencies:
ms: 2.1.2 ms: 2.1.2
dev: true
/defined@1.0.1: /defined@1.0.1:
resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==}
@ -1175,7 +1190,6 @@ packages:
/ms@2.1.2: /ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true
/nanoid@3.3.4: /nanoid@3.3.4:
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}

View File

@ -11,7 +11,7 @@ mod screenshot_manager;
use ambient_light::{Border, ColorCalibration, LedStripConfig, LedStripConfigGroup}; use ambient_light::{Border, ColorCalibration, LedStripConfig, LedStripConfigGroup};
use display_info::DisplayInfo; use display_info::DisplayInfo;
use paris::{error, info, warn}; use paris::{error, info, warn};
use rpc::{MqttRpc, UdpRpc}; use rpc::{BoardInfo, MqttRpc, UdpRpc};
use screenshot::Screenshot; use screenshot::Screenshot;
use screenshot_manager::ScreenshotManager; use screenshot_manager::ScreenshotManager;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -188,6 +188,21 @@ async fn read_config() -> ambient_light::LedStripConfigGroup {
config_manager.configs().await config_manager.configs().await
} }
#[tauri::command]
async fn get_boards() -> Result<Vec<BoardInfo>, String> {
let udp_rpc = UdpRpc::global().await;
if let Err(e) = udp_rpc {
return Err(format!("can not ping: {}", e));
}
let udp_rpc = udp_rpc.as_ref().unwrap();
let boards = udp_rpc.get_boards().await;
let boards = boards.into_iter().collect::<Vec<_>>();
Ok(boards)
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
env_logger::init(); env_logger::init();
@ -200,8 +215,6 @@ async fn main() {
let _mqtt = MqttRpc::global().await; let _mqtt = MqttRpc::global().await;
let _udp = UdpRpc::global().await;
tauri::Builder::default() tauri::Builder::default()
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
greet, greet,
@ -216,6 +229,7 @@ async fn main() {
reverse_led_strip_part, reverse_led_strip_part,
set_color_calibration, set_color_calibration,
read_config, read_config,
get_boards,
]) ])
.register_uri_scheme_protocol("ambient-light", move |_app, request| { .register_uri_scheme_protocol("ambient-light", move |_app, request| {
let response = ResponseBuilder::new().header("Access-Control-Allow-Origin", "*"); let response = ResponseBuilder::new().header("Access-Control-Allow-Origin", "*");
@ -400,6 +414,33 @@ async fn main() {
} }
}); });
let app_handle = app.handle().clone();
tokio::spawn(async move {
loop {
match UdpRpc::global().await {
Ok(udp_rpc) => {
let mut receiver = udp_rpc.clone_boards_change_receiver().await;
loop {
if let Err(err) = receiver.changed().await {
error!("boards change receiver changed error: {}", err);
return;
}
let boards = receiver.borrow().clone();
let boards = boards.into_iter().collect::<Vec<_>>();
app_handle.emit_all("boards_changed", boards).unwrap();
}
}
Err(err) => {
error!("udp rpc error: {}", err);
return;
}
}
}
});
Ok(()) Ok(())
}) })
.run(tauri::generate_context!()) .run(tauri::generate_context!())

View File

@ -1,6 +1,8 @@
use std::net::{Ipv4Addr}; use std::net::{Ipv4Addr};
#[derive(Debug, Clone, Eq, PartialEq, Hash)] use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct BoardInfo { pub struct BoardInfo {
pub name: String, pub name: String,
pub address: Ipv4Addr, pub address: Ipv4Addr,

View File

@ -4,7 +4,7 @@ use mdns_sd::{ServiceDaemon, ServiceEvent};
use paris::{error, info, warn}; use paris::{error, info, warn};
use tokio::{ use tokio::{
net::UdpSocket, net::UdpSocket,
sync::{Mutex, OnceCell}, sync::{watch, Mutex, OnceCell},
}; };
use super::BoardInfo; use super::BoardInfo;
@ -13,6 +13,8 @@ use super::BoardInfo;
pub struct UdpRpc { pub struct UdpRpc {
socket: Arc<Mutex<UdpSocket>>, socket: Arc<Mutex<UdpSocket>>,
boards: Arc<Mutex<HashSet<BoardInfo>>>, boards: Arc<Mutex<HashSet<BoardInfo>>>,
boards_change_sender: Arc<Mutex<watch::Sender<HashSet<BoardInfo>>>>,
boards_change_receiver: Arc<Mutex<watch::Receiver<HashSet<BoardInfo>>>>,
} }
impl UdpRpc { impl UdpRpc {
@ -32,7 +34,15 @@ impl UdpRpc {
let socket = UdpSocket::bind("0.0.0.0:0").await?; let socket = UdpSocket::bind("0.0.0.0:0").await?;
let socket = Arc::new(Mutex::new(socket)); let socket = Arc::new(Mutex::new(socket));
let boards = Arc::new(Mutex::new(HashSet::new())); let boards = Arc::new(Mutex::new(HashSet::new()));
Ok(Self { socket, boards }) let (boards_change_sender, boards_change_receiver) = watch::channel(HashSet::new());
let boards_change_sender = Arc::new(Mutex::new(boards_change_sender));
let boards_change_receiver = Arc::new(Mutex::new(boards_change_receiver));
Ok(Self {
socket,
boards,
boards_change_sender,
boards_change_receiver,
})
} }
async fn initialize(&self) { async fn initialize(&self) {
@ -52,12 +62,10 @@ impl UdpRpc {
}); });
} }
pub async fn search_boards(&self) -> anyhow::Result<()> { async fn search_boards(&self) -> anyhow::Result<()> {
let service_type = "_ambient_light._udp.local.";
let mdns = ServiceDaemon::new()?; let mdns = ServiceDaemon::new()?;
let shared_self = Arc::new(Mutex::new(self.clone())); let shared_self = Arc::new(Mutex::new(self.clone()));
let service_type = "_ambient_light._udp.local.";
let receiver = mdns.browse(&service_type).map_err(|e| { let receiver = mdns.browse(&service_type).map_err(|e| {
warn!("Failed to browse for {:?}: {:?}", service_type, e); warn!("Failed to browse for {:?}: {:?}", service_type, e);
e e
@ -87,6 +95,9 @@ impl UdpRpc {
if boards.insert(board.clone()) { if boards.insert(board.clone()) {
info!("added board {:?}", board); info!("added board {:?}", board);
} }
let sender = self.boards_change_sender.clone().lock_owned().await;
sender.send(boards.clone())?;
} }
other_event => { other_event => {
warn!("{:?}", &other_event); warn!("{:?}", &other_event);
@ -96,4 +107,16 @@ impl UdpRpc {
Ok(()) Ok(())
} }
pub async fn clone_boards_change_receiver(
&self,
) -> watch::Receiver<HashSet<BoardInfo>> {
let boards_change_receiver = self.boards_change_receiver.clone().lock_owned().await;
boards_change_receiver.clone()
}
pub async fn get_boards(&self) -> HashSet<BoardInfo> {
let boards = self.boards.clone().lock_owned().await;
boards.clone()
}
} }

View File

@ -5,6 +5,7 @@ import { createEffect } from 'solid-js';
import { invoke } from '@tauri-apps/api'; import { invoke } from '@tauri-apps/api';
import { setLedStripStore } from './stores/led-strip.store'; import { setLedStripStore } from './stores/led-strip.store';
import { LedStripConfigContainer } from './models/led-strip-config'; import { LedStripConfigContainer } from './models/led-strip-config';
import { InfoIndex } from './components/info/info-index';
function App() { function App() {
createEffect(() => { createEffect(() => {
@ -21,10 +22,12 @@ function App() {
return ( return (
<div> <div>
<div> <div>
<a href="/info"></a>
<a href="/led-strips-configuration"></a> <a href="/led-strips-configuration"></a>
<a href="/white-balance"></a> <a href="/white-balance"></a>
</div> </div>
<Routes> <Routes>
<Route path="/info" component={InfoIndex} />
<Route path="/led-strips-configuration" component={LedStripConfiguration} /> <Route path="/led-strips-configuration" component={LedStripConfiguration} />
<Route path="/white-balance" component={WhiteBalance} /> <Route path="/white-balance" component={WhiteBalance} />
</Routes> </Routes>

View File

@ -0,0 +1,52 @@
import { Component, For, createEffect, createSignal } from 'solid-js';
import { BoardInfo } from '../../models/board-info.model';
import { listen } from '@tauri-apps/api/event';
import debug from 'debug';
import { invoke } from '@tauri-apps/api';
const logger = debug('app:components:info:board-index');
export const BoardIndex: Component = () => {
const [boards, setBoards] = createSignal<BoardInfo[]>([]);
createEffect(() => {
const unlisten = listen<BoardInfo[]>('boards_changed', (ev) => {
logger('boards_changed', ev);
setBoards(ev.payload);
});
invoke<BoardInfo[]>('get_boards').then((boards) => {
logger('get_boards', boards);
setBoards(boards);
});
return () => {
unlisten.then((unlisten) => unlisten());
};
});
return (
<ol class="grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 p-2 gap-2">
<For each={boards()}>
{(board, index) => (
<li class="p-2 rounded shadow bg-slate-50 text-gray-800 relative border-2 border-slate-50 hover:border-sky-300 focus:border-sky-300 transition">
<dl class="flex">
<dt class="w-20">host</dt>
<dd class="flex-auto">{board.name}</dd>
</dl>
<dl class="flex">
<dt class="w-20">Ip Addr</dt>
<dd class="flex-auto font-mono">{board.address}</dd>
</dl>
<dl class="flex">
<dt class="w-20">Port</dt>
<dd class="flex-auto font-mono">{board.port}</dd>
</dl>
<span class="absolute left-2 -top-3 bg-sky-300 text-white px-1 py-0.5 text-xs rounded-sm font-mono">
#{index() + 1}
</span>
</li>
)}
</For>
</ol>
);
};

View File

@ -0,0 +1,10 @@
import { Component } from 'solid-js';
import { BoardIndex } from './board-index';
export const InfoIndex: Component = () => {
return (
<div>
<BoardIndex />
</div>
);
};

View File

@ -0,0 +1,5 @@
export type BoardInfo = {
name: string;
address: string;
port: number;
};