Includes terminal LED simulatorrmt-backend
| @@ -6,6 +6,9 @@ idf_component_register( | |||||
| "ota.cpp" | "ota.cpp" | ||||
| "mqtt.cpp" | "mqtt.cpp" | ||||
| "device.cpp" | "device.cpp" | ||||
| "utils.cpp" | |||||
| "leds.cpp" | |||||
| "presets.cpp" | |||||
| INCLUDE_DIRS "." | INCLUDE_DIRS "." | ||||
| ) | ) | ||||
| @@ -14,7 +14,7 @@ Device::Device(std::string _id) : | |||||
| power_on(true), | power_on(true), | ||||
| client(start_mqtt_client(on_mqtt_connect, on_mqtt_message, this)) | client(start_mqtt_client(on_mqtt_connect, on_mqtt_message, this)) | ||||
| {} | {} | ||||
| void Device::set_mqtt_config(cJSON*) { | void Device::set_mqtt_config(cJSON*) { | ||||
| xSemaphoreTake(mutex, portMAX_DELAY); | xSemaphoreTake(mutex, portMAX_DELAY); | ||||
| @@ -10,7 +10,7 @@ | |||||
| class Device { | class Device { | ||||
| public: | public: | ||||
| Device(std::string id); | Device(std::string id); | ||||
| void set_mqtt_config(cJSON*); | void set_mqtt_config(cJSON*); | ||||
| void lock() const { xSemaphoreTake(mutex, portMAX_DELAY); } | void lock() const { xSemaphoreTake(mutex, portMAX_DELAY); } | ||||
| @@ -0,0 +1,76 @@ | |||||
| static const char *TAG = "leds"; | |||||
| #include <esp_log.h> | |||||
| #include "leds.hpp" | |||||
| #include "utils.hpp" | |||||
| static inline uint8_t mix(int l, int r, Fixed x) { | |||||
| return ((l << shift) + (x * (r - l))) >> shift; | |||||
| } | |||||
| Color Gradient::at(Fixed x) const { | |||||
| int i_left = (x * n_colors) >> shift; | |||||
| int i_right = i_left < n_colors - 1 ? i_left + 1 : 0; | |||||
| Fixed ratio = (x * n_colors) - (i_left << shift); | |||||
| //ESP_LOGI(TAG, "at(%d) = %d between %d and %d", x, ratio, i_left, i_right); | |||||
| Color cl = colors[i_left]; | |||||
| Color cr = colors[i_right]; | |||||
| Color color = { | |||||
| mix(cl.r, cr.r, ratio), | |||||
| mix(cl.g, cr.g, ratio), | |||||
| mix(cl.b, cr.b, ratio) | |||||
| }; | |||||
| /*ESP_LOGI(TAG, "(%d, %d, %d) %f (%d, %d, %d) -> (%d, %d, %d)", | |||||
| cl.r, cl.g, cl.b, | |||||
| ratio / (float)factor, | |||||
| cr.r, cr.g, cr.b, | |||||
| color.r, color.g, color.b | |||||
| );*/ | |||||
| return color; | |||||
| } | |||||
| void Pattern::step(Color pixels[], int len, int64_t &last_us, Fixed &offset) const { | |||||
| int64_t now_us = time_us(); | |||||
| int64_t duration_us = last_us == 0 ? 0 : now_us - last_us; | |||||
| last_us = now_us; | |||||
| //ESP_LOGI(TAG, "duration %d", duration_us); | |||||
| int offset_delta = (duration_us << shift) / (cycle_time_ms * 1000); | |||||
| if (reverse) offset -= offset_delta; else offset += offset_delta; | |||||
| Fixed off = march ? | |||||
| (((int)offset * cycle_length) & ~((1 << shift) - 1)) / cycle_length : | |||||
| offset; | |||||
| //ESP_LOGI(TAG, "cycle time %d, delta %d, offset %d, off %d", cycle_time_ms, offset_delta, offset, off); | |||||
| for (int i = 0; i < len; ++i) { | |||||
| pixels[i] = gradient.at(off + ((i << shift) / cycle_length)); | |||||
| } | |||||
| } | |||||
| LEDStrip::LEDStrip(int _length) : | |||||
| length(_length), pattern(NULL), | |||||
| pixels(new Color[_length]), | |||||
| last_us(0), offset(0) | |||||
| {} | |||||
| void LEDStrip::step() { | |||||
| if (pattern) pattern->step(pixels, length, last_us, offset); | |||||
| } | |||||
| static std::string termcolor(Color c) { | |||||
| return "\x1b[48;2;" + | |||||
| std::to_string((int)c.r) + ";" + | |||||
| std::to_string((int)c.g) + ";" + | |||||
| std::to_string((int)c.b) + "m"; | |||||
| } | |||||
| void TerminalLEDs::show() const { | |||||
| std::string line; | |||||
| for (int i = 0; i < length; i++) { | |||||
| line += termcolor(pixels[i]) + " "; | |||||
| } | |||||
| line += "\x1b[0m"; | |||||
| ESP_LOGI(TAG, "%s", line.c_str()); | |||||
| } | |||||
| @@ -0,0 +1,60 @@ | |||||
| #pragma once | |||||
| #include <cstdint> | |||||
| #include <string> | |||||
| struct Color { | |||||
| uint8_t r, g, b; | |||||
| }; | |||||
| typedef uint16_t Fixed; | |||||
| constexpr int factor = 65536; | |||||
| constexpr int shift = 16; | |||||
| struct Gradient { | |||||
| Color at(Fixed x) const; | |||||
| unsigned int n_colors; | |||||
| Color colors[]; | |||||
| }; | |||||
| struct Pattern { | |||||
| void step(Color[], int, int64_t &last_us, Fixed &offset) const; | |||||
| int cycle_length; | |||||
| int cycle_time_ms; | |||||
| bool reverse; | |||||
| bool march; | |||||
| Gradient gradient; | |||||
| }; | |||||
| class LEDStrip { | |||||
| public: | |||||
| LEDStrip(int length); | |||||
| void step(); | |||||
| virtual void show() const = 0; | |||||
| void setPattern(const Pattern* _pattern) { pattern = _pattern; } | |||||
| const Pattern* getPattern() const { return pattern; } | |||||
| int getLength() const { return length; } | |||||
| protected: | |||||
| const int length; | |||||
| const Pattern *pattern; | |||||
| Color *pixels; | |||||
| private: | |||||
| int64_t last_us; | |||||
| Fixed offset; | |||||
| }; | |||||
| class TerminalLEDs : public LEDStrip { | |||||
| public: | |||||
| TerminalLEDs(int length) : LEDStrip(length) {} | |||||
| void show() const; | |||||
| }; | |||||
| @@ -11,6 +11,10 @@ static const char *TAG = "blinky"; | |||||
| #include "http_serv.hpp" | #include "http_serv.hpp" | ||||
| #include "ota.hpp" | #include "ota.hpp" | ||||
| #include "device.hpp" | #include "device.hpp" | ||||
| #include "utils.hpp" | |||||
| #include "leds.hpp" | |||||
| #include "presets.hpp" | |||||
| #include "utils.hpp" | |||||
| // mDNS / NetBIOS | // mDNS / NetBIOS | ||||
| @@ -80,13 +84,6 @@ 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) { | ||||
| @@ -112,26 +109,63 @@ extern "C" void app_main(void) { | |||||
| // Dummy Filesystem | // Dummy Filesystem | ||||
| start_filesystem(); | start_filesystem(); | ||||
| log_dir("/spiffs"); | |||||
| //log_dir("/spiffs"); | |||||
| // TODO: Something useful | |||||
| Device device("blinky-jr"); | Device device("blinky-jr"); | ||||
| /* | |||||
| int period_us = 1000000; | |||||
| int64_t target_us = now_us(); | |||||
| // TODO: Load config from SPIFFS | |||||
| auto LEDs = TerminalLEDs(20); | |||||
| /*** *** ***/ | |||||
| LEDs.setPattern(Presets::find("rainbow")); | |||||
| /*** *** ***/ | |||||
| while (true) { | 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; | |||||
| ESP_LOGI(TAG, "Configuring LEDs"); | |||||
| device.lock(); | |||||
| const Pattern *pattern = Presets::find(device.get_effect()); | |||||
| if (pattern) { | |||||
| LEDs.setPattern(pattern); | |||||
| } else { | |||||
| ESP_LOGW(TAG, "Could not find pattern '%s'", device.get_effect()); | |||||
| pattern = LEDs.getPattern(); | |||||
| } | |||||
| // TODO: Save Config to SPIFFS | |||||
| bool is_on = device.is_on(); | |||||
| device.unlock(); | |||||
| if (!pattern || !is_on) { | |||||
| if (!is_on) ESP_LOGI(TAG, "Device off"); | |||||
| if (!pattern) ESP_LOGW(TAG, "No LED pattern set"); | |||||
| ESP_LOGI(TAG, "Waiting for new config"); | |||||
| device.wait(); | |||||
| } else { | |||||
| ESP_LOGI(TAG, "Starting animation"); | |||||
| int period_us = 1000000; | |||||
| int64_t target_us = time_us(); | |||||
| while (true) { | |||||
| int64_t delay_us = target_us - time_us(); | |||||
| if (delay_us < 0) delay_us = 0; | |||||
| if (device.wait(delay_us / 1000)) { | |||||
| // If the semaphore is set, go back to top of outer loop | |||||
| break; | |||||
| } | |||||
| LEDs.step(); | |||||
| LEDs.show(); | |||||
| target_us += period_us; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| */ | |||||
| while (device.wait()) ESP_LOGI(TAG, "Reload!"); | |||||
| // Spin | // Spin | ||||
| } | } | ||||
| @@ -0,0 +1,61 @@ | |||||
| #include <map> | |||||
| #include "presets.hpp" | |||||
| namespace Gradients { | |||||
| #define GRADIENT(name, len, ...) \ | |||||
| constexpr Gradient name = { \ | |||||
| .n_colors = len, \ | |||||
| .colors = __VA_ARGS__, \ | |||||
| } | |||||
| GRADIENT(rainbow, 6, { | |||||
| Colors::red, | |||||
| Colors::orange, | |||||
| Colors::yellow, | |||||
| Colors::green, | |||||
| Colors::blue, | |||||
| Colors::purple, | |||||
| }); | |||||
| GRADIENT(peacock, 4, { | |||||
| Colors::teal, | |||||
| Colors::black, | |||||
| Colors::purple, | |||||
| Colors::blue, | |||||
| }); | |||||
| } // Gradients | |||||
| namespace Patterns { | |||||
| #define PATTERN(x) \ | |||||
| static const Pattern x = { \ | |||||
| .cycle_length = 20, \ | |||||
| .cycle_time_ms = 10000, \ | |||||
| .reverse = false, \ | |||||
| .march = false, \ | |||||
| .gradient = Gradients::x, \ | |||||
| } | |||||
| PATTERN(rainbow); | |||||
| PATTERN(peacock); | |||||
| } // Patterns | |||||
| #define PRESET(x) {#x, &Patterns::x} | |||||
| static const std::map <std::string, const Pattern*> presets = { | |||||
| PRESET(rainbow), | |||||
| PRESET(peacock), | |||||
| }; | |||||
| const Pattern* Presets::find(const std::string &name) { | |||||
| auto e = presets.find(name); | |||||
| return e == presets.end() ? NULL : e->second; | |||||
| } | |||||
| @@ -0,0 +1,47 @@ | |||||
| #pragma once | |||||
| #include <string> | |||||
| #include "leds.hpp" | |||||
| namespace Presets { | |||||
| const struct Pattern* find(const std::string&); | |||||
| } // Presets | |||||
| namespace Colors { | |||||
| constexpr Color red = {255, 0, 0}; | |||||
| constexpr Color orange = {78, 25, 0}; | |||||
| constexpr Color yellow = {255, 160, 0}; | |||||
| constexpr Color green = {0, 255, 0}; | |||||
| constexpr Color blue = {0, 0, 255}; | |||||
| constexpr Color purple = {38, 10, 42}; | |||||
| constexpr Color dark_red = {32, 0, 0}; | |||||
| constexpr Color dark_orange = {20, 6, 0}; | |||||
| constexpr Color dark_yellow = {64, 40, 0}; | |||||
| constexpr Color dark_green = {0, 32, 0}; | |||||
| constexpr Color dark_blue = {0, 0, 32}; | |||||
| constexpr Color dark_purple = {8, 3, 10}; | |||||
| constexpr Color pale_red = {255, 64, 64}; | |||||
| constexpr Color pale_orange = {78, 25, 10}; | |||||
| constexpr Color pale_yellow = {255, 160, 10}; | |||||
| constexpr Color pale_green = {64, 255, 25}; | |||||
| constexpr Color pale_blue = {70, 70, 255}; | |||||
| constexpr Color pale_purple = {42, 20, 42}; | |||||
| constexpr Color black = {0, 0, 0}; | |||||
| constexpr Color white = {255, 255, 255}; | |||||
| constexpr Color pink = pale_red; | |||||
| constexpr Color peach = pale_orange; | |||||
| // TODO: chartreuse | |||||
| constexpr Color brown = {6, 2, 0}; | |||||
| constexpr Color teal = {0, 85, 35}; | |||||
| constexpr Color magenta = {255, 0, 255}; | |||||
| } // Colors | |||||
| @@ -0,0 +1,10 @@ | |||||
| #include <sys/time.h> | |||||
| #include "utils.hpp" | |||||
| int64_t time_us() { | |||||
| struct timeval tv_now; | |||||
| gettimeofday(&tv_now, NULL); | |||||
| return (int64_t)tv_now.tv_sec * 1000000L + (int64_t)tv_now.tv_usec; | |||||
| } | |||||
| @@ -0,0 +1,6 @@ | |||||
| #pragma once | |||||
| #include <cstdint> | |||||
| int64_t time_us(); | |||||