1use rust_ethernet_ip::{PlcValue, UdtData};
12
13#[derive(Debug, Clone, PartialEq)]
20pub enum Value {
21 Bool(bool),
22 Sint(i8),
23 Int(i16),
24 Dint(i32),
25 Lint(i64),
26 Usint(u8),
27 Uint(u16),
28 Udint(u32),
29 Ulint(u64),
30 Real(f32),
31 Lreal(f64),
32 String(String),
33 Struct(StructuredValue),
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct StructuredValue {
42 pub symbol_id: Option<i32>,
47 pub data: Vec<u8>,
49}
50
51macro_rules! impl_value_from {
58 ($($ty:ty => $variant:ident),* $(,)?) => {
59 $(
60 impl From<$ty> for Value {
61 fn from(value: $ty) -> Self {
62 Self::$variant(value)
63 }
64 }
65 )*
66 };
67}
68
69impl_value_from!(
70 bool => Bool,
71 i8 => Sint,
72 i16 => Int,
73 i32 => Dint,
74 i64 => Lint,
75 u8 => Usint,
76 u16 => Uint,
77 u32 => Udint,
78 u64 => Ulint,
79 f32 => Real,
80 f64 => Lreal,
81 String => String,
82 StructuredValue => Struct,
83);
84
85impl From<&str> for Value {
86 fn from(value: &str) -> Self {
87 Self::String(value.to_owned())
88 }
89}
90
91impl From<PlcValue> for Value {
92 fn from(value: PlcValue) -> Self {
93 match value {
94 PlcValue::Bool(value) => Value::Bool(value),
95 PlcValue::Sint(value) => Value::Sint(value),
96 PlcValue::Int(value) => Value::Int(value),
97 PlcValue::Dint(value) => Value::Dint(value),
98 PlcValue::Lint(value) => Value::Lint(value),
99 PlcValue::Usint(value) => Value::Usint(value),
100 PlcValue::Uint(value) => Value::Uint(value),
101 PlcValue::Udint(value) => Value::Udint(value),
102 PlcValue::Ulint(value) => Value::Ulint(value),
103 PlcValue::Real(value) => Value::Real(value),
104 PlcValue::Lreal(value) => Value::Lreal(value),
105 PlcValue::String(value) => Value::String(value),
106 PlcValue::Udt(udt) => udt.into(),
107 }
108 }
109}
110
111impl From<UdtData> for Value {
112 fn from(udt: UdtData) -> Self {
113 Value::Struct(StructuredValue {
114 symbol_id: (udt.symbol_id != 0).then_some(udt.symbol_id),
115 data: udt.data,
116 })
117 }
118}
119
120impl From<StructuredValue> for UdtData {
121 fn from(value: StructuredValue) -> Self {
122 Self {
123 symbol_id: value.symbol_id.unwrap_or_default(),
124 data: value.data,
125 }
126 }
127}
128
129impl From<StructuredValue> for PlcValue {
130 fn from(value: StructuredValue) -> Self {
131 PlcValue::Udt(value.into())
132 }
133}
134
135impl From<Value> for PlcValue {
136 fn from(value: Value) -> Self {
137 match value {
138 Value::Bool(value) => PlcValue::Bool(value),
139 Value::Sint(value) => PlcValue::Sint(value),
140 Value::Int(value) => PlcValue::Int(value),
141 Value::Dint(value) => PlcValue::Dint(value),
142 Value::Lint(value) => PlcValue::Lint(value),
143 Value::Usint(value) => PlcValue::Usint(value),
144 Value::Uint(value) => PlcValue::Uint(value),
145 Value::Udint(value) => PlcValue::Udint(value),
146 Value::Ulint(value) => PlcValue::Ulint(value),
147 Value::Real(value) => PlcValue::Real(value),
148 Value::Lreal(value) => PlcValue::Lreal(value),
149 Value::String(value) => PlcValue::String(value),
150 Value::Struct(value) => value.into(),
151 }
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
161 fn converts_scalar_plc_values() {
162 let cases = vec![
163 (PlcValue::Bool(true), Value::Bool(true)),
164 (PlcValue::Sint(-3), Value::Sint(-3)),
165 (PlcValue::Int(-12), Value::Int(-12)),
166 (PlcValue::Dint(1234), Value::Dint(1234)),
167 (PlcValue::Lint(-5678), Value::Lint(-5678)),
168 (PlcValue::Usint(7), Value::Usint(7)),
169 (PlcValue::Uint(42), Value::Uint(42)),
170 (PlcValue::Udint(99), Value::Udint(99)),
171 (PlcValue::Ulint(123_456), Value::Ulint(123_456)),
172 (PlcValue::Real(1.25), Value::Real(1.25)),
173 (PlcValue::Lreal(-9.5), Value::Lreal(-9.5)),
174 (
175 PlcValue::String("hello".to_owned()),
176 Value::String("hello".to_owned()),
177 ),
178 ];
179
180 for (input, expected) in cases {
181 assert_eq!(Value::from(input), expected);
182 }
183 }
184
185 #[test]
187 fn preserves_non_string_udt_as_structured_value() {
188 let udt = UdtData {
189 symbol_id: 99,
190 data: vec![0xde, 0xad, 0xbe, 0xef],
191 };
192
193 assert_eq!(
194 Value::from(udt),
195 Value::Struct(StructuredValue {
196 symbol_id: Some(99),
197 data: vec![0xde, 0xad, 0xbe, 0xef],
198 })
199 );
200 }
201
202 #[test]
204 fn converts_structured_value_back_to_plc_udt() {
205 let plc_value = PlcValue::from(StructuredValue {
206 symbol_id: Some(42),
207 data: vec![0xaa, 0xbb],
208 });
209
210 assert_eq!(
211 plc_value,
212 PlcValue::Udt(UdtData {
213 symbol_id: 42,
214 data: vec![0xaa, 0xbb],
215 })
216 );
217 }
218
219 #[test]
221 fn converts_scalar_value_back_to_plc_value() {
222 assert_eq!(PlcValue::from(Value::Bool(true)), PlcValue::Bool(true));
223 assert_eq!(PlcValue::from(Value::Dint(123)), PlcValue::Dint(123));
224 assert_eq!(
225 PlcValue::from(Value::String("hello".to_owned())),
226 PlcValue::String("hello".to_owned())
227 );
228 }
229
230 #[test]
232 fn converts_rust_scalars_into_value_variants() {
233 assert_eq!(Value::from(true), Value::Bool(true));
234 assert_eq!(Value::from(7_i16), Value::Int(7));
235 assert_eq!(Value::from(3.5_f64), Value::Lreal(3.5));
236 assert_eq!(Value::from("abc"), Value::String("abc".to_owned()));
237 }
238
239 #[test]
241 fn preserves_zero_symbol_id_as_none_for_structured_value() {
242 let udt = UdtData {
243 symbol_id: 0,
244 data: vec![1, 2, 3, 4],
245 };
246
247 assert_eq!(
248 Value::from(udt),
249 Value::Struct(StructuredValue {
250 symbol_id: None,
251 data: vec![1, 2, 3, 4],
252 })
253 );
254 }
255}