Bevy+Ratutui powered Monitoring of Pico-Strike devices
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}