Bevy+Ratutui powered Monitoring of Pico-Strike devices
at main 199 lines 4.9 kB view raw
1use core::net::IpAddr; 2 3use bevy::{ 4 app::{Plugin, Update}, 5 ecs::{ 6 component::Component, 7 entity::Entity, 8 lifecycle::HookContext, 9 name::Name, 10 resource::Resource, 11 system::{Commands, Res, ResMut}, 12 world::DeferredWorld, 13 }, 14 prelude::Deref, 15}; 16use rapidhash::RapidHashSet; 17use ratatui::{ 18 style::{Color, Stylize}, 19 text::{Line, Span, Text, ToSpan}, 20 widgets::{Block, Padding, Paragraph, Widget}, 21}; 22 23use crate::net::DiscoverResponse; 24 25#[derive(Debug, Resource, Default)] 26pub struct UniqueDevices(pub RapidHashSet<DeviceSocket>); 27 28#[derive(Debug, Component)] 29pub struct Device; 30 31#[derive(Debug, Clone, Component, Hash, PartialEq, Eq)] 32#[component(immutable, on_remove = on_remove_device)] 33pub struct DeviceSocket { 34 pub address: String, 35 pub port: u16, 36 pub ip: IpAddr, 37} 38 39#[derive(Debug, Component, Default, Clone, Copy)] 40pub struct DeviceDetector { 41 pub blip_threshold: u16, 42 pub blip_size: usize, 43} 44 45impl Widget for DeviceDetector { 46 fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) 47 where 48 Self: Sized, 49 { 50 let detector_lines = [ 51 Line::from_iter([Span::raw("Threshold: "), self.blip_threshold.to_span()]), 52 Line::from_iter([Span::raw("Blip Size: "), self.blip_size.to_span()]), 53 ]; 54 55 let detector_info = Paragraph::new(Text::from_iter(detector_lines)) 56 .block( 57 Block::bordered() 58 .title("Settings") 59 .padding(Padding::horizontal(1)) 60 .border_style(Color::LightGreen), 61 ) 62 .white(); 63 64 detector_info.render(area, buf); 65 } 66} 67 68fn on_remove_device(mut world: DeferredWorld, context: HookContext) { 69 let component = world 70 .entity(context.entity) 71 .get::<DeviceSocket>() 72 .cloned() 73 .unwrap(); 74 let mut devices = world.resource_mut::<UniqueDevices>(); 75 76 devices.0.remove(&component); 77} 78 79#[derive(Debug, Component)] 80#[component(immutable)] 81pub struct StormLevel(pub u16); 82 83#[derive(Debug, Component, Deref)] 84#[component(immutable)] 85pub struct Timestamp(pub jiff::Timestamp); 86 87impl Timestamp { 88 pub fn from_microseconds(stamp: i64) -> Result<Self, jiff::Error> { 89 jiff::Timestamp::from_microsecond(stamp).map(Self) 90 } 91} 92 93#[derive(Debug, Component, Deref)] 94#[component(immutable)] 95pub struct StormSignal(Vec<i16>); 96 97impl StormSignal { 98 pub fn new(value: Vec<i16>) -> Self { 99 Self(value) 100 } 101} 102 103#[derive(Debug, Component, Deref)] 104#[component(immutable)] 105pub struct SignalAverage(pub u16); 106 107impl SignalAverage { 108 pub fn new(value: u16) -> Self { 109 Self(value) 110 } 111} 112 113#[derive(Debug, Component, Deref)] 114#[component(immutable)] 115pub struct SignalPeaks(Vec<(usize, u16)>); 116 117impl SignalPeaks { 118 pub fn new(value: Vec<(usize, u16)>) -> Self { 119 Self(value) 120 } 121} 122 123#[derive(Debug, Component)] 124#[relationship(relationship_target = Signals)] 125pub struct SignalSource(pub Entity); 126 127#[derive(Component, Debug, Deref)] 128#[relationship_target(relationship = SignalSource, linked_spawn)] 129pub struct Signals { 130 signals: Vec<Entity>, 131} 132 133#[derive(Debug, Component)] 134#[relationship(relationship_target = StormLevels)] 135pub struct StormSource(pub Entity); 136 137#[derive(Component, Debug, Deref)] 138#[relationship_target(relationship = StormSource, linked_spawn)] 139pub struct StormLevels { 140 levels: Vec<Entity>, 141} 142 143#[derive(Debug)] 144pub enum ConnectionState { 145 Disconnected, 146 Connecting, 147 Connected, 148} 149 150impl core::fmt::Display for ConnectionState { 151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 152 match self { 153 Self::Disconnected => write!(f, "Disconnected"), 154 Self::Connecting => write!(f, "Connecting"), 155 Self::Connected => write!(f, "Connected"), 156 } 157 } 158} 159 160#[derive(Debug, Resource)] 161pub struct ConnectedDevice { 162 pub device: Entity, 163 pub connection_state: ConnectionState, 164} 165 166fn register_devices( 167 incoming: Res<DiscoverResponse>, 168 mut unique: ResMut<UniqueDevices>, 169 mut commands: Commands, 170) { 171 let mut devices = Vec::new(); 172 173 while let Ok(discovered) = incoming.0.try_recv() { 174 let device_addr = DeviceSocket { 175 address: discovered.address, 176 port: discovered.port, 177 ip: discovered.ip, 178 }; 179 180 if !unique.0.contains(&device_addr) { 181 unique.0.insert(device_addr.clone()); 182 devices.push((Device, Name::new(discovered.host), device_addr)); 183 } 184 } 185 186 if !devices.is_empty() { 187 commands.spawn_batch(devices); 188 } 189} 190 191#[derive(Debug)] 192pub struct DevicePlugin; 193 194impl Plugin for DevicePlugin { 195 fn build(&self, app: &mut bevy::app::App) { 196 app.init_resource::<UniqueDevices>() 197 .add_systems(Update, register_devices); 198 } 199}