diff --git a/package.json b/package.json index c3553a8..540ecb8 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "@solidjs/router": "^0.8.2", "@tauri-apps/api": "^1.2.0", + "debug": "^4.3.4", "solid-icons": "^1.0.4", "solid-js": "^1.4.7", "solid-tippy": "^0.2.1", @@ -20,6 +21,7 @@ }, "devDependencies": { "@tauri-apps/cli": "^1.2.2", + "@types/debug": "^4.1.7", "@types/node": "^18.7.10", "autoprefixer": "^10.4.14", "postcss": "^8.4.21", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d7d8120..7780059 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,9 @@ dependencies: '@tauri-apps/api': specifier: ^1.2.0 version: 1.2.0 + debug: + specifier: ^4.3.4 + version: 4.3.4 solid-icons: specifier: ^1.0.4 version: 1.0.4(solid-js@1.6.14) @@ -24,6 +27,9 @@ devDependencies: '@tauri-apps/cli': specifier: ^1.2.2 version: 1.2.3 + '@types/debug': + specifier: ^4.1.7 + version: 4.1.7 '@types/node': specifier: ^18.7.10 version: 18.15.3 @@ -766,6 +772,16 @@ packages: '@babel/types': 7.21.3 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: resolution: {integrity: sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==} dev: true @@ -939,7 +955,6 @@ packages: optional: true dependencies: ms: 2.1.2 - dev: true /defined@1.0.1: resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} @@ -1175,7 +1190,6 @@ packages: /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true /nanoid@3.3.4: resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 709eaad..8b78ed5 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -11,7 +11,7 @@ mod screenshot_manager; use ambient_light::{Border, ColorCalibration, LedStripConfig, LedStripConfigGroup}; use display_info::DisplayInfo; use paris::{error, info, warn}; -use rpc::{MqttRpc, UdpRpc}; +use rpc::{BoardInfo, MqttRpc, UdpRpc}; use screenshot::Screenshot; use screenshot_manager::ScreenshotManager; use serde::{Deserialize, Serialize}; @@ -188,6 +188,21 @@ async fn read_config() -> ambient_light::LedStripConfigGroup { config_manager.configs().await } +#[tauri::command] +async fn get_boards() -> Result, 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::>(); + Ok(boards) +} + #[tokio::main] async fn main() { env_logger::init(); @@ -200,8 +215,6 @@ async fn main() { let _mqtt = MqttRpc::global().await; - let _udp = UdpRpc::global().await; - tauri::Builder::default() .invoke_handler(tauri::generate_handler![ greet, @@ -216,6 +229,7 @@ async fn main() { reverse_led_strip_part, set_color_calibration, read_config, + get_boards, ]) .register_uri_scheme_protocol("ambient-light", move |_app, request| { 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::>(); + + app_handle.emit_all("boards_changed", boards).unwrap(); + } + } + Err(err) => { + error!("udp rpc error: {}", err); + return; + } + } + } + }); + Ok(()) }) .run(tauri::generate_context!()) diff --git a/src-tauri/src/rpc/board_info.rs b/src-tauri/src/rpc/board_info.rs index f253147..a8ad36c 100644 --- a/src-tauri/src/rpc/board_info.rs +++ b/src-tauri/src/rpc/board_info.rs @@ -1,6 +1,8 @@ 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 name: String, pub address: Ipv4Addr, diff --git a/src-tauri/src/rpc/udp.rs b/src-tauri/src/rpc/udp.rs index 29b42d5..08af6ca 100644 --- a/src-tauri/src/rpc/udp.rs +++ b/src-tauri/src/rpc/udp.rs @@ -4,7 +4,7 @@ use mdns_sd::{ServiceDaemon, ServiceEvent}; use paris::{error, info, warn}; use tokio::{ net::UdpSocket, - sync::{Mutex, OnceCell}, + sync::{watch, Mutex, OnceCell}, }; use super::BoardInfo; @@ -13,6 +13,8 @@ use super::BoardInfo; pub struct UdpRpc { socket: Arc>, boards: Arc>>, + boards_change_sender: Arc>>>, + boards_change_receiver: Arc>>>, } impl UdpRpc { @@ -32,7 +34,15 @@ impl UdpRpc { let socket = UdpSocket::bind("0.0.0.0:0").await?; let socket = Arc::new(Mutex::new(socket)); 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) { @@ -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 shared_self = Arc::new(Mutex::new(self.clone())); - - let service_type = "_ambient_light._udp.local."; - let receiver = mdns.browse(&service_type).map_err(|e| { warn!("Failed to browse for {:?}: {:?}", service_type, e); e @@ -87,6 +95,9 @@ impl UdpRpc { if boards.insert(board.clone()) { info!("added board {:?}", board); } + + let sender = self.boards_change_sender.clone().lock_owned().await; + sender.send(boards.clone())?; } other_event => { warn!("{:?}", &other_event); @@ -96,4 +107,16 @@ impl UdpRpc { Ok(()) } + + pub async fn clone_boards_change_receiver( + &self, + ) -> watch::Receiver> { + let boards_change_receiver = self.boards_change_receiver.clone().lock_owned().await; + boards_change_receiver.clone() + } + + pub async fn get_boards(&self) -> HashSet { + let boards = self.boards.clone().lock_owned().await; + boards.clone() + } } diff --git a/src/App.tsx b/src/App.tsx index aff76c9..9352b99 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,6 +5,7 @@ import { createEffect } from 'solid-js'; import { invoke } from '@tauri-apps/api'; import { setLedStripStore } from './stores/led-strip.store'; import { LedStripConfigContainer } from './models/led-strip-config'; +import { InfoIndex } from './components/info/info-index'; function App() { createEffect(() => { @@ -21,10 +22,12 @@ function App() { return (
+ diff --git a/src/components/info/board-index.tsx b/src/components/info/board-index.tsx new file mode 100644 index 0000000..e462cbe --- /dev/null +++ b/src/components/info/board-index.tsx @@ -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([]); + + createEffect(() => { + const unlisten = listen('boards_changed', (ev) => { + logger('boards_changed', ev); + setBoards(ev.payload); + }); + + invoke('get_boards').then((boards) => { + logger('get_boards', boards); + setBoards(boards); + }); + + return () => { + unlisten.then((unlisten) => unlisten()); + }; + }); + return ( +
    + + {(board, index) => ( +
  1. +
    +
    host
    +
    {board.name}
    +
    +
    +
    Ip Addr
    +
    {board.address}
    +
    +
    +
    Port
    +
    {board.port}
    +
    + + #{index() + 1} + +
  2. + )} +
    +
+ ); +}; diff --git a/src/components/info/info-index.tsx b/src/components/info/info-index.tsx new file mode 100644 index 0000000..ce10d0b --- /dev/null +++ b/src/components/info/info-index.tsx @@ -0,0 +1,10 @@ +import { Component } from 'solid-js'; +import { BoardIndex } from './board-index'; + +export const InfoIndex: Component = () => { + return ( +
+ +
+ ); +}; diff --git a/src/models/board-info.model.ts b/src/models/board-info.model.ts new file mode 100644 index 0000000..6affb85 --- /dev/null +++ b/src/models/board-info.model.ts @@ -0,0 +1,5 @@ +export type BoardInfo = { + name: string; + address: string; + port: number; +};