use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::model::ScanMode; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ValueType { Null, Bool, Int, UInt, Float, Text, Bytes, Array, Object, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum PointQuality { Good, Bad, Uncertain, Unknown, } impl PointQuality { pub fn from_status_code(status: &opcua::types::StatusCode) -> Self { if status.is_good() { Self::Good } else if status.is_bad() { Self::Bad } else if status.is_uncertain() { Self::Uncertain } else { Self::Unknown } } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(untagged)] pub enum DataValue { Null, Bool(bool), Int(i64), UInt(u64), Float(f64), Text(String), Bytes(Vec), Array(Vec), Object(serde_json::Value), } impl DataValue { pub fn to_json_value(&self) -> serde_json::Value { match self { DataValue::Null => serde_json::Value::Null, DataValue::Bool(v) => serde_json::Value::Bool(*v), DataValue::Int(v) => serde_json::json!(*v), DataValue::UInt(v) => serde_json::json!(*v), DataValue::Float(v) => serde_json::json!(*v), DataValue::Text(v) => serde_json::json!(v), DataValue::Bytes(v) => serde_json::json!(v), DataValue::Array(v) => { serde_json::Value::Array(v.iter().map(DataValue::to_json_value).collect()) } DataValue::Object(v) => v.clone(), } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PointMonitorInfo { pub protocol: String, pub source_id: Uuid, pub point_id: Uuid, pub client_handle: u32, pub scan_mode: ScanMode, #[serde(serialize_with = "crate::util::datetime::option_utc_to_local_str")] pub timestamp: Option>, pub quality: PointQuality, pub value: Option, pub value_type: Option, pub value_text: Option, pub old_value: Option, #[serde(serialize_with = "crate::util::datetime::option_utc_to_local_str")] pub old_timestamp: Option>, pub value_changed: bool, } impl PointMonitorInfo { pub fn value_as_json(&self) -> Option { self.value.as_ref().map(DataValue::to_json_value) } } #[derive(Debug, Clone)] pub struct PointNewValue { pub source_id: Uuid, pub point_id: Option, pub client_handle: u32, pub value: Option, pub value_type: Option, pub value_text: Option, pub quality: PointQuality, pub protocol: String, pub timestamp: Option>, pub scan_mode: ScanMode, } pub fn opcua_variant_to_data(value: &opcua::types::Variant) -> DataValue { use opcua::types::Variant; match value { Variant::Empty => DataValue::Null, Variant::Boolean(v) => DataValue::Bool(*v), Variant::SByte(v) => DataValue::Int(*v as i64), Variant::Byte(v) => DataValue::UInt(*v as u64), Variant::Int16(v) => DataValue::Int(*v as i64), Variant::UInt16(v) => DataValue::UInt(*v as u64), Variant::Int32(v) => DataValue::Int(*v as i64), Variant::UInt32(v) => DataValue::UInt(*v as u64), Variant::Int64(v) => DataValue::Int(*v), Variant::UInt64(v) => DataValue::UInt(*v), Variant::Float(v) => DataValue::Float(*v as f64), Variant::Double(v) => DataValue::Float(*v), Variant::String(v) => DataValue::Text(v.to_string()), Variant::ByteString(v) => DataValue::Bytes(v.value.clone().unwrap_or_default()), Variant::Array(v) => { DataValue::Array(v.values.iter().map(opcua_variant_to_data).collect()) } _ => DataValue::Text(value.to_string()), } } pub fn opcua_variant_type(value: &opcua::types::Variant) -> ValueType { use opcua::types::Variant; match value { Variant::Empty => ValueType::Null, Variant::Boolean(_) => ValueType::Bool, Variant::SByte(_) | Variant::Int16(_) | Variant::Int32(_) | Variant::Int64(_) => ValueType::Int, Variant::Byte(_) | Variant::UInt16(_) | Variant::UInt32(_) | Variant::UInt64(_) => ValueType::UInt, Variant::Float(_) | Variant::Double(_) => ValueType::Float, Variant::String(_) => ValueType::Text, Variant::ByteString(_) => ValueType::Bytes, Variant::Array(_) => ValueType::Array, _ => ValueType::Text, } }