| @@ -5,6 +5,7 @@ idf_component_register( | |||
| "http_serv.cpp" | |||
| "ota.cpp" | |||
| "mqtt.cpp" | |||
| "device.cpp" | |||
| 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 "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 | |||
| } | |||