1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//! Homie 5 protocol core for eBus.
//!
//! The crate performs no I/O: callers pass in received MQTT messages and send the
//! messages it returns, so the same core serves tokio services and microcontrollers.
//!
//! | Module | Contents |
//! |---|---|
//! | [`controller`] | The controller state machine: discovery, pacing, device views, events, `/set` and `$broadcast` (`alloc`) |
//! | [`device`] | The device role: a tree of devices, nodes and properties, publishing and `/set` handling (`alloc`) |
//! | [`description`] | The `$description` model, parsing, validation and wire form (`alloc`) |
//! | [`datatype`] | The nine datatypes, payload checks, `/set` parsing and the encoders |
//! | [`topic`] | Parsing and building `{domain}/5/{device}/...` topics |
//! | [`id`], [`state`], [`payload`] | Topic-level ids, `$state` values, empty-payload rules |
//!
//! A controller runs on the caller's connection and clock. Feed it a link
//! change and messages, then send what [`poll_command`](controller::Controller::poll_command)
//! returns and read what [`poll_event`](controller::Controller::poll_event) returns:
//!
//! ```
//! use ebus_homie::controller::{Config, Controller, Event};
//! use ebus_mqtt::{Command, Instant, Link, Message};
//!
//! let mut c = Controller::new(Config::device("meter-1")?)?;
//! let now = Instant(0);
//! c.handle_link(now, Link::up(false));
//! while let Some(cmd) = c.poll_command() {
//! if let Command::Subscribe { filters, .. } = cmd {
//! assert!(filters.iter().any(|(f, _)| f == "ebus/5/meter-1/$state"));
//! }
//! }
//!
//! let retained = |topic, payload: &'static str| Message::new(topic, payload.as_bytes(), Some(true));
//! c.handle_message(now, retained("ebus/5/meter-1/$state", "ready"));
//! c.handle_message(
//! now,
//! retained(
//! "ebus/5/meter-1/$description",
//! r#"{"homie":"5.0","version":1,"nodes":{"power":{"properties":{"watts":{"datatype":"float"}}}}}"#,
//! ),
//! );
//! while c.poll_command().is_some() {}
//! c.handle_message(now, retained("ebus/5/meter-1/power/watts", "1234.5"));
//!
//! let mut synced = false;
//! while let Some(e) = c.poll_event() {
//! synced |= matches!(e, Event::DeviceSynced { missing: 0, .. });
//! }
//! assert!(synced);
//! let watts = c.device("meter-1").and_then(|d| d.property("power", "watts"));
//! assert_eq!(watts.and_then(|p| p.value.as_ref()).map(|v| &*v.raw), Some("1234.5"));
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! # Features
//!
//! - `std` (default): links `std`; implies `alloc`.
//! - `alloc`: the [`controller`], [`device`] and [`description`] modules and
//! the allocating parts of [`datatype`].
//! - `log`: diagnostics through the `log` crate.
extern crate alloc;
// Public only for tests/vectors.rs; the `datatype` encoders are the text API.
pub use ;
/// The `serde_json` this crate's JSON values ([`serde_json::Value`]) come from.
pub use serde_json;
// Every error type is a `core::error::Error`, with or without `std`.
const _: = ;
/// Homie convention major version, as it appears in topics (`{domain}/5/...`).
pub const HOMIE_TOPIC_VERSION: &str = "5";
/// Value of the `homie` field in a `$description`.
pub const HOMIE_DESCRIPTION_VERSION: &str = "5.0";
/// Topic domain the eBus framework uses in place of Homie's default `homie`.
pub const EBUS_DOMAIN: &str = "ebus";
;