1use std::time::Duration;
9use thiserror::Error;
10
11#[derive(Error, Debug, PartialEq)]
16pub enum Error {
17 #[error("Invalid data length: expected {expected}, got {got}")]
19 UnexpectedDataLength { expected: usize, got: usize },
20
21 #[error("Invalid data in register: {details}")]
23 InvalidData { details: String },
24
25 #[error("Invalid value code for {entity}: {code}")]
27 InvalidValueCode { entity: String, code: u16 },
28
29 #[error("Cannot encode value: {reason}")]
31 EncodeError { reason: String },
32}
33
34pub type Word = u16;
38
39pub const NUMBER_OF_CHANNELS: usize = 8;
41
42#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize))]
45#[repr(u16)]
46pub enum BaudRate {
47 B1200 = 1200, B2400 = 2400, B4800 = 4800, #[default]
55 B9600 = 9600, B19200 = 19200, }
59
60#[cfg(feature = "serde")]
61impl<'de> serde::Deserialize<'de> for BaudRate {
62 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
63 where
64 D: serde::Deserializer<'de>,
65 {
66 let value = u16::deserialize(deserializer)?;
67 BaudRate::try_from(value).map_err(|e| serde::de::Error::custom(e.to_string()))
68 }
69}
70
71impl BaudRate {
72 pub const ADDRESS: u16 = 0x00FF;
74 pub const QUANTITY: u16 = 1;
76
77 pub fn decode_from_holding_registers(words: &[Word]) -> Result<Self, Error> {
95 if words.len() != Self::QUANTITY as usize {
96 return Err(Error::UnexpectedDataLength {
97 expected: Self::QUANTITY as usize,
98 got: words.len(),
99 });
100 }
101 match words[0] {
102 0 => Ok(Self::B1200),
103 1 => Ok(Self::B2400),
104 2 => Ok(Self::B4800),
105 3 => Ok(Self::B9600),
106 4 => Ok(Self::B19200),
107 invalid_code => Err(Error::InvalidValueCode {
108 entity: "BaudRate".to_string(),
109 code: invalid_code,
110 }),
111 }
112 }
113
114 pub fn encode_for_write_register(&self) -> Word {
120 match self {
121 Self::B1200 => 0,
122 Self::B2400 => 1,
123 Self::B4800 => 2,
124 Self::B9600 => 3,
125 Self::B19200 => 4,
126 }
127 }
128}
129
130#[derive(Error, Debug, PartialEq, Eq)]
132#[error("Unsupported baud rate value: {0}. Must be 1200, 2400, 4800, 9600, or 19200.")]
133pub struct ErrorInvalidBaudRate(
134 pub u16,
136);
137
138impl TryFrom<u16> for BaudRate {
139 type Error = ErrorInvalidBaudRate;
140
141 fn try_from(value: u16) -> Result<Self, Self::Error> {
144 match value {
145 1200 => Ok(BaudRate::B1200),
146 2400 => Ok(BaudRate::B2400),
147 4800 => Ok(BaudRate::B4800),
148 9600 => Ok(BaudRate::B9600),
149 19200 => Ok(BaudRate::B19200),
150 _ => Err(ErrorInvalidBaudRate(value)),
151 }
152 }
153}
154
155impl From<BaudRate> for u16 {
156 fn from(baud_rate: BaudRate) -> u16 {
158 match baud_rate {
159 BaudRate::B1200 => 1200,
160 BaudRate::B2400 => 2400,
161 BaudRate::B4800 => 4800,
162 BaudRate::B9600 => 9600,
163 BaudRate::B19200 => 19200,
164 }
165 }
166}
167
168impl From<BaudRate> for u32 {
169 fn from(baud_rate: BaudRate) -> u32 {
170 baud_rate as u16 as u32
171 }
172}
173
174impl std::fmt::Display for BaudRate {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177 write!(f, "{}", *self as u16)
178 }
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
187#[cfg_attr(feature = "serde", derive(serde::Serialize))]
188pub struct Address(u8);
189
190#[cfg(feature = "serde")]
191impl<'de> serde::Deserialize<'de> for Address {
192 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
193 where
194 D: serde::Deserializer<'de>,
195 {
196 let value = u8::deserialize(deserializer)?;
197 Address::try_from(value).map_err(|e| serde::de::Error::custom(e.to_string()))
198 }
199}
200
201impl std::ops::Deref for Address {
203 type Target = u8;
204 fn deref(&self) -> &Self::Target {
205 &self.0
206 }
207}
208
209impl Default for Address {
210 fn default() -> Self {
212 Self(0x01)
214 }
215}
216
217impl Address {
218 pub const ADDRESS: u16 = 0x00FE;
225
226 pub const QUANTITY: u16 = 1; pub const MIN: u8 = 1;
231 pub const MAX: u8 = 247;
233
234 pub const BROADCAST: Address = Address(0xFF);
239
240 pub fn decode_from_holding_registers(words: &[Word]) -> Result<Self, Error> {
259 if words.len() != Self::QUANTITY as usize {
260 return Err(Error::UnexpectedDataLength {
261 expected: Self::QUANTITY as usize,
262 got: words.len(),
263 });
264 }
265 let word_value = words[0];
266
267 if word_value & 0xFF00 != 0 {
268 return Err(Error::InvalidData {
269 details: format!(
270 "Upper byte of address register is non-zero (value: {word_value:#06X})"
271 ),
272 });
273 }
274
275 let address_byte = word_value as u8;
276 match Self::try_from(address_byte) {
277 Ok(address) => Ok(address),
278 Err(err) => Err(Error::InvalidData {
279 details: format!("{err}"),
280 }),
281 }
282 }
283
284 pub fn encode_for_write_register(&self) -> Word {
290 self.0 as Word
291 }
292}
293
294#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
297#[error(
298 "The address value {0} is outside the valid assignable range of {min} to {max}",
299 min = Address::MIN,
300 max = Address::MAX
301)]
302pub struct ErrorAddressOutOfRange(
303 pub u8,
305);
306
307impl TryFrom<u8> for Address {
308 type Error = ErrorAddressOutOfRange;
309
310 fn try_from(value: u8) -> Result<Self, Self::Error> {
331 if (Self::MIN..=Self::MAX).contains(&value) {
332 Ok(Self(value))
333 } else {
334 Err(ErrorAddressOutOfRange(value))
335 }
336 }
337}
338
339impl std::fmt::Display for Address {
341 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
342 write!(f, "{:#04x}", self.0)
343 }
344}
345
346#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
351#[cfg_attr(feature = "serde", derive(serde::Serialize))]
352pub struct Temperature(f32);
353
354#[cfg(feature = "serde")]
355impl<'de> serde::Deserialize<'de> for Temperature {
356 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
357 where
358 D: serde::Deserializer<'de>,
359 {
360 let value = f32::deserialize(deserializer)?;
361 if value.is_nan() {
363 Ok(Temperature::NAN)
364 } else {
365 Temperature::try_from(value).map_err(|e| serde::de::Error::custom(e.to_string()))
366 }
367 }
368}
369
370impl Temperature {
371 pub const NAN: Temperature = Self(f32::NAN); pub const MIN: f32 = -3276.7; pub const MAX: f32 = 3276.7; pub fn decode_from_holding_registers(word: Word) -> Self {
388 match word {
393 0x8000 => Self::NAN,
394 w if w > 0x8000 => Self(((w as f32) - 65536.0) / 10.0),
395 w => Self((w as f32) / 10.0),
396 }
397 }
398
399 pub fn encode_for_write_register(&self) -> Result<Word, Error> {
409 if self.0.is_nan() {
415 return Err(Error::EncodeError {
416 reason: "Temperature value is NAN, which cannot be encoded.".to_string(),
417 });
418 }
419 if self.0 >= 0.0 {
420 Ok((self.0 * 10.0) as Word)
421 } else {
422 Ok((65536.0 + self.0 * 10.0) as Word)
423 }
424 }
425}
426
427impl std::ops::Deref for Temperature {
429 type Target = f32;
430 fn deref(&self) -> &Self::Target {
431 &self.0
432 }
433}
434
435#[derive(Error, Debug, PartialEq)]
441#[error(
442 "The degree celsius value {0} is outside the valid range of {min} to {max}",
443 min = Temperature::MIN,
444 max = Temperature::MAX
445)]
446pub struct ErrorDegreeCelsiusOutOfRange(
447 pub f32,
449);
450
451impl TryFrom<f32> for Temperature {
452 type Error = ErrorDegreeCelsiusOutOfRange;
453
454 fn try_from(value: f32) -> Result<Self, Self::Error> {
459 if value.is_nan() {
460 Err(ErrorDegreeCelsiusOutOfRange(value))
462 } else if !(Self::MIN..=Self::MAX).contains(&value) {
463 Err(ErrorDegreeCelsiusOutOfRange(value))
464 } else {
465 Ok(Self(value))
466 }
467 }
468}
469
470impl std::fmt::Display for Temperature {
471 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
473 if self.0.is_nan() {
474 write!(f, "NAN")
475 } else {
476 write!(f, "{:.1}", self.0)
477 }
478 }
479}
480
481#[derive(Debug, Clone, Copy, PartialEq)]
483#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
484pub struct Temperatures([Temperature; NUMBER_OF_CHANNELS]);
485
486impl Temperatures {
487 pub const ADDRESS: u16 = 0x0000;
489 pub const QUANTITY: u16 = NUMBER_OF_CHANNELS as u16;
491
492 pub fn decode_from_holding_registers(words: &[Word]) -> Result<Self, Error> {
508 if words.len() != NUMBER_OF_CHANNELS {
509 return Err(Error::UnexpectedDataLength {
510 expected: NUMBER_OF_CHANNELS,
511 got: words.len(),
512 });
513 }
514 let mut temperatures = [Temperature::NAN; NUMBER_OF_CHANNELS];
515 for (i, word) in words.iter().enumerate() {
516 temperatures[i] = Temperature::decode_from_holding_registers(*word);
517 }
518 Ok(Self(temperatures))
519 }
520
521 pub fn iter(&self) -> std::slice::Iter<'_, Temperature> {
523 self.0.iter()
524 }
525
526 pub fn as_slice(&self) -> &[Temperature] {
528 &self.0
529 }
530
531 pub fn as_array(&self) -> &[Temperature; NUMBER_OF_CHANNELS] {
533 &self.0
534 }
535}
536
537impl IntoIterator for Temperatures {
538 type Item = Temperature;
539 type IntoIter = std::array::IntoIter<Temperature, NUMBER_OF_CHANNELS>;
540
541 fn into_iter(self) -> Self::IntoIter {
542 self.0.into_iter()
543 }
544}
545
546impl<'a> IntoIterator for &'a Temperatures {
547 type Item = &'a Temperature;
548 type IntoIter = std::slice::Iter<'a, Temperature>;
549
550 fn into_iter(self) -> Self::IntoIter {
551 self.0.iter()
552 }
553}
554
555impl std::fmt::Display for Temperatures {
556 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
558 let temp_strs: Vec<String> = self.0.iter().map(|t| t.to_string()).collect();
559 write!(f, "{}", temp_strs.join(", "))
560 }
561}
562
563impl std::ops::Index<usize> for Temperatures {
564 type Output = Temperature;
565
566 fn index(&self, index: usize) -> &Self::Output {
572 &self.0[index]
573 }
574}
575
576#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
580#[cfg_attr(feature = "serde", derive(serde::Serialize))]
581pub struct Channel(u8);
582
583#[cfg(feature = "serde")]
584impl<'de> serde::Deserialize<'de> for Channel {
585 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
586 where
587 D: serde::Deserializer<'de>,
588 {
589 let value = u8::deserialize(deserializer)?;
590 Channel::try_from(value).map_err(|e| serde::de::Error::custom(e.to_string()))
591 }
592}
593
594impl Channel {
595 pub const MIN: u8 = 0;
597 pub const MAX: u8 = NUMBER_OF_CHANNELS as u8 - 1;
599}
600
601impl std::ops::Deref for Channel {
603 type Target = u8;
604 fn deref(&self) -> &Self::Target {
605 &self.0
606 }
607}
608
609#[derive(Error, Debug, PartialEq, Eq)]
615#[error(
616 "The channel value {0} is outside the valid range of {min} to {max}",
617 min = Channel::MIN,
618 max = Channel::MAX
619)]
620pub struct ErrorChannelOutOfRange(
621 pub u8,
623);
624
625impl TryFrom<u8> for Channel {
626 type Error = ErrorChannelOutOfRange;
627
628 fn try_from(value: u8) -> Result<Self, Self::Error> {
631 if (Self::MIN..=Self::MAX).contains(&value) {
632 Ok(Self(value))
633 } else {
634 Err(ErrorChannelOutOfRange(value))
635 }
636 }
637}
638
639impl std::fmt::Display for Channel {
640 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
642 write!(f, "{}", self.0)
643 }
644}
645
646#[derive(Debug, Clone, Copy, PartialEq)]
650#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
651pub struct TemperatureCorrection([Temperature; NUMBER_OF_CHANNELS]);
652
653impl TemperatureCorrection {
654 pub const ADDRESS: u16 = 0x0008;
656 pub const QUANTITY: u16 = NUMBER_OF_CHANNELS as u16;
658
659 pub fn decode_from_holding_registers(words: &[Word]) -> Result<Self, Error> {
675 if words.len() != NUMBER_OF_CHANNELS {
676 return Err(Error::UnexpectedDataLength {
677 expected: NUMBER_OF_CHANNELS,
678 got: words.len(),
679 });
680 }
681 let mut corrections = [Temperature::NAN; NUMBER_OF_CHANNELS];
682 for (i, word) in words.iter().enumerate() {
683 corrections[i] = Temperature::decode_from_holding_registers(*word);
685 }
686 Ok(Self(corrections))
687 }
688
689 pub fn iter(&self) -> std::slice::Iter<'_, Temperature> {
691 self.0.iter()
692 }
693
694 pub fn as_slice(&self) -> &[Temperature] {
696 &self.0
697 }
698
699 pub fn as_array(&self) -> &[Temperature; NUMBER_OF_CHANNELS] {
701 &self.0
702 }
703
704 pub fn encode_for_write_register(correction_value: Temperature) -> Result<Word, Error> {
720 correction_value.encode_for_write_register()
721 }
722
723 pub fn channel_address(channel: Channel) -> u16 {
733 Self::ADDRESS + (*channel as u16)
734 }
735}
736
737impl IntoIterator for TemperatureCorrection {
738 type Item = Temperature;
739 type IntoIter = std::array::IntoIter<Temperature, NUMBER_OF_CHANNELS>;
740
741 fn into_iter(self) -> Self::IntoIter {
742 self.0.into_iter()
743 }
744}
745
746impl<'a> IntoIterator for &'a TemperatureCorrection {
747 type Item = &'a Temperature;
748 type IntoIter = std::slice::Iter<'a, Temperature>;
749
750 fn into_iter(self) -> Self::IntoIter {
751 self.0.iter()
752 }
753}
754
755impl std::fmt::Display for TemperatureCorrection {
756 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
758 let corr_strs: Vec<String> = self.0.iter().map(|t| t.to_string()).collect();
759 write!(f, "{}", corr_strs.join(", "))
760 }
761}
762
763impl std::ops::Index<usize> for TemperatureCorrection {
764 type Output = Temperature;
765 fn index(&self, index: usize) -> &Self::Output {
771 &self.0[index]
772 }
773}
774
775#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
779#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
780pub struct AutomaticReport(u8);
781
782impl AutomaticReport {
783 pub const ADDRESS: u16 = 0x00FD;
785 pub const QUANTITY: u16 = 1;
787
788 pub const DURATION_MIN: u8 = 0;
790 pub const DURATION_MAX: u8 = 255;
792
793 pub const DISABLED: AutomaticReport = AutomaticReport(0);
795
796 pub fn decode_from_holding_registers(words: &[Word]) -> Result<Self, Error> {
811 if words.len() != Self::QUANTITY as usize {
812 return Err(Error::UnexpectedDataLength {
813 expected: Self::QUANTITY as usize,
814 got: words.len(),
815 });
816 }
817 let word_value = words[0];
818
819 if word_value & 0xFF00 != 0 {
820 return Err(Error::InvalidData {
821 details: format!(
822 "Upper byte of automatic report register is non-zero (value: {word_value:#06X})"
823 ),
824 });
825 }
826
827 Ok(Self::from(word_value as u8))
829 }
830
831 pub fn encode_for_write_register(&self) -> Word {
837 self.0 as Word
838 }
839
840 pub fn as_secs(&self) -> u8 {
842 self.0
843 }
844
845 pub fn as_duration(&self) -> Duration {
848 Duration::from_secs(self.0 as u64)
849 }
850
851 pub fn is_disabled(&self) -> bool {
853 self.0 == Self::DURATION_MIN
854 }
855}
856
857impl std::ops::Deref for AutomaticReport {
858 type Target = u8;
859 fn deref(&self) -> &Self::Target {
861 &self.0
862 }
863}
864
865impl From<u8> for AutomaticReport {
866 fn from(interval_seconds: u8) -> AutomaticReport {
870 Self(interval_seconds)
872 }
873}
874
875#[derive(Error, Debug, PartialEq, Eq)]
877#[error(
878 "The duration {0} seconds is outside the valid automatic report range [{min}, {max}] seconds",
879 min = AutomaticReport::DURATION_MIN,
880 max = AutomaticReport::DURATION_MAX
881)]
882pub struct ErrorDurationOutOfRange(
883 pub u64,
885);
886
887impl TryFrom<Duration> for AutomaticReport {
888 type Error = ErrorDurationOutOfRange;
889
890 fn try_from(value: Duration) -> Result<Self, Self::Error> {
894 let secs = value.as_secs();
895 if (Self::DURATION_MIN as u64..=Self::DURATION_MAX as u64).contains(&secs) {
896 Ok(Self(secs as u8))
897 } else {
898 Err(ErrorDurationOutOfRange(secs))
899 }
900 }
901}
902
903impl std::fmt::Display for AutomaticReport {
904 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
906 write!(f, "{}s", self.0)
907 }
908}
909
910#[derive(Debug, Clone, Copy)]
912pub struct FactoryReset;
913
914impl FactoryReset {
915 pub const ADDRESS: u16 = 0x00FF;
917 const DATA: u16 = 5;
919
920 pub fn encode_for_write_register() -> Word {
927 Self::DATA
928 }
929}
930
931#[cfg(test)]
932mod tests {
933 use super::*;
934 use assert_matches::assert_matches;
935
936 #[test]
938 fn temperature_decode() {
939 assert_eq!(
941 Temperature::decode_from_holding_registers(219),
942 Temperature::try_from(21.9).unwrap()
943 );
944 assert_eq!(
945 Temperature::decode_from_holding_registers(100),
946 Temperature::try_from(10.0).unwrap()
947 );
948 assert_eq!(
949 Temperature::decode_from_holding_registers(0),
950 Temperature::try_from(0.0).unwrap()
951 );
952 assert_eq!(
954 Temperature::decode_from_holding_registers(65424),
955 Temperature::try_from(-11.2).unwrap()
956 );
957 assert_eq!(
958 Temperature::decode_from_holding_registers(65506),
959 Temperature::try_from(-3.0).unwrap()
960 );
961 assert_eq!(
963 Temperature::decode_from_holding_registers(0xFFFF),
964 Temperature::try_from(-0.1).unwrap()
965 ); assert_eq!(
970 Temperature::decode_from_holding_registers(0x8001),
971 Temperature::try_from(-3276.7).unwrap()
972 ); assert!(Temperature::decode_from_holding_registers(i16::MIN as u16).is_nan()); assert_eq!(
975 Temperature::decode_from_holding_registers(32769),
976 Temperature::try_from(-3276.7).unwrap()
977 ); assert_eq!(
981 Temperature::decode_from_holding_registers(32767),
982 Temperature::try_from(3276.7).unwrap()
983 );
984 assert!(Temperature::decode_from_holding_registers(32768).is_nan());
986 assert!(Temperature::decode_from_holding_registers(0x8000).is_nan());
987 }
988
989 #[test]
990 fn temperature_encode() {
991 assert_eq!(
993 Temperature::try_from(21.9)
994 .unwrap()
995 .encode_for_write_register(),
996 Ok(219)
997 );
998 assert_eq!(
999 Temperature::try_from(10.0)
1000 .unwrap()
1001 .encode_for_write_register(),
1002 Ok(100)
1003 );
1004 assert_eq!(
1005 Temperature::try_from(0.0)
1006 .unwrap()
1007 .encode_for_write_register(),
1008 Ok(0)
1009 );
1010 assert_eq!(
1012 Temperature::try_from(-11.2)
1013 .unwrap()
1014 .encode_for_write_register(),
1015 Ok((-11.2 * 10.0) as i16 as u16)
1016 ); assert_eq!(
1018 Temperature::try_from(-3.0)
1019 .unwrap()
1020 .encode_for_write_register(),
1021 Ok((-3.0 * 10.0) as i16 as u16)
1022 ); assert_eq!(
1024 Temperature::try_from(-0.1)
1025 .unwrap()
1026 .encode_for_write_register(),
1027 Ok((-0.1 * 10.0) as i16 as u16)
1028 ); assert_eq!(
1032 Temperature::try_from(Temperature::MAX)
1033 .unwrap()
1034 .encode_for_write_register(),
1035 Ok(32767)
1036 );
1037 assert_eq!(
1038 Temperature::try_from(Temperature::MIN)
1039 .unwrap()
1040 .encode_for_write_register(),
1041 Ok((Temperature::MIN * 10.0) as i16 as u16)
1042 ); }
1044
1045 #[test]
1046 fn temperature_encode_nan() {
1047 assert_matches!(Temperature::NAN.encode_for_write_register(), Err(..));
1051 }
1052
1053 #[test]
1054 fn temperature_try_from() {
1055 assert_matches!(Temperature::try_from(21.9), Ok(t) if *t == 21.9);
1057 assert_matches!(Temperature::try_from(-11.2), Ok(t) if *t == -11.2);
1058 assert_matches!(Temperature::try_from(Temperature::MAX), Ok(t) if *t == Temperature::MAX);
1059 assert_matches!(Temperature::try_from(Temperature::MIN), Ok(t) if *t == Temperature::MIN);
1060
1061 let max_err_val = Temperature::MAX + 0.1;
1063 assert_matches!(Temperature::try_from(max_err_val), Err(ErrorDegreeCelsiusOutOfRange(value)) if value == max_err_val);
1064
1065 let min_err_val = Temperature::MIN - 0.1;
1066 assert_matches!(Temperature::try_from(min_err_val), Err(ErrorDegreeCelsiusOutOfRange(value)) if value == min_err_val);
1067
1068 assert_matches!(
1069 Temperature::try_from(f32::NAN),
1070 Err(ErrorDegreeCelsiusOutOfRange(..))
1071 );
1072 }
1073
1074 #[test]
1075 fn temperature_display() {
1076 assert_eq!(Temperature::try_from(21.9).unwrap().to_string(), "21.9");
1077 assert_eq!(Temperature::try_from(-3.0).unwrap().to_string(), "-3.0");
1078 assert_eq!(Temperature::try_from(0.0).unwrap().to_string(), "0.0");
1079 assert_eq!(Temperature::NAN.to_string(), "NAN");
1080 }
1081
1082 #[test]
1084 fn address_try_from() {
1085 assert_matches!(Address::try_from(0), Err(ErrorAddressOutOfRange(0)));
1086 assert_matches!(Address::try_from(Address::MIN), Ok(a) if *a == Address::MIN);
1087 assert_matches!(Address::try_from(100), Ok(a) if *a == 100);
1088 assert_matches!(Address::try_from(Address::MAX), Ok(a) if *a == Address::MAX);
1089 assert_matches!(
1090 Address::try_from(Address::MAX + 1),
1091 Err(ErrorAddressOutOfRange(248))
1092 );
1093 assert_matches!(Address::try_from(255), Err(ErrorAddressOutOfRange(255)));
1094 }
1096
1097 #[test]
1098 fn address_decode() {
1099 assert_eq!(
1100 Address::decode_from_holding_registers(&[0x0001]).unwrap(),
1101 Address::try_from(1).unwrap()
1102 );
1103 assert_eq!(
1104 Address::decode_from_holding_registers(&[0x00F7]).unwrap(),
1105 Address::try_from(247).unwrap()
1106 );
1107 assert_eq!(
1109 Address::decode_from_holding_registers(&[0x0001]).unwrap(),
1110 Address::try_from(1).unwrap()
1111 );
1112 }
1113
1114 #[test]
1115 fn address_decode_empty() {
1116 assert_matches!(Address::decode_from_holding_registers(&[]), Err(..));
1117 }
1118
1119 #[test]
1120 fn address_decode_invalid() {
1121 assert_matches!(Address::decode_from_holding_registers(&[0x0000]), Err(..));
1122 }
1124
1125 #[test]
1126 fn address_encode() {
1127 assert_eq!(
1128 Address::try_from(1).unwrap().encode_for_write_register(),
1129 0x0001
1130 );
1131 assert_eq!(
1132 Address::try_from(247).unwrap().encode_for_write_register(),
1133 0x00F7
1134 );
1135 assert_eq!(Address::default().encode_for_write_register(), 0x0001);
1136 }
1137
1138 #[test]
1139 fn address_display() {
1140 assert_eq!(Address::try_from(1).unwrap().to_string(), "0x01");
1141 assert_eq!(Address::try_from(25).unwrap().to_string(), "0x19");
1142 assert_eq!(Address::try_from(247).unwrap().to_string(), "0xf7");
1143 }
1144
1145 #[test]
1146 fn address_constants() {
1147 assert_eq!(*Address::BROADCAST, 0xFF);
1148 }
1149
1150 #[test]
1152 fn baudrate_decode() {
1153 assert_matches!(
1154 BaudRate::decode_from_holding_registers(&[0]).unwrap(),
1155 BaudRate::B1200
1156 );
1157 assert_matches!(
1158 BaudRate::decode_from_holding_registers(&[1]).unwrap(),
1159 BaudRate::B2400
1160 );
1161 assert_matches!(
1162 BaudRate::decode_from_holding_registers(&[2]).unwrap(),
1163 BaudRate::B4800
1164 );
1165 assert_matches!(
1166 BaudRate::decode_from_holding_registers(&[3]).unwrap(),
1167 BaudRate::B9600
1168 );
1169 assert_matches!(
1170 BaudRate::decode_from_holding_registers(&[4]).unwrap(),
1171 BaudRate::B19200
1172 );
1173 }
1174
1175 #[test]
1176 fn baudrate_decode_panics_on_invalid_value_high1() {
1177 assert_matches!(BaudRate::decode_from_holding_registers(&[5]), Err(..));
1178 }
1179
1180 #[test]
1181 fn baudrate_decode_panics_on_invalid_value_zero() {
1182 assert_matches!(BaudRate::decode_from_holding_registers(&[]), Err(..));
1183 }
1184
1185 #[test]
1186 fn baudrate_encode() {
1187 assert_eq!(BaudRate::B1200.encode_for_write_register(), 0);
1188 assert_eq!(BaudRate::B2400.encode_for_write_register(), 1);
1189 assert_eq!(BaudRate::B4800.encode_for_write_register(), 2);
1190 assert_eq!(BaudRate::B9600.encode_for_write_register(), 3);
1191 assert_eq!(BaudRate::B19200.encode_for_write_register(), 4);
1192 assert_eq!(BaudRate::default().encode_for_write_register(), 3);
1193 }
1194
1195 #[test]
1196 fn baudrate_try_from_u16() {
1197 assert_matches!(BaudRate::try_from(1200), Ok(BaudRate::B1200));
1198 assert_matches!(BaudRate::try_from(2400), Ok(BaudRate::B2400));
1199 assert_matches!(BaudRate::try_from(4800), Ok(BaudRate::B4800));
1200 assert_matches!(BaudRate::try_from(9600), Ok(BaudRate::B9600));
1201 assert_matches!(BaudRate::try_from(19200), Ok(BaudRate::B19200));
1202 assert_matches!(BaudRate::try_from(0), Err(ErrorInvalidBaudRate(0)));
1204 assert_matches!(BaudRate::try_from(1000), Err(ErrorInvalidBaudRate(1000)));
1205 assert_matches!(BaudRate::try_from(38400), Err(ErrorInvalidBaudRate(38400)));
1206 }
1207
1208 #[test]
1209 fn baudrate_from_baudrate_to_u16() {
1210 assert_eq!(u16::from(BaudRate::B1200), 1200);
1211 assert_eq!(u16::from(BaudRate::B2400), 2400);
1212 assert_eq!(u16::from(BaudRate::B4800), 4800);
1213 assert_eq!(u16::from(BaudRate::B9600), 9600);
1214 assert_eq!(u16::from(BaudRate::B19200), 19200);
1215 assert_eq!(u16::from(BaudRate::default()), 9600);
1216 }
1217
1218 #[test]
1219 fn baudrate_display() {
1220 assert_eq!(BaudRate::B1200.to_string(), "1200");
1221 assert_eq!(BaudRate::B9600.to_string(), "9600");
1222 assert_eq!(BaudRate::B19200.to_string(), "19200");
1223 assert_eq!(BaudRate::default().to_string(), "9600");
1224 }
1225
1226 #[test]
1228 fn channel_try_from() {
1229 assert_matches!(Channel::try_from(Channel::MIN), Ok(c) if *c == Channel::MIN);
1230 assert_matches!(Channel::try_from(3), Ok(c) if *c == 3);
1231 assert_matches!(Channel::try_from(Channel::MAX), Ok(c) if *c == Channel::MAX);
1232 assert_matches!(
1235 Channel::try_from(Channel::MAX + 1),
1236 Err(ErrorChannelOutOfRange(8))
1237 );
1238 assert_matches!(Channel::try_from(255), Err(ErrorChannelOutOfRange(255)));
1239 }
1240
1241 #[test]
1242 fn channel_new() {
1243 assert_eq!(*Channel::try_from(0).unwrap(), 0);
1244 assert_eq!(*Channel::try_from(7).unwrap(), 7);
1245 }
1246
1247 #[test]
1248 fn channel_display() {
1249 assert_eq!(Channel::try_from(0).unwrap().to_string(), "0");
1250 assert_eq!(Channel::try_from(7).unwrap().to_string(), "7");
1251 }
1252
1253 #[test]
1255 fn temperatures_decode() {
1256 let words = [219, 65424, 100, 65506, 32767, 32769, 0, 32768]; let temps = Temperatures::decode_from_holding_registers(&words).unwrap();
1258
1259 assert_eq!(temps[0], Temperature::try_from(21.9).unwrap());
1260 assert_eq!(temps[1], Temperature::try_from(-11.2).unwrap());
1261 assert_eq!(temps[2], Temperature::try_from(10.0).unwrap());
1262 assert_eq!(temps[3], Temperature::try_from(-3.0).unwrap());
1263 assert_eq!(temps[4], Temperature::try_from(3276.7).unwrap()); assert_eq!(temps[5], Temperature::try_from(-3276.7).unwrap()); assert_eq!(temps[6], Temperature::try_from(0.0).unwrap());
1266 assert!(temps[7].is_nan()); let mut iter = temps.iter();
1270 assert_eq!(iter.next(), Some(&Temperature::try_from(21.9).unwrap()));
1271 assert_eq!(iter.next(), Some(&Temperature::try_from(-11.2).unwrap()));
1272 assert!(iter.nth(5).expect("7th element (index 7)").is_nan());
1274 assert_eq!(iter.next(), None);
1275
1276 assert_eq!(temps[0], Temperature::try_from(21.9).unwrap());
1278 assert!(temps[7].is_nan());
1279 }
1280
1281 #[test]
1282 fn temperatures_decode_wrong_size() {
1283 let words = [219, 65424]; assert_matches!(Temperatures::decode_from_holding_registers(&words), Err(..));
1285 }
1286
1287 #[test]
1288 #[should_panic]
1289 fn temperatures_index_out_of_bounds() {
1290 let words = [0; NUMBER_OF_CHANNELS];
1291 let temps = Temperatures::decode_from_holding_registers(&words).unwrap();
1292 let _ = temps[NUMBER_OF_CHANNELS]; }
1294
1295 #[test]
1296 fn temperatures_display() {
1297 let words = [219, 65424, 32768, 0, 0, 0, 0, 0];
1298 let temps = Temperatures::decode_from_holding_registers(&words).unwrap();
1299 assert_eq!(
1300 temps.to_string(),
1301 "21.9, -11.2, NAN, 0.0, 0.0, 0.0, 0.0, 0.0"
1302 );
1303 }
1304
1305 #[test]
1307 fn temperature_correction_decode() {
1308 let words = [10, 65526, 0, 32768, 1, 2, 3, 4]; let corrections = TemperatureCorrection::decode_from_holding_registers(&words).unwrap();
1311
1312 assert_eq!(corrections[0], Temperature::try_from(1.0).unwrap());
1313 assert_eq!(corrections[1], Temperature::try_from(-1.0).unwrap());
1314 assert_eq!(corrections[2], Temperature::try_from(0.0).unwrap());
1315 assert!(corrections[3].is_nan());
1316 assert_eq!(corrections.as_slice().len(), 8);
1317 }
1318
1319 #[test]
1320 fn temperature_correction_encode_single() {
1321 assert_eq!(
1323 TemperatureCorrection::encode_for_write_register(Temperature::try_from(2.0).unwrap()),
1324 Ok(20)
1325 );
1326 assert_eq!(
1327 TemperatureCorrection::encode_for_write_register(Temperature::try_from(-1.5).unwrap()),
1328 Ok((-1.5 * 10.0) as i16 as Word)
1329 ); assert_eq!(
1331 TemperatureCorrection::encode_for_write_register(Temperature::try_from(0.0).unwrap()),
1332 Ok(0)
1333 );
1334 }
1335
1336 #[test]
1337 fn temperature_correction_encode_nan() {
1338 assert_matches!(
1340 TemperatureCorrection::encode_for_write_register(Temperature::NAN),
1341 Err(..)
1342 );
1343 }
1344
1345 #[test]
1346 fn temperature_correction_channel_address() {
1347 assert_eq!(
1348 TemperatureCorrection::channel_address(Channel::try_from(0).unwrap()),
1349 TemperatureCorrection::ADDRESS + 0
1350 ); assert_eq!(
1352 TemperatureCorrection::channel_address(Channel::try_from(1).unwrap()),
1353 TemperatureCorrection::ADDRESS + 1
1354 ); assert_eq!(
1356 TemperatureCorrection::channel_address(Channel::try_from(7).unwrap()),
1357 TemperatureCorrection::ADDRESS + 7
1358 ); assert_eq!(TemperatureCorrection::ADDRESS, 0x0008);
1361 assert_eq!(TemperatureCorrection::QUANTITY, 8);
1362 }
1363
1364 #[test]
1365 fn temperature_correction_display() {
1366 let words = [10, 65526, 0, 32768, 0, 0, 0, 0];
1367 let corr = TemperatureCorrection::decode_from_holding_registers(&words).unwrap();
1368 assert_eq!(corr.to_string(), "1.0, -1.0, 0.0, NAN, 0.0, 0.0, 0.0, 0.0");
1369 }
1370
1371 #[test]
1373 fn automatic_report_decode() {
1374 assert_matches!(AutomaticReport::decode_from_holding_registers(&[0x0000]).unwrap(), report if *report == 0);
1375 assert_matches!(AutomaticReport::decode_from_holding_registers(&[0x0001]).unwrap(), report if *report == 1);
1376 assert_matches!(AutomaticReport::decode_from_holding_registers(&[0x000A]).unwrap(), report if *report == 10); assert_matches!(AutomaticReport::decode_from_holding_registers(&[0x00FF]).unwrap(), report if *report == 255);
1378 }
1380
1381 #[test]
1382 fn automatic_report_decode_invalid() {
1383 assert_matches!(
1384 AutomaticReport::decode_from_holding_registers(&[0x0100]),
1385 Err(..)
1386 ); }
1388
1389 #[test]
1390 fn automatic_report_encode() {
1391 assert_eq!(AutomaticReport(0).encode_for_write_register(), 0);
1392 assert_eq!(AutomaticReport(1).encode_for_write_register(), 1);
1393 assert_eq!(AutomaticReport(10).encode_for_write_register(), 10);
1394 assert_eq!(AutomaticReport(255).encode_for_write_register(), 255);
1395 assert_eq!(AutomaticReport::DISABLED.encode_for_write_register(), 0);
1396 }
1397
1398 #[test]
1399 fn automatic_report_try_from_duration() {
1400 assert_matches!(AutomaticReport::try_from(Duration::ZERO), Ok(r) if r.as_secs() == 0);
1401 assert_matches!(AutomaticReport::try_from(Duration::from_secs(1)), Ok(r) if r.as_secs() == 1);
1402 assert_matches!(AutomaticReport::try_from(Duration::from_secs(10)), Ok(r) if r.as_secs() == 10);
1403 assert_matches!(AutomaticReport::try_from(Duration::from_secs(255)), Ok(r) if r.as_secs() == 255);
1404 assert_matches!(AutomaticReport::try_from(Duration::from_millis(10500)), Ok(r) if r.as_secs() == 10);
1406
1407 let invalid_secs = (AutomaticReport::DURATION_MAX as u64) + 1;
1409 assert_matches!(AutomaticReport::try_from(Duration::from_secs(invalid_secs)), Err(ErrorDurationOutOfRange(value_secs)) if value_secs == invalid_secs);
1410 }
1411
1412 #[test]
1413 fn automatic_report_helpers() {
1414 let report_10s = AutomaticReport::try_from(10).unwrap();
1415 let report_disabled = AutomaticReport::DISABLED;
1416
1417 assert_eq!(report_10s.as_secs(), 10);
1418 assert_eq!(report_10s.as_duration(), Duration::from_secs(10));
1419 assert!(!report_10s.is_disabled());
1420
1421 assert_eq!(report_disabled.as_secs(), 0);
1422 assert_eq!(report_disabled.as_duration(), Duration::ZERO);
1423 assert!(report_disabled.is_disabled());
1424 }
1425
1426 #[test]
1427 fn automatic_report_display() {
1428 assert_eq!(AutomaticReport(0).to_string(), "0s");
1429 assert_eq!(AutomaticReport(1).to_string(), "1s");
1430 assert_eq!(AutomaticReport(255).to_string(), "255s");
1431 }
1432
1433 #[test]
1435 fn factory_reset_encode() {
1436 assert_eq!(FactoryReset::encode_for_write_register(), 5);
1437 }
1438
1439 #[test]
1440 fn factory_reset_address() {
1441 assert_eq!(FactoryReset::ADDRESS, BaudRate::ADDRESS);
1443 }
1444}