diff --git a/files/gamma.json b/files/gamma.json new file mode 100644 index 0000000..c396b2f --- /dev/null +++ b/files/gamma.json @@ -0,0 +1,4 @@ +{ + "gamma": [0,0,0,0,0,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,5,5,5,5,5,6,6,6,6,6,7,7,7,7,8,8,8,8,9,9,9,10,10,10,10,11,11,11,12,12,12,13,13,13,14,14,15,15,15,16,16,17,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25,26,26,27,28,28,29,29,30,31,31,32,32,33,34,34,35,36,37,37,38,39,39,40,41,42,43,43,44,45,46,47,47,48,49,50,51,52,53,54,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,70,71,72,73,74,75,76,77,79,80,81,82,83,85,86,87,88,90,91,92,94,95,96,98,99,100,102,103,105,106,108,109,110,112,113,115,116,118,120,121,123,124,126,128,129,131,132,134,136,138,139,141,143,145,146,148,150,152,154,155,157,159,161,163,165,167,169,171,173,175,177,179,181,183,185,187,189,191,193,196,198,200,202,204,207,209,211,214,216,218,220,223,225,228,230,232,235,237,240,242,245,247,250,252,255], + "inv_gamma": [0,9,18,26,33,39,44,48,52,56,60,63,66,69,72,74,77,79,81,84,86,88,90,92,94,96,97,99,101,103,104,106,107,109,110,112,113,115,116,117,119,120,121,123,124,125,126,128,129,130,131,132,133,134,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,151,152,153,154,155,156,157,158,159,159,160,161,162,163,163,164,165,166,167,167,168,169,170,171,171,172,173,174,174,175,176,176,177,178,179,179,180,181,181,182,183,183,184,185,185,186,187,187,188,189,189,190,191,191,192,192,193,194,194,195,196,196,197,197,198,198,199,200,200,201,201,202,203,203,204,204,205,205,206,206,207,208,208,209,209,210,210,211,211,212,212,213,213,214,215,215,216,216,217,217,218,218,219,219,220,220,221,221,222,222,223,223,224,224,225,225,225,226,226,227,227,228,228,229,229,230,230,231,231,232,232,232,233,233,234,234,235,235,236,236,236,237,237,238,238,239,239,240,240,240,241,241,242,242,242,243,243,244,244,245,245,245,246,246,247,247,247,248,248,249,249,249,250,250,251,251,251,252,252,253,253,253,254,254,255,255] +} diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 76fd37b..96c6de8 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -9,6 +9,7 @@ idf_component_register( "device.cpp" "utils.cpp" "leds.cpp" + "gamma.cpp" "presets.cpp" "spi_leds.cpp" # "screen_leds.cpp" diff --git a/main/device.cpp b/main/device.cpp index f01542d..83d54ca 100644 --- a/main/device.cpp +++ b/main/device.cpp @@ -12,7 +12,7 @@ Device::Device(std::string _id) : ready(false), mutex(xSemaphoreCreateMutex()), sem(xSemaphoreCreateBinary()), - power_on(false), strip_length(0), + power_on(false), strip_length(0), brightness(255), client(start_mqtt_client(on_mqtt_connect, on_mqtt_message, this)) {} @@ -29,7 +29,11 @@ void Device::set_json_config(const cJSON *json) { } const cJSON *length = cJSON_GetObjectItem(json, "length"); if (length && cJSON_IsNumber(length)) { - strip_length = length->valuedouble; + strip_length = length->valueint; + } + const cJSON *jbrightness = cJSON_GetObjectItem(json, "brightness"); + if (jbrightness && cJSON_IsNumber(jbrightness)) { + brightness = jbrightness->valueint; } publish_state_locked(); @@ -55,6 +59,11 @@ void Device::advertise_locked() { cJSON *effects = cJSON_AddArrayToObject(json, "effect_list"); for (auto iter = Presets::map_begin(); iter != Presets::map_end(); ++iter) cJSON_AddItemToArray(effects, cJSON_CreateString(iter->first.c_str())); + cJSON_AddBoolToObject(json, "brightness", true); + cJSON_AddStringToObject(json, "brightness_command_topic", (topic_prefix + "/cmd").c_str()); + cJSON_AddStringToObject(json, "brightness_state_topic", (topic_prefix + "/state").c_str()); + cJSON_AddStringToObject(json, "brightness_template", "{{ value_json.brightness }}"); + cJSON_AddStringToObject(json, "brightness_value_template", "{\"brightness\": {{ value }} }"); char *config = cJSON_PrintUnformatted(json); @@ -193,6 +202,7 @@ cJSON* Device::make_json_config_locked() const { cJSON_AddStringToObject(json, "state", power_on ? "ON" : "OFF"); cJSON_AddStringToObject(json, "effect", effect.c_str()); cJSON_AddNumberToObject(json, "length", strip_length); + cJSON_AddNumberToObject(json, "brightness", brightness); return json; } diff --git a/main/device.hpp b/main/device.hpp index e645b07..5e5d1a1 100644 --- a/main/device.hpp +++ b/main/device.hpp @@ -27,6 +27,7 @@ public: const std::string& get_effect() const { return effect; } bool is_on() const { return power_on; } int get_strip_length() const { return strip_length; } + int get_brightness() const { return brightness; } cJSON* make_json_config_locked() const; @@ -43,6 +44,7 @@ private: bool power_on; std::string effect; int strip_length; + int brightness; esp_mqtt_client_handle_t client; diff --git a/main/gamma.cpp b/main/gamma.cpp new file mode 100644 index 0000000..a7c2a0e --- /dev/null +++ b/main/gamma.cpp @@ -0,0 +1,98 @@ +static const char *TAG = "gamma"; +#include + +#include + +#include "gamma.hpp" +#include "utils.hpp" + + +namespace Gamma { + +static uint8_t gamma[256]; +static uint8_t inv_gamma[256]; + + +static void default_table(uint8_t *table) { + for (int val = 0; val < 256; ++val) *table++ = val; +} + + +static void json_table(cJSON *jarray, uint8_t *table) { + if (jarray->type != cJSON_Array) { + ESP_LOGE(TAG, "Not an array: %s", jarray->string); + } + + int count = cJSON_GetArraySize(jarray); + if (count != 256) { + ESP_LOGE(TAG, "Wrong size for %s: %d", jarray->string, count); + } + + uint8_t *ptr = table; + cJSON *jnumber; + cJSON_ArrayForEach(jnumber, jarray) { + if (jnumber->type != cJSON_Number) { + ESP_LOGE(TAG, "Not a number: %s", jnumber->string); + default_table(table); + return; + } + *ptr++ = (uint8_t)jnumber->valueint; + } +} + +static inline cJSON* load_json(const char *path) { + return cJSON_Parse(read_file(path).c_str()); +} + +void reload() { + default_table(gamma); + default_table(inv_gamma); + + cJSON *json = load_json("/spiffs/gamma.json"); + if (!json) { + ESP_LOGE(TAG, "No gamma values!"); + return; + } + + cJSON *jgamma = cJSON_GetObjectItem(json, "gamma"); + if (!jgamma) { + ESP_LOGE(TAG, "No gamma values!"); + cJSON_Delete(json); + return; + } + + cJSON *jinv_gamma = cJSON_GetObjectItem(json, "inv_gamma"); + if (!jinv_gamma) { + ESP_LOGE(TAG, "No inverse gamma values!"); + cJSON_Delete(json); + return; + } + + json_table(jgamma, gamma); + json_table(jinv_gamma, inv_gamma); + + cJSON_Delete(json); +} + +/* +static inline uint8_t scale(int val, int num, int dem) { + if (dem == 0) return 0; + int val = (val * num) / dem; + return (val >= 256 ? 255 : val); +} +*/ + +static inline Color correct_single(Color c, int b) { + return {gamma[(b * c.r) / 255], gamma[(b * c.g) / 255], gamma[(b * c.b) / 255]}; +} + +void correct(Color *dst, const Color *src, int n, int b) { + while(n-- > 0) *dst++ = correct_single(*src++, b); +} + +Color wrong_single(Color c) { + return {inv_gamma[c.r], inv_gamma[c.g], inv_gamma[c.b]}; +} + + +} // Gamma diff --git a/main/gamma.hpp b/main/gamma.hpp new file mode 100644 index 0000000..5d34ee7 --- /dev/null +++ b/main/gamma.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "leds.hpp" + + +namespace Gamma { + +void reload(); + +void correct(Color *dst, const Color *src, int len, int brightness = 255); + +Color wrong_single(Color); + +} // Gamma diff --git a/main/leds.cpp b/main/leds.cpp index 6ede02e..d508131 100644 --- a/main/leds.cpp +++ b/main/leds.cpp @@ -3,13 +3,15 @@ static const char *TAG = "leds"; #include +#include "gamma.hpp" #include "leds.hpp" #include "utils.hpp" LEDStrip::LEDStrip(int _length) : - length(_length), pattern(NULL), + length(_length), brightness(255), pattern(NULL), pixels(new Color[_length]), + colors(new Color[_length]), state(NULL) {} @@ -23,13 +25,16 @@ void LEDStrip::setLength(int _length) { clear(); length_changing(_length); delete[] pixels; + delete[] colors; length = _length; pixels = new Color[_length]; + colors = new Color[_length]; update_state(); } void LEDStrip::step() { - if (pattern) pattern->step(pixels, length, state); + if (pattern) pattern->step(colors, length, state); + Gamma::correct(pixels, colors, length, brightness); } void LEDStrip::clear() { diff --git a/main/leds.hpp b/main/leds.hpp index 2770ac9..7d00ae0 100644 --- a/main/leds.hpp +++ b/main/leds.hpp @@ -45,10 +45,14 @@ public: void setLength(int length); int getLength() const { return length; } + void setBrightness(int _brightness) { brightness = _brightness; } + protected: int length; + int brightness; const Pattern *pattern; Color *pixels; + Color *colors; private: void update_state(); diff --git a/main/main.cpp b/main/main.cpp index 8dd4b60..56915cc 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -13,6 +13,7 @@ static const char *TAG = "blinky"; #include "device.hpp" #include "utils.hpp" #include "leds.hpp" +#include "gamma.hpp" #include "presets.hpp" #include "utils.hpp" #include "spi_leds.hpp" @@ -111,6 +112,8 @@ extern "C" void app_main(void) { int frequency = 30; while (true) { + Gamma::reload(); + // Trash the old preset in case we can't find the set effect LEDs->setPattern(NULL); Presets::reload(); @@ -121,9 +124,12 @@ extern "C" void app_main(void) { const std::string effect = device.get_effect(); bool is_on = device.is_on(); int length = device.get_strip_length(); + int brightness = device.get_brightness(); cJSON *json = device.make_json_config_locked(); device.unlock(); + LEDs->setBrightness(brightness); + if (length != LEDs->getLength()) { LEDs->setLength(length); } diff --git a/main/patterns/gradient.cpp b/main/patterns/gradient.cpp index 4ffe95a..2f93c14 100644 --- a/main/patterns/gradient.cpp +++ b/main/patterns/gradient.cpp @@ -26,9 +26,9 @@ public: Gradient *_gradient, int _cycle_length=20, int _cycle_time_ms=-1, bool _reverse=false, bool _march=false - ) : cycle_length(_cycle_length), + ) : cycle_length(_cycle_length > 0 ? _cycle_length : _gradient->n_colors), cycle_time_ms( _cycle_time_ms < 0 ? - _cycle_length * 500 : _cycle_time_ms + cycle_length * 500 : _cycle_time_ms ), reverse(_reverse), march(_march), gradient(_gradient) {} @@ -97,8 +97,7 @@ void GradientPattern::step(Color pixels[], int len, Pattern::State *_state) cons // Don't make a major animation jump if it's been terribly long if (duration_us > 100000) duration_us = 100000; - - int offset_delta = (duration_us << shift) / (cycle_time_ms * 1000); + int offset_delta = (cycle_time_ms <= 0 ? 0 : (duration_us << shift) / (cycle_time_ms * 1000)); if (reverse) state->offset += offset_delta; else state->offset -= offset_delta; Fixed off = march ? (((int)state->offset * cycle_length) & ~((1 << shift) - 1)) / cycle_length : @@ -134,5 +133,27 @@ Pattern* json_gradient_pattern(cJSON *pattern, const Presets::ColorMap &colors) gradient->colors[i++] = color; } - return new GradientPattern(gradient); + int cycle_length = 20; + cJSON *jlength = cJSON_GetObjectItem(pattern, "length"); + if (jlength) { + if (jlength->type != cJSON_Number) { + ESP_LOGE(TAG, "Not a number: %s", jlength->string); + } else { + cycle_length = jlength->valueint; + } + } + + int cycle_time_ms = -1; + cJSON *jduration = cJSON_GetObjectItem(pattern, "duration"); + if (jduration) { + if (jduration->type != cJSON_Number) { + ESP_LOGE(TAG, "Not a number: %s", jduration->string); + } else { + cycle_time_ms = jduration->valuedouble * 1000.; + } + } + + // TODO: Reverse, March + + return new GradientPattern(gradient, cycle_length, cycle_time_ms); } diff --git a/main/presets.cpp b/main/presets.cpp index 1f7da36..d10df9c 100644 --- a/main/presets.cpp +++ b/main/presets.cpp @@ -3,6 +3,7 @@ static const char *TAG = "presets"; #include +#include "gamma.hpp" #include "presets.hpp" #include "utils.hpp" @@ -115,7 +116,7 @@ void reload() { cJSON *jcolor; cJSON_ArrayForEach(jcolor, jcolors) { Color color = json_color(jcolor, colors); - colors[jcolor->string] = color; + colors[jcolor->string] = Gamma::wrong_single(color); } cJSON_Delete(jcolors); }