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