Skip to main content

moto_hses_proto/commands/
variable.rs

1//! Variable command definitions for HSES protocol
2
3use crate::{HsesPayload, commands::Command, error::ProtocolError};
4use std::marker::PhantomData;
5
6/// Trait for variable command IDs
7pub trait VariableCommandId {
8    fn command_id() -> u16;
9}
10
11impl VariableCommandId for u8 {
12    fn command_id() -> u16 {
13        0x7a
14    }
15}
16
17impl VariableCommandId for i16 {
18    fn command_id() -> u16 {
19        0x7b
20    }
21}
22
23impl VariableCommandId for i32 {
24    fn command_id() -> u16 {
25        0x7c
26    }
27}
28
29impl VariableCommandId for f32 {
30    fn command_id() -> u16 {
31        0x7d
32    }
33}
34
35impl VariableCommandId for String {
36    fn command_id() -> u16 {
37        0x7e
38    }
39}
40
41/// Command ID and validation for multiple variable operations
42pub trait MultipleVariableCommandId {
43    /// Returns the command ID for multiple variable operations (0x302-0x306)
44    fn multiple_command_id() -> u16;
45
46    /// Returns the size in bytes of a single element
47    fn element_size() -> usize;
48
49    /// Returns the maximum count for this variable type
50    fn max_count() -> u32;
51
52    /// Validates the count for this variable type
53    ///
54    /// # Errors
55    /// Returns `ProtocolError` if count is invalid
56    fn validate_count(count: u32) -> Result<(), ProtocolError>;
57}
58
59impl MultipleVariableCommandId for u8 {
60    fn multiple_command_id() -> u16 {
61        0x302
62    }
63    fn element_size() -> usize {
64        1
65    }
66    fn max_count() -> u32 {
67        474
68    }
69    fn validate_count(count: u32) -> Result<(), ProtocolError> {
70        if count == 0 || count > Self::max_count() {
71            return Err(ProtocolError::InvalidMessage(format!(
72                "Invalid count: {count} (must be 1-{})",
73                Self::max_count()
74            )));
75        }
76        if !count.is_multiple_of(2) {
77            return Err(ProtocolError::InvalidMessage(format!(
78                "Count must be multiple of 2: {count}"
79            )));
80        }
81        Ok(())
82    }
83}
84
85impl MultipleVariableCommandId for i16 {
86    fn multiple_command_id() -> u16 {
87        0x303
88    }
89    fn element_size() -> usize {
90        2
91    }
92    fn max_count() -> u32 {
93        237
94    }
95    fn validate_count(count: u32) -> Result<(), ProtocolError> {
96        if count == 0 || count > Self::max_count() {
97            return Err(ProtocolError::InvalidMessage(format!(
98                "Invalid count: {count} (must be 1-{})",
99                Self::max_count()
100            )));
101        }
102        Ok(())
103    }
104}
105
106impl MultipleVariableCommandId for i32 {
107    fn multiple_command_id() -> u16 {
108        0x304
109    }
110    fn element_size() -> usize {
111        4
112    }
113    fn max_count() -> u32 {
114        118
115    }
116    fn validate_count(count: u32) -> Result<(), ProtocolError> {
117        if count == 0 || count > Self::max_count() {
118            return Err(ProtocolError::InvalidMessage(format!(
119                "Invalid count: {count} (must be 1-{})",
120                Self::max_count()
121            )));
122        }
123        Ok(())
124    }
125}
126
127impl MultipleVariableCommandId for f32 {
128    fn multiple_command_id() -> u16 {
129        0x305
130    }
131    fn element_size() -> usize {
132        4
133    }
134    fn max_count() -> u32 {
135        118
136    }
137    fn validate_count(count: u32) -> Result<(), ProtocolError> {
138        if count == 0 || count > Self::max_count() {
139            return Err(ProtocolError::InvalidMessage(format!(
140                "Invalid count: {count} (must be 1-{})",
141                Self::max_count()
142            )));
143        }
144        Ok(())
145    }
146}
147
148// Implementation for String (S variables - 16 bytes each)
149impl MultipleVariableCommandId for String {
150    fn multiple_command_id() -> u16 {
151        0x306
152    }
153    fn element_size() -> usize {
154        16
155    }
156    fn max_count() -> u32 {
157        29
158    }
159    fn validate_count(count: u32) -> Result<(), ProtocolError> {
160        if count == 0 || count > Self::max_count() {
161            return Err(ProtocolError::InvalidMessage(format!(
162                "Invalid count: {count} (must be 1-{})",
163                Self::max_count()
164            )));
165        }
166        Ok(())
167    }
168}
169
170/// Trait for deserializing multiple variable responses
171pub trait MultipleVariableResponse: Sized + MultipleVariableCommandId {
172    /// Parse a single element from byte slice at given offset
173    ///
174    /// # Errors
175    /// Returns `ProtocolError` if parsing fails
176    fn parse_element(
177        data: &[u8],
178        offset: usize,
179        encoding: crate::encoding::TextEncoding,
180    ) -> Result<Self, ProtocolError>;
181
182    /// Deserialize multiple variables from response data
183    ///
184    /// # Errors
185    /// Returns `ProtocolError` if deserialization fails
186    fn deserialize_multiple(
187        data: &[u8],
188        expected_count: u32,
189        encoding: crate::encoding::TextEncoding,
190    ) -> Result<Vec<Self>, ProtocolError> {
191        // Common validation logic
192        if data.len() < 4 {
193            return Err(ProtocolError::Deserialization(format!(
194                "Response too short: {} bytes (need at least 4)",
195                data.len()
196            )));
197        }
198
199        let response_count = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
200        if response_count != expected_count {
201            return Err(ProtocolError::Deserialization(format!(
202                "Count mismatch: expected {expected_count}, got {response_count}"
203            )));
204        }
205
206        let element_size = Self::element_size();
207        let expected_len = 4 + (expected_count as usize * element_size);
208        if data.len() != expected_len {
209            return Err(ProtocolError::Deserialization(format!(
210                "Invalid response length: got {} bytes, expected {expected_len}",
211                data.len()
212            )));
213        }
214
215        // Parse elements
216        let mut values = Vec::with_capacity(expected_count as usize);
217        for i in 0..expected_count as usize {
218            let offset = 4 + i * element_size;
219            values.push(Self::parse_element(data, offset, encoding)?);
220        }
221        Ok(values)
222    }
223}
224
225impl MultipleVariableResponse for u8 {
226    fn parse_element(
227        data: &[u8],
228        offset: usize,
229        encoding: crate::encoding::TextEncoding,
230    ) -> Result<Self, ProtocolError> {
231        let byte_slice = &data[offset..=offset];
232        Self::deserialize(byte_slice, encoding)
233    }
234}
235
236impl MultipleVariableResponse for i16 {
237    fn parse_element(
238        data: &[u8],
239        offset: usize,
240        encoding: crate::encoding::TextEncoding,
241    ) -> Result<Self, ProtocolError> {
242        let byte_slice = &data[offset..offset + 2];
243        Self::deserialize(byte_slice, encoding)
244    }
245}
246
247impl MultipleVariableResponse for i32 {
248    fn parse_element(
249        data: &[u8],
250        offset: usize,
251        encoding: crate::encoding::TextEncoding,
252    ) -> Result<Self, ProtocolError> {
253        let byte_slice = &data[offset..offset + 4];
254        Self::deserialize(byte_slice, encoding)
255    }
256}
257
258impl MultipleVariableResponse for f32 {
259    fn parse_element(
260        data: &[u8],
261        offset: usize,
262        encoding: crate::encoding::TextEncoding,
263    ) -> Result<Self, ProtocolError> {
264        let byte_slice = &data[offset..offset + 4];
265        Self::deserialize(byte_slice, encoding)
266    }
267}
268
269impl MultipleVariableResponse for String {
270    fn parse_element(
271        data: &[u8],
272        offset: usize,
273        encoding: crate::encoding::TextEncoding,
274    ) -> Result<Self, ProtocolError> {
275        let byte_array = &data[offset..offset + 16];
276        Self::deserialize(byte_array, encoding)
277    }
278}
279
280/// Read multiple variables command (generic)
281/// `T` may be `f32`, which does not implement `Eq`, so we only derive `PartialEq`.
282#[allow(clippy::derive_partial_eq_without_eq)]
283#[derive(Debug, Clone, PartialEq)]
284pub struct ReadMultipleVariables<T: MultipleVariableCommandId + PartialEq> {
285    pub start_variable_number: u16,
286    pub count: u32,
287    pub _phantom: PhantomData<T>,
288}
289
290impl<T: MultipleVariableCommandId + PartialEq> ReadMultipleVariables<T> {
291    /// Create a new `ReadMultipleVariables` command
292    ///
293    /// # Errors
294    /// Returns an error if count validation fails
295    pub fn new(start_variable_number: u16, count: u32) -> Result<Self, ProtocolError> {
296        T::validate_count(count)?;
297        Ok(Self { start_variable_number, count, _phantom: PhantomData })
298    }
299}
300
301impl<T: MultipleVariableCommandId + PartialEq> Command for ReadMultipleVariables<T> {
302    type Response = Vec<T>;
303    fn command_id() -> u16 {
304        T::multiple_command_id()
305    }
306    fn instance(&self) -> u16 {
307        self.start_variable_number
308    }
309    fn attribute(&self) -> u8 {
310        0 // Fixed to 0 for plural commands
311    }
312    fn service(&self) -> u8 {
313        0x33 // Read plural data
314    }
315    fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
316        Ok(self.count.to_le_bytes().to_vec())
317    }
318}
319
320/// Write multiple variables command (generic)
321/// `T` may be `f32`, which does not implement `Eq`, so we only derive `PartialEq`.
322#[allow(clippy::derive_partial_eq_without_eq)]
323#[derive(Debug, Clone, PartialEq)]
324pub struct WriteMultipleVariables<T: MultipleVariableCommandId + PartialEq> {
325    pub start_variable_number: u16,
326    pub values: Vec<T>,
327}
328
329/// Write multiple string variables command with encoding support
330#[derive(Debug, Clone, PartialEq, Eq)]
331pub struct WriteMultipleStringVariables {
332    pub start_variable_number: u16,
333    pub values: Vec<String>,
334    pub text_encoding: crate::encoding::TextEncoding,
335}
336
337impl<T: MultipleVariableCommandId + PartialEq + Clone + HsesPayload> WriteMultipleVariables<T> {
338    /// Create a new `WriteMultipleVariables` command
339    ///
340    /// # Errors
341    /// Returns an error if count validation fails
342    pub fn new(start_variable_number: u16, values: Vec<T>) -> Result<Self, ProtocolError> {
343        let count = u32::try_from(values.len()).map_err(|_| {
344            ProtocolError::InvalidMessage(format!("Values count {} exceeds u32::MAX", values.len()))
345        })?;
346        T::validate_count(count)?;
347        Ok(Self { start_variable_number, values })
348    }
349}
350
351// Type-specific Command implementations for WriteMultipleVariables
352impl Command for WriteMultipleVariables<u8> {
353    type Response = ();
354    fn command_id() -> u16 {
355        0x302
356    }
357    fn instance(&self) -> u16 {
358        self.start_variable_number
359    }
360    fn attribute(&self) -> u8 {
361        0
362    }
363    fn service(&self) -> u8 {
364        0x34
365    }
366    fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
367        let count = u32::try_from(self.values.len()).map_err(|_| {
368            ProtocolError::InvalidMessage(format!(
369                "Values count {} exceeds u32::MAX",
370                self.values.len()
371            ))
372        })?;
373
374        // Validate count using the type's max_count
375        if count == 0 || count > u8::max_count() {
376            return Err(ProtocolError::InvalidMessage(format!(
377                "Invalid count: {count} (must be 1-{})",
378                u8::max_count()
379            )));
380        }
381
382        let mut payload = count.to_le_bytes().to_vec();
383        let serialized_values = self.values.serialize(crate::encoding::TextEncoding::Utf8)?;
384        payload.extend_from_slice(&serialized_values);
385        Ok(payload)
386    }
387}
388
389impl Command for WriteMultipleVariables<i16> {
390    type Response = ();
391    fn command_id() -> u16 {
392        0x303
393    }
394    fn instance(&self) -> u16 {
395        self.start_variable_number
396    }
397    fn attribute(&self) -> u8 {
398        0
399    }
400    fn service(&self) -> u8 {
401        0x34
402    }
403    fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
404        let count = u32::try_from(self.values.len()).map_err(|_| {
405            ProtocolError::InvalidMessage(format!(
406                "Values count {} exceeds u32::MAX",
407                self.values.len()
408            ))
409        })?;
410
411        // Validate count using the type's max_count
412        if count == 0 || count > i16::max_count() {
413            return Err(ProtocolError::InvalidMessage(format!(
414                "Invalid count: {count} (must be 1-{})",
415                i16::max_count()
416            )));
417        }
418
419        let mut payload = count.to_le_bytes().to_vec();
420        let serialized_values = self.values.serialize(crate::encoding::TextEncoding::Utf8)?;
421        payload.extend_from_slice(&serialized_values);
422        Ok(payload)
423    }
424}
425
426impl Command for WriteMultipleVariables<i32> {
427    type Response = ();
428    fn command_id() -> u16 {
429        0x304
430    }
431    fn instance(&self) -> u16 {
432        self.start_variable_number
433    }
434    fn attribute(&self) -> u8 {
435        0
436    }
437    fn service(&self) -> u8 {
438        0x34
439    }
440    fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
441        let count = u32::try_from(self.values.len()).map_err(|_| {
442            ProtocolError::InvalidMessage(format!(
443                "Values count {} exceeds u32::MAX",
444                self.values.len()
445            ))
446        })?;
447
448        // Validate count using the type's max_count
449        if count == 0 || count > i32::max_count() {
450            return Err(ProtocolError::InvalidMessage(format!(
451                "Invalid count: {count} (must be 1-{})",
452                i32::max_count()
453            )));
454        }
455
456        let mut payload = count.to_le_bytes().to_vec();
457        let serialized_values = self.values.serialize(crate::encoding::TextEncoding::Utf8)?;
458        payload.extend_from_slice(&serialized_values);
459        Ok(payload)
460    }
461}
462
463impl Command for WriteMultipleVariables<f32> {
464    type Response = ();
465    fn command_id() -> u16 {
466        0x305
467    }
468    fn instance(&self) -> u16 {
469        self.start_variable_number
470    }
471    fn attribute(&self) -> u8 {
472        0
473    }
474    fn service(&self) -> u8 {
475        0x34
476    }
477    fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
478        let count = u32::try_from(self.values.len()).map_err(|_| {
479            ProtocolError::InvalidMessage(format!(
480                "Values count {} exceeds u32::MAX",
481                self.values.len()
482            ))
483        })?;
484
485        // Validate count using the type's max_count
486        if count == 0 || count > f32::max_count() {
487            return Err(ProtocolError::InvalidMessage(format!(
488                "Invalid count: {count} (must be 1-{})",
489                f32::max_count()
490            )));
491        }
492
493        let mut payload = count.to_le_bytes().to_vec();
494        let serialized_values = self.values.serialize(crate::encoding::TextEncoding::Utf8)?;
495        payload.extend_from_slice(&serialized_values);
496        Ok(payload)
497    }
498}
499
500impl Command for WriteMultipleStringVariables {
501    type Response = ();
502    fn command_id() -> u16 {
503        0x306
504    }
505    fn instance(&self) -> u16 {
506        self.start_variable_number
507    }
508    fn attribute(&self) -> u8 {
509        0
510    }
511    fn service(&self) -> u8 {
512        0x34
513    }
514    fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
515        let count = u32::try_from(self.values.len()).map_err(|_| {
516            ProtocolError::InvalidMessage(format!(
517                "Values count {} exceeds u32::MAX",
518                self.values.len()
519            ))
520        })?;
521
522        // Validate count for String variables (1-29)
523        if count == 0 || count > String::max_count() {
524            return Err(ProtocolError::InvalidMessage(format!(
525                "Invalid count: {count} (must be 1-{})",
526                String::max_count()
527            )));
528        }
529
530        let mut payload = count.to_le_bytes().to_vec();
531        let serialized_values = self.values.serialize(self.text_encoding)?;
532        payload.extend_from_slice(&serialized_values);
533        Ok(payload)
534    }
535}
536
537/// `T` may be `f32`, which does not implement `Eq`, so we only derive `PartialEq`.
538#[allow(clippy::derive_partial_eq_without_eq)]
539#[derive(Debug, Clone, PartialEq)]
540pub struct ReadVariable<T: HsesPayload + VariableCommandId + PartialEq> {
541    pub index: u16, // Support extended variable settings (0-999)
542    pub _phantom: PhantomData<T>,
543}
544
545impl<T: HsesPayload + VariableCommandId + PartialEq> Command for ReadVariable<T> {
546    type Response = T;
547    fn command_id() -> u16 {
548        T::command_id()
549    }
550    fn instance(&self) -> u16 {
551        self.index
552    }
553    fn attribute(&self) -> u8 {
554        0
555    }
556    fn service(&self) -> u8 {
557        0x0E // Get_Attribute_Single
558    }
559    fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
560        Ok(vec![])
561    }
562}
563
564/// `T` may be `f32`, which does not implement `Eq`, so we only derive `PartialEq`.
565#[allow(clippy::derive_partial_eq_without_eq)]
566#[derive(Debug, Clone, PartialEq)]
567pub struct WriteVariable<T: HsesPayload + VariableCommandId + PartialEq> {
568    pub index: u16, // Support extended variable settings (0-999)
569    pub value: T,
570}
571
572/// Write string variable command with encoding support
573#[derive(Debug, Clone, PartialEq, Eq)]
574pub struct WriteStringVar {
575    pub index: u16, // Support extended variable settings (0-999)
576    pub value: String,
577    pub text_encoding: crate::encoding::TextEncoding,
578}
579
580impl<T: HsesPayload + VariableCommandId + PartialEq> Command for WriteVariable<T> {
581    type Response = ();
582    fn command_id() -> u16 {
583        T::command_id()
584    }
585    fn instance(&self) -> u16 {
586        self.index
587    }
588    fn attribute(&self) -> u8 {
589        0
590    }
591    fn service(&self) -> u8 {
592        0x10 // Set_Attribute_Single
593    }
594    fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
595        self.value.serialize(crate::encoding::TextEncoding::Utf8)
596    }
597}
598
599impl Command for WriteStringVar {
600    type Response = ();
601    fn command_id() -> u16 {
602        String::command_id()
603    }
604    fn instance(&self) -> u16 {
605        self.index
606    }
607    fn attribute(&self) -> u8 {
608        0
609    }
610    fn service(&self) -> u8 {
611        0x10 // Set_Attribute_Single
612    }
613    fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
614        self.value.serialize(self.text_encoding)
615    }
616}
617
618#[cfg(test)]
619mod tests {
620    use super::*;
621
622    // Allow expect in tests as they should not fail with valid inputs
623    #[allow(clippy::expect_used)]
624    #[test]
625    fn test_read_variable_construction() {
626        let cmd = ReadVariable::<u8> { index: 10, _phantom: PhantomData };
627        assert_eq!(cmd.index, 10);
628    }
629
630    #[test]
631    fn test_write_variable_construction() {
632        let cmd = WriteVariable::<u8> { index: 10, value: 42 };
633        assert_eq!(cmd.index, 10);
634        assert_eq!(cmd.value, 42);
635    }
636
637    #[test]
638    fn test_variable_command_ids() {
639        assert_eq!(u8::command_id(), 0x7a);
640        assert_eq!(i16::command_id(), 0x7b);
641        assert_eq!(i32::command_id(), 0x7c);
642        assert_eq!(f32::command_id(), 0x7d);
643        assert_eq!(String::command_id(), 0x7e);
644    }
645
646    #[test]
647    fn test_read_variable_command_trait() {
648        let cmd = ReadVariable::<u8> { index: 5, _phantom: PhantomData };
649        assert_eq!(ReadVariable::<u8>::command_id(), 0x7a);
650        assert_eq!(cmd.instance(), 5);
651        assert_eq!(cmd.attribute(), 0);
652        assert_eq!(cmd.service(), 0x0E);
653    }
654
655    #[test]
656    fn test_write_variable_command_trait() {
657        let cmd = WriteVariable::<u8> { index: 5, value: 100 };
658        assert_eq!(WriteVariable::<u8>::command_id(), 0x7a);
659        assert_eq!(cmd.instance(), 5);
660        assert_eq!(cmd.attribute(), 0);
661        assert_eq!(cmd.service(), 0x10);
662    }
663
664    #[test]
665    #[allow(clippy::expect_used)]
666    fn test_write_variable_serialization() {
667        let cmd = WriteVariable::<u8> { index: 5, value: 100 };
668        let serialized = cmd.serialize().expect("Serialization should not fail");
669        assert_eq!(serialized, vec![100]);
670    }
671
672    #[test]
673    fn test_multiple_variable_command_ids() {
674        assert_eq!(u8::multiple_command_id(), 0x302);
675        assert_eq!(i16::multiple_command_id(), 0x303);
676        assert_eq!(i32::multiple_command_id(), 0x304);
677        assert_eq!(f32::multiple_command_id(), 0x305);
678        assert_eq!(String::multiple_command_id(), 0x306);
679    }
680
681    #[test]
682    fn test_multiple_variable_element_sizes() {
683        assert_eq!(u8::element_size(), 1);
684        assert_eq!(i16::element_size(), 2);
685        assert_eq!(i32::element_size(), 4);
686        assert_eq!(f32::element_size(), 4);
687        assert_eq!(String::element_size(), 16);
688    }
689
690    #[test]
691    fn test_multiple_variable_max_counts() {
692        assert_eq!(u8::max_count(), 474);
693        assert_eq!(i16::max_count(), 237);
694        assert_eq!(i32::max_count(), 118);
695        assert_eq!(f32::max_count(), 118);
696        assert_eq!(String::max_count(), 29);
697    }
698
699    #[test]
700    fn test_multiple_variable_count_validation() {
701        // Valid counts
702        assert!(u8::validate_count(2).is_ok());
703        assert!(i16::validate_count(1).is_ok());
704        assert!(i32::validate_count(1).is_ok());
705        assert!(f32::validate_count(1).is_ok());
706        assert!(String::validate_count(1).is_ok());
707
708        // Invalid counts - too small
709        assert!(u8::validate_count(0).is_err());
710        assert!(i16::validate_count(0).is_err());
711        assert!(i32::validate_count(0).is_err());
712        assert!(f32::validate_count(0).is_err());
713        assert!(String::validate_count(0).is_err());
714
715        // Invalid counts - too large
716        assert!(u8::validate_count(475).is_err());
717        assert!(i16::validate_count(238).is_err());
718        assert!(i32::validate_count(119).is_err());
719        assert!(f32::validate_count(119).is_err());
720        assert!(String::validate_count(30).is_err());
721
722        // Invalid count for u8 - not multiple of 2
723        assert!(u8::validate_count(3).is_err());
724    }
725
726    #[test]
727    #[allow(clippy::expect_used)]
728    fn test_read_multiple_variables_construction() {
729        let cmd = ReadMultipleVariables::<u8>::new(0, 2).expect("Valid command should not fail");
730        assert_eq!(cmd.start_variable_number, 0);
731        assert_eq!(cmd.count, 2);
732    }
733
734    #[test]
735    fn test_read_multiple_variables_validation() {
736        // Valid count
737        assert!(ReadMultipleVariables::<u8>::new(0, 2).is_ok());
738
739        // Invalid count - not multiple of 2
740        assert!(ReadMultipleVariables::<u8>::new(0, 3).is_err());
741    }
742
743    #[test]
744    #[allow(clippy::expect_used)]
745    fn test_read_multiple_variables_command_trait() {
746        let cmd = ReadMultipleVariables::<u8>::new(10, 2).expect("Valid command should not fail");
747        assert_eq!(ReadMultipleVariables::<u8>::command_id(), 0x302);
748        assert_eq!(cmd.instance(), 10);
749        assert_eq!(cmd.attribute(), 0);
750        assert_eq!(cmd.service(), 0x33);
751    }
752
753    #[test]
754    #[allow(clippy::expect_used)]
755    fn test_read_multiple_variables_serialization() {
756        let cmd = ReadMultipleVariables::<u8>::new(5, 2).expect("Valid command should not fail");
757        let serialized = cmd.serialize().expect("Serialization should not fail");
758        assert_eq!(serialized, vec![2, 0, 0, 0]); // 2 in little-endian
759    }
760
761    #[test]
762    #[allow(clippy::expect_used)]
763    fn test_write_multiple_variables_construction() {
764        let values = vec![1u8, 2u8];
765        let cmd = WriteMultipleVariables::<u8>::new(0, values.clone())
766            .expect("Valid command should not fail");
767        assert_eq!(cmd.start_variable_number, 0);
768        assert_eq!(cmd.values, values);
769    }
770
771    #[test]
772    fn test_write_multiple_variables_validation() {
773        // Valid values
774        let values = vec![1u8, 2u8];
775        assert!(WriteMultipleVariables::<u8>::new(0, values).is_ok());
776
777        // Invalid values - not multiple of 2
778        let values = vec![1u8, 2u8, 3u8];
779        assert!(WriteMultipleVariables::<u8>::new(0, values).is_err());
780    }
781
782    #[test]
783    #[allow(clippy::expect_used)]
784    fn test_write_multiple_variables_command_trait() {
785        let values = vec![1u8, 2u8];
786        let cmd =
787            WriteMultipleVariables::<u8>::new(10, values).expect("Valid command should not fail");
788        assert_eq!(WriteMultipleVariables::<u8>::command_id(), 0x302);
789        assert_eq!(cmd.instance(), 10);
790        assert_eq!(cmd.attribute(), 0);
791        assert_eq!(cmd.service(), 0x34);
792    }
793
794    #[test]
795    #[allow(clippy::expect_used)]
796    fn test_write_multiple_variables_serialization() {
797        let values = vec![1u8, 2u8];
798        let cmd =
799            WriteMultipleVariables::<u8>::new(5, values).expect("Valid command should not fail");
800        let serialized = cmd.serialize().expect("Serialization should not fail");
801        assert_eq!(serialized, vec![2, 0, 0, 0, 1, 2]); // count + values
802    }
803
804    #[test]
805    #[allow(clippy::expect_used, clippy::unwrap_used, clippy::float_cmp)]
806    fn test_multiple_variable_response_parse_element() {
807        let data = [1u8, 2, 3, 4, 5, 6, 7, 8];
808
809        // Test u8 parsing
810        assert_eq!(u8::parse_element(&data, 0, crate::encoding::TextEncoding::Utf8).unwrap(), 1);
811        assert_eq!(u8::parse_element(&data, 1, crate::encoding::TextEncoding::Utf8).unwrap(), 2);
812
813        // Test i16 parsing
814        assert_eq!(
815            i16::parse_element(&data, 0, crate::encoding::TextEncoding::Utf8).unwrap(),
816            0x0201
817        ); // little-endian
818        assert_eq!(
819            i16::parse_element(&data, 2, crate::encoding::TextEncoding::Utf8).unwrap(),
820            0x0403
821        );
822
823        // Test i32 parsing
824        assert_eq!(
825            i32::parse_element(&data, 0, crate::encoding::TextEncoding::Utf8).unwrap(),
826            0x0403_0201
827        ); // little-endian
828        assert_eq!(
829            i32::parse_element(&data, 4, crate::encoding::TextEncoding::Utf8).unwrap(),
830            0x0807_0605
831        );
832
833        // Test f32 parsing
834        let f32_data = [0x00, 0x00, 0x80, 0x3f]; // 1.0f32 in little-endian
835        assert_eq!(
836            f32::parse_element(&f32_data, 0, crate::encoding::TextEncoding::Utf8).unwrap(),
837            1.0f32
838        );
839
840        // Test String parsing
841        let mut array_data = [0u8; 32];
842        // Test data: "Hello" + null terminator + padding
843        array_data[0..5].copy_from_slice(b"Hello");
844        // Test data: "World" + null terminator + padding
845        array_data[16..21].copy_from_slice(b"World");
846
847        let result =
848            String::parse_element(&array_data, 0, crate::encoding::TextEncoding::Utf8).unwrap();
849        assert_eq!(result, "Hello");
850
851        let result =
852            String::parse_element(&array_data, 16, crate::encoding::TextEncoding::Utf8).unwrap();
853        assert_eq!(result, "World");
854    }
855
856    #[test]
857    #[allow(clippy::unwrap_used)]
858    fn test_multiple_variable_response_deserialize_multiple() {
859        // Test u8 deserialization
860        let mut data = vec![2, 0, 0, 0]; // count = 2
861        data.extend_from_slice(&[1, 2]); // values
862
863        let result =
864            u8::deserialize_multiple(&data, 2, crate::encoding::TextEncoding::Utf8).unwrap();
865        assert_eq!(result, vec![1, 2]);
866
867        // Test i16 deserialization
868        let mut data = vec![2, 0, 0, 0]; // count = 2
869        data.extend_from_slice(&[1, 0, 2, 0]); // values in little-endian
870
871        let result =
872            i16::deserialize_multiple(&data, 2, crate::encoding::TextEncoding::Utf8).unwrap();
873        assert_eq!(result, vec![1, 2]);
874
875        // Test error cases
876        let short_data = vec![1, 0, 0, 0]; // count = 1, but no data
877        assert!(
878            u8::deserialize_multiple(&short_data, 1, crate::encoding::TextEncoding::Utf8).is_err()
879        );
880
881        let wrong_count_data = vec![2, 0, 0, 0, 1]; // count = 2, but only 1 value
882        assert!(
883            u8::deserialize_multiple(&wrong_count_data, 2, crate::encoding::TextEncoding::Utf8)
884                .is_err()
885        );
886    }
887}