├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples ├── availability │ └── availability.ino ├── deviceavailability │ └── deviceavailability.ino ├── deviceproperties │ └── deviceproperties.ino ├── led │ └── led.ino ├── multiple │ └── multiple.ino ├── multipledevices │ └── multipledevices.ino ├── number │ └── number.ino ├── select │ └── select.ino ├── sensorbinary │ └── sensorbinary.ino ├── sensornumeric │ └── sensornumeric.ino └── switch │ └── switch.ino ├── library.properties └── src ├── HaMqttEntities.h ├── habutton.cpp ├── habutton.h ├── haconsts.h ├── hadevice.cpp ├── hadevice.h ├── haentity.cpp ├── haentity.h ├── hakvpairlist.cpp ├── hakvpairlist.h ├── hamqttcontroller.cpp ├── hamqttcontroller.h ├── hanumber.cpp ├── hanumber.h ├── haselect.cpp ├── haselect.h ├── hasensor.cpp ├── hasensor.h ├── hasensorbinary.cpp ├── hasensorbinary.h ├── hasensornumeric.cpp ├── hasensornumeric.h ├── hasensortext.cpp ├── hasensortext.h ├── haswitch.cpp ├── haswitch.h ├── hatext.cpp └── hatext.h /.gitignore: -------------------------------------------------------------------------------- 1 | secrets.h 2 | .vscode 3 | build 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.0.10] - 2025-02-21 4 | 5 | - Fixed bug for entities not grouped in devices 6 | - Improved *lastwill* device configuration 7 | - Removed extra delay in some examples 8 | 9 | ## [1.0.9] - 2024-12-10 10 | 11 | - Fixed bug in HADevice constructor 12 | - Added some features as `progmem` strings to save memory 13 | 14 | ## [1.0.8] - 2024-10-31 15 | 16 | - Fixed bug with some features in entities 17 | - Fixed bug in availability for devices 18 | 19 | ## [1.0.7] - 2024-10-22 20 | 21 | - Fixed Bug on MQTT reconnection. Device availability was not sent to HA 22 | 23 | ## [1.0.6] - 2024-09-22 24 | 25 | - Fixed bug in reconnection to HA when MQTT connection is lost 26 | 27 | ## [1.0.5] - 2024-08-28 28 | 29 | - Fixed bug in reconnection loop 30 | - Added support for LWT 31 | - Added availability for entities and/or devices 32 | - Added device attributes: hw_version, manufacturer, model 33 | - Added Binary Sensor 34 | - Added configuration features: device_class and icon 35 | - Optimized discovery payload to use abbreviations 36 | 37 | ## [1.0.4] - 2024-08-10 38 | 39 | - Fixed bug in unique_id when multiple devices are created 40 | - Improved device constructor for later setup identifier and friendly name 41 | - Added example for multiple devices and devices configured in the setup 42 | function 43 | 44 | ## [1.0.3] - 2024-04-17 45 | 46 | - Fixed MQTT reconnection bug 47 | - Fixed resend all states every 24h 48 | - Updated all examples to use the new connect method 49 | 50 | ## [1.0.2] - 2024-03-18 51 | 52 | - Added *unit_of_measurement* configuration to HASensorNumeric 53 | - Added new example for HASensorNumeric to get a graph history in HomeAssistant 54 | 55 | ## [1.0.1] - 2024-03-17 56 | 57 | - Fixed bug in HANumber configuration payload 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MQTT Home Assistant library for Arduino 2 | 3 | Library for simplifying designs using Home Assistant (HA) MQTT-Discovery. It 4 | includes a set of classes to create entities and publish them to HA. It also 5 | supports joining entities to a device but is not mandatory. 6 | 7 | It is inspired by [Arduino Home Assistant 8 | integration](https://github.com/dawidchyrzynski/arduino-home-assistant) but with 9 | less features. 10 | 11 | > [!NOTE] 12 | > This library contains only a subset of the HA-MQTT integrations. They 13 | > are supported with minimal options. For complex designs consider the 14 | > [Alternatives](alternatives) listed below. 15 | 16 | 17 | ## Details 18 | 19 | The library is intended for use in ESPXX devices. 20 | It is only tested on some ESP32 21 | 22 | **Dependence**: 23 | 24 | - PubSubClient library. 25 | 26 | **Features**: 27 | 28 | - The library implements HA-MQTT discovery [[more 29 | info](https://www.home-assistant.io/integrations/mqtt/)]. 30 | - For simple designs, HA entities can be created without a device. 31 | - For complex designs, HA entities can be grouped into a device (recommended 32 | solution). 33 | - Broker's last will for availability is supported at the device level. 34 | - MQTT callbacks are supported in non-exclusive mode, but it is not necessary to 35 | use them. The state of the entities is updated when the MQTT topic is received 36 | and they can be polled in the main loop. 37 | - For complex designs, the components can be extended through inheritance and 38 | overriding methods such as `onStateChange`. 39 | 40 | For more information on usage, see Examples. Future documentation will be added 41 | (work in progress). 42 | 43 | ## Components supported 44 | 45 | - [X] Button 46 | - [X] Text sensor 47 | - [X] Numeric sensor 48 | - [X] Binary sensor 49 | - [ ] Event 50 | - [X] Number 51 | - [X] Switch 52 | - [X] Text 53 | - [X] Select 54 | 55 | Unchecked items will be supported in future versions but complex components such 56 | as *Alarms*,*Cameras* are not planned to be supported. 57 | 58 | ## HA MQTT properties supported (and not) 59 | 60 | The library supports items marked for entities 61 | 62 | - [X] Name 63 | - [X] Device name 64 | - [X] Availability 65 | - [X] Device class 66 | - [X] Unit of measurement 67 | - [X] Icon 68 | - [X] Entity category 69 | - [X] Other properties not implicitly declared 70 | - [ ] Attributes 71 | 72 | 73 | Unchecked items will be supported in future versions. 74 | 75 | MQTT Device registry attributes supported: 76 | 77 | - [X] Name 78 | - [X] Manufacturer 79 | - [X] Model 80 | - [X] Software version 81 | - [X] Hardware version 82 | - [ ] Serial Number 83 | - [ ] Configuration URL 84 | - [ ] Connections 85 | - [ ] Suggested area 86 | - [ ] Via Device 87 | 88 | ## How to Use 89 | 90 | A complete set of examples is in the folder `examples` but, two simple examples 91 | are shown below. 92 | 93 | **Example 1**: A simple entity created without a device: (full example at 94 | [led.ino](examples/led/led.ino)) 95 | 96 | ```cpp 97 | WiFiClient wifi_client; 98 | PubSubClient mqtt_client(wifi_client); 99 | 100 | HASwitch ha_switch = HASwitch("hamqtt01_uid","Demo Led"); 101 | 102 | void setup() { 103 | pinMode(PIN_LED,OUTPUT); 104 | mqtt_client.setServer(MQTT_SERVER, MQTT_PORT); 105 | 106 | HAMQTT.begin(mqtt_client,1); 107 | HAMQTT.addEntity(ha_switch); 108 | 109 | WiFi.begin("MySSID", "MyPassword"); 110 | } 111 | 112 | void loop() { 113 | bool on_off = ha_switch.getState(); 114 | 115 | digitalWrite(PIN_LED, on_off); 116 | 117 | if (!HAMQTT.connected()) 118 | HAMQTT.connect("HAMQTTExample","user","password"); 119 | 120 | HAMQTT.loop(); 121 | } 122 | ``` 123 | 124 | **Example 2**: Creating a device and grouping entities is better: 125 | 126 | ```cpp 127 | WiFiClient wifi_client; 128 | PubSubClient mqtt_client(wifi_client); 129 | 130 | #define ENTITIES_COUNT 2 131 | #define MAX_TEXT_LENGTH 50 132 | HADevice ha_device("example02","Example 2 HA-MQTT","1.0"); 133 | HASwitch ha_switch = HASwitch("switch_id","Test on/off",ha_device); 134 | HAText ha_text = HAText("text_id","Input text",ha_device,MAX_TEXT_LENGTH); 135 | 136 | void setup() { 137 | mqtt_client.setServer(MQTT_SERVER, MQTT_PORT); 138 | 139 | HAMQTT.begin(mqtt_client,ENTITIES_COUNT); 140 | HAMQTT.addEntity(ha_switch); 141 | HAMQTT.addEntity(ha_text); 142 | 143 | } 144 | ``` 145 | 146 | **Other examples**: 147 | 148 | - Class device: [sensorbinary.ino](examples/sensorbinary/sensorbinary.ino) 149 | - Availability and icon: [availability.ino](examples/availability/availability.ino) 150 | - Device availability and last will: [deviceavailability.ino](examples/deviceavailability/deviceavailability.ino) 151 | 152 | 153 | ## Alternatives 154 | 155 | - [Arduino Home Assistant 156 | integration](https://github.com/dawidchyrzynski/arduino-home-assistant) 157 | - [HAMqttDevice](https://github.com/plapointe6/HAMqttDevice) 158 | - [HAMqttDiscoveryHandler](https://github.com/cyijun/HAMqttDiscoveryHandler) 159 | - [MycilaHADiscovery](https://github.com/mathieucarbou/MycilaHADiscovery) 160 | 161 | ## License 162 | 163 | Licensed under the Apache License, Version 2.0 164 | 165 | ## Author 166 | 167 | Designed and maintained by Paulino Ruiz de Clavijo Vázquez pruiz@us.es 168 | -------------------------------------------------------------------------------- /examples/availability/availability.ino: -------------------------------------------------------------------------------- 1 | /* ha-mqtt-entities library example of availability for entities 2 | 3 | - Board: ESP32* 4 | 5 | This example shows how to 6 | 7 | - Creating a Text Sensor with Availability 8 | - Customize the test sensor icon 9 | - Create a switch to control the availability of the text sensor 10 | - Optimize memory usage by using PSTR to store strings in flash memory 11 | 12 | NOTE: The availability feature at entity level it is not compatible with 13 | last-will feature at device level in this library. 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | // This file is not included in the repository only used for local testing 22 | //#include "secrets.h" 23 | 24 | // You must set the next defines 25 | #ifndef SECRETS_H 26 | #define WIFI_SSID "MyWifi" 27 | #define WIFI_PASSWORD "MyPassword" 28 | #define MQTT_SERVER "192.168.1.X" 29 | #define MQTT_PORT 1883 30 | #define MQTT_USER "MyBrokerUser" 31 | #define MQTT_PASSWORD "MyBrokerPassword" 32 | #endif 33 | 34 | WiFiClient wifi_client; 35 | PubSubClient mqtt_client(wifi_client); 36 | 37 | // HA Parts 38 | #define ENTITIES_COUNT 2 39 | // This example optimizes the memory usage by using PSTR to store the strings 40 | // in flash memory 41 | #define SW_VERSION PSTR("1.0.0") 42 | #define IDENTIFIER PSTR("example_availability") 43 | #define DEVICE_NAME PSTR("Example of availability") 44 | 45 | HADevice ha_device = HADevice(IDENTIFIER,DEVICE_NAME,SW_VERSION); 46 | HAText ha_text = HAText(PSTR("text"), PSTR("Input text"), ha_device, 100); 47 | HASwitch ha_switch = HASwitch(PSTR("switch"), PSTR("Test availability"), ha_device); 48 | 49 | void ha_callback(HAEntity *entity, char *topic, byte *payload, unsigned int length); 50 | 51 | void setup() 52 | { 53 | Serial.begin(115200); 54 | mqtt_client.setServer(MQTT_SERVER, MQTT_PORT); 55 | 56 | // Initialize entities 57 | HAMQTT.begin(mqtt_client, ENTITIES_COUNT); 58 | HAMQTT.addEntity(ha_text); 59 | HAMQTT.addEntity(ha_switch); 60 | 61 | ha_text.addFeature(HA_FEATURE_AVAILABILITY); 62 | ha_text.addFeature(HA_FEATURE_ICON,"mdi:lightbulb"); 63 | 64 | // Initial states 65 | ha_switch.setState(true); 66 | ha_text.setAvailable(true); 67 | ha_text.setState("Hello HomeAssistant"); 68 | 69 | HAMQTT.setCallback(ha_callback); 70 | 71 | // start wifi 72 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 73 | Serial.printf("HaMqttEntities version: %s\n",HA_MQTT_VERSION_S); 74 | } 75 | 76 | void loop() 77 | { 78 | if (WiFi.status() == WL_CONNECTED && !HAMQTT.connected()) 79 | { 80 | if (HAMQTT.connect("examples", MQTT_USER, MQTT_PASSWORD)) 81 | Serial.println("Connected to MQTT"); 82 | else 83 | { 84 | Serial.println("Failed to connect to MQTT"); 85 | delay(5000); 86 | } 87 | } 88 | HAMQTT.loop(); 89 | } 90 | 91 | /* Callback from HA-MQTT entities. It is called when an entity changes its state. 92 | 93 | Entity can be NULL when the received topic is not related to any entity. 94 | This is useful to handle other topics with the same mqtt client. 95 | */ 96 | void ha_callback(HAEntity *entity, char *topic, byte *payload, unsigned int length) 97 | { 98 | if (entity == &ha_switch) 99 | { 100 | if (ha_switch.getState()) 101 | { 102 | ha_text.setAvailable(true); 103 | } 104 | else 105 | { 106 | ha_text.setAvailable(false); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /examples/deviceavailability/deviceavailability.ino: -------------------------------------------------------------------------------- 1 | /* ha-mqtt-entities library example of availability and last will for a device. 2 | 3 | - Board: ESP32* 4 | 5 | This library implements broker lastwill at device level sharing the availability 6 | topic with all the entities of the device. 7 | 8 | Note: The availability feature at entity level has precedence over the device 9 | availability feature. If the device has the availability feature enabled, all 10 | entities of the device must be disabled the availability feature. 11 | 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | // This file is not included in the repository only used for local testing 20 | // #include "secrets.h" 21 | 22 | // You must set the next defines 23 | #ifndef SECRETS_H 24 | #define WIFI_SSID "MyWifi" 25 | #define WIFI_PASSWORD "MyPassword" 26 | #define MQTT_SERVER "192.168.1.X" 27 | #define MQTT_PORT 1883 28 | #define MQTT_USER "MyBrokerUser" 29 | #define MQTT_PASSWORD "MyBrokerPassword" 30 | #endif 31 | 32 | WiFiClient wifi_client; 33 | PubSubClient mqtt_client(wifi_client); 34 | 35 | // HA Parts 36 | #define ENTITIES_COUNT 2 37 | #define SW_VERSION PSTR("1.0.0") 38 | #define IDENTIFIER PSTR("hamqen") 39 | #define DEVICE_NAME PSTR("Example of availability and last will") 40 | 41 | HADevice ha_device = HADevice(IDENTIFIER,DEVICE_NAME,SW_VERSION); 42 | HASwitch ha_switch = HASwitch( 43 | PSTR("switch-id"), PSTR("My switch"), ha_device); 44 | HASensorBinary ha_sensor = HASensorBinary( 45 | PSTR("sensor-id"), PSTR("My binary sensor"), ha_device); 46 | 47 | void ha_callback(HAEntity *entity, char *topic, byte *payload, unsigned int length); 48 | 49 | void setup() 50 | { 51 | Serial.begin(115200); 52 | mqtt_client.setServer(MQTT_SERVER, MQTT_PORT); 53 | 54 | // Initialize entities 55 | HAMQTT.begin(mqtt_client, ENTITIES_COUNT); 56 | HAMQTT.addEntity(ha_switch); 57 | HAMQTT.addEntity(ha_sensor); 58 | 59 | // Features. The device availability enable last will at device level 60 | ha_device.addFeature(HA_FEATURE_AVAILABILITY); 61 | 62 | // By default the device is available is true but, it can be used in other 63 | // parts of the code to disable/enable the device if need 64 | 65 | // ha_device.setAvailable(true); 66 | 67 | // Initial states 68 | ha_switch.setState(true); 69 | 70 | HAMQTT.setCallback(ha_callback); 71 | 72 | // start wifi 73 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 74 | Serial.printf("HaMqttEntities version: %s\n",HA_MQTT_VERSION_S); 75 | } 76 | 77 | void loop() 78 | { 79 | if (WiFi.status() == WL_CONNECTED && !HAMQTT.connected()) 80 | { 81 | // The connect method includes the last will in the pubsub client 82 | if (HAMQTT.connect("examples", MQTT_USER, MQTT_PASSWORD)) 83 | Serial.println("Connected to MQTT"); 84 | else 85 | { 86 | Serial.println("Failed to connect to MQTT"); 87 | delay(5000); 88 | } 89 | } 90 | HAMQTT.loop(); 91 | } 92 | 93 | /* Callback from HA-MQTT entities. It is called when an entity changes its state. 94 | 95 | Entity can be NULL when the received topic is not related to any entity. 96 | This is useful to handle other topics with the same mqtt client. 97 | */ 98 | void ha_callback(HAEntity *entity, char *topic, byte *payload, unsigned int length) 99 | { 100 | if (entity == &ha_switch) 101 | Serial.println("Switch changed"); 102 | } 103 | -------------------------------------------------------------------------------- /examples/deviceproperties/deviceproperties.ino: -------------------------------------------------------------------------------- 1 | /* ha-mqtt-entities library example with all device properties set. 2 | 3 | - Board: ESP32* 4 | 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // This file is not included in the repository only used for local testing 13 | // #include "secrets.h" 14 | 15 | // You must set the next defines 16 | #ifndef SECRETS_H 17 | #define WIFI_SSID "MyWifi" 18 | #define WIFI_PASSWORD "MyPassword" 19 | #define MQTT_SERVER "192.168.1.X" 20 | #define MQTT_PORT 1883 21 | #define MQTT_USER "MyBrokerUser" 22 | #define MQTT_PASSWORD "MyBrokerPassword" 23 | #endif 24 | 25 | WiFiClient wifi_client; 26 | PubSubClient mqtt_client(wifi_client); 27 | 28 | // HA Parts 29 | #define ENTITIES_COUNT 1 30 | #define SW_VERSION "1.0.0" 31 | HADevice ha_device = HADevice(SW_VERSION); 32 | HANumber ha_number = HANumber("slicer1","Slicer",ha_device,1,100,1); 33 | 34 | void ha_callback(HAEntity *entity, char *topic, byte *payload, unsigned int length); 35 | 36 | void setup() { 37 | Serial.begin(115200); 38 | mqtt_client.setServer(MQTT_SERVER, MQTT_PORT); 39 | 40 | // Device configuration out of constructor 41 | ha_device.setName("HA-MQTT: Device with info"); 42 | ha_device.setIdentifier("device01"); // Must be unique to avoid conflicts with other devices 43 | ha_device.setManufacturer("US"); 44 | ha_device.setModel("ESP32"); 45 | ha_device.setHwVersion("2.0.0"); 46 | 47 | // Initialize entities 48 | HAMQTT.begin(mqtt_client,ENTITIES_COUNT); 49 | HAMQTT.addEntity(ha_number); 50 | 51 | ha_number.setState(30); 52 | HAMQTT.setCallback(ha_callback); 53 | 54 | // start wifi 55 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 56 | } 57 | 58 | void loop() { 59 | if (WiFi.status() == WL_CONNECTED && !HAMQTT.connected()) { 60 | if( HAMQTT.connect("examples",MQTT_USER,MQTT_PASSWORD)) 61 | Serial.println("Connected to MQTT"); 62 | else 63 | { 64 | Serial.println("Failed to connect to MQTT"); 65 | delay(5000); 66 | } 67 | } 68 | HAMQTT.loop(); 69 | delay(10); 70 | } 71 | 72 | /* Callback from HA-MQTT entities. It is called when an entity changes its state. 73 | 74 | Entity can be NULL when the received topic is not related to any entity. 75 | This is useful to handle other topics with the same mqtt client. 76 | */ 77 | void ha_callback(HAEntity *entity, char *topic, byte *payload, unsigned int length){ 78 | if(entity != NULL) { 79 | Serial.printf("Changed entity: unique_id='%s' \n",entity->getUniqueId()); 80 | } 81 | else 82 | Serial.println("Callback called from other subscription"); 83 | 84 | } 85 | -------------------------------------------------------------------------------- /examples/led/led.ino: -------------------------------------------------------------------------------- 1 | /* ha-mqtt-entities library simple example 2 | 3 | - Board: ESP32* 4 | 5 | This example creates a MQTT switch entity in the Home Assistant to toggle the 6 | onboard LED. 7 | 8 | In this example, no MQTT device is created in HA, only a switch entity is added. 9 | No callback is added either, the switch state is polled in the main loop. 10 | 11 | You must set the definitions in the code under SECRETS_H. 12 | 13 | */ 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | // This file is not included in the repository only used for local testing 21 | // #include "secrets.h" 22 | 23 | // You must set the next defines 24 | #ifndef SECRETS_H 25 | #define WIFI_SSID "MyWifi" 26 | #define WIFI_PASSWORD "MyPassword" 27 | #define MQTT_SERVER "192.168.1.X" 28 | #define MQTT_PORT 1883 29 | #define MQTT_USER "MyBrokerUser" 30 | #define MQTT_PASSWORD "MyBrokerPassword" 31 | #endif 32 | 33 | WiFiClient wifi_client; 34 | PubSubClient mqtt_client(wifi_client); 35 | 36 | #define PIN_LED 2 // On board LED in ESP32 DevKit v1 37 | #define ENTITIES_COUNT 1 38 | #define SWITCH_UID "example01switch" // Be careful of be unique in HA entities list 39 | 40 | HASwitch ha_switch = HASwitch(SWITCH_UID,"Demo Led"); 41 | 42 | void setup() { 43 | pinMode(PIN_LED,OUTPUT); 44 | 45 | mqtt_client.setServer(MQTT_SERVER, MQTT_PORT); 46 | 47 | HAMQTT.begin(mqtt_client,ENTITIES_COUNT); 48 | HAMQTT.addEntity(ha_switch); 49 | 50 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 51 | } 52 | 53 | void loop() { 54 | bool on_off = ha_switch.getState(); 55 | digitalWrite(PIN_LED, on_off); 56 | if (!HAMQTT.connected()) 57 | HAMQTT.connect("HAMQTTExample",MQTT_USER,MQTT_PASSWORD); 58 | HAMQTT.loop(); 59 | } 60 | -------------------------------------------------------------------------------- /examples/multiple/multiple.ino: -------------------------------------------------------------------------------- 1 | /* ha-mqtt-entities library example with multiple entities into a single device 2 | 3 | - Board: ESP32* 4 | 5 | This example creates an HA-MQTT device with a group of entities. 6 | It also shows how to use the callback to handle events from the entities. 7 | 8 | You must set the definitions in the code under SECRETS_H. 9 | 10 | */ 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | // This file is not included in the repository only used for local testing 18 | // #include "secrets.h" 19 | 20 | // You must set the next defines 21 | #ifndef SECRETS_H 22 | #define WIFI_SSID "MyWifi" 23 | #define WIFI_PASSWORD "MyPassword" 24 | #define MQTT_SERVER "192.168.1.X" 25 | #define MQTT_PORT 1883 26 | #define MQTT_USER "MyBrokerUser" 27 | #define MQTT_PASSWORD "MyBrokerPassword" 28 | #endif 29 | 30 | WiFiClient wifi_client; 31 | PubSubClient mqtt_client(wifi_client); 32 | 33 | // HA Parts 34 | #define ENTITIES_COUNT 3 35 | #define HA_DEV_ID "example04" 36 | #define HA_DEV_FRIENDLY_NAME "Example 4 HA-MQTT" 37 | #define HA_DEV_FIRMWARE_VERSION "1.0" 38 | #define HA_DEV_MANUFACTURER "Arduino" 39 | #define HA_DEV_MODEL "ESP32" 40 | #define HA_DEV_HARDWARE_VERSION "1.8.19" 41 | 42 | HADevice ha_device = HADevice(HA_DEV_ID,HA_DEV_FRIENDLY_NAME, 43 | HA_DEV_FIRMWARE_VERSION,HA_DEV_MANUFACTURER,HA_DEV_MODEL,HA_DEV_HARDWARE_VERSION); 44 | HAText ha_text = HAText("text04uid","Input text",ha_device,100); 45 | HAButton ha_button = HAButton("button04uid","Calculate length",ha_device); 46 | HASelect ha_select = HASelect("select04uid","Select",ha_device,3); 47 | HASensorNumeric ha_sensor = HASensorNumeric("sensor04uid","Text length",ha_device); 48 | 49 | 50 | void ha_callback(HAEntity *entity, char *topic, byte *payload, unsigned int length); 51 | 52 | void setup() { 53 | Serial.begin(115200); 54 | mqtt_client.setServer(MQTT_SERVER, MQTT_PORT); 55 | 56 | HAMQTT.begin(mqtt_client,ENTITIES_COUNT); 57 | HAMQTT.addEntity(ha_button); 58 | HAMQTT.addEntity(ha_sensor); 59 | HAMQTT.addEntity(ha_text); 60 | HAMQTT.addEntity(ha_select); 61 | 62 | ha_select.addOption("Option 1"); 63 | ha_select.addOption("Option 2"); 64 | 65 | HAMQTT.setCallback(ha_callback); 66 | 67 | ha_text.setState("Hello World"); 68 | ha_sensor.setState(strlen(ha_text.getState())); 69 | 70 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 71 | } 72 | 73 | void loop() { 74 | if(WiFi.status() == WL_CONNECTED && !HAMQTT.connected()) 75 | { 76 | Serial.println("Connecting to MQTT..."); 77 | if (HAMQTT.connect("examples",MQTT_USER,MQTT_PASSWORD)) 78 | Serial.println("Connected to MQTT"); 79 | else { 80 | Serial.print("Failed to connect to MQTT, retry in 5 seconds..."); 81 | delay(5000); 82 | } 83 | } 84 | HAMQTT.loop(); 85 | } 86 | 87 | /* Callback from HA-MQTT entities. It is called when an entity changes its state. 88 | 89 | Entity can be NULL when the received topic is not related to any entity. 90 | This is useful to handle other topics with the same mqtt client. 91 | */ 92 | void ha_callback(HAEntity *entity, char *topic, byte *payload, 93 | unsigned int length) { 94 | if(entity == &ha_button) 95 | ha_sensor.setState(strlen(ha_text.getState())); 96 | if(entity == &ha_select) 97 | ha_text.setState(ha_select.getState()); 98 | 99 | } 100 | -------------------------------------------------------------------------------- /examples/multipledevices/multipledevices.ino: -------------------------------------------------------------------------------- 1 | /* ha-mqtt-entities library example: later device configuration and multiple 2 | devices 3 | 4 | - Board: ESP32* 5 | 6 | This example show the follow: 7 | 8 | - Multiple HA-devices in the same hardware device 9 | - Delayed device configuration in the setup function 10 | - Setting availability of one the device affected all entities of the device 11 | 12 | Usage: Turning on/off the switch of device 1 change the availability of the 13 | device 2. 14 | 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | // This file is not included in the repository only used for local testing 23 | // #include "secrets.h" 24 | 25 | // You must set the next defines 26 | #ifndef SECRETS_H 27 | #define WIFI_SSID "MyWifi" 28 | #define WIFI_PASSWORD "MyPassword" 29 | #define MQTT_SERVER "192.168.1.X" 30 | #define MQTT_PORT 1883 31 | #define MQTT_USER "MyBrokerUser" 32 | #define MQTT_PASSWORD "MyBrokerPassword" 33 | #endif 34 | 35 | WiFiClient wifi_client; 36 | PubSubClient mqtt_client(wifi_client); 37 | 38 | // HA Parts 39 | #define ENTITIES_COUNT 3 40 | #define SW_VERSION "1.0.0" 41 | 42 | #define DEVICE1_ID "hadev1" 43 | #define DEVICE1_NAME "Example: Device 1" 44 | 45 | #define DEVICE2_ID "hadev2" 46 | #define DEVICE2_NAME "Example: Device 2" 47 | 48 | // Device 1 definition 49 | HADevice ha_device_1 = HADevice(SW_VERSION); 50 | HASwitch ha_switch_1 = HASwitch("switch","Switch",ha_device_1); 51 | 52 | // Device 2 definition 53 | HADevice ha_device_2 = HADevice(SW_VERSION); 54 | HANumber ha_number_2 = HANumber("number","Slicer",ha_device_2,1,100,1); 55 | HAText ha_text_2 = HAText("text","Input text",ha_device_2,100); 56 | 57 | void ha_callback(HAEntity *entity, char *topic, byte *payload, unsigned int length); 58 | 59 | void setup() { 60 | Serial.begin(115200); 61 | mqtt_client.setServer(MQTT_SERVER, MQTT_PORT); 62 | 63 | // Delayed device configuration 64 | ha_device_1.setIdentifier(DEVICE1_ID); 65 | ha_device_1.setName(DEVICE1_NAME); 66 | 67 | ha_device_2.setIdentifier(DEVICE2_ID); 68 | ha_device_2.setName(DEVICE2_NAME); 69 | 70 | ha_number_2.addFeature(HA_FEATURE_AVAILABILITY); 71 | ha_text_2.addFeature(HA_FEATURE_AVAILABILITY); 72 | 73 | // Initial states 74 | ha_number_2.setAvailable(true); 75 | ha_text_2.setAvailable(true); 76 | 77 | ha_switch_1.setState(true); 78 | ha_number_2.setState(90); 79 | ha_text_2.setState("Hello HomeAssistant"); 80 | 81 | // Initialize HA-MQTT 82 | HAMQTT.begin(mqtt_client,ENTITIES_COUNT); 83 | HAMQTT.addEntity(ha_switch_1); 84 | HAMQTT.addEntity(ha_number_2); 85 | HAMQTT.addEntity(ha_text_2); 86 | 87 | HAMQTT.setCallback(ha_callback); 88 | 89 | // start wifi 90 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 91 | } 92 | 93 | void loop() { 94 | if(WiFi.status() == WL_CONNECTED && !HAMQTT.connected()) { 95 | if( HAMQTT.connect("examples",MQTT_USER,MQTT_PASSWORD)) 96 | Serial.println("Connected to MQTT"); 97 | else 98 | { 99 | Serial.println("Failed to connect to MQTT"); 100 | delay(5000); 101 | } 102 | } 103 | HAMQTT.loop(); 104 | delay(50); 105 | } 106 | 107 | /* Callback from HA-MQTT entities. It is called when an entity changes its state. 108 | 109 | Entity can be NULL when the received topic is not related to any entity. 110 | This is useful to handle other topics with the same mqtt client. 111 | */ 112 | void ha_callback(HAEntity *entity, char *topic, byte *payload, unsigned int length) { 113 | if(entity == &ha_switch_1) { 114 | if(ha_switch_1.getState()) { 115 | ha_device_2.setAvailable(true); 116 | } else { 117 | ha_device_2.setAvailable(false); 118 | } 119 | 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /examples/number/number.ino: -------------------------------------------------------------------------------- 1 | /* ha-mqtt-entities library number slicer example 2 | 3 | - Board: ESP32* 4 | 5 | This example creates an HA-MQTT device with a slicer-number entity. 6 | 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | // This file is not included in the repository only used for local testing 16 | // #include "secrets.h" 17 | 18 | // You must set the next defines 19 | #ifndef SECRETS_H 20 | #define WIFI_SSID "MyWifi" 21 | #define WIFI_PASSWORD "MyPassword" 22 | #define MQTT_SERVER "192.168.1.X" 23 | #define MQTT_PORT 1883 24 | #define MQTT_USER "MyBrokerUser" 25 | #define MQTT_PASSWORD "MyBrokerPassword" 26 | #endif 27 | 28 | WiFiClient wifi_client; 29 | PubSubClient mqtt_client(wifi_client); 30 | 31 | // HA Parts 32 | #define ENTITIES_COUNT 1 33 | HADevice ha_device = HADevice("example01","Example slicer HA-MQTTT","1.0"); 34 | HANumber ha_number = HANumber("example01number","Slicer",ha_device,1,100,1); 35 | 36 | void ha_callback(HAEntity *entity, char *topic, byte *payload, unsigned int length); 37 | 38 | void setup() { 39 | Serial.begin(115200); 40 | mqtt_client.setServer(MQTT_SERVER, MQTT_PORT); 41 | 42 | HAMQTT.begin(mqtt_client,ENTITIES_COUNT); 43 | HAMQTT.addEntity(ha_number); 44 | ha_number.setState(50); 45 | HAMQTT.setCallback(ha_callback); 46 | 47 | // start wifi 48 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 49 | Serial.printf("HaMqttEntities version: %s\n",HA_MQTT_VERSION_S); 50 | } 51 | 52 | void loop() { 53 | if(WiFi.status() == WL_CONNECTED && !HAMQTT.connected()) { 54 | if( HAMQTT.connect("examples",MQTT_USER,MQTT_PASSWORD)) 55 | Serial.println("Connected to MQTT"); 56 | else 57 | { 58 | Serial.println("Failed to connect to MQTT"); 59 | delay(5000); 60 | } 61 | } 62 | HAMQTT.loop(); 63 | } 64 | 65 | /* Callback from HA-MQTT entities. It is called when an entity changes its state. 66 | 67 | Entity can be NULL when the received topic is not related to any entity. 68 | This is useful to handle other topics with the same mqtt client. 69 | */ 70 | void ha_callback(HAEntity *entity, char *topic, byte *payload, unsigned int length){ 71 | if(entity == &ha_number){ 72 | Serial.printf("Changed number state to %d \n",ha_number.getState()); 73 | } 74 | else 75 | Serial.println("Callback called from other subscription"); 76 | 77 | } 78 | -------------------------------------------------------------------------------- /examples/select/select.ino: -------------------------------------------------------------------------------- 1 | /* ha-mqtt-entities library number slicer example 2 | 3 | 4 | This example shows how to initialize the select entity with a const char*[] 5 | array to save RAM 6 | 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | // This file is not included in the repository only used for local testing 16 | // #include "secrets.h" 17 | 18 | // You must set the next defines 19 | #ifndef SECRETS_H 20 | #define WIFI_SSID "MyWifi" 21 | #define WIFI_PASSWORD "MyPassword" 22 | #define MQTT_SERVER "192.168.1.X" 23 | #define MQTT_PORT 1883 24 | #define MQTT_USER "MyBrokerUser" 25 | #define MQTT_PASSWORD "MyBrokerPassword" 26 | #endif 27 | 28 | WiFiClient wifi_client; 29 | PubSubClient mqtt_client(wifi_client); 30 | 31 | // HA Parts 32 | #define ENTITIES_COUNT 1 33 | #define HA_DEVICE_ID "example04" 34 | #define HA_DEVICE_FRIENDLY_NAME "Example Select HA-MQTT" 35 | 36 | #define OPTIONS_COUNT 3 37 | const char *select_options[OPTIONS_COUNT] PROGMEM = { 38 | "Option 1", 39 | "Option 2", 40 | "Option 3" 41 | }; 42 | 43 | HADevice ha_device = HADevice(HA_DEVICE_ID,HA_DEVICE_FRIENDLY_NAME,"1.0"); 44 | HASelect ha_select = HASelect("select04uid","Select",ha_device,OPTIONS_COUNT,select_options); 45 | 46 | 47 | void ha_callback(HAEntity *entity, char *topic, byte *payload, unsigned int length); 48 | 49 | void setup() { 50 | Serial.begin(115200); 51 | mqtt_client.setServer(MQTT_SERVER, MQTT_PORT); 52 | 53 | HAMQTT.begin(mqtt_client,ENTITIES_COUNT); 54 | HAMQTT.addEntity(ha_select); 55 | HAMQTT.setCallback(ha_callback); 56 | 57 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 58 | } 59 | 60 | void loop() { 61 | if(!HAMQTT.connected() && 62 | !HAMQTT.connect("examples",MQTT_USER,MQTT_PASSWORD)) 63 | delay(1000); 64 | HAMQTT.loop(); 65 | } 66 | 67 | /* Callback from HA-MQTT entities. It is called when an entity changes its state. 68 | 69 | Entity can be NULL when the received topic is not related to any entity. 70 | This is useful to handle other topics with the same mqtt client. 71 | */ 72 | void ha_callback(HAEntity *entity, char *topic, byte *payload, 73 | unsigned int length) { 74 | 75 | if(entity == &ha_select) 76 | Serial.printf("Select state: %s\n",ha_select.getState()); 77 | 78 | } 79 | -------------------------------------------------------------------------------- /examples/sensorbinary/sensorbinary.ino: -------------------------------------------------------------------------------- 1 | /* ha-mqtt-entities library example of a binary sensor with device class set to 2 | motion 3 | 4 | - Board: ESP32* 5 | 6 | You must set the definitions in the code under SECRETS_H 7 | 8 | */ 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | // This file is not included in the repository only used for local testing 16 | // #include "secrets.h" 17 | 18 | // You must set the next defines 19 | #ifndef SECRETS_H 20 | #define WIFI_SSID "MyWifi" 21 | #define WIFI_PASSWORD "MyPassword" 22 | #define MQTT_SERVER "192.168.1.X" 23 | #define MQTT_PORT 1883 24 | #define MQTT_USER "MyBrokerUser" 25 | #define MQTT_PASSWORD "MyBrokerPassword" 26 | #endif 27 | 28 | WiFiClient wifi_client; 29 | PubSubClient mqtt_client(wifi_client); 30 | 31 | // HA Parts 32 | #define ENTITIES_COUNT 1 33 | #define SENSOR_ID "my_motion_sensor" 34 | #define FRIENDLY_NAME "Example of motion sensor" 35 | 36 | HASensorBinary ha_sensor = HASensorBinary(SENSOR_ID,FRIENDLY_NAME); 37 | 38 | void setup() { 39 | mqtt_client.setServer(MQTT_SERVER, MQTT_PORT); 40 | 41 | HAMQTT.begin(mqtt_client,ENTITIES_COUNT); 42 | ha_sensor.addFeature(HA_FEATURE_DEVICE_CLASS,"motion"); 43 | HAMQTT.addEntity(ha_sensor); 44 | 45 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 46 | } 47 | 48 | void loop() { 49 | static unsigned long ten_second_delay = millis() + 10000; 50 | static bool motion_detected = false; 51 | HAMQTT.loop(); 52 | if(!HAMQTT.connected() && 53 | !HAMQTT.connect("examples",MQTT_USER,MQTT_PASSWORD)) 54 | delay(1000); 55 | 56 | if(millis() > ten_second_delay) { 57 | // Change the state of the sensor every 10 seconds 58 | ten_second_delay = millis() + 10000; 59 | ha_sensor.setState(motion_detected); 60 | motion_detected = !motion_detected; 61 | } 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /examples/sensornumeric/sensornumeric.ino: -------------------------------------------------------------------------------- 1 | /* ha-mqtt-entities library example of a sensor that shows 2 | history graph in Home Assistant. 3 | 4 | - Board: ESP32* 5 | 6 | You must set the definitions in the code under SECRETS_H. 7 | 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | // This file is not included in the repository only used for local testing 17 | // #include "secrets.h" 18 | 19 | // You must set the next defines 20 | #ifndef SECRETS_H 21 | #define WIFI_SSID "MyWifi" 22 | #define WIFI_PASSWORD "MyPassword" 23 | #define MQTT_SERVER "192.168.1.X" 24 | #define MQTT_PORT 1883 25 | #define MQTT_USER "MyBrokerUser" 26 | #define MQTT_PASSWORD "MyBrokerPassword" 27 | #endif 28 | 29 | WiFiClient wifi_client; 30 | PubSubClient mqtt_client(wifi_client); 31 | 32 | // HA Parts 33 | #define ENTITIES_COUNT 1 34 | #define HA_DEVICE_ID "example04" 35 | #define HA_DEVICE_FRIENDLY_NAME "Example Numeric-Sensor HA-MQTT" 36 | 37 | #define UNIT_OF_MEASUREMENT "V" 38 | #define PRECISION 2 // Number of decimals 39 | 40 | HADevice ha_device = HADevice(HA_DEVICE_ID,HA_DEVICE_FRIENDLY_NAME,"1.0"); 41 | 42 | HASensorNumeric ha_sensor = HASensorNumeric( 43 | "sensor05uid","Volts",ha_device,UNIT_OF_MEASUREMENT,PRECISION); 44 | 45 | void setup() { 46 | mqtt_client.setServer(MQTT_SERVER, MQTT_PORT); 47 | 48 | HAMQTT.begin(mqtt_client,ENTITIES_COUNT); 49 | HAMQTT.addEntity(ha_sensor); 50 | 51 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 52 | } 53 | 54 | void loop() { 55 | static unsigned long one_second_delay = millis() + 1000; 56 | static float counter = 0; 57 | HAMQTT.loop(); 58 | if(!HAMQTT.connected() && 59 | !HAMQTT.connect("examples",MQTT_USER,MQTT_PASSWORD)) 60 | delay(1000); 61 | 62 | if(millis() > one_second_delay) { 63 | // Update the sensor every second creating a sin wave 64 | one_second_delay = millis() + 1000; 65 | counter+=0.1; 66 | ha_sensor.setState(3.3*sin(counter)); 67 | } 68 | 69 | } 70 | 71 | -------------------------------------------------------------------------------- /examples/switch/switch.ino: -------------------------------------------------------------------------------- 1 | /* mqtt-ha-entities library switch example 2 | 3 | This example creates an HA-MQTT device with a switch entity. 4 | 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // This file is not included in the repository only used for local testing 12 | // #include "secrets.h" 13 | 14 | // You must set the next defines 15 | #ifndef SECRETS_H 16 | #define WIFI_SSID "MyWifi" 17 | #define WIFI_PASSWORD "MyPassword" 18 | #define MQTT_SERVER "192.168.1.X" 19 | #define MQTT_PORT 1883 20 | #define MQTT_USER "MyBrokerUser" 21 | #define MQTT_PASSWORD "MyBrokerPassword" 22 | #endif 23 | 24 | WiFiClient wifi_client; 25 | PubSubClient mqtt_client(wifi_client); 26 | 27 | // HA Parts 28 | #define ENTITIES_COUNT 1 29 | HADevice ha_device("example02","Example 2 HA-MQTT","1.0"); 30 | HASwitch ha_switch = HASwitch("example02switch","Test on/off",ha_device); 31 | 32 | void ha_callback(HAEntity *entity, char *topic, byte *payload, unsigned int length); 33 | 34 | void setup() { 35 | Serial.begin(115200); 36 | mqtt_client.setServer(MQTT_SERVER, MQTT_PORT); 37 | 38 | HAMQTT.begin(mqtt_client,ENTITIES_COUNT); 39 | HAMQTT.addEntity(ha_switch); 40 | HAMQTT.setCallback(ha_callback); 41 | 42 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 43 | } 44 | 45 | void loop() { 46 | if(WiFi.status() == WL_CONNECTED && !HAMQTT.connected()) { 47 | if( HAMQTT.connect("examples",MQTT_USER,MQTT_PASSWORD) ) 48 | Serial.println("Connected to MQTT"); 49 | else 50 | { 51 | Serial.println("Failed to connect to MQTT. Retry in 5 seconds ..."); 52 | delay(5000); 53 | } 54 | } 55 | HAMQTT.loop(); 56 | } 57 | 58 | /* Callback from HA-MQTT entities. It is called when an entity changes its state. 59 | 60 | Entity can be NULL when the received topic is not related to any entity. 61 | This is useful to handle other topics with the same mqtt client. 62 | */ 63 | void ha_callback(HAEntity *entity, char *topic, byte *payload, 64 | unsigned int length) 65 | { 66 | Serial.printf("Received topic: %s\n",topic); 67 | if(entity == &ha_switch) { 68 | Serial.printf("Changed switch state to %d \n",ha_switch.getState()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=HaMqttEntities 2 | version=1.0.10 3 | author=Paulino Ruiz de Clavijo Vázquez 4 | maintainer=Paulino Ruiz de Clavijo Vázquez 5 | sentence=Easy HomeAssistant MQTT Integration Library 6 | paragraph=Library to integrate your Arduino/ESP with HomeAssistant using MQTT. It comes with a set of classes corresponding to the different types of entities that HomeAssistant supports via MQTT discovery. It also includes a set of examples to get you started. 7 | category=Communication 8 | url=https://github.com/paulino/ha-mqtt-entities 9 | architectures=* 10 | depends=PubSubClient 11 | includes=HaMqttEntities.h 12 | -------------------------------------------------------------------------------- /src/HaMqttEntities.h: -------------------------------------------------------------------------------- 1 | #ifndef __HAMQTT_H__ 2 | #define __HAMQTT_H__ 3 | 4 | #define HA_MQTT_VERSION_N 10 5 | #define HA_MQTT_VERSION_S "1.0.10" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | // Singleton instance via extern for Arduino library 21 | extern HAMQTTController& HAMQTT; 22 | 23 | #endif 24 | 25 | -------------------------------------------------------------------------------- /src/habutton.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | const char *HAButton::component PROGMEM = "button"; 7 | 8 | HAButton::HAButton(const char *unique_id, const char *name, HADevice& device): 9 | HAEntity(unique_id,name,component,&device) { 10 | } 11 | HAButton::HAButton(const char *unique_id, const char *name): 12 | HAEntity(unique_id,name,component) { 13 | } 14 | 15 | void HAButton::onConnect(PubSubClient * client){ 16 | char topic[HA_MAX_TOPIC_LENGTH],payload[HA_MAX_PAYLOAD_LENGTH]; 17 | getCommandTopic(topic); 18 | client->subscribe(topic); 19 | 20 | getConfigTopic(topic); 21 | getConfigPayload(payload,true,false); 22 | client->publish(topic,payload); 23 | } -------------------------------------------------------------------------------- /src/habutton.h: -------------------------------------------------------------------------------- 1 | #ifndef __HABUTTON_H__ 2 | #define __HABUTTON_H__ 3 | 4 | #include 5 | 6 | class PubSubClient; 7 | class HADevice; 8 | 9 | /** 10 | * @brief Button entity for Home Assistant 11 | * 12 | * Button does not store the state, the event must be handled in the callback 13 | * or in the derived class method onStateChange 14 | * 15 | * HA send by default the string "PRESS", but this class not check the payload, 16 | * it assumes that the payload is "PRESS" 17 | */ 18 | 19 | class HAButton : public HAEntity { 20 | protected: 21 | static const char *component; 22 | 23 | public: 24 | HAButton(const char *unique_id,const char *name,HADevice& device); 25 | HAButton(const char *unique_id,const char *name); 26 | 27 | void onConnect(PubSubClient * client); 28 | void onReceivedTopic(PubSubClient * client, 29 | byte *payload, unsigned int length) {;} 30 | 31 | }; 32 | 33 | #endif -------------------------------------------------------------------------------- /src/haconsts.h: -------------------------------------------------------------------------------- 1 | #ifndef __HACONSTS_H__ 2 | #define __HACONSTS_H__ 3 | 4 | #define HA_FEATURE_AVAILABILITY -1 5 | #define HA_FEATURE_DEVICE_CLASS 0 6 | #define HA_FEATURE_ICON 1 7 | #define HA_FEATURE_ENTITY_CATEGORY 2 8 | #define HA_FEATURE_MODE 3 9 | 10 | #define HA_AVTY_OFF 0 11 | #define HA_AVTY_ON 1 12 | #define HA_AVTY_PENDING_ON 2 13 | #define HA_AVTY_PENDING_OFF 3 14 | #define HA_AVTY_DISABLED 4 15 | 16 | #define HA_TOPIC_HEAD "homeassistant" 17 | 18 | #endif -------------------------------------------------------------------------------- /src/hadevice.cpp: -------------------------------------------------------------------------------- 1 | #include "hadevice.h" 2 | #include "hamqttcontroller.h" 3 | #include "haconsts.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | const char *HADevice::configDeviceTemplate PROGMEM = "\ 11 | \"dev\":{\ 12 | \"ids\":\"%s\",\ 13 | \"name\":\"%s\",\ 14 | \"sw\":\"%s\""; 15 | 16 | 17 | // Available topic shared by all entities 18 | const char *HADevice::availabilityTopicTemplate PROGMEM = \ 19 | HA_TOPIC_HEAD"/%s/available"; 20 | 21 | 22 | HADevice::HADevice(const char *identifier, const char *name, 23 | const char *sw_version, const char *manufacturer, const char *model, 24 | const char *hw_version ) { 25 | this->identifier = NULL; 26 | this->name = NULL; 27 | this->sw_version = sw_version; 28 | this->manufacturer = manufacturer; 29 | this->model = model; 30 | this->hw_version = hw_version; 31 | this->available = HA_AVTY_DISABLED; 32 | this->setIdentifier(identifier); 33 | this->setName(name); 34 | } 35 | 36 | HADevice::HADevice(const char *sw_version) : HADevice() { 37 | this->sw_version = sw_version; 38 | } 39 | HADevice::HADevice() { 40 | this->identifier = NULL; 41 | this->name = NULL; 42 | this->manufacturer = NULL; 43 | this->model = NULL; 44 | this->hw_version = NULL; 45 | this->available = HA_AVTY_DISABLED; 46 | } 47 | 48 | void HADevice::setIdentifier(const char *identifier) { 49 | if (this->identifier != NULL) 50 | delete this->identifier; 51 | this->identifier = strdup(identifier); 52 | } 53 | 54 | void HADevice::setName(const char *name) { 55 | if (this->name != NULL) 56 | delete this->name; 57 | this->name = strdup(name); 58 | } 59 | 60 | char * HADevice::getConfigPayload(char *buffer) { 61 | int len; 62 | sprintf(buffer, configDeviceTemplate, 63 | this->identifier, this->name, this->sw_version); 64 | if (this->manufacturer != NULL) { 65 | len = strlen(buffer); 66 | sprintf(buffer + len, ",\"mf\":\"%s\"", this->manufacturer); 67 | } 68 | if (this->model != NULL) { 69 | len = strlen(buffer); 70 | sprintf(buffer + len, ",\"mdl\":\"%s\"", this->model); 71 | } 72 | if (this->hw_version != NULL) { 73 | len = strlen(buffer); 74 | sprintf(buffer + len, ",\"hw\":\"%s\"", this->hw_version); 75 | } 76 | len = strlen(buffer); 77 | buffer[len++] = '}'; 78 | buffer[len] = '\0'; 79 | return buffer; 80 | } 81 | 82 | void HADevice::setAvailable(bool available) { 83 | if (this->available != HA_AVTY_DISABLED) 84 | { 85 | if (available && this->available != HA_AVTY_ON) 86 | this->available = HA_AVTY_PENDING_ON; 87 | else if (!available && this->available != HA_AVTY_OFF) 88 | this->available = HA_AVTY_PENDING_OFF; 89 | } 90 | HAMQTTController::getInstance().setAvailable(available, *this); 91 | } 92 | 93 | void HADevice::addFeature(int key, const char *value) { 94 | if (key == HA_FEATURE_AVAILABILITY) 95 | { 96 | this->available = HA_AVTY_PENDING_ON; 97 | HAMQTTController::getInstance().setLastWillDevice(*this); 98 | } 99 | 100 | } 101 | 102 | char *HADevice::getAvailabilityTopic(char *buffer) 103 | { 104 | sprintf(buffer,availabilityTopicTemplate,this->identifier); 105 | return buffer; 106 | } 107 | 108 | void HADevice::sendAvailable(PubSubClient *mqttClient,bool force) { 109 | // TODO: Create a new class called HAAvailability 110 | if(this->available == HA_AVTY_DISABLED) 111 | return; 112 | if (force && this->available == HA_AVTY_ON) 113 | this->available = HA_AVTY_PENDING_ON; 114 | else if (force && this->available == HA_AVTY_OFF) 115 | this->available = HA_AVTY_PENDING_OFF; 116 | 117 | if ( this->available != HA_AVTY_PENDING_ON && 118 | this->available != HA_AVTY_PENDING_OFF) 119 | return; 120 | 121 | char topic[HA_MAX_TOPIC_LENGTH]; 122 | getAvailabilityTopic(topic); 123 | if (this->available == HA_AVTY_PENDING_ON) 124 | { 125 | if (! mqttClient->publish(topic,"online")) 126 | return; 127 | this->available = HA_AVTY_ON; 128 | } 129 | else if (this->available == HA_AVTY_PENDING_OFF) 130 | { 131 | if (! mqttClient->publish(topic,"offline")) 132 | return; 133 | this->available = HA_AVTY_OFF; 134 | } 135 | } -------------------------------------------------------------------------------- /src/hadevice.h: -------------------------------------------------------------------------------- 1 | #ifndef __HA_DEVICE_H__ 2 | #define __HA_DEVICE_H__ 3 | 4 | #include 5 | #include 6 | 7 | #include "haconsts.h" 8 | 9 | class PubSubClient; 10 | 11 | class HADevice { 12 | protected: 13 | static const char *configDeviceTemplate; 14 | static const char *availabilityTopicTemplate; 15 | 16 | char *identifier; 17 | char *name; 18 | const char *sw_version; 19 | const char *hw_version; 20 | const char *manufacturer; 21 | const char *model; 22 | 23 | int8_t available; 24 | 25 | HADevice(); 26 | 27 | public: 28 | HADevice(const char *identifier, const char *name, 29 | const char *sw_version, const char *manufacturer = NULL, 30 | const char *model = NULL, const char *hw_version = NULL); 31 | 32 | HADevice(const char *sw_version); 33 | 34 | inline const char *getSwVersion() {return sw_version;} 35 | 36 | void setIdentifier(const char *identifier); 37 | inline const char *getIdentifier() {return identifier;} 38 | void setName(const char *name); 39 | // Not use dynamic or shared buffers, string is not copied 40 | inline void setManufacturer(const char *mf) {this->manufacturer = mf;} 41 | inline void setModel(const char *model) {this->model = model;} 42 | inline void setHwVersion(const char *hw_version) 43 | {this->hw_version = hw_version;} 44 | 45 | /// Add others predefined feature to the device 46 | void addFeature(int key, const char *value = NULL); 47 | char *getConfigPayload(char *buffer); 48 | 49 | /// Set the available state of all entities of the device 50 | void setAvailable(bool available); 51 | /// Returns true if the availability feature of the device is enabled 52 | inline bool getAvailability() {return available != HA_AVTY_DISABLED;} 53 | char *getAvailabilityTopic(char *buffer); 54 | void sendAvailable(PubSubClient *mqttClient,bool force); 55 | 56 | 57 | 58 | }; 59 | #endif -------------------------------------------------------------------------------- /src/haentity.cpp: -------------------------------------------------------------------------------- 1 | #include"haentity.h" 2 | #include"hadevice.h" 3 | #include"haconsts.h" 4 | 5 | #include 6 | 7 | 8 | /* HA discovery template is 9 | //[/]/config node_id is 10 | optional and will ignored in this implementation 11 | */ 12 | 13 | // Configuration topic 14 | const char *HAEntity::configTopicTemplate PROGMEM = "homeassistant/%s/%s/config"; 15 | 16 | // Configuration payload parts 17 | const char *HAEntity::configPayloadTemplate PROGMEM = "{\ 18 | \"~\":\"homeassistant/%s/%s\",\ 19 | \"name\":\"%s\",\ 20 | \"uniq_id\":\"%s\""; 21 | 22 | const char *HAEntity::commandTopicTemplate PROGMEM = "homeassistant/%s/%s/set"; 23 | const char *HAEntity::stateTopicTemplate PROGMEM = "homeassistant/%s/%s/state"; 24 | const char *HAEntity::availabilityTopicTemplate PROGMEM = 25 | "homeassistant/%s/%s/available"; 26 | 27 | 28 | const char *HAEntity::featureKeys[] PROGMEM = { 29 | "dev_cla", 30 | "ic", 31 | "ent_cat", 32 | "mode" 33 | }; 34 | 35 | 36 | HAEntity::HAEntity(const char *id,const char *name,const char *component, 37 | HADevice *ha_device) { 38 | this->uniqueId = NULL; 39 | this->id = id ; 40 | this->component = component; 41 | this->name = name; 42 | this->device = ha_device; 43 | this->available = HA_AVTY_DISABLED; 44 | this->features = NULL; 45 | } 46 | 47 | const char *HAEntity::getUniqueId() { 48 | if (this->device == NULL) 49 | return this->id; 50 | if (this->uniqueId == NULL) 51 | { 52 | this->uniqueId = new char[strlen(this->id) + strlen(this->device->getIdentifier()) + 2]; 53 | sprintf(this->uniqueId,"%s%s",this->device->getIdentifier(),this->id); 54 | } 55 | return this->uniqueId; 56 | } 57 | 58 | char *HAEntity::getConfigPayload(char *buffer, 59 | bool add_command_topic,bool add_state_topic) { 60 | int len; 61 | sprintf(buffer,configPayloadTemplate, component,getUniqueId(), name, 62 | getUniqueId()); 63 | len = strlen(buffer); 64 | if (this->device != NULL) 65 | { 66 | buffer[len] = ','; 67 | this->device->getConfigPayload(buffer+len+1); 68 | len = strlen(buffer); 69 | } 70 | if (add_command_topic) 71 | { 72 | buffer[len] = ','; 73 | sprintf(buffer+len+1, PSTR("\"cmd_t\":\"~/set\"")); 74 | len = strlen(buffer); 75 | } 76 | if (add_state_topic) 77 | { 78 | buffer[len] = ','; 79 | sprintf(buffer+len+1, PSTR("\"stat_t\":\"~/state\"")); 80 | len = strlen(buffer); 81 | } 82 | // Availability if set in the entity or in the device (common to all entities) 83 | if (this->available != HA_AVTY_DISABLED) 84 | { 85 | buffer[len] = ','; 86 | sprintf(buffer+len+1,PSTR("\"avty_t\":\"~/available\"")); 87 | len = strlen(buffer); 88 | } else if (this->device != NULL && this->device->getAvailability()) 89 | { 90 | buffer[len] = ','; 91 | sprintf(buffer+len+1,PSTR("\"avty_t\":\"")); 92 | len = strlen(buffer); 93 | this->device->getAvailabilityTopic(buffer+len); 94 | len = strlen(buffer); 95 | sprintf(buffer+len,"\""); 96 | len = strlen(buffer); 97 | } 98 | // Add features 99 | HAKVPairList *pair = features; 100 | while(pair!=NULL && pair->getKey() != NULL) 101 | { 102 | buffer[len] = ','; 103 | if (pair->getKey() == featureKeys[HA_FEATURE_AVAILABILITY]) 104 | sprintf(buffer+len+1,PSTR("\"%s\":\"available\""),pair->getKey()); 105 | else 106 | sprintf(buffer+len+1,PSTR("\"%s\":\"%s\""),pair->getKey(), 107 | pair->getValue()); 108 | len = strlen(buffer); 109 | pair = pair->getNext(); 110 | } 111 | 112 | buffer[len] = '}'; 113 | buffer[len+1] = '\0'; 114 | return buffer; 115 | } 116 | 117 | char *HAEntity::getConfigTopic(char *buffer){ 118 | sprintf(buffer,configTopicTemplate,this->component,getUniqueId()); 119 | return buffer; 120 | } 121 | 122 | char *HAEntity::getCommandTopic(char *buffer) { 123 | sprintf(buffer,commandTopicTemplate,component,getUniqueId()); 124 | return buffer; 125 | } 126 | 127 | char *HAEntity::getStateTopic(char *buffer) { 128 | sprintf(buffer,stateTopicTemplate,component,getUniqueId()); 129 | return buffer; 130 | } 131 | 132 | void HAEntity::addFeature(int key, const char *value) { 133 | if (key == HA_FEATURE_AVAILABILITY) 134 | this->available = HA_AVTY_PENDING_ON; 135 | else 136 | this->addFeature(featureKeys[key],value); 137 | } 138 | 139 | void HAEntity::addFeature(const char *key, const char *value) { 140 | if (this->features == NULL) 141 | this->features = new HAKVPairList(key,value); 142 | else 143 | features->append(key,value); 144 | } 145 | 146 | void HAEntity::setAvailable(bool available) { 147 | if (this->available == HA_AVTY_DISABLED) 148 | return; 149 | if (available && this->available != HA_AVTY_ON) 150 | this->available = HA_AVTY_PENDING_ON; 151 | else if (!available && this->available != HA_AVTY_OFF) 152 | this->available = HA_AVTY_PENDING_OFF; 153 | } 154 | 155 | void HAEntity::sendAvailable(PubSubClient *mqttClient,bool force) { 156 | if(this->available == HA_AVTY_DISABLED) 157 | return; 158 | if (force && this->available == HA_AVTY_ON) 159 | this->available = HA_AVTY_PENDING_ON; 160 | else if (force && this->available == HA_AVTY_OFF) 161 | this->available = HA_AVTY_PENDING_OFF; 162 | 163 | if ( this->available != HA_AVTY_PENDING_ON && 164 | this->available != HA_AVTY_PENDING_OFF) 165 | return; 166 | 167 | char topic[HA_MAX_TOPIC_LENGTH]; 168 | char payload[HA_MAX_PAYLOAD_LENGTH]; 169 | getAvailabilityTopic(topic); 170 | if (this->available == HA_AVTY_PENDING_ON) 171 | { 172 | sprintf(payload,PSTR("online")); 173 | if (! mqttClient->publish(topic,payload)) 174 | return; 175 | this->available = HA_AVTY_ON; 176 | } 177 | else if (this->available == HA_AVTY_PENDING_OFF) 178 | { 179 | sprintf(payload,PSTR("offline")); 180 | if (! mqttClient->publish(topic,payload)) 181 | return; 182 | this->available = HA_AVTY_OFF; 183 | } 184 | } 185 | 186 | char *HAEntity::getAvailabilityTopic(char *buffer) 187 | { 188 | sprintf(buffer,availabilityTopicTemplate,component,getUniqueId()); 189 | return buffer; 190 | } -------------------------------------------------------------------------------- /src/haentity.h: -------------------------------------------------------------------------------- 1 | /* 2 | Warning: Not use ids (unique_id,device_id) with more than 50 characters 3 | */ 4 | #ifndef __HAENTITY_H__ 5 | #define __HAENTITY_H__ 6 | 7 | #include 8 | #include "hakvpairlist.h" 9 | 10 | #ifndef HA_MAX_TOPIC_LENGTH 11 | #define HA_MAX_TOPIC_LENGTH 128 12 | #endif 13 | #ifndef HA_MAX_PAYLOAD_LENGTH 14 | #define HA_MAX_PAYLOAD_LENGTH 512 15 | #endif 16 | 17 | class PubSubClient; 18 | class HADevice; 19 | 20 | /** Abstract class for HA entities 21 | 22 | */ 23 | class HAEntity 24 | { 25 | protected: 26 | 27 | static const char *configTopicTemplate; 28 | static const char *configPayloadTemplate; 29 | static const char *availabilityTopicTemplate; 30 | static const char *featureKeys[]; 31 | 32 | static const char *commandTopicTemplate; 33 | static const char *stateTopicTemplate; 34 | 35 | HADevice *device; 36 | const char *id; 37 | char *uniqueId; /** Derived from id and device */ 38 | const char *name; 39 | const char *component; 40 | HAKVPairList *features; 41 | int8_t available; 42 | 43 | char *getConfigTopic(char *buffer); 44 | char *getConfigPayload(char *buffer, bool add_command_topic, 45 | bool add_state_topic); 46 | 47 | /// Build default state topic 48 | char *getStateTopic(char *buffer); 49 | 50 | 51 | public: 52 | HAEntity(const char *unique_id, const char *name, const char *component, 53 | HADevice *device = NULL); 54 | 55 | const char *getUniqueId(); 56 | inline HADevice *getDevice() { return device; } 57 | 58 | /// Some entities do not have a command topic and returns NULL 59 | virtual char *getCommandTopic(char *buffer) ; 60 | virtual void sendState(PubSubClient * ) {;} 61 | virtual void setState() {;} 62 | 63 | /// Flag used in HAMQTT indicating if the state has changed 64 | /// and needs to be sent to the broker 65 | virtual bool isDirty() {return false;} 66 | 67 | virtual void onConnect(PubSubClient * ) = 0; 68 | virtual void onReceivedTopic(PubSubClient *, byte* payload, 69 | unsigned int length) = 0; 70 | 71 | /// Action to be performed when the state changes by default it does nothing 72 | /// It can be redefined in the derived class 73 | virtual void onStateChange() {;} 74 | 75 | /// Add predefined feature to the entity 76 | void addFeature(int key, const char *value = NULL); 77 | 78 | /// Add not predefined feature to the entity 79 | void addFeature(const char *key, const char *value); 80 | 81 | /// It only works if previously the availability feature has been added 82 | void setAvailable(bool available); 83 | void sendAvailable(PubSubClient *, bool force = false); 84 | char *getAvailabilityTopic(char *buffer); 85 | 86 | }; 87 | 88 | #endif -------------------------------------------------------------------------------- /src/hakvpairlist.cpp: -------------------------------------------------------------------------------- 1 | #include"hakvpairlist.h" 2 | 3 | HAKVPairList::HAKVPairList(const char *key, const char *value) 4 | { 5 | this->key = key; 6 | this->value = value; 7 | this->next = NULL; 8 | } 9 | 10 | /** Append new element following the list */ 11 | void HAKVPairList::append(const char *key, const char *value) 12 | { 13 | HAKVPairList *pair = new HAKVPairList(key,value); 14 | HAKVPairList *last = this; 15 | while(last->next != NULL) 16 | last = last->next; 17 | last->next = pair; 18 | } -------------------------------------------------------------------------------- /src/hakvpairlist.h: -------------------------------------------------------------------------------- 1 | #ifndef __HAKVPAIRLIST_H__ 2 | #define __HAKVPAIRLIST_H__ 3 | 4 | #include 5 | 6 | class HAKVPairList { 7 | protected: 8 | const char *key; 9 | const char *value; 10 | HAKVPairList *next; 11 | public: 12 | HAKVPairList(const char* key , const char *value ); 13 | void append(const char *key, const char *value=NULL); 14 | inline const char *getKey() {return key;} 15 | inline const char *getValue() {return value;} 16 | inline HAKVPairList *getNext() {return next;} 17 | }; 18 | 19 | #endif -------------------------------------------------------------------------------- /src/hamqttcontroller.cpp: -------------------------------------------------------------------------------- 1 | #include"hamqttcontroller.h" 2 | #include"hadevice.h" 3 | 4 | // When the broker is restarted, the data sent will be lost if HA is not 5 | // connected first. This is the time to wait for HA to connect. 6 | #define HA_DELAY_RECONNECT 5000 7 | 8 | // Delay the first state send, HA need some time to process MQTT discovery and 9 | // enable the entities before sending states 10 | #define HA_DELAY_SEND_STATES 3000 11 | 12 | const char *HAMQTTController::topicHass PROGMEM = "homeassistant/status"; 13 | 14 | HAMQTTController *HAMQTTController::instance = new HAMQTTController(); 15 | 16 | // Global var, Arduino helper 17 | HAMQTTController& HAMQTT = HAMQTTController::getInstance(); 18 | 19 | void HAMQTTController::pubSubClientHandler(char* topic, byte* payload, 20 | unsigned int length) 21 | { 22 | instance->mqttOnReceived(topic,payload,length); 23 | } 24 | 25 | HAMQTTController& HAMQTTController::getInstance() { 26 | return *instance; 27 | } 28 | 29 | HAMQTTController::HAMQTTController() { 30 | this->mqttClient = NULL; 31 | this->entities = NULL; 32 | this->entityCounter = 0; 33 | this->callback = NULL; 34 | this->state = Disconnected; 35 | this->lastWillDevice = NULL; 36 | } 37 | 38 | void HAMQTTController::begin(PubSubClient& mqtt_client,int component_count) { 39 | this->mqttClient = &mqtt_client; 40 | this->entities = new HAEntity*[component_count]; 41 | this->entityCounter = 0; 42 | this->mqttClient->setCallback(HAMQTTController::pubSubClientHandler); 43 | if (this->mqttClient->getBufferSize() < HA_MAX_PAYLOAD_LENGTH) 44 | this->mqttClient->setBufferSize(HA_MAX_PAYLOAD_LENGTH); 45 | } 46 | 47 | void HAMQTTController::addEntity(HAEntity& entity) { 48 | this->entities[this->entityCounter] = &entity; 49 | this->entityCounter++; 50 | } 51 | 52 | void HAMQTTController::setLastWillDevice(HADevice& device) { 53 | this->lastWillDevice = &device; 54 | } 55 | 56 | boolean HAMQTTController::connect(const char *id, const char *user, 57 | const char *pass) { 58 | 59 | if (this->mqttClient == NULL) 60 | return false; 61 | if (this->lastWillDevice != NULL) 62 | { 63 | char will_topic[HA_MAX_TOPIC_LENGTH]; 64 | this->lastWillDevice->getAvailabilityTopic(will_topic); 65 | if (!this->mqttClient->connect(id,user,pass,will_topic,0,false,"offline")) 66 | return false; 67 | } else if (!this->mqttClient->connect(id,user,pass)) 68 | return false; 69 | this->mqttClient->subscribe(topicHass); 70 | this->state = Start; 71 | return this->mqttClient->connected(); 72 | 73 | } 74 | 75 | void HAMQTTController::onConnect() { 76 | HAEntity *entity; 77 | for (int i = 0; i < this->entityCounter; i++){ 78 | entity = this->entities[i]; 79 | entity->onConnect(this->mqttClient); 80 | } 81 | } 82 | 83 | void HAMQTTController::sendAllStates() { 84 | HAEntity *entity; 85 | HADevice *device = NULL; 86 | for (int i = 0; i < this->entityCounter; i++) { 87 | entity = this->entities[i]; 88 | entity->sendAvailable(this->mqttClient,true); 89 | if (device == NULL && entity->getDevice() != NULL) 90 | { 91 | device = entity->getDevice(); 92 | device->sendAvailable(this->mqttClient,true); 93 | } 94 | else if (device != NULL && device != entity->getDevice()) 95 | { 96 | // This happens when there are more than one device 97 | device = entity->getDevice(); 98 | device->sendAvailable(this->mqttClient,true); 99 | } 100 | entity->sendState(this->mqttClient); 101 | } 102 | 103 | } 104 | 105 | bool HAMQTTController::mqttOnReceived(char *topic, byte *payload, 106 | unsigned int length) { 107 | char cmd_topic_buffer[HA_MAX_TOPIC_LENGTH]; 108 | if (strcmp(topic,topicHass) == 0 && length > 3 && payload[1] == 'n') { 109 | // HA topic is received then HA has been restarted and data is required 110 | this->state = Start; 111 | return false; 112 | } 113 | else for (int i = 0; i < this->entityCounter; i++){ 114 | const char *cmd_topic = this->entities[i]->getCommandTopic(cmd_topic_buffer); 115 | HAEntity *entity = this->entities[i]; 116 | if (cmd_topic != NULL && strcmp(cmd_topic,topic) == 0) 117 | { 118 | entity->onReceivedTopic(this->mqttClient,payload,length); 119 | if(this->callback != NULL) 120 | this->callback(entity,topic,payload,length); 121 | entity->sendState(this->mqttClient); 122 | return true; 123 | } 124 | } 125 | if(this->callback != NULL) 126 | this->callback(NULL,topic,payload,length); 127 | return false; 128 | } 129 | 130 | void HAMQTTController::setCallback(HAMQTT_CALLBACK_SIGNATURE(callback)) { 131 | this->callback = callback; 132 | } 133 | 134 | void HAMQTTController::loop() { 135 | HAEntity *entity; 136 | 137 | if (mqttClient == NULL) 138 | return; 139 | 140 | if (!mqttClient->connected()) 141 | this->state = Disconnected; 142 | 143 | this->mqttClient->loop(); 144 | 145 | switch(this->state) 146 | { 147 | case Disconnected: 148 | this->delaySend = millis(); 149 | break; 150 | 151 | case Start: 152 | if (millis() - delaySend > HA_DELAY_RECONNECT ) 153 | { 154 | this->onConnect(); 155 | delaySend = millis(); 156 | this->state = Connecting; 157 | } 158 | break; 159 | case Connecting: 160 | if (millis() - delaySend > HA_DELAY_SEND_STATES ) 161 | { 162 | this->sendAllStates(); 163 | delaySend = millis() - (1000L*3600L*24L); 164 | this->state = Connected; 165 | } 166 | break; 167 | case Connected: 168 | for (int i = 0 ; i < entityCounter ; i++) 169 | { 170 | entity = entities[i]; 171 | if (entity->isDirty()) 172 | entity->sendState(mqttClient); 173 | entity->sendAvailable(mqttClient); 174 | if (!mqttClient->connected()) 175 | break; 176 | } 177 | break; 178 | } 179 | } 180 | 181 | void HAMQTTController::setAvailable(bool available, HADevice& device) { 182 | HAEntity *entity; 183 | for (int i = 0; i < this->entityCounter; i++){ 184 | entity = this->entities[i]; 185 | if (entity->getDevice() == &device) 186 | entity->setAvailable(available); 187 | } 188 | } -------------------------------------------------------------------------------- /src/hamqttcontroller.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | In exclusive mode, the controller will subscribe to the topic of each component and will forward the messages to the corresponding component. 4 | Any topic that is not recognized will be ignored. 5 | 6 | In shared mode, the controller will subscribe to the topic of each component and will forward the user callback 7 | with a pointer to the component or NULL if the topic is not recognized. 8 | 9 | */ 10 | #ifndef __HAMQTTCONTROLLER_H__ 11 | #define __HAMQTTCONTROLLER_H__ 12 | 13 | #include "haentity.h" 14 | #include 15 | 16 | 17 | /* Callback signature: 18 | void callback(HAEntity*, char* topic, byte* payload, unsigned int payload_length) 19 | */ 20 | #define HAMQTT_CALLBACK_SIGNATURE(f_name) void (*f_name)(HAEntity*, char*, byte*, unsigned int) 21 | 22 | // Singleton class associated to the pubsub MQTT client 23 | class HAMQTTController { 24 | 25 | private: 26 | static HAMQTTController *instance; 27 | static const char *topicHass; 28 | HAMQTT_CALLBACK_SIGNATURE(callback); 29 | HAMQTTController(); 30 | 31 | unsigned long int delaySend; 32 | HADevice *lastWillDevice; 33 | 34 | protected: 35 | PubSubClient *mqttClient; 36 | HAEntity **entities; 37 | int entityCounter; 38 | static void pubSubClientHandler(char* topic, byte* payload, unsigned int length); 39 | enum State {Disconnected, Start, Connecting, Connected} state ; 40 | 41 | public: 42 | static HAMQTTController& getInstance(); 43 | HAMQTTController(const HAMQTTController&) = delete; 44 | HAMQTTController& operator=(const HAMQTTController&) = delete; 45 | 46 | void begin(PubSubClient& mqtt_client,int component_count); 47 | void addEntity(HAEntity& entity); 48 | void setLastWillDevice(HADevice& device); 49 | 50 | /** Connect to the MQTT broker using the PubSubClient instance. 51 | * 52 | * On success, it calls onConnect. Using this method is optional, 53 | * but if it is not used, after connecting to MQTT, the onConnect 54 | * method must be called manually. 55 | */ 56 | boolean connect(const char *id, const char *user, const char *pass); 57 | inline boolean connected() { return this->mqttClient->connected(); } 58 | void onConnect(); 59 | void loop(); 60 | void sendAllStates(); 61 | 62 | // It must be called when a mqtt message is received 63 | // return true if the message is for one of the components 64 | bool mqttOnReceived(char *topic, byte *payload, unsigned int length); 65 | 66 | /** 67 | * Set the MQTT callback 68 | * 69 | * When is not set the messages are only processed in each 70 | * component in the mqttOnReceived method 71 | * 72 | * When is set, first the message is processed in each component 73 | * in the mqttOnReceived method and then the callback is called 74 | * 75 | */ 76 | void setCallback(HAMQTT_CALLBACK_SIGNATURE(callback)); 77 | 78 | void setAvailable(bool available, HADevice& device); 79 | 80 | }; 81 | 82 | #endif -------------------------------------------------------------------------------- /src/hanumber.cpp: -------------------------------------------------------------------------------- 1 | #include"hanumber.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | const char *HANumber::component PROGMEM = "number"; 8 | 9 | HANumber::HANumber(const char *unique_id, const char *name, 10 | HADevice& device,int min,int max,const int step): 11 | HANumber(unique_id,name,min,max,step) { 12 | this->device = &device; 13 | } 14 | 15 | HANumber::HANumber(const char *unique_id, const char *name, 16 | int min,int max,const int step): 17 | HAEntity(unique_id,name,component) { 18 | this->dirty = false; 19 | this->max = max; 20 | this->min = min; 21 | this->step = step; 22 | this->state = min; 23 | } 24 | 25 | void HANumber::onConnect(PubSubClient * client){ 26 | char topic[HA_MAX_TOPIC_LENGTH],payload[HA_MAX_PAYLOAD_LENGTH]; 27 | getCommandTopic(topic); 28 | client->subscribe(topic); 29 | 30 | getConfigTopic(topic); 31 | getConfigPayload(payload,true,true); 32 | sprintf(payload+strlen(payload)-1,",\"min\":%d,\"max\":%d,\"step\":%d}", 33 | this->min,this->max,this->step); 34 | client->publish(topic,payload); 35 | } 36 | 37 | void HANumber::sendState(PubSubClient * client){ 38 | dirty = false; 39 | char payload[10]; 40 | char topic[HA_MAX_TOPIC_LENGTH]; 41 | getStateTopic(topic); 42 | sprintf(payload,"%d",this->state); 43 | client->publish(topic,payload); 44 | } 45 | 46 | void HANumber::setState(int state){ 47 | if (state == this->state) 48 | return; 49 | dirty = true; 50 | if (state > this->max) state = this->max; 51 | if (state < this->min) state = this->min; 52 | this->state = state; 53 | this->onStateChange(); 54 | } 55 | 56 | void HANumber::onReceivedTopic(PubSubClient * client, 57 | uint8_t *payload, unsigned int length){ 58 | char buff[15]; 59 | if (length < 1 || length > 14) 60 | return; 61 | strncpy(buff,(char *)payload,length); 62 | this->setState(atoi(buff)); 63 | this->sendState(client); 64 | } 65 | -------------------------------------------------------------------------------- /src/hanumber.h: -------------------------------------------------------------------------------- 1 | #ifndef __HANUMBER_H__ 2 | #define __HANUMBER_H__ 3 | 4 | #include "haentity.h" 5 | 6 | class PubSubClient; 7 | class HADevice; 8 | 9 | class HANumber : public HAEntity { 10 | protected: 11 | static const char *component; 12 | 13 | bool dirty; 14 | int state; 15 | int max; 16 | int min; 17 | int step; 18 | 19 | public: 20 | HANumber(const char *unique_id, const char *name, 21 | HADevice& device, int min, int max, int step); 22 | HANumber(const char *unique_id, const char *name, int min, int max, 23 | int step); 24 | 25 | inline int getState() {return this->state;}; 26 | inline bool isDirty() {return this->dirty;}; 27 | void setState(int state); 28 | 29 | void onConnect(PubSubClient * client); 30 | void onReceivedTopic(PubSubClient * client, byte *payload, 31 | unsigned int length); 32 | void sendState(PubSubClient * client); 33 | 34 | 35 | }; 36 | 37 | #endif -------------------------------------------------------------------------------- /src/haselect.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | const char *HASelect::component PROGMEM = "select"; 6 | 7 | HASelect::HASelect(const char *unique_id, const char *name, u_int8_t max_options): 8 | HAEntity(unique_id,name,component) 9 | { 10 | this->maxOptions = max_options; 11 | this->optionsAdded = 0; 12 | this->state = NULL; 13 | this->options = new char*[max_options]; 14 | } 15 | 16 | HASelect::HASelect(const char *unique_id,const char *name, u_int8_t max_options, 17 | const char *options[]) : HAEntity(unique_id,name,component) { 18 | this->maxOptions = max_options; 19 | this->state = NULL; 20 | this->options = (char **) options; 21 | this->optionsAdded = max_options; 22 | } 23 | 24 | HASelect::HASelect(const char *unique_id, const char *name, HADevice& device, 25 | u_int8_t max_options):HASelect(unique_id,name,max_options) { 26 | this->device = &device; 27 | } 28 | 29 | HASelect::HASelect(const char *unique_id, const char *name, 30 | HADevice& device, u_int8_t max_options, const char* options[]): 31 | HASelect(unique_id,name,max_options,options) { 32 | this->device = &device; 33 | } 34 | 35 | 36 | void HASelect::onConnect(PubSubClient * client) { 37 | char topic[HA_MAX_TOPIC_LENGTH],payload[HA_MAX_PAYLOAD_LENGTH]; 38 | getCommandTopic(topic); 39 | client->subscribe(topic); 40 | 41 | this->getConfigTopic(topic); 42 | this->getConfigPayload(payload,true,true); 43 | // Add options 44 | int index = strlen(payload) - 1; 45 | if (this->optionsAdded > 0) 46 | { 47 | sprintf(payload+index,",\"options\":["); 48 | for (int i=0;ioptionsAdded;i++) 49 | { 50 | index = strlen(payload); 51 | sprintf(payload+index,"\"%s\",",this->options[i]); 52 | } 53 | index = strlen(payload) - 1; 54 | sprintf(payload+index,"]}"); 55 | } 56 | client->publish(topic,payload); 57 | } 58 | 59 | void HASelect::addOption(const char *option) { 60 | if(this->optionsAdded >= this->maxOptions) 61 | return; 62 | this->options[this->optionsAdded] = new char[strlen(option)+1]; 63 | strcpy(this->options[this->optionsAdded],option); 64 | this->optionsAdded++; 65 | } 66 | 67 | void HASelect::sendState(PubSubClient * client) { 68 | char topic[HA_MAX_TOPIC_LENGTH]; 69 | if (state == NULL) 70 | return; 71 | getStateTopic(topic); 72 | client->publish(topic,state); 73 | dirty = false; 74 | } 75 | 76 | void HASelect::setState(const char *txt) { 77 | for (int i=0;ioptionsAdded;i++) 78 | { 79 | if(strcmp(this->options[i],txt) == 0) 80 | { 81 | this->state = this->options[i]; 82 | dirty = true; 83 | this->onStateChange(); 84 | return; 85 | } 86 | } 87 | } 88 | 89 | 90 | void HASelect::onReceivedTopic(PubSubClient * client, byte *payload, 91 | unsigned int length) 92 | { 93 | for (int i=0;ioptionsAdded;i++) 94 | { 95 | if(strncmp(this->options[i],(char *)payload,length) == 0) 96 | { 97 | this->state = this->options[i]; 98 | dirty = true; 99 | this->onStateChange(); 100 | return; 101 | } 102 | } 103 | } 104 | 105 | -------------------------------------------------------------------------------- /src/haselect.h: -------------------------------------------------------------------------------- 1 | #ifndef __HASELECT_H__ 2 | #define __HASELECT_H__ 3 | 4 | #include "haentity.h" 5 | 6 | class PubSubClient; 7 | class HADevice; 8 | 9 | class HASelect : public HAEntity { 10 | protected: 11 | 12 | static const char *component; 13 | 14 | bool dirty; 15 | char *state; // The selected option 16 | u_int8_t maxOptions; 17 | u_int8_t optionsAdded; 18 | char **options; 19 | 20 | public: 21 | 22 | /// Constructor without device 23 | HASelect(const char *unique_id,const char *name,u_int8_t max_options); 24 | HASelect(const char *unique_id,const char *name, u_int8_t max_options, 25 | const char *options[]); 26 | 27 | /// Constructor with device 28 | HASelect(const char *unique_id,const char *name,HADevice& device, 29 | u_int8_t max_options); 30 | HASelect(const char *unique_id,const char *name, HADevice& device, 31 | u_int8_t max_options, const char *options[]); 32 | 33 | 34 | void addOption(const char *option); 35 | 36 | inline bool isDirty() {return this->dirty;}; 37 | const char * getState() {return this->state;}; 38 | 39 | void setState(const char *state); 40 | 41 | void onConnect(PubSubClient * client); 42 | void onReceivedTopic(PubSubClient * client, byte *payload, unsigned int length); 43 | void sendState(PubSubClient * client); 44 | 45 | }; 46 | 47 | #endif -------------------------------------------------------------------------------- /src/hasensor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | const char *HASensor::component PROGMEM = "sensor"; 6 | 7 | HASensor::HASensor(const char *unique_id, const char *name, 8 | const char * component) : HAEntity(unique_id,name,component) { 9 | 10 | } 11 | 12 | void HASensor::onConnect(PubSubClient * client){ 13 | char topic[HA_MAX_TOPIC_LENGTH],payload[HA_MAX_PAYLOAD_LENGTH]; 14 | getConfigTopic(topic); 15 | getConfigPayload(payload,false,true); 16 | client->publish(topic,payload); 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/hasensor.h: -------------------------------------------------------------------------------- 1 | #ifndef __HASENSOR_H__ 2 | #define __HASENSOR_H__ 3 | 4 | #include "haentity.h" 5 | 6 | class PubSubClient; 7 | class HADevice; 8 | /** Base class for sensor, it may not instantiate */ 9 | class HASensor : public HAEntity { 10 | protected: 11 | 12 | static const char *component; 13 | bool dirty; 14 | 15 | public: 16 | HASensor(const char *unique_id, const char *name, 17 | const char * component = HASensor::component); 18 | inline bool isDirty() {return this->dirty;}; 19 | virtual void onConnect(PubSubClient * client); 20 | virtual void onReceivedTopic(PubSubClient *, byte* payload, 21 | unsigned int length) {;} 22 | }; 23 | 24 | #endif -------------------------------------------------------------------------------- /src/hasensorbinary.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | const char *HASensorBinary::component PROGMEM = "binary_sensor"; 7 | 8 | HASensorBinary::HASensorBinary(const char *unique_id, const char *name, 9 | HADevice& device) : HASensor(unique_id,name,component) 10 | { 11 | this->device = &device; 12 | this->state = false; 13 | } 14 | 15 | HASensorBinary::HASensorBinary(const char *unique_id, const char *name) 16 | : HASensor(unique_id,name, component) 17 | { 18 | this->state = false; 19 | } 20 | 21 | void HASensorBinary::sendState(PubSubClient * client) 22 | { 23 | dirty = false; 24 | char topic[HA_MAX_TOPIC_LENGTH]; 25 | getStateTopic(topic); 26 | if (this->state) 27 | client->publish(topic,"ON"); 28 | else 29 | client->publish(topic,"OFF"); 30 | } 31 | 32 | void HASensorBinary::setState(bool on_off) 33 | { 34 | if(this->state == on_off) 35 | return; 36 | dirty = true; 37 | this->state = on_off; 38 | this->onStateChange(); 39 | } -------------------------------------------------------------------------------- /src/hasensorbinary.h: -------------------------------------------------------------------------------- 1 | #ifndef __HASENSORBINARY_H__ 2 | #define __HASENSORBINARY_H__ 3 | 4 | #include "hasensor.h" 5 | 6 | class PubSubClient; 7 | class HADevice; 8 | 9 | class HASensorBinary : public HASensor { 10 | protected: 11 | static const char *component; 12 | bool state; 13 | 14 | public: 15 | HASensorBinary(const char *unique_id,const char *name); 16 | HASensorBinary(const char *unique_id,const char *name,HADevice& device); 17 | 18 | bool getState() {return this->state;}; 19 | void setState(bool state); 20 | 21 | void sendState(PubSubClient * client); 22 | }; 23 | 24 | #endif -------------------------------------------------------------------------------- /src/hasensornumeric.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | 7 | HASensorNumeric::HASensorNumeric(const char *unique_id, const char *name, 8 | HADevice& device, const char* unit_of_measurement, int precision) : 9 | HASensorNumeric(unique_id,name,unit_of_measurement,precision) 10 | { 11 | this->device = &device; 12 | } 13 | HASensorNumeric::HASensorNumeric(const char *unique_id, const char *name, 14 | const char* unit_of_measurement, int precision) : HASensor(unique_id,name) 15 | { 16 | this->unitOfMeasurement = unit_of_measurement; 17 | this->state = 0; 18 | if (precision < 0) 19 | this->precision = 0; 20 | else if (precision > 9) 21 | this->precision = 9; 22 | else 23 | this->precision = precision; 24 | } 25 | 26 | 27 | void HASensorNumeric::onConnect(PubSubClient * client){ 28 | char topic[HA_MAX_TOPIC_LENGTH],payload[HA_MAX_PAYLOAD_LENGTH]; 29 | getConfigTopic(topic); 30 | getConfigPayload(payload,false,true); 31 | if(this->unitOfMeasurement != NULL){ 32 | int len = strlen(payload); 33 | sprintf(payload + len - 1 ,",\"unit_of_measurement\":\"%s\"}",this->unitOfMeasurement); 34 | } 35 | client->publish(topic,payload); 36 | } 37 | 38 | 39 | void HASensorNumeric::sendState(PubSubClient * client) 40 | { 41 | dirty = false; 42 | char topic[HA_MAX_TOPIC_LENGTH]; 43 | char payload[10]; 44 | char format[10]; 45 | getStateTopic(topic); 46 | sprintf(format,"%%.%df",this->precision); 47 | sprintf(payload,format,this->state); 48 | client->publish(topic,payload); 49 | } 50 | 51 | void HASensorNumeric::setState(float n) 52 | { 53 | if(this->state == n) 54 | return; 55 | dirty = true; 56 | this->state = n; 57 | this->onStateChange(); 58 | } -------------------------------------------------------------------------------- /src/hasensornumeric.h: -------------------------------------------------------------------------------- 1 | #ifndef __HASENSORNUMERIC_H__ 2 | #define __HASENSORNUMERIC_H__ 3 | 4 | #include "hasensor.h" 5 | 6 | class PubSubClient; 7 | class HADevice; 8 | 9 | class HASensorNumeric : public HASensor { 10 | protected: 11 | float state; 12 | int precision; 13 | const char *unitOfMeasurement; 14 | 15 | public: 16 | HASensorNumeric(const char *unique_id,const char *name,HADevice& device, 17 | const char* unit_of_measurement=NULL, int precision = 0); 18 | HASensorNumeric(const char *unique_id,const char *name, 19 | const char* unit_of_measurement=NULL, int precision = 0); 20 | 21 | virtual void onConnect(PubSubClient * client); 22 | float getState() {return this->state;}; 23 | void setState(float state); 24 | 25 | void sendState(PubSubClient * client); 26 | }; 27 | 28 | #endif -------------------------------------------------------------------------------- /src/hasensortext.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | HASensorText::HASensorText(const char *unique_id, const char *name, 7 | HADevice& device, int max_size) : HASensorText(unique_id,name,max_size) 8 | { 9 | this->device = &device; 10 | } 11 | 12 | HASensorText::HASensorText(const char *unique_id, const char *name, int max_size) : 13 | HASensor(unique_id,name) 14 | { 15 | this->maxSize = max_size; 16 | this->state = new char[max_size+1]; 17 | this->state[0] = 0; 18 | } 19 | 20 | void HASensorText::sendState(PubSubClient * client) { 21 | dirty = false; 22 | char topic[HA_MAX_TOPIC_LENGTH],payload[HA_MAX_PAYLOAD_LENGTH]; 23 | getStateTopic(topic); 24 | strncpy(payload,this->state,this->maxSize); 25 | client->publish(topic,payload); 26 | } 27 | 28 | void HASensorText::setState(const char *text) { 29 | if(strcmp(this->state,text) == 0) 30 | return; 31 | dirty = true; 32 | strncpy(this->state,text,this->maxSize); 33 | state[this->maxSize] = 0; 34 | this->onStateChange(); 35 | } -------------------------------------------------------------------------------- /src/hasensortext.h: -------------------------------------------------------------------------------- 1 | #ifndef __HASENSORTEXT__ 2 | #define __HASENSORTEXT__ 3 | 4 | #include "hasensor.h" 5 | 6 | class PubSubClient; 7 | class HADevice; 8 | 9 | class HASensorText : public HASensor { 10 | protected: 11 | 12 | char *state; 13 | int maxSize; 14 | 15 | public: 16 | HASensorText(const char *unique_id,const char *name,HADevice& device,int max_size); 17 | HASensorText(const char *unique_id,const char *name,int max_size); 18 | 19 | const char *getState() {return this->state;}; 20 | void setState(const char *state); 21 | void onReceivedTopic(PubSubClient * client, byte *payload, unsigned int length) {;} 22 | 23 | void sendState(PubSubClient * client); 24 | }; 25 | 26 | #endif -------------------------------------------------------------------------------- /src/haswitch.cpp: -------------------------------------------------------------------------------- 1 | #include"haswitch.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "hadevice.h" 8 | 9 | const char *HASwitch::component PROGMEM = "switch"; 10 | 11 | HASwitch::HASwitch(const char *unique_id, const char *name, HADevice& device): 12 | HASwitch(unique_id,name) { 13 | this->device = &device; 14 | } 15 | 16 | HASwitch::HASwitch(const char *unique_id, const char *name): 17 | HAEntity(unique_id,name,component) { 18 | this->dirty = false; 19 | this->state = false; 20 | 21 | } 22 | 23 | // send config and send state 24 | void HASwitch::onConnect(PubSubClient * client){ 25 | char topic[HA_MAX_TOPIC_LENGTH],payload[HA_MAX_PAYLOAD_LENGTH]; 26 | getCommandTopic(topic); 27 | client->subscribe(topic); 28 | 29 | getConfigTopic(topic); 30 | getConfigPayload(payload,true,true); 31 | client->publish(topic,payload); 32 | } 33 | 34 | void HASwitch::sendState(PubSubClient * client){ 35 | dirty = false; 36 | char topic[HA_MAX_TOPIC_LENGTH]; 37 | getStateTopic(topic); 38 | if (this->state) 39 | client->publish(topic,"ON"); 40 | else 41 | client->publish(topic,"OFF"); 42 | } 43 | 44 | void HASwitch::setState(bool state){ 45 | if (state == this->state) 46 | return; 47 | dirty = true; 48 | this->state = state; 49 | this->onStateChange(); 50 | } 51 | 52 | 53 | void HASwitch::onReceivedTopic(PubSubClient * client, byte *payload, 54 | unsigned int length) 55 | { 56 | // Default values in homeassistant are "ON" and "OFF 57 | if (length < 1 || length > 3) 58 | return; 59 | if (payload[0]== 'O' && payload[1] == 'N') 60 | this->setState(true); 61 | else if (payload[0]== 'O' && payload[1] == 'F') 62 | this->setState(false); 63 | } -------------------------------------------------------------------------------- /src/haswitch.h: -------------------------------------------------------------------------------- 1 | #ifndef __HASWITCH_H__ 2 | #define __HASWITCH_H__ 3 | 4 | #include "haentity.h" 5 | 6 | class PubSubClient; 7 | class HADevice; 8 | 9 | class HASwitch : public HAEntity { 10 | protected: 11 | static const char *component; 12 | 13 | bool dirty; 14 | bool state; 15 | 16 | public: 17 | HASwitch(const char *unique_id,const char *name,HADevice& device); 18 | HASwitch(const char *unique_id,const char *name); 19 | 20 | inline bool getState() {return state;}; 21 | inline bool isDirty() {return dirty;}; 22 | void setState(bool state); 23 | 24 | void onConnect(PubSubClient * client); 25 | void onReceivedTopic(PubSubClient * client, byte *payload, unsigned int length); 26 | void sendState(PubSubClient * client); 27 | 28 | }; 29 | 30 | #endif -------------------------------------------------------------------------------- /src/hatext.cpp: -------------------------------------------------------------------------------- 1 | #include"haswitch.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | const char *HAText::component PROGMEM = "text"; 10 | 11 | HAText::HAText(const char *unique_id, const char *name, HADevice& device, 12 | unsigned int max_size):HAText(unique_id,name,max_size) { 13 | this->device = &device; 14 | } 15 | 16 | 17 | HAText::HAText(const char *unique_id, const char *name, unsigned int max_size): 18 | HAEntity(unique_id,name,component) { 19 | this->maxSize = max_size; 20 | this->state = new char[max_size]; 21 | this->state[0] = 0; 22 | 23 | } 24 | 25 | 26 | void HAText::onConnect(PubSubClient * client) { 27 | char topic[HA_MAX_TOPIC_LENGTH],payload[HA_MAX_PAYLOAD_LENGTH]; 28 | getCommandTopic(topic); 29 | client->subscribe(topic); 30 | 31 | this->getConfigTopic(topic); 32 | this->getConfigPayload(payload,true,true); 33 | client->publish(topic,payload); 34 | } 35 | 36 | void HAText::sendState(PubSubClient * client) { 37 | char topic[HA_MAX_TOPIC_LENGTH]; 38 | getStateTopic(topic); 39 | client->publish(topic,state); 40 | dirty = false; 41 | } 42 | 43 | void HAText::setState(const char *txt) { 44 | if(strcmp(this->state,txt) == 0) 45 | return; 46 | dirty = true; 47 | strncpy(this->state,txt,this->maxSize); 48 | this->state[this->maxSize-1] = 0; 49 | this->onStateChange(); 50 | } 51 | 52 | 53 | void HAText::onReceivedTopic(PubSubClient * client, byte *payload, 54 | unsigned int length) 55 | { 56 | int a_length = length < this->maxSize?length:this->maxSize-1; 57 | memcpy(this->state,payload,a_length); 58 | this->state[a_length] = 0; 59 | this->onStateChange(); 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/hatext.h: -------------------------------------------------------------------------------- 1 | #ifndef __HATEXT_H__ 2 | #define __HATEXT_H__ 3 | 4 | #include "haentity.h" 5 | 6 | class PubSubClient; 7 | class HADevice; 8 | 9 | class HAText : public HAEntity { 10 | protected: 11 | 12 | static const char *component; 13 | 14 | bool dirty; 15 | char *state; 16 | unsigned int maxSize; 17 | 18 | public: 19 | HAText(const char *unique_id,const char *name,HADevice& device, 20 | unsigned int max_size); 21 | HAText(const char *unique_id,const char *name, unsigned int max_size); 22 | 23 | 24 | inline bool isDirty() {return this->dirty;}; 25 | const char * getState() {return this->state;}; 26 | void setState(const char *state); 27 | 28 | void onConnect(PubSubClient * client); 29 | void onReceivedTopic(PubSubClient * client, byte *payload, unsigned int length); 30 | void sendState(PubSubClient * client); 31 | 32 | }; 33 | 34 | #endif --------------------------------------------------------------------------------