tangled
alpha
login
or
join now
sachy.dev
/
striker
0
fork
atom
Bevy+Ratutui powered Monitoring of Pico-Strike devices
0
fork
atom
overview
issues
pulls
pipelines
Signals display, timestamp handling
sachy.dev
2 months ago
bcbe8eeb
051c4fd4
+134
-18
2 changed files
expand all
collapse all
unified
split
src
device.rs
views
monitoring.rs
+52
-2
src/device.rs
···
46
46
#[component(immutable)]
47
47
pub struct StormLevel(pub u16);
48
48
49
49
-
#[derive(Debug, Component)]
49
49
+
#[derive(Debug, Component, Deref)]
50
50
#[component(immutable)]
51
51
pub struct Timestamp(pub jiff::Timestamp);
52
52
53
53
+
impl Timestamp {
54
54
+
pub fn from_seconds(stamp: i64) -> Result<Self, jiff::Error> {
55
55
+
jiff::Timestamp::from_second(stamp).map(Self)
56
56
+
}
57
57
+
58
58
+
pub fn from_microseconds(stamp: i64) -> Result<Self, jiff::Error> {
59
59
+
jiff::Timestamp::from_microsecond(stamp).map(Self)
60
60
+
}
61
61
+
}
62
62
+
63
63
+
#[derive(Debug, Component, Deref)]
64
64
+
#[component(immutable)]
65
65
+
pub struct StormSignal(Vec<i16>);
66
66
+
67
67
+
impl StormSignal {
68
68
+
pub fn new(value: Vec<i16>) -> Self {
69
69
+
Self(value)
70
70
+
}
71
71
+
}
72
72
+
73
73
+
#[derive(Debug, Component, Deref)]
74
74
+
#[component(immutable)]
75
75
+
pub struct SignalAverage(pub u16);
76
76
+
77
77
+
impl SignalAverage {
78
78
+
pub fn new(value: u16) -> Self {
79
79
+
Self(value)
80
80
+
}
81
81
+
}
82
82
+
83
83
+
#[derive(Debug, Component, Deref)]
84
84
+
#[component(immutable)]
85
85
+
pub struct SignalPeaks(Vec<u16>);
86
86
+
87
87
+
impl SignalPeaks {
88
88
+
pub fn new(value: Vec<u16>) -> Self {
89
89
+
Self(value)
90
90
+
}
91
91
+
}
92
92
+
93
93
+
#[derive(Debug, Component)]
94
94
+
#[relationship(relationship_target = Signals)]
95
95
+
pub struct SignalSource(pub Entity);
96
96
+
97
97
+
#[derive(Component, Debug, Deref)]
98
98
+
#[relationship_target(relationship = SignalSource, linked_spawn)]
99
99
+
pub struct Signals {
100
100
+
signals: Vec<Entity>,
101
101
+
}
102
102
+
53
103
#[derive(Debug, Component)]
54
104
#[relationship(relationship_target = StormLevels)]
55
105
pub struct StormSource(pub Entity);
56
106
57
107
#[derive(Component, Debug, Deref)]
58
58
-
#[relationship_target(relationship = StormSource)]
108
108
+
#[relationship_target(relationship = StormSource, linked_spawn)]
59
109
pub struct StormLevels {
60
110
levels: Vec<Entity>,
61
111
}
+82
-16
src/views/monitoring.rs
···
18
18
use jiff::SignedDuration;
19
19
use ratatui::{
20
20
layout::{Constraint, HorizontalAlignment, Layout},
21
21
-
style::Color,
21
21
+
style::{Color, Stylize},
22
22
text::{Line, Span},
23
23
-
widgets::{Block, Padding, Paragraph},
23
23
+
widgets::{Axis, Block, Chart, Dataset, Padding, Paragraph},
24
24
};
25
25
use striker_proto::{StrikerResponse, Update};
26
26
27
27
use crate::{
28
28
device::{
29
29
-
ConnectedDevice, Device, DeviceSocket, StormLevel, StormLevels, StormSource, Timestamp,
29
29
+
ConnectedDevice, Device, DeviceSocket, SignalAverage, SignalPeaks, SignalSource, Signals,
30
30
+
StormLevel, StormLevels, StormSignal, StormSource, Timestamp,
30
31
},
31
32
messages::StrikeMessage,
32
33
net::{StrikeConnect, StrikeConnection, StrikeUpdates},
···
111
112
) -> Result {
112
113
let mut entity = commands.entity(connected.0);
113
114
114
114
-
while let Ok(StrikerResponse::Update(Update::Warning { timestamp, level })) =
115
115
-
updates.0.try_recv()
116
116
-
{
117
117
-
entity.with_related::<StormSource>((
118
118
-
Timestamp(jiff::Timestamp::from_second(timestamp)?),
119
119
-
StormLevel(level),
120
120
-
));
115
115
+
while let Ok(StrikerResponse::Update(update)) = updates.0.try_recv() {
116
116
+
match update {
117
117
+
Update::Warning { timestamp, level } => {
118
118
+
entity.with_related::<StormSource>((
119
119
+
Timestamp::from_seconds(timestamp)?,
120
120
+
StormLevel(level),
121
121
+
));
122
122
+
}
123
123
+
Update::Strike {
124
124
+
timestamp,
125
125
+
peaks,
126
126
+
samples,
127
127
+
average,
128
128
+
} => {
129
129
+
entity.with_related::<SignalSource>((
130
130
+
Timestamp::from_microseconds(timestamp)?,
131
131
+
SignalPeaks::new(peaks),
132
132
+
StormSignal::new(samples),
133
133
+
SignalAverage::new(average),
134
134
+
));
135
135
+
}
136
136
+
}
121
137
}
122
138
123
139
Ok(())
···
126
142
pub fn monitoring_view(
127
143
mut context: ResMut<RatatuiContext>,
128
144
connected: Res<ConnectedDevice>,
129
129
-
q_devices: Query<(&Name, Option<&StormLevels>), With<Device>>,
145
145
+
q_devices: Query<(&Name, Option<&StormLevels>, Option<&Signals>), With<Device>>,
130
146
q_levels: Query<(&Timestamp, &StormLevel)>,
147
147
+
q_signals: Query<(&Timestamp, &StormSignal)>,
131
148
) -> Result {
132
149
context.draw(|frame| {
133
150
let [top, mid, bottom] = Layout::vertical([
···
144
161
])
145
162
.areas(top);
146
163
147
147
-
let (device, children) = q_devices.get(connected.0).unwrap();
164
164
+
let (device, levels, signals) = q_devices.get(connected.0).unwrap();
148
165
149
149
-
let latest = children.and_then(|c| c.last().copied().and_then(|a| q_levels.get(a).ok()));
166
166
+
let latest_level = levels.and_then(|c| {
167
167
+
c.last()
168
168
+
.copied()
169
169
+
.and_then(|entity| q_levels.get(entity).ok())
170
170
+
});
171
171
+
let latest_signal = signals
172
172
+
.and_then(|s| s.last().copied())
173
173
+
.and_then(|entity| q_signals.get(entity).ok());
150
174
151
175
let name = Paragraph::new(device.as_str()).block(
152
176
Block::bordered()
···
156
180
.border_style(Color::LightGreen),
157
181
);
158
182
183
183
+
let timestamp_signal = latest_signal.map(|(t, _)| t);
184
184
+
let timestamp_level = latest_level.map(|(t, _)| t);
185
185
+
186
186
+
let timestamp = match (timestamp_signal, timestamp_level) {
187
187
+
(Some(signal), Some(level)) => signal
188
188
+
.duration_since(**level)
189
189
+
.is_positive()
190
190
+
.then_some(signal)
191
191
+
.or(Some(level)),
192
192
+
(Some(time), None) | (None, Some(time)) => Some(time),
193
193
+
_ => None,
194
194
+
};
195
195
+
159
196
let timestamp = Paragraph::new(Line::from_iter(
160
160
-
latest.map(|(t, _)| Span::from(format!("{}", t.0))),
197
197
+
timestamp.map(|t| Span::from(format!("{}", t.0))),
161
198
))
162
199
.block(
163
200
Block::bordered()
···
170
207
let warn_level = Paragraph::new(Line::from_iter(
171
208
Some(Span::raw("Level: "))
172
209
.into_iter()
173
173
-
.chain(latest.map(|(_, s)| Span::from(format!("{}", s.0)))),
210
210
+
.chain(latest_level.map(|(_, s)| Span::from(format!("{}", s.0)))),
174
211
))
175
212
.block(
176
213
Block::bordered()
···
180
217
.border_style(Color::LightGreen),
181
218
);
182
219
220
220
+
let data = latest_signal
221
221
+
.map(|(_, samples)| {
222
222
+
samples
223
223
+
.iter()
224
224
+
.enumerate()
225
225
+
.map(|(x, y)| (x as f64, *y as f64))
226
226
+
.collect()
227
227
+
})
228
228
+
.unwrap_or_else(Vec::new);
229
229
+
230
230
+
let y_bounds = data
231
231
+
.iter()
232
232
+
.map(|(_, y)| y.abs())
233
233
+
.reduce(f64::max)
234
234
+
.unwrap_or(0.0);
235
235
+
236
236
+
let datasets = vec![
237
237
+
Dataset::default()
238
238
+
.graph_type(ratatui::widgets::GraphType::Line)
239
239
+
.marker(ratatui::symbols::Marker::Braille)
240
240
+
.red()
241
241
+
.data(&data),
242
242
+
];
243
243
+
183
244
let block = Block::bordered()
184
245
.title("Details")
185
246
.padding(Padding::new(2, 2, 1, 1))
186
247
.border_style(Color::LightGreen);
187
248
249
249
+
let chart = Chart::new(datasets)
250
250
+
.x_axis(Axis::default().bounds([0.0, 512.0]))
251
251
+
.y_axis(Axis::default().bounds([-y_bounds, y_bounds]))
252
252
+
.block(block);
253
253
+
188
254
let help = Paragraph::new("Keys: 'q'/ESC Quit, BACKSPACE Return to Device select").block(
189
255
Block::bordered()
190
256
.padding(Padding::horizontal(2))
···
194
260
frame.render_widget(name, left);
195
261
frame.render_widget(timestamp, center);
196
262
frame.render_widget(warn_level, right);
197
197
-
frame.render_widget(block, mid);
263
263
+
frame.render_widget(chart, mid);
198
264
frame.render_widget(help, bottom);
199
265
})?;
200
266