├── .gitignore ├── component.mk ├── ota.h ├── CMakeLists.txt ├── Kconfig ├── LICENSE ├── include └── homie.h ├── README.md ├── ota.c └── homie.c /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ -------------------------------------------------------------------------------- /component.mk: -------------------------------------------------------------------------------- 1 | # Use defaults 2 | -------------------------------------------------------------------------------- /ota.h: -------------------------------------------------------------------------------- 1 | 2 | #define HOMIE_OTA_MAX_URL_LEN (256) 3 | 4 | void ota_init(char *url, const char *cert_pem, void (*status_handler)(int)); 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "homie.c" "ota.c" 3 | INCLUDE_DIRS "include" 4 | REQUIRES mqtt app_update esp_https_ota esp_wifi esp_timer 5 | ) 6 | -------------------------------------------------------------------------------- /Kconfig: -------------------------------------------------------------------------------- 1 | menu "Homie HTTP client" 2 | 3 | 4 | config ESP_HTTP_CLIENT_ENABLE_HTTPS 5 | bool "Enable https" 6 | default y 7 | help 8 | This option will enable https protocol by linking mbedtls library and initializing SSL transport 9 | 10 | endmenu -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Craft Metrics Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /include/homie.h: -------------------------------------------------------------------------------- 1 | #ifndef CM_ESP32_HOMIE_H 2 | #define CM_ESP32_HOMIE_H 3 | 4 | #include "mqtt_client.h" 5 | 6 | #define HOMIE_MAX_TOPIC_LEN (64) 7 | 8 | #define HOMIE_MAX_MQTT_URI_LEN (64) 9 | #define HOMIE_MAX_MQTT_USERNAME_LEN (32) 10 | #define HOMIE_MAX_MQTT_PASSWORD_LEN (32) 11 | 12 | #define HOMIE_MAX_CLIENT_ID_LEN (16) 13 | #define HOMIE_MAX_DEVICE_NAME_LEN (16) 14 | #define HOMIE_MAX_BASE_TOPIC_LEN (16) 15 | #define HOMIE_MAX_FIRMWARE_NAME_LEN (32) 16 | #define HOMIE_MAX_FIRMWARE_VERSION_LEN (8) 17 | 18 | typedef struct 19 | { 20 | char mqtt_uri[HOMIE_MAX_MQTT_URI_LEN]; 21 | char mqtt_username[HOMIE_MAX_MQTT_USERNAME_LEN]; 22 | char mqtt_password[HOMIE_MAX_MQTT_PASSWORD_LEN]; 23 | char client_id[HOMIE_MAX_CLIENT_ID_LEN]; 24 | char device_name[HOMIE_MAX_DEVICE_NAME_LEN]; 25 | char base_topic[HOMIE_MAX_BASE_TOPIC_LEN]; 26 | char firmware_name[HOMIE_MAX_FIRMWARE_NAME_LEN]; 27 | char firmware_version[HOMIE_MAX_FIRMWARE_VERSION_LEN]; 28 | bool ota_enabled; 29 | const char *cert_pem; 30 | void (*msg_handler)(char *, char *); 31 | void (*connected_handler)(); 32 | void (*disconnected_handler)(); 33 | void (*ota_status_handler)(int); 34 | } homie_config_t; 35 | 36 | void homie_init(homie_config_t *config); 37 | void homie_subscribe(const char *subtopic); 38 | int homie_publish(const char *subtopic, int qos, int retain, const char *payload, int len, bool queue); 39 | int homie_publishf(const char *subtopic, int qos, int retain, const char *format, ...); 40 | int homie_publish_int(const char *subtopic, int qos, int retain, const int payload); 41 | int homie_publish_bool(const char *subtopic, int qos, int retain, const bool payload); 42 | void homie_mktopic(char *topic, const char *subtopic); 43 | 44 | #endif // CM_ESP32_HOMIE_H 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esp32-homie 2 | 3 | An esp-idf component for the [Homie convention](https://github.com/homieiot/convention). 4 | 5 | ## Goals 6 | 7 | This is alpha-level software. Pull requests are welcome! Here is where we're at: 8 | 9 | - [x] Conforms to 2.0.1 of the Homie specification 10 | - [x] Publishes stats for wifi signal, freeheap, and uptime 11 | - [x] OTA firmware updates 12 | - [ ] Support for extendible nodes/stats 13 | 14 | ## Philosophy 15 | 16 | I believe a minimalist library is a better fit for the ESP-IDF ecosystem rather than a framework. I don't plan to include a captive portal, nor an inversion of program control. The scope of this library will be to manage the MQTT connection using the Homie convention, handle OTA, and little else. 17 | 18 | ## Dependencies 19 | 20 | - ESP IDF 3.2.0 21 | - https://github.com/tuanpmt/espmqtt or ESP IDF mqtt component 22 | 23 | ## How to use 24 | 25 | Clone this component to [ESP-IDF](https://github.com/espressif/esp-idf) project (as submodule): 26 | 27 | ``` 28 | git submodule add https://github.com/craftmetrics/esp32-homie.git components/esp32-homie 29 | ``` 30 | 31 | ## Example 32 | 33 | https://github.com/craftmetrics/esp32-homie-example 34 | 35 | ## OTA Updates 36 | 37 | OTA works according to the following scheme: 38 | 39 | 1. OTA must be enabled in the config passed to `homie_init` (it's off by default) 40 | 1. The initiating entity publishes a message to `./$implementation/ota/url` containing a URL to the new firmware 41 | 1. If an error is encountered, the device publishes a message to `./$implementation/ota/status` 42 | 1. If it is successful, the device reboots into the new firmware 43 | 44 | ## Remote Logging 45 | 46 | When remote logging is enabled, all calls to ESP_LOG\*() are published to `./log`. 47 | 48 | To enable remote logging, send `true` to `./$implementation/logging`. To disable, send `false`. 49 | 50 | Note that the default logger uses ANSI terminal colors in its log output, you may want to set `CONFIG_LOG_COLORS=n` in `sdkconfig` to disable this. 51 | -------------------------------------------------------------------------------- /ota.c: -------------------------------------------------------------------------------- 1 | /* 2 | * esp32-homie OTA functionality is loosely based on 3 | * https://github.com/tuanpmt/esp32-fota 4 | * by Tuanpmt 5 | */ 6 | 7 | #include "freertos/FreeRTOS.h" 8 | #include "freertos/task.h" 9 | #include "math.h" 10 | 11 | #include "esp_https_ota.h" 12 | #include "esp_log.h" 13 | #include "esp_ota_ops.h" 14 | #include "esp_wifi.h" 15 | #include "homie.h" 16 | 17 | #define TAG "HOMIE_OTA" 18 | 19 | typedef struct 20 | { 21 | char *url; 22 | const char *cert_pem; 23 | void (*status_handler)(int); 24 | } homie_ota_config_t; 25 | static homie_ota_config_t *config = NULL; 26 | 27 | static void ota_deinit() 28 | { 29 | free(config->url); 30 | free(config); 31 | config = NULL; 32 | vTaskDelete(NULL); 33 | } 34 | 35 | static void ota_task(void *pvParameter) 36 | { 37 | ESP_LOGI(TAG, "Downloading %s", config->url); 38 | if (config->status_handler) 39 | config->status_handler(0); 40 | 41 | homie_publish("$implementation/ota/status", 1, 0, "202 ota begin", 0, true); 42 | 43 | esp_http_client_config_t ota_http_config = {.url = config->url, .cert_pem = config->cert_pem}; 44 | esp_https_ota_config_t ota_config = {.http_config = &ota_http_config}; 45 | esp_err_t ret = esp_https_ota(&ota_config); 46 | if (ret == ESP_OK) 47 | { 48 | if (config->status_handler) 49 | config->status_handler(1); 50 | 51 | ESP_LOGI(TAG, "OTA Update Complete - rebooting"); 52 | // Send status message to indicate that OTA is complete 53 | homie_publish("$implementation/ota/status", 1, 0, "200", 0, true); 54 | vTaskDelay(3000 / portTICK_PERIOD_MS); 55 | esp_restart(); 56 | } 57 | else 58 | { 59 | if (config->status_handler) 60 | config->status_handler(-1); 61 | 62 | ESP_LOGE(TAG, "esp_https_ota error: %d", ret); 63 | homie_publishf("$implementation/ota/status", 1, 0, "500 esp_https_ota=%d", ret); 64 | } 65 | 66 | ota_deinit(); 67 | } 68 | 69 | void ota_init(char *url, const char *cert_pem, void (*status_handler)(int)) 70 | { 71 | ESP_LOGI(TAG, "Initiating OTA"); 72 | 73 | const esp_partition_t *configured = esp_ota_get_boot_partition(); 74 | const esp_partition_t *running = esp_ota_get_running_partition(); 75 | 76 | // Ensure OTA is configured 77 | if ((configured == NULL) || (configured == NULL)) 78 | { 79 | ESP_LOGE(TAG, "OTA partitions not configured"); 80 | homie_publish("$implementation/ota/status", 1, 0, "500 no ota partitions", 0, true); 81 | free(url); 82 | return; 83 | } 84 | 85 | // Check OTA partitions 86 | if (configured != running) 87 | { 88 | ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x", 89 | configured->address, running->address); 90 | ESP_LOGW(TAG, 91 | "(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)"); 92 | } 93 | ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08x)", running->type, running->subtype, 94 | running->address); 95 | 96 | // Ensure we can't start multiple 97 | if (config != NULL) 98 | { 99 | ESP_LOGE(TAG, "OTA already initiated (0x%x)", (unsigned int)config); 100 | homie_publish("$implementation/ota/status", 1, 0, "500 ota already initiated", 0, true); 101 | free(url); 102 | return; 103 | } 104 | 105 | config = calloc(1, sizeof(homie_ota_config_t)); 106 | config->url = url; 107 | config->status_handler = status_handler; 108 | config->cert_pem = cert_pem; 109 | 110 | // Begin OTA task 111 | xTaskCreate(&ota_task, "ota_task", 8192, NULL, 5, NULL); 112 | } 113 | -------------------------------------------------------------------------------- /homie.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "esp_log.h" 4 | #include "esp_ota_ops.h" 5 | #include "esp_system.h" 6 | #include "esp_wifi.h" 7 | #include "esp_mac.h" 8 | #include "esp_timer.h" 9 | #include "freertos/event_groups.h" 10 | 11 | #include "homie.h" 12 | #include "mqtt_client.h" 13 | #include "ota.h" 14 | 15 | static const char *TAG = "HOMIE"; 16 | 17 | static esp_mqtt_client_handle_t client; 18 | static homie_config_t *config; 19 | 20 | static void homie_connected(); 21 | 22 | static bool _starts_with(const char *pre, const char *str, int lenstr) 23 | { 24 | size_t lenpre = strlen(pre); 25 | return lenstr < lenpre ? false : strncmp(pre, str, lenpre) == 0; 26 | } 27 | 28 | #define REMOTE_LOGGING_MAX_PAYLOAD_LEN 1024 29 | static int _homie_logger(const char *str, va_list l) 30 | { 31 | char buf[REMOTE_LOGGING_MAX_PAYLOAD_LEN]; 32 | 33 | vsnprintf(buf, REMOTE_LOGGING_MAX_PAYLOAD_LEN, str, l); 34 | homie_publish("log", 1, 0, buf, 0, true); 35 | return vprintf(str, l); 36 | } 37 | 38 | // Ensure MQTT logging is disabled, otherwise reboot may hang 39 | static void _reset_logger(void) { 40 | esp_log_set_vprintf(vprintf); 41 | } 42 | 43 | static void homie_handle_mqtt_event(esp_mqtt_event_handle_t event) 44 | { 45 | printf("TOPIC=%.*s\r\n", event->topic_len, event->topic); 46 | printf("DATA=%.*s\r\n", event->data_len, event->data); 47 | 48 | // Check if it is reboot command 49 | char topic[HOMIE_MAX_TOPIC_LEN]; 50 | homie_mktopic(topic, "$implementation/reboot"); 51 | if ((strncmp(topic, event->topic, event->topic_len) == 0) && (strncmp("true", event->data, event->data_len) == 0)) 52 | { 53 | ESP_LOGI(TAG, "Rebooting..."); 54 | esp_restart(); 55 | return; 56 | } 57 | 58 | // Check if it is enable remote console 59 | homie_mktopic(topic, "$implementation/logging"); 60 | if (strncmp(topic, event->topic, event->topic_len) == 0) 61 | { 62 | if (strncmp("true", event->data, event->data_len) == 0) 63 | { 64 | ESP_LOGI(TAG, "Enable remote logging"); 65 | esp_register_shutdown_handler(_reset_logger); 66 | esp_log_set_vprintf(_homie_logger); 67 | ESP_LOGI(TAG, "Remote logging enabled"); 68 | } 69 | else 70 | { 71 | ESP_LOGI(TAG, "Disable remote logging"); 72 | esp_log_set_vprintf(vprintf); 73 | esp_unregister_shutdown_handler(_reset_logger); 74 | ESP_LOGI(TAG, "Remote logging disabled"); 75 | } 76 | return; 77 | } 78 | 79 | // Check if it is a OTA update 80 | homie_mktopic(topic, "$implementation/ota/url"); 81 | if (_starts_with(topic, event->topic, event->topic_len)) 82 | { 83 | char *url = calloc(1, event->data_len + 1); 84 | strncpy(url, event->data, event->data_len); 85 | url[event->data_len] = '\0'; 86 | ota_init(url, config->cert_pem, config->ota_status_handler); 87 | return; 88 | } 89 | 90 | // Call the application's handler 91 | homie_mktopic(topic, ""); 92 | if (config->msg_handler) 93 | { 94 | int subtopic_len = event->topic_len - strlen(topic); 95 | char *subtopic = calloc(1, subtopic_len + 1); 96 | strncpy(subtopic, event->topic + strlen(topic), subtopic_len); 97 | subtopic[subtopic_len] = '\0'; 98 | 99 | char *payload = calloc(1, event->data_len + 1); 100 | strncpy(payload, event->data, event->data_len); 101 | payload[event->data_len] = '\0'; 102 | 103 | config->msg_handler(subtopic, payload); 104 | 105 | free(subtopic); 106 | free(payload); 107 | } 108 | } 109 | 110 | void mqtt_event_handler(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data) 111 | { 112 | esp_mqtt_event_t * event = (esp_mqtt_event_t*)event_data; 113 | 114 | switch (event_id) 115 | { 116 | case MQTT_EVENT_BEFORE_CONNECT: 117 | ESP_LOGI(TAG, "MQTT_EVENT_BEFORE_CONNECT"); 118 | break; 119 | 120 | case MQTT_EVENT_CONNECTED: 121 | ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED"); 122 | homie_connected(); 123 | if (config->connected_handler) 124 | config->connected_handler(); 125 | break; 126 | 127 | case MQTT_EVENT_DISCONNECTED: 128 | ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED"); 129 | if (config->disconnected_handler) 130 | config->disconnected_handler(); 131 | break; 132 | 133 | case MQTT_EVENT_SUBSCRIBED: 134 | ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id); 135 | break; 136 | 137 | case MQTT_EVENT_UNSUBSCRIBED: 138 | ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id); 139 | break; 140 | 141 | case MQTT_EVENT_PUBLISHED: 142 | // Disabled to avoid triggering circular events with remote logging 143 | // ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id); 144 | break; 145 | 146 | case MQTT_EVENT_DATA: 147 | ESP_LOGI(TAG, "MQTT_EVENT_DATA"); 148 | homie_handle_mqtt_event(event); 149 | break; 150 | 151 | case MQTT_EVENT_ERROR: 152 | ESP_LOGI(TAG, "MQTT_EVENT_ERROR"); 153 | break; 154 | 155 | case MQTT_EVENT_ANY: 156 | ESP_LOGI(TAG, "MQTT_EVENT_ANY"); 157 | break; 158 | 159 | case MQTT_EVENT_DELETED: 160 | ESP_LOGI(TAG, "MQTT_EVENT_DELETED"); 161 | break; 162 | 163 | case MQTT_USER_EVENT: 164 | ESP_LOGI(TAG, "MQTT_USER_EVENT"); 165 | break; 166 | 167 | } 168 | } 169 | 170 | static void mqtt_app_start(void) 171 | { 172 | char *lwt_topic = calloc(1, HOMIE_MAX_TOPIC_LEN); 173 | homie_mktopic(lwt_topic, "$online"); 174 | 175 | const esp_mqtt_client_config_t mqtt_cfg = { 176 | .broker.address.uri = config->mqtt_uri, 177 | .broker.verification.certificate = config->cert_pem, 178 | .credentials.username = config->mqtt_username, 179 | .credentials.client_id = config->client_id, 180 | .credentials.authentication.password = config->mqtt_password, 181 | .session.last_will.msg = "false", 182 | .session.last_will.qos = 1, 183 | .session.last_will.retain = 1, 184 | .session.last_will.topic = lwt_topic, 185 | .session.keepalive = 15, 186 | }; 187 | 188 | client = esp_mqtt_client_init(&mqtt_cfg); 189 | esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client); 190 | esp_mqtt_client_start(client); 191 | } 192 | 193 | void homie_mktopic(char *topic, const char *subtopic) 194 | { 195 | snprintf(topic, HOMIE_MAX_TOPIC_LEN, "%s/%s/%s", config->base_topic, config->client_id, subtopic); 196 | } 197 | 198 | void homie_subscribe(const char *subtopic) 199 | { 200 | int msg_id; 201 | char topic[HOMIE_MAX_TOPIC_LEN]; 202 | homie_mktopic(topic, subtopic); 203 | 204 | msg_id = esp_mqtt_client_subscribe(client, topic, 0); 205 | ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); 206 | } 207 | 208 | int homie_publish(const char *subtopic, int qos, int retain, const char *payload, int len, bool queue) 209 | { 210 | if (config == NULL) 211 | { 212 | ESP_LOGI(TAG, "Attempted to publish before homie connected"); 213 | return -2; 214 | } 215 | 216 | // int msg_id; 217 | char topic[HOMIE_MAX_TOPIC_LEN]; 218 | homie_mktopic(topic, subtopic); 219 | 220 | if (queue) 221 | return esp_mqtt_client_enqueue(client, topic, payload, len, qos, retain, true); 222 | else 223 | return esp_mqtt_client_publish(client, topic, payload, len, qos, retain); 224 | } 225 | 226 | int homie_publishf(const char *subtopic, int qos, int retain, const char *format, ...) 227 | { 228 | char payload_string[64]; 229 | va_list argptr; 230 | va_start(argptr, format); 231 | vsnprintf(payload_string, 64, format, argptr); 232 | va_end(argptr); 233 | return homie_publish(subtopic, qos, retain, payload_string, 0, true); 234 | } 235 | 236 | int homie_publish_int(const char *subtopic, int qos, int retain, int payload) 237 | { 238 | char payload_string[16]; 239 | snprintf(payload_string, 16, "%d", payload); 240 | return homie_publish(subtopic, qos, retain, payload_string, 0, true); 241 | } 242 | 243 | int homie_publish_bool(const char *subtopic, int qos, int retain, bool payload) 244 | { 245 | return homie_publish(subtopic, qos, retain, payload ? "true" : "false", 0, true); 246 | } 247 | 248 | static int _clamp(int n, int lower, int upper) 249 | { 250 | return n <= lower ? lower : n >= upper ? upper : n; 251 | } 252 | 253 | static int8_t _get_wifi_rssi() 254 | { 255 | wifi_ap_record_t info; 256 | if (!esp_wifi_sta_get_ap_info(&info)) 257 | { 258 | return info.rssi; 259 | } 260 | return 0; 261 | } 262 | 263 | static void _get_ip(char *ip_string) 264 | { 265 | esp_netif_ip_info_t ip; 266 | esp_netif_get_ip_info(esp_netif_get_default_netif(), &ip); 267 | 268 | sprintf(ip_string, "%u.%u.%u.%u", 269 | (uint8_t)(ip.ip.addr & 0x000000ff), 270 | (uint8_t)((ip.ip.addr & 0x0000ff00) >> 8), 271 | (uint8_t)((ip.ip.addr & 0x00ff0000) >> 16), 272 | (uint8_t)((ip.ip.addr & 0xff000000) >> 24) 273 | ); 274 | 275 | } 276 | 277 | static void _get_mac(char *mac_string, bool sep) 278 | { 279 | // NB: This is the base mac of the device. The actual wifi and eth MAC addresses 280 | // will be assigned as offsets from this. 281 | 282 | uint8_t mac[6]; 283 | esp_efuse_mac_get_default(mac); 284 | 285 | if (sep) 286 | sprintf(mac_string, "%X:%X:%X:%X:%X:%X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 287 | else 288 | sprintf(mac_string, "%x%x%x%x%x%x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 289 | } 290 | 291 | static void homie_connected() 292 | { 293 | char mac_address[18]; 294 | char ip_address[16]; 295 | _get_mac(mac_address, true); 296 | _get_ip(ip_address); 297 | 298 | homie_publish("$online", 1, 1, "true", 0, true); 299 | homie_publish("$name", 1, 1, config->device_name, 0, true); 300 | homie_publish("$localip", 1, 1, ip_address, 0, true); 301 | homie_publish("$mac", 1, 1, mac_address, 0, true); 302 | homie_publish("$fw/name", 1, 1, config->firmware_name, 0, true); 303 | homie_publish("$fw/version", 1, 1, config->firmware_version, 0, true); 304 | 305 | homie_publish_bool("$implementation/ota/enabled", 1, 0, config->ota_enabled); 306 | 307 | const esp_partition_t *running_partition = esp_ota_get_running_partition(); 308 | if (running_partition != NULL) 309 | { 310 | homie_publishf("$implementation/ota/running", 1, 0, "0x%08x", running_partition->address); 311 | } 312 | else 313 | { 314 | homie_publishf("$implementation/ota/running", 1, 0, "NULL"); 315 | } 316 | 317 | const esp_partition_t *boot_partition = esp_ota_get_boot_partition(); 318 | if (boot_partition != NULL) 319 | { 320 | homie_publishf("$implementation/ota/boot", 1, 0, "0x%08x", boot_partition->address); 321 | } 322 | else 323 | { 324 | homie_publishf("$implementation/ota/boot", 1, 0, "NULL"); 325 | } 326 | 327 | homie_subscribe("$implementation/reboot"); 328 | homie_subscribe("$implementation/logging"); 329 | if (config->ota_enabled) 330 | homie_subscribe("$implementation/ota/url/#"); 331 | } 332 | 333 | static void homie_task(void *pvParameter) 334 | { 335 | while (1) 336 | { 337 | homie_publish_int("$stats/uptime", 0, 0, esp_timer_get_time() / 1000000); 338 | 339 | int rssi = _get_wifi_rssi(); 340 | homie_publish_int("$stats/rssi", 0, 0, rssi); 341 | 342 | // Translate to "signal" percentage, assuming RSSI range of (-100,-50) 343 | homie_publish_int("$stats/signal", 0, 0, _clamp((rssi + 100) * 2, 0, 100)); 344 | 345 | homie_publish_int("$stats/freeheap", 0, 0, esp_get_free_heap_size()); 346 | 347 | vTaskDelay(30000 / portTICK_PERIOD_MS); 348 | } 349 | } 350 | 351 | void homie_init(homie_config_t *passed_config) 352 | { 353 | config = passed_config; 354 | 355 | // If client_id is blank, generate one based off the mac 356 | if (!config->client_id[0]) 357 | _get_mac(config->client_id, false); 358 | 359 | mqtt_app_start(); 360 | xTaskCreate(&homie_task, "homie_task", 8192, NULL, 5, NULL); 361 | } 362 | --------------------------------------------------------------------------------