Skip to content

Commit 199ccba

Browse files
committed
initial commit
1 parent 4d55322 commit 199ccba

27 files changed

+4640
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/target
2+
devEnv.sh

.rustfmt.toml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
max_width = 120 # max line length

Cargo.toml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[package]
2+
name = "homie5"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
serde = { version = "1.0", features = ["derive"] }
10+
serde_json = "1.0"
11+
chrono = { version = "0.4.30", features = ["serde"] }
12+
regex = { version = "1.9.5", default-features = true }
13+
thiserror = "1.0.49"
14+
log = "0.4.20"
15+
16+
[dev-dependencies]
17+
rumqttc = "0.24.0"
18+
futures = "0.3.28"
19+
bytes = "1.5.0"
20+
tokio = { version = "1.0", features = ["full"] }
21+
anyhow = "1.0.75"
22+
ctrlc = "3.1"
23+
log = "0.4"
24+
env_logger = "0.9"
25+

README.md

+347
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
# homie5
2+
3+
This is a very low level implemenation of the homie5 protocol in rust.
4+
It aims to be as flexible and unopinionated as possible. There is no direct dependency to a mqtt library.
5+
`homie5` provides a basically support for a protocol implementation for homie5 with clearly defined interface point to a mqtt library.
6+
The library provides fully typed support for all homie5 datatypes.
7+
8+
Due to this, the usage of the library is a bit more involved as with a completly ready to use homie library. Benefit is however that you can use the library basically everywhere from a simple esp32, raspberrypi to a x86 machine.
9+
10+
## Content
11+
12+
<!-- TOC start (generated with https://github.com/derlin/bitdowntoc) -->
13+
14+
- [Installation and usage](#installation-and-usage)
15+
- [Examples](#examples)
16+
- [Documentation](#documentation)
17+
- [Library outline](#library-outline)
18+
- [MQTT "bindings"](#mqtt-bindings)
19+
- [Simple rumqttc binding](#simple-rumqttc-binding)
20+
- [More advanced rumqttc binding](#more-advanced-rumqttc-binding)
21+
- [ESP32MqttClient binding](#esp32mqttclient-binding)
22+
- [Parsing MQTT messages](#parsing-mqtt-messages)
23+
- [Parsing and constructing HomieValues](#parsing-and-constructing-homievalues)
24+
- [Homie Device implementation](#homie-device-implementation)
25+
- [Homie Controller implemention](#homie-controller-implemention)
26+
- [References](#references)
27+
- [Contributing](#contributing)
28+
- [License](#license)
29+
30+
<!-- TOC end -->
31+
32+
<!-- TOC --><a name="installation-and-usage"></a>
33+
34+
# Installation and usage
35+
36+
Some details...
37+
`cargo add homie5`
38+
39+
<!-- TOC --><a name="examples"></a>
40+
41+
# Examples
42+
43+
You can find working examples for both device and controller use case in the `examples/` folder:
44+
45+
- controller_example.rs
46+
Implements a homie5 controller that will discover all homie5 devices on a mqtt broker and print out the devices and their property updates ([more information](./examples/README_controller.md)).
47+
- device_example.rs
48+
Implements a simple LightDevice with state and brightness control properties ([more information](./examples/README_device.md)).
49+
50+
Both examples use rumqttc as a mqtt client implementation and provide a best practice in homie5 usage and in how to integrate the 2 libraries.
51+
52+
<!-- TOC --><a name="documentation"></a>
53+
54+
# Documentation
55+
56+
todo: documentation
57+
58+
<!-- TOC --><a name="library-outline"></a>
59+
60+
## Library outline
61+
62+
Describe the different components and their usage:
63+
64+
- Homie5DeviceProtocol
65+
- Homie5ControllerProtocol
66+
- Homie5Message
67+
- HomieDeviceDescription
68+
- Homie MQTT "Artefacts"
69+
- HomieValue
70+
71+
<!-- TOC --><a name="mqtt-bindings"></a>
72+
73+
## MQTT "bindings"
74+
75+
A few best practices to easily implement mqtt client "bindings".
76+
77+
<!-- TOC --><a name="simple-rumqttc-binding"></a>
78+
79+
### Simple rumqttc binding
80+
81+
Simple helper functions that take the rumqttc AsyncClient as a parameter and convert the homie5 types to rumqttc types
82+
83+
```rust
84+
use homie5::client::{Publish, Subscription, Unsubscribe};
85+
use rumqttc::AsyncClient;
86+
87+
fn qos_to_rumqttc(value: homie5::client::QoS) -> rumqttc::QoS {
88+
match value {
89+
homie5::client::QoS::AtLeastOnce => rumqttc::QoS::AtLeastOnce,
90+
homie5::client::QoS::AtMostOnce => rumqttc::QoS::AtMostOnce,
91+
homie5::client::QoS::ExactlyOnce => rumqttc::QoS::ExactlyOnce,
92+
}
93+
}
94+
fn lw_to_rumqttc(value: homie5::client::LastWill) -> rumqttc::LastWill {
95+
rumqttc::LastWill {
96+
topic: value.topic,
97+
message: value.message.into(),
98+
qos: qos_to_rumqttc(value.qos),
99+
retain: value.retain,
100+
}
101+
}
102+
async fn publish(client: &AsyncClient, p: Publish) -> Result<(), rumqttc::ClientError> {
103+
client
104+
.publish(p.topic, qos_to_rumqttc(p.qos), p.retain, p.payload)
105+
.await
106+
}
107+
108+
async fn subscribe(client: &AsyncClient, subs: impl Iterator<Item = Subscription>) -> Result<(), rumqttc::ClientError> {
109+
for sub in subs {
110+
client.subscribe(sub.topic, qos_to_rumqttc(sub.qos)).await?;
111+
}
112+
Ok(())
113+
}
114+
115+
116+
```
117+
118+
<!-- TOC --><a name="more-advanced-rumqttc-binding"></a>
119+
120+
### More advanced rumqttc binding
121+
122+
This is a more advanced approach. We define a HomieMQTTClient trait that will accecpt the homi5e mqtt types directly and convert the actions to rumqttc AsyncClient actions
123+
124+
```rust
125+
126+
use homie5::client::{Publish, Subscription, Unsubscribe};
127+
use rumqttc::AsyncClient;
128+
129+
pub trait HomieMQTTClient
130+
where
131+
Self::ResultError: Send + Sync,
132+
{
133+
type TargetQoS;
134+
type TargetLastWill;
135+
type ResultError;
136+
137+
fn homie_map_qos(qos: homie5::client::QoS) -> Self::TargetQoS;
138+
fn homie_map_last_will(last_will: homie5::client::LastWill) -> Self::TargetLastWill;
139+
140+
async fn homie_publish(&self, p: Publish) -> Result<(), Self::ResultError>;
141+
142+
async fn homie_subscribe(&self, subs: impl Iterator<Item = Subscription> + Send) -> Result<(), Self::ResultError>;
143+
144+
async fn homie_unsubscribe(&self, subs: impl Iterator<Item = Unsubscribe> + Send) -> Result<(), Self::ResultError>;
145+
}
146+
147+
// Implement the trait for the rumqttc AsyncClient which will enable the client
148+
// to directly use the homie5 mqtt artefacts
149+
impl HomieMQTTClient for AsyncClient {
150+
type TargetQoS = rumqttc::QoS;
151+
type TargetLastWill = rumqttc::LastWill;
152+
type ResultError = anyhow::Error;
153+
154+
fn homie_map_qos(qos: homie5::client::QoS) -> Self::TargetQoS {
155+
match qos {
156+
homie5::client::QoS::AtLeastOnce => rumqttc::QoS::AtLeastOnce,
157+
homie5::client::QoS::AtMostOnce => rumqttc::QoS::AtMostOnce,
158+
homie5::client::QoS::ExactlyOnce => rumqttc::QoS::ExactlyOnce,
159+
}
160+
}
161+
fn homie_map_last_will(last_will: homie5::client::LastWill) -> Self::TargetLastWill {
162+
rumqttc::LastWill {
163+
topic: last_will.topic,
164+
message: last_will.message.into(),
165+
qos: Self::homie_map_qos(last_will.qos),
166+
retain: last_will.retain,
167+
}
168+
}
169+
// Implementation for publishing messages
170+
async fn homie_publish(&self, p: Publish) -> Result<(), Self::ResultError> {
171+
self.publish(p.topic, Self::homie_map_qos(p.qos), p.retain, p.payload)
172+
.await?;
173+
Ok(())
174+
}
175+
176+
// Implementation for subscribing to topics
177+
async fn homie_subscribe(&self, subs: impl Iterator<Item = Subscription> + Send) -> Result<(), Self::ResultError> {
178+
for sub in subs {
179+
self.subscribe(sub.topic, Self::homie_map_qos(sub.qos)).await?;
180+
}
181+
Ok(())
182+
}
183+
184+
// Implementation for unsubscribing from topics
185+
async fn homie_unsubscribe(&self, subs: impl Iterator<Item = Unsubscribe> + Send) -> Result<(), Self::ResultError> {
186+
for sub in subs {
187+
self.unsubscribe(sub.topic).await?;
188+
}
189+
Ok(())
190+
}
191+
}
192+
193+
```
194+
195+
<!-- TOC --><a name="esp32mqttclient-binding"></a>
196+
197+
### ESP32MqttClient binding
198+
199+
```rust
200+
use embedded_svc::mqtt::client::QoS;
201+
use esp_idf_svc::mqtt::client::{EspMqttClient, LwtConfiguration, MqttClientConfiguration};
202+
use esp_idf_sys::EspError;
203+
use homie5::{
204+
client::{Publish, Subscription},
205+
};
206+
207+
pub fn qos_to_esp_qos(value: &homie5::client::QoS) -> QoS {
208+
match value {
209+
homie5::client::QoS::AtLeastOnce => QoS::AtLeastOnce,
210+
homie5::client::QoS::AtMostOnce => QoS::AtMostOnce,
211+
homie5::client::QoS::ExactlyOnce => QoS::ExactlyOnce,
212+
}
213+
}
214+
pub fn lw_to_esp_lw(value: &homie5::client::LastWill) -> LwtConfiguration {
215+
LwtConfiguration {
216+
topic: &value.topic,
217+
payload: &value.message,
218+
qos: qos_to_esp_qos(&value.qos),
219+
retain: value.retain,
220+
}
221+
}
222+
223+
pub fn publish(client: &mut EspMqttClient<'_>, p: Publish) -> Result<(), EspError> {
224+
client.publish(&p.topic, qos_to_esp_qos(&p.qos), p.retain, &p.payload)?;
225+
Ok(())
226+
}
227+
228+
pub fn subscribe(
229+
client: &mut EspMqttClient<'_>,
230+
subs: impl Iterator<Item = Subscription>,
231+
) -> Result<(), EspError> {
232+
for sub in subs {
233+
client.subscribe(&sub.topic, qos_to_esp_qos(&sub.qos))?;
234+
}
235+
Ok(())
236+
}
237+
238+
239+
```
240+
241+
<!-- TOC --><a name="parsing-mqtt-messages"></a>
242+
243+
## Parsing MQTT messages
244+
245+
Show how to parse native mqtt messages to Homie5Messages and explain the different messages and their meaning.
246+
247+
```rust
248+
249+
pub enum Homie5Message {
250+
DeviceState {
251+
device: DeviceIdentifier,
252+
state: HomieDeviceStatus,
253+
},
254+
DeviceDescription {
255+
device: DeviceIdentifier,
256+
description: HomieDeviceDescription,
257+
},
258+
DeviceLog {
259+
device: DeviceIdentifier,
260+
log_msg: String,
261+
},
262+
DeviceAlert {
263+
device: DeviceIdentifier,
264+
alert_id: String,
265+
alert_msg: String,
266+
},
267+
268+
PropertyValue {
269+
property: PropertyIdentifier,
270+
value: String,
271+
},
272+
PropertyTarget {
273+
property: PropertyIdentifier,
274+
target: String,
275+
},
276+
PropertySet {
277+
property: PropertyIdentifier,
278+
set_value: String,
279+
},
280+
281+
Broadcast {
282+
topic_root: String,
283+
subtopic: String,
284+
data: String,
285+
},
286+
287+
DeviceRemoval {
288+
device: DeviceIdentifier,
289+
},
290+
}
291+
292+
```
293+
294+
todo: documentation
295+
296+
<!-- TOC --><a name="parsing-and-constructing-homievalues"></a>
297+
298+
## Parsing and constructing HomieValues
299+
300+
Explain details about HomieValue.
301+
302+
```rust
303+
304+
pub enum HomieValue {
305+
#[default]
306+
Empty,
307+
String(String),
308+
Integer(i64),
309+
Float(f64),
310+
Bool(bool),
311+
Enum(String),
312+
Color(HomieColorValue),
313+
DateTime(chrono::DateTime<chrono::Utc>),
314+
Duration(chrono::Duration),
315+
JSON(serde_json::Value),
316+
}
317+
318+
```
319+
320+
<!-- TOC --><a name="homie-device-implementation"></a>
321+
322+
## Homie Device implementation
323+
324+
Show the basics of how to implement a device with homie5.
325+
Usage of Homie5DeviceProtocol, builder for device, node and property descriptions, message flow and so on...
326+
Also discuss number ranges, property format.
327+
328+
<!-- TOC --><a name="homie-controller-implemention"></a>
329+
330+
## Homie Controller implemention
331+
332+
Show basics of how to implement a controller with homie5.
333+
Usage of Homie5ControllerProtocol, message flow and so on...
334+
335+
<!-- TOC --><a name="references"></a>
336+
337+
# References
338+
339+
<!-- TOC --><a name="contributing"></a>
340+
341+
# Contributing
342+
343+
<!-- TOC --><a name="license"></a>
344+
345+
# License
346+
347+
This project was released under the MIT License ([LICENSE](./LICENSE))

examples/README_controller.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Controller Example
2+
3+
Implements a homie5 controller that will discover all homie5 devices on a mqtt broker and print out the devices and their property updates
4+
5+
## TODO
6+
7+
todo: add better documentation of the example

examples/README_device.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Device Example
2+
3+
Implements a simple LightDevice with state and brightness control properties
4+
5+
## TODO
6+
7+
todo: add better documentation of the example

0 commit comments

Comments
 (0)