use std::collections::{BTreeMap, VecDeque};
use std::fmt;
use serde::{Deserialize, Serialize};
use crate::device::{
ConfigError, ControlCommand, Device, DeviceEvent, DevicePlugin, Job, LogEntry, LogLevel,
PeripheralSignal,
};
use crate::fault::{FaultId, FaultOptions};
use crate::ids::DeviceId;
use crate::log::{EventKind, EventLog, EventRecord, DEFAULT_LOG_CAPACITY};
use crate::nfc::NfcEvent;
use crate::time::{Clock, Millis};
pub const DEFAULT_JOB_CAPACITY: usize = 1_000;
pub const MAX_PERIPHERAL_DEPTH: u8 = 8;
pub const DEFAULT_BUS_CAPACITY: usize = 10_000;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RegistryConfig {
pub log_capacity: usize,
pub job_capacity: usize,
pub bus_capacity: usize,
}
impl Default for RegistryConfig {
fn default() -> Self {
Self {
log_capacity: DEFAULT_LOG_CAPACITY,
job_capacity: DEFAULT_JOB_CAPACITY,
bus_capacity: DEFAULT_BUS_CAPACITY,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
#[serde(tag = "type")]
pub enum BusEvent {
#[serde(rename = "device:added", rename_all = "camelCase")]
Added {
device_id: DeviceId,
kind: String,
at: Millis,
},
#[serde(rename = "device:removed", rename_all = "camelCase")]
Removed {
device_id: DeviceId,
at: Millis,
},
#[serde(rename = "device:state", rename_all = "camelCase")]
State {
device_id: DeviceId,
state: serde_json::Value,
at: Millis,
},
#[serde(rename = "device:output", rename_all = "camelCase")]
Output {
device_id: DeviceId,
job: Job,
},
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
pub struct ActiveFault {
pub id: FaultId,
#[cfg_attr(
feature = "ts",
ts(as = "std::collections::HashMap<String, serde_json::Value>")
)]
pub options: FaultOptions,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
#[serde(rename_all = "camelCase")]
pub struct DeviceSummary {
pub id: DeviceId,
pub kind: String,
pub state: serde_json::Value,
pub faults: Vec<ActiveFault>,
pub supported_faults: Vec<FaultId>,
pub attached_to: Option<DeviceId>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RegistryError {
DuplicateKind(String),
UnknownKind(String),
DuplicateId(DeviceId),
NotFound(DeviceId),
InvalidConfig(ConfigError),
UnsupportedFault {
device: DeviceId,
fault: FaultId,
},
}
impl RegistryError {
pub fn code(&self) -> &'static str {
match self {
RegistryError::DuplicateKind(_) => "duplicate_kind",
RegistryError::UnknownKind(_) => "unknown_kind",
RegistryError::DuplicateId(_) => "duplicate_id",
RegistryError::NotFound(_) => "not_found",
RegistryError::InvalidConfig(_) => "invalid_config",
RegistryError::UnsupportedFault { .. } => "unsupported_fault",
}
}
}
impl fmt::Display for RegistryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RegistryError::DuplicateKind(kind) => {
write!(f, "a plugin for kind `{kind}` is already registered")
}
RegistryError::UnknownKind(kind) => write!(f, "no plugin for device kind `{kind}`"),
RegistryError::DuplicateId(id) => write!(f, "device `{id}` already exists"),
RegistryError::NotFound(id) => write!(f, "device `{id}` not found"),
RegistryError::InvalidConfig(err) => write!(f, "invalid device config: {err}"),
RegistryError::UnsupportedFault { device, fault } => {
write!(f, "device `{device}` does not support fault `{fault}`")
}
}
}
}
impl std::error::Error for RegistryError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
RegistryError::InvalidConfig(err) => Some(err),
_ => None,
}
}
}
struct Slot {
kind: &'static str,
device: Box<dyn Device>,
faults: BTreeMap<FaultId, FaultOptions>,
log: EventLog,
jobs: VecDeque<Job>,
}
pub struct Registry {
clock: Box<dyn Clock>,
config: RegistryConfig,
plugins: BTreeMap<&'static str, Box<dyn DevicePlugin>>,
devices: BTreeMap<DeviceId, Slot>,
bus: VecDeque<BusEvent>,
last_now: Millis,
next_seq: u64,
}
impl Registry {
pub fn new(clock: impl Clock + 'static) -> Self {
Self::with_config(clock, RegistryConfig::default())
}
pub fn with_config(clock: impl Clock + 'static, config: RegistryConfig) -> Self {
Self {
clock: Box::new(clock),
config,
plugins: BTreeMap::new(),
devices: BTreeMap::new(),
bus: VecDeque::new(),
last_now: Millis::ZERO,
next_seq: 1,
}
}
pub fn config(&self) -> RegistryConfig {
self.config
}
pub fn now(&mut self) -> Millis {
let now = self.clock.now().max(self.last_now);
self.last_now = now;
now
}
pub fn register(&mut self, plugin: impl DevicePlugin) -> Result<(), RegistryError> {
self.register_boxed(Box::new(plugin))
}
pub fn register_boxed(&mut self, plugin: Box<dyn DevicePlugin>) -> Result<(), RegistryError> {
let kind = plugin.kind();
if self.plugins.contains_key(kind) {
return Err(RegistryError::DuplicateKind(kind.to_owned()));
}
self.plugins.insert(kind, plugin);
Ok(())
}
pub fn plugin(&self, kind: &str) -> Option<&dyn DevicePlugin> {
self.plugins.get(kind).map(|p| p.as_ref())
}
pub fn plugin_kinds(&self) -> impl Iterator<Item = &'static str> + '_ {
self.plugins.keys().copied()
}
pub fn create(
&mut self,
kind: &str,
id: Option<DeviceId>,
config: serde_json::Value,
) -> Result<DeviceId, RegistryError> {
let plugin = self
.plugins
.get(kind)
.ok_or_else(|| RegistryError::UnknownKind(kind.to_owned()))?;
let kind = plugin.kind();
let id = match id {
Some(id) if self.devices.contains_key(&id) => {
return Err(RegistryError::DuplicateId(id))
}
Some(id) => id,
None => self.generate_id(kind),
};
let device = plugin
.create(id.clone(), config)
.map_err(RegistryError::InvalidConfig)?;
let slot = Slot {
kind,
device,
faults: BTreeMap::new(),
log: EventLog::new(self.config.log_capacity),
jobs: VecDeque::new(),
};
self.devices.insert(id.clone(), slot);
let at = self.now();
self.record(
&id,
at,
EventKind::Created {
kind: kind.to_owned(),
},
);
self.publish(BusEvent::Added {
device_id: id.clone(),
kind: kind.to_owned(),
at,
});
Ok(id)
}
fn generate_id(&self, kind: &str) -> DeviceId {
(1u64..)
.filter_map(|n| format!("{kind}-{n}").parse::<DeviceId>().ok())
.find(|id| !self.devices.contains_key(id))
.unwrap_or_else(|| self.fallback_id())
}
fn fallback_id(&self) -> DeviceId {
let mut n = self.devices.len() as u64 + 1;
loop {
if let Ok(id) = format!("device-{n}").parse::<DeviceId>() {
if !self.devices.contains_key(&id) {
return id;
}
}
n += 1;
}
}
pub fn remove(&mut self, id: &DeviceId) -> Result<(), RegistryError> {
self.devices
.remove(id)
.ok_or_else(|| RegistryError::NotFound(id.clone()))?;
let at = self.now();
self.publish(BusEvent::Removed {
device_id: id.clone(),
at,
});
Ok(())
}
pub fn contains(&self, id: &DeviceId) -> bool {
self.devices.contains_key(id)
}
pub fn get(&self, id: &DeviceId) -> Option<&dyn Device> {
self.devices.get(id).map(|slot| slot.device.as_ref())
}
pub fn summary(&self, id: &DeviceId) -> Option<DeviceSummary> {
self.devices.get(id).map(|slot| summarize(id, slot))
}
pub fn list(&self) -> Vec<DeviceSummary> {
self.devices
.iter()
.map(|(id, slot)| summarize(id, slot))
.collect()
}
pub fn host_bytes(
&mut self,
id: &DeviceId,
data: &[u8],
) -> Result<Vec<DeviceEvent>, RegistryError> {
let now = self.now();
self.slot(id)?;
self.record(
id,
now,
EventKind::BytesIn {
data: bytes::Bytes::copy_from_slice(data),
},
);
let events = self.slot_mut(id)?.device.on_host_bytes(data, now);
self.absorb(id, now, &events);
Ok(events)
}
pub fn host_signals(
&mut self,
id: &DeviceId,
signals: crate::bridge::SerialSignals,
) -> Result<(), RegistryError> {
let now = self.now();
self.slot(id)?;
self.record(id, now, EventKind::Signals { signals });
Ok(())
}
pub fn control(
&mut self,
id: &DeviceId,
command: ControlCommand,
) -> Result<Vec<DeviceEvent>, RegistryError> {
let now = self.now();
self.slot(id)?;
self.record(
id,
now,
EventKind::Control {
command: command.clone(),
},
);
let events = self.slot_mut(id)?.device.on_control(command, now);
self.absorb(id, now, &events);
Ok(events)
}
pub fn log(&mut self, id: &DeviceId, entry: LogEntry) -> Result<(), RegistryError> {
let now = self.now();
self.slot(id)?;
let kind = EventKind::Log {
level: entry.level,
message: entry.message,
};
self.record(id, now, kind);
Ok(())
}
pub fn set_fault(
&mut self,
id: &DeviceId,
fault: FaultId,
active: bool,
options: FaultOptions,
) -> Result<Vec<DeviceEvent>, RegistryError> {
if !self.slot(id)?.device.faults().contains(&fault) {
return Err(RegistryError::UnsupportedFault {
device: id.clone(),
fault,
});
}
let now = self.now();
let slot = self.slot_mut(id)?;
if active {
slot.faults.insert(fault.clone(), options.clone());
} else {
slot.faults.remove(&fault);
}
let events = slot
.device
.set_fault(fault.clone(), active, options.clone(), now);
self.record(
id,
now,
EventKind::Fault {
fault_id: fault,
active,
options,
},
);
self.absorb_with_state(id, now, events)
}
pub fn reset(&mut self, id: &DeviceId) -> Result<Vec<DeviceEvent>, RegistryError> {
let now = self.now();
let slot = self.slot_mut(id)?;
slot.faults.clear();
slot.jobs.clear();
let events = slot.device.reset(now);
self.record(id, now, EventKind::Reset);
self.absorb_with_state(id, now, events)
}
fn absorb_with_state(
&mut self,
id: &DeviceId,
now: Millis,
events: Vec<DeviceEvent>,
) -> Result<Vec<DeviceEvent>, RegistryError> {
if events.iter().any(is_state_change) {
self.absorb(id, now, &events);
} else {
let mut absorbed = events.clone();
absorbed.push(DeviceEvent::StateChanged);
self.absorb(id, now, &absorbed);
}
Ok(events)
}
pub fn tick(&mut self) -> Vec<(DeviceId, DeviceEvent)> {
let now = self.now();
let due: Vec<DeviceId> = self
.devices
.iter()
.filter(|(_, slot)| slot.device.next_deadline().is_some_and(|d| d <= now))
.map(|(id, _)| id.clone())
.collect();
let mut out = Vec::new();
for id in due {
let Some(slot) = self.devices.get_mut(&id) else {
continue;
};
let events = slot.device.on_tick(now);
self.absorb(&id, now, &events);
out.extend(events.into_iter().map(|e| (id.clone(), e)));
}
out
}
pub fn next_deadline(&self) -> Option<Millis> {
self.devices
.values()
.filter_map(|slot| slot.device.next_deadline())
.min()
}
pub fn events(
&self,
id: &DeviceId,
since: Option<u64>,
) -> Result<Vec<&EventRecord>, RegistryError> {
let slot = self.slot(id)?;
Ok(slot.log.since(since.unwrap_or(0)).collect())
}
pub fn jobs(&self, id: &DeviceId) -> Result<impl Iterator<Item = &Job> + '_, RegistryError> {
Ok(self.slot(id)?.jobs.iter().rev())
}
pub fn last_job(&self, id: &DeviceId) -> Result<Option<&Job>, RegistryError> {
Ok(self.slot(id)?.jobs.back())
}
pub fn drain_bus(&mut self) -> Vec<BusEvent> {
self.bus.drain(..).collect()
}
fn slot(&self, id: &DeviceId) -> Result<&Slot, RegistryError> {
self.devices
.get(id)
.ok_or_else(|| RegistryError::NotFound(id.clone()))
}
fn slot_mut(&mut self, id: &DeviceId) -> Result<&mut Slot, RegistryError> {
self.devices
.get_mut(id)
.ok_or_else(|| RegistryError::NotFound(id.clone()))
}
fn record(&mut self, id: &DeviceId, at: Millis, kind: EventKind) {
let seq = self.next_seq;
if let Some(slot) = self.devices.get_mut(id) {
self.next_seq += 1;
slot.log.push(EventRecord {
seq,
at,
device_id: id.clone(),
kind,
});
}
}
fn publish(&mut self, event: BusEvent) {
if self.bus.len() >= self.config.bus_capacity.max(1) {
self.bus.pop_front();
}
self.bus.push_back(event);
}
fn absorb(&mut self, id: &DeviceId, now: Millis, events: &[DeviceEvent]) {
self.absorb_at_depth(id, now, events, 0);
}
fn absorb_at_depth(&mut self, id: &DeviceId, now: Millis, events: &[DeviceEvent], depth: u8) {
let mut state_changed = false;
let mut signals = Vec::new();
let mut spent = Vec::new();
for event in events {
match event {
DeviceEvent::ToHost(data) => {
self.record(id, now, EventKind::BytesOut { data: data.clone() });
}
DeviceEvent::StateChanged => state_changed = true,
DeviceEvent::JobCompleted(job) => {
self.record(id, now, EventKind::JobCompleted { job_id: job.id });
let capacity = self.config.job_capacity.max(1);
if let Some(slot) = self.devices.get_mut(id) {
if slot.jobs.len() >= capacity {
slot.jobs.pop_front();
}
slot.jobs.push_back(job.clone());
}
self.publish(BusEvent::Output {
device_id: id.clone(),
job: job.clone(),
});
}
DeviceEvent::Connected => {
self.record(id, now, EventKind::Connected);
state_changed = true;
}
DeviceEvent::Disconnected => {
self.record(id, now, EventKind::Disconnected);
state_changed = true;
}
DeviceEvent::Log(entry) => {
let kind = EventKind::Log {
level: entry.level,
message: entry.message.clone(),
};
self.record(id, now, kind);
}
DeviceEvent::Peripheral(signal) => signals.push(signal.clone()),
DeviceEvent::Wedge(scan) => {
self.record(id, now, EventKind::Wedge { scan: scan.clone() });
}
DeviceEvent::Nfc(event) => {
if matches!(event, NfcEvent::ReadError { .. }) {
spent.push(FaultId::READ_ERROR);
}
self.record(
id,
now,
EventKind::Nfc {
event: event.clone(),
},
);
}
}
}
for fault in spent {
state_changed |= self.spend_fault(id, now, fault);
}
if state_changed {
if let Some(state) = self.devices.get(id).map(|slot| slot.device.state()) {
self.record(
id,
now,
EventKind::StateChanged {
state: state.clone(),
},
);
self.publish(BusEvent::State {
device_id: id.clone(),
state,
at: now,
});
}
}
for signal in signals {
self.deliver_peripheral(id, now, signal, depth);
}
}
fn spend_fault(&mut self, id: &DeviceId, now: Millis, fault: FaultId) -> bool {
let Some(slot) = self.devices.get_mut(id) else {
return false;
};
if slot.faults.remove(&fault).is_none() {
return false;
}
let _ = slot
.device
.set_fault(fault.clone(), false, FaultOptions::new(), now);
self.record(
id,
now,
EventKind::Fault {
fault_id: fault,
active: false,
options: FaultOptions::new(),
},
);
true
}
fn deliver_peripheral(
&mut self,
from: &DeviceId,
now: Millis,
signal: PeripheralSignal,
depth: u8,
) {
if depth >= MAX_PERIPHERAL_DEPTH {
let message = format!(
"peripheral signal dropped after {MAX_PERIPHERAL_DEPTH} hops (devices signalling in a loop?)"
);
self.record(
from,
now,
EventKind::Log {
level: LogLevel::Warn,
message,
},
);
return;
}
let host = self
.devices
.get(from)
.and_then(|slot| slot.device.attached_to());
let mut targets: Vec<DeviceId> = host.into_iter().collect();
targets.extend(
self.devices
.iter()
.filter(|(id, slot)| {
*id != from && slot.device.attached_to().as_ref() == Some(from)
})
.map(|(id, _)| id.clone()),
);
targets.retain(|t| t != from);
targets.dedup();
for to in targets {
let Some(slot) = self.devices.get_mut(&to) else {
continue;
};
let events = slot.device.on_peripheral(from, signal.clone(), now);
let kind = EventKind::Peripheral {
from: from.clone(),
to: to.clone(),
signal: signal.clone(),
};
self.record(from, now, kind.clone());
self.record(&to, now, kind);
self.absorb_at_depth(&to, now, &events, depth + 1);
}
}
}
fn is_state_change(event: &DeviceEvent) -> bool {
matches!(
event,
DeviceEvent::StateChanged | DeviceEvent::Connected | DeviceEvent::Disconnected
)
}
fn summarize(id: &DeviceId, slot: &Slot) -> DeviceSummary {
DeviceSummary {
id: id.clone(),
kind: slot.kind.to_owned(),
state: slot.device.state(),
faults: slot
.faults
.iter()
.map(|(id, options)| ActiveFault {
id: id.clone(),
options: options.clone(),
})
.collect(),
supported_faults: slot.device.faults(),
attached_to: slot.device.attached_to(),
}
}
impl fmt::Debug for Registry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Registry")
.field("config", &self.config)
.field("plugins", &self.plugins.keys().collect::<Vec<_>>())
.field("devices", &self.devices.keys().collect::<Vec<_>>())
.field("bus_len", &self.bus.len())
.finish_non_exhaustive()
}
}