From 51023de70e2a2e7dfdca22ea62931a1206391202 Mon Sep 17 00:00:00 2001 From: jrhoffa Date: Sun, 11 Sep 2022 20:12:28 -0700 Subject: [PATCH] Add full MQTT/JSON support --- main/device.cpp | 50 +++++++++++++++++++------- main/device.hpp | 8 +++-- main/leds.cpp | 11 ++++++ main/leds.hpp | 11 +++--- main/main.cpp | 94 ++++++++++++++++++++++++++++++++++++++++--------- 5 files changed, 138 insertions(+), 36 deletions(-) diff --git a/main/device.cpp b/main/device.cpp index cdc7c8a..4e09028 100644 --- a/main/device.cpp +++ b/main/device.cpp @@ -11,14 +11,26 @@ Device::Device(std::string _id) : ready(false), should_publish(false), mutex(xSemaphoreCreateMutex()), sem(xSemaphoreCreateBinary()), - power_on(true), + power_on(false), strip_length(0), client(start_mqtt_client(on_mqtt_connect, on_mqtt_message, this)) {} -void Device::set_mqtt_config(cJSON*) { +void Device::set_json_config(const cJSON *json) { xSemaphoreTake(mutex, portMAX_DELAY); - // TODO: update power_on and effect from JSON + const cJSON *state = cJSON_GetObjectItem(json, "state"); + if (state && cJSON_IsString(state)) { + power_on = strcmp(state->valuestring, "ON") == 0; + } + const cJSON *fx = cJSON_GetObjectItem(json, "effect"); + if (fx && cJSON_IsString(fx)) { + effect = fx->valuestring; + } + const cJSON *length = cJSON_GetObjectItem(json, "length"); + if (length && cJSON_IsNumber(length)) { + strip_length = length->valuedouble; + } + publish_state_locked(); xSemaphoreGive(mutex); @@ -55,22 +67,34 @@ void Device::on_mqtt_connect(esp_mqtt_client_handle_t client) { xSemaphoreGive(mutex); } -void Device::on_mqtt_message(esp_mqtt_client_handle_t, esp_mqtt_event_handle_t) { +void Device::on_mqtt_message(esp_mqtt_client_handle_t, esp_mqtt_event_handle_t event) { ESP_LOGI(TAG, "Received command via MQTT"); - // TODO: extract JSON, call set_mqtt_config + cJSON *json = cJSON_ParseWithLength(event->data, event->data_len); + if (json) { + set_json_config(json); + cJSON_Delete(json); + } else { + ESP_LOGE(TAG, "Invalid JSON data"); + } xSemaphoreGive(sem); } +cJSON* Device::make_json_config_locked() const { + cJSON *json = cJSON_CreateObject(); + cJSON_AddItemToObject(json, "state", cJSON_CreateString(power_on ? "ON" : "OFF")); + cJSON_AddItemToObject(json, "effect", cJSON_CreateString(effect.c_str())); + cJSON_AddItemToObject(json, "length", cJSON_CreateNumber(strip_length)); + return json; +} + void Device::publish_state_locked() { if (!(should_publish = !ready)) { + cJSON *json = make_json_config_locked(); + char *config = cJSON_PrintUnformatted(json); 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 + client, state_topic.c_str(), config, 0, 0, 0 )); + cJSON_Delete(json); + cJSON_free(config); } -} \ No newline at end of file +} diff --git a/main/device.hpp b/main/device.hpp index 117b342..6171b05 100644 --- a/main/device.hpp +++ b/main/device.hpp @@ -11,7 +11,7 @@ class Device { public: Device(std::string id); - void set_mqtt_config(cJSON*); + void set_json_config(const cJSON*); void lock() const { xSemaphoreTake(mutex, portMAX_DELAY); } void unlock() const { xSemaphoreGive(mutex); } @@ -26,6 +26,9 @@ public: // Only call while locked const std::string& get_effect() const { return effect; } bool is_on() const { return power_on; } + int get_strip_length() const { return strip_length; } + + cJSON* make_json_config_locked() const; private: std::string id; @@ -40,6 +43,7 @@ private: bool power_on; std::string effect; + int strip_length; esp_mqtt_client_handle_t client; @@ -54,4 +58,4 @@ private: 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/leds.cpp b/main/leds.cpp index 4c429c2..768deae 100644 --- a/main/leds.cpp +++ b/main/leds.cpp @@ -55,6 +55,13 @@ LEDStrip::LEDStrip(int _length) : last_us(0), offset(0) {} +void LEDStrip::setLength(int _length) { + length_changing(_length); + delete[] pixels; + length = _length; + pixels = new Color[_length]; +} + void LEDStrip::step() { if (pattern) pattern->step(pixels, length, last_us, offset); } @@ -74,3 +81,7 @@ void TerminalLEDs::show() const { line += "\x1b[0m"; ESP_LOGI(TAG, "%s", line.c_str()); } + +void TerminalLEDs::length_changing(int new_len) { + ESP_LOGI(TAG, "Length changing: %d->%d", length, new_len); +} diff --git a/main/leds.hpp b/main/leds.hpp index acaa16a..a67bc6b 100644 --- a/main/leds.hpp +++ b/main/leds.hpp @@ -32,19 +32,21 @@ struct Pattern { class LEDStrip { public: - LEDStrip(int length); + LEDStrip(int length = 0); void step(); virtual void show() const = 0; + virtual void length_changing(int) {}; - void setPattern(const Pattern* _pattern) { pattern = _pattern; } + void setPattern(const Pattern *_pattern) { pattern = _pattern; } const Pattern* getPattern() const { return pattern; } + void setLength(int length); int getLength() const { return length; } protected: - const int length; + int length; const Pattern *pattern; Color *pixels; @@ -55,6 +57,7 @@ private: class TerminalLEDs : public LEDStrip { public: - TerminalLEDs(int length) : LEDStrip(length) {} + TerminalLEDs(int length = 0) : LEDStrip(length) {} void show() const; + void length_changing(int); }; diff --git a/main/main.cpp b/main/main.cpp index 554886f..4413116 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -84,6 +84,52 @@ static void log_dir(const std::string &path) { } +static std::string read_file(const char *path) { + std::string data; + FILE *f = fopen(path, "r"); + if (!f) { + ESP_LOGE(TAG, "Failed to read %s", path); + return data; + } + constexpr size_t szbuf = 4096; + char *buf = (char*)malloc(szbuf); + size_t nread; + while ((nread = fread(buf, 1, szbuf - 1, f)) > 0) { + buf[nread] = '\0'; + data += buf; + } + fclose(f); + free(buf); + return data; +} + +static void write_file(const char *path, const std::string &data) { + FILE *f = fopen(path, "w"); + if (!f) { + ESP_LOGE(TAG, "Failed to open %s", path); + return; + } + const char *ptr = data.c_str(); + size_t remain = data.length(); + size_t nwritten = fwrite(ptr, 1, remain, f); + if (nwritten != remain) { + ESP_LOGE(TAG, "Failed to write to %s: %d/%d", path, nwritten, remain); + } + fclose(f); +} + + +std::string gen_config(bool is_on, const std::string &effect) { + return std::string() + "{ " + "\"state\": \"" + (is_on ? "ON" : "OFF") + "\", " + "\"effect\": \"" + effect + "\"" + " }"; +} + + +constexpr char config_path[] = "/spiffs/blinky.json"; + + // Entry Point extern "C" void app_main(void) { @@ -109,38 +155,52 @@ extern "C" void app_main(void) { // Dummy Filesystem start_filesystem(); - //log_dir("/spiffs"); + // TODO: Scrape this out + log_dir("/spiffs"); Device device("blinky-jr"); - // TODO: Load config from SPIFFS + cJSON *json = cJSON_Parse(read_file(config_path).c_str()); + if (json) { + ESP_LOGI(TAG, "Setting config"); + device.set_json_config(json); + cJSON_Delete(json); + } - auto LEDs = TerminalLEDs(20); - /*** *** ***/ - LEDs.setPattern(Presets::find("rainbow")); - /*** *** ***/ + LEDStrip *LEDs = new TerminalLEDs(); while (true) { ESP_LOGI(TAG, "Configuring LEDs"); device.lock(); + const std::string &effect = device.get_effect(); + bool is_on = device.is_on(); + int length = device.get_strip_length(); + cJSON *json = device.make_json_config_locked(); + device.unlock(); + + if (length != LEDs->getLength()) { + LEDs->setLength(length); + } - const Pattern *pattern = Presets::find(device.get_effect()); + const Pattern *pattern = Presets::find(effect); if (pattern) { - LEDs.setPattern(pattern); + LEDs->setPattern(pattern); } else { - ESP_LOGW(TAG, "Could not find pattern '%s'", device.get_effect()); - pattern = LEDs.getPattern(); + ESP_LOGW(TAG, "Could not find pattern '%s'", effect.c_str()); + pattern = LEDs->getPattern(); } - // TODO: Save Config to SPIFFS + char *config = cJSON_PrintUnformatted(json); + write_file(config_path, config); + ESP_LOGI(TAG, "Saved config"); - bool is_on = device.is_on(); - - device.unlock(); + cJSON_free(config); + cJSON_Delete(json); - if (!pattern || !is_on) { + if (length <= 0 || !pattern || !is_on) { if (!is_on) ESP_LOGI(TAG, "Device off"); + if (length <= 0) ESP_LOGW(TAG, "No LEDs configured"); if (!pattern) ESP_LOGW(TAG, "No LED pattern set"); ESP_LOGI(TAG, "Waiting for new config"); @@ -159,8 +219,8 @@ extern "C" void app_main(void) { break; } - LEDs.step(); - LEDs.show(); + LEDs->step(); + LEDs->show(); target_us += period_us; }