use std::collections::VecDeque;
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use crate::bridge::SerialSignals;
use crate::device::{ControlCommand, LogLevel, PeripheralSignal, WedgeScan};
use crate::fault::{FaultId, FaultOptions};
use crate::ids::{DeviceId, JobId};
use crate::nfc::NfcEvent;
use crate::time::Millis;
pub const DEFAULT_LOG_CAPACITY: usize = 10_000;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
#[serde(rename_all = "kebab-case")]
pub enum Direction {
HostToDevice,
DeviceToHost,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum EventKind {
Created {
kind: String,
},
BytesIn {
#[serde(rename = "dataHex", with = "crate::hex::serde_bytes")]
#[cfg_attr(feature = "ts", ts(rename = "dataHex", type = "string"))]
data: Bytes,
},
BytesOut {
#[serde(rename = "dataHex", with = "crate::hex::serde_bytes")]
#[cfg_attr(feature = "ts", ts(rename = "dataHex", type = "string"))]
data: Bytes,
},
Control {
command: ControlCommand,
},
#[serde(rename_all = "camelCase")]
Fault {
fault_id: FaultId,
active: bool,
#[cfg_attr(
feature = "ts",
ts(as = "std::collections::HashMap<String, serde_json::Value>")
)]
options: FaultOptions,
},
StateChanged {
state: serde_json::Value,
},
Connected,
Disconnected,
#[serde(rename_all = "camelCase")]
JobCompleted {
job_id: JobId,
},
Log {
level: LogLevel,
message: String,
},
Reset,
Signals {
signals: SerialSignals,
},
Peripheral {
from: DeviceId,
to: DeviceId,
signal: PeripheralSignal,
},
Wedge {
scan: WedgeScan,
},
Nfc {
event: NfcEvent,
},
}
impl EventKind {
pub fn name(&self) -> &'static str {
match self {
EventKind::Created { .. } => "created",
EventKind::BytesIn { .. } => "bytes-in",
EventKind::BytesOut { .. } => "bytes-out",
EventKind::Control { .. } => "control",
EventKind::Fault { .. } => "fault",
EventKind::StateChanged { .. } => "state-changed",
EventKind::Connected => "connected",
EventKind::Disconnected => "disconnected",
EventKind::JobCompleted { .. } => "job-completed",
EventKind::Log { .. } => "log",
EventKind::Reset => "reset",
EventKind::Signals { .. } => "signals",
EventKind::Peripheral { .. } => "peripheral",
EventKind::Wedge { .. } => "wedge",
EventKind::Nfc { .. } => "nfc",
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
#[serde(rename_all = "camelCase")]
pub struct EventRecord {
pub seq: u64,
pub at: Millis,
pub device_id: DeviceId,
#[serde(flatten)]
#[cfg_attr(feature = "ts", ts(flatten))]
pub kind: EventKind,
}
impl EventRecord {
pub fn direction(&self) -> Option<Direction> {
match &self.kind {
EventKind::BytesIn { .. }
| EventKind::Control {
command: ControlCommand::NfcWrite { .. } | ControlCommand::NfcMakeReadOnly,
} => Some(Direction::HostToDevice),
EventKind::BytesOut { .. }
| EventKind::Wedge { .. }
| EventKind::Nfc {
event: NfcEvent::Reading(_) | NfcEvent::ReadError { .. },
} => Some(Direction::DeviceToHost),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct EventLog {
capacity: usize,
records: VecDeque<EventRecord>,
}
impl EventLog {
pub fn new(capacity: usize) -> Self {
let capacity = capacity.max(1);
Self {
capacity,
records: VecDeque::with_capacity(capacity.min(1024)),
}
}
pub fn push(&mut self, record: EventRecord) {
if self.records.len() == self.capacity {
self.records.pop_front();
}
self.records.push_back(record);
}
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &EventRecord> + ExactSizeIterator {
self.records.iter()
}
pub fn since(&self, since: u64) -> impl Iterator<Item = &EventRecord> {
let start = self.records.partition_point(|r| r.seq <= since);
self.records.range(start..)
}
pub fn len(&self) -> usize {
self.records.len()
}
pub fn is_empty(&self) -> bool {
self.records.is_empty()
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn clear(&mut self) {
self.records.clear();
}
}