From 06ee8d4b0c0a3ef5cba2f41321341c4c12b44a65 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Thu, 12 Nov 2020 23:36:25 +0000 Subject: [PATCH 01/52] start migration of led audio code to new framework --- platformio.ini | 5 +- src/AudioLightMode.h | 45 ++++++++++++++++ src/ColorMode.cpp | 17 ++++++ src/ColorMode.h | 62 ++++++++++++++++++++++ src/FrequencySampler.cpp | 55 ++++++++++++++++++++ src/FrequencySampler.h | 32 ++++++++++++ src/JsonUtil.cpp | 62 ++++++++++++++++++++++ src/JsonUtil.h | 19 +++++++ src/LedSettingsService.cpp | 27 ++++++++++ src/LedSettingsService.h | 56 ++++++++++++++++++++ src/LightMqttSettingsService.cpp | 16 ------ src/LightMqttSettingsService.h | 41 --------------- src/LightStateService.cpp | 73 -------------------------- src/LightStateService.h | 88 -------------------------------- src/main.cpp | 14 ----- 15 files changed, 378 insertions(+), 234 deletions(-) create mode 100644 src/AudioLightMode.h create mode 100644 src/ColorMode.cpp create mode 100644 src/ColorMode.h create mode 100644 src/FrequencySampler.cpp create mode 100644 src/FrequencySampler.h create mode 100644 src/JsonUtil.cpp create mode 100644 src/JsonUtil.h create mode 100644 src/LedSettingsService.cpp create mode 100644 src/LedSettingsService.h delete mode 100644 src/LightMqttSettingsService.cpp delete mode 100644 src/LightMqttSettingsService.h delete mode 100644 src/LightStateService.cpp delete mode 100644 src/LightStateService.h diff --git a/platformio.ini b/platformio.ini index 174353c1..07340723 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,8 +2,8 @@ extra_configs = factory_settings.ini features.ini -default_envs = esp12e -;default_envs = node32s +;default_envs = esp12e +default_envs = node32s [env] build_flags= @@ -35,6 +35,7 @@ lib_deps = ArduinoJson@>=6.0.0,<7.0.0 ESP Async WebServer@>=1.2.0,<2.0.0 AsyncMqttClient@>=0.8.2,<1.0.0 + FastLED [env:esp12e] platform = espressif8266 diff --git a/src/AudioLightMode.h b/src/AudioLightMode.h new file mode 100644 index 00000000..b2187688 --- /dev/null +++ b/src/AudioLightMode.h @@ -0,0 +1,45 @@ +#ifndef AudioLightMode_h +#define AudioLightMode_h + +#include +#include +#include + +template +class AudioLightMode : public StatefulService { + protected: + LedSettingsService* _ledSettingsService; + HttpEndpoint _httpEndpoint; + FSPersistence _fsPersistence; + + public: + // TODO - replace the two strings with a single id - string template or concatication? + AudioLightMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + JsonStateReader stateReader, + JsonStateUpdater stateUpdater, + const String& settingsServicePath, + const String& setttingsFile) : + _httpEndpoint(stateReader, stateUpdater, this, server, settingsServicePath, securityManager), + _fsPersistence(stateReader, stateUpdater, this, fs, setttingsFile) { + } + + /* + * Get the code for the mode as a string + */ + virtual String getId() = 0; + + /* + * Allow the mode to animate the LEDs - called by the main loop. + */ + virtual void tick() = 0; + + /** + * Called when the mode is enabled. + */ + virtual void enable() = 0; + +}; + +#endif // end AudioLightMode_h diff --git a/src/ColorMode.cpp b/src/ColorMode.cpp new file mode 100644 index 00000000..f32921fa --- /dev/null +++ b/src/ColorMode.cpp @@ -0,0 +1,17 @@ +#include + +String ColorMode::getId() { + return "color"; +} + +void ColorMode::enable() { + _refresh = true; +} + +void ColorMode::tick() { + if (_refresh || _audioEnabled) { + fill_solid(_leds, _numLeds, _color); + _ledController->showLeds(_audioEnabled ? calculateEnergyFloat(_includedBands) * _brightness : _brightness); + _refresh = false; + } +} diff --git a/src/ColorMode.h b/src/ColorMode.h new file mode 100644 index 00000000..79daf043 --- /dev/null +++ b/src/ColorMode.h @@ -0,0 +1,62 @@ +#ifndef COLOR_MODE_H +#define COLOR_MODE_H + +#include +#include +#include + +#ifndef FACTORY_COLOR_MODE_COLOR +#define FACTORY_COLOR_MODE_COLOR CRGB::White +#endif + +#ifndef FACTORY_COLOR_MODE_AUDIO_ENABLED +#define FACTORY_COLOR_MODE_AUDIO_ENABLED false +#endif + +#define COLOR_FILE_PATH "/modes/color.json" +#define COLOR_SERVICE_PATH "/rest/modes/color.json" + +class ColorModeSettings { + public: + ColorModeSettings() { + } + + CRGB color = FACTORY_COLOR_MODE_COLOR; + bool audioEnabled = FACTORY_COLOR_MODE_AUDIO_ENABLED; + bool includedBands[NUM_BANDS]; + + static void read(ColorModeSettings& settings, JsonObject& root) { + writeColorToJson(root, &settings.color); + writeBoolToJson(root, &settings.audioEnabled, "audio_enabled"); + writeBooleanArrayToJson(root, settings.includedBands, NUM_BANDS, "included_bands"); + } + + static StateUpdateResult update(JsonObject& root, ColorModeSettings& settings) { + updateColorFromJson(root, &settings.color, FACTORY_COLOR_MODE_COLOR); + updateBoolFromJson(root, &settings.audioEnabled, FACTORY_COLOR_MODE_AUDIO_ENABLED, "audio_enabled"); + updateBooleanArrayFromJson(root, settings.includedBands, NUM_BANDS, "included_bands"); + return StateUpdateResult::CHANGED; + } +}; + +class ColorMode : public AudioLightMode { + private: + boolean _refresh = true; + + public: + ColorMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : + AudioLightMode(server, + fs, + securityManager, + ColorModeSettings::read, + ColorModeSettings::update, + COLOR_SERVICE_PATH, + COLOR_FILE_PATH) { + addUpdateHandler([&](const String& originId) { enable(); }, false); + }; + String getId(); + void tick(); + void enable(); +}; + +#endif diff --git a/src/FrequencySampler.cpp b/src/FrequencySampler.cpp new file mode 100644 index 00000000..4ca00cc7 --- /dev/null +++ b/src/FrequencySampler.cpp @@ -0,0 +1,55 @@ +#include + +void FrequencySampler::begin() { + pinMode(FREQUENCY_SAMPLER_RESET_PIN, OUTPUT); + pinMode(FREQUENCY_SAMPLER_STROBE_PIN, OUTPUT); + pinMode(FREQUENCY_SAMPLER_ANALOG_PIN, INPUT); + digitalWrite(FREQUENCY_SAMPLER_RESET_PIN, LOW); + digitalWrite(FREQUENCY_SAMPLER_STROBE_PIN, HIGH); +} + +void FrequencySampler::loop() { + // take samples 100 times a second (max) + EVERY_N_MILLIS(10) { + update( + [&](FrequencyData& frequencyData) { + // Reset MSGEQ7 IC + digitalWrite(FREQUENCY_SAMPLER_RESET_PIN, HIGH); + delayMicroseconds(5); + digitalWrite(FREQUENCY_SAMPLER_RESET_PIN, LOW); + + // read samples + for (uint8_t i = 0; i < NUM_BANDS; i++) { + // trigger each value in turn + digitalWrite(FREQUENCY_SAMPLER_STROBE_PIN, LOW); + + // allow the output to settle + delayMicroseconds(36); + + // read frequency for pin + frequencyData.bands[i] = analogRead(FREQUENCY_SAMPLER_ANALOG_PIN); + + // re-map frequency to eliminate low level noise + frequencyData.bands[i] = frequencyData.bands[i] > FREQUENCY_SAMPLER_DEAD_ZONE + ? map(frequencyData.bands[i] - FREQUENCY_SAMPLER_DEAD_ZONE, + 0, + ADC_MAX_VALUE - FREQUENCY_SAMPLER_DEAD_ZONE, + 0, + ADC_MAX_VALUE) + : 0; + + // crappy rolling values + frequencyData.rolling[i] = + _rollingAverageFactor * frequencyData.bands[i] + (1 - _rollingAverageFactor) * frequencyData.rolling[i]; + + // strobe pin high again for next loop + digitalWrite(FREQUENCY_SAMPLER_STROBE_PIN, HIGH); + + // wait 72 microseconds + delayMicroseconds(36); + } + return StateUpdateResult::CHANGED; + }, + "loop"); + } +} diff --git a/src/FrequencySampler.h b/src/FrequencySampler.h new file mode 100644 index 00000000..632de2a5 --- /dev/null +++ b/src/FrequencySampler.h @@ -0,0 +1,32 @@ +#ifndef FrequencySampler_h +#define FrequencySampler_h + +#include +#include +#include + +#define FREQUENCY_SAMPLER_DEAD_ZONE 700 +#define FREQUENCY_SAMPLER_RESET_PIN 4 +#define FREQUENCY_SAMPLER_STROBE_PIN 5 +#define FREQUENCY_SAMPLER_ANALOG_PIN 36 +#define FREQUENCY_SAMPLER_DEFAULT_ROLLING_AVG_FACTOR 0.15 + +#define NUM_BANDS 7 +#define ADC_MAX_VALUE 4096 + +class FrequencyData { + public: + uint16_t bands[NUM_BANDS]; + uint16_t rolling[NUM_BANDS]; +}; + +class FrequencySampler : public StatefulService { + public: + void begin(); + void loop(); + + private: + float _rollingAverageFactor = FREQUENCY_SAMPLER_DEFAULT_ROLLING_AVG_FACTOR; +}; + +#endif // end FrequencySampler_h diff --git a/src/JsonUtil.cpp b/src/JsonUtil.cpp new file mode 100644 index 00000000..fabf1a99 --- /dev/null +++ b/src/JsonUtil.cpp @@ -0,0 +1,62 @@ +#include + +void updateBooleanArrayFromJson(JsonObject& root, bool booleanArray[], uint16_t maxLen, String key){ + if (root.containsKey(key) && root[key].is()){ + JsonArray jsonArray = root[key]; + for (uint8_t i = 0; i < maxLen; i++) { + if (i < jsonArray.size() && jsonArray[i].is()) { + booleanArray[i] = jsonArray[i]; + }else{ + booleanArray[i] = false; + } + } + } +} + +void writeBooleanArrayToJson(JsonObject& root, bool booleanArray[], uint16_t len, String key){ + JsonArray array = root.createNestedArray(key); + for (uint8_t i = 0; i < len; i++) { + array.add(booleanArray[i]); + } +} + +void updateColorFromJson(JsonObject& root, CRGB* color, CRGB def, String key){ + if (root.containsKey(key) && root[key].is()){ + String colorString = root[key]; + if (colorString.length() == 7){ + *color = CRGB(strtoll(&colorString[1], NULL, 16)); + } + } else { + *color = def; + } +} + +void writeColorToJson(JsonObject& root, CRGB* color, String key){ + char colorString[8]; + sprintf(colorString,"#%02x%02x%02x", color->r, color->g, color->b); + root[key] = colorString; +} + +void updateByteFromJson(JsonObject& root, uint8_t* value, uint8_t def, String key){ + if (root.containsKey(key) && root[key].is()){ + *value = (uint8_t) root[key]; + }else{ + *value = def; + } +} + +void writeByteToJson(JsonObject& root, uint8_t* value, String key){ + root[key] = (uint8_t) *value; +} + +void updateBoolFromJson(JsonObject& root, bool* value, bool def, String key){ + if (root.containsKey(key) && root[key].is()){ + *value = (bool) root[key]; + }else{ + *value = def; + } +} + +void writeBoolToJson(JsonObject& root, bool* value, String key){ + root[key] = (bool) *value; +} diff --git a/src/JsonUtil.h b/src/JsonUtil.h new file mode 100644 index 00000000..ef346f35 --- /dev/null +++ b/src/JsonUtil.h @@ -0,0 +1,19 @@ +#ifndef COLOR_UTIL_H +#define COLOR_UTIL_H + +#include +#include + +void updateBooleanArrayFromJson(JsonObject& root, bool booleanArray[], uint16_t maxSize, String key); +void writeBooleanArrayToJson(JsonObject& root, bool booleanArray[], uint16_t maxSize, String key); + +void updateColorFromJson(JsonObject& root, CRGB* color, CRGB def, String key = "color"); +void writeColorToJson(JsonObject& root, CRGB* color, String key = "color"); + +void updateByteFromJson(JsonObject& root, uint8_t* value, uint8_t def, String key); +void writeByteToJson(JsonObject& root, uint8_t* value, String key); + +void updateBoolFromJson(JsonObject& root, bool* value, bool def, String key); +void writeBoolToJson(JsonObject& root, bool* value, String key); + +#endif diff --git a/src/LedSettingsService.cpp b/src/LedSettingsService.cpp new file mode 100644 index 00000000..c51200eb --- /dev/null +++ b/src/LedSettingsService.cpp @@ -0,0 +1,27 @@ +#include + +LedSettingsService::LedSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : + _httpEndpoint(LedSettings::read, + LedSettings::update, + this, + server, + LED_SETTINGS_SERVICE_PATH, + securityManager, + AuthenticationPredicates::IS_AUTHENTICATED), + _fsPersistence(LedSettings::read, LedSettings::update, this, fs, LED_SETTINGS_FILE) { + _ledController = &FastLED.addLeds(_leds, NUM_LEDS); + addUpdateHandler([&](const String& originId) { showLeds(); }, false); +} + +void LedSettingsService::begin() { + _fsPersistence.readFromFS(); +} + +void LedSettingsService::update(LedUpdateCallback updateCallback) { + updateCallback(NUM_LEDS, _leds); + showLeds(); +} + +void LedSettingsService::showLeds() { + _ledController->showLeds(_state.brightness); +} diff --git a/src/LedSettingsService.h b/src/LedSettingsService.h new file mode 100644 index 00000000..5ab2b4e4 --- /dev/null +++ b/src/LedSettingsService.h @@ -0,0 +1,56 @@ +#ifndef LedSettingsService_h +#define LedSettingsService_h + +#define LED_SETTINGS_FILE "/config/ledSettings.json" +#define LED_SETTINGS_SERVICE_PATH "/rest/ledSettings" + +#include +#include +#include +#include + +#define LED_DATA_PIN 21 +#define COLOR_ORDER GRB +#define LED_TYPE WS2812 +#define NUM_LEDS 64 + +#ifndef FACTORY_LED_BRIGHTNESS +#define FACTORY_LED_BRIGHTNESS 128 +#endif + +typedef std::function LedUpdateCallback; + +// should be extended to allow number of LEDS to be modified +// should be extended to allow leds to be switched off +// could be extended to allow configuration of PIN (less likely) +// could be extended to allow configuring the strip type (less likely) +class LedSettings { + public: + uint8_t brightness; + + static void read(LedSettings& settings, JsonObject& root) { + root["brightness"] = settings.brightness; + } + + static StateUpdateResult update(JsonObject& root, LedSettings& settings) { + settings.brightness = root["brightness"] | FACTORY_LED_BRIGHTNESS; + return StateUpdateResult::CHANGED; + } +}; + +class LedSettingsService : public StatefulService { + public: + LedSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager); + + void begin(); + void update(LedUpdateCallback updateCallback); + void showLeds(); + + private: + CRGB _leds[NUM_LEDS]; + CLEDController* _ledController; + HttpEndpoint _httpEndpoint; + FSPersistence _fsPersistence; +}; + +#endif // end LedSettingsService_h diff --git a/src/LightMqttSettingsService.cpp b/src/LightMqttSettingsService.cpp deleted file mode 100644 index bddd9098..00000000 --- a/src/LightMqttSettingsService.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include - -LightMqttSettingsService::LightMqttSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : - _httpEndpoint(LightMqttSettings::read, - LightMqttSettings::update, - this, - server, - LIGHT_BROKER_SETTINGS_PATH, - securityManager, - AuthenticationPredicates::IS_AUTHENTICATED), - _fsPersistence(LightMqttSettings::read, LightMqttSettings::update, this, fs, LIGHT_BROKER_SETTINGS_FILE) { -} - -void LightMqttSettingsService::begin() { - _fsPersistence.readFromFS(); -} diff --git a/src/LightMqttSettingsService.h b/src/LightMqttSettingsService.h deleted file mode 100644 index 23a22181..00000000 --- a/src/LightMqttSettingsService.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef LightMqttSettingsService_h -#define LightMqttSettingsService_h - -#include -#include -#include - -#define LIGHT_BROKER_SETTINGS_FILE "/config/brokerSettings.json" -#define LIGHT_BROKER_SETTINGS_PATH "/rest/brokerSettings" - -class LightMqttSettings { - public: - String mqttPath; - String name; - String uniqueId; - - static void read(LightMqttSettings& settings, JsonObject& root) { - root["mqtt_path"] = settings.mqttPath; - root["name"] = settings.name; - root["unique_id"] = settings.uniqueId; - } - - static StateUpdateResult update(JsonObject& root, LightMqttSettings& settings) { - settings.mqttPath = root["mqtt_path"] | ESPUtils::defaultDeviceValue("homeassistant/light/"); - settings.name = root["name"] | ESPUtils::defaultDeviceValue("light-"); - settings.uniqueId = root["unique_id"] | ESPUtils::defaultDeviceValue("light-"); - return StateUpdateResult::CHANGED; - } -}; - -class LightMqttSettingsService : public StatefulService { - public: - LightMqttSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager); - void begin(); - - private: - HttpEndpoint _httpEndpoint; - FSPersistence _fsPersistence; -}; - -#endif // end LightMqttSettingsService_h diff --git a/src/LightStateService.cpp b/src/LightStateService.cpp deleted file mode 100644 index 81696222..00000000 --- a/src/LightStateService.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include - -LightStateService::LightStateService(AsyncWebServer* server, - SecurityManager* securityManager, - AsyncMqttClient* mqttClient, - LightMqttSettingsService* lightMqttSettingsService) : - _httpEndpoint(LightState::read, - LightState::update, - this, - server, - LIGHT_SETTINGS_ENDPOINT_PATH, - securityManager, - AuthenticationPredicates::IS_AUTHENTICATED), - _mqttPubSub(LightState::haRead, LightState::haUpdate, this, mqttClient), - _webSocket(LightState::read, - LightState::update, - this, - server, - LIGHT_SETTINGS_SOCKET_PATH, - securityManager, - AuthenticationPredicates::IS_AUTHENTICATED), - _mqttClient(mqttClient), - _lightMqttSettingsService(lightMqttSettingsService) { - // configure led to be output - pinMode(LED_PIN, OUTPUT); - - // configure MQTT callback - _mqttClient->onConnect(std::bind(&LightStateService::registerConfig, this)); - - // configure update handler for when the light settings change - _lightMqttSettingsService->addUpdateHandler([&](const String& originId) { registerConfig(); }, false); - - // configure settings service update handler to update LED state - addUpdateHandler([&](const String& originId) { onConfigUpdated(); }, false); -} - -void LightStateService::begin() { - _state.ledOn = DEFAULT_LED_STATE; - onConfigUpdated(); -} - -void LightStateService::onConfigUpdated() { - digitalWrite(LED_PIN, _state.ledOn ? LED_ON : LED_OFF); -} - -void LightStateService::registerConfig() { - if (!_mqttClient->connected()) { - return; - } - String configTopic; - String subTopic; - String pubTopic; - - DynamicJsonDocument doc(256); - _lightMqttSettingsService->read([&](LightMqttSettings& settings) { - configTopic = settings.mqttPath + "/config"; - subTopic = settings.mqttPath + "/set"; - pubTopic = settings.mqttPath + "/state"; - doc["~"] = settings.mqttPath; - doc["name"] = settings.name; - doc["unique_id"] = settings.uniqueId; - }); - doc["cmd_t"] = "~/set"; - doc["stat_t"] = "~/state"; - doc["schema"] = "json"; - doc["brightness"] = false; - - String payload; - serializeJson(doc, payload); - _mqttClient->publish(configTopic.c_str(), 0, false, payload.c_str()); - - _mqttPubSub.configureTopics(pubTopic, subTopic); -} diff --git a/src/LightStateService.h b/src/LightStateService.h deleted file mode 100644 index e9c9a9cd..00000000 --- a/src/LightStateService.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef LightStateService_h -#define LightStateService_h - -#include - -#include -#include -#include - -#define LED_PIN 2 -#define PRINT_DELAY 5000 - -#define DEFAULT_LED_STATE false -#define OFF_STATE "OFF" -#define ON_STATE "ON" - -// Note that the built-in LED is on when the pin is low on most NodeMCU boards. -// This is because the anode is tied to VCC and the cathode to the GPIO 4 (Arduino pin 2). -#ifdef ESP32 -#define LED_ON 0x1 -#define LED_OFF 0x0 -#elif defined(ESP8266) -#define LED_ON 0x0 -#define LED_OFF 0x1 -#endif - -#define LIGHT_SETTINGS_ENDPOINT_PATH "/rest/lightState" -#define LIGHT_SETTINGS_SOCKET_PATH "/ws/lightState" - -class LightState { - public: - bool ledOn; - - static void read(LightState& settings, JsonObject& root) { - root["led_on"] = settings.ledOn; - } - - static StateUpdateResult update(JsonObject& root, LightState& lightState) { - boolean newState = root["led_on"] | DEFAULT_LED_STATE; - if (lightState.ledOn != newState) { - lightState.ledOn = newState; - return StateUpdateResult::CHANGED; - } - return StateUpdateResult::UNCHANGED; - } - - static void haRead(LightState& settings, JsonObject& root) { - root["state"] = settings.ledOn ? ON_STATE : OFF_STATE; - } - - static StateUpdateResult haUpdate(JsonObject& root, LightState& lightState) { - String state = root["state"]; - // parse new led state - boolean newState = false; - if (state.equals(ON_STATE)) { - newState = true; - } else if (!state.equals(OFF_STATE)) { - return StateUpdateResult::ERROR; - } - // change the new state, if required - if (lightState.ledOn != newState) { - lightState.ledOn = newState; - return StateUpdateResult::CHANGED; - } - return StateUpdateResult::UNCHANGED; - } -}; - -class LightStateService : public StatefulService { - public: - LightStateService(AsyncWebServer* server, - SecurityManager* securityManager, - AsyncMqttClient* mqttClient, - LightMqttSettingsService* lightMqttSettingsService); - void begin(); - - private: - HttpEndpoint _httpEndpoint; - MqttPubSub _mqttPubSub; - WebSocketTxRx _webSocket; - AsyncMqttClient* _mqttClient; - LightMqttSettingsService* _lightMqttSettingsService; - - void registerConfig(); - void onConfigUpdated(); -}; - -#endif diff --git a/src/main.cpp b/src/main.cpp index 0b1081a4..95cc6017 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,17 +1,9 @@ #include -#include -#include #define SERIAL_BAUD_RATE 115200 AsyncWebServer server(80); ESP8266React esp8266React(&server); -LightMqttSettingsService lightMqttSettingsService = - LightMqttSettingsService(&server, esp8266React.getFS(), esp8266React.getSecurityManager()); -LightStateService lightStateService = LightStateService(&server, - esp8266React.getSecurityManager(), - esp8266React.getMqttClient(), - &lightMqttSettingsService); void setup() { // start serial and filesystem @@ -20,12 +12,6 @@ void setup() { // start the framework and demo project esp8266React.begin(); - // load the initial light settings - lightStateService.begin(); - - // start the light service - lightMqttSettingsService.begin(); - // start the server server.begin(); } From 5d98f12c93d049f275689362903c8547275909a4 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Fri, 13 Nov 2020 21:55:16 +0000 Subject: [PATCH 02/52] messing around with audio leds --- lib/framework/FSPersistence.h | 2 +- platformio.ini | 2 +- src/AudioLightMode.h | 38 +++++++++++++++++++++++++++-------- src/ColorMode.cpp | 29 ++++++++++++++++++++------ src/ColorMode.h | 29 +++++++++++++------------- src/FrequencySampler.cpp | 26 +++++++++++++----------- src/FrequencySampler.h | 22 +++++++++++++++++--- src/LedSettingsService.cpp | 9 ++++----- src/LedSettingsService.h | 11 ++++++++-- 9 files changed, 116 insertions(+), 52 deletions(-) diff --git a/lib/framework/FSPersistence.h b/lib/framework/FSPersistence.h index bba8cda1..7309661e 100644 --- a/lib/framework/FSPersistence.h +++ b/lib/framework/FSPersistence.h @@ -11,7 +11,7 @@ class FSPersistence { JsonStateUpdater stateUpdater, StatefulService* statefulService, FS* fs, - char const* filePath, + const char* filePath, size_t bufferSize = DEFAULT_BUFFER_SIZE) : _stateReader(stateReader), _stateUpdater(stateUpdater), diff --git a/platformio.ini b/platformio.ini index 07340723..1bcd02ff 100644 --- a/platformio.ini +++ b/platformio.ini @@ -29,7 +29,7 @@ framework = arduino monitor_speed = 115200 extra_scripts = - pre:scripts/build_interface.py +; pre:scripts/build_interface.py lib_deps = ArduinoJson@>=6.0.0,<7.0.0 diff --git a/src/AudioLightMode.h b/src/AudioLightMode.h index b2187688..cda423c3 100644 --- a/src/AudioLightMode.h +++ b/src/AudioLightMode.h @@ -4,31 +4,47 @@ #include #include #include +#include + +#define AUDIO_LIGHT_MODE_FILE_PATH_PREFIX "/modes/" +#define AUDIO_LIGHT_MODE_FILE_PATH_SUFFIX ".json" +#define AUDIO_LIGHT_MODE_SERVICE_PATH_PREFIX "/rest/modes/" template class AudioLightMode : public StatefulService { protected: + String _id; LedSettingsService* _ledSettingsService; - HttpEndpoint _httpEndpoint; - FSPersistence _fsPersistence; + FrequencySampler* _frequencySampler; + HttpEndpoint _httpEndpoint; + FSPersistence _fsPersistence; public: - // TODO - replace the two strings with a single id - string template or concatication? AudioLightMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler, JsonStateReader stateReader, JsonStateUpdater stateUpdater, - const String& settingsServicePath, - const String& setttingsFile) : - _httpEndpoint(stateReader, stateUpdater, this, server, settingsServicePath, securityManager), - _fsPersistence(stateReader, stateUpdater, this, fs, setttingsFile) { + const String& id) : + _id(id), + _ledSettingsService(ledSettingsService), + _frequencySampler(frequencySampler), + _httpEndpoint(stateReader, stateUpdater, this, server, AUDIO_LIGHT_MODE_FILE_PATH_PREFIX + id, securityManager), + _fsPersistence(stateReader, + stateUpdater, + this, + fs, + String(AUDIO_LIGHT_MODE_FILE_PATH_PREFIX + id + AUDIO_LIGHT_MODE_FILE_PATH_SUFFIX).c_str()) { } /* * Get the code for the mode as a string */ - virtual String getId() = 0; + const String& getId() { + return _id; + } /* * Allow the mode to animate the LEDs - called by the main loop. @@ -40,6 +56,12 @@ class AudioLightMode : public StatefulService { */ virtual void enable() = 0; + /** + * Called when new frequency data is sampled so the mode can update as required. + * + * Will only be called for the active mode. + */ + virtual void sampleComplete(){}; }; #endif // end AudioLightMode_h diff --git a/src/ColorMode.cpp b/src/ColorMode.cpp index f32921fa..afe8d682 100644 --- a/src/ColorMode.cpp +++ b/src/ColorMode.cpp @@ -1,17 +1,34 @@ #include -String ColorMode::getId() { - return "color"; -} +ColorMode::ColorMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler) : + AudioLightMode(server, + fs, + securityManager, + ledSettingsService, + frequencySampler, + ColorModeSettings::read, + ColorModeSettings::update, + COLOR_MODE_ID) { + addUpdateHandler([&](const String& originId) { enable(); }, false); +}; void ColorMode::enable() { _refresh = true; } void ColorMode::tick() { - if (_refresh || _audioEnabled) { - fill_solid(_leds, _numLeds, _color); - _ledController->showLeds(_audioEnabled ? calculateEnergyFloat(_includedBands) * _brightness : _brightness); + if (_refresh || _state.audioEnabled) { + _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + FrequencyData* frequencyData = _frequencySampler->getFrequencyData(); + fill_solid(leds, numLeds, _state.color); + ledController->showLeds(_state.audioEnabled + ? frequencyData->calculateEnergyFloat(_state.includedBands) * _state.brightness + : _state.brightness); + }); _refresh = false; } } diff --git a/src/ColorMode.h b/src/ColorMode.h index 79daf043..d116031b 100644 --- a/src/ColorMode.h +++ b/src/ColorMode.h @@ -13,26 +13,32 @@ #define FACTORY_COLOR_MODE_AUDIO_ENABLED false #endif -#define COLOR_FILE_PATH "/modes/color.json" -#define COLOR_SERVICE_PATH "/rest/modes/color.json" +#ifndef FACTORY_COLOR_MODE_BRIGHTNESS +#define FACTORY_COLOR_MODE_BRIGHTNESS 128 +#endif + +#define COLOR_MODE_ID "color" class ColorModeSettings { public: ColorModeSettings() { } - CRGB color = FACTORY_COLOR_MODE_COLOR; - bool audioEnabled = FACTORY_COLOR_MODE_AUDIO_ENABLED; + CRGB color; + uint8_t brightness; + bool audioEnabled; bool includedBands[NUM_BANDS]; static void read(ColorModeSettings& settings, JsonObject& root) { writeColorToJson(root, &settings.color); + writeByteToJson(root, &settings.brightness, "brightness"); writeBoolToJson(root, &settings.audioEnabled, "audio_enabled"); writeBooleanArrayToJson(root, settings.includedBands, NUM_BANDS, "included_bands"); } static StateUpdateResult update(JsonObject& root, ColorModeSettings& settings) { updateColorFromJson(root, &settings.color, FACTORY_COLOR_MODE_COLOR); + updateByteFromJson(root, &settings.brightness, FACTORY_COLOR_MODE_BRIGHTNESS, "brightness"); updateBoolFromJson(root, &settings.audioEnabled, FACTORY_COLOR_MODE_AUDIO_ENABLED, "audio_enabled"); updateBooleanArrayFromJson(root, settings.includedBands, NUM_BANDS, "included_bands"); return StateUpdateResult::CHANGED; @@ -44,16 +50,11 @@ class ColorMode : public AudioLightMode { boolean _refresh = true; public: - ColorMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : - AudioLightMode(server, - fs, - securityManager, - ColorModeSettings::read, - ColorModeSettings::update, - COLOR_SERVICE_PATH, - COLOR_FILE_PATH) { - addUpdateHandler([&](const String& originId) { enable(); }, false); - }; + ColorMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler); String getId(); void tick(); void enable(); diff --git a/src/FrequencySampler.cpp b/src/FrequencySampler.cpp index 4ca00cc7..9e445519 100644 --- a/src/FrequencySampler.cpp +++ b/src/FrequencySampler.cpp @@ -27,20 +27,18 @@ void FrequencySampler::loop() { delayMicroseconds(36); // read frequency for pin - frequencyData.bands[i] = analogRead(FREQUENCY_SAMPLER_ANALOG_PIN); + uint16_t value = analogRead(FREQUENCY_SAMPLER_ANALOG_PIN); // re-map frequency to eliminate low level noise - frequencyData.bands[i] = frequencyData.bands[i] > FREQUENCY_SAMPLER_DEAD_ZONE - ? map(frequencyData.bands[i] - FREQUENCY_SAMPLER_DEAD_ZONE, - 0, - ADC_MAX_VALUE - FREQUENCY_SAMPLER_DEAD_ZONE, - 0, - ADC_MAX_VALUE) - : 0; - - // crappy rolling values - frequencyData.rolling[i] = - _rollingAverageFactor * frequencyData.bands[i] + (1 - _rollingAverageFactor) * frequencyData.rolling[i]; + value = value > FREQUENCY_SAMPLER_DEAD_ZONE ? map(value - FREQUENCY_SAMPLER_DEAD_ZONE, + 0, + ADC_MAX_VALUE - FREQUENCY_SAMPLER_DEAD_ZONE, + 0, + ADC_MAX_VALUE) + : 0; + + // crappy smoothing to avoid crazy flickering + frequencyData.bands[i] = _smoothingFactor * frequencyData.bands[i] + (1 - _smoothingFactor) * value; // strobe pin high again for next loop digitalWrite(FREQUENCY_SAMPLER_STROBE_PIN, HIGH); @@ -53,3 +51,7 @@ void FrequencySampler::loop() { "loop"); } } + +FrequencyData* FrequencySampler::getFrequencyData() { + return &_state; +} diff --git a/src/FrequencySampler.h b/src/FrequencySampler.h index 632de2a5..dbbb7f85 100644 --- a/src/FrequencySampler.h +++ b/src/FrequencySampler.h @@ -9,7 +9,7 @@ #define FREQUENCY_SAMPLER_RESET_PIN 4 #define FREQUENCY_SAMPLER_STROBE_PIN 5 #define FREQUENCY_SAMPLER_ANALOG_PIN 36 -#define FREQUENCY_SAMPLER_DEFAULT_ROLLING_AVG_FACTOR 0.15 +#define FREQUENCY_SAMPLER_DEFAULT_SMOOTHING_FACTOR 0.15 #define NUM_BANDS 7 #define ADC_MAX_VALUE 4096 @@ -17,16 +17,32 @@ class FrequencyData { public: uint16_t bands[NUM_BANDS]; - uint16_t rolling[NUM_BANDS]; + + float calculateEnergyFloat(bool* includedBands = NULL) { + uint16_t currentLevel = 0; + uint16_t numBands = 0; + for (uint16_t i = 0; i < NUM_BANDS; i++) { + if (includedBands == NULL || includedBands[i]) { + currentLevel += bands[i]; + numBands++; + } + } + return (float)currentLevel / (NUM_BANDS * ADC_MAX_VALUE); + } + + uint8_t calculateEnergyByte(bool* includedBands = NULL) { + return calculateEnergyFloat(includedBands) * 255; + } }; class FrequencySampler : public StatefulService { public: void begin(); void loop(); + FrequencyData* getFrequencyData(); private: - float _rollingAverageFactor = FREQUENCY_SAMPLER_DEFAULT_ROLLING_AVG_FACTOR; + float _smoothingFactor = FREQUENCY_SAMPLER_DEFAULT_SMOOTHING_FACTOR; }; #endif // end FrequencySampler_h diff --git a/src/LedSettingsService.cpp b/src/LedSettingsService.cpp index c51200eb..8f959c4d 100644 --- a/src/LedSettingsService.cpp +++ b/src/LedSettingsService.cpp @@ -10,7 +10,7 @@ LedSettingsService::LedSettingsService(AsyncWebServer* server, FS* fs, SecurityM AuthenticationPredicates::IS_AUTHENTICATED), _fsPersistence(LedSettings::read, LedSettings::update, this, fs, LED_SETTINGS_FILE) { _ledController = &FastLED.addLeds(_leds, NUM_LEDS); - addUpdateHandler([&](const String& originId) { showLeds(); }, false); + addUpdateHandler([&](const String& originId) { configureLeds(); }, false); } void LedSettingsService::begin() { @@ -18,10 +18,9 @@ void LedSettingsService::begin() { } void LedSettingsService::update(LedUpdateCallback updateCallback) { - updateCallback(NUM_LEDS, _leds); - showLeds(); + updateCallback(_leds, _ledController, NUM_LEDS); } -void LedSettingsService::showLeds() { - _ledController->showLeds(_state.brightness); +void LedSettingsService::configureLeds() { + FastLED.setBrightness(_state.brightness); } diff --git a/src/LedSettingsService.h b/src/LedSettingsService.h index 5ab2b4e4..a9d085b1 100644 --- a/src/LedSettingsService.h +++ b/src/LedSettingsService.h @@ -18,7 +18,11 @@ #define FACTORY_LED_BRIGHTNESS 128 #endif -typedef std::function LedUpdateCallback; +#ifndef FACTORY_LED_SMOOTHING_FACTOR +#define FACTORY_LED_SMOOTHING_FACTOR 0.15 +#endif + +typedef std::function LedUpdateCallback; // should be extended to allow number of LEDS to be modified // should be extended to allow leds to be switched off @@ -27,13 +31,16 @@ typedef std::function LedUpdateCallba class LedSettings { public: uint8_t brightness; + float smoothingFactor; static void read(LedSettings& settings, JsonObject& root) { root["brightness"] = settings.brightness; + root["smoothing_factor"] = settings.smoothingFactor; } static StateUpdateResult update(JsonObject& root, LedSettings& settings) { settings.brightness = root["brightness"] | FACTORY_LED_BRIGHTNESS; + settings.smoothingFactor = root["smoothing_factor"] | FACTORY_LED_SMOOTHING_FACTOR; return StateUpdateResult::CHANGED; } }; @@ -44,13 +51,13 @@ class LedSettingsService : public StatefulService { void begin(); void update(LedUpdateCallback updateCallback); - void showLeds(); private: CRGB _leds[NUM_LEDS]; CLEDController* _ledController; HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; + void configureLeds(); }; #endif // end LedSettingsService_h From 6cf9c4b509c2ea46b385c78e941c95419400a92a Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Fri, 13 Nov 2020 23:31:37 +0000 Subject: [PATCH 03/52] rework audio light settings service --- src/AudioLightMode.h | 41 ++++++++++----- src/AudioLightSettingsService.cpp | 85 +++++++++++++++++++++++++++++++ src/AudioLightSettingsService.h | 53 +++++++++++++++++++ src/ColorMode.cpp | 2 +- src/ColorMode.h | 4 +- 5 files changed, 169 insertions(+), 16 deletions(-) create mode 100644 src/AudioLightSettingsService.cpp create mode 100644 src/AudioLightSettingsService.h diff --git a/src/AudioLightMode.h b/src/AudioLightMode.h index cda423c3..f1a277a2 100644 --- a/src/AudioLightMode.h +++ b/src/AudioLightMode.h @@ -10,8 +10,19 @@ #define AUDIO_LIGHT_MODE_FILE_PATH_SUFFIX ".json" #define AUDIO_LIGHT_MODE_SERVICE_PATH_PREFIX "/rest/modes/" +class AudioLightMode { + public: + virtual const String& getId() = 0; + virtual void begin() = 0; + virtual void readFromFS() = 0; + virtual void writeToFS() = 0; + virtual void tick() = 0; + virtual void enable() = 0; + virtual void sampleComplete(){}; +}; + template -class AudioLightMode : public StatefulService { +class AudioLightModeImpl : public StatefulService, public AudioLightMode { protected: String _id; LedSettingsService* _ledSettingsService; @@ -20,7 +31,7 @@ class AudioLightMode : public StatefulService { FSPersistence _fsPersistence; public: - AudioLightMode(AsyncWebServer* server, + AudioLightModeImpl(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, @@ -45,23 +56,27 @@ class AudioLightMode : public StatefulService { const String& getId() { return _id; } - /* - * Allow the mode to animate the LEDs - called by the main loop. + * Read the config from the file system and disable the update handler */ - virtual void tick() = 0; + void begin() { + _fsPersistence.disableUpdateHandler(); + _fsPersistence.readFromFS(); + } - /** - * Called when the mode is enabled. + /* + * Load the mode config from the file system */ - virtual void enable() = 0; + void readFromFS() { + _fsPersistence.readFromFS(); + } - /** - * Called when new frequency data is sampled so the mode can update as required. - * - * Will only be called for the active mode. + /* + * Save the mode config to the file system */ - virtual void sampleComplete(){}; + void writeToFS() { + _fsPersistence.writeToFS(); + } }; #endif // end AudioLightMode_h diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp new file mode 100644 index 00000000..90917a83 --- /dev/null +++ b/src/AudioLightSettingsService.cpp @@ -0,0 +1,85 @@ +#include + +void junkBodyHandler(AsyncWebServerRequest* request, + String filename, + size_t index, + uint8_t* data, + size_t len, + bool final) { +} + +AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler) : + _httpEndpoint(AudioLightSettings::read, + AudioLightSettings::update, + this, + server, + AUDIO_LIGHT_SERVICE_PATH, + securityManager, + AuthenticationPredicates::IS_AUTHENTICATED) { + server->on(AUDIO_LIGHT_SAVE_MODE_PATH, + HTTP_POST, + std::bind(&AudioLightSettingsService::saveModeConfig, this, std::placeholders::_1), + junkBodyHandler); + server->on(AUDIO_LIGHT_LOAD_MODE_PATH, + HTTP_POST, + std::bind(&AudioLightSettingsService::loadModeConfig, this, std::placeholders::_1), + junkBodyHandler); + frequencySampler->addUpdateHandler([&](const String& originId) { sampleComplete(); }, false); + _modes[0] = new ColorMode(server, fs, securityManager, ledSettingsService, frequencySampler); + _currentMode = _modes[0]; +} + +void AudioLightSettingsService::begin() { + for (uint8_t i = 0; i < NUM_MODES; i++) { + _modes[i]->begin(); + } +} + +void AudioLightSettingsService::loop() { + _currentMode->tick(); +} + +AudioLightMode* AudioLightSettingsService::getMode(const String& modeId) { + for (uint8_t i = 0; i < NUM_MODES; i++) { + AudioLightMode* mode = _modes[i]; + if (mode->getId() == modeId) { + return mode; + } + } + return nullptr; +} + +void AudioLightSettingsService::sampleComplete() { + _currentMode->sampleComplete(); +} + +StateUpdateResult AudioLightSettingsService::update(JsonObject& root, AudioLightSettings& settings) { + String modeId = root["mode_id"] | AUDIO_LIGHT_DEFAULT_MODE; + if (settings.modeId == modeId) { + return StateUpdateResult::UNCHANGED; + } + + AudioLightMode* mode = getMode(modeId); + if (!mode) { + return StateUpdateResult::ERROR; + } + + settings.modeId = modeId; + _currentMode = mode; + _currentMode->enable(); +} + +void AudioLightSettingsService::saveModeConfig(AsyncWebServerRequest* request) { + _currentMode->writeToFS(); + request->send(200, "text/plain", "Saved"); +} + +void AudioLightSettingsService::loadModeConfig(AsyncWebServerRequest* request) { + _currentMode->readFromFS(); + _currentMode->enable(); + request->send(200, "text/plain", "Loaded"); +} diff --git a/src/AudioLightSettingsService.h b/src/AudioLightSettingsService.h new file mode 100644 index 00000000..f51f161c --- /dev/null +++ b/src/AudioLightSettingsService.h @@ -0,0 +1,53 @@ +#ifndef AudioLightSettingsService_h +#define AudioLightSettingsService_h + +#include +#include +#include + +#define NUM_MODES 1 + +#define AUDIO_LIGHT_SERVICE_PATH "/rest/mode" +#define AUDIO_LIGHT_SAVE_MODE_PATH "/rest/mode/save" +#define AUDIO_LIGHT_LOAD_MODE_PATH "/rest/mode/load" + +#define AUDIO_LIGHT_DEFAULT_MODE "color" + +class AudioLightSettings { + public: + String modeId; + + static void read(AudioLightSettings& settings, JsonObject& root) { + root["mode_id"] = settings.modeId; + } + + static StateUpdateResult update(JsonObject& root, AudioLightSettings& settings) { + settings.modeId = root["mode_id"] | AUDIO_LIGHT_DEFAULT_MODE; + return StateUpdateResult::CHANGED; + } +}; + +class AudioLightSettingsService : public StatefulService { + public: + AudioLightSettingsService(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler); + + void begin(); + void loop(); + + private: + HttpEndpoint _httpEndpoint; + AudioLightMode* _modes[NUM_MODES]; + AudioLightMode* _currentMode; + + StateUpdateResult update(JsonObject& root, AudioLightSettings& settings); + AudioLightMode* getMode(const String& modeId); + void sampleComplete(); + void saveModeConfig(AsyncWebServerRequest* request); + void loadModeConfig(AsyncWebServerRequest* request); +}; + +#endif // end AudioLightSettingsService_h \ No newline at end of file diff --git a/src/ColorMode.cpp b/src/ColorMode.cpp index afe8d682..482f7cdc 100644 --- a/src/ColorMode.cpp +++ b/src/ColorMode.cpp @@ -5,7 +5,7 @@ ColorMode::ColorMode(AsyncWebServer* server, SecurityManager* securityManager, LedSettingsService* ledSettingsService, FrequencySampler* frequencySampler) : - AudioLightMode(server, + AudioLightModeImpl(server, fs, securityManager, ledSettingsService, diff --git a/src/ColorMode.h b/src/ColorMode.h index d116031b..afa795eb 100644 --- a/src/ColorMode.h +++ b/src/ColorMode.h @@ -45,7 +45,7 @@ class ColorModeSettings { } }; -class ColorMode : public AudioLightMode { +class ColorMode : public AudioLightModeImpl { private: boolean _refresh = true; @@ -55,7 +55,7 @@ class ColorMode : public AudioLightMode { SecurityManager* securityManager, LedSettingsService* ledSettingsService, FrequencySampler* frequencySampler); - String getId(); + const String& getId(); void tick(); void enable(); }; From 71e58f3c2a220314f7c8679322515071fd8b48e6 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Fri, 13 Nov 2020 23:35:24 +0000 Subject: [PATCH 04/52] fix update function --- src/AudioLightSettingsService.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index 90917a83..55df6997 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -71,6 +71,7 @@ StateUpdateResult AudioLightSettingsService::update(JsonObject& root, AudioLight settings.modeId = modeId; _currentMode = mode; _currentMode->enable(); + return StateUpdateResult::CHANGED; } void AudioLightSettingsService::saveModeConfig(AsyncWebServerRequest* request) { From 886d3587ee9f7fc43a7e838c7c19370996524fa9 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sat, 14 Nov 2020 16:50:51 +0000 Subject: [PATCH 05/52] add second mode --- src/AudioLightMode.h | 4 +- src/AudioLightSettingsService.cpp | 31 ++++++++------ src/AudioLightSettingsService.h | 16 ++++--- src/ColorMode.h | 4 -- src/FrequencySampler.h | 2 +- src/LedSettingsService.h | 8 +++- src/RainbowMode.cpp | 55 ++++++++++++++++++++++++ src/RainbowMode.h | 70 +++++++++++++++++++++++++++++++ src/main.cpp | 26 ++++++++++++ 9 files changed, 185 insertions(+), 31 deletions(-) create mode 100644 src/RainbowMode.cpp create mode 100644 src/RainbowMode.h diff --git a/src/AudioLightMode.h b/src/AudioLightMode.h index f1a277a2..143c2114 100644 --- a/src/AudioLightMode.h +++ b/src/AudioLightMode.h @@ -1,8 +1,6 @@ #ifndef AudioLightMode_h #define AudioLightMode_h -#include -#include #include #include @@ -42,7 +40,7 @@ class AudioLightModeImpl : public StatefulService, public AudioLightMode { _id(id), _ledSettingsService(ledSettingsService), _frequencySampler(frequencySampler), - _httpEndpoint(stateReader, stateUpdater, this, server, AUDIO_LIGHT_MODE_FILE_PATH_PREFIX + id, securityManager), + _httpEndpoint(stateReader, stateUpdater, this, server, AUDIO_LIGHT_MODE_SERVICE_PATH_PREFIX + id, securityManager), _fsPersistence(stateReader, stateUpdater, this, diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index 55df6997..71143ea3 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -14,7 +14,7 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, LedSettingsService* ledSettingsService, FrequencySampler* frequencySampler) : _httpEndpoint(AudioLightSettings::read, - AudioLightSettings::update, + std::bind(&AudioLightSettingsService::update, this, std::placeholders::_1, std::placeholders::_2), this, server, AUDIO_LIGHT_SERVICE_PATH, @@ -29,18 +29,23 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, std::bind(&AudioLightSettingsService::loadModeConfig, this, std::placeholders::_1), junkBodyHandler); frequencySampler->addUpdateHandler([&](const String& originId) { sampleComplete(); }, false); + addUpdateHandler([&](const String& originId) { modeChanged(); }, false); _modes[0] = new ColorMode(server, fs, securityManager, ledSettingsService, frequencySampler); - _currentMode = _modes[0]; + _modes[1] = new RainbowMode(server, fs, securityManager, ledSettingsService, frequencySampler); } void AudioLightSettingsService::begin() { + // configure current mode + _state.currentMode = _modes[0]; + + // initialize all modes for (uint8_t i = 0; i < NUM_MODES; i++) { _modes[i]->begin(); } } void AudioLightSettingsService::loop() { - _currentMode->tick(); + _state.currentMode->tick(); } AudioLightMode* AudioLightSettingsService::getMode(const String& modeId) { @@ -53,34 +58,34 @@ AudioLightMode* AudioLightSettingsService::getMode(const String& modeId) { return nullptr; } +void AudioLightSettingsService::modeChanged() { + _state.currentMode->enable(); +} + void AudioLightSettingsService::sampleComplete() { - _currentMode->sampleComplete(); + _state.currentMode->sampleComplete(); } StateUpdateResult AudioLightSettingsService::update(JsonObject& root, AudioLightSettings& settings) { String modeId = root["mode_id"] | AUDIO_LIGHT_DEFAULT_MODE; - if (settings.modeId == modeId) { + if (settings.currentMode->getId() == modeId) { return StateUpdateResult::UNCHANGED; } - AudioLightMode* mode = getMode(modeId); if (!mode) { return StateUpdateResult::ERROR; } - - settings.modeId = modeId; - _currentMode = mode; - _currentMode->enable(); + settings.currentMode = mode; return StateUpdateResult::CHANGED; } void AudioLightSettingsService::saveModeConfig(AsyncWebServerRequest* request) { - _currentMode->writeToFS(); + _state.currentMode->writeToFS(); request->send(200, "text/plain", "Saved"); } void AudioLightSettingsService::loadModeConfig(AsyncWebServerRequest* request) { - _currentMode->readFromFS(); - _currentMode->enable(); + _state.currentMode->readFromFS(); + _state.currentMode->enable(); request->send(200, "text/plain", "Loaded"); } diff --git a/src/AudioLightSettingsService.h b/src/AudioLightSettingsService.h index f51f161c..6758f97a 100644 --- a/src/AudioLightSettingsService.h +++ b/src/AudioLightSettingsService.h @@ -4,8 +4,9 @@ #include #include #include +#include -#define NUM_MODES 1 +#define NUM_MODES 2 #define AUDIO_LIGHT_SERVICE_PATH "/rest/mode" #define AUDIO_LIGHT_SAVE_MODE_PATH "/rest/mode/save" @@ -15,15 +16,12 @@ class AudioLightSettings { public: - String modeId; + AudioLightMode* currentMode = nullptr; static void read(AudioLightSettings& settings, JsonObject& root) { - root["mode_id"] = settings.modeId; - } - - static StateUpdateResult update(JsonObject& root, AudioLightSettings& settings) { - settings.modeId = root["mode_id"] | AUDIO_LIGHT_DEFAULT_MODE; - return StateUpdateResult::CHANGED; + if (settings.currentMode) { + root["mode_id"] = settings.currentMode->getId(); + } } }; @@ -41,10 +39,10 @@ class AudioLightSettingsService : public StatefulService { private: HttpEndpoint _httpEndpoint; AudioLightMode* _modes[NUM_MODES]; - AudioLightMode* _currentMode; StateUpdateResult update(JsonObject& root, AudioLightSettings& settings); AudioLightMode* getMode(const String& modeId); + void modeChanged(); void sampleComplete(); void saveModeConfig(AsyncWebServerRequest* request); void loadModeConfig(AsyncWebServerRequest* request); diff --git a/src/ColorMode.h b/src/ColorMode.h index afa795eb..94e73cd6 100644 --- a/src/ColorMode.h +++ b/src/ColorMode.h @@ -21,9 +21,6 @@ class ColorModeSettings { public: - ColorModeSettings() { - } - CRGB color; uint8_t brightness; bool audioEnabled; @@ -55,7 +52,6 @@ class ColorMode : public AudioLightModeImpl { SecurityManager* securityManager, LedSettingsService* ledSettingsService, FrequencySampler* frequencySampler); - const String& getId(); void tick(); void enable(); }; diff --git a/src/FrequencySampler.h b/src/FrequencySampler.h index dbbb7f85..0e26c34e 100644 --- a/src/FrequencySampler.h +++ b/src/FrequencySampler.h @@ -1,9 +1,9 @@ #ifndef FrequencySampler_h #define FrequencySampler_h +#include #include #include -#include #define FREQUENCY_SAMPLER_DEAD_ZONE 700 #define FREQUENCY_SAMPLER_RESET_PIN 4 diff --git a/src/LedSettingsService.h b/src/LedSettingsService.h index a9d085b1..cc87d639 100644 --- a/src/LedSettingsService.h +++ b/src/LedSettingsService.h @@ -8,11 +8,17 @@ #include #include #include - +/* #define LED_DATA_PIN 21 #define COLOR_ORDER GRB #define LED_TYPE WS2812 #define NUM_LEDS 64 +*/ + +#define LED_DATA_PIN 21 +#define COLOR_ORDER GRB // GBR +#define LED_TYPE WS2812B +#define NUM_LEDS 9 #ifndef FACTORY_LED_BRIGHTNESS #define FACTORY_LED_BRIGHTNESS 128 diff --git a/src/RainbowMode.cpp b/src/RainbowMode.cpp new file mode 100644 index 00000000..8880add1 --- /dev/null +++ b/src/RainbowMode.cpp @@ -0,0 +1,55 @@ +#include + +RainbowMode::RainbowMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler) : + AudioLightModeImpl(server, + fs, + securityManager, + ledSettingsService, + frequencySampler, + RainbowModeSettings::read, + RainbowModeSettings::update, + RAINBOW_MODE_ID) { + _ledsPerBand = NUM_LEDS / NUM_BANDS; + _remainingLeds = NUM_LEDS % NUM_BANDS; +}; + +void RainbowMode::enable() { + _lastFrameMicros = micros(); +} + +void RainbowMode::tick() { + _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + FrequencyData* frequencyData = _frequencySampler->getFrequencyData(); + + // rotate hue in time based manner + if (_state.rotateSpeed > 0) { + unsigned long rotateDelayMicros = 1000000 / _state.rotateSpeed; + unsigned long currentMicros = micros(); + unsigned long microsElapsed = (unsigned long)(currentMicros - _lastFrameMicros); + if (microsElapsed >= rotateDelayMicros) { + _lastFrameMicros = currentMicros; + _initialhue++; + } + } + + // fill the rainbow + fill_rainbow(leds, numLeds, _initialhue, _state.hueDelta); + + // fade each segment if audio-enabled + if (_state.audioEnabled) { + CRGB* startLed = leds; + for (uint8_t i = 0; i < NUM_BANDS; i++) { + uint16_t numLeds = _ledsPerBand + (i == NUM_BANDS - 1 ? _remainingLeds : 0); + fadeToBlackBy(startLed, numLeds, 255 - map(frequencyData->bands[i], 0, ADC_MAX_VALUE, 0, 255)); + startLed += _ledsPerBand; + } + } + + // update the leds + ledController->showLeds(_state.brightness); + }); +} diff --git a/src/RainbowMode.h b/src/RainbowMode.h new file mode 100644 index 00000000..fa72d43a --- /dev/null +++ b/src/RainbowMode.h @@ -0,0 +1,70 @@ +#ifndef RAINBOW_MODE_H +#define RAINBOW_MODE_H + +#include +#include + +#ifndef FACTORY_RAINBOW_MODE_HUE_DELTA +#define FACTORY_RAINBOW_MODE_HUE_DELTA 250 +#endif + +#ifndef FACTORY_RAINBOW_MODE_BRIGHTNESS +#define FACTORY_RAINBOW_MODE_BRIGHTNESS 128 +#endif + +#ifndef FACTORY_RAINBOW_MODE_ROTATE_SPEED +#define FACTORY_RAINBOW_MODE_ROTATE_SPEED 32 +#endif + +#ifndef FACTORY_RAINBOW_MODE_AUDIO_ENABLED +#define FACTORY_RAINBOW_MODE_AUDIO_ENABLED false +#endif + +#define RAINBOW_MODE_ID "rainbow" + +class RainbowModeSettings { + public: + uint8_t brightness; + uint8_t rotateSpeed; + bool audioEnabled; + uint8_t hueDelta; + + static void read(RainbowModeSettings& settings, JsonObject& root) { + writeByteToJson(root, &settings.brightness, "brightness"); + writeByteToJson(root, &settings.rotateSpeed, "rotate_speed"); + writeBoolToJson(root, &settings.audioEnabled, "audio_enabled"); + writeByteToJson(root, &settings.hueDelta, "hue_delta"); + } + + static StateUpdateResult update(JsonObject& root, RainbowModeSettings& settings) { + updateByteFromJson(root, &settings.brightness, FACTORY_RAINBOW_MODE_BRIGHTNESS, "brightness"); + updateByteFromJson(root, &settings.rotateSpeed, FACTORY_RAINBOW_MODE_ROTATE_SPEED, "rotate_speed"); + updateBoolFromJson(root, &settings.audioEnabled, FACTORY_RAINBOW_MODE_AUDIO_ENABLED, "audio_enabled"); + updateByteFromJson(root, &settings.hueDelta, FACTORY_RAINBOW_MODE_HUE_DELTA, "hue_delta"); + return StateUpdateResult::CHANGED; + } +}; + +class RainbowMode : public AudioLightModeImpl { + private: + // various state and settings for rainbow mode + uint16_t _ledsPerBand; + uint16_t _remainingLeds; + + // delay state for rotation + unsigned long _lastFrameMicros = 0; + + uint8_t _initialhue = 0; + boolean _refresh = true; + + public: + RainbowMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler); + void tick(); + void enable(); +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index 95cc6017..82193460 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,21 @@ #include +#include +#include +#include #define SERIAL_BAUD_RATE 115200 AsyncWebServer server(80); ESP8266React esp8266React(&server); +LedSettingsService ledSettingsService(&server, esp8266React.getFS(), esp8266React.getSecurityManager()); +FrequencySampler frequencySampler; +AudioLightSettingsService audioLightSettingsService(&server, + esp8266React.getFS(), + esp8266React.getSecurityManager(), + &ledSettingsService, + &frequencySampler); + void setup() { // start serial and filesystem Serial.begin(SERIAL_BAUD_RATE); @@ -14,9 +25,24 @@ void setup() { // start the server server.begin(); + + // set up the sampler + frequencySampler.begin(); + + // configure the LED strip + ledSettingsService.begin(); + + // load all of the defaults + audioLightSettingsService.begin(); } void loop() { // run the framework's loop function esp8266React.loop(); + + // allow the sampler to run if needed + frequencySampler.loop(); + + // refresh the LEDs + audioLightSettingsService.loop(); } From 9527bb8b7b1ed898edd1427b92765537d5ecad94 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sat, 14 Nov 2020 21:33:16 +0000 Subject: [PATCH 06/52] add lightning mode --- src/AudioLightSettingsService.cpp | 1 + src/AudioLightSettingsService.h | 3 +- src/ColorMode.h | 6 +-- src/FrequencySampler.h | 2 +- src/JsonUtil.cpp | 48 ++++++++--------- src/JsonUtil.h | 6 +-- src/LightningMode.cpp | 73 +++++++++++++++++++++++++ src/LightningMode.h | 88 +++++++++++++++++++++++++++++++ src/RainbowMode.h | 4 +- 9 files changed, 197 insertions(+), 34 deletions(-) create mode 100644 src/LightningMode.cpp create mode 100644 src/LightningMode.h diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index 71143ea3..77172934 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -32,6 +32,7 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, addUpdateHandler([&](const String& originId) { modeChanged(); }, false); _modes[0] = new ColorMode(server, fs, securityManager, ledSettingsService, frequencySampler); _modes[1] = new RainbowMode(server, fs, securityManager, ledSettingsService, frequencySampler); + _modes[2] = new LightningMode(server, fs, securityManager, ledSettingsService, frequencySampler); } void AudioLightSettingsService::begin() { diff --git a/src/AudioLightSettingsService.h b/src/AudioLightSettingsService.h index 6758f97a..f54882a3 100644 --- a/src/AudioLightSettingsService.h +++ b/src/AudioLightSettingsService.h @@ -5,8 +5,9 @@ #include #include #include +#include -#define NUM_MODES 2 +#define NUM_MODES 3 #define AUDIO_LIGHT_SERVICE_PATH "/rest/mode" #define AUDIO_LIGHT_SAVE_MODE_PATH "/rest/mode/save" diff --git a/src/ColorMode.h b/src/ColorMode.h index 94e73cd6..be602eaa 100644 --- a/src/ColorMode.h +++ b/src/ColorMode.h @@ -1,5 +1,5 @@ -#ifndef COLOR_MODE_H -#define COLOR_MODE_H +#ifndef ColorMode_h +#define ColorMode_h #include #include @@ -37,7 +37,7 @@ class ColorModeSettings { updateColorFromJson(root, &settings.color, FACTORY_COLOR_MODE_COLOR); updateByteFromJson(root, &settings.brightness, FACTORY_COLOR_MODE_BRIGHTNESS, "brightness"); updateBoolFromJson(root, &settings.audioEnabled, FACTORY_COLOR_MODE_AUDIO_ENABLED, "audio_enabled"); - updateBooleanArrayFromJson(root, settings.includedBands, NUM_BANDS, "included_bands"); + updateBooleanArrayFromJson(root, settings.includedBands, NUM_BANDS, true, "included_bands"); return StateUpdateResult::CHANGED; } }; diff --git a/src/FrequencySampler.h b/src/FrequencySampler.h index 0e26c34e..0d5df166 100644 --- a/src/FrequencySampler.h +++ b/src/FrequencySampler.h @@ -27,7 +27,7 @@ class FrequencyData { numBands++; } } - return (float)currentLevel / (NUM_BANDS * ADC_MAX_VALUE); + return (float)currentLevel / (numBands * ADC_MAX_VALUE); } uint8_t calculateEnergyByte(bool* includedBands = NULL) { diff --git a/src/JsonUtil.cpp b/src/JsonUtil.cpp index fabf1a99..1cfc101e 100644 --- a/src/JsonUtil.cpp +++ b/src/JsonUtil.cpp @@ -1,29 +1,29 @@ #include -void updateBooleanArrayFromJson(JsonObject& root, bool booleanArray[], uint16_t maxLen, String key){ - if (root.containsKey(key) && root[key].is()){ +void updateBooleanArrayFromJson(JsonObject& root, bool booleanArray[], uint16_t len, bool def, String key) { + if (root.containsKey(key) && root[key].is()) { JsonArray jsonArray = root[key]; - for (uint8_t i = 0; i < maxLen; i++) { + for (uint8_t i = 0; i < len; i++) { if (i < jsonArray.size() && jsonArray[i].is()) { booleanArray[i] = jsonArray[i]; - }else{ - booleanArray[i] = false; + } else { + booleanArray[i] = def; } } } } -void writeBooleanArrayToJson(JsonObject& root, bool booleanArray[], uint16_t len, String key){ +void writeBooleanArrayToJson(JsonObject& root, bool booleanArray[], uint16_t len, String key) { JsonArray array = root.createNestedArray(key); for (uint8_t i = 0; i < len; i++) { array.add(booleanArray[i]); } } -void updateColorFromJson(JsonObject& root, CRGB* color, CRGB def, String key){ - if (root.containsKey(key) && root[key].is()){ - String colorString = root[key]; - if (colorString.length() == 7){ +void updateColorFromJson(JsonObject& root, CRGB* color, CRGB def, String key) { + if (root.containsKey(key) && root[key].is()) { + String colorString = root[key]; + if (colorString.length() == 7) { *color = CRGB(strtoll(&colorString[1], NULL, 16)); } } else { @@ -31,32 +31,32 @@ void updateColorFromJson(JsonObject& root, CRGB* color, CRGB def, String key){ } } -void writeColorToJson(JsonObject& root, CRGB* color, String key){ +void writeColorToJson(JsonObject& root, CRGB* color, String key) { char colorString[8]; - sprintf(colorString,"#%02x%02x%02x", color->r, color->g, color->b); + sprintf(colorString, "#%02x%02x%02x", color->r, color->g, color->b); root[key] = colorString; } -void updateByteFromJson(JsonObject& root, uint8_t* value, uint8_t def, String key){ - if (root.containsKey(key) && root[key].is()){ - *value = (uint8_t) root[key]; - }else{ +void updateByteFromJson(JsonObject& root, uint8_t* value, uint8_t def, String key) { + if (root.containsKey(key) && root[key].is()) { + *value = (uint8_t)root[key]; + } else { *value = def; } } -void writeByteToJson(JsonObject& root, uint8_t* value, String key){ - root[key] = (uint8_t) *value; +void writeByteToJson(JsonObject& root, uint8_t* value, String key) { + root[key] = (uint8_t)*value; } -void updateBoolFromJson(JsonObject& root, bool* value, bool def, String key){ - if (root.containsKey(key) && root[key].is()){ - *value = (bool) root[key]; - }else{ +void updateBoolFromJson(JsonObject& root, bool* value, bool def, String key) { + if (root.containsKey(key) && root[key].is()) { + *value = (bool)root[key]; + } else { *value = def; } } -void writeBoolToJson(JsonObject& root, bool* value, String key){ - root[key] = (bool) *value; +void writeBoolToJson(JsonObject& root, bool* value, String key) { + root[key] = (bool)*value; } diff --git a/src/JsonUtil.h b/src/JsonUtil.h index ef346f35..55126787 100644 --- a/src/JsonUtil.h +++ b/src/JsonUtil.h @@ -1,10 +1,10 @@ -#ifndef COLOR_UTIL_H -#define COLOR_UTIL_H +#ifndef JsonUtil_h +#define JsonUtil_h #include #include -void updateBooleanArrayFromJson(JsonObject& root, bool booleanArray[], uint16_t maxSize, String key); +void updateBooleanArrayFromJson(JsonObject& root, bool booleanArray[], uint16_t len, bool def, String key); void writeBooleanArrayToJson(JsonObject& root, bool booleanArray[], uint16_t maxSize, String key); void updateColorFromJson(JsonObject& root, CRGB* color, CRGB def, String key = "color"); diff --git a/src/LightningMode.cpp b/src/LightningMode.cpp new file mode 100644 index 00000000..fa4d959d --- /dev/null +++ b/src/LightningMode.cpp @@ -0,0 +1,73 @@ +#include + +LightningMode::LightningMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler) : + AudioLightModeImpl(server, + fs, + securityManager, + ledSettingsService, + frequencySampler, + LightningModeSettings::read, + LightningModeSettings::update, + LIGHTNING_MODE_ID){}; + +void LightningMode::tick() { + _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + if (_refresh) { + _status = Status::IDLE; // assert idle mode + fill_solid(leds, numLeds, CHSV(255, 0, 0)); // clear leds + ledController->showLeds(); // render all leds black + _refresh = false; // clear refresh flag + return; // eager return + } + + if (_status == Status::TRIGGERED) { + _ledstart = random8(numLeds); // Determine starting location of flash + _ledlen = random8(numLeds - _ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) + _numFlashes = random8(3, _state.flashes); // Calculate the random number of flashes we will show + _flashCounter = 0; // The number of flashes we have shown + _status = Status::RUNNING; + } + + if (_status == Status::RUNNING && isWaitTimeElapsed()) { + _dimmer = (_flashCounter == 0) ? 5 : random8(1, 3); // leader scaled by a 5, return strokes brighter + fill_solid(leds + _ledstart, _ledlen, _state.color); // draw the flash + ledController->showLeds(_state.brightness / _dimmer); // show the flash + delay(random8(4, 10)); // wait a small amount of time (use non blocking delay?) + fill_solid(leds + _ledstart, _ledlen, CHSV(255, 0, 0)); // hide flash + ledController->showLeds(); // draw hidden leds + resetWaitTime(); // reset wait time for next flash + + // decrement flash counter, and reset to idle if done + if (++_flashCounter >= _numFlashes) { + _status = Status::IDLE; + } + } + }); +} + +void LightningMode::enable() { + _refresh = true; +} + +void LightningMode::sampleComplete() { + if (_status == Status::IDLE) { + FrequencyData* frequencyData = _frequencySampler->getFrequencyData(); + if (frequencyData->calculateEnergyByte(_state.includedBands) >= _state.threshold) { + _status = Status::TRIGGERED; + } + } +} + +bool LightningMode::isWaitTimeElapsed() { + unsigned long waitTimeElapsed = (unsigned long)(millis() - _waitStartedAt); + return waitTimeElapsed >= _waitDuration; +} + +void LightningMode::resetWaitTime() { + _waitStartedAt = millis(); // mark current time for the non blocking delay + _waitDuration = (_flashCounter == 0 ? 200 : 50) + random8(100); // set wait, lead flash longer (make configurable) +} diff --git a/src/LightningMode.h b/src/LightningMode.h new file mode 100644 index 00000000..969674a0 --- /dev/null +++ b/src/LightningMode.h @@ -0,0 +1,88 @@ +#ifndef LightningMode_h +#define LightningMode_h + +#include +#include + +#ifndef FACTORY_LIGHTNING_MODE_COLOR +#define FACTORY_LIGHTNING_MODE_COLOR CRGB::White +#endif + +#ifndef FACTORY_LIGHTNING_MODE_FLASHES +#define FACTORY_LIGHTNING_MODE_FLASHES 8 +#endif + +#ifndef FACTORY_LIGHTNING_MODE_THRESHOLD +#define FACTORY_LIGHTNING_MODE_THRESHOLD 128 +#endif + +#ifndef FACTORY_LIGHTNING_MODE_BRIGHTNESS +#define FACTORY_LIGHTNING_MODE_BRIGHTNESS 255 +#endif + +#ifndef FACTORY_LIGHTNING_AUDIO_ENABLED +#define FACTORY_LIGHTNING_AUDIO_ENABLED false +#endif + +#define LIGHTNING_MODE_ID "lightning" + +class LightningModeSettings { + public: + CRGB color; + uint8_t brightness; + uint8_t threshold; + uint8_t flashes; + bool audioEnabled; + bool includedBands[NUM_BANDS]; + + static void read(LightningModeSettings& settings, JsonObject& root) { + writeColorToJson(root, &settings.color, "color"); + writeByteToJson(root, &settings.brightness, "brightness"); + writeByteToJson(root, &settings.threshold, "threshold"); + writeByteToJson(root, &settings.flashes, "flashes"); + writeBoolToJson(root, &settings.audioEnabled, "audio_enabled"); + writeBooleanArrayToJson(root, settings.includedBands, NUM_BANDS, "included_bands"); + } + + static StateUpdateResult update(JsonObject& root, LightningModeSettings& settings) { + updateColorFromJson(root, &settings.color, FACTORY_LIGHTNING_MODE_COLOR, "color"); + updateByteFromJson(root, &settings.brightness, FACTORY_LIGHTNING_MODE_BRIGHTNESS, "brightness"); + updateByteFromJson(root, &settings.threshold, FACTORY_LIGHTNING_MODE_THRESHOLD, "threshold"); + updateByteFromJson(root, &settings.flashes, FACTORY_LIGHTNING_MODE_FLASHES, "flashes"); + updateBoolFromJson(root, &settings.audioEnabled, FACTORY_LIGHTNING_AUDIO_ENABLED, "audio_enabled"); + updateBooleanArrayFromJson(root, settings.includedBands, NUM_BANDS, true, "included_bands"); + return StateUpdateResult::CHANGED; + } +}; + +class LightningMode : public AudioLightModeImpl { + private: + enum class Status { IDLE, TRIGGERED, RUNNING }; + + // State variables + Status _status = Status::IDLE; + uint8_t _ledstart; // Starting location of a flash + uint8_t _ledlen; // Length of a flash + uint16_t _dimmer = 1; // State for dimming initial flash by a factor + uint8_t _flashCounter = 0; // Keeps track of how many flashes we have done + uint8_t _numFlashes = 0; // Keeps track of how many flashes we will do + bool _refresh = true; // For applying config updates or enabling the mode + + // Pause tracking + unsigned long _waitStartedAt; + uint16_t _waitDuration; + + public: + LightningMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler); + void tick(); + void enable(); + void sampleComplete(); + bool isWaitTimeElapsed(); + void resetWaitTime(); +}; + +#endif diff --git a/src/RainbowMode.h b/src/RainbowMode.h index fa72d43a..92e3a7cd 100644 --- a/src/RainbowMode.h +++ b/src/RainbowMode.h @@ -1,5 +1,5 @@ -#ifndef RAINBOW_MODE_H -#define RAINBOW_MODE_H +#ifndef RainbowMode_h +#define RainbowMode_h #include #include From 8b5f672dd94b5e279de0661cbdcf9245b716ce0a Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sat, 14 Nov 2020 22:54:16 +0000 Subject: [PATCH 07/52] add confetti mode --- src/AudioLightSettingsService.cpp | 1 + src/AudioLightSettingsService.h | 3 +- src/ConfettiMode.cpp | 76 +++++++++++++++++++++++++++++++ src/ConfettiMode.h | 67 +++++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 src/ConfettiMode.cpp create mode 100644 src/ConfettiMode.h diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index 77172934..c1894fb5 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -33,6 +33,7 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, _modes[0] = new ColorMode(server, fs, securityManager, ledSettingsService, frequencySampler); _modes[1] = new RainbowMode(server, fs, securityManager, ledSettingsService, frequencySampler); _modes[2] = new LightningMode(server, fs, securityManager, ledSettingsService, frequencySampler); + _modes[3] = new ConfettiMode(server, fs, securityManager, ledSettingsService, frequencySampler); } void AudioLightSettingsService::begin() { diff --git a/src/AudioLightSettingsService.h b/src/AudioLightSettingsService.h index f54882a3..ce309bd6 100644 --- a/src/AudioLightSettingsService.h +++ b/src/AudioLightSettingsService.h @@ -6,8 +6,9 @@ #include #include #include +#include -#define NUM_MODES 3 +#define NUM_MODES 4 #define AUDIO_LIGHT_SERVICE_PATH "/rest/mode" #define AUDIO_LIGHT_SAVE_MODE_PATH "/rest/mode/save" diff --git a/src/ConfettiMode.cpp b/src/ConfettiMode.cpp new file mode 100644 index 00000000..09b75dc3 --- /dev/null +++ b/src/ConfettiMode.cpp @@ -0,0 +1,76 @@ +#include + +ConfettiMode::ConfettiMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler) : + AudioLightModeImpl(server, + fs, + securityManager, + ledSettingsService, + frequencySampler, + ConfettiModeSettings::read, + ConfettiModeSettings::update, + CONFETTI_MODE_ID){}; + +void ConfettiMode::tick() { + _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + if (_refresh) { + fill_solid(leds, numLeds, CHSV(255, 0, 0)); + ledController->showLeds(); + _refresh = false; + } + + EVERY_N_MILLISECONDS(100) { + nblendPaletteTowardPalette(_currentPalette, _targetPalette, _state.maxChanges); + } + uint8_t secondHand = (millis() / 1000) % 15; + static uint8_t lastSecond = 99; + if (lastSecond != secondHand) { + lastSecond = secondHand; + switch (secondHand) { + case 0: + _targetPalette = OceanColors_p; + _inc = 1; + _hue = 192; + _fade = 2; + _hueDelta = 255; + break; + case 5: + _targetPalette = LavaColors_p; + _inc = 2; + _hue = 128; + _fade = 8; + _hueDelta = 64; + break; + case 10: + _targetPalette = ForestColors_p; + _inc = 1; + _hue = random16(255); + _fade = 1; + _hueDelta = 16; + break; + case 15: + break; + } + } + + EVERY_N_MILLIS_I(confettiTimer, FACTORY_CONFETTI_MODE_DELAY) { + fadeToBlackBy(leds, numLeds, _fade); + int pos = random16(numLeds); + leds[pos] = + ColorFromPalette(_currentPalette, _hue + random16(_hueDelta) / 4, _state.brightness, _currentBlending); + _hue = _hue + _inc; + ledController->showLeds(); + confettiTimer.setPeriod(_state.delay); + } + }); +} + +void ConfettiMode::enable() { + _refresh = true; +} + +void ConfettiMode::sampleComplete() { +} diff --git a/src/ConfettiMode.h b/src/ConfettiMode.h new file mode 100644 index 00000000..0fc9ed00 --- /dev/null +++ b/src/ConfettiMode.h @@ -0,0 +1,67 @@ +#ifndef ConfettiMode_h +#define ConfettiMode_h + +#include +#include + +#ifndef FACTORY_CONFETTI_MODE_MAX_CHANGES +#define FACTORY_CONFETTI_MODE_MAX_CHANGES 24 +#endif + +#ifndef FACTORY_CONFETTI_MODE_BRIGHTNESS +#define FACTORY_CONFETTI_MODE_BRIGHTNESS 255 +#endif + +#ifndef FACTORY_CONFETTI_MODE_DELAY +#define FACTORY_CONFETTI_MODE_DELAY 5 +#endif + +#define CONFETTI_MODE_ID "confetti" + +// could make palettes configurable, pick from a list perhaps... +class ConfettiModeSettings { + public: + uint8_t maxChanges; // number of changes per cycle + uint8_t brightness; // Brightness of a sequence. Remember, max_brightnessght is the overall limiter. + uint8_t delay; // We don't need much delay (if any) + + static void read(ConfettiModeSettings& settings, JsonObject& root) { + writeByteToJson(root, &settings.maxChanges, "max_changes"); + writeByteToJson(root, &settings.brightness, "brightness"); + writeByteToJson(root, &settings.delay, "delay"); + } + + static StateUpdateResult update(JsonObject& root, ConfettiModeSettings& settings) { + updateByteFromJson(root, &settings.maxChanges, FACTORY_CONFETTI_MODE_MAX_CHANGES, "max_changes"); + updateByteFromJson(root, &settings.brightness, FACTORY_CONFETTI_MODE_BRIGHTNESS, "brightness"); + updateByteFromJson(root, &settings.delay, FACTORY_CONFETTI_MODE_DELAY, "delay"); + return StateUpdateResult::CHANGED; + } +}; + +class ConfettiMode : public AudioLightModeImpl { + private: + // Variables used by the sequences. + uint8_t _fade = 8; // How quickly does it fade? Lower = slower fade rate. + int _hue = 50; // Starting hue. + uint8_t _inc = 1; // Incremental value for rotating hues + int _hueDelta = 255; // Range of random #'s to use for hue + + CRGBPalette16 _currentPalette; // Current primary target + CRGBPalette16 _targetPalette; // Pallette we are blending to + TBlendType _currentBlending = LINEARBLEND; // NOBLEND or LINEARBLEND + + bool _refresh = true; // For applying config updates or enabling the mode + + public: + ConfettiMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler); + void tick(); + void enable(); + void sampleComplete(); +}; + +#endif From e020f0dc7ab7ae0879fee1952cf490679520c000 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sat, 14 Nov 2020 23:04:28 +0000 Subject: [PATCH 08/52] fix confetti mode --- src/ConfettiMode.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ConfettiMode.cpp b/src/ConfettiMode.cpp index 09b75dc3..6d536865 100644 --- a/src/ConfettiMode.cpp +++ b/src/ConfettiMode.cpp @@ -12,7 +12,9 @@ ConfettiMode::ConfettiMode(AsyncWebServer* server, frequencySampler, ConfettiModeSettings::read, ConfettiModeSettings::update, - CONFETTI_MODE_ID){}; + CONFETTI_MODE_ID) { + addUpdateHandler([&](const String& originId) { enable(); }, false); +}; void ConfettiMode::tick() { _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { From 5a3e9b8cd2d8797dc9f9a9191971663a61539ed8 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sat, 14 Nov 2020 23:13:13 +0000 Subject: [PATCH 09/52] add update handler --- src/AudioLightMode.h | 21 +++++++++++++-------- src/ColorMode.cpp | 14 +++++++------- src/LightningMode.cpp | 4 +++- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/AudioLightMode.h b/src/AudioLightMode.h index 143c2114..82798656 100644 --- a/src/AudioLightMode.h +++ b/src/AudioLightMode.h @@ -30,17 +30,22 @@ class AudioLightModeImpl : public StatefulService, public AudioLightMode { public: AudioLightModeImpl(AsyncWebServer* server, - FS* fs, - SecurityManager* securityManager, - LedSettingsService* ledSettingsService, - FrequencySampler* frequencySampler, - JsonStateReader stateReader, - JsonStateUpdater stateUpdater, - const String& id) : + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler, + JsonStateReader stateReader, + JsonStateUpdater stateUpdater, + const String& id) : _id(id), _ledSettingsService(ledSettingsService), _frequencySampler(frequencySampler), - _httpEndpoint(stateReader, stateUpdater, this, server, AUDIO_LIGHT_MODE_SERVICE_PATH_PREFIX + id, securityManager), + _httpEndpoint(stateReader, + stateUpdater, + this, + server, + AUDIO_LIGHT_MODE_SERVICE_PATH_PREFIX + id, + securityManager), _fsPersistence(stateReader, stateUpdater, this, diff --git a/src/ColorMode.cpp b/src/ColorMode.cpp index 482f7cdc..a371a120 100644 --- a/src/ColorMode.cpp +++ b/src/ColorMode.cpp @@ -6,13 +6,13 @@ ColorMode::ColorMode(AsyncWebServer* server, LedSettingsService* ledSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, - fs, - securityManager, - ledSettingsService, - frequencySampler, - ColorModeSettings::read, - ColorModeSettings::update, - COLOR_MODE_ID) { + fs, + securityManager, + ledSettingsService, + frequencySampler, + ColorModeSettings::read, + ColorModeSettings::update, + COLOR_MODE_ID) { addUpdateHandler([&](const String& originId) { enable(); }, false); }; diff --git a/src/LightningMode.cpp b/src/LightningMode.cpp index fa4d959d..fe478016 100644 --- a/src/LightningMode.cpp +++ b/src/LightningMode.cpp @@ -12,7 +12,9 @@ LightningMode::LightningMode(AsyncWebServer* server, frequencySampler, LightningModeSettings::read, LightningModeSettings::update, - LIGHTNING_MODE_ID){}; + LIGHTNING_MODE_ID) { + addUpdateHandler([&](const String& originId) { enable(); }, false); +}; void LightningMode::tick() { _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { From a34e73aa4840e83c5ac78378a142e0bc1db1a0aa Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sat, 14 Nov 2020 23:38:02 +0000 Subject: [PATCH 10/52] fix audio mode creation --- src/AudioLightMode.h | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/AudioLightMode.h b/src/AudioLightMode.h index 82798656..b03a909a 100644 --- a/src/AudioLightMode.h +++ b/src/AudioLightMode.h @@ -23,6 +23,8 @@ template class AudioLightModeImpl : public StatefulService, public AudioLightMode { protected: String _id; + String _filePath; + String _servicePath; LedSettingsService* _ledSettingsService; FrequencySampler* _frequencySampler; HttpEndpoint _httpEndpoint; @@ -38,19 +40,12 @@ class AudioLightModeImpl : public StatefulService, public AudioLightMode { JsonStateUpdater stateUpdater, const String& id) : _id(id), + _filePath(AUDIO_LIGHT_MODE_SERVICE_PATH_PREFIX + id), + _servicePath(AUDIO_LIGHT_MODE_FILE_PATH_PREFIX + id + AUDIO_LIGHT_MODE_FILE_PATH_SUFFIX), _ledSettingsService(ledSettingsService), _frequencySampler(frequencySampler), - _httpEndpoint(stateReader, - stateUpdater, - this, - server, - AUDIO_LIGHT_MODE_SERVICE_PATH_PREFIX + id, - securityManager), - _fsPersistence(stateReader, - stateUpdater, - this, - fs, - String(AUDIO_LIGHT_MODE_FILE_PATH_PREFIX + id + AUDIO_LIGHT_MODE_FILE_PATH_SUFFIX).c_str()) { + _httpEndpoint(stateReader, stateUpdater, this, server, _filePath, securityManager), + _fsPersistence(stateReader, stateUpdater, this, fs, _servicePath.c_str()) { } /* From 252de6cb42e1b61e79bbb93b6af184132e41571f Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sun, 15 Nov 2020 00:08:51 +0000 Subject: [PATCH 11/52] fix boolean array defaulting behaviour --- src/JsonUtil.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/JsonUtil.cpp b/src/JsonUtil.cpp index 1cfc101e..a6314883 100644 --- a/src/JsonUtil.cpp +++ b/src/JsonUtil.cpp @@ -1,13 +1,14 @@ #include void updateBooleanArrayFromJson(JsonObject& root, bool booleanArray[], uint16_t len, bool def, String key) { + for (uint8_t i = 0; i < len; i++) { + booleanArray[i] = def; + } if (root.containsKey(key) && root[key].is()) { JsonArray jsonArray = root[key]; - for (uint8_t i = 0; i < len; i++) { - if (i < jsonArray.size() && jsonArray[i].is()) { + for (uint8_t i = 0; i < len && i < jsonArray.size(); i++) { + if (jsonArray[i].is()) { booleanArray[i] = jsonArray[i]; - } else { - booleanArray[i] = def; } } } From b65da48cff61c6383b6f0ad56e85fff768e31e02 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sun, 15 Nov 2020 00:23:15 +0000 Subject: [PATCH 12/52] only use update callback when necessarry --- src/ConfettiMode.cpp | 81 +++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/src/ConfettiMode.cpp b/src/ConfettiMode.cpp index 6d536865..bb2cd150 100644 --- a/src/ConfettiMode.cpp +++ b/src/ConfettiMode.cpp @@ -17,48 +17,51 @@ ConfettiMode::ConfettiMode(AsyncWebServer* server, }; void ConfettiMode::tick() { - _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { - if (_refresh) { + if (_refresh) { + _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { fill_solid(leds, numLeds, CHSV(255, 0, 0)); ledController->showLeds(); - _refresh = false; - } + }); + _refresh = false; + } - EVERY_N_MILLISECONDS(100) { - nblendPaletteTowardPalette(_currentPalette, _targetPalette, _state.maxChanges); - } - uint8_t secondHand = (millis() / 1000) % 15; - static uint8_t lastSecond = 99; - if (lastSecond != secondHand) { - lastSecond = secondHand; - switch (secondHand) { - case 0: - _targetPalette = OceanColors_p; - _inc = 1; - _hue = 192; - _fade = 2; - _hueDelta = 255; - break; - case 5: - _targetPalette = LavaColors_p; - _inc = 2; - _hue = 128; - _fade = 8; - _hueDelta = 64; - break; - case 10: - _targetPalette = ForestColors_p; - _inc = 1; - _hue = random16(255); - _fade = 1; - _hueDelta = 16; - break; - case 15: - break; - } + EVERY_N_MILLISECONDS(100) { + nblendPaletteTowardPalette(_currentPalette, _targetPalette, _state.maxChanges); + } + + uint8_t secondHand = (millis() / 1000) % 15; + static uint8_t lastSecond = 99; + if (lastSecond != secondHand) { + lastSecond = secondHand; + switch (secondHand) { + case 0: + _targetPalette = OceanColors_p; + _inc = 1; + _hue = 192; + _fade = 2; + _hueDelta = 255; + break; + case 5: + _targetPalette = LavaColors_p; + _inc = 2; + _hue = 128; + _fade = 8; + _hueDelta = 64; + break; + case 10: + _targetPalette = ForestColors_p; + _inc = 1; + _hue = random16(255); + _fade = 1; + _hueDelta = 16; + break; + case 15: + break; } + } - EVERY_N_MILLIS_I(confettiTimer, FACTORY_CONFETTI_MODE_DELAY) { + EVERY_N_MILLIS_I(confettiTimer, FACTORY_CONFETTI_MODE_DELAY) { + _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { fadeToBlackBy(leds, numLeds, _fade); int pos = random16(numLeds); leds[pos] = @@ -66,8 +69,8 @@ void ConfettiMode::tick() { _hue = _hue + _inc; ledController->showLeds(); confettiTimer.setPeriod(_state.delay); - } - }); + }); + } } void ConfettiMode::enable() { From 4502629cd266b6c3dc807b7841fae9847e0e0ef2 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sun, 15 Nov 2020 13:54:06 +0000 Subject: [PATCH 13/52] add fire mode --- src/AudioLightSettingsService.cpp | 1 + src/AudioLightSettingsService.h | 3 +- src/FireMode.cpp | 66 +++++++++++++++++++++++++++++++ src/FireMode.h | 60 ++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 src/FireMode.cpp create mode 100644 src/FireMode.h diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index c1894fb5..18f0f907 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -34,6 +34,7 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, _modes[1] = new RainbowMode(server, fs, securityManager, ledSettingsService, frequencySampler); _modes[2] = new LightningMode(server, fs, securityManager, ledSettingsService, frequencySampler); _modes[3] = new ConfettiMode(server, fs, securityManager, ledSettingsService, frequencySampler); + _modes[4] = new FireMode(server, fs, securityManager, ledSettingsService, frequencySampler); } void AudioLightSettingsService::begin() { diff --git a/src/AudioLightSettingsService.h b/src/AudioLightSettingsService.h index ce309bd6..30bd3574 100644 --- a/src/AudioLightSettingsService.h +++ b/src/AudioLightSettingsService.h @@ -7,8 +7,9 @@ #include #include #include +#include -#define NUM_MODES 4 +#define NUM_MODES 5 #define AUDIO_LIGHT_SERVICE_PATH "/rest/mode" #define AUDIO_LIGHT_SAVE_MODE_PATH "/rest/mode/save" diff --git a/src/FireMode.cpp b/src/FireMode.cpp new file mode 100644 index 00000000..f6645f7e --- /dev/null +++ b/src/FireMode.cpp @@ -0,0 +1,66 @@ +#include + +FireMode::FireMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler) : + AudioLightModeImpl(server, + fs, + securityManager, + ledSettingsService, + frequencySampler, + FireModeSettings::read, + FireModeSettings::update, + FIRE_MODE_ID){}; + +void FireMode::enable() { + _refresh = true; +} + +void FireMode::tick() { + if (_refresh) { + _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + fill_solid(leds, numLeds, CHSV(255, 0, 0)); // clear leds + ledController->showLeds(); // render all leds black + memset(_heatMap, 0, sizeof(uint8_t) * numLeds); // clear heat map + _refresh = false; // clear refresh flag + }); + } + // make firey stuff at ~100FPS + EVERY_N_MILLIS(10) { + _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + // Step 1. Cool down every cell a little + for (int i = 0; i < numLeds; i++) { + _heatMap[i] = qsub8(_heatMap[i], random8(0, ((_state.cooling * 10) / numLeds) + 2)); + } + + // Step 2. Heat from each cell drifts 'up' and diffuses a little + for (int k = numLeds - 1; k >= 2; k--) { + _heatMap[k] = (_heatMap[k - 1] + _heatMap[k - 2] + _heatMap[k - 2]) / 3; + } + + // Step 3. Randomly ignite new 'sparks' of heat near the bottom + if (random8() < _state.sparking) { + int y = random8(7); + _heatMap[y] = qadd8(_heatMap[y], random8(160, 255)); + } + + // Step 4. Map from heat cells to LED colors + for (int j = 0; j < numLeds; j++) { + // Scale the heat value from 0-255 down to 0-240 + // for best results with color palettes. + byte colorindex = scale8(_heatMap[j], 240); + CRGB color = ColorFromPalette(*_firePalette, colorindex); + int pixelnumber; + if (_state.reverse) { + pixelnumber = (numLeds - 1) - j; + } else { + pixelnumber = j; + } + leds[pixelnumber] = color; + } + ledController->showLeds(); + }); + } +} diff --git a/src/FireMode.h b/src/FireMode.h new file mode 100644 index 00000000..1d1cd2b5 --- /dev/null +++ b/src/FireMode.h @@ -0,0 +1,60 @@ +#ifndef FireMode_h +#define FireMode_h + +#include +#include + +#ifndef FACTORY_FIRE_MODE_SPARKING +#define FACTORY_FIRE_MODE_SPARKING 120 +#endif + +#ifndef FACTORY_FIRE_MODE_REVERSE +#define FACTORY_FIRE_MODE_REVERSE false +#endif + +#ifndef FACTORY_FIRE_MODE_COOLING +#define FACTORY_FIRE_MODE_COOLING 80 +#endif + +#define FIRE_MODE_ID "fire" + +// audio enable +// make palette customizable +class FireModeSettings { + public: + bool reverse; + uint8_t cooling; + uint8_t sparking; + + static void read(FireModeSettings& settings, JsonObject& root) { + writeByteToJson(root, &settings.cooling, "cooling"); + writeByteToJson(root, &settings.sparking, "sparking"); + writeBoolToJson(root, &settings.reverse, "reverse"); + } + + static StateUpdateResult update(JsonObject& root, FireModeSettings& settings) { + updateByteFromJson(root, &settings.sparking, FACTORY_FIRE_MODE_SPARKING, "sparking"); + updateByteFromJson(root, &settings.cooling, FACTORY_FIRE_MODE_COOLING, "cooling"); + updateBoolFromJson(root, &settings.reverse, FACTORY_FIRE_MODE_REVERSE, "reverse"); + return StateUpdateResult::CHANGED; + } +}; + +class FireMode : public AudioLightModeImpl { + private: + bool _refresh = true; // For applying config updates or enabling the mode + uint8_t _heatMap[NUM_LEDS]; // Intensity map the led strip - statically allocated ATM + CRGBPalette16 _heat = CRGBPalette16(HeatColors_p); // The heat palette, which looks like fire + CRGBPalette16* _firePalette = &_heat; // The current palette we are using, set to heat ATM + + public: + FireMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler); + void tick(); + void enable(); +}; + +#endif From f15f9824ae4a16167fa930cdd93f28399b164ac4 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sun, 15 Nov 2020 16:58:15 +0000 Subject: [PATCH 14/52] fix save/load paths add frequency transmitter --- src/AudioLightSettingsService.cpp | 18 ++++++++++-------- src/AudioLightSettingsService.h | 4 ++-- src/FrequencySampler.h | 8 ++++++++ src/FrequencyTransmitter.cpp | 26 ++++++++++++++++++++++++++ src/FrequencyTransmitter.h | 20 ++++++++++++++++++++ src/main.cpp | 2 ++ 6 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 src/FrequencyTransmitter.cpp create mode 100644 src/FrequencyTransmitter.h diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index 18f0f907..600dc504 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -20,14 +20,16 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, AUDIO_LIGHT_SERVICE_PATH, securityManager, AuthenticationPredicates::IS_AUTHENTICATED) { - server->on(AUDIO_LIGHT_SAVE_MODE_PATH, - HTTP_POST, - std::bind(&AudioLightSettingsService::saveModeConfig, this, std::placeholders::_1), - junkBodyHandler); - server->on(AUDIO_LIGHT_LOAD_MODE_PATH, - HTTP_POST, - std::bind(&AudioLightSettingsService::loadModeConfig, this, std::placeholders::_1), - junkBodyHandler); + server->on( + AUDIO_LIGHT_SAVE_MODE_PATH, + HTTP_POST, + securityManager->wrapRequest(std::bind(&AudioLightSettingsService::saveModeConfig, this, std::placeholders::_1), + AuthenticationPredicates::IS_AUTHENTICATED)); + server->on( + AUDIO_LIGHT_LOAD_MODE_PATH, + HTTP_POST, + securityManager->wrapRequest(std::bind(&AudioLightSettingsService::loadModeConfig, this, std::placeholders::_1), + AuthenticationPredicates::IS_AUTHENTICATED)); frequencySampler->addUpdateHandler([&](const String& originId) { sampleComplete(); }, false); addUpdateHandler([&](const String& originId) { modeChanged(); }, false); _modes[0] = new ColorMode(server, fs, securityManager, ledSettingsService, frequencySampler); diff --git a/src/AudioLightSettingsService.h b/src/AudioLightSettingsService.h index 30bd3574..721fb31b 100644 --- a/src/AudioLightSettingsService.h +++ b/src/AudioLightSettingsService.h @@ -12,8 +12,8 @@ #define NUM_MODES 5 #define AUDIO_LIGHT_SERVICE_PATH "/rest/mode" -#define AUDIO_LIGHT_SAVE_MODE_PATH "/rest/mode/save" -#define AUDIO_LIGHT_LOAD_MODE_PATH "/rest/mode/load" +#define AUDIO_LIGHT_SAVE_MODE_PATH "/rest/saveMode" +#define AUDIO_LIGHT_LOAD_MODE_PATH "/rest/loadMode" #define AUDIO_LIGHT_DEFAULT_MODE "color" diff --git a/src/FrequencySampler.h b/src/FrequencySampler.h index 0d5df166..2c3d7f6c 100644 --- a/src/FrequencySampler.h +++ b/src/FrequencySampler.h @@ -33,6 +33,14 @@ class FrequencyData { uint8_t calculateEnergyByte(bool* includedBands = NULL) { return calculateEnergyFloat(includedBands) * 255; } + + static void read(FrequencyData& settings, JsonObject& root) { + JsonArray array = root.createNestedArray("bands"); + for (uint16_t i = 0; i < NUM_BANDS; i++) { + array.add(settings.bands[i]); + } + } + }; class FrequencySampler : public StatefulService { diff --git a/src/FrequencyTransmitter.cpp b/src/FrequencyTransmitter.cpp new file mode 100644 index 00000000..3516bf82 --- /dev/null +++ b/src/FrequencyTransmitter.cpp @@ -0,0 +1,26 @@ +#include + +FrequencyTransmitter::FrequencyTransmitter(AsyncWebServer* server, + SecurityManager* securityManager, + FrequencySampler* frequencySampler) : + _frequencySampler(frequencySampler), + _webSocketTx(FrequencyData::read, + this, + server, + FREQUENCY_TRANSMITTER_WS_PATH, + securityManager, + AuthenticationPredicates::IS_AUTHENTICATED) { + frequencySampler->addUpdateHandler([&](const String& originId) { handleSample(); }, false); +} + +void FrequencyTransmitter::handleSample() { + EVERY_N_MILLISECONDS(200) { + update( + [&](FrequencyData& state) { + FrequencyData* frequencyData = _frequencySampler->getFrequencyData(); + std::copy(std::begin(frequencyData->bands), std::end(frequencyData->bands), std::begin(state.bands)); + return StateUpdateResult::CHANGED; + }, + "system"); + } +} diff --git a/src/FrequencyTransmitter.h b/src/FrequencyTransmitter.h new file mode 100644 index 00000000..73d0c6a7 --- /dev/null +++ b/src/FrequencyTransmitter.h @@ -0,0 +1,20 @@ +#ifndef FrequencyTransmitter_h +#define FrequencyTransmitter_h + +#define FREQUENCY_TRANSMITTER_WS_PATH "/ws/frequencies" + +#include +#include +#include + +class FrequencyTransmitter : public StatefulService { + public: + FrequencyTransmitter(AsyncWebServer* server, SecurityManager* securityManager, FrequencySampler* frequencySampler); + + private: + FrequencySampler* _frequencySampler; + WebSocketTx _webSocketTx; + void handleSample(); +}; + +#endif // end FrequencyTransmitter_h diff --git a/src/main.cpp b/src/main.cpp index 82193460..4d434ecb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #define SERIAL_BAUD_RATE 115200 @@ -10,6 +11,7 @@ ESP8266React esp8266React(&server); LedSettingsService ledSettingsService(&server, esp8266React.getFS(), esp8266React.getSecurityManager()); FrequencySampler frequencySampler; +FrequencyTransmitter frequencyTransmitter(&server, esp8266React.getSecurityManager(), &frequencySampler); AudioLightSettingsService audioLightSettingsService(&server, esp8266React.getFS(), esp8266React.getSecurityManager(), From 5b19cd31550d330dfd1adbe91c179b34aa47ff42 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sun, 15 Nov 2020 17:16:00 +0000 Subject: [PATCH 15/52] turn off features we arn't going to be using --- features.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features.ini b/features.ini index f68b0aec..44a3ff13 100644 --- a/features.ini +++ b/features.ini @@ -2,7 +2,7 @@ build_flags = -D FT_PROJECT=1 -D FT_SECURITY=1 - -D FT_MQTT=1 + -D FT_MQTT=0 -D FT_NTP=1 - -D FT_OTA=1 + -D FT_OTA=0 -D FT_UPLOAD_FIRMWARE=1 From d9e8368f58c5a4e72992e83c62a400daa411d2b6 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sun, 15 Nov 2020 17:52:37 +0000 Subject: [PATCH 16/52] allow frequency sampler to access led settings re-activate mode when led settings are updated --- platformio.ini | 2 +- src/AudioLightSettingsService.cpp | 9 +++++---- src/AudioLightSettingsService.h | 4 ++-- src/FrequencySampler.cpp | 6 +++++- src/FrequencySampler.h | 11 ++++++++--- src/LedSettingsService.cpp | 4 ++++ src/LedSettingsService.h | 7 +++++-- src/main.cpp | 2 +- 8 files changed, 31 insertions(+), 14 deletions(-) diff --git a/platformio.ini b/platformio.ini index 1bcd02ff..368ea235 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,7 +11,7 @@ build_flags= ${features.build_flags} -D NO_GLOBAL_ARDUINOOTA ; Uncomment ENABLE_CORS to enable Cross-Origin Resource Sharing (required for local React development) - ;-D ENABLE_CORS + -D ENABLE_CORS -D CORS_ORIGIN=\"http://localhost:3000\" ; Uncomment PROGMEM_WWW to enable the storage of the WWW data in PROGMEM -D PROGMEM_WWW diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index 600dc504..326ed561 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -30,8 +30,9 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, HTTP_POST, securityManager->wrapRequest(std::bind(&AudioLightSettingsService::loadModeConfig, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)); - frequencySampler->addUpdateHandler([&](const String& originId) { sampleComplete(); }, false); - addUpdateHandler([&](const String& originId) { modeChanged(); }, false); + addUpdateHandler([&](const String& originId) { enableMode(); }, false); + frequencySampler->addUpdateHandler([&](const String& originId) { handleSample(); }, false); + ledSettingsService->addUpdateHandler([&](const String& originId) { enableMode(); }, false); _modes[0] = new ColorMode(server, fs, securityManager, ledSettingsService, frequencySampler); _modes[1] = new RainbowMode(server, fs, securityManager, ledSettingsService, frequencySampler); _modes[2] = new LightningMode(server, fs, securityManager, ledSettingsService, frequencySampler); @@ -63,11 +64,11 @@ AudioLightMode* AudioLightSettingsService::getMode(const String& modeId) { return nullptr; } -void AudioLightSettingsService::modeChanged() { +void AudioLightSettingsService::enableMode() { _state.currentMode->enable(); } -void AudioLightSettingsService::sampleComplete() { +void AudioLightSettingsService::handleSample() { _state.currentMode->sampleComplete(); } diff --git a/src/AudioLightSettingsService.h b/src/AudioLightSettingsService.h index 721fb31b..ccaa869a 100644 --- a/src/AudioLightSettingsService.h +++ b/src/AudioLightSettingsService.h @@ -45,8 +45,8 @@ class AudioLightSettingsService : public StatefulService { StateUpdateResult update(JsonObject& root, AudioLightSettings& settings); AudioLightMode* getMode(const String& modeId); - void modeChanged(); - void sampleComplete(); + void enableMode(); + void handleSample(); void saveModeConfig(AsyncWebServerRequest* request); void loadModeConfig(AsyncWebServerRequest* request); }; diff --git a/src/FrequencySampler.cpp b/src/FrequencySampler.cpp index 9e445519..ba294697 100644 --- a/src/FrequencySampler.cpp +++ b/src/FrequencySampler.cpp @@ -1,5 +1,8 @@ #include +FrequencySampler::FrequencySampler(FrequencySamplerSettings* settings) : _settings(settings) { +} + void FrequencySampler::begin() { pinMode(FREQUENCY_SAMPLER_RESET_PIN, OUTPUT); pinMode(FREQUENCY_SAMPLER_STROBE_PIN, OUTPUT); @@ -11,6 +14,7 @@ void FrequencySampler::begin() { void FrequencySampler::loop() { // take samples 100 times a second (max) EVERY_N_MILLIS(10) { + float smoothingFactor = _settings->getSmoothingFactor(); update( [&](FrequencyData& frequencyData) { // Reset MSGEQ7 IC @@ -38,7 +42,7 @@ void FrequencySampler::loop() { : 0; // crappy smoothing to avoid crazy flickering - frequencyData.bands[i] = _smoothingFactor * frequencyData.bands[i] + (1 - _smoothingFactor) * value; + frequencyData.bands[i] = smoothingFactor * frequencyData.bands[i] + (1 - smoothingFactor) * value; // strobe pin high again for next loop digitalWrite(FREQUENCY_SAMPLER_STROBE_PIN, HIGH); diff --git a/src/FrequencySampler.h b/src/FrequencySampler.h index 2c3d7f6c..420801d7 100644 --- a/src/FrequencySampler.h +++ b/src/FrequencySampler.h @@ -9,11 +9,15 @@ #define FREQUENCY_SAMPLER_RESET_PIN 4 #define FREQUENCY_SAMPLER_STROBE_PIN 5 #define FREQUENCY_SAMPLER_ANALOG_PIN 36 -#define FREQUENCY_SAMPLER_DEFAULT_SMOOTHING_FACTOR 0.15 #define NUM_BANDS 7 #define ADC_MAX_VALUE 4096 +class FrequencySamplerSettings { + public: + virtual float getSmoothingFactor() = 0; +}; + class FrequencyData { public: uint16_t bands[NUM_BANDS]; @@ -40,17 +44,18 @@ class FrequencyData { array.add(settings.bands[i]); } } - }; class FrequencySampler : public StatefulService { public: + FrequencySampler(FrequencySamplerSettings* _settings); + void begin(); void loop(); FrequencyData* getFrequencyData(); private: - float _smoothingFactor = FREQUENCY_SAMPLER_DEFAULT_SMOOTHING_FACTOR; + FrequencySamplerSettings* _settings; }; #endif // end FrequencySampler_h diff --git a/src/LedSettingsService.cpp b/src/LedSettingsService.cpp index 8f959c4d..f9f58860 100644 --- a/src/LedSettingsService.cpp +++ b/src/LedSettingsService.cpp @@ -24,3 +24,7 @@ void LedSettingsService::update(LedUpdateCallback updateCallback) { void LedSettingsService::configureLeds() { FastLED.setBrightness(_state.brightness); } + +float LedSettingsService::getSmoothingFactor() { + return _state.brightness; +} diff --git a/src/LedSettingsService.h b/src/LedSettingsService.h index cc87d639..3e8ae7c3 100644 --- a/src/LedSettingsService.h +++ b/src/LedSettingsService.h @@ -4,10 +4,12 @@ #define LED_SETTINGS_FILE "/config/ledSettings.json" #define LED_SETTINGS_SERVICE_PATH "/rest/ledSettings" +#include #include #include #include #include + /* #define LED_DATA_PIN 21 #define COLOR_ORDER GRB @@ -16,7 +18,7 @@ */ #define LED_DATA_PIN 21 -#define COLOR_ORDER GRB // GBR +#define COLOR_ORDER GRB // GBR #define LED_TYPE WS2812B #define NUM_LEDS 9 @@ -51,10 +53,11 @@ class LedSettings { } }; -class LedSettingsService : public StatefulService { +class LedSettingsService : public StatefulService, public FrequencySamplerSettings { public: LedSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager); + float getSmoothingFactor(); void begin(); void update(LedUpdateCallback updateCallback); diff --git a/src/main.cpp b/src/main.cpp index 4d434ecb..e0c89212 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,7 +10,7 @@ AsyncWebServer server(80); ESP8266React esp8266React(&server); LedSettingsService ledSettingsService(&server, esp8266React.getFS(), esp8266React.getSecurityManager()); -FrequencySampler frequencySampler; +FrequencySampler frequencySampler(&ledSettingsService); FrequencyTransmitter frequencyTransmitter(&server, esp8266React.getSecurityManager(), &frequencySampler); AudioLightSettingsService audioLightSettingsService(&server, esp8266React.getFS(), From 76a290f9d81fa0add805f82b91ff823c9ebe77f3 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sun, 15 Nov 2020 22:28:08 +0000 Subject: [PATCH 17/52] styling for christmas --- interface/.env | 2 +- interface/public/app/icon.png | Bin 8940 -> 16422 bytes interface/public/app/manifest.json | 2 +- interface/public/favicon.ico | Bin 1150 -> 1150 bytes interface/src/CustomMuiTheme.tsx | 10 +- interface/src/project/DemoInformation.tsx | 77 ------------ interface/src/project/DemoProject.tsx | 43 ------- .../project/LightMqttSettingsController.tsx | 90 -------------- .../src/project/LightStateRestController.tsx | 67 ----------- .../project/LightStateWebSocketController.tsx | 62 ---------- interface/src/project/LightsProject.tsx | 34 ++++++ interface/src/project/ProjectMenu.tsx | 14 ++- interface/src/project/ProjectRouting.tsx | 6 +- interface/src/project/SpectrumAnalyzer.tsx | 112 ++++++++++++++++++ interface/src/project/types.ts | 10 +- src/LedSettingsService.cpp | 2 +- 16 files changed, 169 insertions(+), 362 deletions(-) delete mode 100644 interface/src/project/DemoInformation.tsx delete mode 100644 interface/src/project/DemoProject.tsx delete mode 100644 interface/src/project/LightMqttSettingsController.tsx delete mode 100644 interface/src/project/LightStateRestController.tsx delete mode 100644 interface/src/project/LightStateWebSocketController.tsx create mode 100644 interface/src/project/LightsProject.tsx create mode 100644 interface/src/project/SpectrumAnalyzer.tsx diff --git a/interface/.env b/interface/.env index a312b2a0..f7c32c25 100644 --- a/interface/.env +++ b/interface/.env @@ -1,5 +1,5 @@ # This is the name of your project. It appears on the sign-in page and in the menu bar. -REACT_APP_PROJECT_NAME=ESP8266 React +REACT_APP_PROJECT_NAME=Christmas Lights # This is the url path your project will be exposed under. REACT_APP_PROJECT_PATH=project diff --git a/interface/public/app/icon.png b/interface/public/app/icon.png index 13dd442c80a693e407fb8c2615f699e4639d3eea..89d7df639171c9f5ec346a2d74f1946df47dcda7 100644 GIT binary patch literal 16422 zcmaKTWmH>D)b2?L5L}D9L!r34yO-ilaf-VIEe^#Bg`!1T+}$Z|1&X^<+#NpN@2>mv z-jkJ;r*$Z7^st2Zv35u>RY%HOXpi zCZ}OQ0zVrZLLRM!h;L;`Y5*^O3fDXMNB)nr!lSWc`c} z_oF+7M+!+^w*|pWQC13g`R|p}R+RL52gylJ*Y$M= zXa9SHBp5QhUT-40$tz1EZlPe{AhV2b_!a;F1t2da{@!chsLea`{dSee^R(~SzQeUC zA;Gn@wD?Ea4~X+pcp*lu^ZAy%>{!Y!flWEMthHo zD?M$ZCYA~c_rET9$h??2iJ$6mySe1AxmLQleDD+TOWQwA-!E_Y=Uwuz>=(Y;|Nq)! zrJxNy{QW#T6rM0rfjC*J8bczILa^#4w#hiY(x1V8p#p+@fpN6T{-8no zW*fv{0YX1c3ly)3Yyf8T-vptP*i4VTd0ijCt!hI2kc#LKUmNbTN@w6qqPh_r9)EP>2F>?h7l-mFPq$csQ z>mtxUUzqEUiY5?&8yHp|fXMkNdV~I(600=Aa16V>rlmuCAfD(MmgmJPra;#5GU`pP zsi6tUnZT$#x$>r_jSh_WFD(|!;`Wh5baCZ7wz4OF87x(ae5GI^0u?p23>_X`beG=SR&Iz4e)soCzU0++gh%zC@mt&+~uzzZCnEF^XjzI+AzH!RP7W7C| z)5k9&OFiy6F}XpkM3#Z1} zH*cbyYmy26&S(F1H<`l>U)_EF*6h=dV{%-}qzhRoAU=Ohzcpa}vOr zOKk!b&Br608fG63H^apVMBaNa^qsu>_AMyCUpiw7tlZPKS?F*lgr!rK{K^{cdT)94 z(t?a{K8v@CW0IJ8lKG=X-(q8Vv~f(v^~Trfe+*!?J$Mp-f4yR)uGrLA@3Zi^XLaM* z*leduzB=vzvvtnb~B!X^-z-oHFxg=5_%tys<#9f*qOn>=3`jM#=0L@A2vav6Kr^a$v<-FYSZEK2L00;eWN{hmBi+wGi>G-V_xwZAWB>(h7SX&BDMt^J{LxYemo|}WeEzB63aBh=-_cz_lLObcmqbdJy z_O4&P*k~R@UyRxVwwY+%UxjG{`*<*mr*AJJz-0oz88f?U zl<258b{hXrFQ*+;d9+nQ9n~Qe3ZP;re7E;8`F+owlzjQ-6%Z&3mxV_s_#O#@`crNX zLzi1vKwLba&u@_)FtC_ZF;y~MhI~c&r1(4%6VX9u^dr~|Xu3ec_@tl975Rs+#ae2w zs31%5D`f?7&=q9jL?N<$a#MGIybe|K4t8oxOuRY`AuKL~Lx0cLYob@xLBH;JpD)$J zWx!+7bN*s@to`;&x*D7~2Z8od%e~;_jw+y1A7BzZd~(D8Ca9V8G~(&m0Ob{7~> z?rIZ}%W0-4qYzw{6`m|4xwY2;I#sWuN=I}@PRP}%3b@jfdo+&zh>oT(LOe%;-?E6! z=NyF@NAp7ZnS~Tl2`&HxDRyYYj03pc!2mV34K0ujWQk%~`{(wC?HX|BT94i z!GcNrihY^Y9t#TV0Qt@EcjR>T{s7m+g6MnH75gQ%n pLK?n3di@lMuw_)U)qx^^ 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 literal 8940 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 diff --git a/interface/public/app/manifest.json b/interface/public/app/manifest.json index f7756611..889f4f32 100644 --- a/interface/public/app/manifest.json +++ b/interface/public/app/manifest.json @@ -1,5 +1,5 @@ { - "name":"ESP8266 React", + "name":"Christmas Lights", "icons":[ { "src":"/app/icon.png", diff --git a/interface/public/favicon.ico b/interface/public/favicon.ico index 399ccae7c23cffc07389cf431b329ac611fc6c01..4f31a657e7fbecc4a1d0232f58458de4b58a7f02 100644 GIT binary patch literal 1150 zcmZQzU<5(|0R|wcz>vYhz#zuJz@P!dKp~(AL>x$w1nk@rW$nGvCW5e&SGGJM&Bkd` zToxrJ4(6pMNhT!*1!e_iEmjG>zpRsde*@W_ATf}fd6BWBLxTekvU$b@2Hqx>20u;e zP5+xtj{a{{tB1f9HvdghYT$h18vXyq75cwTN(>`lW`u@@GMkhdt}t%4`ERzU7Od9H zcmIFmg24YqHOBu<3d8>!)f@b`Sk?XCxK{taNtwYug#-l-n0_!F%D`+|W_aAF9w-mQ zW)&X)O`D8?*zmteqXAgVbV|&B<8s4Wz757)D0+b$y9R47+ct~GHhmWVZMz))gJ_#6 z9{+8(Oa-%HVzwRT&ukhk1JU)nH&_bUv{{}7>9g=V`QNHG`oB%T)qmUR{$TwuG20G{ zTMiB8GU)n2eCIZ2VcT|#+jc$n|LrDP{kI2VyGhnywk=T1w$tK~ZL6&$HoYJ**Iq|2 zx9Lvre6}?H_gIqOzx!g}{~n71{<}|cS>!p%lO30Ska%c=Hk;=(*NxuG-2ZzohhiYx zW18y@hxrbuafZbpubFPgji3!yR zzFMs`%|aW~)JLoBPEgQ=dTmK)T3WE!`lv3u_&xtkCXH>Rpt<~aCg+^*%-l0`l&X-Y zs!GXwy)srRwN@!rBNCHX@^%@N65pEDc{%?}UIE*sM3tg?QCu`7nlFg+ZV`QqDUt!Z zh8(p{6cv3F8=6HN7v?UR)91u6Rs}vfoMw|^V+?{LThKbXU9=6CN4BDEbO$bes&jmW zm|hw4Ic(P|nR8vo-(+tZ6Lg@W`3U7XNA(q=Od#WzYL5A#J(O2^HpMO<{ zXrCANp0%RmX%IKkXOI|(;Zm{zjjwAAh)}wFw0&b?7|H^j^wjecq4w1}my*_!_e>J)$ zj^e|w6gtx(;oZgWw=4@@uNNMV2l1B?y#M(acXADSeeRz%K#RQz;p{Gi9-V+uQa_*1 z2fyDB%Q}hhljFFO*(tFb^*NGVbbhseoL?+`8a>7H+bo<@H}d-Y{`da;%l+kDFWlcH t{qudHXO`(7={^0EeB1|nll3UqzqBps-| - - This simple demo project allows you to control the built-in LED. - It demonstrates how the esp8266-react framework may be extended for your own IoT project. - - - It is recommended that you keep your project interface code under the project directory. - This serves to isolate your project code from the from the rest of the user interface which should - simplify merges should you wish to update your project with future framework changes. - - - The demo project interface code is stored in the 'interface/src/project' directory: - - - - - - - - - - - - - - - - - - - - - - - - - - - See the project README for a full description of the demo project. - - - - ) - } - -} - -export default DemoInformation; diff --git a/interface/src/project/DemoProject.tsx b/interface/src/project/DemoProject.tsx deleted file mode 100644 index 74f25e53..00000000 --- a/interface/src/project/DemoProject.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' - -import { Tabs, Tab } from '@material-ui/core'; - -import { PROJECT_PATH } from '../api'; -import { MenuAppBar } from '../components'; -import { AuthenticatedRoute } from '../authentication'; - -import DemoInformation from './DemoInformation'; -import LightStateRestController from './LightStateRestController'; -import LightStateWebSocketController from './LightStateWebSocketController'; -import LightMqttSettingsController from './LightMqttSettingsController'; - -class DemoProject extends Component { - - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { - this.props.history.push(path); - }; - - render() { - return ( - - - - - - - - - - - - - - - - ) - } - -} - -export default DemoProject; diff --git a/interface/src/project/LightMqttSettingsController.tsx b/interface/src/project/LightMqttSettingsController.tsx deleted file mode 100644 index 7e4db5cf..00000000 --- a/interface/src/project/LightMqttSettingsController.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React, { Component } from 'react'; -import { ValidatorForm, TextValidator } from 'react-material-ui-form-validator'; - -import { Typography, Box } from '@material-ui/core'; -import SaveIcon from '@material-ui/icons/Save'; - -import { ENDPOINT_ROOT } from '../api'; -import { restController, RestControllerProps, RestFormLoader, RestFormProps, FormActions, FormButton, SectionContent } from '../components'; - -import { LightMqttSettings } from './types'; - -export const LIGHT_BROKER_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "brokerSettings"; - -type LightMqttSettingsControllerProps = RestControllerProps; - -class LightMqttSettingsController extends Component { - - componentDidMount() { - this.props.loadData(); - } - - render() { - return ( - - ( - - )} - /> - - ) - } - -} - -export default restController(LIGHT_BROKER_SETTINGS_ENDPOINT, LightMqttSettingsController); - -type LightMqttSettingsControllerFormProps = RestFormProps; - -function LightMqttSettingsControllerForm(props: LightMqttSettingsControllerFormProps) { - const { data, saveData, handleValueChange } = props; - return ( - - - - The LED is controllable via MQTT with the demo project designed to work with Home Assistant's auto discovery feature. - - - - - - - } variant="contained" color="primary" type="submit"> - Save - - - - ); -} diff --git a/interface/src/project/LightStateRestController.tsx b/interface/src/project/LightStateRestController.tsx deleted file mode 100644 index 764ce350..00000000 --- a/interface/src/project/LightStateRestController.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React, { Component } from 'react'; -import { ValidatorForm } from 'react-material-ui-form-validator'; - -import { Typography, Box, Checkbox } from '@material-ui/core'; -import SaveIcon from '@material-ui/icons/Save'; - -import { ENDPOINT_ROOT } from '../api'; -import { restController, RestControllerProps, RestFormLoader, RestFormProps, FormActions, FormButton, SectionContent, BlockFormControlLabel } from '../components'; - -import { LightState } from './types'; - -export const LIGHT_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "lightState"; - -type LightStateRestControllerProps = RestControllerProps; - -class LightStateRestController extends Component { - - componentDidMount() { - this.props.loadData(); - } - - render() { - return ( - - ( - - )} - /> - - ) - } - -} - -export default restController(LIGHT_SETTINGS_ENDPOINT, LightStateRestController); - -type LightStateRestControllerFormProps = RestFormProps; - -function LightStateRestControllerForm(props: LightStateRestControllerFormProps) { - const { data, saveData, handleValueChange } = props; - return ( - - - - The form below controls the LED via the RESTful service exposed by the ESP device. - - - - } - label="LED State?" - /> - - } variant="contained" color="primary" type="submit"> - Save - - - - ); -} diff --git a/interface/src/project/LightStateWebSocketController.tsx b/interface/src/project/LightStateWebSocketController.tsx deleted file mode 100644 index a0b99a2e..00000000 --- a/interface/src/project/LightStateWebSocketController.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React, { Component } from 'react'; -import { ValidatorForm } from 'react-material-ui-form-validator'; - -import { Typography, Box, Switch } from '@material-ui/core'; -import { WEB_SOCKET_ROOT } from '../api'; -import { WebSocketControllerProps, WebSocketFormLoader, WebSocketFormProps, webSocketController } from '../components'; -import { SectionContent, BlockFormControlLabel } from '../components'; - -import { LightState } from './types'; - -export const LIGHT_SETTINGS_WEBSOCKET_URL = WEB_SOCKET_ROOT + "lightState"; - -type LightStateWebSocketControllerProps = WebSocketControllerProps; - -class LightStateWebSocketController extends Component { - - render() { - return ( - - ( - - )} - /> - - ) - } - -} - -export default webSocketController(LIGHT_SETTINGS_WEBSOCKET_URL, 100, LightStateWebSocketController); - -type LightStateWebSocketControllerFormProps = WebSocketFormProps; - -function LightStateWebSocketControllerForm(props: LightStateWebSocketControllerFormProps) { - const { data, saveData, setData } = props; - - const changeLedOn = (event: React.ChangeEvent) => { - setData({ led_on: event.target.checked }, saveData); - } - - return ( - - - - The switch below controls the LED via the WebSocket. It will automatically update whenever the LED state changes. - - - - } - label="LED State?" - /> - - ); -} diff --git a/interface/src/project/LightsProject.tsx b/interface/src/project/LightsProject.tsx new file mode 100644 index 00000000..9db0ffdc --- /dev/null +++ b/interface/src/project/LightsProject.tsx @@ -0,0 +1,34 @@ +import React, { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' + +import { Tabs, Tab } from '@material-ui/core'; + +import { PROJECT_PATH } from '../api'; +import { MenuAppBar } from '../components'; +import { AuthenticatedRoute } from '../authentication'; + +import SpectrumAnalyzer from './SpectrumAnalyzer'; + +class LightsProject extends Component { + + handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + this.props.history.push(path); + }; + + render() { + return ( + + + + + + + + + + ) + } + +} + +export default LightsProject; diff --git a/interface/src/project/ProjectMenu.tsx b/interface/src/project/ProjectMenu.tsx index b7d27397..37d48809 100644 --- a/interface/src/project/ProjectMenu.tsx +++ b/interface/src/project/ProjectMenu.tsx @@ -1,10 +1,12 @@ import React, { Component } from 'react'; import { Link, withRouter, RouteComponentProps } from 'react-router-dom'; -import {List, ListItem, ListItemIcon, ListItemText} from '@material-ui/core'; -import SettingsRemoteIcon from '@material-ui/icons/SettingsRemote'; +import { List, ListItem, ListItemIcon, ListItemText } from '@material-ui/core'; +import FlareIcon from '@material-ui/icons/Flare'; -import { PROJECT_PATH } from '../api'; +import { PROJECT_PATH, WEB_SOCKET_ROOT } from '../api'; + +export const LIGHT_SETTINGS_WEBSOCKET_URL = WEB_SOCKET_ROOT + "spectrum"; class ProjectMenu extends Component { @@ -12,11 +14,11 @@ class ProjectMenu extends Component { const path = this.props.match.url; return ( - + - + - + ) diff --git a/interface/src/project/ProjectRouting.tsx b/interface/src/project/ProjectRouting.tsx index fc378e6d..f3b652bb 100644 --- a/interface/src/project/ProjectRouting.tsx +++ b/interface/src/project/ProjectRouting.tsx @@ -4,7 +4,7 @@ import { Redirect, Switch } from 'react-router'; import { PROJECT_PATH } from '../api'; import { AuthenticatedRoute } from '../authentication'; -import DemoProject from './DemoProject'; +import LightsProject from './LightsProject'; class ProjectRouting extends Component { @@ -16,14 +16,14 @@ class ProjectRouting extends Component { * Add your project page routing below. */ } - + { /* * The redirect below caters for the default project route and redirecting invalid paths. * The "to" property must match one of the routes above for this to work correctly. */ } - + ) } diff --git a/interface/src/project/SpectrumAnalyzer.tsx b/interface/src/project/SpectrumAnalyzer.tsx new file mode 100644 index 00000000..e5febd92 --- /dev/null +++ b/interface/src/project/SpectrumAnalyzer.tsx @@ -0,0 +1,112 @@ +import React, { Component } from 'react'; +import { WEB_SOCKET_ROOT } from '../api'; +import { SectionContent, webSocketController, WebSocketControllerProps, WebSocketFormLoader, WebSocketFormProps } from '../components'; +import { FrequencyData } from './types'; + +export const LIGHT_SETTINGS_WEBSOCKET_URL = WEB_SOCKET_ROOT + "frequencies"; + +function hslToRgb(h: number, s: number, l: number) { + var r, g, b; + + if (s === 0) { + r = g = b = l; + } else { + var hue2rgb = function hue2rgb(p: number, q: number, t: number) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + } + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; +} + +const calculateColorForPercentage = (percentage: number) => { + var hue = (60 - (percentage * 60)) / 360; + var rgb = hslToRgb(hue, 1, 0.5); + return 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')'; +} + +type SpectrumAnalyzerWebSocketControllerProps = WebSocketControllerProps; + +class SpectrumAnalyzerWebSocketController extends Component { + + render() { + return ( + + ( + + )} + /> + + ) + } + +} + +export default webSocketController(LIGHT_SETTINGS_WEBSOCKET_URL, 100, SpectrumAnalyzerWebSocketController); + +type LightStateWebSocketControllerFormProps = WebSocketFormProps; + + +class SpectrumAnalyzer extends Component { + + private canvas = React.createRef(); + + componentDidUpdate() { + const currentCanvas = this.canvas.current; + const canvasContext = currentCanvas?.getContext("2d"); + + if (!currentCanvas || !canvasContext) { + return; + } + const visualizerData = this.props.data.bands; + + // Make it visually fill the positioned parent + currentCanvas.style.backgroundColor = "#C5CAE9"; + currentCanvas.style.width = '100%'; + currentCanvas.style.height = '300px'; + + // ...then set the internal size to match + currentCanvas.width = currentCanvas.offsetWidth; + currentCanvas.height = currentCanvas.offsetHeight; + + if (!visualizerData) { + return; + } + + // clear the canvas + canvasContext.clearRect(0, 0, currentCanvas.width, currentCanvas.height); + + // calculate bar width + var barWidth = currentCanvas.width / visualizerData.length; + + // output the data + for (var i = 0; i < visualizerData.length; i++) { + var barOffset = i * barWidth; + var barValue = visualizerData[i]; + var barPercentage = barValue > 0 ? barValue / 4096 : 0; + var barHeight = -currentCanvas.height * barPercentage; + canvasContext.fillStyle = calculateColorForPercentage(barPercentage); + canvasContext.fillRect(barOffset, currentCanvas.height, barWidth, barHeight); + } + } + + render() { + return ( + + ) + } + +} diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 32212557..74ffae00 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -1,9 +1,3 @@ -export interface LightState { - led_on: boolean; -} - -export interface LightMqttSettings { - unique_id : string; - name: string; - mqtt_path : string; +export interface FrequencyData { + bands : number[]; } diff --git a/src/LedSettingsService.cpp b/src/LedSettingsService.cpp index f9f58860..e06a1358 100644 --- a/src/LedSettingsService.cpp +++ b/src/LedSettingsService.cpp @@ -26,5 +26,5 @@ void LedSettingsService::configureLeds() { } float LedSettingsService::getSmoothingFactor() { - return _state.brightness; + return _state.smoothingFactor; } From 4ea26a4f763d4c832ff905adab488889bf0f79d7 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sun, 15 Nov 2020 23:39:59 +0000 Subject: [PATCH 18/52] add mode selector --- interface/src/components/RestController.tsx | 6 ++-- .../project/AudioLightSettingsController.tsx | 31 ++++++++++++++++ .../src/project/AudioLightSettingsForm.tsx | 35 +++++++++++++++++++ interface/src/project/LightsProject.tsx | 5 ++- interface/src/project/types.ts | 17 +++++++-- src/AudioLightSettingsService.h | 6 ++-- 6 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 interface/src/project/AudioLightSettingsController.tsx create mode 100644 interface/src/project/AudioLightSettingsForm.tsx diff --git a/interface/src/components/RestController.tsx b/interface/src/components/RestController.tsx index c9751c6a..123df8e9 100644 --- a/interface/src/components/RestController.tsx +++ b/interface/src/components/RestController.tsx @@ -4,7 +4,7 @@ import { withSnackbar, WithSnackbarProps } from 'notistack'; import { redirectingAuthorizedFetch } from '../authentication'; export interface RestControllerProps extends WithSnackbarProps { - handleValueChange: (name: keyof D) => (event: React.ChangeEvent) => void; + handleValueChange: (name: keyof D, callback?: () => void) => (event: React.ChangeEvent) => void; setData: (data: D, callback?: () => void) => void; saveData: () => void; @@ -93,9 +93,9 @@ export function restController>(endpointUrl: }); } - handleValueChange = (name: keyof D) => (event: React.ChangeEvent) => { + handleValueChange = (name: keyof D, callback?: () => void) => (event: React.ChangeEvent) => { const data = { ...this.state.data!, [name]: extractEventValue(event) }; - this.setState({ data }); + this.setState({ data }, callback); } render() { diff --git a/interface/src/project/AudioLightSettingsController.tsx b/interface/src/project/AudioLightSettingsController.tsx new file mode 100644 index 00000000..5e57fad0 --- /dev/null +++ b/interface/src/project/AudioLightSettingsController.tsx @@ -0,0 +1,31 @@ +import React, { Component } from 'react'; +import { ENDPOINT_ROOT } from '../api'; + +import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import AudioLightSettingsForm from './AudioLightSettingsForm'; +import { AudioLightSettings } from './types'; + +export const AUDIO_LIGHT_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "audioLightSettings"; + +type AudioLightSettingsControllerProps = RestControllerProps; + +class AudioLightSettingsController extends Component { + + componentDidMount() { + this.props.loadData(); + } + + render() { + return ( + + } + /> + + ); + } + +} + +export default restController(AUDIO_LIGHT_SETTINGS_ENDPOINT, AudioLightSettingsController); \ No newline at end of file diff --git a/interface/src/project/AudioLightSettingsForm.tsx b/interface/src/project/AudioLightSettingsForm.tsx new file mode 100644 index 00000000..4f6f9d7c --- /dev/null +++ b/interface/src/project/AudioLightSettingsForm.tsx @@ -0,0 +1,35 @@ +import React from 'react'; + +import { RestFormProps } from '../components'; +import { AudioLightMode, AudioLightSettings } from './types'; +import { TextField, MenuItem } from '@material-ui/core'; + +type AudioLightSettingsFormProps = RestFormProps; + +class AudioLightSettingsForm extends React.Component { + + + render() { + const { data, handleValueChange, saveData } = this.props; + return ( + + Off + Single Color + Rainbow + Lightning + Confetti + Fire + + ); + } +} + +export default AudioLightSettingsForm; diff --git a/interface/src/project/LightsProject.tsx b/interface/src/project/LightsProject.tsx index 9db0ffdc..a7b0bd5d 100644 --- a/interface/src/project/LightsProject.tsx +++ b/interface/src/project/LightsProject.tsx @@ -8,6 +8,7 @@ import { MenuAppBar } from '../components'; import { AuthenticatedRoute } from '../authentication'; import SpectrumAnalyzer from './SpectrumAnalyzer'; +import AudioLightSettingsController from './AudioLightSettingsController'; class LightsProject extends Component { @@ -19,11 +20,13 @@ class LightsProject extends Component { return ( + + - + ) diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 74ffae00..f4697aec 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -1,3 +1,16 @@ -export interface FrequencyData { - bands : number[]; +export interface FrequencyData { + bands: number[]; +} + +export enum AudioLightMode { + OFF = "color", + COLOR = "color", + RAINBOW = "rainbow", + LIGHTNING = "lightning", + CONFETTI = "confetti", + FIRE = "fire" +} + +export interface AudioLightSettings { + mode_id: AudioLightMode; } diff --git a/src/AudioLightSettingsService.h b/src/AudioLightSettingsService.h index ccaa869a..c8a39f25 100644 --- a/src/AudioLightSettingsService.h +++ b/src/AudioLightSettingsService.h @@ -11,9 +11,9 @@ #define NUM_MODES 5 -#define AUDIO_LIGHT_SERVICE_PATH "/rest/mode" -#define AUDIO_LIGHT_SAVE_MODE_PATH "/rest/saveMode" -#define AUDIO_LIGHT_LOAD_MODE_PATH "/rest/loadMode" +#define AUDIO_LIGHT_SERVICE_PATH "/rest/audioLightSettings" +#define AUDIO_LIGHT_SAVE_MODE_PATH "/rest/saveModeSettings" +#define AUDIO_LIGHT_LOAD_MODE_PATH "/rest/loadModeSettings" #define AUDIO_LIGHT_DEFAULT_MODE "color" From 7f1b20cfcedb877742c3164dfbeb60a713475879 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Mon, 16 Nov 2020 20:39:29 +0000 Subject: [PATCH 19/52] add off mode --- src/AudioLightSettingsService.cpp | 9 +-------- src/AudioLightSettingsService.h | 3 ++- src/OffMode.cpp | 29 ++++++++++++++++++++++++++++ src/OffMode.h | 32 +++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 src/OffMode.cpp create mode 100644 src/OffMode.h diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index 326ed561..4f68242f 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -1,13 +1,5 @@ #include -void junkBodyHandler(AsyncWebServerRequest* request, - String filename, - size_t index, - uint8_t* data, - size_t len, - bool final) { -} - AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, @@ -38,6 +30,7 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, _modes[2] = new LightningMode(server, fs, securityManager, ledSettingsService, frequencySampler); _modes[3] = new ConfettiMode(server, fs, securityManager, ledSettingsService, frequencySampler); _modes[4] = new FireMode(server, fs, securityManager, ledSettingsService, frequencySampler); + _modes[5] = new OffMode(server, fs, securityManager, ledSettingsService, frequencySampler); } void AudioLightSettingsService::begin() { diff --git a/src/AudioLightSettingsService.h b/src/AudioLightSettingsService.h index c8a39f25..69be628f 100644 --- a/src/AudioLightSettingsService.h +++ b/src/AudioLightSettingsService.h @@ -8,8 +8,9 @@ #include #include #include +#include -#define NUM_MODES 5 +#define NUM_MODES 6 #define AUDIO_LIGHT_SERVICE_PATH "/rest/audioLightSettings" #define AUDIO_LIGHT_SAVE_MODE_PATH "/rest/saveModeSettings" diff --git a/src/OffMode.cpp b/src/OffMode.cpp new file mode 100644 index 00000000..bb7ab872 --- /dev/null +++ b/src/OffMode.cpp @@ -0,0 +1,29 @@ +#include + +OffMode::OffMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler) : + AudioLightModeImpl(server, + fs, + securityManager, + ledSettingsService, + frequencySampler, + OffModeSettings::read, + OffModeSettings::update, + OFF_MODE_ID){}; + +void OffMode::enable() { + _refresh = true; +} + +void OffMode::tick() { + if (_refresh) { + _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + fill_solid(leds, numLeds, CHSV(255, 0, 0)); // clear leds + ledController->showLeds(); // render all leds black + _refresh = false; // clear refresh flag + }); + } +} diff --git a/src/OffMode.h b/src/OffMode.h new file mode 100644 index 00000000..53876f24 --- /dev/null +++ b/src/OffMode.h @@ -0,0 +1,32 @@ +#ifndef OffMode_h +#define OffMode_h + +#include +#include + +#define OFF_MODE_ID "off" + +class OffModeSettings { + public: + static void read(OffModeSettings& settings, JsonObject& root) { + } + + static StateUpdateResult update(JsonObject& root, OffModeSettings& settings) { + return StateUpdateResult::CHANGED; + } +}; + +class OffMode : public AudioLightModeImpl { + private: + bool _refresh = true; // For applying config updates or enabling the mode + public: + OffMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler); + void tick(); + void enable(); +}; + +#endif From 0078cbc88976cc169ccb4fcc1fc492b29ec1b923 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Mon, 16 Nov 2020 22:13:53 +0000 Subject: [PATCH 20/52] add websocket --- src/AudioLightMode.h | 23 ++++++++++++++++- src/AudioLightSettingsService.cpp | 41 ++++++++++++++++++++++--------- src/AudioLightSettingsService.h | 13 +++++----- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/AudioLightMode.h b/src/AudioLightMode.h index b03a909a..61926c5f 100644 --- a/src/AudioLightMode.h +++ b/src/AudioLightMode.h @@ -17,6 +17,8 @@ class AudioLightMode { virtual void tick() = 0; virtual void enable() = 0; virtual void sampleComplete(){}; + virtual void readAsJson(JsonObject& root) = 0; + virtual StateUpdateResult updateFromJson(JsonObject& root, const String& originId) = 0; }; template @@ -24,7 +26,9 @@ class AudioLightModeImpl : public StatefulService, public AudioLightMode { protected: String _id; String _filePath; - String _servicePath; + String _servicePath; + JsonStateReader _stateReader; + JsonStateUpdater _stateUpdater; LedSettingsService* _ledSettingsService; FrequencySampler* _frequencySampler; HttpEndpoint _httpEndpoint; @@ -42,6 +46,8 @@ class AudioLightModeImpl : public StatefulService, public AudioLightMode { _id(id), _filePath(AUDIO_LIGHT_MODE_SERVICE_PATH_PREFIX + id), _servicePath(AUDIO_LIGHT_MODE_FILE_PATH_PREFIX + id + AUDIO_LIGHT_MODE_FILE_PATH_SUFFIX), + _stateReader(stateReader), + _stateUpdater(stateUpdater), _ledSettingsService(ledSettingsService), _frequencySampler(frequencySampler), _httpEndpoint(stateReader, stateUpdater, this, server, _filePath, securityManager), @@ -54,6 +60,7 @@ class AudioLightModeImpl : public StatefulService, public AudioLightMode { const String& getId() { return _id; } + /* * Read the config from the file system and disable the update handler */ @@ -75,6 +82,20 @@ class AudioLightModeImpl : public StatefulService, public AudioLightMode { void writeToFS() { _fsPersistence.writeToFS(); } + + /* + * Read the mode settings as json + */ + void readAsJson(JsonObject& root) { + StatefulService::read(root, _stateReader); + } + + /* + * Update the mode settings from json + */ + StateUpdateResult updateFromJson(JsonObject& root, const String& originId) { + return StatefulService::update(root, _stateUpdater, originId); + } }; #endif // end AudioLightMode_h diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index 4f68242f..f26599bf 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -5,13 +5,21 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, SecurityManager* securityManager, LedSettingsService* ledSettingsService, FrequencySampler* frequencySampler) : - _httpEndpoint(AudioLightSettings::read, + _httpEndpoint(std::bind(&AudioLightSettingsService::read, this, std::placeholders::_1, std::placeholders::_2), std::bind(&AudioLightSettingsService::update, this, std::placeholders::_1, std::placeholders::_2), this, server, AUDIO_LIGHT_SERVICE_PATH, securityManager, - AuthenticationPredicates::IS_AUTHENTICATED) { + AuthenticationPredicates::IS_AUTHENTICATED), + _audioLightModeTxRx( + std::bind(&AudioLightSettingsService::read, this, std::placeholders::_1, std::placeholders::_2), + std::bind(&AudioLightSettingsService::update, this, std::placeholders::_1, std::placeholders::_2), + this, + server, + AUDIO_LIGHT_MODE_WS_PATH, + securityManager, + AuthenticationPredicates::IS_AUTHENTICATED) { server->on( AUDIO_LIGHT_SAVE_MODE_PATH, HTTP_POST, @@ -65,17 +73,28 @@ void AudioLightSettingsService::handleSample() { _state.currentMode->sampleComplete(); } -StateUpdateResult AudioLightSettingsService::update(JsonObject& root, AudioLightSettings& settings) { - String modeId = root["mode_id"] | AUDIO_LIGHT_DEFAULT_MODE; - if (settings.currentMode->getId() == modeId) { - return StateUpdateResult::UNCHANGED; +void AudioLightSettingsService::read(AudioLightSettings& settings, JsonObject& root) { + if (settings.currentMode) { + root["mode_id"] = settings.currentMode->getId(); + settings.currentMode->readAsJson(root); } - AudioLightMode* mode = getMode(modeId); - if (!mode) { - return StateUpdateResult::ERROR; +} + +StateUpdateResult AudioLightSettingsService::update(JsonObject& root, AudioLightSettings& settings) { + String modeId = root["mode_id"]; + + // change mode if required + if (settings.currentMode->getId() != modeId) { + AudioLightMode* mode = getMode(modeId); + if (!mode) { + return StateUpdateResult::ERROR; + } + settings.currentMode = mode; + return StateUpdateResult::CHANGED; } - settings.currentMode = mode; - return StateUpdateResult::CHANGED; + + // update mode settings + return settings.currentMode->updateFromJson(root, LOCAL_ORIGIN); } void AudioLightSettingsService::saveModeConfig(AsyncWebServerRequest* request) { diff --git a/src/AudioLightSettingsService.h b/src/AudioLightSettingsService.h index 69be628f..7ec2c8ee 100644 --- a/src/AudioLightSettingsService.h +++ b/src/AudioLightSettingsService.h @@ -9,24 +9,20 @@ #include #include #include +#include #define NUM_MODES 6 #define AUDIO_LIGHT_SERVICE_PATH "/rest/audioLightSettings" #define AUDIO_LIGHT_SAVE_MODE_PATH "/rest/saveModeSettings" #define AUDIO_LIGHT_LOAD_MODE_PATH "/rest/loadModeSettings" +#define AUDIO_LIGHT_MODE_WS_PATH "/ws/audioLightMode" -#define AUDIO_LIGHT_DEFAULT_MODE "color" +#define LOCAL_ORIGIN "local" class AudioLightSettings { public: AudioLightMode* currentMode = nullptr; - - static void read(AudioLightSettings& settings, JsonObject& root) { - if (settings.currentMode) { - root["mode_id"] = settings.currentMode->getId(); - } - } }; class AudioLightSettingsService : public StatefulService { @@ -42,9 +38,12 @@ class AudioLightSettingsService : public StatefulService { private: HttpEndpoint _httpEndpoint; + WebSocketTxRx _audioLightModeTxRx; AudioLightMode* _modes[NUM_MODES]; + void read(AudioLightSettings& settings, JsonObject& root); StateUpdateResult update(JsonObject& root, AudioLightSettings& settings); + AudioLightMode* getMode(const String& modeId); void enableMode(); void handleSample(); From 97e861825dadda9afeec45aca97baa0ca02eec79 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Mon, 16 Nov 2020 22:53:45 +0000 Subject: [PATCH 21/52] add mode types to ui --- interface/src/project/types.ts | 50 +++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index f4697aec..3a528b53 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -2,8 +2,8 @@ export interface FrequencyData { bands: number[]; } -export enum AudioLightMode { - OFF = "color", +export enum AudioLightModeType { + OFF = "off", COLOR = "color", RAINBOW = "rainbow", LIGHTNING = "lightning", @@ -11,6 +11,48 @@ export enum AudioLightMode { FIRE = "fire" } -export interface AudioLightSettings { - mode_id: AudioLightMode; +export interface OffMode { + mode_id: AudioLightModeType.OFF; } + +export interface ColorMode { + mode_id: AudioLightModeType.COLOR; + color: string; + brightness: number; + audio_enabled: boolean; + included_bands: boolean[] +} + +export interface RainbowMode { + mode_id: AudioLightModeType.RAINBOW; + brightness: number; + rotate_speed: 32; + audio_enabled: boolean; + hue_delta: number; +} + +export interface LightningMode { + mode_id: AudioLightModeType.LIGHTNING; + color: string; + brightness: number; + threshold: number; + flashes: number; + audio_enabled: boolean; + included_bands: boolean[] +} + +export interface ConfettiMode { + mode_id: AudioLightModeType.CONFETTI; + max_changes: number; + brightness: number; + delay: number; +} + +export interface FireMode { + mode_id: AudioLightModeType.FIRE; + cooling: number; + spaking: number; + reverse: boolean; +} + +export type AudioLightMode = OffMode | ColorMode | RainbowMode | LightningMode | ConfettiMode | FireMode; From d33ba362373ade66b8f534f5096c338749c85e60 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Mon, 16 Nov 2020 23:19:08 +0000 Subject: [PATCH 22/52] use websocket for mode screen --- .../src/components/WebSocketController.tsx | 6 +++--- .../project/AudioLightSettingsController.tsx | 16 ++++++---------- .../src/project/AudioLightSettingsForm.tsx | 18 +++++++++--------- interface/src/project/types.ts | 2 +- src/AudioLightSettingsService.cpp | 2 +- src/AudioLightSettingsService.h | 3 ++- 6 files changed, 22 insertions(+), 25 deletions(-) diff --git a/interface/src/components/WebSocketController.tsx b/interface/src/components/WebSocketController.tsx index 5fe9fa33..96e1144f 100644 --- a/interface/src/components/WebSocketController.tsx +++ b/interface/src/components/WebSocketController.tsx @@ -7,7 +7,7 @@ import { addAccessTokenParameter } from '../authentication'; import { extractEventValue } from '.'; export interface WebSocketControllerProps extends WithSnackbarProps { - handleValueChange: (name: keyof D) => (event: React.ChangeEvent) => void; + handleValueChange: (name: keyof D, callback?: () => void) => (event: React.ChangeEvent) => void; setData: (data: D, callback?: () => void) => void; saveData: () => void; @@ -112,9 +112,9 @@ export function webSocketController>(ws } }, wsThrottle); - handleValueChange = (name: keyof D) => (event: React.ChangeEvent) => { + handleValueChange = (name: keyof D, callback?: () => void) => (event: React.ChangeEvent) => { const data = { ...this.state.data!, [name]: extractEventValue(event) }; - this.setState({ data }); + this.setState({ data }, callback); } render() { diff --git a/interface/src/project/AudioLightSettingsController.tsx b/interface/src/project/AudioLightSettingsController.tsx index 5e57fad0..3712da67 100644 --- a/interface/src/project/AudioLightSettingsController.tsx +++ b/interface/src/project/AudioLightSettingsController.tsx @@ -1,24 +1,20 @@ import React, { Component } from 'react'; -import { ENDPOINT_ROOT } from '../api'; +import { WEB_SOCKET_ROOT } from '../api'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { SectionContent, webSocketController, WebSocketControllerProps, WebSocketFormLoader } from '../components'; import AudioLightSettingsForm from './AudioLightSettingsForm'; import { AudioLightSettings } from './types'; -export const AUDIO_LIGHT_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "audioLightSettings"; +export const AUDIO_LIGHT_SETTINGS_ENDPOINT = WEB_SOCKET_ROOT + "audioLightSettings"; -type AudioLightSettingsControllerProps = RestControllerProps; +type AudioLightSettingsControllerProps = WebSocketControllerProps; class AudioLightSettingsController extends Component { - componentDidMount() { - this.props.loadData(); - } - render() { return ( - } /> @@ -28,4 +24,4 @@ class AudioLightSettingsController extends Component; +type AudioLightSettingsFormProps = WebSocketFormProps; class AudioLightSettingsForm extends React.Component { @@ -21,12 +21,12 @@ class AudioLightSettingsForm extends React.Component - Off - Single Color - Rainbow - Lightning - Confetti - Fire + Off + Single Color + Rainbow + Lightning + Confetti + Fire ); } diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 3a528b53..f6971374 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -55,4 +55,4 @@ export interface FireMode { reverse: boolean; } -export type AudioLightMode = OffMode | ColorMode | RainbowMode | LightningMode | ConfettiMode | FireMode; +export type AudioLightSettings = OffMode | ColorMode | RainbowMode | LightningMode | ConfettiMode | FireMode; diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index f26599bf..e96545c0 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -17,7 +17,7 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, std::bind(&AudioLightSettingsService::update, this, std::placeholders::_1, std::placeholders::_2), this, server, - AUDIO_LIGHT_MODE_WS_PATH, + AUDIO_LIGHT_WS_PATH, securityManager, AuthenticationPredicates::IS_AUTHENTICATED) { server->on( diff --git a/src/AudioLightSettingsService.h b/src/AudioLightSettingsService.h index 7ec2c8ee..3d125a20 100644 --- a/src/AudioLightSettingsService.h +++ b/src/AudioLightSettingsService.h @@ -14,9 +14,10 @@ #define NUM_MODES 6 #define AUDIO_LIGHT_SERVICE_PATH "/rest/audioLightSettings" +#define AUDIO_LIGHT_WS_PATH "/ws/audioLightSettings" + #define AUDIO_LIGHT_SAVE_MODE_PATH "/rest/saveModeSettings" #define AUDIO_LIGHT_LOAD_MODE_PATH "/rest/loadModeSettings" -#define AUDIO_LIGHT_MODE_WS_PATH "/ws/audioLightMode" #define LOCAL_ORIGIN "local" From 02a6a336c5a0b44da67365c103b9b2ebe5c38d1d Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Tue, 17 Nov 2020 20:17:04 +0000 Subject: [PATCH 23/52] create binder HoC to hold update functions --- interface/package-lock.json | 59 +++++++++++++++++ interface/package.json | 2 + .../src/components/WebSocketController.tsx | 11 ++-- .../src/components/WebSocketFormLoader.tsx | 2 +- .../project/AudioLightSettingsController.tsx | 4 +- .../src/project/AudioLightSettingsForm.tsx | 63 +++++++++++++------ .../src/project/components/ColorPicker.tsx | 38 +++++++++++ interface/src/project/components/Colors.ts | 5 ++ .../src/project/components/IncludedBands.tsx | 37 +++++++++++ .../project/modes/AudioLightLightningMode.tsx | 63 +++++++++++++++++++ .../src/project/modes/AudioLightMode.tsx | 49 +++++++++++++++ interface/src/project/types.ts | 2 +- 12 files changed, 306 insertions(+), 29 deletions(-) create mode 100644 interface/src/project/components/ColorPicker.tsx create mode 100644 interface/src/project/components/Colors.ts create mode 100644 interface/src/project/components/IncludedBands.tsx create mode 100644 interface/src/project/modes/AudioLightLightningMode.tsx create mode 100644 interface/src/project/modes/AudioLightMode.tsx diff --git a/interface/package-lock.json b/interface/package-lock.json index 014040ec..78363b20 100644 --- a/interface/package-lock.json +++ b/interface/package-lock.json @@ -1198,6 +1198,11 @@ "@hapi/hoek": "^8.3.0" } }, + "@icons/material": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", + "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==" + }, "@jest/console": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", @@ -1739,6 +1744,15 @@ "@types/react": "*" } }, + "@types/react-color": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.4.tgz", + "integrity": "sha512-EswbYJDF1kkrx93/YU+BbBtb46CCtDMvTiGmcOa/c5PETnwTiSWoseJ1oSWeRl/4rUXkhME9bVURvvPg0W5YQw==", + "requires": { + "@types/react": "*", + "@types/reactcss": "*" + } + }, "@types/react-dom": { "version": "16.9.9", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.9.tgz", @@ -1783,6 +1797,14 @@ "@types/react": "*" } }, + "@types/reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-d2gQQ0IL6hXLnoRfVYZukQNWHuVsE75DzFTLPUuyyEhJS8G2VvlE+qfQQ91SJjaMqlURRCNIsX7Jcsw6cEuJlA==", + "requires": { + "@types/react": "*" + } + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", @@ -8363,6 +8385,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, + "lodash-es": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz", + "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==" + }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", @@ -8470,6 +8497,11 @@ "object-visit": "^1.0.0" } }, + "material-colors": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -10940,6 +10972,20 @@ } } }, + "react-color": { + "version": "2.19.3", + "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz", + "integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==", + "requires": { + "@icons/material": "^0.2.4", + "lodash": "^4.17.15", + "lodash-es": "^4.17.15", + "material-colors": "^1.2.1", + "prop-types": "^15.5.10", + "reactcss": "^1.2.0", + "tinycolor2": "^1.4.1" + } + }, "react-dev-utils": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz", @@ -11269,6 +11315,14 @@ "prop-types": "^15.6.2" } }, + "reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "requires": { + "lodash": "^4.0.1" + } + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -13160,6 +13214,11 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "tinycolor2": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", + "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", diff --git a/interface/package.json b/interface/package.json index b4080e10..04833145 100644 --- a/interface/package.json +++ b/interface/package.json @@ -9,6 +9,7 @@ "@types/lodash": "^4.14.165", "@types/node": "^12.12.32", "@types/react": "^16.9.56", + "@types/react-color": "^3.0.4", "@types/react-dom": "^16.9.9", "@types/react-material-ui-form-validator": "^2.1.0", "@types/react-router": "^5.1.8", @@ -20,6 +21,7 @@ "moment": "^2.29.1", "notistack": "^1.0.1", "react": "^16.14.0", + "react-color": "^2.19.3", "react-dom": "^16.14.0", "react-dropzone": "^11.2.4", "react-form-validator-core": "^1.0.0", diff --git a/interface/src/components/WebSocketController.tsx b/interface/src/components/WebSocketController.tsx index 96e1144f..f1759a85 100644 --- a/interface/src/components/WebSocketController.tsx +++ b/interface/src/components/WebSocketController.tsx @@ -11,7 +11,7 @@ export interface WebSocketControllerProps extends WithSnackbarProps { setData: (data: D, callback?: () => void) => void; saveData: () => void; - saveDataAndClear(): () => void; + saveDataAndClear(newData?: D): () => void; connected: boolean; data?: D; @@ -103,12 +103,12 @@ export function webSocketController>(ws } }, wsThrottle); - saveDataAndClear = throttle(() => { + saveDataAndClear = throttle((newData?: D) => { const { ws, connected, data } = this.state; if (connected) { this.setState({ data: undefined - }, () => ws.json(data)); + }, () => ws.json(newData || data)); } }, wsThrottle); @@ -118,14 +118,15 @@ export function webSocketController>(ws } render() { + const { connected, data } = this.state; return ; } diff --git a/interface/src/components/WebSocketFormLoader.tsx b/interface/src/components/WebSocketFormLoader.tsx index ee5f335a..5946a209 100644 --- a/interface/src/components/WebSocketFormLoader.tsx +++ b/interface/src/components/WebSocketFormLoader.tsx @@ -17,7 +17,7 @@ const useStyles = makeStyles((theme: Theme) => }) ); -export type WebSocketFormProps = Omit, "connected"> & { data: D }; +export type WebSocketFormProps = Omit, "connected" | "data"> & { data: D }; interface WebSocketFormLoaderProps extends WebSocketControllerProps { render: (props: WebSocketFormProps) => JSX.Element; diff --git a/interface/src/project/AudioLightSettingsController.tsx b/interface/src/project/AudioLightSettingsController.tsx index 3712da67..da8d3d19 100644 --- a/interface/src/project/AudioLightSettingsController.tsx +++ b/interface/src/project/AudioLightSettingsController.tsx @@ -3,11 +3,11 @@ import { WEB_SOCKET_ROOT } from '../api'; import { SectionContent, webSocketController, WebSocketControllerProps, WebSocketFormLoader } from '../components'; import AudioLightSettingsForm from './AudioLightSettingsForm'; -import { AudioLightSettings } from './types'; +import { AudioLightMode } from './types'; export const AUDIO_LIGHT_SETTINGS_ENDPOINT = WEB_SOCKET_ROOT + "audioLightSettings"; -type AudioLightSettingsControllerProps = WebSocketControllerProps; +type AudioLightSettingsControllerProps = WebSocketControllerProps>; class AudioLightSettingsController extends Component { diff --git a/interface/src/project/AudioLightSettingsForm.tsx b/interface/src/project/AudioLightSettingsForm.tsx index 98516c58..b86a50d5 100644 --- a/interface/src/project/AudioLightSettingsForm.tsx +++ b/interface/src/project/AudioLightSettingsForm.tsx @@ -1,33 +1,56 @@ -import React from 'react'; +import React, { Fragment } from 'react'; import { WebSocketFormProps } from '../components'; -import { AudioLightModeType, AudioLightSettings } from './types'; +import { AudioLightModeType, AudioLightMode } from './types'; import { TextField, MenuItem } from '@material-ui/core'; +import AudioLightLightningMode from './modes/AudioLightLightningMode'; -type AudioLightSettingsFormProps = WebSocketFormProps; +type AudioLightSettingsFormProps = WebSocketFormProps>; class AudioLightSettingsForm extends React.Component { + selectModeComponent(): React.ElementType | null { + const mode_id = this.props.data.mode_id; + switch (mode_id) { + // case AudioLightModeType.COLOR: + // return AudioLightColorMode; + // case AudioLightModeType.SPECTRUM: + // return AudioLightSpectrumMode; + // case AudioLightModeType.RAINBOW: + // return AudioLightRainbowMode; + case AudioLightModeType.LIGHTNING: + return AudioLightLightningMode; + //case AudioLightModeType.CONFETTI: + // return AudioLightConfettiMode; + //case AudioLightModeType.FIRE: + // return AudioLightFireMode; + } + return null; + } render() { - const { data, handleValueChange, saveData } = this.props; + const { data, saveDataAndClear } = this.props; + const ModeComponent = this.selectModeComponent(); return ( - - Off - Single Color - Rainbow - Lightning - Confetti - Fire - + + saveDataAndClear({ mode_id: event.target.value as AudioLightModeType })} + fullWidth + margin="normal" + variant="outlined" + select> + Off + Single Color + Rainbow + Lightning + Confetti + + + { ModeComponent && } + ); } } diff --git a/interface/src/project/components/ColorPicker.tsx b/interface/src/project/components/ColorPicker.tsx new file mode 100644 index 00000000..634d8716 --- /dev/null +++ b/interface/src/project/components/ColorPicker.tsx @@ -0,0 +1,38 @@ +import React from 'react'; + +import { Box } from '@material-ui/core'; +import { ColorChangeHandler, HuePicker, TwitterPicker } from 'react-color' + +import { SimpleColors } from './Colors' + +interface ColorPickerProps { + color: string; + onChange: ColorChangeHandler; +} + +class ColorPicker extends React.Component { + render() { + const { + color, + onChange + } = this.props; + return ( + + + + + ); + } +} + +export default ColorPicker; diff --git a/interface/src/project/components/Colors.ts b/interface/src/project/components/Colors.ts new file mode 100644 index 00000000..bd06beb4 --- /dev/null +++ b/interface/src/project/components/Colors.ts @@ -0,0 +1,5 @@ +export const SimpleColors = [ + "#FFFFFF", "#FF0000", "#00FF00", "#0000FF", "#CD5C5C", + "#00FF7F", "#4682B4", "#B22222", "#00BFFF", "#4B0082", + "#FF7F50", "#40E0D0", "#800080", "#FFFF00", "#00008B", + "#FF1493"]; diff --git a/interface/src/project/components/IncludedBands.tsx b/interface/src/project/components/IncludedBands.tsx new file mode 100644 index 00000000..78e7d19b --- /dev/null +++ b/interface/src/project/components/IncludedBands.tsx @@ -0,0 +1,37 @@ +import React from 'react'; + +import Switch from '@material-ui/core/Switch'; + +interface IncludedBandProps { + value: boolean[]; + onChange: (value: boolean[]) => void; +} + +class IncludedBands extends React.Component { + + handleChange = (ordinal: number) => (event: React.ChangeEvent) => { + const { value, onChange } = this.props; + const newValue = [...value]; + newValue[ordinal] = event.target.checked; + onChange(newValue); + }; + + render() { + const { value } = this.props; + return ( +
+ {value.map((v, i) => ( + + ))} +
+ ); + } +} + +export default IncludedBands; \ No newline at end of file diff --git a/interface/src/project/modes/AudioLightLightningMode.tsx b/interface/src/project/modes/AudioLightLightningMode.tsx new file mode 100644 index 00000000..f78d3d6a --- /dev/null +++ b/interface/src/project/modes/AudioLightLightningMode.tsx @@ -0,0 +1,63 @@ +import React from 'react'; + +import FormLabel from '@material-ui/core/FormLabel'; +import Slider from '@material-ui/core/Slider'; + +import { LightningMode } from '../types'; +import IncludedBands from '../components/IncludedBands'; +import ColorPicker from '../components/ColorPicker'; +import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; + +type AudioLightLightningModeProps = AudioLightModeProps; + +class AudioLightLightningMode extends React.Component { + + render() { + const { data, handleChange, handleSliderChange, handleColorChange } = this.props; + + return ( +
+ Color + + + Brightness + + + Flashes + + + Threshold + + + Included Bands + +
+ ); + } +} + +export default audioLightMode(AudioLightLightningMode); diff --git a/interface/src/project/modes/AudioLightMode.tsx b/interface/src/project/modes/AudioLightMode.tsx new file mode 100644 index 00000000..f5208cc5 --- /dev/null +++ b/interface/src/project/modes/AudioLightMode.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import { ColorResult } from "react-color"; +import { WebSocketFormProps } from "../../components"; +import { AudioLightMode } from "../types"; + +export interface AudioLightModeProps { + handleValueChange: (name: keyof D, callback?: () => void) => (event: React.ChangeEvent) => void; + handleChange: (name: keyof D) => (value: T) => void; + handleSliderChange: (name: keyof D) => (event: React.ChangeEvent<{}>, value: number | number[]) => void; + handleColorChange: (name: keyof D) => (value: ColorResult) => void; + data: D; +} + +export function audioLightMode(AudioLightMode: React.ComponentType>) { + return class extends React.Component> { + + handleChange = (name: keyof D) => (value: T) => { + const { data, setData, saveData } = this.props; + setData({ ...data, [name]: value }, saveData); + } + + handleValueChange = (name: keyof D) => { + const { handleValueChange, saveData } = this.props; + return handleValueChange(name, saveData) + } + + handleSliderChange = (name: keyof D) => (event: React.ChangeEvent<{}>, value: number | number[]) => { + const { setData, saveData } = this.props; + setData({ ...this.props.data!, [name]: value }, saveData); + } + + handleColorChange = (name: keyof D) => (value: ColorResult) => { + const { setData, saveData } = this.props; + setData({ ...this.props.data!, [name]: value.hex }, saveData); + } + + render() { + return ( + + ); + } + + } +} diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index f6971374..3a528b53 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -55,4 +55,4 @@ export interface FireMode { reverse: boolean; } -export type AudioLightSettings = OffMode | ColorMode | RainbowMode | LightningMode | ConfettiMode | FireMode; +export type AudioLightMode = OffMode | ColorMode | RainbowMode | LightningMode | ConfettiMode | FireMode; From d9de0d79e514d40682e8da00dae399621d19e475 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Tue, 17 Nov 2020 20:23:49 +0000 Subject: [PATCH 24/52] pad color picker appropriately --- .../src/project/components/ColorPicker.tsx | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/interface/src/project/components/ColorPicker.tsx b/interface/src/project/components/ColorPicker.tsx index 634d8716..44d77f72 100644 --- a/interface/src/project/components/ColorPicker.tsx +++ b/interface/src/project/components/ColorPicker.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Fragment } from 'react'; import { Box } from '@material-ui/core'; import { ColorChangeHandler, HuePicker, TwitterPicker } from 'react-color' @@ -17,20 +17,24 @@ class ColorPicker extends React.Component { onChange } = this.props; return ( - - - - + + + + + + + + ); } } From 8d586f5c2c8b8d902796858405a4954a0611bfa8 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Tue, 17 Nov 2020 21:17:15 +0000 Subject: [PATCH 25/52] add ui support for remaining modes --- .../src/project/AudioLightSettingsForm.tsx | 29 ++++--- .../src/project/modes/AudioLightColorMode.tsx | 57 +++++++++++++ .../project/modes/AudioLightConfettiMode.tsx | 50 ++++++++++++ .../src/project/modes/AudioLightFireMode.tsx | 52 ++++++++++++ .../project/modes/AudioLightLightningMode.tsx | 79 ++++++++++--------- .../project/modes/AudioLightRainbowMode.tsx | 59 ++++++++++++++ interface/src/project/types.ts | 2 +- 7 files changed, 276 insertions(+), 52 deletions(-) create mode 100644 interface/src/project/modes/AudioLightColorMode.tsx create mode 100644 interface/src/project/modes/AudioLightConfettiMode.tsx create mode 100644 interface/src/project/modes/AudioLightFireMode.tsx create mode 100644 interface/src/project/modes/AudioLightRainbowMode.tsx diff --git a/interface/src/project/AudioLightSettingsForm.tsx b/interface/src/project/AudioLightSettingsForm.tsx index b86a50d5..82062a8d 100644 --- a/interface/src/project/AudioLightSettingsForm.tsx +++ b/interface/src/project/AudioLightSettingsForm.tsx @@ -1,9 +1,14 @@ import React, { Fragment } from 'react'; +import { TextField, MenuItem } from '@material-ui/core'; + +import AudioLightLightningMode from './modes/AudioLightLightningMode'; +import AudioLightRainbowMode from './modes/AudioLightRainbowMode'; +import AudioLightFireMode from './modes/AudioLightFireMode'; +import AudioLightColorMode from './modes/AudioLightColorMode'; import { WebSocketFormProps } from '../components'; import { AudioLightModeType, AudioLightMode } from './types'; -import { TextField, MenuItem } from '@material-ui/core'; -import AudioLightLightningMode from './modes/AudioLightLightningMode'; +import AudioLightConfettiMode from './modes/AudioLightConfettiMode'; type AudioLightSettingsFormProps = WebSocketFormProps>; @@ -12,18 +17,16 @@ class AudioLightSettingsForm extends React.ComponentRainbow Lightning Confetti - + Fire { ModeComponent && } diff --git a/interface/src/project/modes/AudioLightColorMode.tsx b/interface/src/project/modes/AudioLightColorMode.tsx new file mode 100644 index 00000000..0961b184 --- /dev/null +++ b/interface/src/project/modes/AudioLightColorMode.tsx @@ -0,0 +1,57 @@ +import React from 'react'; + +import FormLabel from '@material-ui/core/FormLabel'; +import Slider from '@material-ui/core/Slider'; + +import { ColorMode } from '../types'; +import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; +import { Box, Switch } from '@material-ui/core'; +import ColorPicker from '../components/ColorPicker'; +import IncludedBands from '../components/IncludedBands'; + +type AudioLightColorModeProps = AudioLightModeProps; + +class AudioLightColorMode extends React.Component { + + render() { + const { data, handleChange, handleValueChange, handleSliderChange, handleColorChange } = this.props; + + return ( +
+ + Audio Enabled + + + + Color + + + + Brightness + + + Included Bands + +
+ ); + } +} + +export default audioLightMode(AudioLightColorMode); diff --git a/interface/src/project/modes/AudioLightConfettiMode.tsx b/interface/src/project/modes/AudioLightConfettiMode.tsx new file mode 100644 index 00000000..ae564cb1 --- /dev/null +++ b/interface/src/project/modes/AudioLightConfettiMode.tsx @@ -0,0 +1,50 @@ +import React from 'react'; + +import FormLabel from '@material-ui/core/FormLabel'; +import Slider from '@material-ui/core/Slider'; +import { Box } from '@material-ui/core'; + +import { ConfettiMode } from '../types'; +import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; + +type AudioLightConfettiModeProps = AudioLightModeProps; + +class AudioLightConfettiMode extends React.Component { + + render() { + const { data, handleSliderChange } = this.props; + + return ( +
+ + Palette Changes (per cycle) + + Brightness + + Delay + + +
+ ); + } +} + +export default audioLightMode(AudioLightConfettiMode); diff --git a/interface/src/project/modes/AudioLightFireMode.tsx b/interface/src/project/modes/AudioLightFireMode.tsx new file mode 100644 index 00000000..e0aa6fcd --- /dev/null +++ b/interface/src/project/modes/AudioLightFireMode.tsx @@ -0,0 +1,52 @@ +import React from 'react'; + +import FormLabel from '@material-ui/core/FormLabel'; +import Slider from '@material-ui/core/Slider'; + +import { FireMode } from '../types'; +import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; +import { Box, Switch } from '@material-ui/core'; + +type AudioLightFireModeProps = AudioLightModeProps; + +class AudioLightFireMode extends React.Component { + + render() { + const { data, handleValueChange, handleSliderChange } = this.props; + + return ( +
+ + Reverse + + + + + Cooling + + + Sparking + + +
+ ); + } +} + +export default audioLightMode(AudioLightFireMode); diff --git a/interface/src/project/modes/AudioLightLightningMode.tsx b/interface/src/project/modes/AudioLightLightningMode.tsx index f78d3d6a..97b47047 100644 --- a/interface/src/project/modes/AudioLightLightningMode.tsx +++ b/interface/src/project/modes/AudioLightLightningMode.tsx @@ -7,6 +7,7 @@ import { LightningMode } from '../types'; import IncludedBands from '../components/IncludedBands'; import ColorPicker from '../components/ColorPicker'; import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; +import { Box } from '@material-ui/core'; type AudioLightLightningModeProps = AudioLightModeProps; @@ -17,44 +18,46 @@ class AudioLightLightningMode extends React.Component - Color - - - Brightness - - - Flashes - - - Threshold - - - Included Bands - + + Color + + + + Brightness + + Flashes + + Threshold + + + + Included Bands + + ); } diff --git a/interface/src/project/modes/AudioLightRainbowMode.tsx b/interface/src/project/modes/AudioLightRainbowMode.tsx new file mode 100644 index 00000000..951c6b7f --- /dev/null +++ b/interface/src/project/modes/AudioLightRainbowMode.tsx @@ -0,0 +1,59 @@ +import React from 'react'; + +import FormLabel from '@material-ui/core/FormLabel'; +import Slider from '@material-ui/core/Slider'; + +import { RainbowMode } from '../types'; +import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; +import { Box, Switch } from '@material-ui/core'; + +type AudioLightRainbowModeProps = AudioLightModeProps; + +class AudioLightRainbowMode extends React.Component { + + render() { + const { data, handleValueChange, handleSliderChange } = this.props; + + return ( +
+ + Audio Enabled + + + + Brightness + + Rotate Speed + + Hue Delta + + +
+ ); + } +} + +export default audioLightMode(AudioLightRainbowMode); diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 3a528b53..37277929 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -51,7 +51,7 @@ export interface ConfettiMode { export interface FireMode { mode_id: AudioLightModeType.FIRE; cooling: number; - spaking: number; + sparking: number; reverse: boolean; } From 896befa4963e53ff6d11df93d120c42baf43959d Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Wed, 18 Nov 2020 22:29:24 +0000 Subject: [PATCH 26/52] use WS2811 add flags to have webserver run main program CPU core --- platformio.ini | 4 +++- src/LedSettingsService.h | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/platformio.ini b/platformio.ini index 368ea235..52f62c48 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,6 +15,8 @@ build_flags= -D CORS_ORIGIN=\"http://localhost:3000\" ; Uncomment PROGMEM_WWW to enable the storage of the WWW data in PROGMEM -D PROGMEM_WWW + -D FASTLED_ALLOW_INTERRUPTS=0 + -D CONFIG_ASYNC_TCP_RUNNING_CORE=1 ; ensure transitive dependencies are included for correct platforms only lib_compat_mode = strict @@ -29,7 +31,7 @@ framework = arduino monitor_speed = 115200 extra_scripts = -; pre:scripts/build_interface.py + pre:scripts/build_interface.py lib_deps = ArduinoJson@>=6.0.0,<7.0.0 diff --git a/src/LedSettingsService.h b/src/LedSettingsService.h index 3e8ae7c3..100a807f 100644 --- a/src/LedSettingsService.h +++ b/src/LedSettingsService.h @@ -10,17 +10,17 @@ #include #include -/* #define LED_DATA_PIN 21 -#define COLOR_ORDER GRB -#define LED_TYPE WS2812 -#define NUM_LEDS 64 -*/ +#define COLOR_ORDER RGB +#define LED_TYPE WS2811 +#define NUM_LEDS 50 +/* #define LED_DATA_PIN 21 #define COLOR_ORDER GRB // GBR #define LED_TYPE WS2812B #define NUM_LEDS 9 +*/ #ifndef FACTORY_LED_BRIGHTNESS #define FACTORY_LED_BRIGHTNESS 128 From a28a4e9f4f04fa448ddfa41b246faddf6864dd70 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Fri, 20 Nov 2020 23:19:21 +0000 Subject: [PATCH 27/52] add pacifica mode --- .../src/project/AudioLightSettingsForm.tsx | 1 + interface/src/project/types.ts | 3 +- src/AudioLightSettingsService.cpp | 1 + src/AudioLightSettingsService.h | 3 +- src/PacificaMode.cpp | 121 ++++++++++++++++++ src/PacificaMode.h | 93 ++++++++++++++ 6 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 src/PacificaMode.cpp create mode 100644 src/PacificaMode.h diff --git a/interface/src/project/AudioLightSettingsForm.tsx b/interface/src/project/AudioLightSettingsForm.tsx index 82062a8d..b68b8167 100644 --- a/interface/src/project/AudioLightSettingsForm.tsx +++ b/interface/src/project/AudioLightSettingsForm.tsx @@ -51,6 +51,7 @@ class AudioLightSettingsForm extends React.ComponentLightning Confetti Fire + Pacifica { ModeComponent && } diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 37277929..6120faa4 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -8,7 +8,8 @@ export enum AudioLightModeType { RAINBOW = "rainbow", LIGHTNING = "lightning", CONFETTI = "confetti", - FIRE = "fire" + FIRE = "fire", + PACIFICA="pacifica" } export interface OffMode { diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index e96545c0..cc5682fb 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -39,6 +39,7 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, _modes[3] = new ConfettiMode(server, fs, securityManager, ledSettingsService, frequencySampler); _modes[4] = new FireMode(server, fs, securityManager, ledSettingsService, frequencySampler); _modes[5] = new OffMode(server, fs, securityManager, ledSettingsService, frequencySampler); + _modes[6] = new PacificaMode(server, fs, securityManager, ledSettingsService, frequencySampler); } void AudioLightSettingsService::begin() { diff --git a/src/AudioLightSettingsService.h b/src/AudioLightSettingsService.h index 3d125a20..b0cac8ef 100644 --- a/src/AudioLightSettingsService.h +++ b/src/AudioLightSettingsService.h @@ -9,9 +9,10 @@ #include #include #include +#include #include -#define NUM_MODES 6 +#define NUM_MODES 7 #define AUDIO_LIGHT_SERVICE_PATH "/rest/audioLightSettings" #define AUDIO_LIGHT_WS_PATH "/ws/audioLightSettings" diff --git a/src/PacificaMode.cpp b/src/PacificaMode.cpp new file mode 100644 index 00000000..05dd69fe --- /dev/null +++ b/src/PacificaMode.cpp @@ -0,0 +1,121 @@ +#include + +PacificaMode::PacificaMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler) : + AudioLightModeImpl(server, + fs, + securityManager, + ledSettingsService, + frequencySampler, + PacificaModeSettings::read, + PacificaModeSettings::update, + PACIFICA_MODE_ID) { + addUpdateHandler([&](const String& originId) { enable(); }, false); +}; + +void PacificaMode::enable() { + _refresh = true; +} + +void PacificaMode::tick() { + _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + // Increment the four "color index start" counters, one for each wave layer. + // Each is incremented at a different speed, and the speeds vary over time. + static uint16_t sCIStart1, sCIStart2, sCIStart3, sCIStart4; + static uint32_t sLastms = 0; + uint32_t ms = millis(); + uint32_t deltams = ms - sLastms; + sLastms = ms; + uint16_t speedfactor1 = beatsin16(3, 179, 269); + uint16_t speedfactor2 = beatsin16(4, 179, 269); + uint32_t deltams1 = (deltams * speedfactor1) / 256; + uint32_t deltams2 = (deltams * speedfactor2) / 256; + uint32_t deltams21 = (deltams1 + deltams2) / 2; + sCIStart1 += (deltams1 * beatsin88(1011, 10, 13)); + sCIStart2 -= (deltams21 * beatsin88(777, 8, 11)); + sCIStart3 -= (deltams1 * beatsin88(501, 5, 7)); + sCIStart4 -= (deltams2 * beatsin88(257, 4, 6)); + + // Clear out the LED array to a dim background blue-green + fill_solid(leds, NUM_LEDS, CRGB(2, 6, 10)); + + // Render each of four layers, with different scales and speeds, that vary over time + pacifica_one_layer(leds, + numLeds, + pacifica_palette_1, + sCIStart1, + beatsin16(3, 11 * 256, 14 * 256), + beatsin8(10, 70, 130), + 0 - beat16(301)); + pacifica_one_layer(leds, + numLeds, + pacifica_palette_2, + sCIStart2, + beatsin16(4, 6 * 256, 9 * 256), + beatsin8(17, 40, 80), + beat16(401)); + pacifica_one_layer(leds, numLeds, pacifica_palette_3, sCIStart3, 6 * 256, beatsin8(9, 10, 38), 0 - beat16(503)); + pacifica_one_layer(leds, numLeds, pacifica_palette_3, sCIStart4, 5 * 256, beatsin8(8, 10, 28), beat16(601)); + + // Add brighter 'whitecaps' where the waves lines up more + pacifica_add_whitecaps(leds, numLeds); + + // Deepen the blues and greens a bit + pacifica_deepen_colors(leds, numLeds); + + // Show the leds + ledController->showLeds(); + }); +} + +// Add one layer of waves into the led array +void PacificaMode::pacifica_one_layer(CRGB* leds, + const uint16_t numLeds, + CRGBPalette16& p, + uint16_t cistart, + uint16_t wavescale, + uint8_t bri, + uint16_t ioff) { + uint16_t ci = cistart; + uint16_t waveangle = ioff; + uint16_t wavescale_half = (wavescale / 2) + 20; + for (uint16_t i = 0; i < numLeds; i++) { + waveangle += 250; + uint16_t s16 = sin16(waveangle) + 32768; + uint16_t cs = scale16(s16, wavescale_half) + wavescale_half; + ci += cs; + uint16_t sindex16 = sin16(ci) + 32768; + uint8_t sindex8 = scale16(sindex16, 240); + CRGB c = ColorFromPalette(p, sindex8, bri, LINEARBLEND); + leds[i] += c; + } +} + +// Add extra 'white' to areas where the four layers of light have lined up brightly +void PacificaMode::pacifica_add_whitecaps(CRGB* leds, const uint16_t numLeds) { + uint8_t basethreshold = beatsin8(9, 55, 65); + uint8_t wave = beat8(7); + + for (uint16_t i = 0; i < numLeds; i++) { + uint8_t threshold = scale8(sin8(wave), 20) + basethreshold; + wave += 7; + uint8_t l = leds[i].getAverageLight(); + if (l > threshold) { + uint8_t overage = l - threshold; + uint8_t overage2 = qadd8(overage, overage); + leds[i] += CRGB(overage, overage2, qadd8(overage2, overage2)); + } + } +} + +// Deepen the blues and greens +void PacificaMode::pacifica_deepen_colors(CRGB* leds, const uint16_t numLeds) { + for (uint16_t i = 0; i < numLeds; i++) { + leds[i].blue = scale8(leds[i].blue, 145); + leds[i].green = scale8(leds[i].green, 200); + leds[i] |= CRGB(2, 5, 7); + } +} diff --git a/src/PacificaMode.h b/src/PacificaMode.h new file mode 100644 index 00000000..38ebd935 --- /dev/null +++ b/src/PacificaMode.h @@ -0,0 +1,93 @@ +#ifndef PacificaMode_h +#define PacificaMode_h + +#include +#include +#include + +#define PACIFICA_MODE_ID "pacifica" + +class PacificaModeSettings { + public: + static void read(PacificaModeSettings& settings, JsonObject& root) { + } + + static StateUpdateResult update(JsonObject& root, PacificaModeSettings& settings) { + return StateUpdateResult::CHANGED; + } +}; + +class PacificaMode : public AudioLightModeImpl { + private: + CRGBPalette16 pacifica_palette_1 = {0x000507, + 0x000409, + 0x00030B, + 0x00030D, + 0x000210, + 0x000212, + 0x000114, + 0x000117, + 0x000019, + 0x00001C, + 0x000026, + 0x000031, + 0x00003B, + 0x000046, + 0x14554B, + 0x28AA50}; + CRGBPalette16 pacifica_palette_2 = {0x000507, + 0x000409, + 0x00030B, + 0x00030D, + 0x000210, + 0x000212, + 0x000114, + 0x000117, + 0x000019, + 0x00001C, + 0x000026, + 0x000031, + 0x00003B, + 0x000046, + 0x0C5F52, + 0x19BE5F}; + CRGBPalette16 pacifica_palette_3 = {0x000208, + 0x00030E, + 0x000514, + 0x00061A, + 0x000820, + 0x000927, + 0x000B2D, + 0x000C33, + 0x000E39, + 0x001040, + 0x001450, + 0x001860, + 0x001C70, + 0x002080, + 0x1040BF, + 0x2060FF}; + + boolean _refresh = true; + + void pacifica_one_layer(CRGB* leds, + const uint16_t numLeds, + CRGBPalette16& p, + uint16_t cistart, + uint16_t wavescale, + uint8_t bri, + uint16_t ioff); + void pacifica_deepen_colors(CRGB* leds, const uint16_t numLeds); + void pacifica_add_whitecaps(CRGB* leds, const uint16_t numLeds); + + public: + PacificaMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler); + void tick(); + void enable(); +}; + +#endif From ff85ad37467d1436266866d13f0343c25c942fc5 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sat, 21 Nov 2020 19:42:01 +0000 Subject: [PATCH 28/52] add pride mode --- .../src/project/AudioLightSettingsForm.tsx | 1 + interface/src/project/types.ts | 13 +++- src/AudioLightSettingsService.cpp | 1 + src/AudioLightSettingsService.h | 3 +- src/PrideMode.cpp | 66 +++++++++++++++++++ src/PrideMode.h | 35 ++++++++++ 6 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 src/PrideMode.cpp create mode 100644 src/PrideMode.h diff --git a/interface/src/project/AudioLightSettingsForm.tsx b/interface/src/project/AudioLightSettingsForm.tsx index b68b8167..e3a7cdf8 100644 --- a/interface/src/project/AudioLightSettingsForm.tsx +++ b/interface/src/project/AudioLightSettingsForm.tsx @@ -52,6 +52,7 @@ class AudioLightSettingsForm extends React.ComponentConfetti Fire Pacifica + Pride { ModeComponent && } diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 6120faa4..e3f8bd20 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -9,7 +9,8 @@ export enum AudioLightModeType { LIGHTNING = "lightning", CONFETTI = "confetti", FIRE = "fire", - PACIFICA="pacifica" + PACIFICA = "pacifica", + PRIDE = "pride" } export interface OffMode { @@ -56,4 +57,12 @@ export interface FireMode { reverse: boolean; } -export type AudioLightMode = OffMode | ColorMode | RainbowMode | LightningMode | ConfettiMode | FireMode; +export interface PacificaMode { + mode_id: AudioLightModeType.PACIFICA; +} + +export interface PrideMode { + mode_id: AudioLightModeType.PRIDE; +} + +export type AudioLightMode = OffMode | ColorMode | RainbowMode | LightningMode | ConfettiMode | FireMode | PacificaMode | PrideMode; diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index cc5682fb..406a474a 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -40,6 +40,7 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, _modes[4] = new FireMode(server, fs, securityManager, ledSettingsService, frequencySampler); _modes[5] = new OffMode(server, fs, securityManager, ledSettingsService, frequencySampler); _modes[6] = new PacificaMode(server, fs, securityManager, ledSettingsService, frequencySampler); + _modes[7] = new PrideMode(server, fs, securityManager, ledSettingsService, frequencySampler); } void AudioLightSettingsService::begin() { diff --git a/src/AudioLightSettingsService.h b/src/AudioLightSettingsService.h index b0cac8ef..3fbf064b 100644 --- a/src/AudioLightSettingsService.h +++ b/src/AudioLightSettingsService.h @@ -10,9 +10,10 @@ #include #include #include +#include #include -#define NUM_MODES 7 +#define NUM_MODES 8 #define AUDIO_LIGHT_SERVICE_PATH "/rest/audioLightSettings" #define AUDIO_LIGHT_WS_PATH "/ws/audioLightSettings" diff --git a/src/PrideMode.cpp b/src/PrideMode.cpp new file mode 100644 index 00000000..c7905de7 --- /dev/null +++ b/src/PrideMode.cpp @@ -0,0 +1,66 @@ +#include + +PrideMode::PrideMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler) : + AudioLightModeImpl(server, + fs, + securityManager, + ledSettingsService, + frequencySampler, + PrideModeSettings::read, + PrideModeSettings::update, + PRIDE_MODE_ID) { + addUpdateHandler([&](const String& originId) { enable(); }, false); +}; + +void PrideMode::enable() { + _refresh = true; +} + +void PrideMode::tick() { + _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + static uint16_t sPseudotime = 0; + static uint16_t sLastMillis = 0; + static uint16_t sHue16 = 0; + + uint8_t sat8 = beatsin88(87, 220, 250); + uint8_t brightdepth = beatsin88(341, 96, 224); + uint16_t brightnessthetainc16 = beatsin88(203, (25 * 256), (40 * 256)); + uint8_t msmultiplier = beatsin88(147, 23, 60); + + uint16_t hue16 = sHue16; // gHue * 256; + uint16_t hueinc16 = beatsin88(113, 1, 3000); + + uint16_t ms = millis(); + uint16_t deltams = ms - sLastMillis; + sLastMillis = ms; + sPseudotime += deltams * msmultiplier; + sHue16 += deltams * beatsin88(400, 5, 9); + uint16_t brightnesstheta16 = sPseudotime; + + for (uint16_t i = 0; i < numLeds; i++) { + hue16 += hueinc16; + uint8_t hue8 = hue16 / 256; + + brightnesstheta16 += brightnessthetainc16; + uint16_t b16 = sin16(brightnesstheta16) + 32768; + + uint16_t bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536; + uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536; + bri8 += (255 - brightdepth); + + CRGB newcolor = CHSV(hue8, sat8, bri8); + + uint16_t pixelnumber = i; + pixelnumber = (numLeds - 1) - pixelnumber; + + nblend(leds[pixelnumber], newcolor, 64); + } + + // Show the leds + ledController->showLeds(); + }); +} diff --git a/src/PrideMode.h b/src/PrideMode.h new file mode 100644 index 00000000..4634323e --- /dev/null +++ b/src/PrideMode.h @@ -0,0 +1,35 @@ +#ifndef PrideMode_h +#define PrideMode_h + +#include +#include +#include + +#define PRIDE_MODE_ID "pride" + +class PrideModeSettings { + public: + static void read(PrideModeSettings& settings, JsonObject& root) { + } + + static StateUpdateResult update(JsonObject& root, PrideModeSettings& settings) { + return StateUpdateResult::CHANGED; + } +}; + +class PrideMode : public AudioLightModeImpl { + private: + + boolean _refresh = true; + + public: + PrideMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + FrequencySampler* frequencySampler); + void tick(); + void enable(); +}; + +#endif From 2ba9bab5e050b574006103a53cda0629ee3ae7ef Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sat, 21 Nov 2020 20:22:17 +0000 Subject: [PATCH 29/52] add load and save buttons --- interface/.env.development | 4 +- .../project/AudioLightSettingsController.tsx | 4 +- .../src/project/AudioLightSettingsForm.tsx | 44 ++++++++++++++++++- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/interface/.env.development b/interface/.env.development index b12cfd04..f7bb8583 100644 --- a/interface/.env.development +++ b/interface/.env.development @@ -1,4 +1,4 @@ # Change the IP address to that of your ESP device to enable local development of the UI. # Remember to also enable CORS in platformio.ini before uploading the code to the device. -REACT_APP_HTTP_ROOT=http://192.168.0.88 -REACT_APP_WEB_SOCKET_ROOT=ws://192.168.0.88 +REACT_APP_HTTP_ROOT=http://192.168.0.77 +REACT_APP_WEB_SOCKET_ROOT=ws://192.168.0.77 diff --git a/interface/src/project/AudioLightSettingsController.tsx b/interface/src/project/AudioLightSettingsController.tsx index da8d3d19..a88a768c 100644 --- a/interface/src/project/AudioLightSettingsController.tsx +++ b/interface/src/project/AudioLightSettingsController.tsx @@ -1,10 +1,12 @@ import React, { Component } from 'react'; -import { WEB_SOCKET_ROOT } from '../api'; +import { ENDPOINT_ROOT, WEB_SOCKET_ROOT } from '../api'; import { SectionContent, webSocketController, WebSocketControllerProps, WebSocketFormLoader } from '../components'; import AudioLightSettingsForm from './AudioLightSettingsForm'; import { AudioLightMode } from './types'; +export const AUDIO_LIGHT_SAVE_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "saveModeSettings"; +export const AUDIO_LIGHT_LOAD_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "loadModeSettings"; export const AUDIO_LIGHT_SETTINGS_ENDPOINT = WEB_SOCKET_ROOT + "audioLightSettings"; type AudioLightSettingsControllerProps = WebSocketControllerProps>; diff --git a/interface/src/project/AudioLightSettingsForm.tsx b/interface/src/project/AudioLightSettingsForm.tsx index e3a7cdf8..dd2a4bcd 100644 --- a/interface/src/project/AudioLightSettingsForm.tsx +++ b/interface/src/project/AudioLightSettingsForm.tsx @@ -1,19 +1,53 @@ import React, { Fragment } from 'react'; import { TextField, MenuItem } from '@material-ui/core'; +import SaveIcon from '@material-ui/icons/Save'; +import LoadIcon from '@material-ui/icons/SaveAlt'; import AudioLightLightningMode from './modes/AudioLightLightningMode'; import AudioLightRainbowMode from './modes/AudioLightRainbowMode'; import AudioLightFireMode from './modes/AudioLightFireMode'; import AudioLightColorMode from './modes/AudioLightColorMode'; -import { WebSocketFormProps } from '../components'; +import { FormActions, FormButton, WebSocketFormProps } from '../components'; import { AudioLightModeType, AudioLightMode } from './types'; import AudioLightConfettiMode from './modes/AudioLightConfettiMode'; +import { redirectingAuthorizedFetch } from '../authentication'; +import { AUDIO_LIGHT_LOAD_SETTINGS_ENDPOINT, AUDIO_LIGHT_SAVE_SETTINGS_ENDPOINT } from './AudioLightSettingsController'; type AudioLightSettingsFormProps = WebSocketFormProps>; class AudioLightSettingsForm extends React.Component { + saveMode = async () => { + const { enqueueSnackbar } = this.props; + try { + const response = await redirectingAuthorizedFetch(AUDIO_LIGHT_SAVE_SETTINGS_ENDPOINT, { method: 'POST' }); + if (response.status === 200) { + enqueueSnackbar("Mode settings saved", { variant: 'success' }); + } else { + throw new Error(`Unexpected response code ${response.status}`); + } + } catch (error) { + const errorMessage = error.message || "Unknown error"; + enqueueSnackbar("Problem saving: " + errorMessage, { variant: 'error' }); + } + } + + loadMode = async () => { + const { enqueueSnackbar } = this.props; + try { + const response = await redirectingAuthorizedFetch(AUDIO_LIGHT_LOAD_SETTINGS_ENDPOINT, { method: 'POST' }); + if (response.status === 200) { + enqueueSnackbar("Mode settings loaded", { variant: 'success' }); + } else { + throw new Error(`Unexpected response code ${response.status}`); + } + } catch (error) { + const errorMessage = error.message || "Unknown error"; + enqueueSnackbar("Problem loading: " + errorMessage, { variant: 'error' }); + } + } + selectModeComponent(): React.ElementType | null { const mode_id = this.props.data.mode_id; switch (mode_id) { @@ -55,6 +89,14 @@ class AudioLightSettingsForm extends React.ComponentPride { ModeComponent && } + + } variant="contained" color="primary" onClick={this.saveMode}> + Save Settings + + } variant="contained" color="secondary" onClick={this.loadMode}> + Load Settings + + ); } From b54236f2423bc663d2eea4e90592d800912db317 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sat, 21 Nov 2020 22:22:11 +0000 Subject: [PATCH 30/52] experiment with palette manager --- lib/framework/SecuritySettingsService.h | 4 +- src/FireMode.cpp | 6 +- src/FireMode.h | 32 ++++--- src/PaletteManager.h | 106 ++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 15 deletions(-) create mode 100644 src/PaletteManager.h diff --git a/lib/framework/SecuritySettingsService.h b/lib/framework/SecuritySettingsService.h index 236bfe4e..77c8badd 100644 --- a/lib/framework/SecuritySettingsService.h +++ b/lib/framework/SecuritySettingsService.h @@ -38,7 +38,7 @@ class SecuritySettings { // users JsonArray users = root.createNestedArray("users"); - for (User user : settings.users) { + for (const User& user : settings.users) { JsonObject userRoot = users.createNestedObject(); userRoot["username"] = user.username; userRoot["password"] = user.password; @@ -103,7 +103,7 @@ class SecuritySettingsService : public SecurityManager { SecuritySettingsService(AsyncWebServer* server, FS* fs); ~SecuritySettingsService(); - // minimal set of functions to support framework with security settings disabled + // minimal set of functions to support framework with security settings disabled Authentication authenticateRequest(AsyncWebServerRequest* request); ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate); ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate); diff --git a/src/FireMode.cpp b/src/FireMode.cpp index f6645f7e..ba3b0bb4 100644 --- a/src/FireMode.cpp +++ b/src/FireMode.cpp @@ -10,8 +10,8 @@ FireMode::FireMode(AsyncWebServer* server, securityManager, ledSettingsService, frequencySampler, - FireModeSettings::read, - FireModeSettings::update, + std::bind(&FireMode::read, this, std::placeholders::_1, std::placeholders::_2), + std::bind(&FireMode::update, this, std::placeholders::_1, std::placeholders::_2), FIRE_MODE_ID){}; void FireMode::enable() { @@ -51,7 +51,7 @@ void FireMode::tick() { // Scale the heat value from 0-255 down to 0-240 // for best results with color palettes. byte colorindex = scale8(_heatMap[j], 240); - CRGB color = ColorFromPalette(*_firePalette, colorindex); + CRGB color = ColorFromPalette(_state.palette.palette, colorindex); int pixelnumber; if (_state.reverse) { pixelnumber = (numLeds - 1) - j; diff --git a/src/FireMode.h b/src/FireMode.h index 1d1cd2b5..8af39f99 100644 --- a/src/FireMode.h +++ b/src/FireMode.h @@ -3,6 +3,7 @@ #include #include +#include #ifndef FACTORY_FIRE_MODE_SPARKING #define FACTORY_FIRE_MODE_SPARKING 120 @@ -16,6 +17,10 @@ #define FACTORY_FIRE_MODE_COOLING 80 #endif +#ifndef FACTORY_FIRE_MODE_PALETTE +#define FACTORY_FIRE_MODE_PALETTE "heat" +#endif + #define FIRE_MODE_ID "fire" // audio enable @@ -25,27 +30,34 @@ class FireModeSettings { bool reverse; uint8_t cooling; uint8_t sparking; + Palette palette; +}; + +class FireMode : public AudioLightModeImpl { + private: + bool _refresh = true; // For applying config updates or enabling the mode + uint8_t _heatMap[NUM_LEDS]; // Intensity map the led strip - statically allocated ATM + PaletteManager _paletteManager; // The palette manager to load the chosen palettes - static void read(FireModeSettings& settings, JsonObject& root) { + void read(FireModeSettings& settings, JsonObject& root) { writeByteToJson(root, &settings.cooling, "cooling"); writeByteToJson(root, &settings.sparking, "sparking"); writeBoolToJson(root, &settings.reverse, "reverse"); + root["palette"] = settings.palette.id; } - static StateUpdateResult update(JsonObject& root, FireModeSettings& settings) { + StateUpdateResult update(JsonObject& root, FireModeSettings& settings) { updateByteFromJson(root, &settings.sparking, FACTORY_FIRE_MODE_SPARKING, "sparking"); updateByteFromJson(root, &settings.cooling, FACTORY_FIRE_MODE_COOLING, "cooling"); updateBoolFromJson(root, &settings.reverse, FACTORY_FIRE_MODE_REVERSE, "reverse"); + + // select the palette + settings.palette = _paletteManager.getPalette(root["palette"] | FACTORY_FIRE_MODE_PALETTE); + // we make one change to the selected palette + // we assert that the first color is black to create the fire effect + settings.palette.palette[0] = 0x000000; return StateUpdateResult::CHANGED; } -}; - -class FireMode : public AudioLightModeImpl { - private: - bool _refresh = true; // For applying config updates or enabling the mode - uint8_t _heatMap[NUM_LEDS]; // Intensity map the led strip - statically allocated ATM - CRGBPalette16 _heat = CRGBPalette16(HeatColors_p); // The heat palette, which looks like fire - CRGBPalette16* _firePalette = &_heat; // The current palette we are using, set to heat ATM public: FireMode(AsyncWebServer* server, diff --git a/src/PaletteManager.h b/src/PaletteManager.h new file mode 100644 index 00000000..be476ea7 --- /dev/null +++ b/src/PaletteManager.h @@ -0,0 +1,106 @@ +#ifndef PaletteManager_h +#define PaletteManager_h + +#include +#include +#include +#include + +const TProgmemRGBPalette16 PacificaColors1_p FL_PROGMEM = {0x000507, + 0x000409, + 0x00030B, + 0x00030D, + 0x000210, + 0x000212, + 0x000114, + 0x000117, + 0x000019, + 0x00001C, + 0x000026, + 0x000031, + 0x00003B, + 0x000046, + 0x14554B, + 0x28AA50}; + +const TProgmemRGBPalette16 PacificaColors2_p FL_PROGMEM = {0x000507, + 0x000409, + 0x00030B, + 0x00030D, + 0x000210, + 0x000212, + 0x000114, + 0x000117, + 0x000019, + 0x00001C, + 0x000026, + 0x000031, + 0x00003B, + 0x000046, + 0x0C5F52, + 0x19BE5F}; + +const TProgmemRGBPalette16 PacificaColors3_p FL_PROGMEM = {0x000208, + 0x00030E, + 0x000514, + 0x00061A, + 0x000820, + 0x000927, + 0x000B2D, + 0x000C33, + 0x000E39, + 0x001040, + 0x001450, + 0x001860, + 0x001C70, + 0x002080, + 0x1040BF, + 0x2060FF}; + +class Palette { + public: + String id; + CRGBPalette16 palette; + + // the default palette - rainbow + Palette() : id("rainbow"), palette(RainbowColors_p) { + } + + // custom palettes + Palette(String id, CRGBPalette16 palette) : id(id), palette(palette) { + } +}; + +/** + * May be extended to support a palette editor in the UI. + */ +class PaletteManager { + private: + std::list palettes; + + public: + PaletteManager() { + palettes.push_back(Palette()); + palettes.push_back(Palette("partycolors", PartyColors_p)); + palettes.push_back(Palette("heat", HeatColors_p)); + palettes.push_back(Palette("rainbowstripe", RainbowStripeColors_p)); + palettes.push_back(Palette("cloud", CloudColors_p)); + palettes.push_back(Palette("lava", LavaColors_p)); + palettes.push_back(Palette("ocean", OceanColors_p)); + palettes.push_back(Palette("forest", ForestColors_p)); + palettes.push_back(Palette("pacifica1", PacificaColors1_p)); + palettes.push_back(Palette("pacifica2", PacificaColors2_p)); + palettes.push_back(Palette("pacifica3", PacificaColors3_p)); + } + + Palette getPalette(const String& paletteId) { + for (const Palette& palette : palettes) { + if (palette.id == paletteId) { + return palette; + } + } + return Palette(); + } +}; + +#endif From 04e10703cb339796e9e337ce6c33defdf1718cb4 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sun, 22 Nov 2020 22:55:19 +0000 Subject: [PATCH 31/52] WIP - Palette editor --- interface/.env.development | 4 +- .../project/AudioLightSettingsController.tsx | 7 +- .../src/project/AudioLightSettingsForm.tsx | 3 +- interface/src/project/LightsProject.tsx | 3 + interface/src/project/PaletteForm.tsx | 85 ++++++++++ .../src/project/PaletteSettingsController.tsx | 29 ++++ interface/src/project/PaletteSettingsForm.tsx | 149 ++++++++++++++++++ .../src/project/modes/AudioLightFireMode.tsx | 24 ++- interface/src/project/types.ts | 40 +++++ src/AudioLightMode.h | 5 + src/AudioLightSettingsService.cpp | 21 ++- src/AudioLightSettingsService.h | 1 + src/ColorMode.cpp | 2 + src/ColorMode.h | 1 + src/ConfettiMode.cpp | 2 + src/ConfettiMode.h | 1 + src/FireMode.cpp | 14 +- src/FireMode.h | 14 +- src/JsonUtil.cpp | 27 ++++ src/JsonUtil.h | 3 + src/LightningMode.cpp | 2 + src/LightningMode.h | 1 + src/OffMode.cpp | 2 + src/OffMode.h | 1 + src/PacificaMode.cpp | 4 +- src/PacificaMode.h | 1 + ...etteManager.h => PaletteSettingsService.h} | 78 +++++++-- src/PrideMode.cpp | 4 +- src/PrideMode.h | 10 +- src/RainbowMode.cpp | 2 + src/RainbowMode.h | 1 + src/main.cpp | 2 + 32 files changed, 495 insertions(+), 48 deletions(-) create mode 100644 interface/src/project/PaletteForm.tsx create mode 100644 interface/src/project/PaletteSettingsController.tsx create mode 100644 interface/src/project/PaletteSettingsForm.tsx rename src/{PaletteManager.h => PaletteSettingsService.h} (66%) diff --git a/interface/.env.development b/interface/.env.development index f7bb8583..b12cfd04 100644 --- a/interface/.env.development +++ b/interface/.env.development @@ -1,4 +1,4 @@ # Change the IP address to that of your ESP device to enable local development of the UI. # Remember to also enable CORS in platformio.ini before uploading the code to the device. -REACT_APP_HTTP_ROOT=http://192.168.0.77 -REACT_APP_WEB_SOCKET_ROOT=ws://192.168.0.77 +REACT_APP_HTTP_ROOT=http://192.168.0.88 +REACT_APP_WEB_SOCKET_ROOT=ws://192.168.0.88 diff --git a/interface/src/project/AudioLightSettingsController.tsx b/interface/src/project/AudioLightSettingsController.tsx index a88a768c..a14ffbbd 100644 --- a/interface/src/project/AudioLightSettingsController.tsx +++ b/interface/src/project/AudioLightSettingsController.tsx @@ -1,13 +1,8 @@ import React, { Component } from 'react'; -import { ENDPOINT_ROOT, WEB_SOCKET_ROOT } from '../api'; import { SectionContent, webSocketController, WebSocketControllerProps, WebSocketFormLoader } from '../components'; import AudioLightSettingsForm from './AudioLightSettingsForm'; -import { AudioLightMode } from './types'; - -export const AUDIO_LIGHT_SAVE_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "saveModeSettings"; -export const AUDIO_LIGHT_LOAD_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "loadModeSettings"; -export const AUDIO_LIGHT_SETTINGS_ENDPOINT = WEB_SOCKET_ROOT + "audioLightSettings"; +import { AudioLightMode, AUDIO_LIGHT_SETTINGS_ENDPOINT } from './types'; type AudioLightSettingsControllerProps = WebSocketControllerProps>; diff --git a/interface/src/project/AudioLightSettingsForm.tsx b/interface/src/project/AudioLightSettingsForm.tsx index dd2a4bcd..f0ea56fc 100644 --- a/interface/src/project/AudioLightSettingsForm.tsx +++ b/interface/src/project/AudioLightSettingsForm.tsx @@ -9,10 +9,9 @@ import AudioLightFireMode from './modes/AudioLightFireMode'; import AudioLightColorMode from './modes/AudioLightColorMode'; import { FormActions, FormButton, WebSocketFormProps } from '../components'; -import { AudioLightModeType, AudioLightMode } from './types'; +import { AudioLightModeType, AudioLightMode, AUDIO_LIGHT_LOAD_SETTINGS_ENDPOINT, AUDIO_LIGHT_SAVE_SETTINGS_ENDPOINT } from './types'; import AudioLightConfettiMode from './modes/AudioLightConfettiMode'; import { redirectingAuthorizedFetch } from '../authentication'; -import { AUDIO_LIGHT_LOAD_SETTINGS_ENDPOINT, AUDIO_LIGHT_SAVE_SETTINGS_ENDPOINT } from './AudioLightSettingsController'; type AudioLightSettingsFormProps = WebSocketFormProps>; diff --git a/interface/src/project/LightsProject.tsx b/interface/src/project/LightsProject.tsx index a7b0bd5d..1f3360ab 100644 --- a/interface/src/project/LightsProject.tsx +++ b/interface/src/project/LightsProject.tsx @@ -9,6 +9,7 @@ import { AuthenticatedRoute } from '../authentication'; import SpectrumAnalyzer from './SpectrumAnalyzer'; import AudioLightSettingsController from './AudioLightSettingsController'; +import PaletteSettingsController from './PaletteSettingsController'; class LightsProject extends Component { @@ -21,10 +22,12 @@ class LightsProject extends Component { + + diff --git a/interface/src/project/PaletteForm.tsx b/interface/src/project/PaletteForm.tsx new file mode 100644 index 00000000..d2fe5da5 --- /dev/null +++ b/interface/src/project/PaletteForm.tsx @@ -0,0 +1,85 @@ +import React, { RefObject } from 'react'; +import { ValidatorForm } from 'react-material-ui-form-validator'; + +import { Dialog, DialogTitle, DialogContent, DialogActions, Box, Slider } from '@material-ui/core'; + +import { FormButton } from '../components'; +import { generateGradient, Palette } from './types'; +import { ChromePicker, ColorResult } from 'react-color'; + +interface PaletteFormProps { + creating: boolean; + palette: Palette; + uniqueId: (value: any) => boolean; + updatePalette: (palette: Palette) => void; + onDoneEditing: () => void; + onCancelEditing: () => void; +} + +interface PaletteFormState { + color: number; +} + +class PaletteForm extends React.Component { + + state: PaletteFormState = { + color: 0 + } + + formRef: RefObject = React.createRef(); + + componentDidMount() { + ValidatorForm.addValidationRule('uniqueId', this.props.uniqueId); + } + + submit = () => { + this.formRef.current.submit(); + } + + selectColor = (event: React.ChangeEvent<{}>, value: number | number[]) => { + this.setState({ color: value as number }); + } + + changeColor = (result: ColorResult) => { + const { color } = this.state; + const { updatePalette, palette } = this.props; + const colors = [...palette.colors]; + colors[color] = result.hex; + updatePalette({ ...palette, colors }); + } + + render() { + const { palette, creating, onDoneEditing, onCancelEditing } = this.props; + const { color } = this.state; + + return ( + + + {creating ? 'Add' : 'Modify'} Palette + + + + + + + + Cancel + + + Done + + + + + ); + } +} + +export default PaletteForm; diff --git a/interface/src/project/PaletteSettingsController.tsx b/interface/src/project/PaletteSettingsController.tsx new file mode 100644 index 00000000..934467b6 --- /dev/null +++ b/interface/src/project/PaletteSettingsController.tsx @@ -0,0 +1,29 @@ +import React, { Component } from 'react'; + +import {restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import PaletteSettingsForm from './PaletteSettingsForm'; + +import { PaletteSettings, PALETTE_SETTINGS_ENDPOINT } from './types'; + +type PaletteSettingsControllerProps = RestControllerProps; + +class PaletteSettingsController extends Component { + + componentDidMount() { + this.props.loadData(); + } + + render() { + return ( + + } + /> + + ) + } + +} + +export default restController(PALETTE_SETTINGS_ENDPOINT, PaletteSettingsController); diff --git a/interface/src/project/PaletteSettingsForm.tsx b/interface/src/project/PaletteSettingsForm.tsx new file mode 100644 index 00000000..89354865 --- /dev/null +++ b/interface/src/project/PaletteSettingsForm.tsx @@ -0,0 +1,149 @@ +import { Button, IconButton, isWidthDown, Table, TableBody, TableCell, TableFooter, TableHead, TableRow, withWidth, WithWidthProps } from '@material-ui/core'; +import React, { Fragment } from 'react'; +import { ValidatorForm } from 'react-material-ui-form-validator'; + +import EditIcon from '@material-ui/icons/Edit'; +import DeleteIcon from '@material-ui/icons/Delete'; +import SaveIcon from '@material-ui/icons/Save'; +import PersonAddIcon from '@material-ui/icons/PersonAdd'; + +import { FormActions, FormButton, RestFormProps } from '../components'; +import { DEFAULT_PALETTE, generateGradient, Palette, PaletteSettings } from './types'; +import PaletteForm from './PaletteForm'; + +type PaletteSettingsFormProps = RestFormProps & WithWidthProps; + +interface PaletteSettingsFormState { + creating: boolean; + palette?: Palette; +} + +class PaletteSettingsForm extends React.Component { + + state: PaletteSettingsFormState = { + creating: false + }; + + uniqueId = (id: string) => { + return !this.props.data.palettes.find(p => p.id = id); + } + + removePalette = (palette: Palette) => { + const { data } = this.props; + const palettes = data.palettes.filter(p => p.id !== palette.id); + this.props.setData({ ...data, palettes }); + } + + startEditingPalette = (palette: Palette) => { + this.setState({ + creating: false, + palette + }); + }; + + cancelEditingPalette = () => { + this.setState({ + palette: undefined + }); + } + + doneEditingPalette = () => { + const { palette } = this.state; + if (palette) { + const { data } = this.props; + const palettes = data.palettes.filter(p => p.id !== palette.id); + palettes.push(palette); + this.props.setData({ ...data, palettes }); + this.setState({ + palette: undefined + }); + } + }; + + createPalette = () => { + this.setState({ + creating: true, + palette: { + id: "", + colors: [...DEFAULT_PALETTE] + } + }); + }; + + updatePalette = (palette: Palette) => { + this.setState({ palette }); + }; + + renderPalettes = () => { + const { data } = this.props; + return data.palettes.sort((a, b) => a.id.localeCompare(b.id)).map(palette => ( + + + {palette.id} + + + + this.removePalette(palette)}> + + + this.startEditingPalette(palette)}> + + + + + )); + } + + render() { + const { saveData, width } = this.props; + const { creating, palette } = this.state; + + return ( + + + + + + Palette + Gradient + + + + + {this.renderPalettes()} + + + + + + + + + +
+ + } variant="contained" color="primary" type="submit"> + Save + + +
+ { + palette && + + } +
+ ); + } + +} + +export default withWidth()(PaletteSettingsForm); diff --git a/interface/src/project/modes/AudioLightFireMode.tsx b/interface/src/project/modes/AudioLightFireMode.tsx index e0aa6fcd..6482b49d 100644 --- a/interface/src/project/modes/AudioLightFireMode.tsx +++ b/interface/src/project/modes/AudioLightFireMode.tsx @@ -5,7 +5,7 @@ import Slider from '@material-ui/core/Slider'; import { FireMode } from '../types'; import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; -import { Box, Switch } from '@material-ui/core'; +import { Box, MenuItem, Switch, TextField } from '@material-ui/core'; type AudioLightFireModeProps = AudioLightModeProps; @@ -16,6 +16,28 @@ class AudioLightFireMode extends React.Component { return (
+ + Rainbow + Party + Heat + Rainbow Stripe + Cloud + Lava + Ocean + Forest + Pacifica 1 + Pacifica 2 + Pacifica 3 + + Reverse { + return `linear-gradient(0.25turn, ${palette.colors.join(', ')})`; +}; + +export const DEFAULT_PALETTE = [ + "#ff0000", + "#d52a00", + "#ab5500", + "#ab7f00", + "#abab00", + "#56d500", + "#00ff00", + "#00d52a", + "#00ab55", + "#0056aa", + "#0000ff", + "#2a00d5", + "#5500ab", + "#7f0081", + "#ab0055", + "#d5002b" +]; diff --git a/src/AudioLightMode.h b/src/AudioLightMode.h index 61926c5f..3260d61d 100644 --- a/src/AudioLightMode.h +++ b/src/AudioLightMode.h @@ -2,6 +2,7 @@ #define AudioLightMode_h #include +#include #include #define AUDIO_LIGHT_MODE_FILE_PATH_PREFIX "/modes/" @@ -16,6 +17,7 @@ class AudioLightMode { virtual void writeToFS() = 0; virtual void tick() = 0; virtual void enable() = 0; + virtual void refreshPalettes(const String& originId){}; virtual void sampleComplete(){}; virtual void readAsJson(JsonObject& root) = 0; virtual StateUpdateResult updateFromJson(JsonObject& root, const String& originId) = 0; @@ -30,6 +32,7 @@ class AudioLightModeImpl : public StatefulService, public AudioLightMode { JsonStateReader _stateReader; JsonStateUpdater _stateUpdater; LedSettingsService* _ledSettingsService; + PaletteSettingsService* _paletteSettingsService; FrequencySampler* _frequencySampler; HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; @@ -39,6 +42,7 @@ class AudioLightModeImpl : public StatefulService, public AudioLightMode { FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler, JsonStateReader stateReader, JsonStateUpdater stateUpdater, @@ -49,6 +53,7 @@ class AudioLightModeImpl : public StatefulService, public AudioLightMode { _stateReader(stateReader), _stateUpdater(stateUpdater), _ledSettingsService(ledSettingsService), + _paletteSettingsService(paletteSettingsService), _frequencySampler(frequencySampler), _httpEndpoint(stateReader, stateUpdater, this, server, _filePath, securityManager), _fsPersistence(stateReader, stateUpdater, this, fs, _servicePath.c_str()) { diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index 406a474a..caa2eb6c 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -4,6 +4,7 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : _httpEndpoint(std::bind(&AudioLightSettingsService::read, this, std::placeholders::_1, std::placeholders::_2), std::bind(&AudioLightSettingsService::update, this, std::placeholders::_1, std::placeholders::_2), @@ -33,14 +34,18 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, addUpdateHandler([&](const String& originId) { enableMode(); }, false); frequencySampler->addUpdateHandler([&](const String& originId) { handleSample(); }, false); ledSettingsService->addUpdateHandler([&](const String& originId) { enableMode(); }, false); - _modes[0] = new ColorMode(server, fs, securityManager, ledSettingsService, frequencySampler); - _modes[1] = new RainbowMode(server, fs, securityManager, ledSettingsService, frequencySampler); - _modes[2] = new LightningMode(server, fs, securityManager, ledSettingsService, frequencySampler); - _modes[3] = new ConfettiMode(server, fs, securityManager, ledSettingsService, frequencySampler); - _modes[4] = new FireMode(server, fs, securityManager, ledSettingsService, frequencySampler); - _modes[5] = new OffMode(server, fs, securityManager, ledSettingsService, frequencySampler); - _modes[6] = new PacificaMode(server, fs, securityManager, ledSettingsService, frequencySampler); - _modes[7] = new PrideMode(server, fs, securityManager, ledSettingsService, frequencySampler); + _modes[0] = new ColorMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); + _modes[1] = + new RainbowMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); + _modes[2] = + new LightningMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); + _modes[3] = + new ConfettiMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); + _modes[4] = new FireMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); + _modes[5] = new OffMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); + _modes[6] = + new PacificaMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); + _modes[7] = new PrideMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); } void AudioLightSettingsService::begin() { diff --git a/src/AudioLightSettingsService.h b/src/AudioLightSettingsService.h index 3fbf064b..28cfb020 100644 --- a/src/AudioLightSettingsService.h +++ b/src/AudioLightSettingsService.h @@ -34,6 +34,7 @@ class AudioLightSettingsService : public StatefulService { FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler); void begin(); diff --git a/src/ColorMode.cpp b/src/ColorMode.cpp index a371a120..b38a32cb 100644 --- a/src/ColorMode.cpp +++ b/src/ColorMode.cpp @@ -4,11 +4,13 @@ ColorMode::ColorMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, fs, securityManager, ledSettingsService, + paletteSettingsService, frequencySampler, ColorModeSettings::read, ColorModeSettings::update, diff --git a/src/ColorMode.h b/src/ColorMode.h index be602eaa..0e74d4c3 100644 --- a/src/ColorMode.h +++ b/src/ColorMode.h @@ -51,6 +51,7 @@ class ColorMode : public AudioLightModeImpl { FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler); void tick(); void enable(); diff --git a/src/ConfettiMode.cpp b/src/ConfettiMode.cpp index bb2cd150..3a4017bc 100644 --- a/src/ConfettiMode.cpp +++ b/src/ConfettiMode.cpp @@ -4,11 +4,13 @@ ConfettiMode::ConfettiMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, fs, securityManager, ledSettingsService, + paletteSettingsService, frequencySampler, ConfettiModeSettings::read, ConfettiModeSettings::update, diff --git a/src/ConfettiMode.h b/src/ConfettiMode.h index 0fc9ed00..22ebdf86 100644 --- a/src/ConfettiMode.h +++ b/src/ConfettiMode.h @@ -58,6 +58,7 @@ class ConfettiMode : public AudioLightModeImpl { FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler); void tick(); void enable(); diff --git a/src/FireMode.cpp b/src/FireMode.cpp index ba3b0bb4..40a7500b 100644 --- a/src/FireMode.cpp +++ b/src/FireMode.cpp @@ -4,11 +4,13 @@ FireMode::FireMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, fs, securityManager, ledSettingsService, + paletteSettingsService, frequencySampler, std::bind(&FireMode::read, this, std::placeholders::_1, std::placeholders::_2), std::bind(&FireMode::update, this, std::placeholders::_1, std::placeholders::_2), @@ -18,6 +20,16 @@ void FireMode::enable() { _refresh = true; } +void FireMode::refreshPalettes(const String& originId) { + _refresh = true; + StatefulService::update( + [&](FireModeSettings& settings) { + _paletteSettingsService->refreshPalette(settings.palette); + return StateUpdateResult::CHANGED; + }, + originId); +} + void FireMode::tick() { if (_refresh) { _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { @@ -51,7 +63,7 @@ void FireMode::tick() { // Scale the heat value from 0-255 down to 0-240 // for best results with color palettes. byte colorindex = scale8(_heatMap[j], 240); - CRGB color = ColorFromPalette(_state.palette.palette, colorindex); + CRGB color = ColorFromPalette(_state.palette.colors, colorindex); int pixelnumber; if (_state.reverse) { pixelnumber = (numLeds - 1) - j; diff --git a/src/FireMode.h b/src/FireMode.h index 8af39f99..155aeb53 100644 --- a/src/FireMode.h +++ b/src/FireMode.h @@ -3,7 +3,6 @@ #include #include -#include #ifndef FACTORY_FIRE_MODE_SPARKING #define FACTORY_FIRE_MODE_SPARKING 120 @@ -27,17 +26,16 @@ // make palette customizable class FireModeSettings { public: + Palette palette; bool reverse; uint8_t cooling; uint8_t sparking; - Palette palette; }; class FireMode : public AudioLightModeImpl { private: - bool _refresh = true; // For applying config updates or enabling the mode - uint8_t _heatMap[NUM_LEDS]; // Intensity map the led strip - statically allocated ATM - PaletteManager _paletteManager; // The palette manager to load the chosen palettes + bool _refresh = true; // For applying config updates or enabling the mode + uint8_t _heatMap[NUM_LEDS]; // Intensity map the led strip - statically allocated ATM void read(FireModeSettings& settings, JsonObject& root) { writeByteToJson(root, &settings.cooling, "cooling"); @@ -52,10 +50,10 @@ class FireMode : public AudioLightModeImpl { updateBoolFromJson(root, &settings.reverse, FACTORY_FIRE_MODE_REVERSE, "reverse"); // select the palette - settings.palette = _paletteManager.getPalette(root["palette"] | FACTORY_FIRE_MODE_PALETTE); + settings.palette = _paletteSettingsService->getPalette(root["palette"] | FACTORY_FIRE_MODE_PALETTE); // we make one change to the selected palette // we assert that the first color is black to create the fire effect - settings.palette.palette[0] = 0x000000; + settings.palette.colors[0] = 0x000000; return StateUpdateResult::CHANGED; } @@ -64,9 +62,11 @@ class FireMode : public AudioLightModeImpl { FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler); void tick(); void enable(); + void refreshPalettes(const String& originId); }; #endif diff --git a/src/JsonUtil.cpp b/src/JsonUtil.cpp index a6314883..3476119d 100644 --- a/src/JsonUtil.cpp +++ b/src/JsonUtil.cpp @@ -21,6 +21,33 @@ void writeBooleanArrayToJson(JsonObject& root, bool booleanArray[], uint16_t len } } +void updatePaletteFromJson(JsonObject& root, CRGBPalette16* palette, CRGB def, String key) { + for (uint8_t i = 0; i < 16; i++) { + palette->entries[i] = def; + } + if (root.containsKey(key) && root[key].is()) { + JsonArray jsonArray = root[key]; + for (uint8_t i = 0; i < 16 && i < jsonArray.size(); i++) { + if (jsonArray[i].is()) { + String colorString = jsonArray[i]; + if (colorString.length() == 7) { + palette->entries[i] = CRGB(strtoll(&colorString[1], NULL, 16)); + } + } + } + } +} + +void writePaletteToJson(JsonObject& root, const CRGBPalette16* palette, String key) { + JsonArray array = root.createNestedArray(key); + for (uint8_t i = 0; i < 16; i++) { + CRGB color = palette->entries[i]; + char colorString[8]; + sprintf(colorString, "#%02x%02x%02x", color.r, color.g, color.b); + array.add(colorString); + } +} + void updateColorFromJson(JsonObject& root, CRGB* color, CRGB def, String key) { if (root.containsKey(key) && root[key].is()) { String colorString = root[key]; diff --git a/src/JsonUtil.h b/src/JsonUtil.h index 55126787..c2c846b6 100644 --- a/src/JsonUtil.h +++ b/src/JsonUtil.h @@ -10,6 +10,9 @@ void writeBooleanArrayToJson(JsonObject& root, bool booleanArray[], uint16_t max void updateColorFromJson(JsonObject& root, CRGB* color, CRGB def, String key = "color"); void writeColorToJson(JsonObject& root, CRGB* color, String key = "color"); +void updatePaletteFromJson(JsonObject& root, CRGBPalette16* palette, CRGB def, String key = "colors"); +void writePaletteToJson(JsonObject& root, const CRGBPalette16* palette, String key = "colors"); + void updateByteFromJson(JsonObject& root, uint8_t* value, uint8_t def, String key); void writeByteToJson(JsonObject& root, uint8_t* value, String key); diff --git a/src/LightningMode.cpp b/src/LightningMode.cpp index fe478016..8d7cd6b4 100644 --- a/src/LightningMode.cpp +++ b/src/LightningMode.cpp @@ -4,11 +4,13 @@ LightningMode::LightningMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, fs, securityManager, ledSettingsService, + paletteSettingsService, frequencySampler, LightningModeSettings::read, LightningModeSettings::update, diff --git a/src/LightningMode.h b/src/LightningMode.h index 969674a0..31ca58be 100644 --- a/src/LightningMode.h +++ b/src/LightningMode.h @@ -77,6 +77,7 @@ class LightningMode : public AudioLightModeImpl { FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler); void tick(); void enable(); diff --git a/src/OffMode.cpp b/src/OffMode.cpp index bb7ab872..0ecc4ecf 100644 --- a/src/OffMode.cpp +++ b/src/OffMode.cpp @@ -4,11 +4,13 @@ OffMode::OffMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, fs, securityManager, ledSettingsService, + paletteSettingsService, frequencySampler, OffModeSettings::read, OffModeSettings::update, diff --git a/src/OffMode.h b/src/OffMode.h index 53876f24..84c86b76 100644 --- a/src/OffMode.h +++ b/src/OffMode.h @@ -24,6 +24,7 @@ class OffMode : public AudioLightModeImpl { FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler); void tick(); void enable(); diff --git a/src/PacificaMode.cpp b/src/PacificaMode.cpp index 05dd69fe..25639895 100644 --- a/src/PacificaMode.cpp +++ b/src/PacificaMode.cpp @@ -4,11 +4,13 @@ PacificaMode::PacificaMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, fs, securityManager, ledSettingsService, + paletteSettingsService, frequencySampler, PacificaModeSettings::read, PacificaModeSettings::update, @@ -21,7 +23,7 @@ void PacificaMode::enable() { } void PacificaMode::tick() { - _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { // Increment the four "color index start" counters, one for each wave layer. // Each is incremented at a different speed, and the speeds vary over time. static uint16_t sCIStart1, sCIStart2, sCIStart3, sCIStart4; diff --git a/src/PacificaMode.h b/src/PacificaMode.h index 38ebd935..bf9a8e2a 100644 --- a/src/PacificaMode.h +++ b/src/PacificaMode.h @@ -85,6 +85,7 @@ class PacificaMode : public AudioLightModeImpl { FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler); void tick(); void enable(); diff --git a/src/PaletteManager.h b/src/PaletteSettingsService.h similarity index 66% rename from src/PaletteManager.h rename to src/PaletteSettingsService.h index be476ea7..6be61d6f 100644 --- a/src/PaletteManager.h +++ b/src/PaletteSettingsService.h @@ -1,11 +1,14 @@ -#ifndef PaletteManager_h -#define PaletteManager_h +#ifndef PaletteSettingsService_h +#define PaletteSettingsService_h #include #include #include #include +#define PALETTE_SETTINGS_FILE "/config/paletteSettings.json" +#define PALETTE_SETTINGS_SERVICE_PATH "/rest/paletteSettings" + const TProgmemRGBPalette16 PacificaColors1_p FL_PROGMEM = {0x000507, 0x000409, 0x00030B, @@ -60,28 +63,24 @@ const TProgmemRGBPalette16 PacificaColors3_p FL_PROGMEM = {0x000208, class Palette { public: String id; - CRGBPalette16 palette; + CRGBPalette16 colors; // the default palette - rainbow - Palette() : id("rainbow"), palette(RainbowColors_p) { + Palette() : id("rainbow"), colors(RainbowColors_p) { } // custom palettes - Palette(String id, CRGBPalette16 palette) : id(id), palette(palette) { + Palette(String id, CRGBPalette16 colors) : id(id), colors(colors) { } }; -/** - * May be extended to support a palette editor in the UI. - */ -class PaletteManager { - private: +class PaletteSettings { + public: std::list palettes; - public: - PaletteManager() { + PaletteSettings() { palettes.push_back(Palette()); - palettes.push_back(Palette("partycolors", PartyColors_p)); + palettes.push_back(Palette("party", PartyColors_p)); palettes.push_back(Palette("heat", HeatColors_p)); palettes.push_back(Palette("rainbowstripe", RainbowStripeColors_p)); palettes.push_back(Palette("cloud", CloudColors_p)); @@ -93,8 +92,59 @@ class PaletteManager { palettes.push_back(Palette("pacifica3", PacificaColors3_p)); } + static void read(PaletteSettings& settings, JsonObject& root) { + JsonArray palettes = root.createNestedArray("palettes"); + for (Palette palette : settings.palettes) { + JsonObject paletteRoot = palettes.createNestedObject(); + paletteRoot["id"] = palette.id; + writePaletteToJson(paletteRoot, &palette.colors); + } + } + + static StateUpdateResult update(JsonObject& root, PaletteSettings& settings) { + settings.palettes.clear(); + if (root["palettes"].is()) { + for (JsonObject paletteRoot : root["palettes"].as()) { + String id = paletteRoot["id"]; + if (!id.isEmpty()) { + Palette palette = Palette(); + palette.id = id; + updatePaletteFromJson(paletteRoot, &palette.colors, CRGB::Black); + settings.palettes.push_back(palette); + } + } + } + return StateUpdateResult::CHANGED; + } +}; + +/** + * May be extended to support a palette editor in the UI. + */ +class PaletteSettingsService : public StatefulService { + private: + HttpEndpoint _httpEndpoint; + FSPersistence _fsPersistence; + + public: + PaletteSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : + _httpEndpoint(PaletteSettings::read, + PaletteSettings::update, + this, + server, + PALETTE_SETTINGS_SERVICE_PATH, + securityManager, + AuthenticationPredicates::IS_AUTHENTICATED, + 10240), + _fsPersistence(PaletteSettings::read, PaletteSettings::update, this, fs, PALETTE_SETTINGS_FILE, 10240) { + } + + void refreshPalette(Palette& palette) { + palette = getPalette(palette.id); + } + Palette getPalette(const String& paletteId) { - for (const Palette& palette : palettes) { + for (Palette palette : _state.palettes) { if (palette.id == paletteId) { return palette; } diff --git a/src/PrideMode.cpp b/src/PrideMode.cpp index c7905de7..a8169403 100644 --- a/src/PrideMode.cpp +++ b/src/PrideMode.cpp @@ -4,11 +4,13 @@ PrideMode::PrideMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, fs, securityManager, ledSettingsService, + paletteSettingsService, frequencySampler, PrideModeSettings::read, PrideModeSettings::update, @@ -59,7 +61,7 @@ void PrideMode::tick() { nblend(leds[pixelnumber], newcolor, 64); } - + // Show the leds ledController->showLeds(); }); diff --git a/src/PrideMode.h b/src/PrideMode.h index 4634323e..0144d91f 100644 --- a/src/PrideMode.h +++ b/src/PrideMode.h @@ -19,15 +19,15 @@ class PrideModeSettings { class PrideMode : public AudioLightModeImpl { private: - boolean _refresh = true; public: PrideMode(AsyncWebServer* server, - FS* fs, - SecurityManager* securityManager, - LedSettingsService* ledSettingsService, - FrequencySampler* frequencySampler); + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, + FrequencySampler* frequencySampler); void tick(); void enable(); }; diff --git a/src/RainbowMode.cpp b/src/RainbowMode.cpp index 8880add1..8638f912 100644 --- a/src/RainbowMode.cpp +++ b/src/RainbowMode.cpp @@ -4,11 +4,13 @@ RainbowMode::RainbowMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, fs, securityManager, ledSettingsService, + paletteSettingsService, frequencySampler, RainbowModeSettings::read, RainbowModeSettings::update, diff --git a/src/RainbowMode.h b/src/RainbowMode.h index 92e3a7cd..3ffe7883 100644 --- a/src/RainbowMode.h +++ b/src/RainbowMode.h @@ -62,6 +62,7 @@ class RainbowMode : public AudioLightModeImpl { FS* fs, SecurityManager* securityManager, LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler); void tick(); void enable(); diff --git a/src/main.cpp b/src/main.cpp index e0c89212..2fa5ab85 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,12 +10,14 @@ AsyncWebServer server(80); ESP8266React esp8266React(&server); LedSettingsService ledSettingsService(&server, esp8266React.getFS(), esp8266React.getSecurityManager()); +PaletteSettingsService paletteSettingsService(&server, esp8266React.getFS(), esp8266React.getSecurityManager()); FrequencySampler frequencySampler(&ledSettingsService); FrequencyTransmitter frequencyTransmitter(&server, esp8266React.getSecurityManager(), &frequencySampler); AudioLightSettingsService audioLightSettingsService(&server, esp8266React.getFS(), esp8266React.getSecurityManager(), &ledSettingsService, + &paletteSettingsService, &frequencySampler); void setup() { From a7c5e9472cc90c69e8c1c288ef2bfce86138c8e9 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sun, 22 Nov 2020 23:55:47 +0000 Subject: [PATCH 32/52] add id to palette for, --- interface/src/project/PaletteForm.tsx | 19 ++++++++++++++++++- interface/src/project/PaletteSettingsForm.tsx | 6 +++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/interface/src/project/PaletteForm.tsx b/interface/src/project/PaletteForm.tsx index d2fe5da5..105268ff 100644 --- a/interface/src/project/PaletteForm.tsx +++ b/interface/src/project/PaletteForm.tsx @@ -1,5 +1,5 @@ import React, { RefObject } from 'react'; -import { ValidatorForm } from 'react-material-ui-form-validator'; +import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator'; import { Dialog, DialogTitle, DialogContent, DialogActions, Box, Slider } from '@material-ui/core'; @@ -40,6 +40,11 @@ class PaletteForm extends React.Component { this.setState({ color: value as number }); } + updateId = (event: React.ChangeEvent) => { + const { updatePalette, palette } = this.props; + updatePalette({ ...palette, id: event.target.value }); + } + changeColor = (result: ColorResult) => { const { color } = this.state; const { updatePalette, palette } = this.props; @@ -57,6 +62,18 @@ class PaletteForm extends React.Component { {creating ? 'Add' : 'Modify'} Palette + { - return !this.props.data.palettes.find(p => p.id = id); + return !this.props.data.palettes.find(p => p.id === id); } removePalette = (palette: Palette) => { @@ -80,7 +80,7 @@ class PaletteSettingsForm extends React.Component {palette.id} - + this.removePalette(palette)}> @@ -104,8 +104,8 @@ class PaletteSettingsForm extends React.Component + Id Palette - Gradient From 926148b83eda89bcfb964312ecb2267550662a96 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Mon, 23 Nov 2020 20:29:04 +0000 Subject: [PATCH 33/52] update palette --- src/AudioLightMode.h | 1 - src/AudioLightSettingsService.cpp | 1 + src/FireMode.cpp | 14 ++++---------- src/FireMode.h | 10 +++++----- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/AudioLightMode.h b/src/AudioLightMode.h index 3260d61d..644a0561 100644 --- a/src/AudioLightMode.h +++ b/src/AudioLightMode.h @@ -17,7 +17,6 @@ class AudioLightMode { virtual void writeToFS() = 0; virtual void tick() = 0; virtual void enable() = 0; - virtual void refreshPalettes(const String& originId){}; virtual void sampleComplete(){}; virtual void readAsJson(JsonObject& root) = 0; virtual StateUpdateResult updateFromJson(JsonObject& root, const String& originId) = 0; diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index caa2eb6c..2a432508 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -34,6 +34,7 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, addUpdateHandler([&](const String& originId) { enableMode(); }, false); frequencySampler->addUpdateHandler([&](const String& originId) { handleSample(); }, false); ledSettingsService->addUpdateHandler([&](const String& originId) { enableMode(); }, false); + paletteSettingsService->addUpdateHandler([&](const String& originId) { enableMode(); }, false); _modes[0] = new ColorMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); _modes[1] = new RainbowMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); diff --git a/src/FireMode.cpp b/src/FireMode.cpp index 40a7500b..553e7d27 100644 --- a/src/FireMode.cpp +++ b/src/FireMode.cpp @@ -18,16 +18,10 @@ FireMode::FireMode(AsyncWebServer* server, void FireMode::enable() { _refresh = true; -} - -void FireMode::refreshPalettes(const String& originId) { - _refresh = true; - StatefulService::update( - [&](FireModeSettings& settings) { - _paletteSettingsService->refreshPalette(settings.palette); - return StateUpdateResult::CHANGED; - }, - originId); + updateWithoutPropagation([&](FireModeSettings& settings) { + selectPalette(settings.palette.id, settings); + return StateUpdateResult::CHANGED; + }); } void FireMode::tick() { diff --git a/src/FireMode.h b/src/FireMode.h index 155aeb53..659185a3 100644 --- a/src/FireMode.h +++ b/src/FireMode.h @@ -48,13 +48,13 @@ class FireMode : public AudioLightModeImpl { updateByteFromJson(root, &settings.sparking, FACTORY_FIRE_MODE_SPARKING, "sparking"); updateByteFromJson(root, &settings.cooling, FACTORY_FIRE_MODE_COOLING, "cooling"); updateBoolFromJson(root, &settings.reverse, FACTORY_FIRE_MODE_REVERSE, "reverse"); + selectPalette(root["palette"] | FACTORY_FIRE_MODE_PALETTE, settings); + return StateUpdateResult::CHANGED; + } - // select the palette - settings.palette = _paletteSettingsService->getPalette(root["palette"] | FACTORY_FIRE_MODE_PALETTE); - // we make one change to the selected palette - // we assert that the first color is black to create the fire effect + void selectPalette(const String& paletteId, FireModeSettings& settings) { + settings.palette = _paletteSettingsService->getPalette(paletteId); settings.palette.colors[0] = 0x000000; - return StateUpdateResult::CHANGED; } public: From 382773892768bfe1b185b76c4b3bd82d37a418d6 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Mon, 23 Nov 2020 21:14:20 +0000 Subject: [PATCH 34/52] make palettes configurable for pacifica mode --- .../src/project/AudioLightSettingsForm.tsx | 3 + .../src/project/components/PalettePicker.tsx | 45 +++++++++++ .../src/project/modes/AudioLightFireMode.tsx | 24 +----- .../project/modes/AudioLightPacificaMode.tsx | 39 ++++++++++ interface/src/project/types.ts | 3 + src/PacificaMode.cpp | 19 ++--- src/PacificaMode.h | 75 +++++-------------- src/PaletteSettingsService.h | 22 +++--- 8 files changed, 131 insertions(+), 99 deletions(-) create mode 100644 interface/src/project/components/PalettePicker.tsx create mode 100644 interface/src/project/modes/AudioLightPacificaMode.tsx diff --git a/interface/src/project/AudioLightSettingsForm.tsx b/interface/src/project/AudioLightSettingsForm.tsx index f0ea56fc..6fb463d1 100644 --- a/interface/src/project/AudioLightSettingsForm.tsx +++ b/interface/src/project/AudioLightSettingsForm.tsx @@ -12,6 +12,7 @@ import { FormActions, FormButton, WebSocketFormProps } from '../components'; import { AudioLightModeType, AudioLightMode, AUDIO_LIGHT_LOAD_SETTINGS_ENDPOINT, AUDIO_LIGHT_SAVE_SETTINGS_ENDPOINT } from './types'; import AudioLightConfettiMode from './modes/AudioLightConfettiMode'; import { redirectingAuthorizedFetch } from '../authentication'; +import AudioLightPacificaMode from './modes/AudioLightPacificaMode'; type AudioLightSettingsFormProps = WebSocketFormProps>; @@ -60,6 +61,8 @@ class AudioLightSettingsForm extends React.Component) => void; +} + +class PalettePicker extends React.Component { + render() { + const { + name, + label, + value, + onChange + } = this.props; + return ( + + Rainbow + Party + Heat + Rainbow Stripe + Cloud + Lava + Ocean + Forest + Pacifica 1 + Pacifica 2 + Pacifica 3 + + ); + } +} + +export default PalettePicker; diff --git a/interface/src/project/modes/AudioLightFireMode.tsx b/interface/src/project/modes/AudioLightFireMode.tsx index 6482b49d..8309c5d3 100644 --- a/interface/src/project/modes/AudioLightFireMode.tsx +++ b/interface/src/project/modes/AudioLightFireMode.tsx @@ -6,6 +6,7 @@ import Slider from '@material-ui/core/Slider'; import { FireMode } from '../types'; import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; import { Box, MenuItem, Switch, TextField } from '@material-ui/core'; +import PalettePicker from '../components/PalettePicker'; type AudioLightFireModeProps = AudioLightModeProps; @@ -16,28 +17,11 @@ class AudioLightFireMode extends React.Component { return (
- - Rainbow - Party - Heat - Rainbow Stripe - Cloud - Lava - Ocean - Forest - Pacifica 1 - Pacifica 2 - Pacifica 3 - - + onChange={handleValueChange('palette')} /> Reverse { color="primary" /> - Cooling { value={data.cooling} onChange={handleSliderChange('cooling')} /> - Sparking ; + +class AudioLightPacificaMode extends React.Component { + + render() { + const { data, handleValueChange, handleSliderChange } = this.props; + + return ( +
+ + + + + + +
+ ); + } +} + +export default audioLightMode(AudioLightPacificaMode); diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index d83c3958..806ebc62 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -62,6 +62,9 @@ export interface FireMode { export interface PacificaMode { mode_id: AudioLightModeType.PACIFICA; + palette1: string; + palette2: string; + palette3: string; } export interface PrideMode { diff --git a/src/PacificaMode.cpp b/src/PacificaMode.cpp index 25639895..6f668807 100644 --- a/src/PacificaMode.cpp +++ b/src/PacificaMode.cpp @@ -12,8 +12,8 @@ PacificaMode::PacificaMode(AsyncWebServer* server, ledSettingsService, paletteSettingsService, frequencySampler, - PacificaModeSettings::read, - PacificaModeSettings::update, + std::bind(&PacificaMode::read, this, std::placeholders::_1, std::placeholders::_2), + std::bind(&PacificaMode::update, this, std::placeholders::_1, std::placeholders::_2), PACIFICA_MODE_ID) { addUpdateHandler([&](const String& originId) { enable(); }, false); }; @@ -47,20 +47,15 @@ void PacificaMode::tick() { // Render each of four layers, with different scales and speeds, that vary over time pacifica_one_layer(leds, numLeds, - pacifica_palette_1, + _state.palette1.colors, sCIStart1, beatsin16(3, 11 * 256, 14 * 256), beatsin8(10, 70, 130), 0 - beat16(301)); - pacifica_one_layer(leds, - numLeds, - pacifica_palette_2, - sCIStart2, - beatsin16(4, 6 * 256, 9 * 256), - beatsin8(17, 40, 80), - beat16(401)); - pacifica_one_layer(leds, numLeds, pacifica_palette_3, sCIStart3, 6 * 256, beatsin8(9, 10, 38), 0 - beat16(503)); - pacifica_one_layer(leds, numLeds, pacifica_palette_3, sCIStart4, 5 * 256, beatsin8(8, 10, 28), beat16(601)); + pacifica_one_layer( + leds, numLeds, _state.palette2.colors, sCIStart2, beatsin16(4, 6 * 256, 9 * 256), beatsin8(17, 40, 80), beat16(401)); + pacifica_one_layer(leds, numLeds, _state.palette3.colors, sCIStart3, 6 * 256, beatsin8(9, 10, 38), 0 - beat16(503)); + pacifica_one_layer(leds, numLeds, _state.palette3.colors, sCIStart4, 5 * 256, beatsin8(8, 10, 28), beat16(601)); // Add brighter 'whitecaps' where the waves lines up more pacifica_add_whitecaps(leds, numLeds); diff --git a/src/PacificaMode.h b/src/PacificaMode.h index bf9a8e2a..3db06583 100644 --- a/src/PacificaMode.h +++ b/src/PacificaMode.h @@ -5,71 +5,36 @@ #include #include +#define PACIFICA_MODE_PALETTE1 "Pacifica 1" +#define PACIFICA_MODE_PALETTE2 "Pacifica 2" +#define PACIFICA_MODE_PALETTE3 "Pacifica 3" + #define PACIFICA_MODE_ID "pacifica" class PacificaModeSettings { public: - static void read(PacificaModeSettings& settings, JsonObject& root) { - } - - static StateUpdateResult update(JsonObject& root, PacificaModeSettings& settings) { - return StateUpdateResult::CHANGED; - } + Palette palette1; + Palette palette2; + Palette palette3; }; class PacificaMode : public AudioLightModeImpl { private: - CRGBPalette16 pacifica_palette_1 = {0x000507, - 0x000409, - 0x00030B, - 0x00030D, - 0x000210, - 0x000212, - 0x000114, - 0x000117, - 0x000019, - 0x00001C, - 0x000026, - 0x000031, - 0x00003B, - 0x000046, - 0x14554B, - 0x28AA50}; - CRGBPalette16 pacifica_palette_2 = {0x000507, - 0x000409, - 0x00030B, - 0x00030D, - 0x000210, - 0x000212, - 0x000114, - 0x000117, - 0x000019, - 0x00001C, - 0x000026, - 0x000031, - 0x00003B, - 0x000046, - 0x0C5F52, - 0x19BE5F}; - CRGBPalette16 pacifica_palette_3 = {0x000208, - 0x00030E, - 0x000514, - 0x00061A, - 0x000820, - 0x000927, - 0x000B2D, - 0x000C33, - 0x000E39, - 0x001040, - 0x001450, - 0x001860, - 0x001C70, - 0x002080, - 0x1040BF, - 0x2060FF}; - boolean _refresh = true; + void read(PacificaModeSettings& settings, JsonObject& root) { + root["palette1"] = settings.palette1.id; + root["palette2"] = settings.palette2.id; + root["palette3"] = settings.palette3.id; + } + + StateUpdateResult update(JsonObject& root, PacificaModeSettings& settings) { + settings.palette1 = _paletteSettingsService->getPalette(root["palette1"] | PACIFICA_MODE_PALETTE1); + settings.palette2 = _paletteSettingsService->getPalette(root["palette2"] | PACIFICA_MODE_PALETTE2); + settings.palette3 = _paletteSettingsService->getPalette(root["palette3"] | PACIFICA_MODE_PALETTE3); + return StateUpdateResult::CHANGED; + } + void pacifica_one_layer(CRGB* leds, const uint16_t numLeds, CRGBPalette16& p, diff --git a/src/PaletteSettingsService.h b/src/PaletteSettingsService.h index 6be61d6f..50d03073 100644 --- a/src/PaletteSettingsService.h +++ b/src/PaletteSettingsService.h @@ -66,7 +66,7 @@ class Palette { CRGBPalette16 colors; // the default palette - rainbow - Palette() : id("rainbow"), colors(RainbowColors_p) { + Palette() : id("Rainbow"), colors(RainbowColors_p) { } // custom palettes @@ -80,16 +80,16 @@ class PaletteSettings { PaletteSettings() { palettes.push_back(Palette()); - palettes.push_back(Palette("party", PartyColors_p)); - palettes.push_back(Palette("heat", HeatColors_p)); - palettes.push_back(Palette("rainbowstripe", RainbowStripeColors_p)); - palettes.push_back(Palette("cloud", CloudColors_p)); - palettes.push_back(Palette("lava", LavaColors_p)); - palettes.push_back(Palette("ocean", OceanColors_p)); - palettes.push_back(Palette("forest", ForestColors_p)); - palettes.push_back(Palette("pacifica1", PacificaColors1_p)); - palettes.push_back(Palette("pacifica2", PacificaColors2_p)); - palettes.push_back(Palette("pacifica3", PacificaColors3_p)); + palettes.push_back(Palette("Party", PartyColors_p)); + palettes.push_back(Palette("Heat", HeatColors_p)); + palettes.push_back(Palette("Rainbow Stripe", RainbowStripeColors_p)); + palettes.push_back(Palette("Cloud", CloudColors_p)); + palettes.push_back(Palette("Lava", LavaColors_p)); + palettes.push_back(Palette("Ocean", OceanColors_p)); + palettes.push_back(Palette("Forest", ForestColors_p)); + palettes.push_back(Palette("Pacifica 1", PacificaColors1_p)); + palettes.push_back(Palette("Pacifica 2", PacificaColors2_p)); + palettes.push_back(Palette("Pacifica 3", PacificaColors3_p)); } static void read(PaletteSettings& settings, JsonObject& root) { From 7e766eb0c91985bea539e15a897a4f8da0a70d95 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Mon, 23 Nov 2020 22:18:07 +0000 Subject: [PATCH 35/52] add configurable palette to confetti mode --- interface/.env.development | 4 +- .../project/modes/AudioLightConfettiMode.tsx | 18 ++++++- .../src/project/modes/AudioLightFireMode.tsx | 2 +- .../project/modes/AudioLightPacificaMode.tsx | 5 +- interface/src/project/types.ts | 3 ++ src/ConfettiMode.cpp | 24 ++++++---- src/ConfettiMode.h | 48 ++++++++++++++----- src/PacificaMode.cpp | 15 +++++- src/PacificaMode.h | 20 +++++--- src/PaletteSettingsService.h | 4 ++ src/main.cpp | 3 ++ 11 files changed, 109 insertions(+), 37 deletions(-) diff --git a/interface/.env.development b/interface/.env.development index b12cfd04..f7bb8583 100644 --- a/interface/.env.development +++ b/interface/.env.development @@ -1,4 +1,4 @@ # Change the IP address to that of your ESP device to enable local development of the UI. # Remember to also enable CORS in platformio.ini before uploading the code to the device. -REACT_APP_HTTP_ROOT=http://192.168.0.88 -REACT_APP_WEB_SOCKET_ROOT=ws://192.168.0.88 +REACT_APP_HTTP_ROOT=http://192.168.0.77 +REACT_APP_WEB_SOCKET_ROOT=ws://192.168.0.77 diff --git a/interface/src/project/modes/AudioLightConfettiMode.tsx b/interface/src/project/modes/AudioLightConfettiMode.tsx index ae564cb1..2fa572aa 100644 --- a/interface/src/project/modes/AudioLightConfettiMode.tsx +++ b/interface/src/project/modes/AudioLightConfettiMode.tsx @@ -6,16 +6,32 @@ import { Box } from '@material-ui/core'; import { ConfettiMode } from '../types'; import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; +import PalettePicker from '../components/PalettePicker'; type AudioLightConfettiModeProps = AudioLightModeProps; class AudioLightConfettiMode extends React.Component { render() { - const { data, handleSliderChange } = this.props; + const { data, handleSliderChange, handleValueChange } = this.props; return (
+ + + Palette Changes (per cycle) ; diff --git a/interface/src/project/modes/AudioLightPacificaMode.tsx b/interface/src/project/modes/AudioLightPacificaMode.tsx index daebe15a..01e2e157 100644 --- a/interface/src/project/modes/AudioLightPacificaMode.tsx +++ b/interface/src/project/modes/AudioLightPacificaMode.tsx @@ -9,7 +9,7 @@ type AudioLightPacificaModeProps = AudioLightModeProps; class AudioLightPacificaMode extends React.Component { render() { - const { data, handleValueChange, handleSliderChange } = this.props; + const { data, handleValueChange } = this.props; return (
@@ -18,19 +18,16 @@ class AudioLightPacificaMode extends React.Component - - -
); } diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 806ebc62..cbd7e610 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -47,6 +47,9 @@ export interface LightningMode { export interface ConfettiMode { mode_id: AudioLightModeType.CONFETTI; + palette1: string; + palette2: string; + palette3: string; max_changes: number; brightness: number; delay: number; diff --git a/src/ConfettiMode.cpp b/src/ConfettiMode.cpp index 3a4017bc..5eb9ac60 100644 --- a/src/ConfettiMode.cpp +++ b/src/ConfettiMode.cpp @@ -12,12 +12,22 @@ ConfettiMode::ConfettiMode(AsyncWebServer* server, ledSettingsService, paletteSettingsService, frequencySampler, - ConfettiModeSettings::read, - ConfettiModeSettings::update, + std::bind(&ConfettiMode::read, this, std::placeholders::_1, std::placeholders::_2), + std::bind(&ConfettiMode::update, this, std::placeholders::_1, std::placeholders::_2), CONFETTI_MODE_ID) { addUpdateHandler([&](const String& originId) { enable(); }, false); }; +void ConfettiMode::enable() { + _refresh = true; + updateWithoutPropagation([&](ConfettiModeSettings& settings) { + _paletteSettingsService->refreshPalette(settings.palette1); + _paletteSettingsService->refreshPalette(settings.palette2); + _paletteSettingsService->refreshPalette(settings.palette3); + return StateUpdateResult::CHANGED; + }); +} + void ConfettiMode::tick() { if (_refresh) { _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { @@ -37,21 +47,21 @@ void ConfettiMode::tick() { lastSecond = secondHand; switch (secondHand) { case 0: - _targetPalette = OceanColors_p; + _targetPalette = _state.palette1.colors; _inc = 1; _hue = 192; _fade = 2; _hueDelta = 255; break; case 5: - _targetPalette = LavaColors_p; + _targetPalette = _state.palette2.colors; _inc = 2; _hue = 128; _fade = 8; _hueDelta = 64; break; case 10: - _targetPalette = ForestColors_p; + _targetPalette = _state.palette3.colors; _inc = 1; _hue = random16(255); _fade = 1; @@ -75,9 +85,5 @@ void ConfettiMode::tick() { } } -void ConfettiMode::enable() { - _refresh = true; -} - void ConfettiMode::sampleComplete() { } diff --git a/src/ConfettiMode.h b/src/ConfettiMode.h index 22ebdf86..015d1051 100644 --- a/src/ConfettiMode.h +++ b/src/ConfettiMode.h @@ -16,6 +16,18 @@ #define FACTORY_CONFETTI_MODE_DELAY 5 #endif +#ifndef FACTORY_CONFETTI_MODE_PALETTE1 +#define FACTORY_CONFETTI_MODE_PALETTE1 "Ocean" +#endif + +#ifndef FACTORY_CONFETTI_MODE_PALETTE2 +#define FACTORY_CONFETTI_MODE_PALETTE2 "Lava" +#endif + +#ifndef FACTORY_CONFETTI_MODE_PALETTE3 +#define FACTORY_CONFETTI_MODE_PALETTE3 "Forest" +#endif + #define CONFETTI_MODE_ID "confetti" // could make palettes configurable, pick from a list perhaps... @@ -25,18 +37,9 @@ class ConfettiModeSettings { uint8_t brightness; // Brightness of a sequence. Remember, max_brightnessght is the overall limiter. uint8_t delay; // We don't need much delay (if any) - static void read(ConfettiModeSettings& settings, JsonObject& root) { - writeByteToJson(root, &settings.maxChanges, "max_changes"); - writeByteToJson(root, &settings.brightness, "brightness"); - writeByteToJson(root, &settings.delay, "delay"); - } - - static StateUpdateResult update(JsonObject& root, ConfettiModeSettings& settings) { - updateByteFromJson(root, &settings.maxChanges, FACTORY_CONFETTI_MODE_MAX_CHANGES, "max_changes"); - updateByteFromJson(root, &settings.brightness, FACTORY_CONFETTI_MODE_BRIGHTNESS, "brightness"); - updateByteFromJson(root, &settings.delay, FACTORY_CONFETTI_MODE_DELAY, "delay"); - return StateUpdateResult::CHANGED; - } + Palette palette1; + Palette palette2; + Palette palette3; }; class ConfettiMode : public AudioLightModeImpl { @@ -53,6 +56,27 @@ class ConfettiMode : public AudioLightModeImpl { bool _refresh = true; // For applying config updates or enabling the mode + void read(ConfettiModeSettings& settings, JsonObject& root) { + writeByteToJson(root, &settings.maxChanges, "max_changes"); + writeByteToJson(root, &settings.brightness, "brightness"); + writeByteToJson(root, &settings.delay, "delay"); + + root["palette1"] = settings.palette1.id; + root["palette2"] = settings.palette2.id; + root["palette3"] = settings.palette3.id; + } + + StateUpdateResult update(JsonObject& root, ConfettiModeSettings& settings) { + updateByteFromJson(root, &settings.maxChanges, FACTORY_CONFETTI_MODE_MAX_CHANGES, "max_changes"); + updateByteFromJson(root, &settings.brightness, FACTORY_CONFETTI_MODE_BRIGHTNESS, "brightness"); + updateByteFromJson(root, &settings.delay, FACTORY_CONFETTI_MODE_DELAY, "delay"); + + settings.palette1 = _paletteSettingsService->getPalette(root["palette1"] | FACTORY_CONFETTI_MODE_PALETTE1); + settings.palette2 = _paletteSettingsService->getPalette(root["palette2"] | FACTORY_CONFETTI_MODE_PALETTE2); + settings.palette3 = _paletteSettingsService->getPalette(root["palette3"] | FACTORY_CONFETTI_MODE_PALETTE3); + return StateUpdateResult::CHANGED; + } + public: ConfettiMode(AsyncWebServer* server, FS* fs, diff --git a/src/PacificaMode.cpp b/src/PacificaMode.cpp index 6f668807..66f554cb 100644 --- a/src/PacificaMode.cpp +++ b/src/PacificaMode.cpp @@ -20,6 +20,12 @@ PacificaMode::PacificaMode(AsyncWebServer* server, void PacificaMode::enable() { _refresh = true; + updateWithoutPropagation([&](PacificaModeSettings& settings) { + _paletteSettingsService->refreshPalette(settings.palette1); + _paletteSettingsService->refreshPalette(settings.palette2); + _paletteSettingsService->refreshPalette(settings.palette3); + return StateUpdateResult::CHANGED; + }); } void PacificaMode::tick() { @@ -52,8 +58,13 @@ void PacificaMode::tick() { beatsin16(3, 11 * 256, 14 * 256), beatsin8(10, 70, 130), 0 - beat16(301)); - pacifica_one_layer( - leds, numLeds, _state.palette2.colors, sCIStart2, beatsin16(4, 6 * 256, 9 * 256), beatsin8(17, 40, 80), beat16(401)); + pacifica_one_layer(leds, + numLeds, + _state.palette2.colors, + sCIStart2, + beatsin16(4, 6 * 256, 9 * 256), + beatsin8(17, 40, 80), + beat16(401)); pacifica_one_layer(leds, numLeds, _state.palette3.colors, sCIStart3, 6 * 256, beatsin8(9, 10, 38), 0 - beat16(503)); pacifica_one_layer(leds, numLeds, _state.palette3.colors, sCIStart4, 5 * 256, beatsin8(8, 10, 28), beat16(601)); diff --git a/src/PacificaMode.h b/src/PacificaMode.h index 3db06583..107557cc 100644 --- a/src/PacificaMode.h +++ b/src/PacificaMode.h @@ -5,9 +5,17 @@ #include #include -#define PACIFICA_MODE_PALETTE1 "Pacifica 1" -#define PACIFICA_MODE_PALETTE2 "Pacifica 2" -#define PACIFICA_MODE_PALETTE3 "Pacifica 3" +#ifndef FACTORY_PACIFICA_MODE_PALETTE1 +#define FACTORY_PACIFICA_MODE_PALETTE1 "Pacifica 1" +#endif + +#ifndef FACTORY_PACIFICA_MODE_PALETTE2 +#define FACTORY_PACIFICA_MODE_PALETTE2 "Pacifica 2" +#endif + +#ifndef FACTORY_PACIFICA_MODE_PALETTE3 +#define FACTORY_PACIFICA_MODE_PALETTE3 "Pacifica 3" +#endif #define PACIFICA_MODE_ID "pacifica" @@ -29,9 +37,9 @@ class PacificaMode : public AudioLightModeImpl { } StateUpdateResult update(JsonObject& root, PacificaModeSettings& settings) { - settings.palette1 = _paletteSettingsService->getPalette(root["palette1"] | PACIFICA_MODE_PALETTE1); - settings.palette2 = _paletteSettingsService->getPalette(root["palette2"] | PACIFICA_MODE_PALETTE2); - settings.palette3 = _paletteSettingsService->getPalette(root["palette3"] | PACIFICA_MODE_PALETTE3); + settings.palette1 = _paletteSettingsService->getPalette(root["palette1"] | FACTORY_PACIFICA_MODE_PALETTE1); + settings.palette2 = _paletteSettingsService->getPalette(root["palette2"] | FACTORY_PACIFICA_MODE_PALETTE2); + settings.palette3 = _paletteSettingsService->getPalette(root["palette3"] | FACTORY_PACIFICA_MODE_PALETTE3); return StateUpdateResult::CHANGED; } diff --git a/src/PaletteSettingsService.h b/src/PaletteSettingsService.h index 50d03073..5cbeed3f 100644 --- a/src/PaletteSettingsService.h +++ b/src/PaletteSettingsService.h @@ -139,6 +139,10 @@ class PaletteSettingsService : public StatefulService { _fsPersistence(PaletteSettings::read, PaletteSettings::update, this, fs, PALETTE_SETTINGS_FILE, 10240) { } + void begin() { + _fsPersistence.readFromFS(); + } + void refreshPalette(Palette& palette) { palette = getPalette(palette.id); } diff --git a/src/main.cpp b/src/main.cpp index 2fa5ab85..9e256799 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,6 +36,9 @@ void setup() { // configure the LED strip ledSettingsService.begin(); + // load the palettes + paletteSettingsService.begin(); + // load all of the defaults audioLightSettingsService.begin(); } From b98c90fac43b1ac1b8fbab1276bb1f35edc501b7 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Mon, 23 Nov 2020 23:34:00 +0000 Subject: [PATCH 36/52] use palettes from service instead of hardcoded palettes --- interface/src/project/LightsProject.tsx | 15 +++--- .../src/project/PaletteSettingsContext.tsx | 12 +++++ .../src/project/PaletteSettingsLoader.tsx | 39 ++++++++++++++ .../src/project/components/PalettePicker.tsx | 53 +++++++------------ src/PaletteSettingsService.h | 26 +++++---- 5 files changed, 92 insertions(+), 53 deletions(-) create mode 100644 interface/src/project/PaletteSettingsContext.tsx create mode 100644 interface/src/project/PaletteSettingsLoader.tsx diff --git a/interface/src/project/LightsProject.tsx b/interface/src/project/LightsProject.tsx index 1f3360ab..b56f77fb 100644 --- a/interface/src/project/LightsProject.tsx +++ b/interface/src/project/LightsProject.tsx @@ -10,6 +10,7 @@ import { AuthenticatedRoute } from '../authentication'; import SpectrumAnalyzer from './SpectrumAnalyzer'; import AudioLightSettingsController from './AudioLightSettingsController'; import PaletteSettingsController from './PaletteSettingsController'; +import PaletteSettingsLoader from './PaletteSettingsLoader'; class LightsProject extends Component { @@ -25,12 +26,14 @@ class LightsProject extends Component { - - - - - - + + + + + + + + ) } diff --git a/interface/src/project/PaletteSettingsContext.tsx b/interface/src/project/PaletteSettingsContext.tsx new file mode 100644 index 00000000..df3da935 --- /dev/null +++ b/interface/src/project/PaletteSettingsContext.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import { PaletteSettings } from "./types"; + +export type PaletteSettingsContextType = { + paletteSettingsUpdated: (paletteSettings: PaletteSettings) => void; + paletteSettings: PaletteSettings; +} + +const PaletteSettingsContextDefaultValue = {} as PaletteSettingsContextType; +export const PaletteSettingsContext = React.createContext( + PaletteSettingsContextDefaultValue +); diff --git a/interface/src/project/PaletteSettingsLoader.tsx b/interface/src/project/PaletteSettingsLoader.tsx new file mode 100644 index 00000000..26bdd6e3 --- /dev/null +++ b/interface/src/project/PaletteSettingsLoader.tsx @@ -0,0 +1,39 @@ +import React, { Component } from 'react'; + +import { restController, RestControllerProps, RestFormLoader, RestFormProps } from '../components'; +import { PaletteSettingsContext } from './PaletteSettingsContext'; +import { PaletteSettings, PALETTE_SETTINGS_ENDPOINT } from './types'; + +class PaletteSettingsLoader extends Component> { + + componentDidMount() { + this.props.loadData(); + } + + render() { + return ( + } + /> + ) + } + +} + +class PaletteSettingsContextProvider extends Component> { + + render() { + return ( + + {this.props.children} + + ) + } + +} + +export default restController(PALETTE_SETTINGS_ENDPOINT, PaletteSettingsLoader); diff --git a/interface/src/project/components/PalettePicker.tsx b/interface/src/project/components/PalettePicker.tsx index fa2a2336..625d99fc 100644 --- a/interface/src/project/components/PalettePicker.tsx +++ b/interface/src/project/components/PalettePicker.tsx @@ -1,5 +1,6 @@ -import React from 'react'; +import React, { FC, useContext } from 'react'; import { MenuItem, TextField } from '@material-ui/core'; +import { PaletteSettingsContext } from '../PaletteSettingsContext'; interface PalettePickerProps { name: string; @@ -8,38 +9,24 @@ interface PalettePickerProps { onChange: (event: React.ChangeEvent) => void; } -class PalettePicker extends React.Component { - render() { - const { - name, - label, - value, - onChange - } = this.props; - return ( - - Rainbow - Party - Heat - Rainbow Stripe - Cloud - Lava - Ocean - Forest - Pacifica 1 - Pacifica 2 - Pacifica 3 - - ); - } +const PalettePicker: FC = ({ name, label, value, onChange }) => { + const context = useContext(PaletteSettingsContext); + return ( + + {context.paletteSettings.palettes.map(palette => ( + {palette.id} + ))} + + ); } + export default PalettePicker; diff --git a/src/PaletteSettingsService.h b/src/PaletteSettingsService.h index 5cbeed3f..9b62606c 100644 --- a/src/PaletteSettingsService.h +++ b/src/PaletteSettingsService.h @@ -78,20 +78,6 @@ class PaletteSettings { public: std::list palettes; - PaletteSettings() { - palettes.push_back(Palette()); - palettes.push_back(Palette("Party", PartyColors_p)); - palettes.push_back(Palette("Heat", HeatColors_p)); - palettes.push_back(Palette("Rainbow Stripe", RainbowStripeColors_p)); - palettes.push_back(Palette("Cloud", CloudColors_p)); - palettes.push_back(Palette("Lava", LavaColors_p)); - palettes.push_back(Palette("Ocean", OceanColors_p)); - palettes.push_back(Palette("Forest", ForestColors_p)); - palettes.push_back(Palette("Pacifica 1", PacificaColors1_p)); - palettes.push_back(Palette("Pacifica 2", PacificaColors2_p)); - palettes.push_back(Palette("Pacifica 3", PacificaColors3_p)); - } - static void read(PaletteSettings& settings, JsonObject& root) { JsonArray palettes = root.createNestedArray("palettes"); for (Palette palette : settings.palettes) { @@ -113,6 +99,18 @@ class PaletteSettings { settings.palettes.push_back(palette); } } + } else { + settings.palettes.push_back(Palette()); + settings.palettes.push_back(Palette("Party", PartyColors_p)); + settings.palettes.push_back(Palette("Heat", HeatColors_p)); + settings.palettes.push_back(Palette("Rainbow Stripe", RainbowStripeColors_p)); + settings.palettes.push_back(Palette("Cloud", CloudColors_p)); + settings.palettes.push_back(Palette("Lava", LavaColors_p)); + settings.palettes.push_back(Palette("Ocean", OceanColors_p)); + settings.palettes.push_back(Palette("Forest", ForestColors_p)); + settings.palettes.push_back(Palette("Pacifica 1", PacificaColors1_p)); + settings.palettes.push_back(Palette("Pacifica 2", PacificaColors2_p)); + settings.palettes.push_back(Palette("Pacifica 3", PacificaColors3_p)); } return StateUpdateResult::CHANGED; } From f0f1c3f022fc9055109e65c53a4f61c476df0d09 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Mon, 23 Nov 2020 23:56:01 +0000 Subject: [PATCH 37/52] improve palette select --- .../project/AudioLightSettingsController.tsx | 15 +++++++++------ .../src/project/PaletteSettingsController.tsx | 19 +++++++++++-------- interface/src/project/SpectrumAnalyzer.tsx | 19 +++++++++++-------- .../src/project/components/PalettePicker.tsx | 12 ++++++++++-- 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/interface/src/project/AudioLightSettingsController.tsx b/interface/src/project/AudioLightSettingsController.tsx index a14ffbbd..1c890280 100644 --- a/interface/src/project/AudioLightSettingsController.tsx +++ b/interface/src/project/AudioLightSettingsController.tsx @@ -1,3 +1,4 @@ +import { Container } from '@material-ui/core'; import React, { Component } from 'react'; import { SectionContent, webSocketController, WebSocketControllerProps, WebSocketFormLoader } from '../components'; @@ -10,12 +11,14 @@ class AudioLightSettingsController extends Component - } - /> - + + + } + /> + + ); } diff --git a/interface/src/project/PaletteSettingsController.tsx b/interface/src/project/PaletteSettingsController.tsx index 934467b6..e521a9ed 100644 --- a/interface/src/project/PaletteSettingsController.tsx +++ b/interface/src/project/PaletteSettingsController.tsx @@ -1,9 +1,10 @@ +import { Container } from '@material-ui/core'; import React, { Component } from 'react'; -import {restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; import PaletteSettingsForm from './PaletteSettingsForm'; -import { PaletteSettings, PALETTE_SETTINGS_ENDPOINT } from './types'; +import { PaletteSettings, PALETTE_SETTINGS_ENDPOINT } from './types'; type PaletteSettingsControllerProps = RestControllerProps; @@ -15,12 +16,14 @@ class PaletteSettingsController extends Component - } - /> - + + + } + /> + + ) } diff --git a/interface/src/project/SpectrumAnalyzer.tsx b/interface/src/project/SpectrumAnalyzer.tsx index e5febd92..778b2354 100644 --- a/interface/src/project/SpectrumAnalyzer.tsx +++ b/interface/src/project/SpectrumAnalyzer.tsx @@ -1,3 +1,4 @@ +import { Container } from '@material-ui/core'; import React, { Component } from 'react'; import { WEB_SOCKET_ROOT } from '../api'; import { SectionContent, webSocketController, WebSocketControllerProps, WebSocketFormLoader, WebSocketFormProps } from '../components'; @@ -42,14 +43,16 @@ class SpectrumAnalyzerWebSocketController extends Component - ( - - )} - /> - + + + ( + + )} + /> + + ) } diff --git a/interface/src/project/components/PalettePicker.tsx b/interface/src/project/components/PalettePicker.tsx index 625d99fc..da7c42a8 100644 --- a/interface/src/project/components/PalettePicker.tsx +++ b/interface/src/project/components/PalettePicker.tsx @@ -1,6 +1,7 @@ import React, { FC, useContext } from 'react'; -import { MenuItem, TextField } from '@material-ui/core'; +import { Box, ListItemText, MenuItem, TextField } from '@material-ui/core'; import { PaletteSettingsContext } from '../PaletteSettingsContext'; +import { generateGradient } from '../types'; interface PalettePickerProps { name: string; @@ -22,7 +23,14 @@ const PalettePicker: FC = ({ name, label, value, onChange }) variant="outlined" select> {context.paletteSettings.palettes.map(palette => ( - {palette.id} + + + + + + + + ))} ); From 313cdbfada98305e9228442f8087019be7fd0e0f Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Tue, 24 Nov 2020 00:00:08 +0000 Subject: [PATCH 38/52] remove ellipses from form --- interface/src/project/AudioLightSettingsForm.tsx | 2 +- interface/src/project/modes/AudioLightConfettiMode.tsx | 6 +++--- interface/src/project/modes/AudioLightFireMode.tsx | 2 +- interface/src/project/modes/AudioLightPacificaMode.tsx | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/interface/src/project/AudioLightSettingsForm.tsx b/interface/src/project/AudioLightSettingsForm.tsx index 6fb463d1..0935c880 100644 --- a/interface/src/project/AudioLightSettingsForm.tsx +++ b/interface/src/project/AudioLightSettingsForm.tsx @@ -74,7 +74,7 @@ class AudioLightSettingsForm extends React.Component saveDataAndClear({ mode_id: event.target.value as AudioLightModeType })} fullWidth diff --git a/interface/src/project/modes/AudioLightConfettiMode.tsx b/interface/src/project/modes/AudioLightConfettiMode.tsx index 2fa572aa..01bf4ccf 100644 --- a/interface/src/project/modes/AudioLightConfettiMode.tsx +++ b/interface/src/project/modes/AudioLightConfettiMode.tsx @@ -19,17 +19,17 @@ class AudioLightConfettiMode extends React.Component diff --git a/interface/src/project/modes/AudioLightFireMode.tsx b/interface/src/project/modes/AudioLightFireMode.tsx index fb4e5096..97c43dee 100644 --- a/interface/src/project/modes/AudioLightFireMode.tsx +++ b/interface/src/project/modes/AudioLightFireMode.tsx @@ -19,7 +19,7 @@ class AudioLightFireMode extends React.Component {
diff --git a/interface/src/project/modes/AudioLightPacificaMode.tsx b/interface/src/project/modes/AudioLightPacificaMode.tsx index 01e2e157..a3133a75 100644 --- a/interface/src/project/modes/AudioLightPacificaMode.tsx +++ b/interface/src/project/modes/AudioLightPacificaMode.tsx @@ -15,17 +15,17 @@ class AudioLightPacificaMode extends React.Component
From 8da3f6fac66e316da3d41bca03ae840f700cdd43 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Wed, 25 Nov 2020 20:48:43 +0000 Subject: [PATCH 39/52] add some configuration properties to pride mode --- .../src/project/AudioLightSettingsForm.tsx | 3 + .../src/project/modes/AudioLightPrideMode.tsx | 73 +++++++++++++++++++ interface/src/project/types.ts | 6 ++ src/PrideMode.cpp | 4 +- src/PrideMode.h | 46 ++++++++++++ 5 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 interface/src/project/modes/AudioLightPrideMode.tsx diff --git a/interface/src/project/AudioLightSettingsForm.tsx b/interface/src/project/AudioLightSettingsForm.tsx index 0935c880..e39a37d0 100644 --- a/interface/src/project/AudioLightSettingsForm.tsx +++ b/interface/src/project/AudioLightSettingsForm.tsx @@ -13,6 +13,7 @@ import { AudioLightModeType, AudioLightMode, AUDIO_LIGHT_LOAD_SETTINGS_ENDPOINT, import AudioLightConfettiMode from './modes/AudioLightConfettiMode'; import { redirectingAuthorizedFetch } from '../authentication'; import AudioLightPacificaMode from './modes/AudioLightPacificaMode'; +import AudioLightPrideMode from './modes/AudioLightPrideMode'; type AudioLightSettingsFormProps = WebSocketFormProps>; @@ -63,6 +64,8 @@ class AudioLightSettingsForm extends React.Component; + +class AudioLightPrideMode extends React.Component { + render() { + const { data, handleSliderChange } = this.props; + + return ( +
+ + Brightness BPM + + Brightness Freq Min + + Brightness Freq Max + + Hue BPM + + Hue Delta Min + + Hue Delta Max + + +
+ ); + } +} + +export default audioLightMode(AudioLightPrideMode); diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index cbd7e610..d806a06a 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -72,6 +72,12 @@ export interface PacificaMode { export interface PrideMode { mode_id: AudioLightModeType.PRIDE; + brightness_bpm: number; + brightness_freq_min: number; + brightness_freq_max: number; + hue_bpm: number; + hue_delta_min: number; + hue_delta_max: number; } export interface PaletteSettings { diff --git a/src/PrideMode.cpp b/src/PrideMode.cpp index a8169403..8e72fed2 100644 --- a/src/PrideMode.cpp +++ b/src/PrideMode.cpp @@ -31,10 +31,10 @@ void PrideMode::tick() { uint8_t sat8 = beatsin88(87, 220, 250); uint8_t brightdepth = beatsin88(341, 96, 224); uint16_t brightnessthetainc16 = beatsin88(203, (25 * 256), (40 * 256)); - uint8_t msmultiplier = beatsin88(147, 23, 60); + uint8_t msmultiplier = beatsin88( _state.brightnessBpm, _state.brightnessFreqMin, _state.brightnessFreqMax); uint16_t hue16 = sHue16; // gHue * 256; - uint16_t hueinc16 = beatsin88(113, 1, 3000); + uint16_t hueinc16 = beatsin88(_state.hueBpm, _state.hueDeltaMin, _state.hueDeltaMax); uint16_t ms = millis(); uint16_t deltams = ms - sLastMillis; diff --git a/src/PrideMode.h b/src/PrideMode.h index 0144d91f..75d74ad8 100644 --- a/src/PrideMode.h +++ b/src/PrideMode.h @@ -7,12 +7,58 @@ #define PRIDE_MODE_ID "pride" +#ifndef FACTORY_PRIDE_MODE_BRIGHTNESS_MULTIPLIER_MIN +#define FACTORY_PRIDE_MODE_BRIGHTNESS_MULTIPLIER_MIN 23 +#endif + +#ifndef FACTORY_PRIDE_MODE_BRIGHTNESS_MULTIPLIER_MAX +#define FACTORY_PRIDE_MODE_BRIGHTNESS_MULTIPLIER_MAX 60 +#endif + +#ifndef FACTORY_PRIDE_MODE_BRIGHTNESS_BPM +#define FACTORY_PRIDE_MODE_BRIGHTNESS_BPM 147 +#endif + +#ifndef FACTORY_PRIDE_MODE_HUE_BPM +#define FACTORY_PRIDE_MODE_HUE_BPM 113 +#endif + +#ifndef FACTORY_PRIDE_MODE_HUDE_DELTA_MIN +#define FACTORY_PRIDE_MODE_HUDE_DELTA_MIN 1 +#endif + +#ifndef FACTORY_PRIDE_MODE_HUDE_DELTA_MAX +#define FACTORY_PRIDE_MODE_HUDE_DELTA_MAX 3000 +#endif + class PrideModeSettings { public: + uint8_t brightnessBpm; + uint8_t brightnessFreqMin; + uint8_t brightnessFreqMax; + + uint8_t hueBpm; + uint16_t hueDeltaMin; + uint16_t hueDeltaMax; + static void read(PrideModeSettings& settings, JsonObject& root) { + writeByteToJson(root, &settings.brightnessBpm, "brightness_bpm"); + writeByteToJson(root, &settings.brightnessFreqMin, "brightness_freq_min"); + writeByteToJson(root, &settings.brightnessFreqMax, "brightness_freq_max"); + writeByteToJson(root, &settings.hueBpm, "hue_bpm"); + root["hue_delta_min"] = settings.hueDeltaMin; + root["hue_delta_max"] = settings.hueDeltaMax; } static StateUpdateResult update(JsonObject& root, PrideModeSettings& settings) { + updateByteFromJson(root, &settings.brightnessBpm, FACTORY_PRIDE_MODE_BRIGHTNESS_BPM, "brightness_bpm"); + updateByteFromJson( + root, &settings.brightnessFreqMin, FACTORY_PRIDE_MODE_BRIGHTNESS_MULTIPLIER_MIN, "brightness_freq_min"); + updateByteFromJson( + root, &settings.brightnessFreqMax, FACTORY_PRIDE_MODE_BRIGHTNESS_MULTIPLIER_MAX, "brightness_freq_max"); + updateByteFromJson(root, &settings.hueBpm, FACTORY_PRIDE_MODE_HUE_BPM, "hue_bpm"); + settings.hueDeltaMin = root["hue_delta_min"] | FACTORY_PRIDE_MODE_HUDE_DELTA_MIN; + settings.hueDeltaMax = root["hue_delta_max"] | FACTORY_PRIDE_MODE_HUDE_DELTA_MAX; return StateUpdateResult::CHANGED; } }; From 0c503e848fad83e586a731e15b41d2d00e1429e8 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Fri, 27 Nov 2020 00:24:17 +0000 Subject: [PATCH 40/52] WIP - Rotate Mode --- .../project/AudioLightSettingsController.tsx | 11 +- .../src/project/AudioLightSettingsForm.tsx | 47 ++------ .../src/project/modes/AudioLightPrideMode.tsx | 2 +- .../project/modes/AudioLightRotateMode.tsx | 45 ++++++++ interface/src/project/types.ts | 101 +++++++++++++++++- src/AudioLightSettingsService.cpp | 7 ++ src/AudioLightSettingsService.h | 6 +- src/RotateMode.cpp | 88 +++++++++++++++ src/RotateMode.h | 40 +++++++ 9 files changed, 302 insertions(+), 45 deletions(-) create mode 100644 interface/src/project/modes/AudioLightRotateMode.tsx create mode 100644 src/RotateMode.cpp create mode 100644 src/RotateMode.h diff --git a/interface/src/project/AudioLightSettingsController.tsx b/interface/src/project/AudioLightSettingsController.tsx index 1c890280..87a86f14 100644 --- a/interface/src/project/AudioLightSettingsController.tsx +++ b/interface/src/project/AudioLightSettingsController.tsx @@ -3,6 +3,7 @@ import React, { Component } from 'react'; import { SectionContent, webSocketController, WebSocketControllerProps, WebSocketFormLoader } from '../components'; import AudioLightSettingsForm from './AudioLightSettingsForm'; +import PaletteSettingsLoader from './PaletteSettingsLoader'; import { AudioLightMode, AUDIO_LIGHT_SETTINGS_ENDPOINT } from './types'; type AudioLightSettingsControllerProps = WebSocketControllerProps>; @@ -13,10 +14,12 @@ class AudioLightSettingsController extends Component - } - /> + + } + /> + ); diff --git a/interface/src/project/AudioLightSettingsForm.tsx b/interface/src/project/AudioLightSettingsForm.tsx index e39a37d0..74e7346d 100644 --- a/interface/src/project/AudioLightSettingsForm.tsx +++ b/interface/src/project/AudioLightSettingsForm.tsx @@ -3,17 +3,9 @@ import { TextField, MenuItem } from '@material-ui/core'; import SaveIcon from '@material-ui/icons/Save'; import LoadIcon from '@material-ui/icons/SaveAlt'; -import AudioLightLightningMode from './modes/AudioLightLightningMode'; -import AudioLightRainbowMode from './modes/AudioLightRainbowMode'; -import AudioLightFireMode from './modes/AudioLightFireMode'; -import AudioLightColorMode from './modes/AudioLightColorMode'; - import { FormActions, FormButton, WebSocketFormProps } from '../components'; -import { AudioLightModeType, AudioLightMode, AUDIO_LIGHT_LOAD_SETTINGS_ENDPOINT, AUDIO_LIGHT_SAVE_SETTINGS_ENDPOINT } from './types'; -import AudioLightConfettiMode from './modes/AudioLightConfettiMode'; +import { AudioLightModeType, AudioLightMode, AUDIO_LIGHT_LOAD_SETTINGS_ENDPOINT, AUDIO_LIGHT_SAVE_SETTINGS_ENDPOINT, AUDIO_LIGHT_MODE_METADATA } from './types'; import { redirectingAuthorizedFetch } from '../authentication'; -import AudioLightPacificaMode from './modes/AudioLightPacificaMode'; -import AudioLightPrideMode from './modes/AudioLightPrideMode'; type AudioLightSettingsFormProps = WebSocketFormProps>; @@ -49,25 +41,9 @@ class AudioLightSettingsForm extends React.Component - Off - Single Color - Rainbow - Lightning - Confetti - Fire - Pacifica - Pride + { + Object.entries(AudioLightModeType).map(([, mode_id]) => ( + + {AUDIO_LIGHT_MODE_METADATA[mode_id].label} + + )) + }
{ ModeComponent && } diff --git a/interface/src/project/modes/AudioLightPrideMode.tsx b/interface/src/project/modes/AudioLightPrideMode.tsx index a31c48b6..522a2b8f 100644 --- a/interface/src/project/modes/AudioLightPrideMode.tsx +++ b/interface/src/project/modes/AudioLightPrideMode.tsx @@ -5,7 +5,7 @@ import Slider from '@material-ui/core/Slider'; import { PrideMode } from '../types'; import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; -import { Box, Switch } from '@material-ui/core'; +import { Box } from '@material-ui/core'; type AudioLightPrideModeProps = AudioLightModeProps; diff --git a/interface/src/project/modes/AudioLightRotateMode.tsx b/interface/src/project/modes/AudioLightRotateMode.tsx new file mode 100644 index 00000000..218a0387 --- /dev/null +++ b/interface/src/project/modes/AudioLightRotateMode.tsx @@ -0,0 +1,45 @@ +import { Box, FormLabel, Input, MenuItem, Select } from '@material-ui/core'; +import React from 'react'; + +import { AudioLightModeType, AUDIO_LIGHT_MODE_METADATA, RotateMode } from '../types'; +import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; + +type AudioLightRotateModeProps = AudioLightModeProps; + +class AudioLightRotateMode extends React.Component { + + changeModes = this.props.handleChange("modes"); + + render() { + const { data } = this.props; + return ( +
+ + Audio Enabled + } + fullWidth + variant="outlined" + > + { + Object.entries(AudioLightModeType) + .filter(([, mode_id]) => AUDIO_LIGHT_MODE_METADATA[mode_id].rotate) + .map(([, mode_id]) => ( + + {AUDIO_LIGHT_MODE_METADATA[mode_id].label} + + )) + } + + +
+ ); + } +} + +export default audioLightMode(AudioLightRotateMode); diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index d806a06a..df889798 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -1,9 +1,25 @@ import { ENDPOINT_ROOT, WEB_SOCKET_ROOT } from "../api"; +import { WebSocketFormProps } from "../components"; + +import AudioLightColorMode from "./modes/AudioLightColorMode"; +import AudioLightConfettiMode from "./modes/AudioLightConfettiMode"; +import AudioLightFireMode from "./modes/AudioLightFireMode"; +import AudioLightLightningMode from "./modes/AudioLightLightningMode"; +import AudioLightPacificaMode from "./modes/AudioLightPacificaMode"; +import AudioLightPrideMode from "./modes/AudioLightPrideMode"; +import AudioLightRainbowMode from "./modes/AudioLightRainbowMode"; +import AudioLightRotateMode from "./modes/AudioLightRotateMode"; export interface FrequencyData { bands: number[]; } +export interface AudioLightModeMetadata { + label: string; + renderer?: React.ComponentType>; + rotate: boolean; +} + export enum AudioLightModeType { OFF = "off", COLOR = "color", @@ -12,13 +28,19 @@ export enum AudioLightModeType { CONFETTI = "confetti", FIRE = "fire", PACIFICA = "pacifica", - PRIDE = "pride" + PRIDE = "pride", + ROTATE = "rotate" } export interface OffMode { mode_id: AudioLightModeType.OFF; } +export const OFF_MODE_METADATA: AudioLightModeMetadata = { + label: "Off", + rotate: false +}; + export interface ColorMode { mode_id: AudioLightModeType.COLOR; color: string; @@ -27,6 +49,12 @@ export interface ColorMode { included_bands: boolean[] } +export const COLOR_MODE_METADATA: AudioLightModeMetadata = { + label: "Color", + renderer: AudioLightColorMode, + rotate: true +}; + export interface RainbowMode { mode_id: AudioLightModeType.RAINBOW; brightness: number; @@ -35,6 +63,12 @@ export interface RainbowMode { hue_delta: number; } +export const RAINBOW_MODE_METADATA: AudioLightModeMetadata = { + label: "Rainbow", + renderer: AudioLightRainbowMode, + rotate: true +}; + export interface LightningMode { mode_id: AudioLightModeType.LIGHTNING; color: string; @@ -45,6 +79,12 @@ export interface LightningMode { included_bands: boolean[] } +export const LIGHTNING_MODE_METADATA: AudioLightModeMetadata = { + label: "Lightning", + renderer: AudioLightLightningMode, + rotate: true +}; + export interface ConfettiMode { mode_id: AudioLightModeType.CONFETTI; palette1: string; @@ -55,6 +95,12 @@ export interface ConfettiMode { delay: number; } +export const CONFETTI_MODE_METADATA: AudioLightModeMetadata = { + label: "Confetti", + renderer: AudioLightConfettiMode, + rotate: true +}; + export interface FireMode { mode_id: AudioLightModeType.FIRE; palette: string; @@ -63,6 +109,12 @@ export interface FireMode { reverse: boolean; } +export const FIRE_MODE_METADATA: AudioLightModeMetadata = { + label: "Fire", + renderer: AudioLightFireMode, + rotate: true +}; + export interface PacificaMode { mode_id: AudioLightModeType.PACIFICA; palette1: string; @@ -70,6 +122,12 @@ export interface PacificaMode { palette3: string; } +export const PACIFICA_MODE_METADATA: AudioLightModeMetadata = { + label: "Pacifica", + renderer: AudioLightPacificaMode, + rotate: true +}; + export interface PrideMode { mode_id: AudioLightModeType.PRIDE; brightness_bpm: number; @@ -80,6 +138,23 @@ export interface PrideMode { hue_delta_max: number; } +export const PRIDE_MODE_METADATA: AudioLightModeMetadata = { + label: "Pride", + renderer: AudioLightPrideMode, + rotate: true +}; + +export interface RotateMode { + mode_id: AudioLightModeType.ROTATE; + modes: AudioLightModeType[]; +} + +export const ROTATE_MODE_METADATA: AudioLightModeMetadata = { + label: "Rotate", + renderer: AudioLightRotateMode, + rotate: false +}; + export interface PaletteSettings { palettes: Palette[]; } @@ -89,7 +164,17 @@ export interface Palette { colors: string[]; }; -export type AudioLightMode = OffMode | ColorMode | RainbowMode | LightningMode | ConfettiMode | FireMode | PacificaMode | PrideMode; +export type AudioLightMode = ( + OffMode | + ColorMode | + RainbowMode | + LightningMode | + ConfettiMode | + FireMode | + PacificaMode | + PrideMode | + RotateMode +); export const PALETTE_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "paletteSettings"; export const AUDIO_LIGHT_SAVE_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "saveModeSettings"; @@ -118,3 +203,15 @@ export const DEFAULT_PALETTE = [ "#ab0055", "#d5002b" ]; + +export const AUDIO_LIGHT_MODE_METADATA: { [type in AudioLightModeType]: AudioLightModeMetadata } = { + off: OFF_MODE_METADATA, + color: COLOR_MODE_METADATA, + rainbow: RAINBOW_MODE_METADATA, + lightning: LIGHTNING_MODE_METADATA, + confetti: CONFETTI_MODE_METADATA, + fire: FIRE_MODE_METADATA, + pacifica: PACIFICA_MODE_METADATA, + pride: PRIDE_MODE_METADATA, + rotate: ROTATE_MODE_METADATA +} diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index 2a432508..321760b9 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -47,6 +47,13 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, _modes[6] = new PacificaMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); _modes[7] = new PrideMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); + _modes[8] = new RotateMode(server, + fs, + securityManager, + ledSettingsService, + paletteSettingsService, + frequencySampler, + std::bind(&AudioLightSettingsService::getMode, this, std::placeholders::_1)); } void AudioLightSettingsService::begin() { diff --git a/src/AudioLightSettingsService.h b/src/AudioLightSettingsService.h index 28cfb020..8baed3af 100644 --- a/src/AudioLightSettingsService.h +++ b/src/AudioLightSettingsService.h @@ -11,9 +11,10 @@ #include #include #include +#include #include -#define NUM_MODES 8 +#define NUM_MODES 9 #define AUDIO_LIGHT_SERVICE_PATH "/rest/audioLightSettings" #define AUDIO_LIGHT_WS_PATH "/ws/audioLightSettings" @@ -40,6 +41,7 @@ class AudioLightSettingsService : public StatefulService { void begin(); void loop(); + private: HttpEndpoint _httpEndpoint; WebSocketTxRx _audioLightModeTxRx; @@ -47,8 +49,8 @@ class AudioLightSettingsService : public StatefulService { void read(AudioLightSettings& settings, JsonObject& root); StateUpdateResult update(JsonObject& root, AudioLightSettings& settings); - AudioLightMode* getMode(const String& modeId); + void enableMode(); void handleSample(); void saveModeConfig(AsyncWebServerRequest* request); diff --git a/src/RotateMode.cpp b/src/RotateMode.cpp new file mode 100644 index 00000000..a8625aa0 --- /dev/null +++ b/src/RotateMode.cpp @@ -0,0 +1,88 @@ +#include + +RotateMode::RotateMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, + FrequencySampler* frequencySampler, + ModeFetcher modeFetcher) : + AudioLightModeImpl(server, + fs, + securityManager, + ledSettingsService, + paletteSettingsService, + frequencySampler, + std::bind(&RotateMode::read, this, std::placeholders::_1, std::placeholders::_2), + std::bind(&RotateMode::update, this, std::placeholders::_1, std::placeholders::_2), + ROTATE_MODE_ID), + _modeFetcher(modeFetcher) { + addUpdateHandler([&](const String& originId) { enable(); }, false); +}; + +void RotateMode::read(RotateModeSettings& settings, JsonObject& root) { + JsonArray modes = root.createNestedArray("modes"); + for (String mode : settings.modes) { + modes.add(mode); + } +} + +StateUpdateResult RotateMode::update(JsonObject& root, RotateModeSettings& settings) { + settings.modes.clear(); + if (root["modes"].is()) { + for (String mode : root["modes"].as()) { + // do not allow self - that would be bad! + // check the mode exists + if (mode != ROTATE_MODE_ID && _modeFetcher(mode)) { + settings.modes.push_back(mode); + } + } + } + return StateUpdateResult::CHANGED; +} + +void RotateMode::enable() { + _refresh = true; +} + +void RotateMode::tick() { + if (_refresh) { + selectMode(0); + _refresh = false; + } + + // hand off to the selected mode to do its thing + if (_selectedMode) { + _selectedMode->tick(); + } + + // select the next mode + EVERY_N_MILLISECONDS(5000) { + selectMode(_currentMode + 1); + } +} + +void RotateMode::selectMode(uint8_t newMode) { + AudioLightMode* nextMode = nullptr; + // select the next mode + if (_state.modes.size() > 0) { + // increment mode, resetting to zero if we've gone past the end of the array + _currentMode = newMode < _state.modes.size() ? newMode : 0; + // find the next mode + nextMode = _modeFetcher(*std::next(_state.modes.begin(), _currentMode)); + } + // activate the next mode if it's differnt from the selected mode + if (nextMode != _selectedMode) { + _selectedMode = nextMode; + if (_selectedMode) { + // new mode selected, enable it + _selectedMode->enable(); + } else { + // no mode selected, clear LEDs + _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + fill_solid(leds, numLeds, CHSV(255, 0, 0)); // clear leds + ledController->showLeds(); // render all leds black + }); + } + } +} diff --git a/src/RotateMode.h b/src/RotateMode.h new file mode 100644 index 00000000..7131b970 --- /dev/null +++ b/src/RotateMode.h @@ -0,0 +1,40 @@ +#ifndef RotateMode_h +#define RotateMode_h + +#include +#include +#include + +#define ROTATE_MODE_ID "rotate" + +using ModeFetcher = std::function; + +class RotateModeSettings { + public: + std::list modes; +}; + +class RotateMode : public AudioLightModeImpl { + private: + uint8_t _currentMode = 0; + boolean _refresh = true; + ModeFetcher _modeFetcher; + AudioLightMode* _selectedMode; + void selectMode(uint8_t modeOrdinal); + + public: + RotateMode(AsyncWebServer* server, + FS* fs, + SecurityManager* securityManager, + LedSettingsService* ledSettingsService, + PaletteSettingsService* paletteSettingsService, + FrequencySampler* frequencySampler, + ModeFetcher modeFetcher); + void tick(); + void enable(); + + void read(RotateModeSettings& settings, JsonObject& root); + StateUpdateResult update(JsonObject& root, RotateModeSettings& settings); +}; + +#endif From 1665eb978b2bec86390088d162c35d1e218353a0 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Fri, 27 Nov 2020 19:50:06 +0000 Subject: [PATCH 41/52] allow modes to self-describe if they support rotate --- interface/src/project/LightsProject.tsx | 14 ++++++-------- src/AudioLightMode.h | 3 +++ src/OffMode.cpp | 4 ++++ src/OffMode.h | 1 + src/RotateMode.cpp | 9 ++++++--- src/RotateMode.h | 1 + 6 files changed, 21 insertions(+), 11 deletions(-) diff --git a/interface/src/project/LightsProject.tsx b/interface/src/project/LightsProject.tsx index b56f77fb..3efbe2ab 100644 --- a/interface/src/project/LightsProject.tsx +++ b/interface/src/project/LightsProject.tsx @@ -26,14 +26,12 @@ class LightsProject extends Component { - - - - - - - - + + + + + + ) } diff --git a/src/AudioLightMode.h b/src/AudioLightMode.h index 644a0561..43db9cf6 100644 --- a/src/AudioLightMode.h +++ b/src/AudioLightMode.h @@ -18,6 +18,9 @@ class AudioLightMode { virtual void tick() = 0; virtual void enable() = 0; virtual void sampleComplete(){}; + virtual bool canRotate() { + return true; + }; virtual void readAsJson(JsonObject& root) = 0; virtual StateUpdateResult updateFromJson(JsonObject& root, const String& originId) = 0; }; diff --git a/src/OffMode.cpp b/src/OffMode.cpp index 0ecc4ecf..9011100d 100644 --- a/src/OffMode.cpp +++ b/src/OffMode.cpp @@ -29,3 +29,7 @@ void OffMode::tick() { }); } } + +bool OffMode::canRotate() { + return false; +}; diff --git a/src/OffMode.h b/src/OffMode.h index 84c86b76..13a7472e 100644 --- a/src/OffMode.h +++ b/src/OffMode.h @@ -28,6 +28,7 @@ class OffMode : public AudioLightModeImpl { FrequencySampler* frequencySampler); void tick(); void enable(); + bool canRotate(); }; #endif diff --git a/src/RotateMode.cpp b/src/RotateMode.cpp index a8625aa0..dc0e1ea1 100644 --- a/src/RotateMode.cpp +++ b/src/RotateMode.cpp @@ -31,9 +31,8 @@ StateUpdateResult RotateMode::update(JsonObject& root, RotateModeSettings& setti settings.modes.clear(); if (root["modes"].is()) { for (String mode : root["modes"].as()) { - // do not allow self - that would be bad! - // check the mode exists - if (mode != ROTATE_MODE_ID && _modeFetcher(mode)) { + AudioLightMode* candidateMode = _modeFetcher(mode); + if (candidateMode && candidateMode->canRotate()) { settings.modes.push_back(mode); } } @@ -86,3 +85,7 @@ void RotateMode::selectMode(uint8_t newMode) { } } } + +bool RotateMode::canRotate() { + return false; +}; diff --git a/src/RotateMode.h b/src/RotateMode.h index 7131b970..972f27d1 100644 --- a/src/RotateMode.h +++ b/src/RotateMode.h @@ -35,6 +35,7 @@ class RotateMode : public AudioLightModeImpl { void read(RotateModeSettings& settings, JsonObject& root); StateUpdateResult update(JsonObject& root, RotateModeSettings& settings); + bool canRotate(); }; #endif From 540757e7cadd10fee24637d6fadd5ee110b0a25e Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sat, 28 Nov 2020 00:03:48 +0000 Subject: [PATCH 42/52] add rotate mode transfer list --- interface/src/project/LightsProject.tsx | 1 - .../project/components/ModeTransferList.tsx | 163 ++++++++++++++++++ .../project/modes/AudioLightRotateMode.tsx | 48 +++--- interface/src/project/types.ts | 4 + src/RotateMode.cpp | 11 +- src/RotateMode.h | 8 +- 6 files changed, 207 insertions(+), 28 deletions(-) create mode 100644 interface/src/project/components/ModeTransferList.tsx diff --git a/interface/src/project/LightsProject.tsx b/interface/src/project/LightsProject.tsx index 3efbe2ab..1f3360ab 100644 --- a/interface/src/project/LightsProject.tsx +++ b/interface/src/project/LightsProject.tsx @@ -10,7 +10,6 @@ import { AuthenticatedRoute } from '../authentication'; import SpectrumAnalyzer from './SpectrumAnalyzer'; import AudioLightSettingsController from './AudioLightSettingsController'; import PaletteSettingsController from './PaletteSettingsController'; -import PaletteSettingsLoader from './PaletteSettingsLoader'; class LightsProject extends Component { diff --git a/interface/src/project/components/ModeTransferList.tsx b/interface/src/project/components/ModeTransferList.tsx new file mode 100644 index 00000000..c1f72fba --- /dev/null +++ b/interface/src/project/components/ModeTransferList.tsx @@ -0,0 +1,163 @@ +import React, { FC, useEffect } from 'react'; + +import { makeStyles } from '@material-ui/core/styles'; +import Grid from '@material-ui/core/Grid'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; +import Checkbox from '@material-ui/core/Checkbox'; +import Button from '@material-ui/core/Button'; +import Paper from '@material-ui/core/Paper'; + +import { ROTATE_AUDIO_LIGHT_MODES, AudioLightModeType, AUDIO_LIGHT_MODE_METADATA } from '../types'; + +const useStyles = makeStyles((theme) => ({ + root: { + margin: 'auto', + }, + paper: { + width: 200, + height: 230, + overflow: 'auto', + }, + button: { + margin: theme.spacing(0.5, 0), + }, +})); + +function not(a: AudioLightModeType[], b: AudioLightModeType[]) { + return a.filter((value) => b.indexOf(value) === -1); +} + +const intersection = (a: AudioLightModeType[], b: AudioLightModeType[]) => { + return a.filter((type) => b.includes(type)); +} + +interface TranserListProps { + selected: AudioLightModeType[]; + onSelectionChanged: (selected: AudioLightModeType[]) => void; +} + +const ModeTransferList: FC = ({ selected, onSelectionChanged }) => { + const classes = useStyles(); + const [checked, setChecked] = React.useState([]); + const [left, setLeft] = React.useState(not(selected, ROTATE_AUDIO_LIGHT_MODES)); + const [right, setRight] = React.useState(selected); + + const leftChecked = intersection(checked, left); + const rightChecked = intersection(checked, right); + + useEffect(() => { + setLeft(not(ROTATE_AUDIO_LIGHT_MODES, selected)); + setRight(selected); + }, [selected]); + + const handleToggle = (value: AudioLightModeType) => () => { + const currentIndex = checked.indexOf(value); + const newChecked = [...checked]; + + if (currentIndex === -1) { + newChecked.push(value); + } else { + newChecked.splice(currentIndex, 1); + } + + setChecked(newChecked); + }; + + const handleAllRight = () => { + setChecked(not(checked, leftChecked)); + onSelectionChanged(ROTATE_AUDIO_LIGHT_MODES); + }; + + const handleCheckedRight = () => { + onSelectionChanged(right.concat(leftChecked)); + setChecked(not(checked, leftChecked)); + }; + + const handleCheckedLeft = () => { + onSelectionChanged(not(right, rightChecked)); + setChecked(not(checked, rightChecked)); + }; + + const handleAllLeft = () => { + setChecked(not(checked, rightChecked)); + onSelectionChanged([]); + }; + + const customList = (items: AudioLightModeType[]) => ( + + + {items.map((value) => { + return ( + + + + + + + ); + })} + + + + ); + + return ( + + {customList(left)} + + + + + + + + + {customList(right)} + + ); +} + +export default ModeTransferList; diff --git a/interface/src/project/modes/AudioLightRotateMode.tsx b/interface/src/project/modes/AudioLightRotateMode.tsx index 218a0387..ef1d83ff 100644 --- a/interface/src/project/modes/AudioLightRotateMode.tsx +++ b/interface/src/project/modes/AudioLightRotateMode.tsx @@ -1,41 +1,41 @@ -import { Box, FormLabel, Input, MenuItem, Select } from '@material-ui/core'; +import { Box, FormLabel, Slider } from '@material-ui/core'; import React from 'react'; +import ModeTransferList from '../components/ModeTransferList'; -import { AudioLightModeType, AUDIO_LIGHT_MODE_METADATA, RotateMode } from '../types'; +import { RotateMode } from '../types'; import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; type AudioLightRotateModeProps = AudioLightModeProps; +const millisToMinutesAndSeconds = (millis: number) => { + var minutes = Math.floor(millis / 60000); + var seconds = Math.floor((millis % 60000) / 1000); + return minutes + ":" + (seconds < 10 ? '0' : '') + seconds; +} + class AudioLightRotateMode extends React.Component { changeModes = this.props.handleChange("modes"); render() { - const { data } = this.props; + const { data, handleSliderChange } = this.props; return (
- Audio Enabled - } - fullWidth - variant="outlined" - > - { - Object.entries(AudioLightModeType) - .filter(([, mode_id]) => AUDIO_LIGHT_MODE_METADATA[mode_id].rotate) - .map(([, mode_id]) => ( - - {AUDIO_LIGHT_MODE_METADATA[mode_id].label} - - )) - } - + Delay + + + +
); diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index df889798..c2dcc5ab 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -147,6 +147,7 @@ export const PRIDE_MODE_METADATA: AudioLightModeMetadata = { export interface RotateMode { mode_id: AudioLightModeType.ROTATE; modes: AudioLightModeType[]; + delay: number; } export const ROTATE_MODE_METADATA: AudioLightModeMetadata = { @@ -215,3 +216,6 @@ export const AUDIO_LIGHT_MODE_METADATA: { [type in AudioLightModeType]: AudioLig pride: PRIDE_MODE_METADATA, rotate: ROTATE_MODE_METADATA } + +export const AUDIO_LIGHT_MODES = Object.entries(AudioLightModeType).map(value => value[1]); +export const ROTATE_AUDIO_LIGHT_MODES = Object.entries(AudioLightModeType).map(value => value[1]).filter(value => AUDIO_LIGHT_MODE_METADATA[value].rotate); diff --git a/src/RotateMode.cpp b/src/RotateMode.cpp index dc0e1ea1..10ad5063 100644 --- a/src/RotateMode.cpp +++ b/src/RotateMode.cpp @@ -25,6 +25,7 @@ void RotateMode::read(RotateModeSettings& settings, JsonObject& root) { for (String mode : settings.modes) { modes.add(mode); } + root["delay"] = settings.delay; } StateUpdateResult RotateMode::update(JsonObject& root, RotateModeSettings& settings) { @@ -37,6 +38,7 @@ StateUpdateResult RotateMode::update(JsonObject& root, RotateModeSettings& setti } } } + settings.delay = root["delay"] | FACTORY_ROTATE_MODE_DELAY; return StateUpdateResult::CHANGED; } @@ -45,8 +47,12 @@ void RotateMode::enable() { } void RotateMode::tick() { + unsigned long currentMillis = millis(); + + // refresh if we are required to if (_refresh) { selectMode(0); + _modeChangedAt = millis(); _refresh = false; } @@ -55,9 +61,10 @@ void RotateMode::tick() { _selectedMode->tick(); } - // select the next mode - EVERY_N_MILLISECONDS(5000) { + // change mode if it's time + if ((unsigned long)(currentMillis - _modeChangedAt) >= _state.delay) { selectMode(_currentMode + 1); + _modeChangedAt = millis(); } } diff --git a/src/RotateMode.h b/src/RotateMode.h index 972f27d1..5b6734b7 100644 --- a/src/RotateMode.h +++ b/src/RotateMode.h @@ -5,6 +5,10 @@ #include #include +#ifndef FACTORY_ROTATE_MODE_DELAY +#define FACTORY_ROTATE_MODE_DELAY 30000 +#endif + #define ROTATE_MODE_ID "rotate" using ModeFetcher = std::function; @@ -12,12 +16,14 @@ using ModeFetcher = std::function; class RotateModeSettings { public: std::list modes; + uint32_t delay; }; class RotateMode : public AudioLightModeImpl { private: - uint8_t _currentMode = 0; boolean _refresh = true; + uint8_t _currentMode = 0; + unsigned long _modeChangedAt = 0; ModeFetcher _modeFetcher; AudioLightMode* _selectedMode; void selectMode(uint8_t modeOrdinal); From d11ede4e6766cb6bd0c2929a8cc7fa16a5aa4394 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sat, 28 Nov 2020 13:42:41 +0000 Subject: [PATCH 43/52] add power and audio settings to ui --- .../src/project/LedSettingsController.tsx | 32 +++++++++ interface/src/project/LedSettingsForm.tsx | 65 +++++++++++++++++++ interface/src/project/LightsProject.tsx | 3 + .../project/components/ModeTransferList.tsx | 10 ++- .../project/modes/AudioLightRotateMode.tsx | 1 + interface/src/project/types.ts | 7 ++ src/FrequencySampler.cpp | 8 +-- src/FrequencySampler.h | 1 + src/LedSettingsService.cpp | 7 +- src/LedSettingsService.h | 39 ++++++++--- src/LightningMode.cpp | 2 +- src/PacificaMode.cpp | 2 +- 12 files changed, 153 insertions(+), 24 deletions(-) create mode 100644 interface/src/project/LedSettingsController.tsx create mode 100644 interface/src/project/LedSettingsForm.tsx diff --git a/interface/src/project/LedSettingsController.tsx b/interface/src/project/LedSettingsController.tsx new file mode 100644 index 00000000..fd98ed71 --- /dev/null +++ b/interface/src/project/LedSettingsController.tsx @@ -0,0 +1,32 @@ +import { Container } from '@material-ui/core'; +import React, { Component } from 'react'; + +import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import LedSettingsForm from './LedSettingsForm'; + +import { LedSettings, LED_SETTINGS_ENDPOINT } from './types'; + +type LedSettingsControllerProps = RestControllerProps; + +class LedSettingsController extends Component { + + componentDidMount() { + this.props.loadData(); + } + + render() { + return ( + + + } + /> + + + ) + } + +} + +export default restController(LED_SETTINGS_ENDPOINT, LedSettingsController); diff --git a/interface/src/project/LedSettingsForm.tsx b/interface/src/project/LedSettingsForm.tsx new file mode 100644 index 00000000..882b17be --- /dev/null +++ b/interface/src/project/LedSettingsForm.tsx @@ -0,0 +1,65 @@ +import React from 'react'; + +import { Box, FormLabel, Slider, withWidth, WithWidthProps } from '@material-ui/core'; +import SaveIcon from '@material-ui/icons/Save'; +import { ValidatorForm } from 'react-material-ui-form-validator'; + +import { FormActions, FormButton, RestFormProps } from '../components'; +import { LedSettings } from './types'; + +type LedSettingsFormProps = RestFormProps & WithWidthProps; + +const milliwatsToWatts = (milliwatts: number) => milliwatts / 1000; + +class LedSettingsForm extends React.Component { + + handleSliderChange = (name: keyof LedSettings) => (event: React.ChangeEvent<{}>, value: number | number[]) => { + const { setData } = this.props; + setData({ ...this.props.data!, [name]: value }); + } + + render() { + const { data, saveData } = this.props; + return ( + + + LED Max Power in Watts (0 = unlimited) + + Audio Dead Zone + + Audio Smoothing Factor + + + + } variant="contained" color="primary" type="submit"> + Save + + + + ); + } + +} + +export default withWidth()(LedSettingsForm); + diff --git a/interface/src/project/LightsProject.tsx b/interface/src/project/LightsProject.tsx index 1f3360ab..83d369f4 100644 --- a/interface/src/project/LightsProject.tsx +++ b/interface/src/project/LightsProject.tsx @@ -10,6 +10,7 @@ import { AuthenticatedRoute } from '../authentication'; import SpectrumAnalyzer from './SpectrumAnalyzer'; import AudioLightSettingsController from './AudioLightSettingsController'; import PaletteSettingsController from './PaletteSettingsController'; +import LedSettingsController from './LedSettingsController'; class LightsProject extends Component { @@ -23,11 +24,13 @@ class LightsProject extends Component { + + diff --git a/interface/src/project/components/ModeTransferList.tsx b/interface/src/project/components/ModeTransferList.tsx index c1f72fba..71b242ba 100644 --- a/interface/src/project/components/ModeTransferList.tsx +++ b/interface/src/project/components/ModeTransferList.tsx @@ -13,17 +13,15 @@ import Paper from '@material-ui/core/Paper'; import { ROTATE_AUDIO_LIGHT_MODES, AudioLightModeType, AUDIO_LIGHT_MODE_METADATA } from '../types'; const useStyles = makeStyles((theme) => ({ - root: { - margin: 'auto', - }, paper: { - width: 200, + width: 180, height: 230, overflow: 'auto', }, button: { margin: theme.spacing(0.5, 0), - }, + minWidth: 35 + } })); function not(a: AudioLightModeType[], b: AudioLightModeType[]) { @@ -109,7 +107,7 @@ const ModeTransferList: FC = ({ selected, onSelectionChanged } ); return ( - + {customList(left)} diff --git a/interface/src/project/modes/AudioLightRotateMode.tsx b/interface/src/project/modes/AudioLightRotateMode.tsx index ef1d83ff..f2e9803c 100644 --- a/interface/src/project/modes/AudioLightRotateMode.tsx +++ b/interface/src/project/modes/AudioLightRotateMode.tsx @@ -35,6 +35,7 @@ class AudioLightRotateMode extends React.Component { />
+ Modes
diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index c2dcc5ab..f78c1082 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -160,6 +160,12 @@ export interface PaletteSettings { palettes: Palette[]; } +export interface LedSettings { + max_power_milliwatts: number; + dead_zone: number; + smoothing_factor: number; +} + export interface Palette { id: string; colors: string[]; @@ -177,6 +183,7 @@ export type AudioLightMode = ( RotateMode ); +export const LED_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "ledSettings"; export const PALETTE_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "paletteSettings"; export const AUDIO_LIGHT_SAVE_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "saveModeSettings"; export const AUDIO_LIGHT_LOAD_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "loadModeSettings"; diff --git a/src/FrequencySampler.cpp b/src/FrequencySampler.cpp index ba294697..3000edfb 100644 --- a/src/FrequencySampler.cpp +++ b/src/FrequencySampler.cpp @@ -15,6 +15,7 @@ void FrequencySampler::loop() { // take samples 100 times a second (max) EVERY_N_MILLIS(10) { float smoothingFactor = _settings->getSmoothingFactor(); + uint16_t deadZone = _settings->getDeadZone(); update( [&](FrequencyData& frequencyData) { // Reset MSGEQ7 IC @@ -34,12 +35,7 @@ void FrequencySampler::loop() { uint16_t value = analogRead(FREQUENCY_SAMPLER_ANALOG_PIN); // re-map frequency to eliminate low level noise - value = value > FREQUENCY_SAMPLER_DEAD_ZONE ? map(value - FREQUENCY_SAMPLER_DEAD_ZONE, - 0, - ADC_MAX_VALUE - FREQUENCY_SAMPLER_DEAD_ZONE, - 0, - ADC_MAX_VALUE) - : 0; + value = value > deadZone ? map(value - deadZone, 0, ADC_MAX_VALUE - deadZone, 0, ADC_MAX_VALUE) : 0; // crappy smoothing to avoid crazy flickering frequencyData.bands[i] = smoothingFactor * frequencyData.bands[i] + (1 - smoothingFactor) * value; diff --git a/src/FrequencySampler.h b/src/FrequencySampler.h index 420801d7..f84a6e3f 100644 --- a/src/FrequencySampler.h +++ b/src/FrequencySampler.h @@ -16,6 +16,7 @@ class FrequencySamplerSettings { public: virtual float getSmoothingFactor() = 0; + virtual uint16_t getDeadZone() = 0; }; class FrequencyData { diff --git a/src/LedSettingsService.cpp b/src/LedSettingsService.cpp index e06a1358..3ceecb02 100644 --- a/src/LedSettingsService.cpp +++ b/src/LedSettingsService.cpp @@ -22,9 +22,14 @@ void LedSettingsService::update(LedUpdateCallback updateCallback) { } void LedSettingsService::configureLeds() { - FastLED.setBrightness(_state.brightness); + // zero means "unlimited" + FastLED.setMaxPowerInMilliWatts(_state.maxPowerMilliwatts == 0 ? 0xFFFFFFFF : _state.maxPowerMilliwatts); } float LedSettingsService::getSmoothingFactor() { return _state.smoothingFactor; } + +uint16_t LedSettingsService::getDeadZone() { + return _state.deadZone; +} diff --git a/src/LedSettingsService.h b/src/LedSettingsService.h index 100a807f..7e0ea2f1 100644 --- a/src/LedSettingsService.h +++ b/src/LedSettingsService.h @@ -22,32 +22,51 @@ #define NUM_LEDS 9 */ -#ifndef FACTORY_LED_BRIGHTNESS -#define FACTORY_LED_BRIGHTNESS 128 +#ifndef FACTORY_LED_MAX_POWER_MILLIWATTS +#define FACTORY_LED_MAX_POWER_MILLIWATTS 0 #endif #ifndef FACTORY_LED_SMOOTHING_FACTOR #define FACTORY_LED_SMOOTHING_FACTOR 0.15 #endif +#ifndef FACTORY_LED_DEAD_ZONE +#define FACTORY_LED_DEAD_ZONE 700 +#endif + typedef std::function LedUpdateCallback; -// should be extended to allow number of LEDS to be modified -// should be extended to allow leds to be switched off -// could be extended to allow configuration of PIN (less likely) -// could be extended to allow configuring the strip type (less likely) +/** + * Should be extended to support: + * + * - multiple led strips of differnt types + * - configurable data pins + * - configuration of number of leds per strip + * + * This is slightly tricky in the current state of the code as some effects rely on NUM_LEDS constant to set up data + * arrays. + * + * - Effects could be made to dynamically generate their arrays and alter them when required + * - Effects could be destroyed and re-configured via a factory pattern which may allow for greater flexibility. + * + * The latter approach would mean effects must be destructor friendly but it would be much more memory efficent that way + * too as only the "active" effect need be memory resident. + */ class LedSettings { public: - uint8_t brightness; + uint32_t maxPowerMilliwatts; + uint16_t deadZone; float smoothingFactor; static void read(LedSettings& settings, JsonObject& root) { - root["brightness"] = settings.brightness; + root["max_power_milliwatts"] = settings.maxPowerMilliwatts; + root["dead_zone"] = settings.deadZone; root["smoothing_factor"] = settings.smoothingFactor; } static StateUpdateResult update(JsonObject& root, LedSettings& settings) { - settings.brightness = root["brightness"] | FACTORY_LED_BRIGHTNESS; + settings.maxPowerMilliwatts = root["max_power_milliwatts"] | FACTORY_LED_MAX_POWER_MILLIWATTS; + settings.deadZone = root["dead_zone"] | FACTORY_LED_DEAD_ZONE; settings.smoothingFactor = root["smoothing_factor"] | FACTORY_LED_SMOOTHING_FACTOR; return StateUpdateResult::CHANGED; } @@ -58,6 +77,8 @@ class LedSettingsService : public StatefulService, public Frequency LedSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager); float getSmoothingFactor(); + uint16_t getDeadZone(); + void begin(); void update(LedUpdateCallback updateCallback); diff --git a/src/LightningMode.cpp b/src/LightningMode.cpp index 8d7cd6b4..8eb9a9a2 100644 --- a/src/LightningMode.cpp +++ b/src/LightningMode.cpp @@ -30,7 +30,7 @@ void LightningMode::tick() { if (_status == Status::TRIGGERED) { _ledstart = random8(numLeds); // Determine starting location of flash - _ledlen = random8(numLeds - _ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) + _ledlen = random8(numLeds - _ledstart); // Determine length of flash (not to go beyond numLeds-1) _numFlashes = random8(3, _state.flashes); // Calculate the random number of flashes we will show _flashCounter = 0; // The number of flashes we have shown _status = Status::RUNNING; diff --git a/src/PacificaMode.cpp b/src/PacificaMode.cpp index 66f554cb..7158115b 100644 --- a/src/PacificaMode.cpp +++ b/src/PacificaMode.cpp @@ -48,7 +48,7 @@ void PacificaMode::tick() { sCIStart4 -= (deltams2 * beatsin88(257, 4, 6)); // Clear out the LED array to a dim background blue-green - fill_solid(leds, NUM_LEDS, CRGB(2, 6, 10)); + fill_solid(leds, numLeds, CRGB(2, 6, 10)); // Render each of four layers, with different scales and speeds, that vary over time pacifica_one_layer(leds, From 58b120b0fb339369f80c50248f9570fc3d0b4a2e Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sat, 28 Nov 2020 20:29:12 +0000 Subject: [PATCH 44/52] always use FastLED.show() to ensure correct scaling --- src/ColorMode.cpp | 7 +++---- src/ConfettiMode.cpp | 8 ++++---- src/FireMode.cpp | 10 ++++++---- src/LedSettingsService.cpp | 3 +-- src/LedSettingsService.h | 13 +++++-------- src/LightningMode.cpp | 8 ++++---- src/OffMode.cpp | 4 ++-- src/PacificaMode.cpp | 4 ++-- src/PrideMode.cpp | 6 +++--- src/RainbowMode.cpp | 6 ++++-- src/RotateMode.cpp | 4 ++-- 11 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/ColorMode.cpp b/src/ColorMode.cpp index b38a32cb..13a57493 100644 --- a/src/ColorMode.cpp +++ b/src/ColorMode.cpp @@ -24,12 +24,11 @@ void ColorMode::enable() { void ColorMode::tick() { if (_refresh || _state.audioEnabled) { - _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { FrequencyData* frequencyData = _frequencySampler->getFrequencyData(); fill_solid(leds, numLeds, _state.color); - ledController->showLeds(_state.audioEnabled - ? frequencyData->calculateEnergyFloat(_state.includedBands) * _state.brightness - : _state.brightness); + FastLED.show(_state.audioEnabled ? frequencyData->calculateEnergyFloat(_state.includedBands) * _state.brightness + : _state.brightness); }); _refresh = false; } diff --git a/src/ConfettiMode.cpp b/src/ConfettiMode.cpp index 5eb9ac60..fd7afbd5 100644 --- a/src/ConfettiMode.cpp +++ b/src/ConfettiMode.cpp @@ -30,9 +30,9 @@ void ConfettiMode::enable() { void ConfettiMode::tick() { if (_refresh) { - _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { fill_solid(leds, numLeds, CHSV(255, 0, 0)); - ledController->showLeds(); + FastLED.show(); }); _refresh = false; } @@ -73,13 +73,13 @@ void ConfettiMode::tick() { } EVERY_N_MILLIS_I(confettiTimer, FACTORY_CONFETTI_MODE_DELAY) { - _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { fadeToBlackBy(leds, numLeds, _fade); int pos = random16(numLeds); leds[pos] = ColorFromPalette(_currentPalette, _hue + random16(_hueDelta) / 4, _state.brightness, _currentBlending); _hue = _hue + _inc; - ledController->showLeds(); + FastLED.show(); confettiTimer.setPeriod(_state.delay); }); } diff --git a/src/FireMode.cpp b/src/FireMode.cpp index 553e7d27..83412613 100644 --- a/src/FireMode.cpp +++ b/src/FireMode.cpp @@ -26,16 +26,16 @@ void FireMode::enable() { void FireMode::tick() { if (_refresh) { - _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { fill_solid(leds, numLeds, CHSV(255, 0, 0)); // clear leds - ledController->showLeds(); // render all leds black + FastLED.show(); // render all leds black memset(_heatMap, 0, sizeof(uint8_t) * numLeds); // clear heat map _refresh = false; // clear refresh flag }); } // make firey stuff at ~100FPS EVERY_N_MILLIS(10) { - _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { // Step 1. Cool down every cell a little for (int i = 0; i < numLeds; i++) { _heatMap[i] = qsub8(_heatMap[i], random8(0, ((_state.cooling * 10) / numLeds) + 2)); @@ -66,7 +66,9 @@ void FireMode::tick() { } leds[pixelnumber] = color; } - ledController->showLeds(); + + // Step 5. Render the update + FastLED.show(); }); } } diff --git a/src/LedSettingsService.cpp b/src/LedSettingsService.cpp index 3ceecb02..d40673d4 100644 --- a/src/LedSettingsService.cpp +++ b/src/LedSettingsService.cpp @@ -18,11 +18,10 @@ void LedSettingsService::begin() { } void LedSettingsService::update(LedUpdateCallback updateCallback) { - updateCallback(_leds, _ledController, NUM_LEDS); + updateCallback(_leds, NUM_LEDS); } void LedSettingsService::configureLeds() { - // zero means "unlimited" FastLED.setMaxPowerInMilliWatts(_state.maxPowerMilliwatts == 0 ? 0xFFFFFFFF : _state.maxPowerMilliwatts); } diff --git a/src/LedSettingsService.h b/src/LedSettingsService.h index 7e0ea2f1..8e6b6a4e 100644 --- a/src/LedSettingsService.h +++ b/src/LedSettingsService.h @@ -34,23 +34,20 @@ #define FACTORY_LED_DEAD_ZONE 700 #endif -typedef std::function LedUpdateCallback; +typedef std::function LedUpdateCallback; /** - * Should be extended to support: - * - * - multiple led strips of differnt types - * - configurable data pins - * - configuration of number of leds per strip + * May be extended to support configuration of number of leds per strip. * * This is slightly tricky in the current state of the code as some effects rely on NUM_LEDS constant to set up data * arrays. * - * - Effects could be made to dynamically generate their arrays and alter them when required + * - Effects could be made to dynamically generate their data arrays and alter them when required * - Effects could be destroyed and re-configured via a factory pattern which may allow for greater flexibility. + * - The callback approach could be abandoned in favor of direct access (static variable or singleton) * * The latter approach would mean effects must be destructor friendly but it would be much more memory efficent that way - * too as only the "active" effect need be memory resident. + * too because only the "active" effect needs be memory resident. */ class LedSettings { public: diff --git a/src/LightningMode.cpp b/src/LightningMode.cpp index 8eb9a9a2..80d4867f 100644 --- a/src/LightningMode.cpp +++ b/src/LightningMode.cpp @@ -19,11 +19,11 @@ LightningMode::LightningMode(AsyncWebServer* server, }; void LightningMode::tick() { - _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { if (_refresh) { _status = Status::IDLE; // assert idle mode fill_solid(leds, numLeds, CHSV(255, 0, 0)); // clear leds - ledController->showLeds(); // render all leds black + FastLED.show(); // render all leds black _refresh = false; // clear refresh flag return; // eager return } @@ -39,10 +39,10 @@ void LightningMode::tick() { if (_status == Status::RUNNING && isWaitTimeElapsed()) { _dimmer = (_flashCounter == 0) ? 5 : random8(1, 3); // leader scaled by a 5, return strokes brighter fill_solid(leds + _ledstart, _ledlen, _state.color); // draw the flash - ledController->showLeds(_state.brightness / _dimmer); // show the flash + FastLED.show(_state.brightness / _dimmer); // show the flash delay(random8(4, 10)); // wait a small amount of time (use non blocking delay?) fill_solid(leds + _ledstart, _ledlen, CHSV(255, 0, 0)); // hide flash - ledController->showLeds(); // draw hidden leds + FastLED.show(); // draw hidden leds resetWaitTime(); // reset wait time for next flash // decrement flash counter, and reset to idle if done diff --git a/src/OffMode.cpp b/src/OffMode.cpp index 9011100d..6a72b5cc 100644 --- a/src/OffMode.cpp +++ b/src/OffMode.cpp @@ -22,9 +22,9 @@ void OffMode::enable() { void OffMode::tick() { if (_refresh) { - _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { fill_solid(leds, numLeds, CHSV(255, 0, 0)); // clear leds - ledController->showLeds(); // render all leds black + FastLED.show(); // render all leds black _refresh = false; // clear refresh flag }); } diff --git a/src/PacificaMode.cpp b/src/PacificaMode.cpp index 7158115b..a14a1ca1 100644 --- a/src/PacificaMode.cpp +++ b/src/PacificaMode.cpp @@ -29,7 +29,7 @@ void PacificaMode::enable() { } void PacificaMode::tick() { - _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { // Increment the four "color index start" counters, one for each wave layer. // Each is incremented at a different speed, and the speeds vary over time. static uint16_t sCIStart1, sCIStart2, sCIStart3, sCIStart4; @@ -75,7 +75,7 @@ void PacificaMode::tick() { pacifica_deepen_colors(leds, numLeds); // Show the leds - ledController->showLeds(); + FastLED.show(); }); } diff --git a/src/PrideMode.cpp b/src/PrideMode.cpp index 8e72fed2..bdaee372 100644 --- a/src/PrideMode.cpp +++ b/src/PrideMode.cpp @@ -23,7 +23,7 @@ void PrideMode::enable() { } void PrideMode::tick() { - _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { static uint16_t sPseudotime = 0; static uint16_t sLastMillis = 0; static uint16_t sHue16 = 0; @@ -31,7 +31,7 @@ void PrideMode::tick() { uint8_t sat8 = beatsin88(87, 220, 250); uint8_t brightdepth = beatsin88(341, 96, 224); uint16_t brightnessthetainc16 = beatsin88(203, (25 * 256), (40 * 256)); - uint8_t msmultiplier = beatsin88( _state.brightnessBpm, _state.brightnessFreqMin, _state.brightnessFreqMax); + uint8_t msmultiplier = beatsin88(_state.brightnessBpm, _state.brightnessFreqMin, _state.brightnessFreqMax); uint16_t hue16 = sHue16; // gHue * 256; uint16_t hueinc16 = beatsin88(_state.hueBpm, _state.hueDeltaMin, _state.hueDeltaMax); @@ -63,6 +63,6 @@ void PrideMode::tick() { } // Show the leds - ledController->showLeds(); + FastLED.show(); }); } diff --git a/src/RainbowMode.cpp b/src/RainbowMode.cpp index 8638f912..a2a8ffed 100644 --- a/src/RainbowMode.cpp +++ b/src/RainbowMode.cpp @@ -24,7 +24,7 @@ void RainbowMode::enable() { } void RainbowMode::tick() { - _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { FrequencyData* frequencyData = _frequencySampler->getFrequencyData(); // rotate hue in time based manner @@ -51,7 +51,9 @@ void RainbowMode::tick() { } } + + // update the leds - ledController->showLeds(_state.brightness); + FastLED.show(_state.brightness); }); } diff --git a/src/RotateMode.cpp b/src/RotateMode.cpp index 10ad5063..58d594c9 100644 --- a/src/RotateMode.cpp +++ b/src/RotateMode.cpp @@ -85,9 +85,9 @@ void RotateMode::selectMode(uint8_t newMode) { _selectedMode->enable(); } else { // no mode selected, clear LEDs - _ledSettingsService->update([&](CRGB* leds, CLEDController* ledController, const uint16_t numLeds) { + _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { fill_solid(leds, numLeds, CHSV(255, 0, 0)); // clear leds - ledController->showLeds(); // render all leds black + FastLED.show(); // render all leds black }); } } From 5bdf1e7ea3dc751dead93f7e36be75bf5c56c4c2 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sat, 28 Nov 2020 23:14:01 +0000 Subject: [PATCH 45/52] make some simplifications --- src/AudioLightMode.h | 7 +-- src/AudioLightSettingsService.cpp | 39 ++++++++------ src/AudioLightSettingsService.h | 14 +++-- src/ColorMode.cpp | 14 ++--- src/ColorMode.h | 3 +- src/ConfettiMode.cpp | 28 ++++------ src/ConfettiMode.h | 4 +- src/FastLEDSettings.h | 20 +++++++ src/FireMode.cpp | 72 ++++++++++++------------- src/FireMode.h | 3 +- src/FrequencySampler.cpp | 17 ++++-- src/FrequencySampler.h | 15 +++--- src/LedSettingsService.cpp | 18 ------- src/LedSettingsService.h | 38 +------------ src/LightningMode.cpp | 58 ++++++++++---------- src/LightningMode.h | 3 +- src/OffMode.cpp | 12 ++--- src/OffMode.h | 3 +- src/PacificaMode.cpp | 88 +++++++++++++++---------------- src/PacificaMode.h | 3 +- src/PaletteSettingsService.h | 8 +-- src/PrideMode.cpp | 7 +-- src/PrideMode.h | 3 +- src/RainbowMode.cpp | 58 +++++++++----------- src/RainbowMode.h | 3 +- src/RotateMode.cpp | 18 +++---- src/RotateMode.h | 11 ++-- src/main.cpp | 10 ++-- 28 files changed, 252 insertions(+), 325 deletions(-) create mode 100644 src/FastLEDSettings.h diff --git a/src/AudioLightMode.h b/src/AudioLightMode.h index 43db9cf6..3507d5c3 100644 --- a/src/AudioLightMode.h +++ b/src/AudioLightMode.h @@ -1,7 +1,7 @@ #ifndef AudioLightMode_h #define AudioLightMode_h -#include +#include #include #include @@ -15,7 +15,7 @@ class AudioLightMode { virtual void begin() = 0; virtual void readFromFS() = 0; virtual void writeToFS() = 0; - virtual void tick() = 0; + virtual void tick(CRGB* leds, const uint16_t numLeds) = 0; virtual void enable() = 0; virtual void sampleComplete(){}; virtual bool canRotate() { @@ -33,7 +33,6 @@ class AudioLightModeImpl : public StatefulService, public AudioLightMode { String _servicePath; JsonStateReader _stateReader; JsonStateUpdater _stateUpdater; - LedSettingsService* _ledSettingsService; PaletteSettingsService* _paletteSettingsService; FrequencySampler* _frequencySampler; HttpEndpoint _httpEndpoint; @@ -43,7 +42,6 @@ class AudioLightModeImpl : public StatefulService, public AudioLightMode { AudioLightModeImpl(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler, JsonStateReader stateReader, @@ -54,7 +52,6 @@ class AudioLightModeImpl : public StatefulService, public AudioLightMode { _servicePath(AUDIO_LIGHT_MODE_FILE_PATH_PREFIX + id + AUDIO_LIGHT_MODE_FILE_PATH_SUFFIX), _stateReader(stateReader), _stateUpdater(stateUpdater), - _ledSettingsService(ledSettingsService), _paletteSettingsService(paletteSettingsService), _frequencySampler(frequencySampler), _httpEndpoint(stateReader, stateUpdater, this, server, _filePath, securityManager), diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index 321760b9..8e0a1de3 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -20,7 +20,8 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, server, AUDIO_LIGHT_WS_PATH, securityManager, - AuthenticationPredicates::IS_AUTHENTICATED) { + AuthenticationPredicates::IS_AUTHENTICATED), + _ledSettingsService(ledSettingsService) { server->on( AUDIO_LIGHT_SAVE_MODE_PATH, HTTP_POST, @@ -33,24 +34,20 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, AuthenticationPredicates::IS_AUTHENTICATED)); addUpdateHandler([&](const String& originId) { enableMode(); }, false); frequencySampler->addUpdateHandler([&](const String& originId) { handleSample(); }, false); - ledSettingsService->addUpdateHandler([&](const String& originId) { enableMode(); }, false); + ledSettingsService->addUpdateHandler([&](const String& originId) { updateSettings(); }, false); paletteSettingsService->addUpdateHandler([&](const String& originId) { enableMode(); }, false); - _modes[0] = new ColorMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); - _modes[1] = - new RainbowMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); - _modes[2] = - new LightningMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); - _modes[3] = - new ConfettiMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); - _modes[4] = new FireMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); - _modes[5] = new OffMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); - _modes[6] = - new PacificaMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); - _modes[7] = new PrideMode(server, fs, securityManager, ledSettingsService, paletteSettingsService, frequencySampler); + _ledController = &FastLED.addLeds(_leds, NUM_LEDS); + _modes[0] = new OffMode(server, fs, securityManager, paletteSettingsService, frequencySampler); + _modes[1] = new ColorMode(server, fs, securityManager, paletteSettingsService, frequencySampler); + _modes[2] = new RainbowMode(server, fs, securityManager, paletteSettingsService, frequencySampler); + _modes[3] = new LightningMode(server, fs, securityManager, paletteSettingsService, frequencySampler); + _modes[4] = new ConfettiMode(server, fs, securityManager, paletteSettingsService, frequencySampler); + _modes[5] = new FireMode(server, fs, securityManager, paletteSettingsService, frequencySampler); + _modes[6] = new PacificaMode(server, fs, securityManager, paletteSettingsService, frequencySampler); + _modes[7] = new PrideMode(server, fs, securityManager, paletteSettingsService, frequencySampler); _modes[8] = new RotateMode(server, fs, securityManager, - ledSettingsService, paletteSettingsService, frequencySampler, std::bind(&AudioLightSettingsService::getMode, this, std::placeholders::_1)); @@ -64,10 +61,20 @@ void AudioLightSettingsService::begin() { for (uint8_t i = 0; i < NUM_MODES; i++) { _modes[i]->begin(); } + + // update the settings + updateSettings(); +} + +void AudioLightSettingsService::updateSettings() { + _ledSettingsService->read([&](LedSettings& ledSettings) { + FastLED.setMaxPowerInMilliWatts(ledSettings.maxPowerMilliwatts == 0 ? 0xFFFFFFFF : ledSettings.maxPowerMilliwatts); + }); + enableMode(); } void AudioLightSettingsService::loop() { - _state.currentMode->tick(); + _state.currentMode->tick(_leds, NUM_LEDS); } AudioLightMode* AudioLightSettingsService::getMode(const String& modeId) { diff --git a/src/AudioLightSettingsService.h b/src/AudioLightSettingsService.h index 8baed3af..3a6a6254 100644 --- a/src/AudioLightSettingsService.h +++ b/src/AudioLightSettingsService.h @@ -1,9 +1,13 @@ #ifndef AudioLightSettingsService_h #define AudioLightSettingsService_h +#include +#include +#include +#include +#include + #include -#include -#include #include #include #include @@ -12,7 +16,6 @@ #include #include #include -#include #define NUM_MODES 9 @@ -41,16 +44,19 @@ class AudioLightSettingsService : public StatefulService { void begin(); void loop(); - private: HttpEndpoint _httpEndpoint; WebSocketTxRx _audioLightModeTxRx; + LedSettingsService* _ledSettingsService; + CRGB _leds[NUM_LEDS]; + CLEDController* _ledController; AudioLightMode* _modes[NUM_MODES]; void read(AudioLightSettings& settings, JsonObject& root); StateUpdateResult update(JsonObject& root, AudioLightSettings& settings); AudioLightMode* getMode(const String& modeId); + void updateSettings(); void enableMode(); void handleSample(); void saveModeConfig(AsyncWebServerRequest* request); diff --git a/src/ColorMode.cpp b/src/ColorMode.cpp index 13a57493..0f6244e9 100644 --- a/src/ColorMode.cpp +++ b/src/ColorMode.cpp @@ -3,13 +3,11 @@ ColorMode::ColorMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, fs, securityManager, - ledSettingsService, paletteSettingsService, frequencySampler, ColorModeSettings::read, @@ -22,14 +20,12 @@ void ColorMode::enable() { _refresh = true; } -void ColorMode::tick() { +void ColorMode::tick(CRGB* leds, const uint16_t numLeds) { if (_refresh || _state.audioEnabled) { - _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { - FrequencyData* frequencyData = _frequencySampler->getFrequencyData(); - fill_solid(leds, numLeds, _state.color); - FastLED.show(_state.audioEnabled ? frequencyData->calculateEnergyFloat(_state.includedBands) * _state.brightness - : _state.brightness); - }); + FrequencyData* frequencyData = _frequencySampler->getFrequencyData(); + fill_solid(leds, numLeds, _state.color); + FastLED.show(_state.audioEnabled ? frequencyData->calculateEnergyFloat(_state.includedBands) * _state.brightness + : _state.brightness); _refresh = false; } } diff --git a/src/ColorMode.h b/src/ColorMode.h index 0e74d4c3..3f381076 100644 --- a/src/ColorMode.h +++ b/src/ColorMode.h @@ -50,10 +50,9 @@ class ColorMode : public AudioLightModeImpl { ColorMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler); - void tick(); + void tick(CRGB* leds, const uint16_t numLeds); void enable(); }; diff --git a/src/ConfettiMode.cpp b/src/ConfettiMode.cpp index fd7afbd5..257c51ef 100644 --- a/src/ConfettiMode.cpp +++ b/src/ConfettiMode.cpp @@ -3,13 +3,11 @@ ConfettiMode::ConfettiMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, fs, securityManager, - ledSettingsService, paletteSettingsService, frequencySampler, std::bind(&ConfettiMode::read, this, std::placeholders::_1, std::placeholders::_2), @@ -28,12 +26,10 @@ void ConfettiMode::enable() { }); } -void ConfettiMode::tick() { +void ConfettiMode::tick(CRGB* leds, const uint16_t numLeds) { if (_refresh) { - _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { - fill_solid(leds, numLeds, CHSV(255, 0, 0)); - FastLED.show(); - }); + fill_solid(leds, numLeds, CHSV(255, 0, 0)); + FastLED.show(); _refresh = false; } @@ -73,17 +69,11 @@ void ConfettiMode::tick() { } EVERY_N_MILLIS_I(confettiTimer, FACTORY_CONFETTI_MODE_DELAY) { - _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { - fadeToBlackBy(leds, numLeds, _fade); - int pos = random16(numLeds); - leds[pos] = - ColorFromPalette(_currentPalette, _hue + random16(_hueDelta) / 4, _state.brightness, _currentBlending); - _hue = _hue + _inc; - FastLED.show(); - confettiTimer.setPeriod(_state.delay); - }); + fadeToBlackBy(leds, numLeds, _fade); + int pos = random16(numLeds); + leds[pos] = ColorFromPalette(_currentPalette, _hue + random16(_hueDelta) / 4, _state.brightness, _currentBlending); + _hue = _hue + _inc; + FastLED.show(); + confettiTimer.setPeriod(_state.delay); } } - -void ConfettiMode::sampleComplete() { -} diff --git a/src/ConfettiMode.h b/src/ConfettiMode.h index 015d1051..85ebb57b 100644 --- a/src/ConfettiMode.h +++ b/src/ConfettiMode.h @@ -81,12 +81,10 @@ class ConfettiMode : public AudioLightModeImpl { ConfettiMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler); - void tick(); + void tick(CRGB* leds, const uint16_t numLeds); void enable(); - void sampleComplete(); }; #endif diff --git a/src/FastLEDSettings.h b/src/FastLEDSettings.h new file mode 100644 index 00000000..5ea17d75 --- /dev/null +++ b/src/FastLEDSettings.h @@ -0,0 +1,20 @@ +#ifndef FastLEDSettings_h +#define FastLEDSettings_h + +#ifndef LED_DATA_PIN +#define LED_DATA_PIN 21 +#endif + +#ifndef COLOR_ORDER +#define COLOR_ORDER RGB +#endif + +#ifndef LED_TYPE +#define LED_TYPE WS2811 +#endif + +#ifndef NUM_LEDS +#define NUM_LEDS 50 +#endif + +#endif \ No newline at end of file diff --git a/src/FireMode.cpp b/src/FireMode.cpp index 83412613..140d2bcd 100644 --- a/src/FireMode.cpp +++ b/src/FireMode.cpp @@ -3,13 +3,11 @@ FireMode::FireMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, fs, securityManager, - ledSettingsService, paletteSettingsService, frequencySampler, std::bind(&FireMode::read, this, std::placeholders::_1, std::placeholders::_2), @@ -24,51 +22,47 @@ void FireMode::enable() { }); } -void FireMode::tick() { +void FireMode::tick(CRGB* leds, const uint16_t numLeds) { if (_refresh) { - _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { - fill_solid(leds, numLeds, CHSV(255, 0, 0)); // clear leds - FastLED.show(); // render all leds black - memset(_heatMap, 0, sizeof(uint8_t) * numLeds); // clear heat map - _refresh = false; // clear refresh flag - }); + fill_solid(leds, numLeds, CHSV(255, 0, 0)); // clear leds + FastLED.show(); // render all leds black + memset(_heatMap, 0, sizeof(uint8_t) * numLeds); // clear heat map + _refresh = false; // clear refresh flag } // make firey stuff at ~100FPS EVERY_N_MILLIS(10) { - _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { - // Step 1. Cool down every cell a little - for (int i = 0; i < numLeds; i++) { - _heatMap[i] = qsub8(_heatMap[i], random8(0, ((_state.cooling * 10) / numLeds) + 2)); - } + // Step 1. Cool down every cell a little + for (int i = 0; i < numLeds; i++) { + _heatMap[i] = qsub8(_heatMap[i], random8(0, ((_state.cooling * 10) / numLeds) + 2)); + } - // Step 2. Heat from each cell drifts 'up' and diffuses a little - for (int k = numLeds - 1; k >= 2; k--) { - _heatMap[k] = (_heatMap[k - 1] + _heatMap[k - 2] + _heatMap[k - 2]) / 3; - } + // Step 2. Heat from each cell drifts 'up' and diffuses a little + for (int k = numLeds - 1; k >= 2; k--) { + _heatMap[k] = (_heatMap[k - 1] + _heatMap[k - 2] + _heatMap[k - 2]) / 3; + } - // Step 3. Randomly ignite new 'sparks' of heat near the bottom - if (random8() < _state.sparking) { - int y = random8(7); - _heatMap[y] = qadd8(_heatMap[y], random8(160, 255)); - } + // Step 3. Randomly ignite new 'sparks' of heat near the bottom + if (random8() < _state.sparking) { + int y = random8(7); + _heatMap[y] = qadd8(_heatMap[y], random8(160, 255)); + } - // Step 4. Map from heat cells to LED colors - for (int j = 0; j < numLeds; j++) { - // Scale the heat value from 0-255 down to 0-240 - // for best results with color palettes. - byte colorindex = scale8(_heatMap[j], 240); - CRGB color = ColorFromPalette(_state.palette.colors, colorindex); - int pixelnumber; - if (_state.reverse) { - pixelnumber = (numLeds - 1) - j; - } else { - pixelnumber = j; - } - leds[pixelnumber] = color; + // Step 4. Map from heat cells to LED colors + for (int j = 0; j < numLeds; j++) { + // Scale the heat value from 0-255 down to 0-240 + // for best results with color palettes. + byte colorindex = scale8(_heatMap[j], 240); + CRGB color = ColorFromPalette(_state.palette.colors, colorindex); + int pixelnumber; + if (_state.reverse) { + pixelnumber = (numLeds - 1) - j; + } else { + pixelnumber = j; } + leds[pixelnumber] = color; + } - // Step 5. Render the update - FastLED.show(); - }); + // Step 5. Render the update + FastLED.show(); } } diff --git a/src/FireMode.h b/src/FireMode.h index 659185a3..7e98083f 100644 --- a/src/FireMode.h +++ b/src/FireMode.h @@ -61,10 +61,9 @@ class FireMode : public AudioLightModeImpl { FireMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler); - void tick(); + void tick(CRGB* leds, const uint16_t numLeds); void enable(); void refreshPalettes(const String& originId); }; diff --git a/src/FrequencySampler.cpp b/src/FrequencySampler.cpp index 3000edfb..1df5b399 100644 --- a/src/FrequencySampler.cpp +++ b/src/FrequencySampler.cpp @@ -1,6 +1,7 @@ #include -FrequencySampler::FrequencySampler(FrequencySamplerSettings* settings) : _settings(settings) { +FrequencySampler::FrequencySampler(LedSettingsService* ledSettingsService) : _ledSettingsService(ledSettingsService) { + ledSettingsService->addUpdateHandler([&](const String& originId) { updateSettings(); }, false); } void FrequencySampler::begin() { @@ -9,13 +10,12 @@ void FrequencySampler::begin() { pinMode(FREQUENCY_SAMPLER_ANALOG_PIN, INPUT); digitalWrite(FREQUENCY_SAMPLER_RESET_PIN, LOW); digitalWrite(FREQUENCY_SAMPLER_STROBE_PIN, HIGH); + updateSettings(); } void FrequencySampler::loop() { // take samples 100 times a second (max) EVERY_N_MILLIS(10) { - float smoothingFactor = _settings->getSmoothingFactor(); - uint16_t deadZone = _settings->getDeadZone(); update( [&](FrequencyData& frequencyData) { // Reset MSGEQ7 IC @@ -35,10 +35,10 @@ void FrequencySampler::loop() { uint16_t value = analogRead(FREQUENCY_SAMPLER_ANALOG_PIN); // re-map frequency to eliminate low level noise - value = value > deadZone ? map(value - deadZone, 0, ADC_MAX_VALUE - deadZone, 0, ADC_MAX_VALUE) : 0; + value = value > _deadZone ? map(value - _deadZone, 0, ADC_MAX_VALUE - _deadZone, 0, ADC_MAX_VALUE) : 0; // crappy smoothing to avoid crazy flickering - frequencyData.bands[i] = smoothingFactor * frequencyData.bands[i] + (1 - smoothingFactor) * value; + frequencyData.bands[i] = _smoothingFactor * frequencyData.bands[i] + (1 - _smoothingFactor) * value; // strobe pin high again for next loop digitalWrite(FREQUENCY_SAMPLER_STROBE_PIN, HIGH); @@ -52,6 +52,13 @@ void FrequencySampler::loop() { } } +void FrequencySampler::updateSettings() { + _ledSettingsService->read([&](LedSettings& ledSettings) { + _smoothingFactor = ledSettings.smoothingFactor; + _deadZone = ledSettings.deadZone; + }); +} + FrequencyData* FrequencySampler::getFrequencyData() { return &_state; } diff --git a/src/FrequencySampler.h b/src/FrequencySampler.h index f84a6e3f..413c3332 100644 --- a/src/FrequencySampler.h +++ b/src/FrequencySampler.h @@ -4,6 +4,7 @@ #include #include #include +#include #define FREQUENCY_SAMPLER_DEAD_ZONE 700 #define FREQUENCY_SAMPLER_RESET_PIN 4 @@ -13,12 +14,6 @@ #define NUM_BANDS 7 #define ADC_MAX_VALUE 4096 -class FrequencySamplerSettings { - public: - virtual float getSmoothingFactor() = 0; - virtual uint16_t getDeadZone() = 0; -}; - class FrequencyData { public: uint16_t bands[NUM_BANDS]; @@ -49,14 +44,18 @@ class FrequencyData { class FrequencySampler : public StatefulService { public: - FrequencySampler(FrequencySamplerSettings* _settings); + FrequencySampler(LedSettingsService* ledSettingsService); void begin(); void loop(); FrequencyData* getFrequencyData(); private: - FrequencySamplerSettings* _settings; + LedSettingsService* _ledSettingsService; + float _smoothingFactor = 0; + float _deadZone = 0; + + void updateSettings(); }; #endif // end FrequencySampler_h diff --git a/src/LedSettingsService.cpp b/src/LedSettingsService.cpp index d40673d4..02c56206 100644 --- a/src/LedSettingsService.cpp +++ b/src/LedSettingsService.cpp @@ -9,26 +9,8 @@ LedSettingsService::LedSettingsService(AsyncWebServer* server, FS* fs, SecurityM securityManager, AuthenticationPredicates::IS_AUTHENTICATED), _fsPersistence(LedSettings::read, LedSettings::update, this, fs, LED_SETTINGS_FILE) { - _ledController = &FastLED.addLeds(_leds, NUM_LEDS); - addUpdateHandler([&](const String& originId) { configureLeds(); }, false); } void LedSettingsService::begin() { _fsPersistence.readFromFS(); } - -void LedSettingsService::update(LedUpdateCallback updateCallback) { - updateCallback(_leds, NUM_LEDS); -} - -void LedSettingsService::configureLeds() { - FastLED.setMaxPowerInMilliWatts(_state.maxPowerMilliwatts == 0 ? 0xFFFFFFFF : _state.maxPowerMilliwatts); -} - -float LedSettingsService::getSmoothingFactor() { - return _state.smoothingFactor; -} - -uint16_t LedSettingsService::getDeadZone() { - return _state.deadZone; -} diff --git a/src/LedSettingsService.h b/src/LedSettingsService.h index 8e6b6a4e..421433b3 100644 --- a/src/LedSettingsService.h +++ b/src/LedSettingsService.h @@ -4,23 +4,9 @@ #define LED_SETTINGS_FILE "/config/ledSettings.json" #define LED_SETTINGS_SERVICE_PATH "/rest/ledSettings" -#include #include #include #include -#include - -#define LED_DATA_PIN 21 -#define COLOR_ORDER RGB -#define LED_TYPE WS2811 -#define NUM_LEDS 50 - -/* -#define LED_DATA_PIN 21 -#define COLOR_ORDER GRB // GBR -#define LED_TYPE WS2812B -#define NUM_LEDS 9 -*/ #ifndef FACTORY_LED_MAX_POWER_MILLIWATTS #define FACTORY_LED_MAX_POWER_MILLIWATTS 0 @@ -34,21 +20,6 @@ #define FACTORY_LED_DEAD_ZONE 700 #endif -typedef std::function LedUpdateCallback; - -/** - * May be extended to support configuration of number of leds per strip. - * - * This is slightly tricky in the current state of the code as some effects rely on NUM_LEDS constant to set up data - * arrays. - * - * - Effects could be made to dynamically generate their data arrays and alter them when required - * - Effects could be destroyed and re-configured via a factory pattern which may allow for greater flexibility. - * - The callback approach could be abandoned in favor of direct access (static variable or singleton) - * - * The latter approach would mean effects must be destructor friendly but it would be much more memory efficent that way - * too because only the "active" effect needs be memory resident. - */ class LedSettings { public: uint32_t maxPowerMilliwatts; @@ -69,22 +40,15 @@ class LedSettings { } }; -class LedSettingsService : public StatefulService, public FrequencySamplerSettings { +class LedSettingsService : public StatefulService { public: LedSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager); - float getSmoothingFactor(); - uint16_t getDeadZone(); - void begin(); - void update(LedUpdateCallback updateCallback); private: - CRGB _leds[NUM_LEDS]; - CLEDController* _ledController; HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; - void configureLeds(); }; #endif // end LedSettingsService_h diff --git a/src/LightningMode.cpp b/src/LightningMode.cpp index 80d4867f..ffc9c303 100644 --- a/src/LightningMode.cpp +++ b/src/LightningMode.cpp @@ -3,13 +3,11 @@ LightningMode::LightningMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, fs, securityManager, - ledSettingsService, paletteSettingsService, frequencySampler, LightningModeSettings::read, @@ -18,39 +16,37 @@ LightningMode::LightningMode(AsyncWebServer* server, addUpdateHandler([&](const String& originId) { enable(); }, false); }; -void LightningMode::tick() { - _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { - if (_refresh) { - _status = Status::IDLE; // assert idle mode - fill_solid(leds, numLeds, CHSV(255, 0, 0)); // clear leds - FastLED.show(); // render all leds black - _refresh = false; // clear refresh flag - return; // eager return - } +void LightningMode::tick(CRGB* leds, const uint16_t numLeds) { + if (_refresh) { + _status = Status::IDLE; // assert idle mode + fill_solid(leds, numLeds, CHSV(255, 0, 0)); // clear leds + FastLED.show(); // render all leds black + _refresh = false; // clear refresh flag + return; // eager return + } - if (_status == Status::TRIGGERED) { - _ledstart = random8(numLeds); // Determine starting location of flash - _ledlen = random8(numLeds - _ledstart); // Determine length of flash (not to go beyond numLeds-1) - _numFlashes = random8(3, _state.flashes); // Calculate the random number of flashes we will show - _flashCounter = 0; // The number of flashes we have shown - _status = Status::RUNNING; - } + if (_status == Status::TRIGGERED) { + _ledstart = random8(numLeds); // Determine starting location of flash + _ledlen = random8(numLeds - _ledstart); // Determine length of flash (not to go beyond numLeds-1) + _numFlashes = random8(3, _state.flashes); // Calculate the random number of flashes we will show + _flashCounter = 0; // The number of flashes we have shown + _status = Status::RUNNING; + } - if (_status == Status::RUNNING && isWaitTimeElapsed()) { - _dimmer = (_flashCounter == 0) ? 5 : random8(1, 3); // leader scaled by a 5, return strokes brighter - fill_solid(leds + _ledstart, _ledlen, _state.color); // draw the flash - FastLED.show(_state.brightness / _dimmer); // show the flash - delay(random8(4, 10)); // wait a small amount of time (use non blocking delay?) - fill_solid(leds + _ledstart, _ledlen, CHSV(255, 0, 0)); // hide flash - FastLED.show(); // draw hidden leds - resetWaitTime(); // reset wait time for next flash + if (_status == Status::RUNNING && isWaitTimeElapsed()) { + _dimmer = (_flashCounter == 0) ? 5 : random8(1, 3); // leader scaled by a 5, return strokes brighter + fill_solid(leds + _ledstart, _ledlen, _state.color); // draw the flash + FastLED.show(_state.brightness / _dimmer); // show the flash + delay(random8(4, 10)); // wait a small amount of time (use non blocking delay?) + fill_solid(leds + _ledstart, _ledlen, CHSV(255, 0, 0)); // hide flash + FastLED.show(); // draw hidden leds + resetWaitTime(); // reset wait time for next flash - // decrement flash counter, and reset to idle if done - if (++_flashCounter >= _numFlashes) { - _status = Status::IDLE; - } + // decrement flash counter, and reset to idle if done + if (++_flashCounter >= _numFlashes) { + _status = Status::IDLE; } - }); + } } void LightningMode::enable() { diff --git a/src/LightningMode.h b/src/LightningMode.h index 31ca58be..a78bf2bf 100644 --- a/src/LightningMode.h +++ b/src/LightningMode.h @@ -76,10 +76,9 @@ class LightningMode : public AudioLightModeImpl { LightningMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler); - void tick(); + void tick(CRGB* leds, const uint16_t numLeds); void enable(); void sampleComplete(); bool isWaitTimeElapsed(); diff --git a/src/OffMode.cpp b/src/OffMode.cpp index 6a72b5cc..e5d6b69c 100644 --- a/src/OffMode.cpp +++ b/src/OffMode.cpp @@ -3,13 +3,11 @@ OffMode::OffMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, fs, securityManager, - ledSettingsService, paletteSettingsService, frequencySampler, OffModeSettings::read, @@ -20,13 +18,11 @@ void OffMode::enable() { _refresh = true; } -void OffMode::tick() { +void OffMode::tick(CRGB* leds, const uint16_t numLeds) { if (_refresh) { - _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { - fill_solid(leds, numLeds, CHSV(255, 0, 0)); // clear leds - FastLED.show(); // render all leds black - _refresh = false; // clear refresh flag - }); + fill_solid(leds, numLeds, CHSV(255, 0, 0)); // clear leds + FastLED.show(); // render all leds black + _refresh = false; // clear refresh flag } } diff --git a/src/OffMode.h b/src/OffMode.h index 13a7472e..1c679d79 100644 --- a/src/OffMode.h +++ b/src/OffMode.h @@ -23,10 +23,9 @@ class OffMode : public AudioLightModeImpl { OffMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler); - void tick(); + void tick(CRGB* leds, const uint16_t numLeds); void enable(); bool canRotate(); }; diff --git a/src/PacificaMode.cpp b/src/PacificaMode.cpp index a14a1ca1..41f6151f 100644 --- a/src/PacificaMode.cpp +++ b/src/PacificaMode.cpp @@ -3,13 +3,11 @@ PacificaMode::PacificaMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, fs, securityManager, - ledSettingsService, paletteSettingsService, frequencySampler, std::bind(&PacificaMode::read, this, std::placeholders::_1, std::placeholders::_2), @@ -28,55 +26,53 @@ void PacificaMode::enable() { }); } -void PacificaMode::tick() { - _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { - // Increment the four "color index start" counters, one for each wave layer. - // Each is incremented at a different speed, and the speeds vary over time. - static uint16_t sCIStart1, sCIStart2, sCIStart3, sCIStart4; - static uint32_t sLastms = 0; - uint32_t ms = millis(); - uint32_t deltams = ms - sLastms; - sLastms = ms; - uint16_t speedfactor1 = beatsin16(3, 179, 269); - uint16_t speedfactor2 = beatsin16(4, 179, 269); - uint32_t deltams1 = (deltams * speedfactor1) / 256; - uint32_t deltams2 = (deltams * speedfactor2) / 256; - uint32_t deltams21 = (deltams1 + deltams2) / 2; - sCIStart1 += (deltams1 * beatsin88(1011, 10, 13)); - sCIStart2 -= (deltams21 * beatsin88(777, 8, 11)); - sCIStart3 -= (deltams1 * beatsin88(501, 5, 7)); - sCIStart4 -= (deltams2 * beatsin88(257, 4, 6)); +void PacificaMode::tick(CRGB* leds, const uint16_t numLeds) { + // Increment the four "color index start" counters, one for each wave layer. + // Each is incremented at a different speed, and the speeds vary over time. + static uint16_t sCIStart1, sCIStart2, sCIStart3, sCIStart4; + static uint32_t sLastms = 0; + uint32_t ms = millis(); + uint32_t deltams = ms - sLastms; + sLastms = ms; + uint16_t speedfactor1 = beatsin16(3, 179, 269); + uint16_t speedfactor2 = beatsin16(4, 179, 269); + uint32_t deltams1 = (deltams * speedfactor1) / 256; + uint32_t deltams2 = (deltams * speedfactor2) / 256; + uint32_t deltams21 = (deltams1 + deltams2) / 2; + sCIStart1 += (deltams1 * beatsin88(1011, 10, 13)); + sCIStart2 -= (deltams21 * beatsin88(777, 8, 11)); + sCIStart3 -= (deltams1 * beatsin88(501, 5, 7)); + sCIStart4 -= (deltams2 * beatsin88(257, 4, 6)); - // Clear out the LED array to a dim background blue-green - fill_solid(leds, numLeds, CRGB(2, 6, 10)); + // Clear out the LED array to a dim background blue-green + fill_solid(leds, numLeds, CRGB(2, 6, 10)); - // Render each of four layers, with different scales and speeds, that vary over time - pacifica_one_layer(leds, - numLeds, - _state.palette1.colors, - sCIStart1, - beatsin16(3, 11 * 256, 14 * 256), - beatsin8(10, 70, 130), - 0 - beat16(301)); - pacifica_one_layer(leds, - numLeds, - _state.palette2.colors, - sCIStart2, - beatsin16(4, 6 * 256, 9 * 256), - beatsin8(17, 40, 80), - beat16(401)); - pacifica_one_layer(leds, numLeds, _state.palette3.colors, sCIStart3, 6 * 256, beatsin8(9, 10, 38), 0 - beat16(503)); - pacifica_one_layer(leds, numLeds, _state.palette3.colors, sCIStart4, 5 * 256, beatsin8(8, 10, 28), beat16(601)); + // Render each of four layers, with different scales and speeds, that vary over time + pacifica_one_layer(leds, + numLeds, + _state.palette1.colors, + sCIStart1, + beatsin16(3, 11 * 256, 14 * 256), + beatsin8(10, 70, 130), + 0 - beat16(301)); + pacifica_one_layer(leds, + numLeds, + _state.palette2.colors, + sCIStart2, + beatsin16(4, 6 * 256, 9 * 256), + beatsin8(17, 40, 80), + beat16(401)); + pacifica_one_layer(leds, numLeds, _state.palette3.colors, sCIStart3, 6 * 256, beatsin8(9, 10, 38), 0 - beat16(503)); + pacifica_one_layer(leds, numLeds, _state.palette3.colors, sCIStart4, 5 * 256, beatsin8(8, 10, 28), beat16(601)); - // Add brighter 'whitecaps' where the waves lines up more - pacifica_add_whitecaps(leds, numLeds); + // Add brighter 'whitecaps' where the waves lines up more + pacifica_add_whitecaps(leds, numLeds); - // Deepen the blues and greens a bit - pacifica_deepen_colors(leds, numLeds); + // Deepen the blues and greens a bit + pacifica_deepen_colors(leds, numLeds); - // Show the leds - FastLED.show(); - }); + // Show the leds + FastLED.show(); } // Add one layer of waves into the led array diff --git a/src/PacificaMode.h b/src/PacificaMode.h index 107557cc..5ca8e0ed 100644 --- a/src/PacificaMode.h +++ b/src/PacificaMode.h @@ -57,10 +57,9 @@ class PacificaMode : public AudioLightModeImpl { PacificaMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler); - void tick(); + void tick(CRGB* leds, const uint16_t numLeds); void enable(); }; diff --git a/src/PaletteSettingsService.h b/src/PaletteSettingsService.h index 9b62606c..5256e063 100644 --- a/src/PaletteSettingsService.h +++ b/src/PaletteSettingsService.h @@ -1,10 +1,12 @@ #ifndef PaletteSettingsService_h #define PaletteSettingsService_h -#include -#include +#include +#include +#include #include -#include +#include +#include #define PALETTE_SETTINGS_FILE "/config/paletteSettings.json" #define PALETTE_SETTINGS_SERVICE_PATH "/rest/paletteSettings" diff --git a/src/PrideMode.cpp b/src/PrideMode.cpp index bdaee372..af9dc1e2 100644 --- a/src/PrideMode.cpp +++ b/src/PrideMode.cpp @@ -3,13 +3,11 @@ PrideMode::PrideMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, fs, securityManager, - ledSettingsService, paletteSettingsService, frequencySampler, PrideModeSettings::read, @@ -22,8 +20,7 @@ void PrideMode::enable() { _refresh = true; } -void PrideMode::tick() { - _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { +void PrideMode::tick(CRGB* leds, const uint16_t numLeds) { static uint16_t sPseudotime = 0; static uint16_t sLastMillis = 0; static uint16_t sHue16 = 0; @@ -62,7 +59,5 @@ void PrideMode::tick() { nblend(leds[pixelnumber], newcolor, 64); } - // Show the leds FastLED.show(); - }); } diff --git a/src/PrideMode.h b/src/PrideMode.h index 75d74ad8..68c772ff 100644 --- a/src/PrideMode.h +++ b/src/PrideMode.h @@ -71,10 +71,9 @@ class PrideMode : public AudioLightModeImpl { PrideMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler); - void tick(); + void tick(CRGB* leds, const uint16_t numLeds); void enable(); }; diff --git a/src/RainbowMode.cpp b/src/RainbowMode.cpp index a2a8ffed..76c5213f 100644 --- a/src/RainbowMode.cpp +++ b/src/RainbowMode.cpp @@ -3,13 +3,11 @@ RainbowMode::RainbowMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler) : AudioLightModeImpl(server, fs, securityManager, - ledSettingsService, paletteSettingsService, frequencySampler, RainbowModeSettings::read, @@ -23,37 +21,33 @@ void RainbowMode::enable() { _lastFrameMicros = micros(); } -void RainbowMode::tick() { - _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { - FrequencyData* frequencyData = _frequencySampler->getFrequencyData(); - - // rotate hue in time based manner - if (_state.rotateSpeed > 0) { - unsigned long rotateDelayMicros = 1000000 / _state.rotateSpeed; - unsigned long currentMicros = micros(); - unsigned long microsElapsed = (unsigned long)(currentMicros - _lastFrameMicros); - if (microsElapsed >= rotateDelayMicros) { - _lastFrameMicros = currentMicros; - _initialhue++; - } +void RainbowMode::tick(CRGB* leds, const uint16_t numLeds) { + FrequencyData* frequencyData = _frequencySampler->getFrequencyData(); + + // rotate hue in time based manner + if (_state.rotateSpeed > 0) { + unsigned long rotateDelayMicros = 1000000 / _state.rotateSpeed; + unsigned long currentMicros = micros(); + unsigned long microsElapsed = (unsigned long)(currentMicros - _lastFrameMicros); + if (microsElapsed >= rotateDelayMicros) { + _lastFrameMicros = currentMicros; + _initialhue++; } - - // fill the rainbow - fill_rainbow(leds, numLeds, _initialhue, _state.hueDelta); - - // fade each segment if audio-enabled - if (_state.audioEnabled) { - CRGB* startLed = leds; - for (uint8_t i = 0; i < NUM_BANDS; i++) { - uint16_t numLeds = _ledsPerBand + (i == NUM_BANDS - 1 ? _remainingLeds : 0); - fadeToBlackBy(startLed, numLeds, 255 - map(frequencyData->bands[i], 0, ADC_MAX_VALUE, 0, 255)); - startLed += _ledsPerBand; - } + } + + // fill the rainbow + fill_rainbow(leds, numLeds, _initialhue, _state.hueDelta); + + // fade each segment if audio-enabled + if (_state.audioEnabled) { + CRGB* startLed = leds; + for (uint8_t i = 0; i < NUM_BANDS; i++) { + uint16_t numLeds = _ledsPerBand + (i == NUM_BANDS - 1 ? _remainingLeds : 0); + fadeToBlackBy(startLed, numLeds, 255 - map(frequencyData->bands[i], 0, ADC_MAX_VALUE, 0, 255)); + startLed += _ledsPerBand; } + } - - - // update the leds - FastLED.show(_state.brightness); - }); + // update the leds + FastLED.show(_state.brightness); } diff --git a/src/RainbowMode.h b/src/RainbowMode.h index 3ffe7883..da54a436 100644 --- a/src/RainbowMode.h +++ b/src/RainbowMode.h @@ -61,10 +61,9 @@ class RainbowMode : public AudioLightModeImpl { RainbowMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler); - void tick(); + void tick(CRGB* leds, const uint16_t numLeds); void enable(); }; diff --git a/src/RotateMode.cpp b/src/RotateMode.cpp index 58d594c9..84ed0004 100644 --- a/src/RotateMode.cpp +++ b/src/RotateMode.cpp @@ -3,14 +3,12 @@ RotateMode::RotateMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler, ModeFetcher modeFetcher) : AudioLightModeImpl(server, fs, securityManager, - ledSettingsService, paletteSettingsService, frequencySampler, std::bind(&RotateMode::read, this, std::placeholders::_1, std::placeholders::_2), @@ -46,29 +44,29 @@ void RotateMode::enable() { _refresh = true; } -void RotateMode::tick() { +void RotateMode::tick(CRGB* leds, const uint16_t numLeds) { unsigned long currentMillis = millis(); // refresh if we are required to if (_refresh) { - selectMode(0); + selectMode(0, leds, numLeds); _modeChangedAt = millis(); _refresh = false; } // hand off to the selected mode to do its thing if (_selectedMode) { - _selectedMode->tick(); + _selectedMode->tick(leds, numLeds); } // change mode if it's time if ((unsigned long)(currentMillis - _modeChangedAt) >= _state.delay) { - selectMode(_currentMode + 1); + selectMode(_currentMode + 1, leds, numLeds); _modeChangedAt = millis(); } } -void RotateMode::selectMode(uint8_t newMode) { +void RotateMode::selectMode(uint8_t newMode, CRGB* leds, const uint16_t numLeds) { AudioLightMode* nextMode = nullptr; // select the next mode if (_state.modes.size() > 0) { @@ -85,10 +83,8 @@ void RotateMode::selectMode(uint8_t newMode) { _selectedMode->enable(); } else { // no mode selected, clear LEDs - _ledSettingsService->update([&](CRGB* leds, const uint16_t numLeds) { - fill_solid(leds, numLeds, CHSV(255, 0, 0)); // clear leds - FastLED.show(); // render all leds black - }); + fill_solid(leds, numLeds, CHSV(255, 0, 0)); // clear leds + FastLED.show(); // render all leds black } } } diff --git a/src/RotateMode.h b/src/RotateMode.h index 5b6734b7..d98af3d4 100644 --- a/src/RotateMode.h +++ b/src/RotateMode.h @@ -26,21 +26,20 @@ class RotateMode : public AudioLightModeImpl { unsigned long _modeChangedAt = 0; ModeFetcher _modeFetcher; AudioLightMode* _selectedMode; - void selectMode(uint8_t modeOrdinal); + + void read(RotateModeSettings& settings, JsonObject& root); + StateUpdateResult update(JsonObject& root, RotateModeSettings& settings); + void selectMode(uint8_t newMode, CRGB* leds, const uint16_t numLeds); public: RotateMode(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, - LedSettingsService* ledSettingsService, PaletteSettingsService* paletteSettingsService, FrequencySampler* frequencySampler, ModeFetcher modeFetcher); - void tick(); + void tick(CRGB* leds, const uint16_t numLeds); void enable(); - - void read(RotateModeSettings& settings, JsonObject& root); - StateUpdateResult update(JsonObject& root, RotateModeSettings& settings); bool canRotate(); }; diff --git a/src/main.cpp b/src/main.cpp index 9e256799..b63ceaa9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,20 +27,20 @@ void setup() { // start the framework and demo project esp8266React.begin(); - // start the server - server.begin(); + // load general settings + ledSettingsService.begin(); // set up the sampler frequencySampler.begin(); - // configure the LED strip - ledSettingsService.begin(); - // load the palettes paletteSettingsService.begin(); // load all of the defaults audioLightSettingsService.begin(); + + // start the server + server.begin(); } void loop() { From acb2c3da15efe80c8d438a7e272cfdcbbeb90deb Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sun, 29 Nov 2020 18:06:39 +0000 Subject: [PATCH 46/52] enable rotate mode by default --- src/AudioLightSettingsService.cpp | 2 +- src/FastLEDSettings.h | 2 +- src/RotateMode.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index 8e0a1de3..dc12b8ff 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -55,7 +55,7 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, void AudioLightSettingsService::begin() { // configure current mode - _state.currentMode = _modes[0]; + _state.currentMode = _modes[7]; // initialize all modes for (uint8_t i = 0; i < NUM_MODES; i++) { diff --git a/src/FastLEDSettings.h b/src/FastLEDSettings.h index 5ea17d75..05cb6f60 100644 --- a/src/FastLEDSettings.h +++ b/src/FastLEDSettings.h @@ -14,7 +14,7 @@ #endif #ifndef NUM_LEDS -#define NUM_LEDS 50 +#define NUM_LEDS 100 #endif #endif \ No newline at end of file diff --git a/src/RotateMode.cpp b/src/RotateMode.cpp index 84ed0004..5896b303 100644 --- a/src/RotateMode.cpp +++ b/src/RotateMode.cpp @@ -76,7 +76,7 @@ void RotateMode::selectMode(uint8_t newMode, CRGB* leds, const uint16_t numLeds) nextMode = _modeFetcher(*std::next(_state.modes.begin(), _currentMode)); } // activate the next mode if it's differnt from the selected mode - if (nextMode != _selectedMode) { + if (nextMode != _selectedMode || _refresh) { _selectedMode = nextMode; if (_selectedMode) { // new mode selected, enable it From 45e3c5d79bb2ce38027ac85cc7c8cb83b65415d2 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sun, 29 Nov 2020 18:07:40 +0000 Subject: [PATCH 47/52] enable rotate mode by default --- src/AudioLightSettingsService.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index dc12b8ff..6f8dcf1b 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -55,7 +55,7 @@ AudioLightSettingsService::AudioLightSettingsService(AsyncWebServer* server, void AudioLightSettingsService::begin() { // configure current mode - _state.currentMode = _modes[7]; + _state.currentMode = _modes[8]; // initialize all modes for (uint8_t i = 0; i < NUM_MODES; i++) { From e58e83820c923ef230e3d8ac2b154ba6ab1460ab Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sun, 29 Nov 2020 20:58:13 +0000 Subject: [PATCH 48/52] move payload from top to nested in autio light settings request/response --- interface/.env.development | 4 +- .../src/project/modes/AudioLightColorMode.tsx | 4 +- .../project/modes/AudioLightConfettiMode.tsx | 4 +- .../src/project/modes/AudioLightFireMode.tsx | 4 +- .../project/modes/AudioLightLightningMode.tsx | 4 +- .../src/project/modes/AudioLightMode.tsx | 34 ++-- .../project/modes/AudioLightPacificaMode.tsx | 4 +- .../src/project/modes/AudioLightPrideMode.tsx | 4 +- .../project/modes/AudioLightRainbowMode.tsx | 4 +- .../project/modes/AudioLightRotateMode.tsx | 4 +- interface/src/project/types.ts | 182 ++++++++---------- src/AudioLightSettingsService.cpp | 30 ++- src/FastLEDSettings.h | 14 +- 13 files changed, 146 insertions(+), 150 deletions(-) diff --git a/interface/.env.development b/interface/.env.development index f7bb8583..b12cfd04 100644 --- a/interface/.env.development +++ b/interface/.env.development @@ -1,4 +1,4 @@ # Change the IP address to that of your ESP device to enable local development of the UI. # Remember to also enable CORS in platformio.ini before uploading the code to the device. -REACT_APP_HTTP_ROOT=http://192.168.0.77 -REACT_APP_WEB_SOCKET_ROOT=ws://192.168.0.77 +REACT_APP_HTTP_ROOT=http://192.168.0.88 +REACT_APP_WEB_SOCKET_ROOT=ws://192.168.0.88 diff --git a/interface/src/project/modes/AudioLightColorMode.tsx b/interface/src/project/modes/AudioLightColorMode.tsx index 0961b184..281f3c2a 100644 --- a/interface/src/project/modes/AudioLightColorMode.tsx +++ b/interface/src/project/modes/AudioLightColorMode.tsx @@ -3,13 +3,13 @@ import React from 'react'; import FormLabel from '@material-ui/core/FormLabel'; import Slider from '@material-ui/core/Slider'; -import { ColorMode } from '../types'; +import { ColorModeSettings } from '../types'; import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; import { Box, Switch } from '@material-ui/core'; import ColorPicker from '../components/ColorPicker'; import IncludedBands from '../components/IncludedBands'; -type AudioLightColorModeProps = AudioLightModeProps; +type AudioLightColorModeProps = AudioLightModeProps; class AudioLightColorMode extends React.Component { diff --git a/interface/src/project/modes/AudioLightConfettiMode.tsx b/interface/src/project/modes/AudioLightConfettiMode.tsx index 01bf4ccf..4627a4ed 100644 --- a/interface/src/project/modes/AudioLightConfettiMode.tsx +++ b/interface/src/project/modes/AudioLightConfettiMode.tsx @@ -4,11 +4,11 @@ import FormLabel from '@material-ui/core/FormLabel'; import Slider from '@material-ui/core/Slider'; import { Box } from '@material-ui/core'; -import { ConfettiMode } from '../types'; +import { ConfettiModeSettings } from '../types'; import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; import PalettePicker from '../components/PalettePicker'; -type AudioLightConfettiModeProps = AudioLightModeProps; +type AudioLightConfettiModeProps = AudioLightModeProps; class AudioLightConfettiMode extends React.Component { diff --git a/interface/src/project/modes/AudioLightFireMode.tsx b/interface/src/project/modes/AudioLightFireMode.tsx index 97c43dee..0cd0e410 100644 --- a/interface/src/project/modes/AudioLightFireMode.tsx +++ b/interface/src/project/modes/AudioLightFireMode.tsx @@ -3,12 +3,12 @@ import React from 'react'; import FormLabel from '@material-ui/core/FormLabel'; import Slider from '@material-ui/core/Slider'; -import { FireMode } from '../types'; +import { FireModeSettings } from '../types'; import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; import { Box, Switch } from '@material-ui/core'; import PalettePicker from '../components/PalettePicker'; -type AudioLightFireModeProps = AudioLightModeProps; +type AudioLightFireModeProps = AudioLightModeProps; class AudioLightFireMode extends React.Component { diff --git a/interface/src/project/modes/AudioLightLightningMode.tsx b/interface/src/project/modes/AudioLightLightningMode.tsx index 97b47047..8716617b 100644 --- a/interface/src/project/modes/AudioLightLightningMode.tsx +++ b/interface/src/project/modes/AudioLightLightningMode.tsx @@ -3,13 +3,13 @@ import React from 'react'; import FormLabel from '@material-ui/core/FormLabel'; import Slider from '@material-ui/core/Slider'; -import { LightningMode } from '../types'; +import { LightningModeSettings } from '../types'; import IncludedBands from '../components/IncludedBands'; import ColorPicker from '../components/ColorPicker'; import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; import { Box } from '@material-ui/core'; -type AudioLightLightningModeProps = AudioLightModeProps; +type AudioLightLightningModeProps = AudioLightModeProps; class AudioLightLightningMode extends React.Component { diff --git a/interface/src/project/modes/AudioLightMode.tsx b/interface/src/project/modes/AudioLightMode.tsx index f5208cc5..e71e8b09 100644 --- a/interface/src/project/modes/AudioLightMode.tsx +++ b/interface/src/project/modes/AudioLightMode.tsx @@ -1,9 +1,9 @@ import React from "react"; import { ColorResult } from "react-color"; -import { WebSocketFormProps } from "../../components"; -import { AudioLightMode } from "../types"; +import { extractEventValue, WebSocketFormProps } from "../../components"; +import { AudioLightMode, AudioLightModeSettings } from "../types"; -export interface AudioLightModeProps { +export interface AudioLightModeProps { handleValueChange: (name: keyof D, callback?: () => void) => (event: React.ChangeEvent) => void; handleChange: (name: keyof D) => (value: T) => void; handleSliderChange: (name: keyof D) => (event: React.ChangeEvent<{}>, value: number | number[]) => void; @@ -11,37 +11,37 @@ export interface AudioLightModeProps { data: D; } -export function audioLightMode(AudioLightMode: React.ComponentType>) { - return class extends React.Component> { +export function audioLightMode(AudioLightModeComponent: React.ComponentType>) { + return class extends React.Component> { handleChange = (name: keyof D) => (value: T) => { const { data, setData, saveData } = this.props; - setData({ ...data, [name]: value }, saveData); - } - - handleValueChange = (name: keyof D) => { - const { handleValueChange, saveData } = this.props; - return handleValueChange(name, saveData) + setData({ ...data, settings: { ...data.settings, [name]: value } }, saveData); } + handleValueChange = (name: keyof D) => (event: React.ChangeEvent) => { + const { data, setData, saveData } = this.props; + setData({ ...data, settings: { ...data.settings, [name]: extractEventValue(event) } }, saveData) + } + handleSliderChange = (name: keyof D) => (event: React.ChangeEvent<{}>, value: number | number[]) => { - const { setData, saveData } = this.props; - setData({ ...this.props.data!, [name]: value }, saveData); + const { data, setData, saveData } = this.props; + setData({ ...data, settings: { ...data.settings, [name]: value } }, saveData); } handleColorChange = (name: keyof D) => (value: ColorResult) => { - const { setData, saveData } = this.props; - setData({ ...this.props.data!, [name]: value.hex }, saveData); + const { data, setData, saveData } = this.props; + setData({ ...data, settings: { ...data.settings, [name]: value.hex } }, saveData); } render() { return ( - + data={this.props.data.settings as D} /> ); } diff --git a/interface/src/project/modes/AudioLightPacificaMode.tsx b/interface/src/project/modes/AudioLightPacificaMode.tsx index a3133a75..a4ef4824 100644 --- a/interface/src/project/modes/AudioLightPacificaMode.tsx +++ b/interface/src/project/modes/AudioLightPacificaMode.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { PacificaMode } from '../types'; +import { PacificaModeSettings } from '../types'; import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; import PalettePicker from '../components/PalettePicker'; -type AudioLightPacificaModeProps = AudioLightModeProps; +type AudioLightPacificaModeProps = AudioLightModeProps; class AudioLightPacificaMode extends React.Component { diff --git a/interface/src/project/modes/AudioLightPrideMode.tsx b/interface/src/project/modes/AudioLightPrideMode.tsx index 522a2b8f..a4cd7a95 100644 --- a/interface/src/project/modes/AudioLightPrideMode.tsx +++ b/interface/src/project/modes/AudioLightPrideMode.tsx @@ -3,11 +3,11 @@ import React from 'react'; import FormLabel from '@material-ui/core/FormLabel'; import Slider from '@material-ui/core/Slider'; -import { PrideMode } from '../types'; +import { PrideModeSettings } from '../types'; import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; import { Box } from '@material-ui/core'; -type AudioLightPrideModeProps = AudioLightModeProps; +type AudioLightPrideModeProps = AudioLightModeProps; class AudioLightPrideMode extends React.Component { render() { diff --git a/interface/src/project/modes/AudioLightRainbowMode.tsx b/interface/src/project/modes/AudioLightRainbowMode.tsx index 951c6b7f..ca70d1cd 100644 --- a/interface/src/project/modes/AudioLightRainbowMode.tsx +++ b/interface/src/project/modes/AudioLightRainbowMode.tsx @@ -3,11 +3,11 @@ import React from 'react'; import FormLabel from '@material-ui/core/FormLabel'; import Slider from '@material-ui/core/Slider'; -import { RainbowMode } from '../types'; +import { RainbowModeSettings } from '../types'; import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; import { Box, Switch } from '@material-ui/core'; -type AudioLightRainbowModeProps = AudioLightModeProps; +type AudioLightRainbowModeProps = AudioLightModeProps; class AudioLightRainbowMode extends React.Component { diff --git a/interface/src/project/modes/AudioLightRotateMode.tsx b/interface/src/project/modes/AudioLightRotateMode.tsx index f2e9803c..262ca765 100644 --- a/interface/src/project/modes/AudioLightRotateMode.tsx +++ b/interface/src/project/modes/AudioLightRotateMode.tsx @@ -2,10 +2,10 @@ import { Box, FormLabel, Slider } from '@material-ui/core'; import React from 'react'; import ModeTransferList from '../components/ModeTransferList'; -import { RotateMode } from '../types'; +import { RotateModeSettings } from '../types'; import { audioLightMode, AudioLightModeProps } from './AudioLightMode'; -type AudioLightRotateModeProps = AudioLightModeProps; +type AudioLightRotateModeProps = AudioLightModeProps; const millisToMinutesAndSeconds = (millis: number) => { var minutes = Math.floor(millis / 60000); diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index f78c1082..d7013076 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -14,10 +14,9 @@ export interface FrequencyData { bands: number[]; } -export interface AudioLightModeMetadata { - label: string; - renderer?: React.ComponentType>; - rotate: boolean; +export interface Palette { + id: string; + colors: string[]; } export enum AudioLightModeType { @@ -32,45 +31,24 @@ export enum AudioLightModeType { ROTATE = "rotate" } -export interface OffMode { - mode_id: AudioLightModeType.OFF; +export interface OffModeSettings { } -export const OFF_MODE_METADATA: AudioLightModeMetadata = { - label: "Off", - rotate: false -}; - -export interface ColorMode { - mode_id: AudioLightModeType.COLOR; +export interface ColorModeSettings { color: string; brightness: number; audio_enabled: boolean; included_bands: boolean[] } -export const COLOR_MODE_METADATA: AudioLightModeMetadata = { - label: "Color", - renderer: AudioLightColorMode, - rotate: true -}; - -export interface RainbowMode { - mode_id: AudioLightModeType.RAINBOW; +export interface RainbowModeSettings { brightness: number; rotate_speed: 32; audio_enabled: boolean; hue_delta: number; } -export const RAINBOW_MODE_METADATA: AudioLightModeMetadata = { - label: "Rainbow", - renderer: AudioLightRainbowMode, - rotate: true -}; - -export interface LightningMode { - mode_id: AudioLightModeType.LIGHTNING; +export interface LightningModeSettings { color: string; brightness: number; threshold: number; @@ -79,14 +57,7 @@ export interface LightningMode { included_bands: boolean[] } -export const LIGHTNING_MODE_METADATA: AudioLightModeMetadata = { - label: "Lightning", - renderer: AudioLightLightningMode, - rotate: true -}; - -export interface ConfettiMode { - mode_id: AudioLightModeType.CONFETTI; +export interface ConfettiModeSettings { palette1: string; palette2: string; palette3: string; @@ -95,41 +66,20 @@ export interface ConfettiMode { delay: number; } -export const CONFETTI_MODE_METADATA: AudioLightModeMetadata = { - label: "Confetti", - renderer: AudioLightConfettiMode, - rotate: true -}; - -export interface FireMode { - mode_id: AudioLightModeType.FIRE; +export interface FireModeSettings { palette: string; cooling: number; sparking: number; reverse: boolean; } -export const FIRE_MODE_METADATA: AudioLightModeMetadata = { - label: "Fire", - renderer: AudioLightFireMode, - rotate: true -}; - -export interface PacificaMode { - mode_id: AudioLightModeType.PACIFICA; +export interface PacificaModeSettings { palette1: string; palette2: string; palette3: string; } -export const PACIFICA_MODE_METADATA: AudioLightModeMetadata = { - label: "Pacifica", - renderer: AudioLightPacificaMode, - rotate: true -}; - -export interface PrideMode { - mode_id: AudioLightModeType.PRIDE; +export interface PrideModeSettings { brightness_bpm: number; brightness_freq_min: number; brightness_freq_max: number; @@ -138,24 +88,11 @@ export interface PrideMode { hue_delta_max: number; } -export const PRIDE_MODE_METADATA: AudioLightModeMetadata = { - label: "Pride", - renderer: AudioLightPrideMode, - rotate: true -}; - -export interface RotateMode { - mode_id: AudioLightModeType.ROTATE; +export interface RotateModeSettings { modes: AudioLightModeType[]; delay: number; } -export const ROTATE_MODE_METADATA: AudioLightModeMetadata = { - label: "Rotate", - renderer: AudioLightRotateMode, - rotate: false -}; - export interface PaletteSettings { palettes: Palette[]; } @@ -166,22 +103,22 @@ export interface LedSettings { smoothing_factor: number; } -export interface Palette { - id: string; - colors: string[]; -}; - -export type AudioLightMode = ( - OffMode | - ColorMode | - RainbowMode | - LightningMode | - ConfettiMode | - FireMode | - PacificaMode | - PrideMode | - RotateMode -); +export type AudioLightModeSettings = ( + OffModeSettings | + ColorModeSettings | + RainbowModeSettings | + LightningModeSettings | + ConfettiModeSettings | + FireModeSettings | + PacificaModeSettings | + PrideModeSettings | + RotateModeSettings +) + +export interface AudioLightMode { + mode_id: AudioLightModeType; + settings: AudioLightModeSettings; +} export const LED_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "ledSettings"; export const PALETTE_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "paletteSettings"; @@ -191,7 +128,7 @@ export const AUDIO_LIGHT_SETTINGS_ENDPOINT = WEB_SOCKET_ROOT + "audioLightSettin export const generateGradient = (palette: Palette) => { return `linear-gradient(0.25turn, ${palette.colors.join(', ')})`; -}; +} export const DEFAULT_PALETTE = [ "#ff0000", @@ -212,16 +149,57 @@ export const DEFAULT_PALETTE = [ "#d5002b" ]; -export const AUDIO_LIGHT_MODE_METADATA: { [type in AudioLightModeType]: AudioLightModeMetadata } = { - off: OFF_MODE_METADATA, - color: COLOR_MODE_METADATA, - rainbow: RAINBOW_MODE_METADATA, - lightning: LIGHTNING_MODE_METADATA, - confetti: CONFETTI_MODE_METADATA, - fire: FIRE_MODE_METADATA, - pacifica: PACIFICA_MODE_METADATA, - pride: PRIDE_MODE_METADATA, - rotate: ROTATE_MODE_METADATA +export interface AudioLightModeMetadata { + label: string; + renderer?: React.ComponentType>; + rotate: boolean; +} + +export const AUDIO_LIGHT_MODE_METADATA: { [type in AudioLightModeType]: AudioLightModeMetadata } = { + off: { + label: "Off", + rotate: false + }, + color: { + label: "Color", + renderer: AudioLightColorMode, + rotate: true + }, + rainbow: { + label: "Rainbow", + renderer: AudioLightRainbowMode, + rotate: true + }, + lightning: { + label: "Lightning", + renderer: AudioLightLightningMode, + rotate: true + }, + confetti: { + label: "Confetti", + renderer: AudioLightConfettiMode, + rotate: true + }, + fire: { + label: "Fire", + renderer: AudioLightFireMode, + rotate: true + }, + pacifica: { + label: "Pacifica", + renderer: AudioLightPacificaMode, + rotate: true + }, + pride: { + label: "Pride", + renderer: AudioLightPrideMode, + rotate: true + }, + rotate: { + label: "Rotate", + renderer: AudioLightRotateMode, + rotate: false + } } export const AUDIO_LIGHT_MODES = Object.entries(AudioLightModeType).map(value => value[1]); diff --git a/src/AudioLightSettingsService.cpp b/src/AudioLightSettingsService.cpp index 6f8dcf1b..11b95f9b 100644 --- a/src/AudioLightSettingsService.cpp +++ b/src/AudioLightSettingsService.cpp @@ -98,25 +98,43 @@ void AudioLightSettingsService::handleSample() { void AudioLightSettingsService::read(AudioLightSettings& settings, JsonObject& root) { if (settings.currentMode) { root["mode_id"] = settings.currentMode->getId(); - settings.currentMode->readAsJson(root); + JsonObject settingsRoot = root.createNestedObject("settings"); + settings.currentMode->readAsJson(settingsRoot); } } StateUpdateResult AudioLightSettingsService::update(JsonObject& root, AudioLightSettings& settings) { - String modeId = root["mode_id"]; + // we must have a mode id + if (!root["mode_id"].is()) { + return StateUpdateResult::ERROR; + } - // change mode if required + // change mode if required, exit early on error + StateUpdateResult modeUpdateResult = StateUpdateResult::UNCHANGED; + String modeId = root["mode_id"]; if (settings.currentMode->getId() != modeId) { AudioLightMode* mode = getMode(modeId); if (!mode) { return StateUpdateResult::ERROR; } settings.currentMode = mode; - return StateUpdateResult::CHANGED; + modeUpdateResult = StateUpdateResult::CHANGED; + } + + // change settings, exit early on error + StateUpdateResult settingsUpdateResult = StateUpdateResult::UNCHANGED; + if (root["settings"].is()) { + JsonObject modeSettings = root["settings"].as(); + settingsUpdateResult = settings.currentMode->updateFromJson(modeSettings, LOCAL_ORIGIN); + if (settingsUpdateResult == StateUpdateResult::ERROR) { + return StateUpdateResult::ERROR; + } } - // update mode settings - return settings.currentMode->updateFromJson(root, LOCAL_ORIGIN); + // calculate update state + return modeUpdateResult == StateUpdateResult::CHANGED || settingsUpdateResult == StateUpdateResult::CHANGED + ? StateUpdateResult::CHANGED + : StateUpdateResult::UNCHANGED; } void AudioLightSettingsService::saveModeConfig(AsyncWebServerRequest* request) { diff --git a/src/FastLEDSettings.h b/src/FastLEDSettings.h index 05cb6f60..1d7e4f05 100644 --- a/src/FastLEDSettings.h +++ b/src/FastLEDSettings.h @@ -6,15 +6,15 @@ #endif #ifndef COLOR_ORDER -#define COLOR_ORDER RGB -#endif +#define COLOR_ORDER GRB +#endif // RGB #ifndef LED_TYPE -#define LED_TYPE WS2811 -#endif +#define LED_TYPE WS2812 +#endif // WS2811 #ifndef NUM_LEDS -#define NUM_LEDS 100 -#endif +#define NUM_LEDS 9 +#endif // 100 -#endif \ No newline at end of file +#endif From 0e38524727e54cf0eab0ac57854f3d74793a2905 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sun, 29 Nov 2020 21:20:13 +0000 Subject: [PATCH 49/52] slim padding on mode transfer list --- .../src/project/components/ModeTransferList.tsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/interface/src/project/components/ModeTransferList.tsx b/interface/src/project/components/ModeTransferList.tsx index 71b242ba..c61e1367 100644 --- a/interface/src/project/components/ModeTransferList.tsx +++ b/interface/src/project/components/ModeTransferList.tsx @@ -4,7 +4,6 @@ import { makeStyles } from '@material-ui/core/styles'; import Grid from '@material-ui/core/Grid'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; -import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemText from '@material-ui/core/ListItemText'; import Checkbox from '@material-ui/core/Checkbox'; import Button from '@material-ui/core/Button'; @@ -14,7 +13,7 @@ import { ROTATE_AUDIO_LIGHT_MODES, AudioLightModeType, AUDIO_LIGHT_MODE_METADATA const useStyles = makeStyles((theme) => ({ paper: { - width: 180, + width: 160, height: 230, overflow: 'auto', }, @@ -89,14 +88,12 @@ const ModeTransferList: FC = ({ selected, onSelectionChanged } {items.map((value) => { return ( - - - - + + ); From 185cd20ceeceddd3476899890af767f04dbd57c6 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sun, 29 Nov 2020 22:45:25 +0000 Subject: [PATCH 50/52] rename id to name in palette --- interface/src/project/PaletteForm.tsx | 20 +++++++++---------- interface/src/project/PaletteSettingsForm.tsx | 20 +++++++++---------- .../src/project/components/PalettePicker.tsx | 4 ++-- interface/src/project/types.ts | 2 +- src/ConfettiMode.h | 6 +++--- src/FireMode.cpp | 2 +- src/FireMode.h | 6 +++--- src/PacificaMode.h | 6 +++--- src/PaletteSettingsService.h | 20 +++++++++---------- 9 files changed, 43 insertions(+), 43 deletions(-) diff --git a/interface/src/project/PaletteForm.tsx b/interface/src/project/PaletteForm.tsx index 105268ff..8b5f5f72 100644 --- a/interface/src/project/PaletteForm.tsx +++ b/interface/src/project/PaletteForm.tsx @@ -10,7 +10,7 @@ import { ChromePicker, ColorResult } from 'react-color'; interface PaletteFormProps { creating: boolean; palette: Palette; - uniqueId: (value: any) => boolean; + uniqueName: (name: any) => boolean; updatePalette: (palette: Palette) => void; onDoneEditing: () => void; onCancelEditing: () => void; @@ -29,7 +29,7 @@ class PaletteForm extends React.Component { formRef: RefObject = React.createRef(); componentDidMount() { - ValidatorForm.addValidationRule('uniqueId', this.props.uniqueId); + ValidatorForm.addValidationRule('uniqueName', this.props.uniqueName); } submit = () => { @@ -40,9 +40,9 @@ class PaletteForm extends React.Component { this.setState({ color: value as number }); } - updateId = (event: React.ChangeEvent) => { + updateName = (event: React.ChangeEvent) => { const { updatePalette, palette } = this.props; - updatePalette({ ...palette, id: event.target.value }); + updatePalette({ ...palette, name: event.target.value }); } changeColor = (result: ColorResult) => { @@ -63,15 +63,15 @@ class PaletteForm extends React.Component { {creating ? 'Add' : 'Modify'} Palette diff --git a/interface/src/project/PaletteSettingsForm.tsx b/interface/src/project/PaletteSettingsForm.tsx index 3fa4b741..77d4f6f8 100644 --- a/interface/src/project/PaletteSettingsForm.tsx +++ b/interface/src/project/PaletteSettingsForm.tsx @@ -24,13 +24,13 @@ class PaletteSettingsForm extends React.Component { - return !this.props.data.palettes.find(p => p.id === id); + uniqueName = (name: string) => { + return !this.props.data.palettes.find(p => p.name === name); } removePalette = (palette: Palette) => { const { data } = this.props; - const palettes = data.palettes.filter(p => p.id !== palette.id); + const palettes = data.palettes.filter(p => p.name !== palette.name); this.props.setData({ ...data, palettes }); } @@ -51,7 +51,7 @@ class PaletteSettingsForm extends React.Component p.id !== palette.id); + const palettes = data.palettes.filter(p => p.name !== palette.name); palettes.push(palette); this.props.setData({ ...data, palettes }); this.setState({ @@ -64,7 +64,7 @@ class PaletteSettingsForm extends React.Component { const { data } = this.props; - return data.palettes.sort((a, b) => a.id.localeCompare(b.id)).map(palette => ( - + return data.palettes.sort((a, b) => a.name.localeCompare(b.name)).map(palette => ( + - {palette.id} + {palette.name} @@ -104,7 +104,7 @@ class PaletteSettingsForm extends React.Component - Id + Name Palette @@ -137,7 +137,7 @@ class PaletteSettingsForm extends React.Component } diff --git a/interface/src/project/components/PalettePicker.tsx b/interface/src/project/components/PalettePicker.tsx index da7c42a8..742441da 100644 --- a/interface/src/project/components/PalettePicker.tsx +++ b/interface/src/project/components/PalettePicker.tsx @@ -23,10 +23,10 @@ const PalettePicker: FC = ({ name, label, value, onChange }) variant="outlined" select> {context.paletteSettings.palettes.map(palette => ( - + - + diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index d7013076..525ba750 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -15,7 +15,7 @@ export interface FrequencyData { } export interface Palette { - id: string; + name: string; colors: string[]; } diff --git a/src/ConfettiMode.h b/src/ConfettiMode.h index 85ebb57b..d43b9b59 100644 --- a/src/ConfettiMode.h +++ b/src/ConfettiMode.h @@ -61,9 +61,9 @@ class ConfettiMode : public AudioLightModeImpl { writeByteToJson(root, &settings.brightness, "brightness"); writeByteToJson(root, &settings.delay, "delay"); - root["palette1"] = settings.palette1.id; - root["palette2"] = settings.palette2.id; - root["palette3"] = settings.palette3.id; + root["palette1"] = settings.palette1.name; + root["palette2"] = settings.palette2.name; + root["palette3"] = settings.palette3.name; } StateUpdateResult update(JsonObject& root, ConfettiModeSettings& settings) { diff --git a/src/FireMode.cpp b/src/FireMode.cpp index 140d2bcd..ba4c9635 100644 --- a/src/FireMode.cpp +++ b/src/FireMode.cpp @@ -17,7 +17,7 @@ FireMode::FireMode(AsyncWebServer* server, void FireMode::enable() { _refresh = true; updateWithoutPropagation([&](FireModeSettings& settings) { - selectPalette(settings.palette.id, settings); + selectPalette(settings.palette.name, settings); return StateUpdateResult::CHANGED; }); } diff --git a/src/FireMode.h b/src/FireMode.h index 7e98083f..28cd768d 100644 --- a/src/FireMode.h +++ b/src/FireMode.h @@ -41,7 +41,7 @@ class FireMode : public AudioLightModeImpl { writeByteToJson(root, &settings.cooling, "cooling"); writeByteToJson(root, &settings.sparking, "sparking"); writeBoolToJson(root, &settings.reverse, "reverse"); - root["palette"] = settings.palette.id; + root["palette"] = settings.palette.name; } StateUpdateResult update(JsonObject& root, FireModeSettings& settings) { @@ -52,8 +52,8 @@ class FireMode : public AudioLightModeImpl { return StateUpdateResult::CHANGED; } - void selectPalette(const String& paletteId, FireModeSettings& settings) { - settings.palette = _paletteSettingsService->getPalette(paletteId); + void selectPalette(const String& name, FireModeSettings& settings) { + settings.palette = _paletteSettingsService->getPalette(name); settings.palette.colors[0] = 0x000000; } diff --git a/src/PacificaMode.h b/src/PacificaMode.h index 5ca8e0ed..db7e7f7e 100644 --- a/src/PacificaMode.h +++ b/src/PacificaMode.h @@ -31,9 +31,9 @@ class PacificaMode : public AudioLightModeImpl { boolean _refresh = true; void read(PacificaModeSettings& settings, JsonObject& root) { - root["palette1"] = settings.palette1.id; - root["palette2"] = settings.palette2.id; - root["palette3"] = settings.palette3.id; + root["palette1"] = settings.palette1.name; + root["palette2"] = settings.palette2.name; + root["palette3"] = settings.palette3.name; } StateUpdateResult update(JsonObject& root, PacificaModeSettings& settings) { diff --git a/src/PaletteSettingsService.h b/src/PaletteSettingsService.h index 5256e063..26a1a862 100644 --- a/src/PaletteSettingsService.h +++ b/src/PaletteSettingsService.h @@ -64,15 +64,15 @@ const TProgmemRGBPalette16 PacificaColors3_p FL_PROGMEM = {0x000208, class Palette { public: - String id; + String name; CRGBPalette16 colors; // the default palette - rainbow - Palette() : id("Rainbow"), colors(RainbowColors_p) { + Palette() : name("Rainbow"), colors(RainbowColors_p) { } // custom palettes - Palette(String id, CRGBPalette16 colors) : id(id), colors(colors) { + Palette(String name, CRGBPalette16 colors) : name(name), colors(colors) { } }; @@ -84,7 +84,7 @@ class PaletteSettings { JsonArray palettes = root.createNestedArray("palettes"); for (Palette palette : settings.palettes) { JsonObject paletteRoot = palettes.createNestedObject(); - paletteRoot["id"] = palette.id; + paletteRoot["name"] = palette.name; writePaletteToJson(paletteRoot, &palette.colors); } } @@ -93,10 +93,10 @@ class PaletteSettings { settings.palettes.clear(); if (root["palettes"].is()) { for (JsonObject paletteRoot : root["palettes"].as()) { - String id = paletteRoot["id"]; - if (!id.isEmpty()) { + String name = paletteRoot["name"]; + if (!name.isEmpty()) { Palette palette = Palette(); - palette.id = id; + palette.name = name; updatePaletteFromJson(paletteRoot, &palette.colors, CRGB::Black); settings.palettes.push_back(palette); } @@ -144,12 +144,12 @@ class PaletteSettingsService : public StatefulService { } void refreshPalette(Palette& palette) { - palette = getPalette(palette.id); + palette = getPalette(palette.name); } - Palette getPalette(const String& paletteId) { + Palette getPalette(const String& name) { for (Palette palette : _state.palettes) { - if (palette.id == paletteId) { + if (palette.name == name) { return palette; } } From 6d1fe5bede29c9c9f9ab3041757507505eef819c Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sun, 29 Nov 2020 22:53:34 +0000 Subject: [PATCH 51/52] add scrollable to tabs modify menu to fix tab srolling issue --- interface/src/ap/AccessPoint.tsx | 2 +- interface/src/components/MenuAppBar.tsx | 10 +++++----- interface/src/mqtt/Mqtt.tsx | 2 +- interface/src/ntp/NetworkTime.tsx | 2 +- interface/src/project/LightsProject.tsx | 2 +- interface/src/security/Security.tsx | 2 +- interface/src/system/System.tsx | 2 +- interface/src/wifi/WiFiConnection.tsx | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/interface/src/ap/AccessPoint.tsx b/interface/src/ap/AccessPoint.tsx index eba011e2..93b89cdf 100644 --- a/interface/src/ap/AccessPoint.tsx +++ b/interface/src/ap/AccessPoint.tsx @@ -21,7 +21,7 @@ class AccessPoint extends Component { const { authenticatedContext } = this.props; return ( - + diff --git a/interface/src/components/MenuAppBar.tsx b/interface/src/components/MenuAppBar.tsx index dfe5f227..bf911532 100644 --- a/interface/src/components/MenuAppBar.tsx +++ b/interface/src/components/MenuAppBar.tsx @@ -25,9 +25,6 @@ import { withFeatures, WithFeaturesProps } from '../features/FeaturesContext'; const drawerWidth = 290; const styles = (theme: Theme) => createStyles({ - root: { - display: 'flex', - }, drawer: { [theme.breakpoints.up('md')]: { width: drawerWidth, @@ -64,7 +61,10 @@ const styles = (theme: Theme) => createStyles({ width: drawerWidth, }, content: { - flexGrow: 1 + flexGrow: 1, + [theme.breakpoints.up('md')]: { + marginLeft: drawerWidth, + }, }, authMenu: { zIndex: theme.zIndex.tooltip, @@ -219,7 +219,7 @@ class MenuAppBar extends React.Component { ); return ( -
+
{ const { authenticatedContext } = this.props; return ( - + diff --git a/interface/src/ntp/NetworkTime.tsx b/interface/src/ntp/NetworkTime.tsx index ebefb6e4..5cdbd29d 100644 --- a/interface/src/ntp/NetworkTime.tsx +++ b/interface/src/ntp/NetworkTime.tsx @@ -21,7 +21,7 @@ class NetworkTime extends Component { const { authenticatedContext } = this.props; return ( - + diff --git a/interface/src/project/LightsProject.tsx b/interface/src/project/LightsProject.tsx index 83d369f4..3dee5e28 100644 --- a/interface/src/project/LightsProject.tsx +++ b/interface/src/project/LightsProject.tsx @@ -21,7 +21,7 @@ class LightsProject extends Component { render() { return ( - + diff --git a/interface/src/security/Security.tsx b/interface/src/security/Security.tsx index 4e99769b..a12f81a0 100644 --- a/interface/src/security/Security.tsx +++ b/interface/src/security/Security.tsx @@ -20,7 +20,7 @@ class Security extends Component { render() { return ( - + diff --git a/interface/src/system/System.tsx b/interface/src/system/System.tsx index 671d5e07..d3e5dd45 100644 --- a/interface/src/system/System.tsx +++ b/interface/src/system/System.tsx @@ -24,7 +24,7 @@ class System extends Component { const { authenticatedContext, features } = this.props; return ( - + {features.ota && ( diff --git a/interface/src/wifi/WiFiConnection.tsx b/interface/src/wifi/WiFiConnection.tsx index c6cef06f..b2beb3e0 100644 --- a/interface/src/wifi/WiFiConnection.tsx +++ b/interface/src/wifi/WiFiConnection.tsx @@ -42,7 +42,7 @@ class WiFiConnection extends Component - + From 2da9b1762e29c9e03085b330c38924025caeea40 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Mon, 30 Nov 2020 21:27:31 +0000 Subject: [PATCH 52/52] adjust mode transfer list format --- interface/src/project/components/ModeTransferList.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/project/components/ModeTransferList.tsx b/interface/src/project/components/ModeTransferList.tsx index c61e1367..b5d79279 100644 --- a/interface/src/project/components/ModeTransferList.tsx +++ b/interface/src/project/components/ModeTransferList.tsx @@ -13,7 +13,8 @@ import { ROTATE_AUDIO_LIGHT_MODES, AudioLightModeType, AUDIO_LIGHT_MODE_METADATA const useStyles = makeStyles((theme) => ({ paper: { - width: 160, + marginTop: theme.spacing(2), + width: 170, height: 230, overflow: 'auto', },