matc/devman/
mod.rs

1//! Device manager for simplified Matter device interaction.
2//!
3//! Wraps certificate management, transport, controller, and a persistent device
4//! registry so that commissioning and connecting to devices is simpler
5//!
6//! # First-time setup
7//! ```no_run
8//! # use matc::devman::{DeviceManager, ManagerConfig};
9//! # #[tokio::main]
10//! # async fn main() -> anyhow::Result<()> {
11//! let config = ManagerConfig { fabric_id: 1000, controller_id: 100,
12//!                              local_address: "0.0.0.0:5555".into() };
13//! let dm = DeviceManager::create("./matter-data", config).await?;
14//! let conn = dm.commission("192.168.1.100:5540", 123456, 300, "kitchen light").await?;
15//! # Ok(())
16//! # }
17//! ```
18//!
19//! # Reconnecting later
20//! ```no_run
21//! # use matc::devman::DeviceManager;
22//! # #[tokio::main]
23//! # async fn main() -> anyhow::Result<()> {
24//! let dm = DeviceManager::load("./matter-data").await?;
25//! let conn = dm.connect_by_name("kitchen light").await?;
26//! # Ok(())
27//! # }
28//! ```
29
30mod config;
31mod device;
32
33pub use config::ManagerConfig;
34pub use device::Device;
35
36use std::sync::Arc;
37
38use anyhow::{Context, Result};
39
40use crate::{certmanager, controller, transport};
41
42pub struct DeviceManager {
43    base_path: String,
44    config: ManagerConfig,
45    transport: Arc<transport::Transport>,
46    controller: Arc<controller::Controller>,
47    certmanager: Arc<dyn certmanager::CertManager>,
48    registry: std::sync::Mutex<device::DeviceRegistry>,
49}
50
51impl DeviceManager {
52    /// First-time setup: creates directory structure, bootstraps CA,
53    /// creates controller user, and saves config.
54    pub async fn create(base_path: &str, config: ManagerConfig) -> Result<Self> {
55        std::fs::create_dir_all(base_path)
56            .context(format!("creating base directory {}", base_path))?;
57        config::save_config(base_path, &config)?;
58
59        let pem = config::pem_path(base_path);
60        let cm = certmanager::FileCertManager::new(config.fabric_id, &pem);
61        cm.bootstrap()?;
62        cm.create_user(config.controller_id)?;
63
64        let cm: Arc<dyn certmanager::CertManager> = certmanager::FileCertManager::load(&pem)?;
65        let transport = transport::Transport::new(&config.local_address).await?;
66        let controller = controller::Controller::new(&cm, &transport, config.fabric_id)?;
67        let registry = device::DeviceRegistry::load(&config::devices_path(base_path))?;
68
69        Ok(Self {
70            base_path: base_path.to_owned(),
71            config,
72            transport,
73            controller,
74            certmanager: cm,
75            registry: std::sync::Mutex::new(registry),
76        })
77    }
78
79    /// Load an existing device manager from a previously created base directory.
80    pub async fn load(base_path: &str) -> Result<Self> {
81        let config = config::load_config(base_path)?;
82        let pem = config::pem_path(base_path);
83        let cm: Arc<dyn certmanager::CertManager> = certmanager::FileCertManager::load(&pem)?;
84        let transport = transport::Transport::new(&config.local_address).await?;
85        let controller = controller::Controller::new(&cm, &transport, config.fabric_id)?;
86        let registry = device::DeviceRegistry::load(&config::devices_path(base_path))?;
87
88        Ok(Self {
89            base_path: base_path.to_owned(),
90            config,
91            transport,
92            controller,
93            certmanager: cm,
94            registry: std::sync::Mutex::new(registry),
95        })
96    }
97
98    /// Commission a device and save it to the registry.
99    /// Returns an authenticated connection ready for commands.
100    pub async fn commission(
101        &self,
102        address: &str,
103        pin: u32,
104        node_id: u64,
105        name: &str,
106    ) -> Result<controller::Connection> {
107        let conn = self.transport.create_connection(address).await;
108        let connection = self
109            .controller
110            .commission(&conn, pin, node_id, self.config.controller_id)
111            .await?;
112
113        let device = Device {
114            node_id,
115            address: address.to_owned(),
116            name: name.to_owned(),
117        };
118        self.registry
119            .lock()
120            .map_err(|e| anyhow::anyhow!("registry lock: {}", e))?
121            .add(device)?;
122
123        Ok(connection)
124    }
125
126    /// Connect to a previously commissioned device by node ID.
127    pub async fn connect(&self, node_id: u64) -> Result<controller::Connection> {
128        let address = {
129            let reg = self.registry.lock().map_err(|e| anyhow::anyhow!("registry lock: {}", e))?;
130            reg.get(node_id)
131                .context(format!("device {} not found in registry", node_id))?
132                .address
133                .clone()
134        };
135        let conn = self.transport.create_connection(&address).await;
136        self.controller
137            .auth_sigma(&conn, node_id, self.config.controller_id)
138            .await
139    }
140
141    /// Connect to a previously commissioned device by friendly name.
142    pub async fn connect_by_name(&self, name: &str) -> Result<controller::Connection> {
143        let (node_id, address) = {
144            let reg = self.registry.lock().map_err(|e| anyhow::anyhow!("registry lock: {}", e))?;
145            let dev = reg
146                .get_by_name(name)
147                .context(format!("device '{}' not found in registry", name))?;
148            (dev.node_id, dev.address.clone())
149        };
150        let conn = self.transport.create_connection(&address).await;
151        self.controller
152            .auth_sigma(&conn, node_id, self.config.controller_id)
153            .await
154    }
155
156    /// List all registered devices.
157    pub fn list_devices(&self) -> Result<Vec<Device>> {
158        let reg = self.registry.lock().map_err(|e| anyhow::anyhow!("registry lock: {}", e))?;
159        Ok(reg.list().to_vec())
160    }
161
162    /// Get a device by node ID.
163    pub fn get_device(&self, node_id: u64) -> Result<Option<Device>> {
164        let reg = self.registry.lock().map_err(|e| anyhow::anyhow!("registry lock: {}", e))?;
165        Ok(reg.get(node_id).cloned())
166    }
167
168    /// Get a device by friendly name.
169    pub fn get_device_by_name(&self, name: &str) -> Result<Option<Device>> {
170        let reg = self.registry.lock().map_err(|e| anyhow::anyhow!("registry lock: {}", e))?;
171        Ok(reg.get_by_name(name).cloned())
172    }
173
174    /// Remove a device from the registry.
175    pub fn remove_device(&self, node_id: u64) -> Result<()> {
176        self.registry
177            .lock()
178            .map_err(|e| anyhow::anyhow!("registry lock: {}", e))?
179            .remove(node_id)
180    }
181
182    /// Rename a device in the registry.
183    pub fn rename_device(&self, node_id: u64, name: &str) -> Result<()> {
184        self.registry
185            .lock()
186            .map_err(|e| anyhow::anyhow!("registry lock: {}", e))?
187            .rename(node_id, name)
188    }
189
190    /// Update the stored address for a device.
191    pub fn update_device_address(&self, node_id: u64, address: &str) -> Result<()> {
192        self.registry
193            .lock()
194            .map_err(|e| anyhow::anyhow!("registry lock: {}", e))?
195            .update_address(node_id, address)
196    }
197
198    /// Get a reference to the underlying controller.
199    pub fn controller(&self) -> &Arc<controller::Controller> {
200        &self.controller
201    }
202
203    /// Get a reference to the underlying transport.
204    pub fn transport(&self) -> &Arc<transport::Transport> {
205        &self.transport
206    }
207
208    /// Get a reference to the certificate manager.
209    pub fn certmanager(&self) -> &Arc<dyn certmanager::CertManager> {
210        &self.certmanager
211    }
212
213    /// Get the config.
214    pub fn config(&self) -> &ManagerConfig {
215        &self.config
216    }
217
218    /// Get the base path.
219    pub fn base_path(&self) -> &str {
220        &self.base_path
221    }
222}