1use std::collections::HashMap;
4use std::sync::Arc;
5
6use async_trait::async_trait;
7use parking_lot::RwLock;
8use tokio::sync::broadcast;
9
10use mabi_core::{
11 device::{Device, DeviceInfo, DeviceState, DeviceStatistics},
12 error::{Error, Result},
13 protocol::Protocol,
14 types::{AccessMode, DataPoint, DataPointDef, DataPointId, DataType},
15 value::Value,
16};
17
18use crate::config::BacnetServerConfig;
19use crate::object::property::{BACnetValue, PropertyId};
20use crate::object::registry::ObjectRegistry;
21use crate::object::standard::{
22 AnalogInput, AnalogOutput, AnalogValue, BinaryInput, BinaryOutput, BinaryValue,
23};
24use crate::object::traits::{ArcObject, BACnetObject};
25use crate::object::types::{ObjectId, ObjectType};
26
27pub struct BacnetDevice {
32 info: DeviceInfo,
33 device_instance: u32,
34 objects: Arc<ObjectRegistry>,
35 point_defs: RwLock<HashMap<String, DataPointDef>>,
36 object_to_point: RwLock<HashMap<ObjectId, String>>,
37 stats: RwLock<DeviceStatistics>,
38 event_tx: broadcast::Sender<DataPoint>,
39}
40
41impl BacnetDevice {
42 pub fn new(config: &BacnetServerConfig) -> Self {
44 let (event_tx, _) = broadcast::channel(1000);
45
46 let info = DeviceInfo::new(
47 format!("bacnet-{}", config.device_instance),
48 &config.device_name,
49 Protocol::BacnetIp,
50 )
51 .with_metadata("device_instance", config.device_instance.to_string())
52 .with_metadata("vendor_id", config.vendor_id.to_string());
53
54 Self {
55 info,
56 device_instance: config.device_instance,
57 objects: Arc::new(ObjectRegistry::new()),
58 point_defs: RwLock::new(HashMap::new()),
59 object_to_point: RwLock::new(HashMap::new()),
60 stats: RwLock::new(DeviceStatistics::default()),
61 event_tx,
62 }
63 }
64
65 pub fn with_registry(config: &BacnetServerConfig, registry: ObjectRegistry) -> Self {
67 let mut device = Self::new(config);
68 device.objects = Arc::new(registry);
69 device.sync_point_definitions();
70 device
71 }
72
73 pub fn device_instance(&self) -> u32 {
75 self.device_instance
76 }
77
78 pub fn objects(&self) -> &Arc<ObjectRegistry> {
80 &self.objects
81 }
82
83 pub fn register_object(&self, object: ArcObject) {
85 let object_id = object.object_identifier();
86 let object_name = object.object_name().to_string();
87
88 let point_id = format!("{}:{}", object_id.object_type as u16, object_id.instance);
90
91 let data_type = match object_id.object_type {
93 ObjectType::AnalogInput | ObjectType::AnalogOutput | ObjectType::AnalogValue => {
94 DataType::Float32
95 }
96 ObjectType::BinaryInput | ObjectType::BinaryOutput | ObjectType::BinaryValue => {
97 DataType::Bool
98 }
99 ObjectType::MultiStateInput
100 | ObjectType::MultiStateOutput
101 | ObjectType::MultiStateValue => DataType::UInt32,
102 _ => DataType::String,
103 };
104
105 let access = match object_id.object_type {
107 ObjectType::AnalogOutput
108 | ObjectType::AnalogValue
109 | ObjectType::BinaryOutput
110 | ObjectType::BinaryValue
111 | ObjectType::MultiStateOutput
112 | ObjectType::MultiStateValue => AccessMode::ReadWrite,
113 _ => AccessMode::ReadOnly,
114 };
115
116 let def = DataPointDef::new(&point_id, &object_name, data_type).with_access(access);
117
118 self.objects.register(object);
120
121 self.object_to_point
123 .write()
124 .insert(object_id, point_id.clone());
125 self.point_defs.write().insert(point_id, def);
126 }
127
128 pub fn add_analog_input(&self, instance: u32, name: &str) -> ObjectId {
130 let object = Arc::new(AnalogInput::new(instance, name));
131 let id = object.object_identifier();
132 self.register_object(object);
133 id
134 }
135
136 pub fn add_analog_output(&self, instance: u32, name: &str) -> ObjectId {
138 let object = Arc::new(AnalogOutput::new(instance, name));
139 let id = object.object_identifier();
140 self.register_object(object);
141 id
142 }
143
144 pub fn add_analog_value(&self, instance: u32, name: &str) -> ObjectId {
146 let object = Arc::new(AnalogValue::new(instance, name));
147 let id = object.object_identifier();
148 self.register_object(object);
149 id
150 }
151
152 pub fn add_binary_input(&self, instance: u32, name: &str) -> ObjectId {
154 let object = Arc::new(BinaryInput::new(instance, name));
155 let id = object.object_identifier();
156 self.register_object(object);
157 id
158 }
159
160 pub fn add_binary_output(&self, instance: u32, name: &str) -> ObjectId {
162 let object = Arc::new(BinaryOutput::new(instance, name));
163 let id = object.object_identifier();
164 self.register_object(object);
165 id
166 }
167
168 pub fn add_binary_value(&self, instance: u32, name: &str) -> ObjectId {
170 let object = Arc::new(BinaryValue::new(instance, name));
171 let id = object.object_identifier();
172 self.register_object(object);
173 id
174 }
175
176 fn sync_point_definitions(&self) {
178 for object in self.objects.iter() {
179 let object_id = object.object_identifier();
180 let object_name = object.object_name().to_string();
181 let point_id = format!("{}:{}", object_id.object_type as u16, object_id.instance);
182
183 let data_type = match object_id.object_type {
184 ObjectType::AnalogInput | ObjectType::AnalogOutput | ObjectType::AnalogValue => {
185 DataType::Float32
186 }
187 ObjectType::BinaryInput | ObjectType::BinaryOutput | ObjectType::BinaryValue => {
188 DataType::Bool
189 }
190 ObjectType::MultiStateInput
191 | ObjectType::MultiStateOutput
192 | ObjectType::MultiStateValue => DataType::UInt32,
193 _ => DataType::String,
194 };
195
196 let access = match object_id.object_type {
197 ObjectType::AnalogOutput
198 | ObjectType::AnalogValue
199 | ObjectType::BinaryOutput
200 | ObjectType::BinaryValue
201 | ObjectType::MultiStateOutput
202 | ObjectType::MultiStateValue => AccessMode::ReadWrite,
203 _ => AccessMode::ReadOnly,
204 };
205
206 let def = DataPointDef::new(&point_id, &object_name, data_type).with_access(access);
207
208 self.object_to_point
209 .write()
210 .insert(object_id, point_id.clone());
211 self.point_defs.write().insert(point_id, def);
212 }
213 }
214
215 fn point_id_to_object_id(&self, point_id: &str) -> Option<ObjectId> {
217 let parts: Vec<&str> = point_id.split(':').collect();
218 if parts.len() != 2 {
219 return None;
220 }
221
222 let type_code: u16 = parts[0].parse().ok()?;
223 let instance: u32 = parts[1].parse().ok()?;
224 let object_type = ObjectType::from_u16(type_code)?;
225
226 Some(ObjectId::new(object_type, instance))
227 }
228
229 fn bacnet_to_core_value(bacnet: &BACnetValue) -> Option<Value> {
231 match bacnet {
232 BACnetValue::Null => Some(Value::Null),
233 BACnetValue::Boolean(b) => Some(Value::Bool(*b)),
234 BACnetValue::Unsigned(u) => Some(Value::U32(*u)),
235 BACnetValue::Signed(i) => Some(Value::I32(*i)),
236 BACnetValue::Real(f) => Some(Value::F32(*f)),
237 BACnetValue::Double(d) => Some(Value::F64(*d)),
238 BACnetValue::CharacterString(s) => Some(Value::String(s.clone())),
239 BACnetValue::Enumerated(e) => Some(Value::U32(*e)),
240 _ => None,
241 }
242 }
243
244 fn core_to_bacnet_value(value: &Value) -> Option<BACnetValue> {
246 match value {
247 Value::Null => Some(BACnetValue::Null),
248 Value::Bool(b) => Some(BACnetValue::Boolean(*b)),
249 Value::U8(u) => Some(BACnetValue::Unsigned(*u as u32)),
250 Value::U16(u) => Some(BACnetValue::Unsigned(*u as u32)),
251 Value::U32(u) => Some(BACnetValue::Unsigned(*u)),
252 Value::U64(u) => Some(BACnetValue::Unsigned(*u as u32)),
253 Value::I8(i) => Some(BACnetValue::Signed(*i as i32)),
254 Value::I16(i) => Some(BACnetValue::Signed(*i as i32)),
255 Value::I32(i) => Some(BACnetValue::Signed(*i)),
256 Value::I64(i) => Some(BACnetValue::Signed(*i as i32)),
257 Value::F32(f) => Some(BACnetValue::Real(*f)),
258 Value::F64(d) => Some(BACnetValue::Double(*d)),
259 Value::String(s) => Some(BACnetValue::CharacterString(s.clone())),
260 _ => None,
261 }
262 }
263}
264
265#[async_trait]
266impl Device for BacnetDevice {
267 fn info(&self) -> &DeviceInfo {
268 &self.info
269 }
270
271 async fn initialize(&mut self) -> Result<()> {
272 self.info.state = DeviceState::Online;
273 self.info.point_count = self.point_defs.read().len();
274 Ok(())
275 }
276
277 async fn start(&mut self) -> Result<()> {
278 self.info.state = DeviceState::Online;
279 Ok(())
280 }
281
282 async fn stop(&mut self) -> Result<()> {
283 self.info.state = DeviceState::Offline;
284 Ok(())
285 }
286
287 async fn tick(&mut self) -> Result<()> {
288 Ok(())
289 }
290
291 fn point_definitions(&self) -> Vec<&DataPointDef> {
292 vec![]
295 }
296
297 fn point_definition(&self, _point_id: &str) -> Option<&DataPointDef> {
298 None
300 }
301
302 async fn read(&self, point_id: &str) -> Result<DataPoint> {
303 let object_id = self
304 .point_id_to_object_id(point_id)
305 .ok_or_else(|| Error::point_not_found(&self.info.id, point_id))?;
306
307 let bacnet_value = self
308 .objects
309 .read_property(&object_id, PropertyId::PresentValue)
310 .map_err(|e| Error::point_not_found(&self.info.id, &e.to_string()))?;
311
312 let value = Self::bacnet_to_core_value(&bacnet_value)
313 .ok_or_else(|| Error::point_not_found(&self.info.id, "Cannot convert BACnet value"))?;
314
315 self.stats.write().record_read();
316 let id = DataPointId::new(&self.info.id, point_id);
317 Ok(DataPoint::new(id, value))
318 }
319
320 async fn write(&mut self, point_id: &str, value: Value) -> Result<()> {
321 let object_id = self
322 .point_id_to_object_id(point_id)
323 .ok_or_else(|| Error::point_not_found(&self.info.id, point_id))?;
324
325 let is_writable = matches!(
327 object_id.object_type,
328 ObjectType::AnalogOutput
329 | ObjectType::AnalogValue
330 | ObjectType::BinaryOutput
331 | ObjectType::BinaryValue
332 | ObjectType::MultiStateOutput
333 | ObjectType::MultiStateValue
334 );
335
336 if !is_writable {
337 return Err(Error::NotSupported(format!(
338 "Object {} is read-only",
339 point_id
340 )));
341 }
342
343 let bacnet_value = Self::core_to_bacnet_value(&value)
344 .ok_or_else(|| Error::NotSupported("Cannot convert value to BACnet format".into()))?;
345
346 self.objects
347 .write_property(&object_id, PropertyId::PresentValue, bacnet_value)
348 .map_err(|e| Error::point_not_found(&self.info.id, &e.to_string()))?;
349
350 self.stats.write().record_write();
351
352 let id = DataPointId::new(&self.info.id, point_id);
353 let _ = self.event_tx.send(DataPoint::new(id, value));
354
355 Ok(())
356 }
357
358 fn subscribe(&self) -> Option<broadcast::Receiver<DataPoint>> {
359 Some(self.event_tx.subscribe())
360 }
361
362 fn statistics(&self) -> DeviceStatistics {
363 self.stats.read().clone()
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370
371 #[tokio::test]
372 async fn test_bacnet_device() {
373 let config = BacnetServerConfig::default();
374 let device = BacnetDevice::new(&config);
375
376 device.add_analog_input(1, "Temperature");
377 device.add_analog_output(1, "Setpoint");
378
379 assert_eq!(device.objects.len(), 2);
380 }
381
382 #[tokio::test]
383 async fn test_bacnet_read_write() {
384 let config = BacnetServerConfig::default();
385 let mut device = BacnetDevice::new(&config);
386
387 let id = device.add_analog_output(1, "Setpoint");
388 device.initialize().await.unwrap();
389
390 if let Some(object) = device.objects.get(&id) {
392 let _ = object.write_property(PropertyId::PresentValue, BACnetValue::Real(21.5));
393 }
394
395 let dp = device.read("1:1").await.unwrap();
397 assert!((dp.value.as_f64().unwrap() - 21.5).abs() < 0.01);
398 }
399}