| @@ -5,6 +5,7 @@ idf_component_register( | |||||
| "http_serv.cpp" | "http_serv.cpp" | ||||
| "ota.cpp" | "ota.cpp" | ||||
| "mqtt.cpp" | "mqtt.cpp" | ||||
| "device.cpp" | |||||
| INCLUDE_DIRS "." | INCLUDE_DIRS "." | ||||
| ) | ) | ||||
| @@ -0,0 +1,76 @@ | |||||
| static const char *TAG = "device"; | |||||
| #include <esp_log.h> | |||||
| #include "device.hpp" | |||||
| Device::Device(std::string _id) : | |||||
| id(_id), | |||||
| state_topic("light/" + id + "/state"), | |||||
| cmd_topic("light/" + id + "/cmd"), | |||||
| ready(false), should_publish(false), | |||||
| mutex(xSemaphoreCreateMutex()), | |||||
| sem(xSemaphoreCreateBinary()), | |||||
| power_on(true), | |||||
| client(start_mqtt_client(on_mqtt_connect, on_mqtt_message, this)) | |||||
| {} | |||||
| void Device::set_mqtt_config(cJSON*) { | |||||
| xSemaphoreTake(mutex, portMAX_DELAY); | |||||
| // TODO: update power_on and effect from JSON | |||||
| publish_state_locked(); | |||||
| xSemaphoreGive(mutex); | |||||
| } | |||||
| void Device::on_mqtt_connect(esp_mqtt_client_handle_t client) { | |||||
| xSemaphoreTake(mutex, portMAX_DELAY); | |||||
| ESP_LOGI(TAG, "Connected to MQTT"); | |||||
| if (esp_mqtt_client_subscribe(client, cmd_topic.c_str(), 0) < 0) { | |||||
| ESP_LOGE(TAG, "Failed to subscribe to %s", cmd_topic.c_str()); | |||||
| } | |||||
| if (0 > esp_mqtt_client_publish( | |||||
| client, | |||||
| ("homeassistant/light/" + id + "/config").c_str(), | |||||
| ("{" | |||||
| "\"unique_id\": \"" + id + "\", " | |||||
| "\"device\": {\"name\": \"blinky_" + id + "\"}, " | |||||
| "\"state_topic\": \"" + state_topic + "\", " | |||||
| "\"command_topic\": \"" + cmd_topic + "\", " | |||||
| "\"schema\": \"json\", " | |||||
| "\"effect\": True, " | |||||
| "\"effect_list\": [\"dummy\"]" | |||||
| "}").c_str(), 0, | |||||
| 0, 0 | |||||
| )) { | |||||
| ESP_LOGE(TAG, "Failed to publish discovery message"); | |||||
| } | |||||
| ready = true; | |||||
| if (should_publish) publish_state_locked(); | |||||
| xSemaphoreGive(mutex); | |||||
| } | |||||
| void Device::on_mqtt_message(esp_mqtt_client_handle_t, esp_mqtt_event_handle_t) { | |||||
| ESP_LOGI(TAG, "Received command via MQTT"); | |||||
| // TODO: extract JSON, call set_mqtt_config | |||||
| xSemaphoreGive(sem); | |||||
| } | |||||
| void Device::publish_state_locked() { | |||||
| if (!(should_publish = !ready)) { | |||||
| ESP_ERROR_CHECK(esp_mqtt_client_publish( | |||||
| client, | |||||
| state_topic.c_str(), | |||||
| ("{" | |||||
| "\"state\": \"" + std::string(power_on ? "ON" : "OFF") + "\", " | |||||
| "\"effect\": \"" + effect + "\"" | |||||
| "}").c_str(), 0, | |||||
| 0, 0 | |||||
| )); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,57 @@ | |||||
| #pragma once | |||||
| #include <string> | |||||
| #include <cJSON.h> | |||||
| #include "mqtt.hpp" | |||||
| class Device { | |||||
| public: | |||||
| Device(std::string id); | |||||
| void set_mqtt_config(cJSON*); | |||||
| void lock() const { xSemaphoreTake(mutex, portMAX_DELAY); } | |||||
| void unlock() const { xSemaphoreGive(mutex); } | |||||
| bool wait(int timeout_ms = -1) const { | |||||
| return pdTRUE == xSemaphoreTake( | |||||
| sem, | |||||
| (timeout_ms < 0 ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms)) | |||||
| ); | |||||
| } | |||||
| // Only call while locked | |||||
| const std::string& get_effect() const { return effect; } | |||||
| bool is_on() const { return power_on; } | |||||
| private: | |||||
| std::string id; | |||||
| std::string state_topic; | |||||
| std::string cmd_topic; | |||||
| bool ready, should_publish; | |||||
| SemaphoreHandle_t mutex; | |||||
| SemaphoreHandle_t sem; | |||||
| bool power_on; | |||||
| std::string effect; | |||||
| esp_mqtt_client_handle_t client; | |||||
| static void on_mqtt_connect(esp_mqtt_client_handle_t client, void* data) { | |||||
| ((Device*)data)->on_mqtt_connect(client); | |||||
| } | |||||
| static void on_mqtt_message(esp_mqtt_client_handle_t client, esp_mqtt_event_handle_t event, void* data) { | |||||
| ((Device*)data)->on_mqtt_message(client, event); | |||||
| } | |||||
| void on_mqtt_connect(esp_mqtt_client_handle_t); | |||||
| void on_mqtt_message(esp_mqtt_client_handle_t, esp_mqtt_event_handle_t); | |||||
| void publish_state_locked(); | |||||
| }; | |||||
| @@ -10,7 +10,7 @@ static const char *TAG = "blinky"; | |||||
| #include "wifi.hpp" | #include "wifi.hpp" | ||||
| #include "http_serv.hpp" | #include "http_serv.hpp" | ||||
| #include "ota.hpp" | #include "ota.hpp" | ||||
| #include "mqtt.hpp" | |||||
| #include "device.hpp" | |||||
| // mDNS / NetBIOS | // mDNS / NetBIOS | ||||
| @@ -80,6 +80,13 @@ static void log_dir(const std::string &path) { | |||||
| } | } | ||||
| int64_t now_us() { | |||||
| struct timeval tv_now; | |||||
| gettimeofday(&tv_now, NULL); | |||||
| return (int64_t)tv_now.tv_sec * 1000000L + (int64_t)tv_now.tv_usec; | |||||
| } | |||||
| // Entry Point | // Entry Point | ||||
| extern "C" void app_main(void) { | extern "C" void app_main(void) { | ||||
| @@ -108,8 +115,23 @@ extern "C" void app_main(void) { | |||||
| log_dir("/spiffs"); | log_dir("/spiffs"); | ||||
| // TODO: Something useful | // TODO: Something useful | ||||
| esp_mqtt_client_handle_t client = start_mqtt_client(NULL, NULL, NULL); | |||||
| esp_mqtt_client_subscribe(client, "#", 0); | |||||
| Device device("blinky-jr"); | |||||
| /* | |||||
| int period_us = 1000000; | |||||
| int64_t target_us = now_us(); | |||||
| while (true) { | |||||
| int64_t delay_us = target_us - now_us(); | |||||
| if (delay_us < 0) delay_us = 0; | |||||
| if (device.wait(delay_us / 1000)) | |||||
| ESP_LOGI(TAG, "Reload!"); | |||||
| else { | |||||
| ESP_LOGI(TAG, "Timeout!"); | |||||
| target_us += period_us; | |||||
| } | |||||
| } | |||||
| */ | |||||
| while (device.wait()) ESP_LOGI(TAG, "Reload!"); | |||||
| // Spin | // Spin | ||||
| } | } | ||||