Skip to main content

mabi_runtime/
device.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use async_trait::async_trait;
5use parking_lot::RwLock;
6
7use mabi_core::device::{DeviceHandle, DeviceInfo, DeviceState};
8use mabi_core::types::{DataPoint, DataPointDef};
9use mabi_core::value::Value;
10use mabi_core::Result as CoreResult;
11
12/// Shared protocol-agnostic device surface for controllers.
13#[async_trait]
14pub trait DevicePort: Send + Sync {
15    /// Returns the current device metadata snapshot.
16    fn info(&self) -> DeviceInfo;
17
18    /// Returns the stable device identifier.
19    fn id(&self) -> String {
20        self.info().id
21    }
22
23    /// Returns the current device state.
24    fn state(&self) -> DeviceState {
25        self.info().state
26    }
27
28    /// Starts the device port if supported.
29    async fn start(&self) -> CoreResult<()>;
30
31    /// Stops the device port if supported.
32    async fn stop(&self) -> CoreResult<()>;
33
34    /// Reads a point value.
35    async fn read(&self, point_id: &str) -> CoreResult<DataPoint>;
36
37    /// Writes a point value.
38    async fn write(&self, point_id: &str, value: Value) -> CoreResult<()>;
39
40    /// Returns the point definitions exposed by the device, if available.
41    fn point_definitions(&self) -> Vec<DataPointDef> {
42        Vec::new()
43    }
44}
45
46/// Shared trait-object type for device controllers.
47pub type DynDevicePort = Arc<dyn DevicePort>;
48
49/// Adapter from the legacy `mabi-core` device handle into the shared runtime surface.
50#[derive(Clone)]
51pub struct CoreDevicePort {
52    handle: DeviceHandle,
53}
54
55impl CoreDevicePort {
56    /// Wraps an existing device handle.
57    pub fn new(handle: DeviceHandle) -> Self {
58        Self { handle }
59    }
60
61    /// Returns the wrapped handle.
62    pub fn handle(&self) -> &DeviceHandle {
63        &self.handle
64    }
65
66    /// Converts the adapter into a shared device port.
67    pub fn into_shared(self) -> DynDevicePort {
68        Arc::new(self)
69    }
70}
71
72impl From<DeviceHandle> for CoreDevicePort {
73    fn from(handle: DeviceHandle) -> Self {
74        Self::new(handle)
75    }
76}
77
78#[async_trait]
79impl DevicePort for CoreDevicePort {
80    fn info(&self) -> DeviceInfo {
81        self.handle.info()
82    }
83
84    async fn start(&self) -> CoreResult<()> {
85        self.handle.start().await
86    }
87
88    async fn stop(&self) -> CoreResult<()> {
89        self.handle.stop().await
90    }
91
92    async fn read(&self, point_id: &str) -> CoreResult<DataPoint> {
93        self.handle.read(point_id).await
94    }
95
96    async fn write(&self, point_id: &str, value: Value) -> CoreResult<()> {
97        self.handle.write(point_id, value).await
98    }
99}
100
101/// Shared device registry used by controllers such as scenarios and chaos layers.
102#[derive(Clone, Default)]
103pub struct DeviceRegistry {
104    inner: Arc<RwLock<HashMap<String, DynDevicePort>>>,
105}
106
107impl DeviceRegistry {
108    /// Creates an empty registry.
109    pub fn new() -> Self {
110        Self::default()
111    }
112
113    /// Registers a device port under the provided identifier.
114    pub fn register(&self, device_id: impl Into<String>, port: DynDevicePort) {
115        self.inner.write().insert(device_id.into(), port);
116    }
117
118    /// Registers a legacy device handle via the runtime adapter.
119    pub fn register_handle(&self, device_id: impl Into<String>, handle: DeviceHandle) {
120        self.register(device_id, CoreDevicePort::new(handle).into_shared());
121    }
122
123    /// Returns a device port if present.
124    pub fn get(&self, device_id: &str) -> Option<DynDevicePort> {
125        self.inner.read().get(device_id).cloned()
126    }
127
128    /// Returns whether a device exists.
129    pub fn contains(&self, device_id: &str) -> bool {
130        self.inner.read().contains_key(device_id)
131    }
132
133    /// Returns all registered device identifiers.
134    pub fn device_ids(&self) -> Vec<String> {
135        self.inner.read().keys().cloned().collect()
136    }
137
138    /// Returns a snapshot of all registered device ports.
139    pub fn entries(&self) -> Vec<(String, DynDevicePort)> {
140        self.inner
141            .read()
142            .iter()
143            .map(|(device_id, port)| (device_id.clone(), Arc::clone(port)))
144            .collect()
145    }
146
147    /// Returns the device count.
148    pub fn len(&self) -> usize {
149        self.inner.read().len()
150    }
151
152    /// Returns true if the registry is empty.
153    pub fn is_empty(&self) -> bool {
154        self.inner.read().is_empty()
155    }
156
157    /// Removes a device by identifier.
158    pub fn remove(&self, device_id: &str) -> Option<DynDevicePort> {
159        self.inner.write().remove(device_id)
160    }
161
162    /// Clears the registry.
163    pub fn clear(&self) {
164        self.inner.write().clear();
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::DeviceRegistry;
171
172    #[test]
173    fn registry_starts_empty() {
174        let registry = DeviceRegistry::new();
175        assert!(registry.is_empty());
176        assert_eq!(registry.len(), 0);
177    }
178}