diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 84078a2..54fa613 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -5,6 +5,7 @@ idf_component_register( "http_serv.cpp" "ota.cpp" "mqtt.cpp" + "device.cpp" INCLUDE_DIRS "." ) diff --git a/main/device.cpp b/main/device.cpp new file mode 100644 index 0000000..03d4893 --- /dev/null +++ b/main/device.cpp @@ -0,0 +1,76 @@ +static const char *TAG = "device"; +#include + +#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 + )); + } +} \ No newline at end of file diff --git a/main/device.hpp b/main/device.hpp new file mode 100644 index 0000000..38b0641 --- /dev/null +++ b/main/device.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include + +#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(); +}; \ No newline at end of file diff --git a/main/main.cpp b/main/main.cpp index 7fb5559..0b8d291 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -10,7 +10,7 @@ static const char *TAG = "blinky"; #include "wifi.hpp" #include "http_serv.hpp" #include "ota.hpp" -#include "mqtt.hpp" +#include "device.hpp" // 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 extern "C" void app_main(void) { @@ -108,8 +115,23 @@ extern "C" void app_main(void) { log_dir("/spiffs"); // 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 }