|
| 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)) |
0 commit comments