feat: 支持获取和查看板子连接的情况。
This commit is contained in:
parent
11045f27d8
commit
6c90a5e655
@ -419,12 +419,13 @@ async fn main() {
|
|||||||
loop {
|
loop {
|
||||||
match UdpRpc::global().await {
|
match UdpRpc::global().await {
|
||||||
Ok(udp_rpc) => {
|
Ok(udp_rpc) => {
|
||||||
let mut receiver = udp_rpc.clone_boards_change_receiver().await;
|
let mut receiver = udp_rpc.subscribe_boards_change();
|
||||||
loop {
|
loop {
|
||||||
if let Err(err) = receiver.changed().await {
|
if let Err(err) = receiver.changed().await {
|
||||||
error!("boards change receiver changed error: {}", err);
|
error!("boards change receiver changed error: {}", err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
log::info!("boards changed");
|
||||||
|
|
||||||
let boards = receiver.borrow().clone();
|
let boards = receiver.borrow().clone();
|
||||||
|
|
||||||
|
@ -1,10 +1,61 @@
|
|||||||
use std::net::{Ipv4Addr};
|
use std::{net::Ipv4Addr, time::Duration};
|
||||||
|
|
||||||
|
use paris::warn;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::{net::UdpSocket, time::timeout};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
pub struct BoardInfo {
|
pub struct BoardInfo {
|
||||||
pub name: String,
|
pub host: String,
|
||||||
pub address: Ipv4Addr,
|
pub address: Ipv4Addr,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
}
|
pub is_online: bool,
|
||||||
|
pub checked_at: Option<std::time::SystemTime>,
|
||||||
|
pub ttl: Option<u128>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoardInfo {
|
||||||
|
pub fn new(host: String, address: Ipv4Addr, port: u16) -> Self {
|
||||||
|
Self {
|
||||||
|
host,
|
||||||
|
address,
|
||||||
|
port,
|
||||||
|
is_online: false,
|
||||||
|
checked_at: None,
|
||||||
|
ttl: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn check(&mut self) -> anyhow::Result<()> {
|
||||||
|
let socket = UdpSocket::bind("0.0.0.0:0").await?;
|
||||||
|
socket.connect((self.address, self.port)).await?;
|
||||||
|
|
||||||
|
let instant = std::time::Instant::now();
|
||||||
|
|
||||||
|
socket.send(&[1]).await?;
|
||||||
|
let mut buf = [0u8; 1];
|
||||||
|
let recv_future = socket.recv(&mut buf);
|
||||||
|
|
||||||
|
match timeout(Duration::from_secs(5), recv_future).await {
|
||||||
|
Ok(_) => {
|
||||||
|
let ttl = instant.elapsed();
|
||||||
|
log::info!("buf: {:?}", buf);
|
||||||
|
if buf == [1] {
|
||||||
|
self.is_online = true;
|
||||||
|
} else {
|
||||||
|
self.is_online = false;
|
||||||
|
}
|
||||||
|
self.ttl = Some(ttl.as_millis());
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
warn!("timeout");
|
||||||
|
self.is_online = false;
|
||||||
|
self.ttl = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.checked_at = Some(std::time::SystemTime::now());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
use std::{collections::HashSet, sync::Arc, time::Duration};
|
use std::{collections::HashSet, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use itertools::Itertools;
|
|
||||||
use mdns_sd::{ServiceDaemon, ServiceEvent};
|
use mdns_sd::{ServiceDaemon, ServiceEvent};
|
||||||
use paris::{error, info, warn};
|
use paris::{error, info, warn};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
net::UdpSocket,
|
net::UdpSocket,
|
||||||
sync::{watch, Mutex, OnceCell, RwLock},
|
sync::{watch, OnceCell, RwLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::BoardInfo;
|
use super::BoardInfo;
|
||||||
@ -15,8 +14,7 @@ use super::BoardInfo;
|
|||||||
pub struct UdpRpc {
|
pub struct UdpRpc {
|
||||||
socket: Arc<UdpSocket>,
|
socket: Arc<UdpSocket>,
|
||||||
boards: Arc<RwLock<HashSet<BoardInfo>>>,
|
boards: Arc<RwLock<HashSet<BoardInfo>>>,
|
||||||
boards_change_sender: Arc<Mutex<watch::Sender<HashSet<BoardInfo>>>>,
|
boards_change_sender: Arc<watch::Sender<Vec<BoardInfo>>>,
|
||||||
boards_change_receiver: Arc<Mutex<watch::Receiver<HashSet<BoardInfo>>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UdpRpc {
|
impl UdpRpc {
|
||||||
@ -36,22 +34,23 @@ 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(socket);
|
let socket = Arc::new(socket);
|
||||||
let boards = Arc::new(RwLock::new(HashSet::new()));
|
let boards = Arc::new(RwLock::new(HashSet::new()));
|
||||||
let (boards_change_sender, boards_change_receiver) = watch::channel(HashSet::new());
|
let (boards_change_sender, _) = watch::channel(Vec::new());
|
||||||
let boards_change_sender = Arc::new(Mutex::new(boards_change_sender));
|
let boards_change_sender = Arc::new(boards_change_sender);
|
||||||
let boards_change_receiver = Arc::new(Mutex::new(boards_change_receiver));
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
socket,
|
socket,
|
||||||
boards,
|
boards,
|
||||||
boards_change_sender,
|
boards_change_sender,
|
||||||
boards_change_receiver,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn initialize(&self) {
|
async fn initialize(&self) {
|
||||||
let shared_self = Arc::new(self.clone());
|
let shared_self = Arc::new(self.clone());
|
||||||
|
|
||||||
|
let shared_self_for_search = shared_self.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
match shared_self.search_boards().await {
|
match shared_self_for_search.search_boards().await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("search_boards finished");
|
info!("search_boards finished");
|
||||||
}
|
}
|
||||||
@ -62,44 +61,63 @@ impl UdpRpc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let shared_self_for_check = shared_self.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
shared_self_for_check.check_boards().await;
|
||||||
|
});
|
||||||
|
|
||||||
|
// let shared_self_for_watch = shared_self.clone();
|
||||||
|
// tokio::spawn(async move {
|
||||||
|
// let mut rx = shared_self_for_watch.clone_boards_change_receiver().await;
|
||||||
|
|
||||||
|
// // let mut rx = sub_tx.subscribe();
|
||||||
|
// // drop(sub_tx);
|
||||||
|
// while rx.changed().await.is_ok() {
|
||||||
|
// let boards = rx.borrow().clone();
|
||||||
|
// info!("boards changed: {:?}", boards);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn search_boards(&self) -> anyhow::Result<()> {
|
async fn search_boards(&self) -> anyhow::Result<()> {
|
||||||
let service_type = "_ambient_light._udp.local.";
|
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 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
|
||||||
})?;
|
})?;
|
||||||
|
let sender = self.boards_change_sender.clone();
|
||||||
|
|
||||||
while let Ok(event) = receiver.recv() {
|
while let Ok(event) = receiver.recv() {
|
||||||
match event {
|
match event {
|
||||||
ServiceEvent::ServiceResolved(info) => {
|
ServiceEvent::ServiceResolved(info) => {
|
||||||
info!(
|
info!(
|
||||||
"Resolved a new service: {} host: {} port: {} IP: {:?} TXT properties: {:?}",
|
"Resolved a new service: {} host: {} port: {} IP: {:?} TXT properties: {:?}",
|
||||||
info.get_fullname(),
|
info.get_fullname(),
|
||||||
info.get_hostname(),
|
info.get_hostname(),
|
||||||
info.get_port(),
|
info.get_port(),
|
||||||
info.get_addresses(),
|
info.get_addresses(),
|
||||||
info.get_properties(),
|
info.get_properties(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let shared_self = shared_self.lock().await;
|
let mut boards = self.boards.write().await;
|
||||||
let mut boards = shared_self.boards.write().await;
|
|
||||||
|
|
||||||
let board = BoardInfo {
|
let board = BoardInfo::new(
|
||||||
name: info.get_fullname().to_string(),
|
info.get_fullname().to_string(),
|
||||||
address: info.get_addresses().iter().next().unwrap().clone(),
|
info.get_addresses().iter().next().unwrap().clone(),
|
||||||
port: info.get_port(),
|
info.get_port(),
|
||||||
};
|
);
|
||||||
|
|
||||||
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;
|
let tx_boards = boards.iter().cloned().collect();
|
||||||
sender.send(boards.clone())?;
|
drop(boards);
|
||||||
|
|
||||||
|
sender.send(tx_boards)?;
|
||||||
|
tokio::task::yield_now().await;
|
||||||
}
|
}
|
||||||
other_event => {
|
other_event => {
|
||||||
warn!("{:?}", &other_event);
|
warn!("{:?}", &other_event);
|
||||||
@ -110,32 +128,28 @@ impl UdpRpc {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn clone_boards_change_receiver(
|
pub fn subscribe_boards_change(&self) -> watch::Receiver<Vec<BoardInfo>> {
|
||||||
&self,
|
self.boards_change_sender.subscribe()
|
||||||
) -> 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> {
|
pub async fn get_boards(&self) -> Vec<BoardInfo> {
|
||||||
let boards = self.boards.read().await;
|
let boards: tokio::sync::RwLockReadGuard<HashSet<BoardInfo>> = self.boards.read().await;
|
||||||
boards.clone()
|
boards.iter().cloned().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_to_all(&self, buff: &Vec<u8>) -> anyhow::Result<()> {
|
pub async fn send_to_all(&self, buff: &Vec<u8>) -> anyhow::Result<()> {
|
||||||
let boards = self.get_boards().await;
|
let boards = self.get_boards().await;
|
||||||
let socket = self.socket.clone();
|
let socket = self.socket.clone();
|
||||||
|
|
||||||
let handlers = boards.into_iter()
|
let handlers = boards.into_iter().map(|board| {
|
||||||
.map(|board| {
|
|
||||||
let socket = socket.clone();
|
let socket = socket.clone();
|
||||||
let buff = buff.clone();
|
let buff = buff.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
match socket.send_to(&buff, (board.address, board.port)).await {
|
match socket.send_to(&buff, (board.address, board.port)).await {
|
||||||
Ok(_) => {},
|
Ok(_) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("failed to send to {}: {:?}", board.name, err);
|
error!("failed to send to {}: {:?}", board.host, err);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -144,4 +158,31 @@ impl UdpRpc {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn check_boards(&self) {
|
||||||
|
loop {
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
|
||||||
|
let mut boards = self.get_boards().await;
|
||||||
|
|
||||||
|
if boards.is_empty() {
|
||||||
|
info!("no boards found");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for board in &mut boards {
|
||||||
|
if let Err(err) = board.check().await {
|
||||||
|
error!("failed to check board {}: {:?}", board.host, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let board_change_sender = self.boards_change_sender.clone();
|
||||||
|
if let Err(err) = board_change_sender.send(boards) {
|
||||||
|
error!("failed to send board change: {:?}", err);
|
||||||
|
} else {
|
||||||
|
info!("send");
|
||||||
|
}
|
||||||
|
drop(board_change_sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import { BoardInfo } from '../../models/board-info.model';
|
|||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import { BoardInfoPanel } from './board-info-panel';
|
||||||
|
|
||||||
const logger = debug('app:components:info:board-index');
|
const logger = debug('app:components:info:board-index');
|
||||||
|
|
||||||
@ -28,19 +29,8 @@ export const BoardIndex: Component = () => {
|
|||||||
<ol class="grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 p-2 gap-2">
|
<ol class="grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 p-2 gap-2">
|
||||||
<For each={boards()}>
|
<For each={boards()}>
|
||||||
{(board, index) => (
|
{(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">
|
<li class="bg-slate-50 text-gray-800 relative border-2 border-slate-50 hover:border-sky-300 focus:border-sky-300 transition">
|
||||||
<dl class="flex">
|
<BoardInfoPanel board={board} />
|
||||||
<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">
|
<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}
|
#{index() + 1}
|
||||||
</span>
|
</span>
|
||||||
|
49
src/components/info/board-info-panel.tsx
Normal file
49
src/components/info/board-info-panel.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { Component, ParentComponent, createMemo } from 'solid-js';
|
||||||
|
import { BoardInfo } from '../../models/board-info.model';
|
||||||
|
|
||||||
|
type ItemProps = {
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Item: ParentComponent<ItemProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<dl class="flex">
|
||||||
|
<dt class="w-20">{props.label}</dt>
|
||||||
|
<dd class="flex-auto">{props.children}</dd>
|
||||||
|
</dl>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BoardInfoPanel: Component<{ board: BoardInfo }> = (props) => {
|
||||||
|
const ttl = createMemo(() => {
|
||||||
|
if (!props.board.is_online) {
|
||||||
|
return '--';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.board.ttl == null) {
|
||||||
|
return 'timeout';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span class="font-mono">{props.board.ttl.toFixed(0)}</span> ms
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section class="p-2 rounded shadow">
|
||||||
|
<Item label="Host">{props.board.host}</Item>
|
||||||
|
<Item label="Ip Addr">
|
||||||
|
<span class="font-mono">{props.board.address}</span>
|
||||||
|
</Item>
|
||||||
|
<Item label="Port">
|
||||||
|
<span class="font-mono">{props.board.port}</span>
|
||||||
|
</Item>
|
||||||
|
<Item label="Status">
|
||||||
|
<span class="font-mono">{props.board.is_online ? 'Online' : 'Offline'}</span>
|
||||||
|
</Item>
|
||||||
|
<Item label="TTL">{ttl()}</Item>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
@ -1,5 +1,8 @@
|
|||||||
export type BoardInfo = {
|
export type BoardInfo = {
|
||||||
name: string;
|
host: string;
|
||||||
address: string;
|
address: string;
|
||||||
port: number;
|
port: number;
|
||||||
|
ttl: number;
|
||||||
|
is_online: boolean;
|
||||||
|
checked_at: Date;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user