1use std::collections::VecDeque;
4
5use bytes::Bytes;
6use serde::{Deserialize, Serialize};
7
8use crate::bridge::SerialSignals;
9use crate::device::{ControlCommand, LogLevel, PeripheralSignal, WedgeScan};
10use crate::fault::{FaultId, FaultOptions};
11use crate::ids::{DeviceId, JobId};
12use crate::nfc::NfcEvent;
13use crate::time::Millis;
14
15pub const DEFAULT_LOG_CAPACITY: usize = 10_000;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
20#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
21#[serde(rename_all = "kebab-case")]
22pub enum Direction {
23 HostToDevice,
25 DeviceToHost,
27}
28
29#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
31#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
32#[serde(tag = "type", rename_all = "kebab-case")]
33pub enum EventKind {
34 Created {
36 kind: String,
38 },
39 BytesIn {
41 #[serde(rename = "dataHex", with = "crate::hex::serde_bytes")]
43 #[cfg_attr(feature = "ts", ts(rename = "dataHex", type = "string"))]
44 data: Bytes,
45 },
46 BytesOut {
48 #[serde(rename = "dataHex", with = "crate::hex::serde_bytes")]
50 #[cfg_attr(feature = "ts", ts(rename = "dataHex", type = "string"))]
51 data: Bytes,
52 },
53 Control {
55 command: ControlCommand,
57 },
58 #[serde(rename_all = "camelCase")]
60 Fault {
61 fault_id: FaultId,
63 active: bool,
65 #[cfg_attr(
67 feature = "ts",
68 ts(as = "std::collections::HashMap<String, serde_json::Value>")
69 )]
70 options: FaultOptions,
71 },
72 StateChanged {
74 state: serde_json::Value,
76 },
77 Connected,
79 Disconnected,
81 #[serde(rename_all = "camelCase")]
83 JobCompleted {
84 job_id: JobId,
86 },
87 Log {
89 level: LogLevel,
91 message: String,
93 },
94 Reset,
96 Signals {
98 signals: SerialSignals,
100 },
101 Peripheral {
103 from: DeviceId,
105 to: DeviceId,
107 signal: PeripheralSignal,
109 },
110 Wedge {
112 scan: WedgeScan,
114 },
115 Nfc {
117 event: NfcEvent,
119 },
120}
121
122impl EventKind {
123 pub fn name(&self) -> &'static str {
125 match self {
126 EventKind::Created { .. } => "created",
127 EventKind::BytesIn { .. } => "bytes-in",
128 EventKind::BytesOut { .. } => "bytes-out",
129 EventKind::Control { .. } => "control",
130 EventKind::Fault { .. } => "fault",
131 EventKind::StateChanged { .. } => "state-changed",
132 EventKind::Connected => "connected",
133 EventKind::Disconnected => "disconnected",
134 EventKind::JobCompleted { .. } => "job-completed",
135 EventKind::Log { .. } => "log",
136 EventKind::Reset => "reset",
137 EventKind::Signals { .. } => "signals",
138 EventKind::Peripheral { .. } => "peripheral",
139 EventKind::Wedge { .. } => "wedge",
140 EventKind::Nfc { .. } => "nfc",
141 }
142 }
143}
144
145#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
150#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
151#[serde(rename_all = "camelCase")]
152pub struct EventRecord {
153 pub seq: u64,
155 pub at: Millis,
157 pub device_id: DeviceId,
159 #[serde(flatten)]
161 #[cfg_attr(feature = "ts", ts(flatten))]
162 pub kind: EventKind,
163}
164
165impl EventRecord {
166 pub fn direction(&self) -> Option<Direction> {
169 match &self.kind {
170 EventKind::BytesIn { .. }
171 | EventKind::Control {
172 command: ControlCommand::NfcWrite { .. } | ControlCommand::NfcMakeReadOnly,
173 } => Some(Direction::HostToDevice),
174 EventKind::BytesOut { .. }
175 | EventKind::Wedge { .. }
176 | EventKind::Nfc {
177 event: NfcEvent::Reading(_) | NfcEvent::ReadError { .. },
178 } => Some(Direction::DeviceToHost),
179 _ => None,
180 }
181 }
182}
183
184#[derive(Clone, Debug)]
195pub struct EventLog {
196 capacity: usize,
197 records: VecDeque<EventRecord>,
198}
199
200impl EventLog {
201 pub fn new(capacity: usize) -> Self {
203 let capacity = capacity.max(1);
204 Self {
205 capacity,
206 records: VecDeque::with_capacity(capacity.min(1024)),
207 }
208 }
209
210 pub fn push(&mut self, record: EventRecord) {
212 if self.records.len() == self.capacity {
213 self.records.pop_front();
214 }
215 self.records.push_back(record);
216 }
217
218 pub fn iter(&self) -> impl DoubleEndedIterator<Item = &EventRecord> + ExactSizeIterator {
220 self.records.iter()
221 }
222
223 pub fn since(&self, since: u64) -> impl Iterator<Item = &EventRecord> {
225 let start = self.records.partition_point(|r| r.seq <= since);
227 self.records.range(start..)
228 }
229
230 pub fn len(&self) -> usize {
232 self.records.len()
233 }
234
235 pub fn is_empty(&self) -> bool {
237 self.records.is_empty()
238 }
239
240 pub fn capacity(&self) -> usize {
242 self.capacity
243 }
244
245 pub fn clear(&mut self) {
247 self.records.clear();
248 }
249}