1use crate::values::{ColorFormat, EnumValue, Value, ValueError};
2use std::collections::HashMap;
3use std::fmt::{self, Debug, Display, Formatter};
4use std::ops::RangeInclusive;
5use std::str::FromStr;
6use std::time::Duration;
7use thiserror::Error;
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub enum State {
13 Unknown,
16 Init,
18 Ready,
20 Disconnected,
22 Sleeping,
24 Lost,
27 Alert,
30}
31
32impl State {
33 fn as_str(&self) -> &'static str {
34 match self {
35 Self::Unknown => "unknown",
36 Self::Init => "init",
37 Self::Ready => "ready",
38 Self::Disconnected => "disconnected",
39 Self::Sleeping => "sleeping",
40 Self::Lost => "lost",
41 Self::Alert => "alert",
42 }
43 }
44}
45
46#[derive(Clone, Debug, Error, Eq, PartialEq)]
50#[error("Invalid state '{0}'")]
51pub struct ParseStateError(String);
52
53impl FromStr for State {
54 type Err = ParseStateError;
55
56 fn from_str(s: &str) -> Result<Self, Self::Err> {
57 match s {
58 "init" => Ok(Self::Init),
59 "ready" => Ok(Self::Ready),
60 "disconnected" => Ok(Self::Disconnected),
61 "sleeping" => Ok(Self::Sleeping),
62 "lost" => Ok(Self::Lost),
63 "alert" => Ok(Self::Alert),
64 _ => Err(ParseStateError(s.to_owned())),
65 }
66 }
67}
68
69impl Display for State {
70 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
71 f.write_str(self.as_str())
72 }
73}
74
75#[derive(Clone, Copy, Debug, Eq, PartialEq)]
77pub enum Datatype {
78 Integer,
80 Float,
82 Boolean,
84 String,
86 Enum,
89 Color,
93}
94
95impl Datatype {
96 fn as_str(&self) -> &'static str {
97 match self {
98 Self::Integer => "integer",
99 Self::Float => "float",
100 Self::Boolean => "boolean",
101 Self::String => "string",
102 Self::Enum => "enum",
103 Self::Color => "color",
104 }
105 }
106}
107
108impl Display for Datatype {
109 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
110 f.write_str(self.as_str())
111 }
112}
113
114#[derive(Clone, Debug, Error, Eq, PartialEq)]
117#[error("Invalid datatype '{0}'")]
118pub struct ParseDatatypeError(String);
119
120impl FromStr for Datatype {
121 type Err = ParseDatatypeError;
122
123 fn from_str(s: &str) -> Result<Self, Self::Err> {
124 match s {
125 "integer" => Ok(Self::Integer),
126 "float" => Ok(Self::Float),
127 "boolean" => Ok(Self::Boolean),
128 "string" => Ok(Self::String),
129 "enum" => Ok(Self::Enum),
130 "color" => Ok(Self::Color),
131 _ => Err(ParseDatatypeError(s.to_owned())),
132 }
133 }
134}
135
136#[derive(Clone, Debug, Eq, PartialEq)]
141pub struct Property {
142 pub id: String,
145
146 pub name: Option<String>,
149
150 pub datatype: Option<Datatype>,
153
154 pub settable: bool,
158
159 pub retained: bool,
161
162 pub unit: Option<String>,
166
167 pub format: Option<String>,
174
175 pub value: Option<String>,
180}
181
182impl Property {
183 pub(crate) fn new(id: &str) -> Property {
189 Property {
190 id: id.to_owned(),
191 name: None,
192 datatype: None,
193 settable: false,
194 retained: true,
195 unit: None,
196 format: None,
197 value: None,
198 }
199 }
200
201 pub fn has_required_attributes(&self) -> bool {
205 self.name.is_some() && self.datatype.is_some()
206 }
207
208 pub fn value<T: Value>(&self) -> Result<T, ValueError> {
212 T::valid_for(self.datatype, &self.format)?;
213
214 match self.value {
215 None => Err(ValueError::Unknown),
216 Some(ref value) => value.parse().map_err(|_| ValueError::ParseFailed {
217 value: value.to_owned(),
218 datatype: T::datatype(),
219 }),
220 }
221 }
222
223 pub fn color_format(&self) -> Result<ColorFormat, ValueError> {
225 if let Some(actual) = self.datatype {
228 if actual != Datatype::Color {
229 return Err(ValueError::WrongDatatype {
230 expected: Datatype::Color,
231 actual,
232 });
233 }
234 }
235
236 match self.format {
237 None => Err(ValueError::Unknown),
238 Some(ref format) => format.parse(),
239 }
240 }
241
242 pub fn enum_values(&self) -> Result<Vec<&str>, ValueError> {
244 EnumValue::valid_for(self.datatype, &self.format)?;
245
246 match self.format {
247 None => Err(ValueError::Unknown),
248 Some(ref format) => {
249 if format.is_empty() {
250 Err(ValueError::WrongFormat {
251 format: "".to_owned(),
252 })
253 } else {
254 Ok(format.split(',').collect())
255 }
256 }
257 }
258 }
259
260 pub fn range<T: Value + Copy>(&self) -> Result<RangeInclusive<T>, ValueError> {
263 T::valid_for(self.datatype, &self.format)?;
264
265 match self.format {
266 None => Err(ValueError::Unknown),
267 Some(ref format) => {
268 if let [Ok(start), Ok(end)] = format
269 .splitn(2, ':')
270 .map(|part| part.parse())
271 .collect::<Vec<_>>()
272 .as_slice()
273 {
274 Ok(RangeInclusive::new(*start, *end))
275 } else {
276 Err(ValueError::WrongFormat {
277 format: format.to_owned(),
278 })
279 }
280 }
281 }
282 }
283}
284
285#[derive(Clone, Debug, Eq, PartialEq)]
290pub struct Node {
291 pub id: String,
294
295 pub name: Option<String>,
298
299 pub node_type: Option<String>,
302
303 pub properties: HashMap<String, Property>,
305}
306
307impl Node {
308 pub(crate) fn new(id: &str) -> Node {
314 Node {
315 id: id.to_owned(),
316 name: None,
317 node_type: None,
318 properties: HashMap::new(),
319 }
320 }
321
322 pub(crate) fn add_property(&mut self, property: Property) {
324 self.properties.insert(property.id.clone(), property);
325 }
326
327 pub fn has_required_attributes(&self) -> bool {
331 self.name.is_some()
332 && self.node_type.is_some()
333 && !self.properties.is_empty()
334 && self
335 .properties
336 .values()
337 .all(|property| property.has_required_attributes())
338 }
339}
340
341#[derive(Clone, Debug, Eq, PartialEq)]
343pub struct Extension {
344 pub id: String,
347 pub version: String,
349 pub homie_versions: Vec<String>,
351}
352
353#[derive(Clone, Debug, Error, Eq, PartialEq)]
355#[error("Invalid extension '{0}'")]
356pub struct ParseExtensionError(String);
357
358impl FromStr for Extension {
359 type Err = ParseExtensionError;
360
361 fn from_str(s: &str) -> Result<Self, Self::Err> {
362 let parts: Vec<_> = s.split(':').collect();
363 if let [id, version, homie_versions] = parts.as_slice() {
364 if let Some(homie_versions) = homie_versions.strip_prefix('[') {
365 if let Some(homie_versions) = homie_versions.strip_suffix(']') {
366 return Ok(Extension {
367 id: (*id).to_owned(),
368 version: (*version).to_owned(),
369 homie_versions: homie_versions.split(';').map(|p| p.to_owned()).collect(),
370 });
371 }
372 }
373 }
374 Err(ParseExtensionError(s.to_owned()))
375 }
376}
377
378#[derive(Clone, Debug, PartialEq)]
383pub struct Device {
384 pub id: String,
387
388 pub homie_version: String,
390
391 pub name: Option<String>,
394
395 pub state: State,
398
399 pub implementation: Option<String>,
401
402 pub nodes: HashMap<String, Node>,
404
405 pub extensions: Vec<Extension>,
407
408 pub local_ip: Option<String>,
410
411 pub mac: Option<String>,
413
414 pub firmware_name: Option<String>,
416
417 pub firmware_version: Option<String>,
419
420 pub stats_interval: Option<Duration>,
422
423 pub stats_uptime: Option<Duration>,
425
426 pub stats_signal: Option<i64>,
428
429 pub stats_cputemp: Option<f64>,
431
432 pub stats_cpuload: Option<i64>,
434
435 pub stats_battery: Option<i64>,
437
438 pub stats_freeheap: Option<u64>,
440
441 pub stats_supply: Option<f64>,
443}
444
445impl Device {
446 pub(crate) fn new(id: &str, homie_version: &str) -> Device {
453 Device {
454 id: id.to_owned(),
455 homie_version: homie_version.to_owned(),
456 name: None,
457 state: State::Unknown,
458 implementation: None,
459 nodes: HashMap::new(),
460 extensions: Vec::default(),
461 local_ip: None,
462 mac: None,
463 firmware_name: None,
464 firmware_version: None,
465 stats_interval: None,
466 stats_uptime: None,
467 stats_signal: None,
468 stats_cputemp: None,
469 stats_cpuload: None,
470 stats_battery: None,
471 stats_freeheap: None,
472 stats_supply: None,
473 }
474 }
475
476 pub(crate) fn add_node(&mut self, node: Node) {
478 self.nodes.insert(node.id.clone(), node);
479 }
480
481 pub fn has_required_attributes(&self) -> bool {
485 self.name.is_some()
486 && self.state != State::Unknown
487 && self
488 .nodes
489 .values()
490 .all(|node| node.has_required_attributes())
491 }
492}
493
494#[cfg(test)]
495mod tests {
496 use super::*;
497 use crate::values::{ColorHsv, ColorRgb, EnumValue};
498
499 #[test]
500 fn extension_parse_succeeds() {
501 let legacy_stats: Extension = "org.homie.legacy-stats:0.1.1:[4.x]".parse().unwrap();
502 assert_eq!(legacy_stats.id, "org.homie.legacy-stats");
503 assert_eq!(legacy_stats.version, "0.1.1");
504 assert_eq!(legacy_stats.homie_versions, &["4.x"]);
505
506 let meta: Extension = "eu.epnw.meta:1.1.0:[3.0.1;4.x]".parse().unwrap();
507 assert_eq!(meta.id, "eu.epnw.meta");
508 assert_eq!(meta.version, "1.1.0");
509 assert_eq!(meta.homie_versions, &["3.0.1", "4.x"]);
510
511 let minimal: Extension = "a:0:[]".parse().unwrap();
512 assert_eq!(minimal.id, "a");
513 assert_eq!(minimal.version, "0");
514 assert_eq!(minimal.homie_versions, &[""]);
515 }
516
517 #[test]
518 fn extension_parse_fails() {
519 assert_eq!(
520 "".parse::<Extension>(),
521 Err(ParseExtensionError("".to_owned()))
522 );
523 assert_eq!(
524 "test.blah:1.2.3".parse::<Extension>(),
525 Err(ParseExtensionError("test.blah:1.2.3".to_owned()))
526 );
527 assert_eq!(
528 "test.blah:1.2.3:4.x".parse::<Extension>(),
529 Err(ParseExtensionError("test.blah:1.2.3:4.x".to_owned()))
530 );
531 }
532
533 #[test]
534 fn property_integer_parse() {
535 let mut property = Property::new("property_id");
536
537 assert_eq!(property.value::<i64>(), Err(ValueError::Unknown));
539
540 property.value = Some("-".to_owned());
542 assert_eq!(
543 property.value::<i64>(),
544 Err(ValueError::ParseFailed {
545 value: "-".to_owned(),
546 datatype: Datatype::Integer,
547 })
548 );
549
550 property.value = Some("42".to_owned());
552 assert_eq!(property.value(), Ok(42));
553
554 property.datatype = Some(Datatype::Integer);
556 assert_eq!(property.value(), Ok(42));
557
558 property.value = Some("-66".to_owned());
560 assert_eq!(property.value(), Ok(-66));
561
562 property.datatype = Some(Datatype::Float);
564 assert_eq!(
565 property.value::<i64>(),
566 Err(ValueError::WrongDatatype {
567 actual: Datatype::Float,
568 expected: Datatype::Integer,
569 })
570 );
571 }
572
573 #[test]
574 fn property_float_parse() {
575 let mut property = Property::new("property_id");
576
577 assert_eq!(property.value::<f64>(), Err(ValueError::Unknown));
579
580 property.value = Some("-".to_owned());
582 assert_eq!(
583 property.value::<f64>(),
584 Err(ValueError::ParseFailed {
585 value: "-".to_owned(),
586 datatype: Datatype::Float,
587 })
588 );
589
590 property.value = Some("42.36".to_owned());
592 assert_eq!(property.value(), Ok(42.36));
593
594 property.datatype = Some(Datatype::Float);
596 assert_eq!(property.value(), Ok(42.36));
597
598 property.datatype = Some(Datatype::Integer);
600 assert_eq!(
601 property.value::<f64>(),
602 Err(ValueError::WrongDatatype {
603 actual: Datatype::Integer,
604 expected: Datatype::Float,
605 })
606 );
607 }
608
609 #[test]
610 fn property_color_parse() {
611 let mut property = Property::new("property_id");
612
613 assert_eq!(property.value::<ColorRgb>(), Err(ValueError::Unknown));
615 assert_eq!(property.value::<ColorHsv>(), Err(ValueError::Unknown));
616
617 property.value = Some("".to_owned());
619 assert_eq!(
620 property.value::<ColorRgb>(),
621 Err(ValueError::ParseFailed {
622 value: "".to_owned(),
623 datatype: Datatype::Color,
624 })
625 );
626
627 property.value = Some("12,34,56".to_owned());
629 assert_eq!(
630 property.value(),
631 Ok(ColorRgb {
632 r: 12,
633 g: 34,
634 b: 56
635 })
636 );
637 assert_eq!(
638 property.value(),
639 Ok(ColorHsv {
640 h: 12,
641 s: 34,
642 v: 56
643 })
644 );
645
646 property.datatype = Some(Datatype::Color);
648 assert_eq!(
649 property.value(),
650 Ok(ColorRgb {
651 r: 12,
652 g: 34,
653 b: 56
654 })
655 );
656 assert_eq!(
657 property.value(),
658 Ok(ColorHsv {
659 h: 12,
660 s: 34,
661 v: 56
662 })
663 );
664
665 property.format = Some("rgb".to_owned());
667 assert_eq!(
668 property.value(),
669 Ok(ColorRgb {
670 r: 12,
671 g: 34,
672 b: 56
673 })
674 );
675 assert_eq!(
676 property.value::<ColorHsv>(),
677 Err(ValueError::WrongFormat {
678 format: "rgb".to_owned()
679 })
680 );
681
682 property.datatype = Some(Datatype::Integer);
684 assert_eq!(
685 property.value::<ColorRgb>(),
686 Err(ValueError::WrongDatatype {
687 actual: Datatype::Integer,
688 expected: Datatype::Color,
689 })
690 );
691 assert_eq!(
692 property.value::<ColorHsv>(),
693 Err(ValueError::WrongDatatype {
694 actual: Datatype::Integer,
695 expected: Datatype::Color,
696 })
697 );
698 }
699
700 #[test]
701 fn property_enum_parse() {
702 let mut property = Property::new("property_id");
703
704 assert_eq!(property.value::<EnumValue>(), Err(ValueError::Unknown));
706
707 property.value = Some("".to_owned());
709 assert_eq!(
710 property.value::<EnumValue>(),
711 Err(ValueError::ParseFailed {
712 value: "".to_owned(),
713 datatype: Datatype::Enum,
714 })
715 );
716
717 property.value = Some("anything".to_owned());
719 assert_eq!(property.value(), Ok(EnumValue::new("anything")));
720
721 property.datatype = Some(Datatype::Enum);
723 assert_eq!(property.value(), Ok(EnumValue::new("anything")));
724
725 property.datatype = Some(Datatype::String);
727 assert_eq!(
728 property.value::<EnumValue>(),
729 Err(ValueError::WrongDatatype {
730 actual: Datatype::String,
731 expected: Datatype::Enum,
732 })
733 );
734 }
735
736 #[test]
737 fn property_color_format() {
738 let mut property = Property::new("property_id");
739
740 assert_eq!(property.color_format(), Err(ValueError::Unknown));
742
743 property.format = Some("".to_owned());
745 assert_eq!(
746 property.color_format(),
747 Err(ValueError::WrongFormat {
748 format: "".to_owned()
749 })
750 );
751
752 property.format = Some("rgb".to_owned());
754 assert_eq!(property.color_format(), Ok(ColorFormat::Rgb));
755 property.format = Some("hsv".to_owned());
756 assert_eq!(property.color_format(), Ok(ColorFormat::Hsv));
757
758 property.datatype = Some(Datatype::Integer);
760 assert_eq!(
761 property.color_format(),
762 Err(ValueError::WrongDatatype {
763 actual: Datatype::Integer,
764 expected: Datatype::Color
765 })
766 );
767
768 property.datatype = Some(Datatype::Color);
770 assert_eq!(property.color_format(), Ok(ColorFormat::Hsv));
771 }
772
773 #[test]
774 fn property_enum_format() {
775 let mut property = Property::new("property_id");
776
777 assert_eq!(property.enum_values(), Err(ValueError::Unknown));
779
780 property.format = Some("".to_owned());
782 assert_eq!(
783 property.enum_values(),
784 Err(ValueError::WrongFormat {
785 format: "".to_owned()
786 })
787 );
788
789 property.format = Some("one".to_owned());
791 assert_eq!(property.enum_values(), Ok(vec!["one"]));
792
793 property.format = Some("one,two,three".to_owned());
795 assert_eq!(property.enum_values(), Ok(vec!["one", "two", "three"]));
796
797 property.datatype = Some(Datatype::Enum);
799 assert_eq!(property.enum_values(), Ok(vec!["one", "two", "three"]));
800
801 property.datatype = Some(Datatype::Color);
803 assert_eq!(
804 property.enum_values(),
805 Err(ValueError::WrongDatatype {
806 actual: Datatype::Color,
807 expected: Datatype::Enum
808 })
809 );
810 }
811
812 #[test]
813 fn property_numeric_format() {
814 let mut property = Property::new("property_id");
815
816 assert_eq!(property.range::<i64>(), Err(ValueError::Unknown));
818 assert_eq!(property.range::<f64>(), Err(ValueError::Unknown));
819
820 property.format = Some("".to_owned());
822 assert_eq!(
823 property.range::<i64>(),
824 Err(ValueError::WrongFormat {
825 format: "".to_owned()
826 })
827 );
828 assert_eq!(
829 property.range::<f64>(),
830 Err(ValueError::WrongFormat {
831 format: "".to_owned()
832 })
833 );
834
835 property.format = Some("1:10".to_owned());
837 assert_eq!(property.range(), Ok(1..=10));
838 assert_eq!(property.range(), Ok(1.0..=10.0));
839
840 property.format = Some("3.6:4.2".to_owned());
842 assert_eq!(property.range(), Ok(3.6..=4.2));
843 assert_eq!(
844 property.range::<i64>(),
845 Err(ValueError::WrongFormat {
846 format: "3.6:4.2".to_owned()
847 })
848 );
849
850 property.datatype = Some(Datatype::Integer);
852 property.format = Some("1:10".to_owned());
853 assert_eq!(property.range(), Ok(1..=10));
854
855 assert_eq!(
857 property.range::<f64>(),
858 Err(ValueError::WrongDatatype {
859 actual: Datatype::Integer,
860 expected: Datatype::Float
861 })
862 );
863 }
864
865 #[test]
866 fn property_has_required_attributes() {
867 let mut property = Property::new("property_id");
868 assert!(!property.has_required_attributes());
869
870 property.name = Some("Property name".to_owned());
871 assert!(!property.has_required_attributes());
872
873 property.datatype = Some(Datatype::Integer);
874 assert!(property.has_required_attributes());
875 }
876
877 fn property_with_required_attributes() -> Property {
879 let mut property = Property::new("property_id");
880 property.name = Some("Property name".to_owned());
881 property.datatype = Some(Datatype::Integer);
882 property
883 }
884
885 #[test]
886 fn node_has_required_attributes() {
887 let mut node = Node::new("node_id");
888 assert!(!node.has_required_attributes());
889
890 node.name = Some("Node name".to_owned());
891 assert!(!node.has_required_attributes());
892
893 node.node_type = Some("Node type".to_owned());
894 assert!(!node.has_required_attributes());
895
896 node.add_property(property_with_required_attributes());
897 assert!(node.has_required_attributes());
898
899 node.add_property(Property::new("property_without_required_attributes"));
900 assert!(!node.has_required_attributes());
901 }
902
903 fn node_with_required_attributes() -> Node {
905 let mut node = Node::new("node_id");
906 node.name = Some("Node name".to_owned());
907 node.node_type = Some("Node type".to_owned());
908 node.add_property(property_with_required_attributes());
909 node
910 }
911
912 #[test]
913 fn device_has_required_attributes() {
914 let mut device = Device::new("device_id", "123");
915 assert!(!device.has_required_attributes());
916
917 device.name = Some("Device name".to_owned());
918 assert!(!device.has_required_attributes());
919
920 device.state = State::Init;
921 assert!(device.has_required_attributes());
922
923 device.add_node(node_with_required_attributes());
924 assert!(device.has_required_attributes());
925
926 device.add_node(Node::new("node_without_required_attributes"));
927 assert!(!device.has_required_attributes());
928 }
929}