From 94d7cde368b9fecf5c1ef1e35b4d2d5ca426d021 Mon Sep 17 00:00:00 2001 From: "flo.laschober@gmail.com" Date: Sun, 18 Apr 2021 23:26:38 +0200 Subject: [PATCH 01/13] First draft for dynamic configuration parsing from json files --- data/BaseConfig.json | 80 ++++++ .../Config/Setup/clock-only/Configuration.h | 11 + .../Modules/ConfigManager/inc/ConfigManager.h | 93 +++++++ .../ConfigManager/src/ConfigManager.cpp | 247 ++++++++++++++++++ lib/LED_clock/library.json | 1 + platformio.ini | 3 +- src/main.cpp | 4 + 7 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 data/BaseConfig.json create mode 100644 lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h create mode 100644 lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp diff --git a/data/BaseConfig.json b/data/BaseConfig.json new file mode 100644 index 0000000..aac1d86 --- /dev/null +++ b/data/BaseConfig.json @@ -0,0 +1,80 @@ +{ + "ENABLE_WIFI": true, + "WIFI": { + "ENABLE_BLYNK": true, + "BLYNK": { + "AUTH_TOKEN": "AUTH_TOKEN_GOES_HERE", + "SERVER": "blynk-cloud.com" + }, + "OTA_UPLOAD": true, + "HOST_NAME": "LED-Shelf", + "NUM_RETRIES": 50, + "SMART_CONFIG": true, + "WIFI_SSID": "SET_YOUR_SSID_HERE", + "WIFI_PW": "SET_YOUR_PASSWORD_HERE" + }, + "COLORS": + { + "DEFAULT_HOUR": 16777215, + "DEFAULT_MINUTE": 15794175, + "DEFAULT_INTERNAL": 255, + "DEFAULT_SEPARATION_DOT": 255, + "OTA_UPDATE": 16753920, + "WIFI_CONNECTING": 255, + "WIFI_CONNECTION_SUCCESSFUL": 32768, + "WIFI_SMART_CONFIG": 16776960, + "ERROR": 16711680 + }, + "NTP_SERVER": "pool.ntp.org", + "TIMEZONE_INFO": "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00", + "TIME_SYNC_INTERVAL": 1800, + "TIMER_FLASH_TIME": false, + "TIMER_FLASH_COUNT": 10, + "ALARM_NOTIFICATION_PERIOD": 600, + "NOTIFICATION_BRIGHTNESS": 125, + "TIME_UPDATE_INTERVAL": 500, + "LED": { + "LED_DATA_PIN": 21, + "NUM_SEGMENTS": 32, + "NUM_LEDS_PER_SEGMENT": 12, + "APPEND_DOWN_LIGHTERS": true, + "ADDITIONAL_LEDS": 12, + "DOWNLIGHT_LED_DATA_PIN": 22, + "NUM_DISPLAYS": 7, + "DISPLAY_0_AT_MIDNIGHT": false, + "DISPLAY_SWITCH_OFF_AT_0": false, + "USE_24_HOUR_FORMAT": false, + "NUM_SEGMENTS_PROGRESS": 16, + "LOADING_ANIMATION_DURATION": 3000, + "BRIGHTNESS_INTERPOLATION": 3000, + "DISPLAY_FOR_SEPARATION_DOT": 3, + "ANIMATION_TARGET_FPS": 60, + "ANIMATION_AFTERGLOW": 0.2, + "DOT_FLASH_SPEED": 2000, + "DOT_FLASH_INTERVAL": 4000, + "NUM_SEPARATION_DOTS": 2, + "DISPLAY_IDS": + { + "HIGHER_DIGIT_HOUR": 0, + "FIRST_INTERMEDIATE": 1, + "LOWER_DIGIT_HOUR": 2, + "SECOND_INTERMEDIATE": 3, + "HIGHER_DIGIT_MINUTE": 4, + "THIRD_INTERMEDIATE": 5, + "LOWER_DIGIT_MINUTE": 6 + } + }, + "ENABLE_LIGHT_SENSOR": false, + "LIGHT_SENSOR": { + "LIGHT_SENSOR_PIN": 34, + "LIGHT_SENSOR_AVERAGE": 15, + "LIGHT_SENSOR_MEDIAN_WIDTH": 5, + "LIGHT_SENSOR_READ_DELAY": 500, + "LIGHT_SENSOR_MIN": 0, + "LIGHT_SENSOR_MAX": 4095, + "LIGHT_SENSOR_SENSITIVITY": 100 + }, + "TIME_MANAGER_DEMO_MODE": false, + "DIGIT_ANIMATION_SPEED": 900, + "FASTLED_SAFE_DELAY_MS": 20 +} \ No newline at end of file diff --git a/lib/LED_clock/Config/Setup/clock-only/Configuration.h b/lib/LED_clock/Config/Setup/clock-only/Configuration.h index 4f6a255..5c87a88 100644 --- a/lib/LED_clock/Config/Setup/clock-only/Configuration.h +++ b/lib/LED_clock/Config/Setup/clock-only/Configuration.h @@ -9,6 +9,17 @@ #ifndef _CONFIGURATIONS_H_ #define _CONFIGURATIONS_H_ +//Place all new configs here: +#define BASE_CONFIG_FILE_NAME "/BaseConfig.json" +#define JSON_BUFFER_CHUNK_SIZE 1024 +#define JSON_BUFFER_INITIAL_SIZE 200 +#define JSON_BUFFER_BASE_CONFIG 3072 +#define MAX_JSON_BUFFER 5000 + + + +// Legacy configs: + /** * \addtogroup MainConfiguration * \brief Main configuration settings diff --git a/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h new file mode 100644 index 0000000..f27c76d --- /dev/null +++ b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h @@ -0,0 +1,93 @@ +/** + * \file ConfigManager.h + * \author Florian Laschober + * \brief Config manager class definition + */ + +#ifndef __CONFIG_MANAGER_H_ +#define __CONFIG_MANAGER_H_ + +#include +#include +#include +#include +#include "Configuration.h" + +/** + * \brief The config manager is responsible to handle the json configuration files. + * It stores all config values in variables to be able to use them in the code + */ +class ConfigManager +{ +public: + typedef void (*propertyChangedCallback)(void); +private: + struct changedEventCallback { + propertyChangedCallback callback; + void* property; + uint8_t configClass; + }; + static ConfigManager* instance; + LinkedList eventCallbacks; + static char UNDEFINED[]; + + ConfigManager(); + DynamicJsonDocument* deserializeDynamically(fs::File &input, uint64_t initialDocSize); + + template + bool parseValue(jsonObjectType* root, String key, valueType* value, uint8_t ConfigClass); + + template + bool parseValue(jsonObjectType* root, String key, valueType* value); + void checkCallbacks(void* Property, uint8_t ConfigClass); +public: + struct WIFI_config { + bool enabled; + bool blynk_enabled; + bool ota_enabled; + bool smart_config_enabled; + uint16_t connection_retries; + char* blynk_token; + char* blynk_server; + char* host_name; + char* ssid; + char* pw; + }; + + struct Color_config { + uint32_t default_hour; + uint32_t default_minute; + uint32_t default_internal; + uint32_t default_separation_dot; + uint32_t ota_update; + uint32_t wifi_connecting; + uint32_t wifi_connection_successful; + uint32_t wifi_smart_config; + uint32_t error; + }; + + + enum ConfigChanges {C_WIFI = 0x01, C_COLORS = 0x02, C_LED = 0x04, C_LIGHT_SENSOR = 0x08, C_TIME = 0x10, C_OTHER = 0x20}; + WIFI_config WIFI; + Color_config Colors; + + /** + * \brief Destroys the Config Manager object and cause #ConfigManager::getInstance to create a new object the next time it is called + */ + ~ConfigManager(); + + /** + * \brief Get the instance of the ConfigManager object or create it if it was not yet instantiated. + * + * \return ConfigManager* returns the address to the ConfigManager object + */ + static ConfigManager* getInstance(); + + bool loadConfigFromMemory(); + void registerOnChangedCallback(void* Property, propertyChangedCallback Callback); + void registerOnChangedCallback(uint8_t ConfigClass, propertyChangedCallback Callback); + void unregisterOnChangedCallback(propertyChangedCallback Callback, void* Property = nullptr); +}; + + +#endif diff --git a/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp b/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp new file mode 100644 index 0000000..08bf2b6 --- /dev/null +++ b/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp @@ -0,0 +1,247 @@ +/** + * \file ConfigManager.cpp + * \author Florian Laschober + * \brief Implementation of the ConfigManager class member functions + */ + + +#include "ConfigManager.h" + +ConfigManager* ConfigManager::instance = nullptr; +char ConfigManager::UNDEFINED[] = "UNDEFINED"; + +ConfigManager::ConfigManager() +{ + if(!SPIFFS.begin(true)) + { + Serial.println("Filesystem mount failed aborting..."); + abort(); + } + + Serial.printf("Filesystem mount successful; %d out of %d bytes used\n\r", SPIFFS.usedBytes(), SPIFFS.totalBytes()); + + // writing some default values: + WIFI.enabled = false; + WIFI.blynk_enabled = false; + WIFI.ota_enabled = false; + WIFI.smart_config_enabled = false; + WIFI.connection_retries = 0; + WIFI.blynk_token = UNDEFINED; + WIFI.blynk_server = UNDEFINED; + WIFI.host_name = UNDEFINED; + WIFI.ssid = UNDEFINED; + WIFI.pw = UNDEFINED; + + loadConfigFromMemory(); +} + +ConfigManager::~ConfigManager() +{ + instance = nullptr; +} + +ConfigManager* ConfigManager::getInstance() +{ + if(instance == nullptr) + { + instance = new ConfigManager(); + } + return instance; +} + +DynamicJsonDocument* ConfigManager::deserializeDynamically(fs::File &input, uint64_t initialDocSize) +{ + //try to initialize a json document big enough to fit all the data + uint64_t DocumentSize = initialDocSize; + DynamicJsonDocument* doc; + bool successful = false; + + do + { + doc = new DynamicJsonDocument(DocumentSize); + DeserializationError error = deserializeJson(*doc, input); + if (error) + { + delete doc; + if (error.code() == DeserializationError::NoMemory) + { + DocumentSize += JSON_BUFFER_CHUNK_SIZE; + } + else + { + Serial.println("E: json:" + String(error.c_str())); + break; + } + } + else + { + successful = true; + break; //no error detected -> memory size is okay + } + } while (DocumentSize < MAX_JSON_BUFFER); + + if(successful == true) + { + return doc; + } + return nullptr; +} + +void ConfigManager::checkCallbacks(void* property, uint8_t ConfigClass) +{ + for (int i = 0; i < eventCallbacks.size(); i++) + { + if(eventCallbacks[i]->property != nullptr) + { + if(eventCallbacks[i]->property == property) + { + eventCallbacks[i]->callback(); + } + } + else + { + if((eventCallbacks[i]->configClass & ConfigClass) != 0x00) + { + eventCallbacks[i]->callback(); + } + } + } +} + +template +bool ConfigManager::parseValue(jsonObjectType* root, String key, valueType* value) +{ + bool successful = false; + JsonVariant jvalue = (*root)[key]; + if (!jvalue.isNull()) + { + if(!jvalue.is()) + { + Serial.println("E: " + key + " is not the right type"); + } + else + { + *value = jvalue.as(); + successful = true; + } + } + return successful; +} + +template +bool ConfigManager::parseValue(jsonObjectType* root, String key, valueType* value, uint8_t ConfigClass) +{ + bool successful = false; + JsonVariant jvalue = (*root)[key]; + if (!jvalue.isNull()) + { + if(!jvalue.is()) + { + Serial.println("E: " + key + " is not the right type"); + } + else + { + valueType newValue = (valueType)jvalue.as(); + if(*value != newValue) + { + *value = newValue; + checkCallbacks(value, ConfigClass); + } + successful = true; + } + } + return successful; +} + +bool ConfigManager::loadConfigFromMemory() +{ + #define ERROR_HANDLING(x, key) if(x == false) { Serial.printf("E: Key %s is mandatory", key); delete doc; return false;} + bool loadOK = false; + if(SPIFFS.exists(BASE_CONFIG_FILE_NAME)) + { + File jsonFile = SPIFFS.open(BASE_CONFIG_FILE_NAME, "r"); + if (!jsonFile) + { + Serial.println("E: Open config file failed"); + } + else + { + DynamicJsonDocument* doc = deserializeDynamically(jsonFile, JSON_BUFFER_BASE_CONFIG); + if(doc != nullptr) + { + ERROR_HANDLING(parseValue(doc, "ENABLE_WIFI", &(WIFI.enabled), C_WIFI), "ENABLE_WIFI") + + if(WIFI.enabled == true) + { + JsonObject WIFI_object; + ERROR_HANDLING(parseValue(doc, "WIFI", &WIFI_object), "WIFI") + + ERROR_HANDLING(parseValue(&WIFI_object, "ENABLE_BLYNK", &(WIFI.blynk_enabled), C_WIFI), "ENABLE_BLYNK") + + if(WIFI.blynk_enabled == true) + { + JsonObject blynk_object; + ERROR_HANDLING(parseValue(&WIFI_object, "BLYNK", &blynk_object), "BLYNK") + ERROR_HANDLING(parseValue(&blynk_object, "AUTH_TOKEN", &(WIFI.blynk_token), C_WIFI), "AUTH_TOKEN") + ERROR_HANDLING(parseValue(&blynk_object, "SERVER", &(WIFI.blynk_server), C_WIFI), "SERVER") + } + + ERROR_HANDLING(parseValue(&WIFI_object, "OTA_UPLOAD", &(WIFI.ota_enabled), C_WIFI), "OTA_UPLOAD") + ERROR_HANDLING(parseValue(&WIFI_object, "NUM_RETRIES", &(WIFI.connection_retries), C_WIFI), "NUM_RETRIES") + ERROR_HANDLING(parseValue(&WIFI_object, "SMART_CONFIG", &(WIFI.smart_config_enabled), C_WIFI), "SMART_CONFIG") + ERROR_HANDLING(parseValue(&WIFI_object, "HOST_NAME", &(WIFI.host_name), C_WIFI), "HOST_NAME") + ERROR_HANDLING(parseValue(&WIFI_object, "WIFI_SSID", &(WIFI.ssid), C_WIFI), "WIFI_SSID") + ERROR_HANDLING(parseValue(&WIFI_object, "WIFI_PW", &(WIFI.pw), C_WIFI), "WIFI_PW") + } + { // Color config + JsonObject colors_object; + ERROR_HANDLING(parseValue(doc, "COLORS", &colors_object), "COLORS") + ERROR_HANDLING(parseValue(&colors_object, "DEFAULT_HOUR", &(Colors.default_hour), C_COLORS), "DEFAULT_HOUR") + ERROR_HANDLING(parseValue(&colors_object, "DEFAULT_MINUTE", &(Colors.default_minute), C_COLORS), "DEFAULT_MINUTE") + ERROR_HANDLING(parseValue(&colors_object, "DEFAULT_INTERNAL", &(Colors.default_internal), C_COLORS), "DEFAULT_INTERNAL") + ERROR_HANDLING(parseValue(&colors_object, "DEFAULT_SEPARATION_DOT", &(Colors.default_separation_dot), C_COLORS), "DEFAULT_SEPARATION_DOT") + ERROR_HANDLING(parseValue(&colors_object, "OTA_UPDATE", &(Colors.ota_update), C_COLORS), "OTA_UPDATE") + ERROR_HANDLING(parseValue(&colors_object, "WIFI_CONNECTING", &(Colors.wifi_connecting), C_COLORS), "WIFI_CONNECTING") + ERROR_HANDLING(parseValue(&colors_object, "WIFI_CONNECTION_SUCCESSFUL", &(Colors.wifi_connection_successful), C_COLORS), "WIFI_CONNECTION_SUCCESSFUL") + ERROR_HANDLING(parseValue(&colors_object, "WIFI_SMART_CONFIG", &(Colors.wifi_smart_config), C_COLORS), "WIFI_SMART_CONFIG") + ERROR_HANDLING(parseValue(&colors_object, "ERROR", &(Colors.error), C_COLORS), "ERROR") + } + + Serial.println("Config parsed successfully"); + delete doc; //delete the dynamically allocated document after all values have been stored in variables + loadOK = true; + } + jsonFile.close(); + } + } + else + { + Serial.printf("E: Config file %s does not exist on the filesystem yet. Please create it first\n\r", BASE_CONFIG_FILE_NAME); + loadOK = false; + } + return loadOK; +} + +void ConfigManager::registerOnChangedCallback(void* Property, propertyChangedCallback Callback) +{ + changedEventCallback* newCallback = new changedEventCallback{.callback = Callback, .property = Property, .configClass = 0}; + eventCallbacks.add(newCallback); +} + +void ConfigManager::registerOnChangedCallback(uint8_t ConfigClass, propertyChangedCallback Callback) +{ + changedEventCallback* newCallback = new changedEventCallback{.callback = Callback, .property = nullptr, .configClass = ConfigClass}; + eventCallbacks.add(newCallback); +} + +void ConfigManager::unregisterOnChangedCallback(propertyChangedCallback Callback, void* Property) +{ + for (int i = 0; i < eventCallbacks.size(); i++) + { + if(eventCallbacks[i]->callback == Callback && (Property == nullptr || eventCallbacks[i]->property == Property)) + { + eventCallbacks.remove(i); + return; + } + } +} \ No newline at end of file diff --git a/lib/LED_clock/library.json b/lib/LED_clock/library.json index 7c94d89..29bdc2b 100644 --- a/lib/LED_clock/library.json +++ b/lib/LED_clock/library.json @@ -23,6 +23,7 @@ "-I Modules/Misc/inc", "-I Modules/SevenSegment/inc", "-I Modules/TimeManager/inc", + "-I Modules/ConfigManager/inc", "-I Config/Setup/clock-only", "-I Config/Blynk/default", "-I Config/Animations/clock-only", diff --git a/platformio.ini b/platformio.ini index 78de737..dddc4a0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,10 +14,11 @@ board = nodemcu-32s framework = arduino upload_speed = 921600 monitor_speed = 115200 -lib_deps = +lib_deps = fastled/FastLED@^3.4.0 ivanseidel/LinkedList@0.0.0-alpha+sha.dac3874d28 blynkkk/Blynk@^0.6.7 + bblanchon/ArduinoJson@^6.17.3 ; Uncomment these to use OTA as a flash method. This only works if the ESp already has code running on it where OTA update is enabled. ; upload_protocol = espota diff --git a/src/main.cpp b/src/main.cpp index 425a93c..52f1079 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include #include "DisplayManager.h" #include "ClockState.h" +#include "ConfigManager.h" #if RUN_WITHOUT_WIFI == false #include "WiFi.h" @@ -90,6 +91,8 @@ void setup() ShelfDisplays->setInternalLEDColor(INTERNAL_COLOR); ShelfDisplays->setDotLEDColor(SEPERATION_DOT_COLOR); + ConfigManager::getInstance(); + #if RUN_WITHOUT_WIFI == false wifiSetup(); #endif @@ -268,6 +271,7 @@ void TimerDone() else // U_SPIFFS { type = "filesystem"; + SPIFFS.end(); } // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() Serial.println("Start updating " + type); From 2851981a33d219bee6259b7ec86bcb01548a8c89 Mon Sep 17 00:00:00 2001 From: "flo.laschober@gmail.com" Date: Wed, 21 Apr 2021 00:27:58 +0200 Subject: [PATCH 02/13] First proof of concept of a configuration UI Proof of concept implementations of #5 and #7 --- .gitignore | 3 + WebConfigUI/.gitignore | 3 + WebConfigUI/babel.config.json | 3 + WebConfigUI/index.html | 12 + WebConfigUI/package.json | 40 ++ WebConfigUI/postcss.config.js | 6 + WebConfigUI/src/App.jsx | 20 + WebConfigUI/src/components/Dialog.jsx | 90 ++++ WebConfigUI/src/components/RadioGroup.jsx | 28 ++ WebConfigUI/src/css/tailwind.css | 28 ++ WebConfigUI/src/main.jsx | 7 + WebConfigUI/src/sites/Navigation.jsx | 36 ++ WebConfigUI/src/sites/Settings.jsx | 11 + WebConfigUI/src/sites/WIFI-setup.jsx | 54 +++ WebConfigUI/tailwind.config.js | 14 + WebConfigUI/webpack.config.js | 50 +++ data/index.html | 15 + .../Animations/clock-only/Animations.cpp | 4 +- .../Config/Animations/default/Animations.cpp | 4 +- .../Animations/diy-machines/Animations.cpp | 4 +- .../default/SegmentTransitions.cpp | 50 +-- lib/LED_clock/Modules/Animator/inc/Animator.h | 6 +- .../Modules/Animator/src/Animator.cpp | 32 +- .../Modules/ConfigManager/inc/ConfigManager.h | 4 +- .../ConfigManager/src/ConfigManager.cpp | 12 +- .../DisplayManager/inc/DisplayManager.h | 4 +- .../DisplayManager/src/DisplayManager.cpp | 14 +- lib/LED_clock/Modules/Misc/inc/LinkedList.h | 421 ++++++++++++++++++ partitions_custom.csv | 6 + platformio.ini | 4 +- src/main.cpp | 23 + 31 files changed, 947 insertions(+), 61 deletions(-) create mode 100644 WebConfigUI/.gitignore create mode 100644 WebConfigUI/babel.config.json create mode 100644 WebConfigUI/index.html create mode 100644 WebConfigUI/package.json create mode 100644 WebConfigUI/postcss.config.js create mode 100644 WebConfigUI/src/App.jsx create mode 100644 WebConfigUI/src/components/Dialog.jsx create mode 100644 WebConfigUI/src/components/RadioGroup.jsx create mode 100644 WebConfigUI/src/css/tailwind.css create mode 100644 WebConfigUI/src/main.jsx create mode 100644 WebConfigUI/src/sites/Navigation.jsx create mode 100644 WebConfigUI/src/sites/Settings.jsx create mode 100644 WebConfigUI/src/sites/WIFI-setup.jsx create mode 100644 WebConfigUI/tailwind.config.js create mode 100644 WebConfigUI/webpack.config.js create mode 100644 data/index.html create mode 100644 lib/LED_clock/Modules/Misc/inc/LinkedList.h create mode 100644 partitions_custom.csv diff --git a/.gitignore b/.gitignore index d600099..24ac2a6 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ doc/_templates doc/doxygen doc/doxyapi +#compressed files in the data folder +data/*.gz + # Created by https://www.gitignore.io/api/platformio # Edit at https://www.gitignore.io/?templates=platformio diff --git a/WebConfigUI/.gitignore b/WebConfigUI/.gitignore new file mode 100644 index 0000000..a7ca8fd --- /dev/null +++ b/WebConfigUI/.gitignore @@ -0,0 +1,3 @@ +node_modules/** +build/** +package-lock.json \ No newline at end of file diff --git a/WebConfigUI/babel.config.json b/WebConfigUI/babel.config.json new file mode 100644 index 0000000..86ef210 --- /dev/null +++ b/WebConfigUI/babel.config.json @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-env", "@babel/preset-react"] +} diff --git a/WebConfigUI/index.html b/WebConfigUI/index.html new file mode 100644 index 0000000..77cf4f6 --- /dev/null +++ b/WebConfigUI/index.html @@ -0,0 +1,12 @@ + + + + + + LED Pixel Clock Configuration + + +
+ + + diff --git a/WebConfigUI/package.json b/WebConfigUI/package.json new file mode 100644 index 0000000..8e7f38f --- /dev/null +++ b/WebConfigUI/package.json @@ -0,0 +1,40 @@ +{ + "name": "web-config-ui", + "version": "1.0.0", + "description": "", + "main": "src/main.jsx", + "scripts": { + "dev": "vite", + "build": "webpack --mode production", + "start": "webpack serve --mode development" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/core": "^7.13.15", + "@babel/preset-env": "^7.13.15", + "@babel/preset-react": "^7.13.13", + "@headlessui/react": "^1.0.0", + "@tailwindcss/forms": "^0.3.2", + "autoprefixer": "^10.2.5", + "babel-loader": "^8.2.2", + "brotli-webpack-plugin": "^1.1.0", + "compression-webpack-plugin": "^7.1.2", + "css-loader": "^5.2.4", + "html-webpack-plugin": "^5.3.1", + "postcss": "^8.2.10", + "postcss-loader": "^5.2.0", + "postcss-preset-env": "^6.7.0", + "style-loader": "^2.0.0", + "tailwindcss": "^2.1.1", + "vite": "^2.2.1", + "webpack": "^5.34.0", + "webpack-cli": "^4.6.0", + "webpack-dev-server": "^3.11.2" + }, + "dependencies": { + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-router-dom": "^5.2.0" + } +} diff --git a/WebConfigUI/postcss.config.js b/WebConfigUI/postcss.config.js new file mode 100644 index 0000000..a288142 --- /dev/null +++ b/WebConfigUI/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/WebConfigUI/src/App.jsx b/WebConfigUI/src/App.jsx new file mode 100644 index 0000000..5c2257e --- /dev/null +++ b/WebConfigUI/src/App.jsx @@ -0,0 +1,20 @@ +import React from "react"; +import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; + +import Navigation from "./sites/Navigation"; +import WIFI_setup from "./sites/WIFI-setup"; +import Settings from "./sites/Settings"; + +export default function App() { + return ( +
+ + + } /> + } /> + } /> + + +
+ ); +} diff --git a/WebConfigUI/src/components/Dialog.jsx b/WebConfigUI/src/components/Dialog.jsx new file mode 100644 index 0000000..9db1fd5 --- /dev/null +++ b/WebConfigUI/src/components/Dialog.jsx @@ -0,0 +1,90 @@ +import { Dialog, Transition } from "@headlessui/react"; +import { Fragment, useEffect, useRef, useState } from "react"; +import { useTimeoutFn } from "react-use"; + +export default function MyModal() { + const [open, setOpen] = useState(false); + const cancelButtonRef = useRef(); + + // This code makes the demo automatically show on initial render + let [, clear, resetIsShowing] = useTimeoutFn(() => setOpen(true), 750); + useEffect(() => { + clear(); + setOpen(true); + }, []); + + function closeModal() { + setOpen(false); + resetIsShowing(); + } + + return ( + + +
+ + + + + {/* This element is to trick the browser into centering the modal contents. */} + + +
+ + Payment successful + +
+

+ Your payment has been successfully submitted. We’ve sent your + an email with all of the details of your order. +

+
+ +
+ +
+
+
+
+
+
+ ); +} diff --git a/WebConfigUI/src/components/RadioGroup.jsx b/WebConfigUI/src/components/RadioGroup.jsx new file mode 100644 index 0000000..27d0017 --- /dev/null +++ b/WebConfigUI/src/components/RadioGroup.jsx @@ -0,0 +1,28 @@ +import React from "react"; +import { useState } from "react"; +import { RadioGroup } from "@headlessui/react"; + +export default function MyRadioGroup() { + let [plan, setPlan] = useState("startup"); + + return ( + + Plan + + {({ checked }) => ( + Startup + )} + + + {({ checked }) => ( + Business + )} + + + {({ checked }) => ( + Enterprise + )} + + + ); +} diff --git a/WebConfigUI/src/css/tailwind.css b/WebConfigUI/src/css/tailwind.css new file mode 100644 index 0000000..fa52b09 --- /dev/null +++ b/WebConfigUI/src/css/tailwind.css @@ -0,0 +1,28 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + + +@layer base { + [type='text'], + [type='email'], + [type='url'], + [type='password'], + [type='number'], + [type='date'], + [type='datetime-local'], + [type='month'], + [type='search'], + [type='tel'], + [type='time'], + [type='week'], + [multiple], + textarea, + select { + @apply w-full border-gray-300 rounded-lg shadow-sm focus:border-indigo-500 focus:ring-indigo-500 transform transition; + } + [type='checkbox'], + [type='radio'] { + @apply rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 transform transition; + } +} diff --git a/WebConfigUI/src/main.jsx b/WebConfigUI/src/main.jsx new file mode 100644 index 0000000..bb314c3 --- /dev/null +++ b/WebConfigUI/src/main.jsx @@ -0,0 +1,7 @@ +import React from "react"; +import ReactDOM from "react-dom"; + +import App from "./App"; +import "./css/tailwind.css"; + +ReactDOM.render(, document.getElementById("root")); diff --git a/WebConfigUI/src/sites/Navigation.jsx b/WebConfigUI/src/sites/Navigation.jsx new file mode 100644 index 0000000..bf684a7 --- /dev/null +++ b/WebConfigUI/src/sites/Navigation.jsx @@ -0,0 +1,36 @@ +import { Link, withRouter } from "react-router-dom"; +import { Menu } from "@headlessui/react"; +import React from "react"; + +const pages = [ + {link: "/WIFI", label: "WIFI Setup"}, + {link: "/Settings", label: "Settings"}, +]; + +function NavigationMenu(props) { + + return ( +
+

LED Pixel Clock

+
+ + + {pages.map((page) =>( + + {({ active }) => ( + + {page.label} + + )} + + ))} + + +
+
+ ); +} + +export default withRouter(NavigationMenu); diff --git a/WebConfigUI/src/sites/Settings.jsx b/WebConfigUI/src/sites/Settings.jsx new file mode 100644 index 0000000..274b320 --- /dev/null +++ b/WebConfigUI/src/sites/Settings.jsx @@ -0,0 +1,11 @@ +import React from "react"; + +const Settings = () => { + return ( +
+ +
+ ); +} + +export default Settings; \ No newline at end of file diff --git a/WebConfigUI/src/sites/WIFI-setup.jsx b/WebConfigUI/src/sites/WIFI-setup.jsx new file mode 100644 index 0000000..a7f6321 --- /dev/null +++ b/WebConfigUI/src/sites/WIFI-setup.jsx @@ -0,0 +1,54 @@ +import React, { useState } from 'react'; + +const WIFI_new = () => { + const [SSID, setSSID] = useState(""); + + const handleSSIDChanged = (object) => { + setSSID(object.target.value); + } + + const handleSubmit = (event) => { + event.preventDefault(); + console.log("SSID: " + SSID); + } + + return ( +
+
+
+

Connect to your WIFI

+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ + +
+
+ +
+
+
+
+ ) +} + +export default WIFI_new; \ No newline at end of file diff --git a/WebConfigUI/tailwind.config.js b/WebConfigUI/tailwind.config.js new file mode 100644 index 0000000..243114f --- /dev/null +++ b/WebConfigUI/tailwind.config.js @@ -0,0 +1,14 @@ +module.exports = { + purge: { + enabled: true, + content: ["./src/**/*.jsx", "./index.html"], + }, + darkMode: false, // or 'media' or 'class' + theme: {}, + variants: { + extend: { + backgroundColor: ["active"], + }, + }, + plugins: [require('@tailwindcss/forms')], +}; diff --git a/WebConfigUI/webpack.config.js b/WebConfigUI/webpack.config.js new file mode 100644 index 0000000..207b11a --- /dev/null +++ b/WebConfigUI/webpack.config.js @@ -0,0 +1,50 @@ +const path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const CompressionPlugin = require("compression-webpack-plugin"); + +module.exports = { + mode: 'production',//production; development + module: { + rules: [ + { + test: /\.css$/i, + use: ['style-loader', 'css-loader', 'postcss-loader'] + }, + { + test: /\.jsx$/, + exclude: /node_modules/, + use: ["babel-loader"] + }, + { + test: /\.js$/, + exclude: /node_modules/, + use: ["babel-loader"] + } + ] + }, + optimization: { + // splitChunks: { chunks: "all" }, + minimize: true + }, + entry: { + index: path.resolve(__dirname, "src", "main.jsx") + }, + output: { + path: path.resolve(__dirname, "build"), + publicPath: '/' + }, + devServer: { + historyApiFallback: true, + }, + plugins: [ + new CompressionPlugin({ + test: /\.(js|jsx|css|svg)$/ + }), + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, "index.html") + }) + ], + resolve: { + extensions: ['', '.js', '.jsx'], + } +}; diff --git a/data/index.html b/data/index.html new file mode 100644 index 0000000..820d4d4 --- /dev/null +++ b/data/index.html @@ -0,0 +1,15 @@ + + + + + + + LED Pixel Clock Configuration + + + + +
+ + + \ No newline at end of file diff --git a/lib/LED_clock/Config/Animations/clock-only/Animations.cpp b/lib/LED_clock/Config/Animations/clock-only/Animations.cpp index a88b201..f1c22d8 100644 --- a/lib/LED_clock/Config/Animations/clock-only/Animations.cpp +++ b/lib/LED_clock/Config/Animations/clock-only/Animations.cpp @@ -104,7 +104,7 @@ Animator::ComplexAmination* InitIndefiniteLoadingAnimation(uint16_t totalAnimati step18->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToBottom, NO_ANIMATION}; step18->easingEffects = new EasingBase*[LENGTH] {NO_EASING, NO_EASING}; - LinkedList* allSteps = new LinkedList(); + DynamicList* allSteps = new DynamicList(); allSteps->add(step0); allSteps->add(step1); allSteps->add(step2); @@ -209,7 +209,7 @@ Animator::ComplexAmination* InitLoadingProgressAnimation(uint16_t totalAnimation step17->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateInToBottom}; step17->easingEffects = new EasingBase*[LENGTH] {NO_EASING}; - LinkedList* allSteps = new LinkedList(); + DynamicList* allSteps = new DynamicList(); allSteps->add(step0); allSteps->add(step1); allSteps->add(step2); diff --git a/lib/LED_clock/Config/Animations/default/Animations.cpp b/lib/LED_clock/Config/Animations/default/Animations.cpp index 2c05cd4..3fb1f42 100644 --- a/lib/LED_clock/Config/Animations/default/Animations.cpp +++ b/lib/LED_clock/Config/Animations/default/Animations.cpp @@ -95,7 +95,7 @@ Animator::ComplexAmination* InitIndefiniteLoadingAnimation(uint16_t totalAnimati step16->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToBottom, NO_ANIMATION}; step16->easingEffects = new EasingBase*[LENGTH] {NO_EASING, NO_EASING}; - LinkedList* allSteps = new LinkedList(); + DynamicList* allSteps = new DynamicList(); allSteps->add(step0); allSteps->add(step1); allSteps->add(step2); @@ -191,7 +191,7 @@ Animator::ComplexAmination* InitLoadingProgressAnimation(uint16_t totalAnimation step15->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateInToBottom}; step15->easingEffects = new EasingBase*[LENGTH] {NO_EASING}; - LinkedList* allSteps = new LinkedList(); + DynamicList* allSteps = new DynamicList(); allSteps->add(step0); allSteps->add(step1); allSteps->add(step2); diff --git a/lib/LED_clock/Config/Animations/diy-machines/Animations.cpp b/lib/LED_clock/Config/Animations/diy-machines/Animations.cpp index 319f3a2..968e9f3 100644 --- a/lib/LED_clock/Config/Animations/diy-machines/Animations.cpp +++ b/lib/LED_clock/Config/Animations/diy-machines/Animations.cpp @@ -55,7 +55,7 @@ Animator::ComplexAmination* InitIndefiniteLoadingAnimation(uint16_t totalAnimati step6->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToBottom, NO_ANIMATION}; step6->easingEffects = new EasingBase*[LENGTH] {NO_EASING, NO_EASING}; - LinkedList* allSteps = new LinkedList(); + DynamicList* allSteps = new DynamicList(); allSteps->add(step0); allSteps->add(step1); allSteps->add(step2); @@ -100,7 +100,7 @@ Animator::ComplexAmination* InitLoadingProgressAnimation(uint16_t totalAnimation step5->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateInToBottom}; step5->easingEffects = new EasingBase*[LENGTH] {NO_EASING}; - LinkedList* allSteps = new LinkedList(); + DynamicList* allSteps = new DynamicList(); allSteps->add(step0); allSteps->add(step1); allSteps->add(step2); diff --git a/lib/LED_clock/Config/Transitions/default/SegmentTransitions.cpp b/lib/LED_clock/Config/Transitions/default/SegmentTransitions.cpp index 99ce914..b26bca6 100644 --- a/lib/LED_clock/Config/Transitions/default/SegmentTransitions.cpp +++ b/lib/LED_clock/Config/Transitions/default/SegmentTransitions.cpp @@ -111,7 +111,7 @@ Animator::ComplexAmination* InitAnimate0to1(uint16_t totalAnimationLength) step1->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToRight, AnimationEffects::AnimateOutToRight}; step1->easingEffects = new EasingBase*[LENGTH] {cubicEaseOut, cubicEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); AnimationStepSequence->add(step1); @@ -139,7 +139,7 @@ Animator::ComplexAmination* InitAnimate1to2(uint16_t totalAnimationLength) step2->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateInToRight, AnimationEffects::AnimateInToLeft}; step2->easingEffects = new EasingBase*[LENGTH] {bounceEaseOut, bounceEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); AnimationStepSequence->add(step1); AnimationStepSequence->add(step2); @@ -160,7 +160,7 @@ Animator::ComplexAmination* InitAnimate2to3(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToBottom, AnimationEffects::AnimateInToTop}; step0->easingEffects = new EasingBase*[LENGTH] {bounceEaseOut, bounceEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); @@ -179,7 +179,7 @@ Animator::ComplexAmination* InitAnimate2to0(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateInToBottom, AnimationEffects::AnimateOutToRight, AnimationEffects::AnimateInToBottom}; step0->easingEffects = new EasingBase*[LENGTH] {bounceEaseOut, cubicEaseInOut, bounceEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); @@ -198,7 +198,7 @@ Animator::ComplexAmination* InitAnimate3to4(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToRight, AnimationEffects::AnimateInToBottom, AnimationEffects::AnimateOutToLeft}; step0->easingEffects = new EasingBase*[LENGTH] {bounceEaseOut, bounceEaseOut, bounceEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); @@ -217,7 +217,7 @@ Animator::ComplexAmination* InitAnimate4to5(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToTop, AnimationEffects::AnimateInToLeft, AnimationEffects::AnimateInToLeft}; step0->easingEffects = new EasingBase*[LENGTH] {bounceEaseOut, bounceEaseOut, bounceEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); @@ -236,7 +236,7 @@ Animator::ComplexAmination* InitAnimate5to6(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[1] {AnimationEffects::AnimateInToTop}; step0->easingEffects = new EasingBase*[LENGTH] {bounceEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); @@ -255,7 +255,7 @@ Animator::ComplexAmination* InitAnimate5to0(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToRight, AnimationEffects::AnimateInToBottom, AnimationEffects::AnimateInToTop}; step0->easingEffects = new EasingBase*[LENGTH] {bounceEaseOut, bounceEaseOut, bounceEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); @@ -282,7 +282,7 @@ Animator::ComplexAmination* InitAnimate6to7(uint16_t totalAnimationLength) step2->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToRight, NO_ANIMATION, NO_ANIMATION}; step2->easingEffects = new EasingBase*[LENGTH] {cubicEaseOut, NO_EASING, NO_EASING}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); AnimationStepSequence->add(step1); AnimationStepSequence->add(step2); @@ -308,7 +308,7 @@ Animator::ComplexAmination* InitAnimate7to8(uint16_t totalAnimationLength) step1->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateInToTop, AnimationEffects::AnimateInToRight}; step1->easingEffects = new EasingBase*[LENGTH] {cubicEaseOut, cubicEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); AnimationStepSequence->add(step1); @@ -328,7 +328,7 @@ Animator::ComplexAmination* InitAnimate8to9(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToBottom}; step0->easingEffects = new EasingBase*[LENGTH] {bounceEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); @@ -347,7 +347,7 @@ Animator::ComplexAmination* InitAnimate9to0(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToLeft, AnimationEffects::AnimateInToBottom}; step0->easingEffects = new EasingBase*[LENGTH] {bounceEaseOut, bounceEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); @@ -366,7 +366,7 @@ Animator::ComplexAmination* InitAnimate1toOFF(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToBottom, AnimationEffects::AnimateOutToTop}; step0->easingEffects = new EasingBase*[LENGTH] {cubicEaseInOut, cubicEaseInOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); @@ -385,7 +385,7 @@ Animator::ComplexAmination* InitAnimateOFFto1(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateInToTop, AnimationEffects::AnimateInToBottom}; step0->easingEffects = new EasingBase*[LENGTH] {cubicEaseInOut, cubicEaseInOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); @@ -404,7 +404,7 @@ Animator::ComplexAmination* InitAnimate9to8(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateInToTop}; step0->easingEffects = new EasingBase*[LENGTH] {bounceEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); @@ -427,7 +427,7 @@ Animator::ComplexAmination* InitAnimate8to7(uint16_t totalAnimationLength) step1->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToRight, NO_ANIMATION, NO_ANIMATION}; step1->easingEffects = new EasingBase*[LENGTH] {cubicEaseOut, cubicEaseOut, NO_EASING}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); AnimationStepSequence->add(step1); @@ -451,7 +451,7 @@ Animator::ComplexAmination* InitAnimate7to6(uint16_t totalAnimationLength) step1->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateInToRight, AnimationEffects::AnimateInToTop, NO_ANIMATION}; step1->easingEffects = new EasingBase*[LENGTH] {cubicEaseOut, cubicEaseOut, NO_EASING}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); AnimationStepSequence->add(step1); @@ -471,7 +471,7 @@ Animator::ComplexAmination* InitAnimate6to5(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToBottom}; step0->easingEffects = new EasingBase*[LENGTH] {bounceEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); @@ -490,7 +490,7 @@ Animator::ComplexAmination* InitAnimate5to4(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToRight, AnimationEffects::AnimateInToBottom, AnimationEffects::AnimateOutToRight}; step0->easingEffects = new EasingBase*[LENGTH] {bounceEaseOut, bounceEaseOut, bounceEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); @@ -509,7 +509,7 @@ Animator::ComplexAmination* InitAnimate4to3(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToTop, AnimationEffects::AnimateInToRight, AnimationEffects::AnimateInToLeft}; step0->easingEffects = new EasingBase*[LENGTH] {bounceEaseOut, bounceEaseOut, bounceEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); @@ -528,7 +528,7 @@ Animator::ComplexAmination* InitAnimate3to2(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToBottom, AnimationEffects::AnimateInToTop}; step0->easingEffects = new EasingBase*[LENGTH] {bounceEaseOut, bounceEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); @@ -555,7 +555,7 @@ Animator::ComplexAmination* InitAnimate2to1(uint16_t totalAnimationLength) step2->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateInToTop, AnimationEffects::AnimateOutToRight}; step2->easingEffects = new EasingBase*[LENGTH] {NO_EASING, NO_EASING}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); AnimationStepSequence->add(step1); AnimationStepSequence->add(step2); @@ -580,7 +580,7 @@ Animator::ComplexAmination* InitAnimate1to0(uint16_t totalAnimationLength) step1->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateInToTop, AnimationEffects::AnimateInToBottom}; step1->easingEffects = new EasingBase*[LENGTH] {bounceEaseOut, bounceEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); AnimationStepSequence->add(step1); @@ -600,7 +600,7 @@ Animator::ComplexAmination* InitAnimate0to9(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToTop, AnimationEffects::AnimateInToRight}; step0->easingEffects = new EasingBase*[LENGTH] {bounceEaseOut, bounceEaseOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); @@ -619,7 +619,7 @@ Animator::ComplexAmination* InitAnimate0to5(uint16_t totalAnimationLength) step0->animationEffects = new AnimatableObject::AnimationFunction[LENGTH] {AnimationEffects::AnimateOutToBottom, AnimationEffects::AnimateInToLeft, AnimationEffects::AnimateOutToBottom}; step0->easingEffects = new EasingBase*[LENGTH] {cubicEaseInOut, cubicEaseInOut, cubicEaseInOut}; - LinkedList* AnimationStepSequence = new LinkedList(); + DynamicList* AnimationStepSequence = new DynamicList(); AnimationStepSequence->add(step0); Animator::ComplexAmination* animation = new Animator::ComplexAmination(); diff --git a/lib/LED_clock/Modules/Animator/inc/Animator.h b/lib/LED_clock/Modules/Animator/inc/Animator.h index 055b80f..8168e60 100644 --- a/lib/LED_clock/Modules/Animator/inc/Animator.h +++ b/lib/LED_clock/Modules/Animator/inc/Animator.h @@ -8,7 +8,7 @@ #define __ANIMATOR_H_ #include -#include +#include "LinkedList.h" #define FASTLED_INTERNAL #include #include "AnimatableObject.h" @@ -51,7 +51,7 @@ class Animator typedef struct { uint8_t animationComplexity; uint16_t LengthPerAnimation; - LinkedList* animations; + DynamicList* animations; } ComplexAmination; struct ComplexAnimationInstance { @@ -64,7 +64,7 @@ class Animator private: static Animator* currentInstance; - LinkedList AnimatableObjects; + DynamicList AnimatableObjects; static unsigned long lastLEDUpdate; int16_t getIndexInList(AnimatableObject* object); diff --git a/lib/LED_clock/Modules/Animator/src/Animator.cpp b/lib/LED_clock/Modules/Animator/src/Animator.cpp index 941789d..07953e0 100644 --- a/lib/LED_clock/Modules/Animator/src/Animator.cpp +++ b/lib/LED_clock/Modules/Animator/src/Animator.cpp @@ -167,6 +167,11 @@ void Animator::startAnimationStep(uint16_t stepindex, ComplexAnimationInstance* } animationStep* StepToStart = animationInst->animation->animations->get(stepindex); + if(StepToStart == nullptr) + { + Serial.printf("[E] Complex animation stepindex wa invalid. Animation step %d was not started\n\r", stepindex); + return; + } bool hasCallbacks = false; bool wasEmpty = true; AnimatableObject* currentObject; @@ -175,18 +180,25 @@ void Animator::startAnimationStep(uint16_t stepindex, ComplexAnimationInstance* if(StepToStart->arrayIndex[j] != -1) { currentObject = animationInst->objects[StepToStart->arrayIndex[j]]; - setAnimationDuration(currentObject, animationInst->animation->LengthPerAnimation); - currentObject->ComplexAnimationManager = this; - if(hasCallbacks == false) //only assign the callbacks to one object as all of them should start and end at the same time + if(currentObject != nullptr) { - hasCallbacks = true; - currentObject->complexAnimationInst = animationInst; - currentObject->ComplexAnimDoneCallback = &Animator::animationIterationDoneCallback; - currentObject->ComplexAnimStartCallback = &Animator::animationIterationStartCallback; + setAnimationDuration(currentObject, animationInst->animation->LengthPerAnimation); + currentObject->ComplexAnimationManager = this; + if(hasCallbacks == false) //only assign the callbacks to one object as all of them should start and end at the same time + { + hasCallbacks = true; + currentObject->complexAnimationInst = animationInst; + currentObject->ComplexAnimDoneCallback = &Animator::animationIterationDoneCallback; + currentObject->ComplexAnimStartCallback = &Animator::animationIterationStartCallback; + } + startAnimation(currentObject, StepToStart->animationEffects[j], StepToStart->easingEffects[j]); + animationInst->running = true; + wasEmpty = false; + } + else + { + Serial.printf("[E] Complex animation object was nullpointer. Animation step %d was not started\n\r", stepindex); } - startAnimation(currentObject, StepToStart->animationEffects[j], StepToStart->easingEffects[j]); - animationInst->running = true; - wasEmpty = false; } } if(wasEmpty == true) diff --git a/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h index f27c76d..0d67455 100644 --- a/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h +++ b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include "LinkedList.h" #include "Configuration.h" /** @@ -28,7 +28,7 @@ class ConfigManager uint8_t configClass; }; static ConfigManager* instance; - LinkedList eventCallbacks; + DynamicList eventCallbacks; static char UNDEFINED[]; ConfigManager(); diff --git a/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp b/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp index 08bf2b6..3ede038 100644 --- a/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp +++ b/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp @@ -91,18 +91,18 @@ void ConfigManager::checkCallbacks(void* property, uint8_t ConfigClass) { for (int i = 0; i < eventCallbacks.size(); i++) { - if(eventCallbacks[i]->property != nullptr) + if(eventCallbacks.get(i)->property != nullptr) { - if(eventCallbacks[i]->property == property) + if(eventCallbacks.get(i)->property == property) { - eventCallbacks[i]->callback(); + eventCallbacks.get(i)->callback(); } } else { - if((eventCallbacks[i]->configClass & ConfigClass) != 0x00) + if((eventCallbacks.get(i)->configClass & ConfigClass) != 0x00) { - eventCallbacks[i]->callback(); + eventCallbacks.get(i)->callback(); } } } @@ -238,7 +238,7 @@ void ConfigManager::unregisterOnChangedCallback(propertyChangedCallback Callback { for (int i = 0; i < eventCallbacks.size(); i++) { - if(eventCallbacks[i]->callback == Callback && (Property == nullptr || eventCallbacks[i]->property == Property)) + if(eventCallbacks.get(i)->callback == Callback && (Property == nullptr || eventCallbacks.get(i)->property == Property)) { eventCallbacks.remove(i); return; diff --git a/lib/LED_clock/Modules/DisplayManager/inc/DisplayManager.h b/lib/LED_clock/Modules/DisplayManager/inc/DisplayManager.h index b7ae746..6743880 100644 --- a/lib/LED_clock/Modules/DisplayManager/inc/DisplayManager.h +++ b/lib/LED_clock/Modules/DisplayManager/inc/DisplayManager.h @@ -59,7 +59,7 @@ class DisplayManager SegmentPositions_t segmentPosition; DisplayIDs Display; } SegmentInstanceError; - static LinkedList* SegmentIndexErrorList; + static DynamicList* SegmentIndexErrorList; CRGB leds[NUM_LEDS]; #if APPEND_DOWN_LIGHTERS == false @@ -67,7 +67,7 @@ class DisplayManager #endif #if ENABLE_LIGHT_SENSOR == true - LinkedList lightSensorMeasurements; + DynamicList lightSensorMeasurements; uint64_t lastSensorMeasurement; uint8_t lightSensorBrightness; void takeBrightnessMeasurement(); diff --git a/lib/LED_clock/Modules/DisplayManager/src/DisplayManager.cpp b/lib/LED_clock/Modules/DisplayManager/src/DisplayManager.cpp index f5d4cdc..111ae99 100644 --- a/lib/LED_clock/Modules/DisplayManager/src/DisplayManager.cpp +++ b/lib/LED_clock/Modules/DisplayManager/src/DisplayManager.cpp @@ -8,7 +8,7 @@ #include "DisplayManager.h" DisplayManager* DisplayManager::instance = nullptr; -LinkedList* DisplayManager::SegmentIndexErrorList = nullptr; +DynamicList* DisplayManager::SegmentIndexErrorList = nullptr; DisplayManager::DisplayManager() { @@ -106,7 +106,7 @@ void DisplayManager::takeBrightnessMeasurement() } else { - LinkedList sortedMeasurements; + DynamicList sortedMeasurements; //copy all the values to a new temporary list in order to sort them for (uint16_t i = 0; i < lightSensorMeasurements.size(); i++) { @@ -397,9 +397,10 @@ int16_t DisplayManager::getGlobalSegmentIndex(SegmentPositions_t segmentPosition { if(SegmentIndexErrorList == nullptr) { - SegmentIndexErrorList = new LinkedList(); + SegmentIndexErrorList = new DynamicList(); } - SegmentInstanceError newError = {.segmentPosition = segmentPosition, .Display = Display}; + SegmentInstanceError* newError = new SegmentInstanceError{.segmentPosition = segmentPosition, .Display = Display}; + SegmentIndexErrorList->add(newError); } else // once Serial was initialized print errors right away to not forget about them @@ -413,10 +414,11 @@ void DisplayManager::printAnimationInitErrors() { if(SegmentIndexErrorList != nullptr) { + SegmentInstanceError* currentError; while(SegmentIndexErrorList->size() > 0) { - SegmentInstanceError currentError = SegmentIndexErrorList->pop(); - Serial.printf("[W] Segment not valid; Position: %d; Display: %d\n\r", currentError.segmentPosition, currentError.Display); + currentError = SegmentIndexErrorList->pop(); + Serial.printf("[W] Segment not valid; Position: %d; Display: %d\n\r", currentError->segmentPosition, currentError->Display); } } } diff --git a/lib/LED_clock/Modules/Misc/inc/LinkedList.h b/lib/LED_clock/Modules/Misc/inc/LinkedList.h new file mode 100644 index 0000000..c49ff55 --- /dev/null +++ b/lib/LED_clock/Modules/Misc/inc/LinkedList.h @@ -0,0 +1,421 @@ +// It was required to change the name of the Linked list class as it would have otherwise crashed with the linked list implementation from ESPAsyncWebServer + +/* + LinkedList.h - V1.1 - Generic LinkedList implementation + Works better with FIFO, because LIFO will need to + search the entire List to find the last one; + + For instructions, go to https://github.com/ivanseidel/LinkedList + + Created by Ivan Seidel Gomes, March, 2013. + Released into the public domain. +*/ + +#ifndef __LINKED_LIST1_H_ +#define __LINKED_LIST1_H_ + + +#include + +template +struct ListNode +{ + T data; + ListNode *next; +}; + +template +class DynamicList{ + +protected: + int _size; + ListNode *root; + ListNode *last; + + // Helps "get" method, by saving last position + ListNode *lastNodeGot; + int lastIndexGot; + // isCached should be set to FALSE + // everytime the list suffer changes + bool isCached; + + ListNode* getNode(int index); + + ListNode* findEndOfSortedString(ListNode *p, int (*cmp)(T &, T &)); + +public: + DynamicList(); + DynamicList(int sizeIndex, T _t); //initiate list size and default value + ~DynamicList(); + + /* + Returns current size of LinkedList + */ + virtual int size(); + /* + Adds a T object in the specified index; + Unlink and link the LinkedList correcly; + Increment _size + */ + virtual bool add(int index, T); + /* + Adds a T object in the end of the LinkedList; + Increment _size; + */ + virtual bool add(T); + /* + Adds a T object in the start of the LinkedList; + Increment _size; + */ + virtual bool unshift(T); + /* + Set the object at index, with T; + */ + virtual bool set(int index, T); + /* + Remove object at index; + If index is not reachable, returns false; + else, decrement _size + */ + virtual T remove(int index); + /* + Remove last object; + */ + virtual T pop(); + /* + Remove first object; + */ + virtual T shift(); + /* + Get the index'th element on the list; + Return Element if accessible, + else, return false; + */ + virtual T get(int index); + + /* + Clear the entire array + */ + virtual void clear(); + + /* + Sort the list, given a comparison function + */ + virtual void sort(int (*cmp)(T &, T &)); + + // add support to array brakets [] operator + inline T& operator[](int index); + inline T& operator[](size_t& i) { return this->get(i); } + inline const T& operator[](const size_t& i) const { return this->get(i); } + +}; + +// Initialize LinkedList with false values +template +DynamicList::DynamicList() +{ + root=NULL; + last=NULL; + _size=0; + + lastNodeGot = root; + lastIndexGot = 0; + isCached = false; +} + +// Clear Nodes and free Memory +template +DynamicList::~DynamicList() +{ + ListNode* tmp; + while(root!=NULL) + { + tmp=root; + root=root->next; + delete tmp; + } + last = NULL; + _size=0; + isCached = false; +} + +/* + Actualy "logic" coding +*/ + +template +ListNode* DynamicList::getNode(int index){ + + int _pos = 0; + ListNode* current = root; + + // Check if the node trying to get is + // immediatly AFTER the previous got one + if(isCached && lastIndexGot <= index){ + _pos = lastIndexGot; + current = lastNodeGot; + } + + while(_pos < index && current){ + current = current->next; + + _pos++; + } + + // Check if the object index got is the same as the required + if(_pos == index){ + isCached = true; + lastIndexGot = index; + lastNodeGot = current; + + return current; + } + + return NULL; +} + +template +int DynamicList::size(){ + return _size; +} + +template +DynamicList::DynamicList(int sizeIndex, T _t){ + for (int i = 0; i < sizeIndex; i++){ + add(_t); + } +} + +template +bool DynamicList::add(int index, T _t){ + + if(index >= _size) + return add(_t); + + if(index == 0) + return unshift(_t); + + ListNode *tmp = new ListNode(), + *_prev = getNode(index-1); + tmp->data = _t; + tmp->next = _prev->next; + _prev->next = tmp; + + _size++; + isCached = false; + + return true; +} + +template +bool DynamicList::add(T _t){ + + ListNode *tmp = new ListNode(); + tmp->data = _t; + tmp->next = NULL; + + if(root){ + // Already have elements inserted + last->next = tmp; + last = tmp; + }else{ + // First element being inserted + root = tmp; + last = tmp; + } + + _size++; + isCached = false; + + return true; +} + +template +bool DynamicList::unshift(T _t){ + + if(_size == 0) + return add(_t); + + ListNode *tmp = new ListNode(); + tmp->next = root; + tmp->data = _t; + root = tmp; + + _size++; + isCached = false; + + return true; +} + + +template +T& DynamicList::operator[](int index) { + return getNode(index)->data; +} + +template +bool DynamicList::set(int index, T _t){ + // Check if index position is in bounds + if(index < 0 || index >= _size) + return false; + + getNode(index)->data = _t; + return true; +} + +template +T DynamicList::pop(){ + if(_size <= 0) + return T(); + + isCached = false; + + if(_size >= 2){ + ListNode *tmp = getNode(_size - 2); + T ret = tmp->next->data; + delete(tmp->next); + tmp->next = NULL; + last = tmp; + _size--; + return ret; + }else{ + // Only one element left on the list + T ret = root->data; + delete(root); + root = NULL; + last = NULL; + _size = 0; + return ret; + } +} + +template +T DynamicList::shift(){ + if(_size <= 0) + return T(); + + if(_size > 1){ + ListNode *_next = root->next; + T ret = root->data; + delete(root); + root = _next; + _size --; + isCached = false; + + return ret; + }else{ + // Only one left, then pop() + return pop(); + } + +} + +template +T DynamicList::remove(int index){ + if (index < 0 || index >= _size) + { + return T(); + } + + if(index == 0) + return shift(); + + if (index == _size-1) + { + return pop(); + } + + ListNode *tmp = getNode(index - 1); + ListNode *toDelete = tmp->next; + T ret = toDelete->data; + tmp->next = tmp->next->next; + delete(toDelete); + _size--; + isCached = false; + return ret; +} + + +template +T DynamicList::get(int index){ + ListNode *tmp = getNode(index); + + return (tmp ? tmp->data : T()); +} + +template +void DynamicList::clear(){ + while(size() > 0) + shift(); +} + +template +void DynamicList::sort(int (*cmp)(T &, T &)){ + if(_size < 2) return; // trivial case; + + for(;;) { + + ListNode **joinPoint = &root; + + while(*joinPoint) { + ListNode *a = *joinPoint; + ListNode *a_end = findEndOfSortedString(a, cmp); + + if(!a_end->next ) { + if(joinPoint == &root) { + last = a_end; + isCached = false; + return; + } + else { + break; + } + } + + ListNode *b = a_end->next; + ListNode *b_end = findEndOfSortedString(b, cmp); + + ListNode *tail = b_end->next; + + a_end->next = NULL; + b_end->next = NULL; + + while(a && b) { + if(cmp(a->data, b->data) <= 0) { + *joinPoint = a; + joinPoint = &a->next; + a = a->next; + } + else { + *joinPoint = b; + joinPoint = &b->next; + b = b->next; + } + } + + if(a) { + *joinPoint = a; + while(a->next) a = a->next; + a->next = tail; + joinPoint = &a->next; + } + else { + *joinPoint = b; + while(b->next) b = b->next; + b->next = tail; + joinPoint = &b->next; + } + } + } +} + +template +ListNode* DynamicList::findEndOfSortedString(ListNode *p, int (*cmp)(T &, T &)) { + while(p->next && cmp(p->data, p->next->data) <= 0) { + p = p->next; + } + + return p; +} + +#endif \ No newline at end of file diff --git a/partitions_custom.csv b/partitions_custom.csv new file mode 100644 index 0000000..2bf77f5 --- /dev/null +++ b/partitions_custom.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x180000, +app1, app, ota_1, 0x190000,0x180000, +spiffs, data, spiffs, 0x310000,0x90000, \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index dddc4a0..5518fc7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,11 +14,13 @@ board = nodemcu-32s framework = arduino upload_speed = 921600 monitor_speed = 115200 +board_build.partitions = partitions_custom.csv lib_deps = fastled/FastLED@^3.4.0 - ivanseidel/LinkedList@0.0.0-alpha+sha.dac3874d28 blynkkk/Blynk@^0.6.7 bblanchon/ArduinoJson@^6.17.3 + me-no-dev/ESP Async WebServer@^1.2.3 + ivanseidel/LinkedList@0.0.0-alpha+sha.dac3874d28 ; Uncomment these to use OTA as a flash method. This only works if the ESp already has code running on it where OTA update is enabled. ; upload_protocol = espota diff --git a/src/main.cpp b/src/main.cpp index 52f1079..5e29b62 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,6 +9,7 @@ #include "DisplayManager.h" #include "ClockState.h" #include "ConfigManager.h" +#include "ESPAsyncWebServer.h" #if RUN_WITHOUT_WIFI == false #include "WiFi.h" @@ -24,6 +25,7 @@ DisplayManager* ShelfDisplays = DisplayManager::getInstance(); BlynkConfig* BlynkConfiguration = BlynkConfig::getInstance(); TimeManager* timeM = TimeManager::getInstance(); ClockState* states = ClockState::getInstance(); +AsyncWebServer server(80); #if ENABLE_OTA_UPLOAD == true void setupOTA(); @@ -80,6 +82,24 @@ void startupAnimation() } } +void sendWebFile(AsyncWebServerRequest *request) +{ + request->send(SPIFFS, "/index.html", "text/html"); +} + +void startWebServer() +{ + server.on("/", HTTP_GET, sendWebFile); + + server.on("/index.js.gz", HTTP_GET, [](AsyncWebServerRequest *request){ + AsyncWebServerResponse* response = request->beginResponse(SPIFFS, "/index.js.gz", "text/html"); + response->addHeader("Content-Encoding", "gzip"); + request->send(response); + }); + + server.begin(); +} + void setup() { Serial.begin(115200); @@ -107,6 +127,8 @@ void setup() BlynkConfiguration->setup(); #endif + startWebServer(); + Serial.println("Fetching time from NTP server..."); if(timeM->init() == false) { @@ -119,6 +141,7 @@ void setup() Serial.println("Displaying startup animation..."); startupAnimation(); Serial.println("Setup done. Main Loop starting..."); + Serial.printf("Free heap: %d\n\r", esp_get_free_heap_size()); } void loop() From 5fae5c245a81b13c115c52c7e7c4d727bdff37c0 Mon Sep 17 00:00:00 2001 From: "flo.laschober@gmail.com" Date: Thu, 22 Apr 2021 00:32:33 +0200 Subject: [PATCH 03/13] New settings component with dynamically generated UI for #5 --- WebConfigUI/src/components/Dialog.jsx | 83 +++++---- WebConfigUI/src/css/tailwind.css | 15 ++ WebConfigUI/src/data/BaseConfig.json | 28 +++ WebConfigUI/src/data/BaseConfig_definition.js | 164 ++++++++++++++++++ WebConfigUI/src/sites/Navigation.jsx | 6 +- WebConfigUI/src/sites/Settings.jsx | 120 ++++++++++++- WebConfigUI/src/sites/WIFI-setup.jsx | 41 ++++- 7 files changed, 400 insertions(+), 57 deletions(-) create mode 100644 WebConfigUI/src/data/BaseConfig.json create mode 100644 WebConfigUI/src/data/BaseConfig_definition.js diff --git a/WebConfigUI/src/components/Dialog.jsx b/WebConfigUI/src/components/Dialog.jsx index 9db1fd5..690ccd7 100644 --- a/WebConfigUI/src/components/Dialog.jsx +++ b/WebConfigUI/src/components/Dialog.jsx @@ -1,33 +1,42 @@ import { Dialog, Transition } from "@headlessui/react"; -import { Fragment, useEffect, useRef, useState } from "react"; -import { useTimeoutFn } from "react-use"; +import React, { Fragment, useEffect, useRef, useState } from "react"; -export default function MyModal() { - const [open, setOpen] = useState(false); - const cancelButtonRef = useRef(); +export default function MyModal(props) { + let OkayButtonRef = useRef(); - // This code makes the demo automatically show on initial render - let [, clear, resetIsShowing] = useTimeoutFn(() => setOpen(true), 750); - useEffect(() => { - clear(); - setOpen(true); - }, []); + props.type == "OK/Cancel" - function closeModal() { - setOpen(false); - resetIsShowing(); + let cancelButton = "" + let okayButtonLabel = "OK" + + if(props.confirmMessage) + { + okayButtonLabel = props.confirmMessage + } + + if(props.type == "OK/Cancel") + { + cancelButton = ( + + ) } return ( - + props.onClose(false)} >
- + - - {/* This element is to trick the browser into centering the modal contents. */} - -
+
- Payment successful + {props.title} -
-

- Your payment has been successfully submitted. We’ve sent your - an email with all of the details of your order. + +

+ {props.children}

-
+ -
- + {cancelButton}
diff --git a/WebConfigUI/src/css/tailwind.css b/WebConfigUI/src/css/tailwind.css index fa52b09..a8dbe68 100644 --- a/WebConfigUI/src/css/tailwind.css +++ b/WebConfigUI/src/css/tailwind.css @@ -26,3 +26,18 @@ @apply rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 transform transition; } } + +@layer components { + .btn { + @apply justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 + } + + .btn-primary { + @apply text-white bg-indigo-600 hover:bg-indigo-700 + + } + + .btn-secondary { + @apply text-indigo-900 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-400 + } +} \ No newline at end of file diff --git a/WebConfigUI/src/data/BaseConfig.json b/WebConfigUI/src/data/BaseConfig.json new file mode 100644 index 0000000..155449e --- /dev/null +++ b/WebConfigUI/src/data/BaseConfig.json @@ -0,0 +1,28 @@ +{ + "ENABLE_BLYNK": true, + "BLYNK_AUTH_TOKEN": "AUTH_TOKEN_GOES_HERE", + "BLYNK_SERVER": "blynk-cloud.com", + "OTA_UPLOAD": true, + "HOST_NAME": "LED-Shelf", + "NUM_RETRIES": 50, + "SMART_CONFIG": true, + "NTP_SERVER": "pool.ntp.org", + "TIMEZONE_INFO": "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00", + "TIME_SYNC_INTERVAL": 1800, + "TIMER_FLASH_TIME": false, + "TIMER_FLASH_COUNT": 10, + "ALARM_NOTIFICATION_PERIOD": 600, + "NOTIFICATION_BRIGHTNESS": 125, + "TIME_UPDATE_INTERVAL": 500, + "DISPLAY_0_AT_MIDNIGHT": false, + "DISPLAY_SWITCH_OFF_AT_0": false, + "USE_24_HOUR_FORMAT": false, + "LOADING_ANIMATION_DURATION": 3000, + "BRIGHTNESS_INTERPOLATION": 3000, + "ANIMATION_TARGET_FPS": 60, + "ANIMATION_AFTERGLOW": 0.2, + "DOT_FLASH_SPEED": 2000, + "DOT_FLASH_INTERVAL": 4000, + "NUM_SEPARATION_DOTS": 2, + "DIGIT_ANIMATION_SPEED": 900 +} \ No newline at end of file diff --git a/WebConfigUI/src/data/BaseConfig_definition.js b/WebConfigUI/src/data/BaseConfig_definition.js new file mode 100644 index 0000000..84f4b26 --- /dev/null +++ b/WebConfigUI/src/data/BaseConfig_definition.js @@ -0,0 +1,164 @@ +const definition = [ + { + label: "Enable Blynk functionality", + type: "bool", + target: "ENABLE_BLYNK" + }, + { + label: "Blynk Auth token", + type: "string", + target: "BLYNK_AUTH_TOKEN", + onlyIf: "ENABLE_BLYNK" + }, + { + label: "Blynk server URL", + type: "string", + target: "BLYNK_SERVER", + onlyIf: "ENABLE_BLYNK" + }, + { + label: "Enable OTA Updates", + type: "bool", + target: "OTA_UPLOAD" + }, + { + label: "Host name", + type: "string", + target: "HOST_NAME" + }, + { + label: "Number of retries for WIFI reconnection before smart config is started (if enabled)", + type: "int", + target: "NUM_RETRIES", + min: 1, + max: 1000 + }, + { + label: "Enable Smart config if WIFI connection was lost", + type: "bool", + target: "SMART_CONFIG" + }, + { + label: "NTP server URL", + type: "string", + target: "NTP_SERVER" + }, + { + label: "Enter a configuration string for you timezone according to this website: https://remotemonitoringsystems.ca/time-zone-abbreviations.php", + type: "string", + target: "TIMEZONE_INFO" + }, + { + label: "Time in seconds for the interval in which the time should be synchronized with the time (NTP) server", + type: "int", + target: "TIME_SYNC_INTERVAL", + min: 1, + max: 4294967295 + }, + { + label: "Flash the current time in case a timer is expired instead of flashing 00:00", + type: "bool", + target: "TIMER_FLASH_TIME" + }, + { + label: "Number of flashes until an alarm is considered expired and the system goes back to normal", + type: "int", + target: "TIMER_FLASH_COUNT", + min: 1, + max: 65535 + }, + { + label: "For how long the Display should flash when an alarm was fired in seconds. For example: An Alarm for 12:00 would stop flashing at 12:01 if this was set to 60", + type: "int", + target: "ALARM_NOTIFICATION_PERIOD", + min: 1, + max: 65535 + }, + { + label: "How bright the clock should flash when an alarm or timer was triggered", + type: "int", + target: "NOTIFICATION_BRIGHTNESS", + min: 0, + max: 255 + }, + { + label: "How often the time on the displays is updated in MS", + type: "int", + target: "TIME_UPDATE_INTERVAL", + min: 1, + max: 65535 + }, + { + label: "If turned on the display will show 0 at midnight and 12 otherwise", + type: "bool", + target: "DISPLAY_0_AT_MIDNIGHT", + }, + { + label: "If turned on the higher digit displays will turn off in case they would show 0", + type: "bool", + target: "DISPLAY_SWITCH_OFF_AT_0" + }, + { + label: "If set to true 24 hour format will be used. Only usable if the hour display has all 7 segments", + type: "bool", + target: "USE_24_HOUR_FORMAT" + }, + { + label: "The time it shall take for one iteration of the loading animation", + type: "int", + target: "LOADING_ANIMATION_DURATION", + min: 1, + max: 65535 + }, + { + label: "How fast the brightness interpolation shall react to brightness changes", + type: "int", + target: "BRIGHTNESS_INTERPOLATION", + min: 1, + max: 65535 + }, + { + label: "Target frames per second for the smoothness of animations", + type: "int", + target: "ANIMATION_TARGET_FPS", + min: 1, + max: 255 + }, + { + label: "Length of sooth animation transition from fully on to black and vice versa in percent. NOTE: The higher this number the less obvious easing effects like bounce or elastic will be", + type: "float", + target: "ANIMATION_AFTERGLOW", + min: 0, + max: 1 + }, + { + label: "Length of the dot/s fading animation in MS. One flash fades in and out", + type: "int", + target: "DOT_FLASH_SPEED", + min: 1, + max: 65535 + }, + { + label: "Interval in MS in which the dot/s should flash", + type: "int", + target: "DOT_FLASH_INTERVAL", + min: 1, + max: 65535 + }, + { + label: "Number of separation dots that shall be displayed", + type: "int", + target: "NUM_SEPARATION_DOTS", + min: 0, + max: 2 + }, + { + label: "The time it takes in MS for one digit to morph into another", + type: "int", + target: "DIGIT_ANIMATION_SPEED", + min: 1, + max: 65535 + }, +]; + +export default definition; \ No newline at end of file diff --git a/WebConfigUI/src/sites/Navigation.jsx b/WebConfigUI/src/sites/Navigation.jsx index bf684a7..bca51c7 100644 --- a/WebConfigUI/src/sites/Navigation.jsx +++ b/WebConfigUI/src/sites/Navigation.jsx @@ -5,10 +5,12 @@ import React from "react"; const pages = [ {link: "/WIFI", label: "WIFI Setup"}, {link: "/Settings", label: "Settings"}, + {link: "/", label: "Colors"}, + {link: "/", label: "Display Setup"}, + {link: "/", label: "Animation Setup"}, ]; function NavigationMenu(props) { - return (

LED Pixel Clock

@@ -16,7 +18,7 @@ function NavigationMenu(props) { {pages.map((page) =>( - + {({ active }) => ( { + const settings = [...settingsEntries]; + settingsEntries.forEach((element, index) => { + settings[index].value = settingsValues[element.target]; + }); + return settings; +} const Settings = () => { - return ( -
+ const [Settings, setSettings] = useState(loadConfig()); + const [dialogShown, setDialogShown] = useState(false); + + const handleSubmit = (event) => { + event.preventDefault(); + setDialogShown(true); + } + + const handleDialogClose = (result) => { + setDialogShown(false); + if(result == true) + { + + } + } + + const valueChangedHandler = (event, target) => { + const targetSettingIndex = Settings.findIndex(p => p.target === target); + const setting = { + ...Settings[targetSettingIndex] + }; + + const newValue = event.target.value; + + switch(event.target.type) + { + case "checkbox": + setting.value = event.target.checked; + break; + case "number": + if(isNaN(newValue) || newValue < setting.min || newValue > setting.max) + { + return; + } + default: + setting.value = newValue; + break; + } + + const settings = [...Settings]; + settings[targetSettingIndex] = setting; + setSettings(settings); + } + + const isDisabled = (setting) => { + if("onlyIf" in setting) + { + const targetSettingIndex = Settings.findIndex(p => p.target === setting.onlyIf); + if(Settings[targetSettingIndex].type === "bool") + { + return !Settings[targetSettingIndex].value; + } + } + return false; + } + + const settingsForm = Settings.map((settingEntry, index) => { + let Disabled = isDisabled(settingEntry); + switch(settingEntry.type) + { + case "string": + return ( +
+ +
+ valueChangedHandler(e, settingEntry.target)} autoComplete="off" type="text"/> +
+
+ ) + case "bool": + return ( +
+ valueChangedHandler(e, settingEntry.target)}/> + +
+ ) + case "int": + return ( +
+ +
+ valueChangedHandler(e, settingEntry.target)} autoComplete="off" type="number"/> +
+
+ ) + } + }) + return ( +
+
+
+

General Settings

+
+
+ {settingsForm} +
+ +
+
+
+ + To save the changes The device will reboot. You may loose connection to this interface temporarily. +
); } diff --git a/WebConfigUI/src/sites/WIFI-setup.jsx b/WebConfigUI/src/sites/WIFI-setup.jsx index a7f6321..d26014d 100644 --- a/WebConfigUI/src/sites/WIFI-setup.jsx +++ b/WebConfigUI/src/sites/WIFI-setup.jsx @@ -1,19 +1,44 @@ import React, { useState } from 'react'; +import Dialog from '../components/Dialog' const WIFI_new = () => { const [SSID, setSSID] = useState(""); + const [PW, setPW] = useState(""); + const [createAP, setCreateAP] = useState(false); + const [dialogShown, setDialogShown] = useState(false); const handleSSIDChanged = (object) => { setSSID(object.target.value); } + const handlePWChanged = (object) => { + setPW(object.target.value); + } + + const handleCreateAPChanged = (object) => { + setCreateAP(object.target.checked); + } + const handleSubmit = (event) => { event.preventDefault(); - console.log("SSID: " + SSID); + setDialogShown(true); + } + + const handleDialogClose = (result) => { + setDialogShown(false); + if(result == true) + { + console.log("SSID: " + SSID); + console.log("PW: " + PW); + console.log("Create AP: " + createAP); + setSSID(""); + setPW(""); + setCreateAP(false); + } } return ( -
+

Connect to your WIFI

@@ -22,31 +47,29 @@ const WIFI_new = () => {
- +
- +
- +
-
+ To change WIFI settings the device has to restart. Please reconnect to the new device IP once it restarted.
) } From ccd793dc8419e95971db9bc662230301175fa16f Mon Sep 17 00:00:00 2001 From: "flo.laschober@gmail.com" Date: Sun, 25 Apr 2021 17:51:30 +0200 Subject: [PATCH 04/13] Added More setting pages --- WebConfigUI/package.json | 7 +- WebConfigUI/src/App.jsx | 90 +++- WebConfigUI/src/components/RadioGroup.jsx | 28 -- WebConfigUI/src/components/SettingsForm.jsx | 160 ++++++++ WebConfigUI/src/css/tailwind.css | 3 +- WebConfigUI/src/data/BaseConfig_definition.js | 164 -------- WebConfigUI/src/data/ColorConfig.json | 11 + WebConfigUI/src/data/HWConfig.json | 28 ++ WebConfigUI/src/data/UI_definitions.js | 386 ++++++++++++++++++ WebConfigUI/src/data/UI_definitions.json | 384 +++++++++++++++++ WebConfigUI/src/sites/Navigation.jsx | 7 +- WebConfigUI/src/sites/Settings.jsx | 132 +----- WebConfigUI/src/sites/WIFI-setup.jsx | 10 +- WebConfigUI/tailwind.config.js | 3 +- WebConfigUI/webpack.config.js | 1 - 15 files changed, 1074 insertions(+), 340 deletions(-) delete mode 100644 WebConfigUI/src/components/RadioGroup.jsx create mode 100644 WebConfigUI/src/components/SettingsForm.jsx delete mode 100644 WebConfigUI/src/data/BaseConfig_definition.js create mode 100644 WebConfigUI/src/data/ColorConfig.json create mode 100644 WebConfigUI/src/data/HWConfig.json create mode 100644 WebConfigUI/src/data/UI_definitions.js create mode 100644 WebConfigUI/src/data/UI_definitions.json diff --git a/WebConfigUI/package.json b/WebConfigUI/package.json index 8e7f38f..7fba29c 100644 --- a/WebConfigUI/package.json +++ b/WebConfigUI/package.json @@ -4,9 +4,9 @@ "description": "", "main": "src/main.jsx", "scripts": { - "dev": "vite", - "build": "webpack --mode production", - "start": "webpack serve --mode development" + "dev": "set NODE_ENV=development && set PURGE_FILES=0 && vite", + "build": "set NODE_ENV=production && set PURGE_FILES=1 && webpack --mode production", + "start": "set NODE_ENV=development && set PURGE_FILES=0 && webpack serve --mode development" }, "author": "", "license": "ISC", @@ -33,6 +33,7 @@ "webpack-dev-server": "^3.11.2" }, "dependencies": { + "esbuild": "^0.11.13", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router-dom": "^5.2.0" diff --git a/WebConfigUI/src/App.jsx b/WebConfigUI/src/App.jsx index 5c2257e..66c1b00 100644 --- a/WebConfigUI/src/App.jsx +++ b/WebConfigUI/src/App.jsx @@ -1,20 +1,86 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import Navigation from "./sites/Navigation"; import WIFI_setup from "./sites/WIFI-setup"; import Settings from "./sites/Settings"; +import UI_definitions from './data/UI_definitions.json' + + +// Development function for testing locally +const getConfigFile = async (fileUrl, hook) => { + import(/* @vite-ignore */"./data" + fileUrl ).then(config => { + hook(config); + }) +} + export default function App() { - return ( -
- - - } /> - } /> - } /> - - -
- ); + const [BaseConfig, setBaseConfig] = useState({}); + const [ColorConfig, setColorConfig] = useState({}); + const [HWConfig, setHWConfig] = useState({}); + + // Function for fetching the config file from the server + // const getConfigFile = async (fileUrl, hook) => { + // const requestOptions = { + // method: 'GET', + // headers: { 'Content-Type': 'application/json' } + // }; + // const response = await fetch(fileUrl, requestOptions); + // if (!response.ok) { + // console.log("Error while fetching config file: " + response.status); + // return; + // } + // hook(response.json()); + // } + + const putConfigFile = async (fileUrl, value) => { + console.log("Putting: " + value) + console.log("To: " + fileUrl) + const requestOptions = { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: value + }; + const response = await fetch(fileUrl, requestOptions); + if (!response.ok) { + console.log("Error while fetching config file: " + response.status); + return null; + } + return await response.json(); + } + + useEffect(() => { + getConfigFile('/BaseConfig.json', setBaseConfig); + getConfigFile('/ColorConfig.json', setColorConfig); + getConfigFile('/HWConfig.json', setHWConfig); + }, []); + + const handleSubmit = (domain, values) => { + putConfigFile(domain, values); + } + + return ( +
+ + + } /> + + } /> + + + General Settings + } /> + + + Color Settings + } /> + + + Hardware Settings + } /> + + +
+ ); } diff --git a/WebConfigUI/src/components/RadioGroup.jsx b/WebConfigUI/src/components/RadioGroup.jsx deleted file mode 100644 index 27d0017..0000000 --- a/WebConfigUI/src/components/RadioGroup.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import { useState } from "react"; -import { RadioGroup } from "@headlessui/react"; - -export default function MyRadioGroup() { - let [plan, setPlan] = useState("startup"); - - return ( - - Plan - - {({ checked }) => ( - Startup - )} - - - {({ checked }) => ( - Business - )} - - - {({ checked }) => ( - Enterprise - )} - - - ); -} diff --git a/WebConfigUI/src/components/SettingsForm.jsx b/WebConfigUI/src/components/SettingsForm.jsx new file mode 100644 index 0000000..f2d46cb --- /dev/null +++ b/WebConfigUI/src/components/SettingsForm.jsx @@ -0,0 +1,160 @@ +import React, { useState } from "react"; +import Dialog from '../components/Dialog' + + +const loadConfig = (UIDefinition, InitialValues) => { + const settings = [...UIDefinition]; + UIDefinition.forEach((element, index) => { + settings[index].value = InitialValues[element.target]; + }); + return settings; +} + +const SettingsForm = (props) => { + const [Settings, setSettings] = useState(loadConfig(props.UIDefinition, props.InitialValues)); + const [dialogShown, setDialogShown] = useState(false); + + const handleSubmit = (event) => { + event.preventDefault(); + setDialogShown(true); + } + + const handleDialogClose = (result) => { + setDialogShown(false); + if(result == true) + { + let results = {}; + Settings.forEach(setting => { + results[setting.target] = setting.value + }); + props.onSubmit(props.domain, JSON.stringify(results)); + } + } + + const valueChangedHandler = (event, target) => { + const targetSettingIndex = Settings.findIndex(p => p.target === target); + + const setting = { + ...Settings[targetSettingIndex] + }; + + const newValue = event.target.value; + switch(event.target.type) + { + case "checkbox": + setting.value = event.target.checked; + break; + case "number": + if(isNaN(newValue) || (newValue < setting.min || newValue > setting.max) && newValue != "" || newValue.includes("e") || (newValue.includes(".") && setting.type === "int")) + { + return; + } + default: + setting.value = newValue; + break; + } + + const settings = [...Settings]; + settings[targetSettingIndex] = setting; + setSettings(settings); + } + + const isDisabled = (setting) => { + if("onlyIf" in setting) + { + const invert = setting.onlyIf.startsWith("!"); + const onlyIfTarget = setting.onlyIf.replace("!", ""); + + const targetSettingIndex = Settings.findIndex(p => p.target === onlyIfTarget); + if(Settings[targetSettingIndex].type === "bool") + { + if(invert) + { + return Settings[targetSettingIndex].value; + } + else + { + return !Settings[targetSettingIndex].value; + } + } + } + return false; + } + + const toColor = (num) => { + num >>>= 0; + var b = num & 0xFF, + g = (num & 0xFF00) >>> 8, + r = (num & 0xFF0000) >>> 16; + return "rgba(" + [r, g, b, 1].join(",") + ")"; + } + + const settingsForm = Settings.map((settingEntry, index) => { + let Disabled = isDisabled(settingEntry); + let stepSize = 1; + switch(settingEntry.type) + { + case "string": + return ( +
+ +
+ valueChangedHandler(e, settingEntry.target)} autoComplete="off" type="text"/> +
+
+ ) + case "bool": + return ( +
+ valueChangedHandler(e, settingEntry.target)}/> + +
+ ) + case "float": + stepSize = 0.1 + case "int": + return ( +
+ +
+ valueChangedHandler(e, settingEntry.target)} autoComplete="off" type="number"/> +
+
+ ) + case "color": + const test = { + backgroundColor: toColor(Settings[index].value) + } + return ( +
+ +
+
+ + valueChangedHandler(e, settingEntry.target)} type="range"/> + valueChangedHandler(e, settingEntry.target)} type="range"/> + valueChangedHandler(e, settingEntry.target)} type="range"/> +
+
+ ) + } + }) + return ( +
+
+ {settingsForm} +
+ +
+
+ + + To save the changes the device may reboot. You may loose connection to this interface temporarily. + +
+ ); +} + +export default SettingsForm; diff --git a/WebConfigUI/src/css/tailwind.css b/WebConfigUI/src/css/tailwind.css index a8dbe68..4b93d89 100644 --- a/WebConfigUI/src/css/tailwind.css +++ b/WebConfigUI/src/css/tailwind.css @@ -34,10 +34,9 @@ .btn-primary { @apply text-white bg-indigo-600 hover:bg-indigo-700 - } .btn-secondary { @apply text-indigo-900 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-400 } -} \ No newline at end of file +} diff --git a/WebConfigUI/src/data/BaseConfig_definition.js b/WebConfigUI/src/data/BaseConfig_definition.js deleted file mode 100644 index 84f4b26..0000000 --- a/WebConfigUI/src/data/BaseConfig_definition.js +++ /dev/null @@ -1,164 +0,0 @@ -const definition = [ - { - label: "Enable Blynk functionality", - type: "bool", - target: "ENABLE_BLYNK" - }, - { - label: "Blynk Auth token", - type: "string", - target: "BLYNK_AUTH_TOKEN", - onlyIf: "ENABLE_BLYNK" - }, - { - label: "Blynk server URL", - type: "string", - target: "BLYNK_SERVER", - onlyIf: "ENABLE_BLYNK" - }, - { - label: "Enable OTA Updates", - type: "bool", - target: "OTA_UPLOAD" - }, - { - label: "Host name", - type: "string", - target: "HOST_NAME" - }, - { - label: "Number of retries for WIFI reconnection before smart config is started (if enabled)", - type: "int", - target: "NUM_RETRIES", - min: 1, - max: 1000 - }, - { - label: "Enable Smart config if WIFI connection was lost", - type: "bool", - target: "SMART_CONFIG" - }, - { - label: "NTP server URL", - type: "string", - target: "NTP_SERVER" - }, - { - label: "Enter a configuration string for you timezone according to this website: https://remotemonitoringsystems.ca/time-zone-abbreviations.php", - type: "string", - target: "TIMEZONE_INFO" - }, - { - label: "Time in seconds for the interval in which the time should be synchronized with the time (NTP) server", - type: "int", - target: "TIME_SYNC_INTERVAL", - min: 1, - max: 4294967295 - }, - { - label: "Flash the current time in case a timer is expired instead of flashing 00:00", - type: "bool", - target: "TIMER_FLASH_TIME" - }, - { - label: "Number of flashes until an alarm is considered expired and the system goes back to normal", - type: "int", - target: "TIMER_FLASH_COUNT", - min: 1, - max: 65535 - }, - { - label: "For how long the Display should flash when an alarm was fired in seconds. For example: An Alarm for 12:00 would stop flashing at 12:01 if this was set to 60", - type: "int", - target: "ALARM_NOTIFICATION_PERIOD", - min: 1, - max: 65535 - }, - { - label: "How bright the clock should flash when an alarm or timer was triggered", - type: "int", - target: "NOTIFICATION_BRIGHTNESS", - min: 0, - max: 255 - }, - { - label: "How often the time on the displays is updated in MS", - type: "int", - target: "TIME_UPDATE_INTERVAL", - min: 1, - max: 65535 - }, - { - label: "If turned on the display will show 0 at midnight and 12 otherwise", - type: "bool", - target: "DISPLAY_0_AT_MIDNIGHT", - }, - { - label: "If turned on the higher digit displays will turn off in case they would show 0", - type: "bool", - target: "DISPLAY_SWITCH_OFF_AT_0" - }, - { - label: "If set to true 24 hour format will be used. Only usable if the hour display has all 7 segments", - type: "bool", - target: "USE_24_HOUR_FORMAT" - }, - { - label: "The time it shall take for one iteration of the loading animation", - type: "int", - target: "LOADING_ANIMATION_DURATION", - min: 1, - max: 65535 - }, - { - label: "How fast the brightness interpolation shall react to brightness changes", - type: "int", - target: "BRIGHTNESS_INTERPOLATION", - min: 1, - max: 65535 - }, - { - label: "Target frames per second for the smoothness of animations", - type: "int", - target: "ANIMATION_TARGET_FPS", - min: 1, - max: 255 - }, - { - label: "Length of sooth animation transition from fully on to black and vice versa in percent. NOTE: The higher this number the less obvious easing effects like bounce or elastic will be", - type: "float", - target: "ANIMATION_AFTERGLOW", - min: 0, - max: 1 - }, - { - label: "Length of the dot/s fading animation in MS. One flash fades in and out", - type: "int", - target: "DOT_FLASH_SPEED", - min: 1, - max: 65535 - }, - { - label: "Interval in MS in which the dot/s should flash", - type: "int", - target: "DOT_FLASH_INTERVAL", - min: 1, - max: 65535 - }, - { - label: "Number of separation dots that shall be displayed", - type: "int", - target: "NUM_SEPARATION_DOTS", - min: 0, - max: 2 - }, - { - label: "The time it takes in MS for one digit to morph into another", - type: "int", - target: "DIGIT_ANIMATION_SPEED", - min: 1, - max: 65535 - }, -]; - -export default definition; \ No newline at end of file diff --git a/WebConfigUI/src/data/ColorConfig.json b/WebConfigUI/src/data/ColorConfig.json new file mode 100644 index 0000000..7425c52 --- /dev/null +++ b/WebConfigUI/src/data/ColorConfig.json @@ -0,0 +1,11 @@ +{ + "HOUR": 16777215, + "MINUTE": 15794175, + "INTERNAL": 255, + "SEPARATION_DOT": 255, + "OTA_UPDATE": 16753920, + "WIFI_CONNECTING": 255, + "WIFI_CONNECTION_SUCCESSFUL": 32768, + "WIFI_SMART_CONFIG": 16776960, + "ERROR": 16711680 +} diff --git a/WebConfigUI/src/data/HWConfig.json b/WebConfigUI/src/data/HWConfig.json new file mode 100644 index 0000000..f1a1c2f --- /dev/null +++ b/WebConfigUI/src/data/HWConfig.json @@ -0,0 +1,28 @@ +{ + "LED_DATA_PIN": 21, + "NUM_SEGMENTS": 32, + "NUM_LEDS_PER_SEGMENT": 12, + "APPEND_DOWN_LIGHTERS": true, + "ADDITIONAL_LEDS": 12, + "DOWNLIGHT_LED_DATA_PIN": 22, + "NUM_DISPLAYS": 7, + "NUM_SEGMENTS_PROGRESS": 16, + "DISPLAY_FOR_SEPARATION_DOT": 3, + "HIGHER_DIGIT_HOUR_DISPLAY": 0, + "FIRST_INTERMEDIATE_DISPLAY": 1, + "LOWER_DIGIT_HOUR_DISPLAY": 2, + "SECOND_INTERMEDIATE_DISPLAY": 3, + "HIGHER_DIGIT_MINUTE_DISPLAY": 4, + "THIRD_INTERMEDIATE_DISPLAY": 5, + "LOWER_DIGIT_MINUTE_DISPLAY": 6, + "ENABLE_LIGHT_SENSOR": false, + "LIGHT_SENSOR_PIN": 34, + "LIGHT_SENSOR_AVERAGE": 15, + "LIGHT_SENSOR_MEDIAN_WIDTH": 5, + "LIGHT_SENSOR_READ_DELAY": 500, + "LIGHT_SENSOR_MIN": 0, + "LIGHT_SENSOR_MAX": 4095, + "LIGHT_SENSOR_SENSITIVITY": 100, + "TIME_MANAGER_DEMO_MODE": false, + "FASTLED_SAFE_DELAY_MS": 20 +} diff --git a/WebConfigUI/src/data/UI_definitions.js b/WebConfigUI/src/data/UI_definitions.js new file mode 100644 index 0000000..5e96535 --- /dev/null +++ b/WebConfigUI/src/data/UI_definitions.js @@ -0,0 +1,386 @@ +export const BaseSettings = [ + { + label: "Enable Blynk functionality", + type: "bool", + target: "ENABLE_BLYNK" + }, + { + label: "Blynk Auth token", + type: "string", + target: "BLYNK_AUTH_TOKEN", + onlyIf: "ENABLE_BLYNK" + }, + { + label: "Blynk server URL", + type: "string", + target: "BLYNK_SERVER", + onlyIf: "ENABLE_BLYNK" + }, + { + label: "Enable OTA Updates", + type: "bool", + target: "OTA_UPLOAD" + }, + { + label: "Host name", + type: "string", + target: "HOST_NAME" + }, + { + label: "Number of retries for WIFI reconnection before smart config is started (if enabled)", + type: "int", + target: "NUM_RETRIES", + min: 1, + max: 1000 + }, + { + label: "Enable Smart config if WIFI connection was lost", + type: "bool", + target: "SMART_CONFIG" + }, + { + label: "NTP server URL", + type: "string", + target: "NTP_SERVER" + }, + { + label: "Enter a configuration string for you timezone according to this website: https://remotemonitoringsystems.ca/time-zone-abbreviations.php", + type: "string", + target: "TIMEZONE_INFO" + }, + { + label: "Time in seconds for the interval in which the time should be synchronized with the time (NTP) server", + type: "int", + target: "TIME_SYNC_INTERVAL", + min: 1, + max: 4294967295 + }, + { + label: "Flash the current time in case a timer is expired instead of flashing 00:00", + type: "bool", + target: "TIMER_FLASH_TIME" + }, + { + label: "Number of flashes until an alarm is considered expired and the system goes back to normal", + type: "int", + target: "TIMER_FLASH_COUNT", + min: 1, + max: 65535 + }, + { + label: "For how long the Display should flash when an alarm was fired in seconds. For example: An Alarm for 12:00 would stop flashing at 12:01 if this was set to 60", + type: "int", + target: "ALARM_NOTIFICATION_PERIOD", + min: 1, + max: 65535 + }, + { + label: "How bright the clock should flash when an alarm or timer was triggered", + type: "int", + target: "NOTIFICATION_BRIGHTNESS", + min: 0, + max: 255 + }, + { + label: "How often the time on the displays is updated in MS", + type: "int", + target: "TIME_UPDATE_INTERVAL", + min: 1, + max: 65535 + }, + { + label: "If turned on the display will show 0 at midnight and 12 otherwise", + type: "bool", + target: "DISPLAY_0_AT_MIDNIGHT", + }, + { + label: "If turned on the higher digit displays will turn off in case they would show 0", + type: "bool", + target: "DISPLAY_SWITCH_OFF_AT_0" + }, + { + label: "If set to true 24 hour format will be used. Only usable if the hour display has all 7 segments", + type: "bool", + target: "USE_24_HOUR_FORMAT" + }, + { + label: "The time it shall take for one iteration of the loading animation", + type: "int", + target: "LOADING_ANIMATION_DURATION", + min: 1, + max: 65535 + }, + { + label: "How fast the brightness interpolation shall react to brightness changes", + type: "int", + target: "BRIGHTNESS_INTERPOLATION", + min: 1, + max: 65535 + }, + { + label: "Target frames per second for the smoothness of animations", + type: "int", + target: "ANIMATION_TARGET_FPS", + min: 1, + max: 255 + }, + { + label: "Length of sooth animation transition from fully on to black and vice versa in percent. NOTE: The higher this number the less obvious easing effects like bounce or elastic will be", + type: "float", + target: "ANIMATION_AFTERGLOW", + min: 0, + max: 1 + }, + { + label: "Length of the dot/s fading animation in MS. One flash fades in and out", + type: "int", + target: "DOT_FLASH_SPEED", + min: 1, + max: 65535 + }, + { + label: "Interval in MS in which the dot/s should flash", + type: "int", + target: "DOT_FLASH_INTERVAL", + min: 1, + max: 65535 + }, + { + label: "Number of separation dots that shall be displayed", + type: "int", + target: "NUM_SEPARATION_DOTS", + min: 0, + max: 2 + }, + { + label: "The time it takes in MS for one digit to morph into another", + type: "int", + target: "DIGIT_ANIMATION_SPEED", + min: 1, + max: 65535 + }, +]; + +export const ColorSettings = [ + { + label: "Color of the hour segments", + type: "color", + target: "HOUR", + min: 0, + max: 16777215 + }, + { + label: "Color of the minute segments", + type: "int", + target: "MINUTE", + min: 0, + max: 16777215 + }, + { + label: "Color of the internal LEDs", + type: "int", + target: "INTERNAL", + min: 0, + max: 16777215 + }, + { + label: "Color of the separation dot LEDs", + type: "int", + target: "SEPARATION_DOT", + min: 0, + max: 16777215 + }, + { + label: "Color of the LEDs for the OTA update progress bar", + type: "int", + target: "OTA_UPDATE", + min: 0, + max: 16777215 + }, + { + label: "Color of the LEDs while searching for a WIFI network", + type: "int", + target: "WIFI_CONNECTING", + min: 0, + max: 16777215 + }, + { + label: "Color of the LEDs signaling a successful WIFI connection", + type: "int", + target: "WIFI_CONNECTION_SUCCESSFUL", + min: 0, + max: 16777215 + }, + { + label: "Color of the LEDs if system is waiting for WIFI smart config", + type: "int", + target: "WIFI_SMART_CONFIG", + min: 0, + max: 16777215 + }, + { + label: "Color of the LEDs signaling an error of some sort", + type: "int", + target: "ERROR", + min: 0, + max: 16777215 + }, +]; + +export const HWSettings = [ + { + label: "Pin to which the led strip data pin is connected to", + type: "int", + target: "LED_DATA_PIN", + min: 0, + max: 42 + }, + { + label: "Total number of segments that have LEDs in the shelf", + type: "int", + target: "NUM_SEGMENTS", + min: 1, + max: 65535 + }, + { + label: "Number of LEDs in each segment", + type: "int", + target: "NUM_LEDS_PER_SEGMENT", + min: 1, + max: 255 + }, + { + label: "If you wired the down lighter LEDs to the end of the LED strips set this to true", + type: "bool", + target: "APPEND_DOWN_LIGHTERS" + }, + { + label: "If you wired the down lighter LEDs to the end of the LED strips enable this", + type: "int", + target: "DOWNLIGHT_LED_DATA_PIN", + min: 0, + max: 42, + onlyIf: "!APPEND_DOWN_LIGHTERS" + }, + { + label: "Number of LEDs For interior lights", + type: "int", + target: "ADDITIONAL_LEDS", + min: 0, + max: 65535 + }, + { + label: "Number of seven segment displays on the clock", + type: "int", + target: "NUM_DISPLAYS", + min: 4, + max: 7 + }, + { + label: "The number of segments to use for displaying a progress bar for the OTA updates. ATTENTION: this also depends on the configured animation which is used", + type: "int", + target: "NUM_SEGMENTS_PROGRESS", + min: 1, + max: 65535 + }, + //TODO: make a dropdown for this + { + label: "The display on which the separation dot should be shown", + type: "enum selector", + target: "DISPLAY_FOR_SEPARATION_DOT", + enum: "DisplayIDs", + min: 1, + max: 7 + }, + //TODO: Make UI element to disable individual displays + { + label: "The indices of the displays in the order they are configured in", + type: "customizable enum", + elements: [ + {target: "HIGHER_DIGIT_HOUR_DISPLAY", label: "Higher digit hour display"}, + {target: "FIRST_INTERMEDIATE_DISPLAY", label: "First intermediate display"}, + {target: "LOWER_DIGIT_HOUR_DISPLAY", label: "Lower digit hour display"}, + {target: "SECOND_INTERMEDIATE_DISPLAY", label: "Second intermediate display"}, + {target: "HIGHER_DIGIT_MINUTE_DISPLAY", label: "Higher digit minute display"}, + {target: "THIRD_INTERMEDIATE_DISPLAY", label: "Third intermediate display"}, + {target: "LOWER_DIGIT_MINUTE_DISPLAY", label: "Lower digit minute display"} + ], + target: "DisplayIDs", + deactivated: -1, + min: 1, + max: 7 + }, + { + label: "Enable automatic brightness adjustments based on a light sensor", + type: "bool", + target: "ENABLE_LIGHT_SENSOR" + }, + { + label: "ADC pin to which the light sensor is connected to", + type: "int", + target: "LIGHT_SENSOR_PIN", + min: 0, + max: 42, + onlyIf: "ENABLE_LIGHT_SENSOR" + }, + { + label: "How many measurements shall be averaged. Higher number -> smoother but slower change", + type: "int", + target: "LIGHT_SENSOR_AVERAGE", + min: 0, + max: 255, + onlyIf: "ENABLE_LIGHT_SENSOR" + }, + { + label: "Width of the median calculation. Higher number -> smoother change Should never be higher than the LIGHT_SENSOR_AVERAGE", + type: "int", + target: "LIGHT_SENSOR_MEDIAN_WIDTH", + min: 0, + max: "LIGHT_SENSOR_AVERAGE", + onlyIf: "ENABLE_LIGHT_SENSOR" + }, + { + label: "Time that should pass in MS before the light sensor is read again. Higher number -> slower adjustments but also changes will be more sudden", + type: "int", + target: "LIGHT_SENSOR_READ_DELAY", + min: 0, + max: 65535, + onlyIf: "ENABLE_LIGHT_SENSOR" + }, + { + label: "AnalogRead value if the light sensor reads complete darkness", + type: "int", + target: "LIGHT_SENSOR_MIN", + min: 0, + max: 4095, + onlyIf: "ENABLE_LIGHT_SENSOR" + }, + { + label: "AnalogRead value if the light sensor reads the brightest", + type: "int", + target: "LIGHT_SENSOR_MAX", + min: 0, + max: 4095, + onlyIf: "ENABLE_LIGHT_SENSOR" + }, + { + label: "How much the can the light sensor values influence the led brightness", + type: "int", + target: "LIGHT_SENSOR_SENSITIVITY", + min: 0, + max: 255, + onlyIf: "ENABLE_LIGHT_SENSOR" + }, + { + label: "(Debugging Feature) Enable for a demo of all the digit transitions", + type: "bool", + target: "TIME_MANAGER_DEMO_MODE" + }, + { + label: "(Debugging Feature) The minimum delay between led updates in MS. Usually anything faster than 20MS causes problems. Increase if your LEDs do not refresh properly", + type: "int", + target: "FASTLED_SAFE_DELAY_MS", + min: 0, + max: 65535, + }, +]; diff --git a/WebConfigUI/src/data/UI_definitions.json b/WebConfigUI/src/data/UI_definitions.json new file mode 100644 index 0000000..17ff458 --- /dev/null +++ b/WebConfigUI/src/data/UI_definitions.json @@ -0,0 +1,384 @@ +{ + "BaseSettings": [ + { + "label": "Enable Blynk functionality", + "type": "bool", + "target": "ENABLE_BLYNK" + }, + { + "label": "Blynk Auth token", + "type": "string", + "target": "BLYNK_AUTH_TOKEN", + "onlyIf": "ENABLE_BLYNK" + }, + { + "label": "Blynk server URL", + "type": "string", + "target": "BLYNK_SERVER", + "onlyIf": "ENABLE_BLYNK" + }, + { + "label": "Enable OTA Updates", + "type": "bool", + "target": "OTA_UPLOAD" + }, + { + "label": "Host name", + "type": "string", + "target": "HOST_NAME" + }, + { + "label": "Number of retries for WIFI reconnection before smart config is started (if enabled)", + "type": "int", + "target": "NUM_RETRIES", + "min": 1, + "max": 1000 + }, + { + "label": "Enable Smart config if WIFI connection was lost", + "type": "bool", + "target": "SMART_CONFIG" + }, + { + "label": "NTP server URL", + "type": "string", + "target": "NTP_SERVER" + }, + { + "label": "Enter a configuration string for you timezone according to this website: https://remotemonitoringsystems.ca/time-zone-abbreviations.php", + "type": "string", + "target": "TIMEZONE_INFO" + }, + { + "label": "Time in seconds for the interval in which the time should be synchronized with the time (NTP) server", + "type": "int", + "target": "TIME_SYNC_INTERVAL", + "min": 1, + "max": 4294967295 + }, + { + "label": "Flash the current time in case a timer is expired instead of flashing 00:00", + "type": "bool", + "target": "TIMER_FLASH_TIME" + }, + { + "label": "Number of flashes until an alarm is considered expired and the system goes back to normal", + "type": "int", + "target": "TIMER_FLASH_COUNT", + "min": 1, + "max": 65535 + }, + { + "label": "For how long the Display should flash when an alarm was fired in seconds. For example: An Alarm for 12:00 would stop flashing at 12:01 if this was set to 60", + "type": "int", + "target": "ALARM_NOTIFICATION_PERIOD", + "min": 1, + "max": 65535 + }, + { + "label": "How bright the clock should flash when an alarm or timer was triggered", + "type": "int", + "target": "NOTIFICATION_BRIGHTNESS", + "min": 0, + "max": 255 + }, + { + "label": "How often the time on the displays is updated in MS", + "type": "int", + "target": "TIME_UPDATE_INTERVAL", + "min": 1, + "max": 65535 + }, + { + "label": "If turned on the display will show 0 at midnight and 12 otherwise", + "type": "bool", + "target": "DISPLAY_0_AT_MIDNIGHT" + }, + { + "label": "If turned on the higher digit displays will turn off in case they would show 0", + "type": "bool", + "target": "DISPLAY_SWITCH_OFF_AT_0" + }, + { + "label": "If set to true 24 hour format will be used. Only usable if the hour display has all 7 segments", + "type": "bool", + "target": "USE_24_HOUR_FORMAT" + }, + { + "label": "The time it shall take for one iteration of the loading animation", + "type": "int", + "target": "LOADING_ANIMATION_DURATION", + "min": 1, + "max": 65535 + }, + { + "label": "How fast the brightness interpolation shall react to brightness changes", + "type": "int", + "target": "BRIGHTNESS_INTERPOLATION", + "min": 1, + "max": 65535 + }, + { + "label": "Target frames per second for the smoothness of animations", + "type": "int", + "target": "ANIMATION_TARGET_FPS", + "min": 1, + "max": 255 + }, + { + "label": "Length of sooth animation transition from fully on to black and vice versa in percent. NOTE: The higher this number the less obvious easing effects like bounce or elastic will be", + "type": "float", + "target": "ANIMATION_AFTERGLOW", + "min": 0, + "max": 1 + }, + { + "label": "Length of the dot/s fading animation in MS. One flash fades in and out", + "type": "int", + "target": "DOT_FLASH_SPEED", + "min": 1, + "max": 65535 + }, + { + "label": "Interval in MS in which the dot/s should flash", + "type": "int", + "target": "DOT_FLASH_INTERVAL", + "min": 1, + "max": 65535 + }, + { + "label": "Number of separation dots that shall be displayed", + "type": "int", + "target": "NUM_SEPARATION_DOTS", + "min": 0, + "max": 2 + }, + { + "label": "The time it takes in MS for one digit to morph into another", + "type": "int", + "target": "DIGIT_ANIMATION_SPEED", + "min": 1, + "max": 65535 + } + ], + "ColorSettings": [ + { + "label": "Color of the hour segments", + "type": "color", + "target": "HOUR", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the minute segments", + "type": "int", + "target": "MINUTE", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the internal LEDs", + "type": "int", + "target": "INTERNAL", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the separation dot LEDs", + "type": "int", + "target": "SEPARATION_DOT", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the LEDs for the OTA update progress bar", + "type": "int", + "target": "OTA_UPDATE", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the LEDs while searching for a WIFI network", + "type": "int", + "target": "WIFI_CONNECTING", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the LEDs signaling a successful WIFI connection", + "type": "int", + "target": "WIFI_CONNECTION_SUCCESSFUL", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the LEDs if system is waiting for WIFI smart config", + "type": "int", + "target": "WIFI_SMART_CONFIG", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the LEDs signaling an error of some sort", + "type": "int", + "target": "ERROR", + "min": 0, + "max": 16777215 + } + ], + "HWSettings": [ + { + "label": "Pin to which the led strip data pin is connected to", + "type": "int", + "target": "LED_DATA_PIN", + "min": 0, + "max": 42 + }, + { + "label": "Total number of segments that have LEDs in the shelf", + "type": "int", + "target": "NUM_SEGMENTS", + "min": 1, + "max": 65535 + }, + { + "label": "Number of LEDs in each segment", + "type": "int", + "target": "NUM_LEDS_PER_SEGMENT", + "min": 1, + "max": 255 + }, + { + "label": "If you wired the down lighter LEDs to the end of the LED strips set this to true", + "type": "bool", + "target": "APPEND_DOWN_LIGHTERS" + }, + { + "label": "If you wired the down lighter LEDs to the end of the LED strips enable this", + "type": "int", + "target": "DOWNLIGHT_LED_DATA_PIN", + "min": 0, + "max": 42, + "onlyIf": "!APPEND_DOWN_LIGHTERS" + }, + { + "label": "Number of LEDs For interior lights", + "type": "int", + "target": "ADDITIONAL_LEDS", + "min": 0, + "max": 65535 + }, + { + "label": "Number of seven segment displays on the clock", + "type": "int", + "target": "NUM_DISPLAYS", + "min": 4, + "max": 7 + }, + { + "label": "The number of segments to use for displaying a progress bar for the OTA updates. ATTENTION: this also depends on the configured animation which is used", + "type": "int", + "target": "NUM_SEGMENTS_PROGRESS", + "min": 1, + "max": 65535 + }, + { + "label": "The display on which the separation dot should be shown", + "type": "enum selector", + "target": "DISPLAY_FOR_SEPARATION_DOT", + "enum": "DisplayIDs", + "min": 1, + "max": 7 + }, + { + "label": "The indices of the displays in the order they are configured in", + "type": "customizable enum", + "elements": [ + {"target": "HIGHER_DIGIT_HOUR_DISPLAY", "label": "Higher digit hour display"}, + {"target": "FIRST_INTERMEDIATE_DISPLAY", "label": "First intermediate display"}, + {"target": "LOWER_DIGIT_HOUR_DISPLAY", "label": "Lower digit hour display"}, + {"target": "SECOND_INTERMEDIATE_DISPLAY", "label": "Second intermediate display"}, + {"target": "HIGHER_DIGIT_MINUTE_DISPLAY", "label": "Higher digit minute display"}, + {"target": "THIRD_INTERMEDIATE_DISPLAY", "label": "Third intermediate display"}, + {"target": "LOWER_DIGIT_MINUTE_DISPLAY", "label": "Lower digit minute display"} + ], + "target": "DisplayIDs", + "deactivated": -1, + "min": 1, + "max": 7 + }, + { + "label": "Enable automatic brightness adjustments based on a light sensor", + "type": "bool", + "target": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "ADC pin to which the light sensor is connected to", + "type": "int", + "target": "LIGHT_SENSOR_PIN", + "min": 0, + "max": 42, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "How many measurements shall be averaged. Higher number -> smoother but slower change", + "type": "int", + "target": "LIGHT_SENSOR_AVERAGE", + "min": 0, + "max": 255, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "Width of the median calculation. Higher number -> smoother change Should never be higher than the LIGHT_SENSOR_AVERAGE", + "type": "int", + "target": "LIGHT_SENSOR_MEDIAN_WIDTH", + "min": 0, + "max": "LIGHT_SENSOR_AVERAGE", + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "Time that should pass in MS before the light sensor is read again. Higher number -> slower adjustments but also changes will be more sudden", + "type": "int", + "target": "LIGHT_SENSOR_READ_DELAY", + "min": 0, + "max": 65535, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "AnalogRead value if the light sensor reads complete darkness", + "type": "int", + "target": "LIGHT_SENSOR_MIN", + "min": 0, + "max": 4095, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "AnalogRead value if the light sensor reads the brightest", + "type": "int", + "target": "LIGHT_SENSOR_MAX", + "min": 0, + "max": 4095, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "How much the can the light sensor values influence the led brightness", + "type": "int", + "target": "LIGHT_SENSOR_SENSITIVITY", + "min": 0, + "max": 255, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "(Debugging Feature) Enable for a demo of all the digit transitions", + "type": "bool", + "target": "TIME_MANAGER_DEMO_MODE" + }, + { + "label": "(Debugging Feature) The minimum delay between led updates in MS. Usually anything faster than 20MS causes problems. Increase if your LEDs do not refresh properly", + "type": "int", + "target": "FASTLED_SAFE_DELAY_MS", + "min": 0, + "max": 65535 + } + ] +} diff --git a/WebConfigUI/src/sites/Navigation.jsx b/WebConfigUI/src/sites/Navigation.jsx index bca51c7..9779584 100644 --- a/WebConfigUI/src/sites/Navigation.jsx +++ b/WebConfigUI/src/sites/Navigation.jsx @@ -4,8 +4,9 @@ import React from "react"; const pages = [ {link: "/WIFI", label: "WIFI Setup"}, - {link: "/Settings", label: "Settings"}, - {link: "/", label: "Colors"}, + {link: "/BaseSettings", label: "Base Settings"}, + {link: "/Colors", label: "Colors"}, + {link: "/HWSetup", label: "Hardware Setup"}, {link: "/", label: "Display Setup"}, {link: "/", label: "Animation Setup"}, ]; @@ -14,7 +15,7 @@ function NavigationMenu(props) { return (

LED Pixel Clock

-
+
{pages.map((page) =>( diff --git a/WebConfigUI/src/sites/Settings.jsx b/WebConfigUI/src/sites/Settings.jsx index e05900a..c6cb20f 100644 --- a/WebConfigUI/src/sites/Settings.jsx +++ b/WebConfigUI/src/sites/Settings.jsx @@ -1,125 +1,17 @@ -import React, { useEffect, useState } from "react"; -import Dialog from '../components/Dialog' -import settingsEntries from '../data/BaseConfig_definition' -import settingsValues from '../data/BaseConfig.json' +import React from "react"; +import SettingsForm from '../components/SettingsForm' -const loadConfig = () => { - const settings = [...settingsEntries]; - settingsEntries.forEach((element, index) => { - settings[index].value = settingsValues[element.target]; - }); - return settings; -} - -const Settings = () => { - const [Settings, setSettings] = useState(loadConfig()); - const [dialogShown, setDialogShown] = useState(false); - - const handleSubmit = (event) => { - event.preventDefault(); - setDialogShown(true); - } - - const handleDialogClose = (result) => { - setDialogShown(false); - if(result == true) - { - - } - } - - const valueChangedHandler = (event, target) => { - const targetSettingIndex = Settings.findIndex(p => p.target === target); - - const setting = { - ...Settings[targetSettingIndex] - }; - - const newValue = event.target.value; - - switch(event.target.type) - { - case "checkbox": - setting.value = event.target.checked; - break; - case "number": - if(isNaN(newValue) || newValue < setting.min || newValue > setting.max) - { - return; - } - default: - setting.value = newValue; - break; - } - - const settings = [...Settings]; - settings[targetSettingIndex] = setting; - setSettings(settings); - } - - const isDisabled = (setting) => { - if("onlyIf" in setting) - { - const targetSettingIndex = Settings.findIndex(p => p.target === setting.onlyIf); - if(Settings[targetSettingIndex].type === "bool") - { - return !Settings[targetSettingIndex].value; - } - } - return false; - } - - const settingsForm = Settings.map((settingEntry, index) => { - let Disabled = isDisabled(settingEntry); - switch(settingEntry.type) - { - case "string": - return ( -
- -
- valueChangedHandler(e, settingEntry.target)} autoComplete="off" type="text"/> -
-
- ) - case "bool": - return ( -
- valueChangedHandler(e, settingEntry.target)}/> - -
- ) - case "int": - return ( -
- -
- valueChangedHandler(e, settingEntry.target)} autoComplete="off" type="number"/> -
-
- ) - } - }) +const Settings = (props) => { return ( -
-
-
-

General Settings

-
-
- {settingsForm} -
- -
-
-
- - To save the changes The device will reboot. You may loose connection to this interface temporarily. - -
+
+
+
+

{props.children}

+
+ +
+
); } -export default Settings; \ No newline at end of file +export default Settings; diff --git a/WebConfigUI/src/sites/WIFI-setup.jsx b/WebConfigUI/src/sites/WIFI-setup.jsx index d26014d..ed085c2 100644 --- a/WebConfigUI/src/sites/WIFI-setup.jsx +++ b/WebConfigUI/src/sites/WIFI-setup.jsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import Dialog from '../components/Dialog' -const WIFI_new = () => { +const WIFI_new = (props) => { const [SSID, setSSID] = useState(""); const [PW, setPW] = useState(""); const [createAP, setCreateAP] = useState(false); @@ -28,12 +28,10 @@ const WIFI_new = () => { setDialogShown(false); if(result == true) { - console.log("SSID: " + SSID); - console.log("PW: " + PW); - console.log("Create AP: " + createAP); - setSSID(""); + setSSID(""); setPW(""); setCreateAP(false); + props.onSubmit(props.domain, JSON.stringify({SSID: SSID, PW: PW, CreateAP: createAP})); } } @@ -74,4 +72,4 @@ const WIFI_new = () => { ) } -export default WIFI_new; \ No newline at end of file +export default WIFI_new; diff --git a/WebConfigUI/tailwind.config.js b/WebConfigUI/tailwind.config.js index 243114f..92aa104 100644 --- a/WebConfigUI/tailwind.config.js +++ b/WebConfigUI/tailwind.config.js @@ -1,8 +1,9 @@ module.exports = { purge: { - enabled: true, + enabled: process.env.PURGE_FILES == 1, content: ["./src/**/*.jsx", "./index.html"], }, + darkMode: false, // or 'media' or 'class' theme: {}, variants: { diff --git a/WebConfigUI/webpack.config.js b/WebConfigUI/webpack.config.js index 207b11a..0e38f2d 100644 --- a/WebConfigUI/webpack.config.js +++ b/WebConfigUI/webpack.config.js @@ -3,7 +3,6 @@ const HtmlWebpackPlugin = require("html-webpack-plugin"); const CompressionPlugin = require("compression-webpack-plugin"); module.exports = { - mode: 'production',//production; development module: { rules: [ { From b058a87eb03fbbec9f45e4f9b1643930c26ea400 Mon Sep 17 00:00:00 2001 From: "flo.laschober@gmail.com" Date: Mon, 26 Apr 2021 19:47:28 +0200 Subject: [PATCH 05/13] Unified indentation for all files --- WebConfigUI/babel.config.json | 2 +- WebConfigUI/index.html | 18 +- WebConfigUI/package.json | 78 +- WebConfigUI/postcss.config.js | 8 +- WebConfigUI/src/App.jsx | 12 +- WebConfigUI/src/components/Dialog.jsx | 154 ++-- WebConfigUI/src/components/SettingsForm.jsx | 28 +- WebConfigUI/src/css/tailwind.css | 60 +- WebConfigUI/src/data/BaseConfig.json | 52 +- WebConfigUI/src/data/HWConfig.json | 52 +- WebConfigUI/src/data/UI_definitions.js | 386 ---------- WebConfigUI/src/data/UI_definitions.json | 764 ++++++++++---------- WebConfigUI/src/sites/Navigation.jsx | 56 +- WebConfigUI/src/sites/Settings.jsx | 4 +- WebConfigUI/src/sites/WIFI-setup.jsx | 2 +- WebConfigUI/tailwind.config.js | 24 +- WebConfigUI/webpack.config.js | 86 +-- WebConfigUI/workspace.code-workspace | 12 + data/BaseConfig.json | 104 +-- data/ColorConfig.json | 11 + data/HWConfig.json | 28 + 21 files changed, 777 insertions(+), 1164 deletions(-) delete mode 100644 WebConfigUI/src/data/UI_definitions.js create mode 100644 WebConfigUI/workspace.code-workspace create mode 100644 data/ColorConfig.json create mode 100644 data/HWConfig.json diff --git a/WebConfigUI/babel.config.json b/WebConfigUI/babel.config.json index 86ef210..e07bf46 100644 --- a/WebConfigUI/babel.config.json +++ b/WebConfigUI/babel.config.json @@ -1,3 +1,3 @@ { - "presets": ["@babel/preset-env", "@babel/preset-react"] + "presets": ["@babel/preset-env", "@babel/preset-react"] } diff --git a/WebConfigUI/index.html b/WebConfigUI/index.html index 77cf4f6..b037a25 100644 --- a/WebConfigUI/index.html +++ b/WebConfigUI/index.html @@ -1,12 +1,12 @@ - - - - LED Pixel Clock Configuration - - -
- - + + + + LED Pixel Clock Configuration + + +
+ + diff --git a/WebConfigUI/package.json b/WebConfigUI/package.json index 7fba29c..cad4407 100644 --- a/WebConfigUI/package.json +++ b/WebConfigUI/package.json @@ -1,41 +1,41 @@ { - "name": "web-config-ui", - "version": "1.0.0", - "description": "", - "main": "src/main.jsx", - "scripts": { - "dev": "set NODE_ENV=development && set PURGE_FILES=0 && vite", - "build": "set NODE_ENV=production && set PURGE_FILES=1 && webpack --mode production", - "start": "set NODE_ENV=development && set PURGE_FILES=0 && webpack serve --mode development" - }, - "author": "", - "license": "ISC", - "devDependencies": { - "@babel/core": "^7.13.15", - "@babel/preset-env": "^7.13.15", - "@babel/preset-react": "^7.13.13", - "@headlessui/react": "^1.0.0", - "@tailwindcss/forms": "^0.3.2", - "autoprefixer": "^10.2.5", - "babel-loader": "^8.2.2", - "brotli-webpack-plugin": "^1.1.0", - "compression-webpack-plugin": "^7.1.2", - "css-loader": "^5.2.4", - "html-webpack-plugin": "^5.3.1", - "postcss": "^8.2.10", - "postcss-loader": "^5.2.0", - "postcss-preset-env": "^6.7.0", - "style-loader": "^2.0.0", - "tailwindcss": "^2.1.1", - "vite": "^2.2.1", - "webpack": "^5.34.0", - "webpack-cli": "^4.6.0", - "webpack-dev-server": "^3.11.2" - }, - "dependencies": { - "esbuild": "^0.11.13", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "react-router-dom": "^5.2.0" - } + "name": "web-config-ui", + "version": "1.0.0", + "description": "", + "main": "src/main.jsx", + "scripts": { + "dev": "set NODE_ENV=development && set PURGE_FILES=0 && vite", + "build": "set NODE_ENV=production && set PURGE_FILES=1 && webpack --mode production", + "start": "set NODE_ENV=development && set PURGE_FILES=0 && webpack serve --mode development" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/core": "^7.13.15", + "@babel/preset-env": "^7.13.15", + "@babel/preset-react": "^7.13.13", + "@headlessui/react": "^1.0.0", + "@tailwindcss/forms": "^0.3.2", + "autoprefixer": "^10.2.5", + "babel-loader": "^8.2.2", + "brotli-webpack-plugin": "^1.1.0", + "compression-webpack-plugin": "^7.1.2", + "css-loader": "^5.2.4", + "html-webpack-plugin": "^5.3.1", + "postcss": "^8.2.10", + "postcss-loader": "^5.2.0", + "postcss-preset-env": "^6.7.0", + "style-loader": "^2.0.0", + "tailwindcss": "^2.1.1", + "vite": "^2.2.1", + "webpack": "^5.34.0", + "webpack-cli": "^4.6.0", + "webpack-dev-server": "^3.11.2" + }, + "dependencies": { + "esbuild": "^0.11.13", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-router-dom": "^5.2.0" + } } diff --git a/WebConfigUI/postcss.config.js b/WebConfigUI/postcss.config.js index a288142..d64cc37 100644 --- a/WebConfigUI/postcss.config.js +++ b/WebConfigUI/postcss.config.js @@ -1,6 +1,6 @@ module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, }; diff --git a/WebConfigUI/src/App.jsx b/WebConfigUI/src/App.jsx index 66c1b00..ac910fc 100644 --- a/WebConfigUI/src/App.jsx +++ b/WebConfigUI/src/App.jsx @@ -5,7 +5,7 @@ import Navigation from "./sites/Navigation"; import WIFI_setup from "./sites/WIFI-setup"; import Settings from "./sites/Settings"; -import UI_definitions from './data/UI_definitions.json' +import {BaseSettings, ColorSettings, HWSettings} from './data/UI_definitions' // Development function for testing locally @@ -52,8 +52,8 @@ export default function App() { useEffect(() => { getConfigFile('/BaseConfig.json', setBaseConfig); - getConfigFile('/ColorConfig.json', setColorConfig); - getConfigFile('/HWConfig.json', setHWConfig); + getConfigFile('/ColorConfig.json', setColorConfig); + getConfigFile('/HWConfig.json', setHWConfig); }, []); const handleSubmit = (domain, values) => { @@ -68,15 +68,15 @@ export default function App() { } /> - + General Settings } /> - + Color Settings } /> - + Hardware Settings } /> diff --git a/WebConfigUI/src/components/Dialog.jsx b/WebConfigUI/src/components/Dialog.jsx index 690ccd7..9e3daa8 100644 --- a/WebConfigUI/src/components/Dialog.jsx +++ b/WebConfigUI/src/components/Dialog.jsx @@ -1,87 +1,87 @@ import { Dialog, Transition } from "@headlessui/react"; -import React, { Fragment, useEffect, useRef, useState } from "react"; +import React, { Fragment, useRef} from "react"; export default function MyModal(props) { - let OkayButtonRef = useRef(); + let OkayButtonRef = useRef(); - props.type == "OK/Cancel" + props.type == "OK/Cancel" - let cancelButton = "" - let okayButtonLabel = "OK" + let cancelButton = "" + let okayButtonLabel = "OK" - if(props.confirmMessage) - { - okayButtonLabel = props.confirmMessage - } + if(props.confirmMessage) + { + okayButtonLabel = props.confirmMessage + } - if(props.type == "OK/Cancel") - { - cancelButton = ( - - ) - } + if(props.type == "OK/Cancel") + { + cancelButton = ( + + ) + } - return ( - - props.onClose(false)} - > -
- - - - -
- - {props.title} - - -

- {props.children} -

-
+ return ( + + props.onClose(false)} + > +
+ + + + +
+ + {props.title} + + +

+ {props.children} +

+
-
- - {cancelButton} -
-
-
-
-
-
- ); +
+ + {cancelButton} +
+
+
+
+
+
+ ); } diff --git a/WebConfigUI/src/components/SettingsForm.jsx b/WebConfigUI/src/components/SettingsForm.jsx index f2d46cb..98a5346 100644 --- a/WebConfigUI/src/components/SettingsForm.jsx +++ b/WebConfigUI/src/components/SettingsForm.jsx @@ -3,15 +3,15 @@ import Dialog from '../components/Dialog' const loadConfig = (UIDefinition, InitialValues) => { - const settings = [...UIDefinition]; - UIDefinition.forEach((element, index) => { + const settings = [...UIDefinition]; + UIDefinition.forEach((element, index) => { settings[index].value = InitialValues[element.target]; - }); - return settings; + }); + return settings; } const SettingsForm = (props) => { - const [Settings, setSettings] = useState(loadConfig(props.UIDefinition, props.InitialValues)); + const [Settings, setSettings] = useState(loadConfig(props.UIDefinition, props.InitialValues)); const [dialogShown, setDialogShown] = useState(false); const handleSubmit = (event) => { @@ -31,7 +31,7 @@ const SettingsForm = (props) => { } } - const valueChangedHandler = (event, target) => { + const valueChangedHandler = (event, target) => { const targetSettingIndex = Settings.findIndex(p => p.target === target); const setting = { @@ -57,9 +57,9 @@ const SettingsForm = (props) => { const settings = [...Settings]; settings[targetSettingIndex] = setting; setSettings(settings); - } + } - const isDisabled = (setting) => { + const isDisabled = (setting) => { if("onlyIf" in setting) { const invert = setting.onlyIf.startsWith("!"); @@ -79,7 +79,7 @@ const SettingsForm = (props) => { } } return false; - } + } const toColor = (num) => { num >>>= 0; @@ -89,7 +89,7 @@ const SettingsForm = (props) => { return "rgba(" + [r, g, b, 1].join(",") + ")"; } - const settingsForm = Settings.map((settingEntry, index) => { + const settingsForm = Settings.map((settingEntry, index) => { let Disabled = isDisabled(settingEntry); let stepSize = 1; switch(settingEntry.type) @@ -130,7 +130,7 @@ const SettingsForm = (props) => {
- + valueChangedHandler(e, settingEntry.target)} type="range"/> valueChangedHandler(e, settingEntry.target)} type="range"/> valueChangedHandler(e, settingEntry.target)} type="range"/> @@ -138,8 +138,8 @@ const SettingsForm = (props) => {
) } - }) - return ( + }) + return (
{settingsForm} @@ -154,7 +154,7 @@ const SettingsForm = (props) => { To save the changes the device may reboot. You may loose connection to this interface temporarily.
- ); + ); } export default SettingsForm; diff --git a/WebConfigUI/src/css/tailwind.css b/WebConfigUI/src/css/tailwind.css index 4b93d89..3fa03a9 100644 --- a/WebConfigUI/src/css/tailwind.css +++ b/WebConfigUI/src/css/tailwind.css @@ -4,39 +4,39 @@ @layer base { - [type='text'], - [type='email'], - [type='url'], - [type='password'], - [type='number'], - [type='date'], - [type='datetime-local'], - [type='month'], - [type='search'], - [type='tel'], - [type='time'], - [type='week'], - [multiple], - textarea, - select { - @apply w-full border-gray-300 rounded-lg shadow-sm focus:border-indigo-500 focus:ring-indigo-500 transform transition; - } - [type='checkbox'], - [type='radio'] { - @apply rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 transform transition; - } + [type='text'], + [type='email'], + [type='url'], + [type='password'], + [type='number'], + [type='date'], + [type='datetime-local'], + [type='month'], + [type='search'], + [type='tel'], + [type='time'], + [type='week'], + [multiple], + textarea, + select { + @apply w-full border-gray-300 rounded-lg shadow-sm focus:border-indigo-500 focus:ring-indigo-500 transform transition; + } + [type='checkbox'], + [type='radio'] { + @apply rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 transform transition; + } } @layer components { - .btn { - @apply justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 - } + .btn { + @apply justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 + } - .btn-primary { - @apply text-white bg-indigo-600 hover:bg-indigo-700 - } + .btn-primary { + @apply text-white bg-indigo-600 hover:bg-indigo-700 + } - .btn-secondary { - @apply text-indigo-900 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-400 - } + .btn-secondary { + @apply text-indigo-900 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-400 + } } diff --git a/WebConfigUI/src/data/BaseConfig.json b/WebConfigUI/src/data/BaseConfig.json index 155449e..180d34a 100644 --- a/WebConfigUI/src/data/BaseConfig.json +++ b/WebConfigUI/src/data/BaseConfig.json @@ -1,28 +1,28 @@ { - "ENABLE_BLYNK": true, - "BLYNK_AUTH_TOKEN": "AUTH_TOKEN_GOES_HERE", - "BLYNK_SERVER": "blynk-cloud.com", - "OTA_UPLOAD": true, - "HOST_NAME": "LED-Shelf", - "NUM_RETRIES": 50, - "SMART_CONFIG": true, - "NTP_SERVER": "pool.ntp.org", - "TIMEZONE_INFO": "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00", - "TIME_SYNC_INTERVAL": 1800, - "TIMER_FLASH_TIME": false, - "TIMER_FLASH_COUNT": 10, - "ALARM_NOTIFICATION_PERIOD": 600, - "NOTIFICATION_BRIGHTNESS": 125, - "TIME_UPDATE_INTERVAL": 500, - "DISPLAY_0_AT_MIDNIGHT": false, - "DISPLAY_SWITCH_OFF_AT_0": false, - "USE_24_HOUR_FORMAT": false, - "LOADING_ANIMATION_DURATION": 3000, - "BRIGHTNESS_INTERPOLATION": 3000, - "ANIMATION_TARGET_FPS": 60, - "ANIMATION_AFTERGLOW": 0.2, - "DOT_FLASH_SPEED": 2000, - "DOT_FLASH_INTERVAL": 4000, - "NUM_SEPARATION_DOTS": 2, - "DIGIT_ANIMATION_SPEED": 900 + "ENABLE_BLYNK": true, + "BLYNK_AUTH_TOKEN": "AUTH_TOKEN_GOES_HERE", + "BLYNK_SERVER": "blynk-cloud.com", + "OTA_UPLOAD": true, + "HOST_NAME": "LED-Shelf", + "NUM_RETRIES": 50, + "SMART_CONFIG": true, + "NTP_SERVER": "pool.ntp.org", + "TIMEZONE_INFO": "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00", + "TIME_SYNC_INTERVAL": 1800, + "TIMER_FLASH_TIME": false, + "TIMER_FLASH_COUNT": 10, + "ALARM_NOTIFICATION_PERIOD": 600, + "NOTIFICATION_BRIGHTNESS": 125, + "TIME_UPDATE_INTERVAL": 500, + "DISPLAY_0_AT_MIDNIGHT": false, + "DISPLAY_SWITCH_OFF_AT_0": false, + "USE_24_HOUR_FORMAT": false, + "LOADING_ANIMATION_DURATION": 3000, + "BRIGHTNESS_INTERPOLATION": 3000, + "ANIMATION_TARGET_FPS": 60, + "ANIMATION_AFTERGLOW": 0.2, + "DOT_FLASH_SPEED": 2000, + "DOT_FLASH_INTERVAL": 4000, + "NUM_SEPARATION_DOTS": 2, + "DIGIT_ANIMATION_SPEED": 900 } \ No newline at end of file diff --git a/WebConfigUI/src/data/HWConfig.json b/WebConfigUI/src/data/HWConfig.json index f1a1c2f..b8a8f78 100644 --- a/WebConfigUI/src/data/HWConfig.json +++ b/WebConfigUI/src/data/HWConfig.json @@ -1,28 +1,28 @@ { - "LED_DATA_PIN": 21, - "NUM_SEGMENTS": 32, - "NUM_LEDS_PER_SEGMENT": 12, - "APPEND_DOWN_LIGHTERS": true, - "ADDITIONAL_LEDS": 12, - "DOWNLIGHT_LED_DATA_PIN": 22, - "NUM_DISPLAYS": 7, - "NUM_SEGMENTS_PROGRESS": 16, - "DISPLAY_FOR_SEPARATION_DOT": 3, - "HIGHER_DIGIT_HOUR_DISPLAY": 0, - "FIRST_INTERMEDIATE_DISPLAY": 1, - "LOWER_DIGIT_HOUR_DISPLAY": 2, - "SECOND_INTERMEDIATE_DISPLAY": 3, - "HIGHER_DIGIT_MINUTE_DISPLAY": 4, - "THIRD_INTERMEDIATE_DISPLAY": 5, - "LOWER_DIGIT_MINUTE_DISPLAY": 6, - "ENABLE_LIGHT_SENSOR": false, - "LIGHT_SENSOR_PIN": 34, - "LIGHT_SENSOR_AVERAGE": 15, - "LIGHT_SENSOR_MEDIAN_WIDTH": 5, - "LIGHT_SENSOR_READ_DELAY": 500, - "LIGHT_SENSOR_MIN": 0, - "LIGHT_SENSOR_MAX": 4095, - "LIGHT_SENSOR_SENSITIVITY": 100, - "TIME_MANAGER_DEMO_MODE": false, - "FASTLED_SAFE_DELAY_MS": 20 + "LED_DATA_PIN": 21, + "NUM_SEGMENTS": 32, + "NUM_LEDS_PER_SEGMENT": 12, + "APPEND_DOWN_LIGHTERS": true, + "ADDITIONAL_LEDS": 12, + "DOWNLIGHT_LED_DATA_PIN": 22, + "NUM_DISPLAYS": 7, + "NUM_SEGMENTS_PROGRESS": 16, + "DISPLAY_FOR_SEPARATION_DOT": 3, + "HIGHER_DIGIT_HOUR_DISPLAY": 0, + "FIRST_INTERMEDIATE_DISPLAY": 1, + "LOWER_DIGIT_HOUR_DISPLAY": 2, + "SECOND_INTERMEDIATE_DISPLAY": 3, + "HIGHER_DIGIT_MINUTE_DISPLAY": 4, + "THIRD_INTERMEDIATE_DISPLAY": 5, + "LOWER_DIGIT_MINUTE_DISPLAY": 6, + "ENABLE_LIGHT_SENSOR": false, + "LIGHT_SENSOR_PIN": 34, + "LIGHT_SENSOR_AVERAGE": 15, + "LIGHT_SENSOR_MEDIAN_WIDTH": 5, + "LIGHT_SENSOR_READ_DELAY": 500, + "LIGHT_SENSOR_MIN": 0, + "LIGHT_SENSOR_MAX": 4095, + "LIGHT_SENSOR_SENSITIVITY": 100, + "TIME_MANAGER_DEMO_MODE": false, + "FASTLED_SAFE_DELAY_MS": 20 } diff --git a/WebConfigUI/src/data/UI_definitions.js b/WebConfigUI/src/data/UI_definitions.js deleted file mode 100644 index 5e96535..0000000 --- a/WebConfigUI/src/data/UI_definitions.js +++ /dev/null @@ -1,386 +0,0 @@ -export const BaseSettings = [ - { - label: "Enable Blynk functionality", - type: "bool", - target: "ENABLE_BLYNK" - }, - { - label: "Blynk Auth token", - type: "string", - target: "BLYNK_AUTH_TOKEN", - onlyIf: "ENABLE_BLYNK" - }, - { - label: "Blynk server URL", - type: "string", - target: "BLYNK_SERVER", - onlyIf: "ENABLE_BLYNK" - }, - { - label: "Enable OTA Updates", - type: "bool", - target: "OTA_UPLOAD" - }, - { - label: "Host name", - type: "string", - target: "HOST_NAME" - }, - { - label: "Number of retries for WIFI reconnection before smart config is started (if enabled)", - type: "int", - target: "NUM_RETRIES", - min: 1, - max: 1000 - }, - { - label: "Enable Smart config if WIFI connection was lost", - type: "bool", - target: "SMART_CONFIG" - }, - { - label: "NTP server URL", - type: "string", - target: "NTP_SERVER" - }, - { - label: "Enter a configuration string for you timezone according to this website: https://remotemonitoringsystems.ca/time-zone-abbreviations.php", - type: "string", - target: "TIMEZONE_INFO" - }, - { - label: "Time in seconds for the interval in which the time should be synchronized with the time (NTP) server", - type: "int", - target: "TIME_SYNC_INTERVAL", - min: 1, - max: 4294967295 - }, - { - label: "Flash the current time in case a timer is expired instead of flashing 00:00", - type: "bool", - target: "TIMER_FLASH_TIME" - }, - { - label: "Number of flashes until an alarm is considered expired and the system goes back to normal", - type: "int", - target: "TIMER_FLASH_COUNT", - min: 1, - max: 65535 - }, - { - label: "For how long the Display should flash when an alarm was fired in seconds. For example: An Alarm for 12:00 would stop flashing at 12:01 if this was set to 60", - type: "int", - target: "ALARM_NOTIFICATION_PERIOD", - min: 1, - max: 65535 - }, - { - label: "How bright the clock should flash when an alarm or timer was triggered", - type: "int", - target: "NOTIFICATION_BRIGHTNESS", - min: 0, - max: 255 - }, - { - label: "How often the time on the displays is updated in MS", - type: "int", - target: "TIME_UPDATE_INTERVAL", - min: 1, - max: 65535 - }, - { - label: "If turned on the display will show 0 at midnight and 12 otherwise", - type: "bool", - target: "DISPLAY_0_AT_MIDNIGHT", - }, - { - label: "If turned on the higher digit displays will turn off in case they would show 0", - type: "bool", - target: "DISPLAY_SWITCH_OFF_AT_0" - }, - { - label: "If set to true 24 hour format will be used. Only usable if the hour display has all 7 segments", - type: "bool", - target: "USE_24_HOUR_FORMAT" - }, - { - label: "The time it shall take for one iteration of the loading animation", - type: "int", - target: "LOADING_ANIMATION_DURATION", - min: 1, - max: 65535 - }, - { - label: "How fast the brightness interpolation shall react to brightness changes", - type: "int", - target: "BRIGHTNESS_INTERPOLATION", - min: 1, - max: 65535 - }, - { - label: "Target frames per second for the smoothness of animations", - type: "int", - target: "ANIMATION_TARGET_FPS", - min: 1, - max: 255 - }, - { - label: "Length of sooth animation transition from fully on to black and vice versa in percent. NOTE: The higher this number the less obvious easing effects like bounce or elastic will be", - type: "float", - target: "ANIMATION_AFTERGLOW", - min: 0, - max: 1 - }, - { - label: "Length of the dot/s fading animation in MS. One flash fades in and out", - type: "int", - target: "DOT_FLASH_SPEED", - min: 1, - max: 65535 - }, - { - label: "Interval in MS in which the dot/s should flash", - type: "int", - target: "DOT_FLASH_INTERVAL", - min: 1, - max: 65535 - }, - { - label: "Number of separation dots that shall be displayed", - type: "int", - target: "NUM_SEPARATION_DOTS", - min: 0, - max: 2 - }, - { - label: "The time it takes in MS for one digit to morph into another", - type: "int", - target: "DIGIT_ANIMATION_SPEED", - min: 1, - max: 65535 - }, -]; - -export const ColorSettings = [ - { - label: "Color of the hour segments", - type: "color", - target: "HOUR", - min: 0, - max: 16777215 - }, - { - label: "Color of the minute segments", - type: "int", - target: "MINUTE", - min: 0, - max: 16777215 - }, - { - label: "Color of the internal LEDs", - type: "int", - target: "INTERNAL", - min: 0, - max: 16777215 - }, - { - label: "Color of the separation dot LEDs", - type: "int", - target: "SEPARATION_DOT", - min: 0, - max: 16777215 - }, - { - label: "Color of the LEDs for the OTA update progress bar", - type: "int", - target: "OTA_UPDATE", - min: 0, - max: 16777215 - }, - { - label: "Color of the LEDs while searching for a WIFI network", - type: "int", - target: "WIFI_CONNECTING", - min: 0, - max: 16777215 - }, - { - label: "Color of the LEDs signaling a successful WIFI connection", - type: "int", - target: "WIFI_CONNECTION_SUCCESSFUL", - min: 0, - max: 16777215 - }, - { - label: "Color of the LEDs if system is waiting for WIFI smart config", - type: "int", - target: "WIFI_SMART_CONFIG", - min: 0, - max: 16777215 - }, - { - label: "Color of the LEDs signaling an error of some sort", - type: "int", - target: "ERROR", - min: 0, - max: 16777215 - }, -]; - -export const HWSettings = [ - { - label: "Pin to which the led strip data pin is connected to", - type: "int", - target: "LED_DATA_PIN", - min: 0, - max: 42 - }, - { - label: "Total number of segments that have LEDs in the shelf", - type: "int", - target: "NUM_SEGMENTS", - min: 1, - max: 65535 - }, - { - label: "Number of LEDs in each segment", - type: "int", - target: "NUM_LEDS_PER_SEGMENT", - min: 1, - max: 255 - }, - { - label: "If you wired the down lighter LEDs to the end of the LED strips set this to true", - type: "bool", - target: "APPEND_DOWN_LIGHTERS" - }, - { - label: "If you wired the down lighter LEDs to the end of the LED strips enable this", - type: "int", - target: "DOWNLIGHT_LED_DATA_PIN", - min: 0, - max: 42, - onlyIf: "!APPEND_DOWN_LIGHTERS" - }, - { - label: "Number of LEDs For interior lights", - type: "int", - target: "ADDITIONAL_LEDS", - min: 0, - max: 65535 - }, - { - label: "Number of seven segment displays on the clock", - type: "int", - target: "NUM_DISPLAYS", - min: 4, - max: 7 - }, - { - label: "The number of segments to use for displaying a progress bar for the OTA updates. ATTENTION: this also depends on the configured animation which is used", - type: "int", - target: "NUM_SEGMENTS_PROGRESS", - min: 1, - max: 65535 - }, - //TODO: make a dropdown for this - { - label: "The display on which the separation dot should be shown", - type: "enum selector", - target: "DISPLAY_FOR_SEPARATION_DOT", - enum: "DisplayIDs", - min: 1, - max: 7 - }, - //TODO: Make UI element to disable individual displays - { - label: "The indices of the displays in the order they are configured in", - type: "customizable enum", - elements: [ - {target: "HIGHER_DIGIT_HOUR_DISPLAY", label: "Higher digit hour display"}, - {target: "FIRST_INTERMEDIATE_DISPLAY", label: "First intermediate display"}, - {target: "LOWER_DIGIT_HOUR_DISPLAY", label: "Lower digit hour display"}, - {target: "SECOND_INTERMEDIATE_DISPLAY", label: "Second intermediate display"}, - {target: "HIGHER_DIGIT_MINUTE_DISPLAY", label: "Higher digit minute display"}, - {target: "THIRD_INTERMEDIATE_DISPLAY", label: "Third intermediate display"}, - {target: "LOWER_DIGIT_MINUTE_DISPLAY", label: "Lower digit minute display"} - ], - target: "DisplayIDs", - deactivated: -1, - min: 1, - max: 7 - }, - { - label: "Enable automatic brightness adjustments based on a light sensor", - type: "bool", - target: "ENABLE_LIGHT_SENSOR" - }, - { - label: "ADC pin to which the light sensor is connected to", - type: "int", - target: "LIGHT_SENSOR_PIN", - min: 0, - max: 42, - onlyIf: "ENABLE_LIGHT_SENSOR" - }, - { - label: "How many measurements shall be averaged. Higher number -> smoother but slower change", - type: "int", - target: "LIGHT_SENSOR_AVERAGE", - min: 0, - max: 255, - onlyIf: "ENABLE_LIGHT_SENSOR" - }, - { - label: "Width of the median calculation. Higher number -> smoother change Should never be higher than the LIGHT_SENSOR_AVERAGE", - type: "int", - target: "LIGHT_SENSOR_MEDIAN_WIDTH", - min: 0, - max: "LIGHT_SENSOR_AVERAGE", - onlyIf: "ENABLE_LIGHT_SENSOR" - }, - { - label: "Time that should pass in MS before the light sensor is read again. Higher number -> slower adjustments but also changes will be more sudden", - type: "int", - target: "LIGHT_SENSOR_READ_DELAY", - min: 0, - max: 65535, - onlyIf: "ENABLE_LIGHT_SENSOR" - }, - { - label: "AnalogRead value if the light sensor reads complete darkness", - type: "int", - target: "LIGHT_SENSOR_MIN", - min: 0, - max: 4095, - onlyIf: "ENABLE_LIGHT_SENSOR" - }, - { - label: "AnalogRead value if the light sensor reads the brightest", - type: "int", - target: "LIGHT_SENSOR_MAX", - min: 0, - max: 4095, - onlyIf: "ENABLE_LIGHT_SENSOR" - }, - { - label: "How much the can the light sensor values influence the led brightness", - type: "int", - target: "LIGHT_SENSOR_SENSITIVITY", - min: 0, - max: 255, - onlyIf: "ENABLE_LIGHT_SENSOR" - }, - { - label: "(Debugging Feature) Enable for a demo of all the digit transitions", - type: "bool", - target: "TIME_MANAGER_DEMO_MODE" - }, - { - label: "(Debugging Feature) The minimum delay between led updates in MS. Usually anything faster than 20MS causes problems. Increase if your LEDs do not refresh properly", - type: "int", - target: "FASTLED_SAFE_DELAY_MS", - min: 0, - max: 65535, - }, -]; diff --git a/WebConfigUI/src/data/UI_definitions.json b/WebConfigUI/src/data/UI_definitions.json index 17ff458..f89985b 100644 --- a/WebConfigUI/src/data/UI_definitions.json +++ b/WebConfigUI/src/data/UI_definitions.json @@ -1,384 +1,384 @@ { - "BaseSettings": [ - { - "label": "Enable Blynk functionality", - "type": "bool", - "target": "ENABLE_BLYNK" - }, - { - "label": "Blynk Auth token", - "type": "string", - "target": "BLYNK_AUTH_TOKEN", - "onlyIf": "ENABLE_BLYNK" - }, - { - "label": "Blynk server URL", - "type": "string", - "target": "BLYNK_SERVER", - "onlyIf": "ENABLE_BLYNK" - }, - { - "label": "Enable OTA Updates", - "type": "bool", - "target": "OTA_UPLOAD" - }, - { - "label": "Host name", - "type": "string", - "target": "HOST_NAME" - }, - { - "label": "Number of retries for WIFI reconnection before smart config is started (if enabled)", - "type": "int", - "target": "NUM_RETRIES", - "min": 1, - "max": 1000 - }, - { - "label": "Enable Smart config if WIFI connection was lost", - "type": "bool", - "target": "SMART_CONFIG" - }, - { - "label": "NTP server URL", - "type": "string", - "target": "NTP_SERVER" - }, - { - "label": "Enter a configuration string for you timezone according to this website: https://remotemonitoringsystems.ca/time-zone-abbreviations.php", - "type": "string", - "target": "TIMEZONE_INFO" - }, - { - "label": "Time in seconds for the interval in which the time should be synchronized with the time (NTP) server", - "type": "int", - "target": "TIME_SYNC_INTERVAL", - "min": 1, - "max": 4294967295 - }, - { - "label": "Flash the current time in case a timer is expired instead of flashing 00:00", - "type": "bool", - "target": "TIMER_FLASH_TIME" - }, - { - "label": "Number of flashes until an alarm is considered expired and the system goes back to normal", - "type": "int", - "target": "TIMER_FLASH_COUNT", - "min": 1, - "max": 65535 - }, - { - "label": "For how long the Display should flash when an alarm was fired in seconds. For example: An Alarm for 12:00 would stop flashing at 12:01 if this was set to 60", - "type": "int", - "target": "ALARM_NOTIFICATION_PERIOD", - "min": 1, - "max": 65535 - }, - { - "label": "How bright the clock should flash when an alarm or timer was triggered", - "type": "int", - "target": "NOTIFICATION_BRIGHTNESS", - "min": 0, - "max": 255 - }, - { - "label": "How often the time on the displays is updated in MS", - "type": "int", - "target": "TIME_UPDATE_INTERVAL", - "min": 1, - "max": 65535 - }, - { - "label": "If turned on the display will show 0 at midnight and 12 otherwise", - "type": "bool", - "target": "DISPLAY_0_AT_MIDNIGHT" - }, - { - "label": "If turned on the higher digit displays will turn off in case they would show 0", - "type": "bool", - "target": "DISPLAY_SWITCH_OFF_AT_0" - }, - { - "label": "If set to true 24 hour format will be used. Only usable if the hour display has all 7 segments", - "type": "bool", - "target": "USE_24_HOUR_FORMAT" - }, - { - "label": "The time it shall take for one iteration of the loading animation", - "type": "int", - "target": "LOADING_ANIMATION_DURATION", - "min": 1, - "max": 65535 - }, - { - "label": "How fast the brightness interpolation shall react to brightness changes", - "type": "int", - "target": "BRIGHTNESS_INTERPOLATION", - "min": 1, - "max": 65535 - }, - { - "label": "Target frames per second for the smoothness of animations", - "type": "int", - "target": "ANIMATION_TARGET_FPS", - "min": 1, - "max": 255 - }, - { - "label": "Length of sooth animation transition from fully on to black and vice versa in percent. NOTE: The higher this number the less obvious easing effects like bounce or elastic will be", - "type": "float", - "target": "ANIMATION_AFTERGLOW", - "min": 0, - "max": 1 - }, - { - "label": "Length of the dot/s fading animation in MS. One flash fades in and out", - "type": "int", - "target": "DOT_FLASH_SPEED", - "min": 1, - "max": 65535 - }, - { - "label": "Interval in MS in which the dot/s should flash", - "type": "int", - "target": "DOT_FLASH_INTERVAL", - "min": 1, - "max": 65535 - }, - { - "label": "Number of separation dots that shall be displayed", - "type": "int", - "target": "NUM_SEPARATION_DOTS", - "min": 0, - "max": 2 - }, - { - "label": "The time it takes in MS for one digit to morph into another", - "type": "int", - "target": "DIGIT_ANIMATION_SPEED", - "min": 1, - "max": 65535 - } - ], - "ColorSettings": [ - { - "label": "Color of the hour segments", - "type": "color", - "target": "HOUR", - "min": 0, - "max": 16777215 - }, - { - "label": "Color of the minute segments", - "type": "int", - "target": "MINUTE", - "min": 0, - "max": 16777215 - }, - { - "label": "Color of the internal LEDs", - "type": "int", - "target": "INTERNAL", - "min": 0, - "max": 16777215 - }, - { - "label": "Color of the separation dot LEDs", - "type": "int", - "target": "SEPARATION_DOT", - "min": 0, - "max": 16777215 - }, - { - "label": "Color of the LEDs for the OTA update progress bar", - "type": "int", - "target": "OTA_UPDATE", - "min": 0, - "max": 16777215 - }, - { - "label": "Color of the LEDs while searching for a WIFI network", - "type": "int", - "target": "WIFI_CONNECTING", - "min": 0, - "max": 16777215 - }, - { - "label": "Color of the LEDs signaling a successful WIFI connection", - "type": "int", - "target": "WIFI_CONNECTION_SUCCESSFUL", - "min": 0, - "max": 16777215 - }, - { - "label": "Color of the LEDs if system is waiting for WIFI smart config", - "type": "int", - "target": "WIFI_SMART_CONFIG", - "min": 0, - "max": 16777215 - }, - { - "label": "Color of the LEDs signaling an error of some sort", - "type": "int", - "target": "ERROR", - "min": 0, - "max": 16777215 - } - ], - "HWSettings": [ - { - "label": "Pin to which the led strip data pin is connected to", - "type": "int", - "target": "LED_DATA_PIN", - "min": 0, - "max": 42 - }, - { - "label": "Total number of segments that have LEDs in the shelf", - "type": "int", - "target": "NUM_SEGMENTS", - "min": 1, - "max": 65535 - }, - { - "label": "Number of LEDs in each segment", - "type": "int", - "target": "NUM_LEDS_PER_SEGMENT", - "min": 1, - "max": 255 - }, - { - "label": "If you wired the down lighter LEDs to the end of the LED strips set this to true", - "type": "bool", - "target": "APPEND_DOWN_LIGHTERS" - }, - { - "label": "If you wired the down lighter LEDs to the end of the LED strips enable this", - "type": "int", - "target": "DOWNLIGHT_LED_DATA_PIN", - "min": 0, - "max": 42, - "onlyIf": "!APPEND_DOWN_LIGHTERS" - }, - { - "label": "Number of LEDs For interior lights", - "type": "int", - "target": "ADDITIONAL_LEDS", - "min": 0, - "max": 65535 - }, - { - "label": "Number of seven segment displays on the clock", - "type": "int", - "target": "NUM_DISPLAYS", - "min": 4, - "max": 7 - }, - { - "label": "The number of segments to use for displaying a progress bar for the OTA updates. ATTENTION: this also depends on the configured animation which is used", - "type": "int", - "target": "NUM_SEGMENTS_PROGRESS", - "min": 1, - "max": 65535 - }, - { - "label": "The display on which the separation dot should be shown", - "type": "enum selector", - "target": "DISPLAY_FOR_SEPARATION_DOT", - "enum": "DisplayIDs", - "min": 1, - "max": 7 - }, - { - "label": "The indices of the displays in the order they are configured in", - "type": "customizable enum", - "elements": [ - {"target": "HIGHER_DIGIT_HOUR_DISPLAY", "label": "Higher digit hour display"}, - {"target": "FIRST_INTERMEDIATE_DISPLAY", "label": "First intermediate display"}, - {"target": "LOWER_DIGIT_HOUR_DISPLAY", "label": "Lower digit hour display"}, - {"target": "SECOND_INTERMEDIATE_DISPLAY", "label": "Second intermediate display"}, - {"target": "HIGHER_DIGIT_MINUTE_DISPLAY", "label": "Higher digit minute display"}, - {"target": "THIRD_INTERMEDIATE_DISPLAY", "label": "Third intermediate display"}, - {"target": "LOWER_DIGIT_MINUTE_DISPLAY", "label": "Lower digit minute display"} - ], - "target": "DisplayIDs", - "deactivated": -1, - "min": 1, - "max": 7 - }, - { - "label": "Enable automatic brightness adjustments based on a light sensor", - "type": "bool", - "target": "ENABLE_LIGHT_SENSOR" - }, - { - "label": "ADC pin to which the light sensor is connected to", - "type": "int", - "target": "LIGHT_SENSOR_PIN", - "min": 0, - "max": 42, - "onlyIf": "ENABLE_LIGHT_SENSOR" - }, - { - "label": "How many measurements shall be averaged. Higher number -> smoother but slower change", - "type": "int", - "target": "LIGHT_SENSOR_AVERAGE", - "min": 0, - "max": 255, - "onlyIf": "ENABLE_LIGHT_SENSOR" - }, - { - "label": "Width of the median calculation. Higher number -> smoother change Should never be higher than the LIGHT_SENSOR_AVERAGE", - "type": "int", - "target": "LIGHT_SENSOR_MEDIAN_WIDTH", - "min": 0, - "max": "LIGHT_SENSOR_AVERAGE", - "onlyIf": "ENABLE_LIGHT_SENSOR" - }, - { - "label": "Time that should pass in MS before the light sensor is read again. Higher number -> slower adjustments but also changes will be more sudden", - "type": "int", - "target": "LIGHT_SENSOR_READ_DELAY", - "min": 0, - "max": 65535, - "onlyIf": "ENABLE_LIGHT_SENSOR" - }, - { - "label": "AnalogRead value if the light sensor reads complete darkness", - "type": "int", - "target": "LIGHT_SENSOR_MIN", - "min": 0, - "max": 4095, - "onlyIf": "ENABLE_LIGHT_SENSOR" - }, - { - "label": "AnalogRead value if the light sensor reads the brightest", - "type": "int", - "target": "LIGHT_SENSOR_MAX", - "min": 0, - "max": 4095, - "onlyIf": "ENABLE_LIGHT_SENSOR" - }, - { - "label": "How much the can the light sensor values influence the led brightness", - "type": "int", - "target": "LIGHT_SENSOR_SENSITIVITY", - "min": 0, - "max": 255, - "onlyIf": "ENABLE_LIGHT_SENSOR" - }, - { - "label": "(Debugging Feature) Enable for a demo of all the digit transitions", - "type": "bool", - "target": "TIME_MANAGER_DEMO_MODE" - }, - { - "label": "(Debugging Feature) The minimum delay between led updates in MS. Usually anything faster than 20MS causes problems. Increase if your LEDs do not refresh properly", - "type": "int", - "target": "FASTLED_SAFE_DELAY_MS", - "min": 0, - "max": 65535 - } - ] + "BaseSettings": [ + { + "label": "Enable Blynk functionality", + "type": "bool", + "target": "ENABLE_BLYNK" + }, + { + "label": "Blynk Auth token", + "type": "string", + "target": "BLYNK_AUTH_TOKEN", + "onlyIf": "ENABLE_BLYNK" + }, + { + "label": "Blynk server URL", + "type": "string", + "target": "BLYNK_SERVER", + "onlyIf": "ENABLE_BLYNK" + }, + { + "label": "Enable OTA Updates", + "type": "bool", + "target": "OTA_UPLOAD" + }, + { + "label": "Host name", + "type": "string", + "target": "HOST_NAME" + }, + { + "label": "Number of retries for WIFI reconnection before smart config is started (if enabled)", + "type": "int", + "target": "NUM_RETRIES", + "min": 1, + "max": 1000 + }, + { + "label": "Enable Smart config if WIFI connection was lost", + "type": "bool", + "target": "SMART_CONFIG" + }, + { + "label": "NTP server URL", + "type": "string", + "target": "NTP_SERVER" + }, + { + "label": "Enter a configuration string for you timezone according to this website: https://remotemonitoringsystems.ca/time-zone-abbreviations.php", + "type": "string", + "target": "TIMEZONE_INFO" + }, + { + "label": "Time in seconds for the interval in which the time should be synchronized with the time (NTP) server", + "type": "int", + "target": "TIME_SYNC_INTERVAL", + "min": 1, + "max": 4294967295 + }, + { + "label": "Flash the current time in case a timer is expired instead of flashing 00:00", + "type": "bool", + "target": "TIMER_FLASH_TIME" + }, + { + "label": "Number of flashes until an alarm is considered expired and the system goes back to normal", + "type": "int", + "target": "TIMER_FLASH_COUNT", + "min": 1, + "max": 65535 + }, + { + "label": "For how long the Display should flash when an alarm was fired in seconds. For example: An Alarm for 12:00 would stop flashing at 12:01 if this was set to 60", + "type": "int", + "target": "ALARM_NOTIFICATION_PERIOD", + "min": 1, + "max": 65535 + }, + { + "label": "How bright the clock should flash when an alarm or timer was triggered", + "type": "int", + "target": "NOTIFICATION_BRIGHTNESS", + "min": 0, + "max": 255 + }, + { + "label": "How often the time on the displays is updated in MS", + "type": "int", + "target": "TIME_UPDATE_INTERVAL", + "min": 1, + "max": 65535 + }, + { + "label": "If turned on the display will show 0 at midnight and 12 otherwise", + "type": "bool", + "target": "DISPLAY_0_AT_MIDNIGHT" + }, + { + "label": "If turned on the higher digit displays will turn off in case they would show 0", + "type": "bool", + "target": "DISPLAY_SWITCH_OFF_AT_0" + }, + { + "label": "If set to true 24 hour format will be used. Only usable if the hour display has all 7 segments", + "type": "bool", + "target": "USE_24_HOUR_FORMAT" + }, + { + "label": "The time it shall take for one iteration of the loading animation", + "type": "int", + "target": "LOADING_ANIMATION_DURATION", + "min": 1, + "max": 65535 + }, + { + "label": "How fast the brightness interpolation shall react to brightness changes", + "type": "int", + "target": "BRIGHTNESS_INTERPOLATION", + "min": 1, + "max": 65535 + }, + { + "label": "Target frames per second for the smoothness of animations", + "type": "int", + "target": "ANIMATION_TARGET_FPS", + "min": 1, + "max": 255 + }, + { + "label": "Length of sooth animation transition from fully on to black and vice versa in percent. NOTE: The higher this number the less obvious easing effects like bounce or elastic will be", + "type": "float", + "target": "ANIMATION_AFTERGLOW", + "min": 0, + "max": 1 + }, + { + "label": "Length of the dot/s fading animation in MS. One flash fades in and out", + "type": "int", + "target": "DOT_FLASH_SPEED", + "min": 1, + "max": 65535 + }, + { + "label": "Interval in MS in which the dot/s should flash", + "type": "int", + "target": "DOT_FLASH_INTERVAL", + "min": 1, + "max": 65535 + }, + { + "label": "Number of separation dots that shall be displayed", + "type": "int", + "target": "NUM_SEPARATION_DOTS", + "min": 0, + "max": 2 + }, + { + "label": "The time it takes in MS for one digit to morph into another", + "type": "int", + "target": "DIGIT_ANIMATION_SPEED", + "min": 1, + "max": 65535 + } + ], + "ColorSettings": [ + { + "label": "Color of the hour segments", + "type": "color", + "target": "HOUR", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the minute segments", + "type": "int", + "target": "MINUTE", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the internal LEDs", + "type": "int", + "target": "INTERNAL", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the separation dot LEDs", + "type": "int", + "target": "SEPARATION_DOT", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the LEDs for the OTA update progress bar", + "type": "int", + "target": "OTA_UPDATE", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the LEDs while searching for a WIFI network", + "type": "int", + "target": "WIFI_CONNECTING", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the LEDs signaling a successful WIFI connection", + "type": "int", + "target": "WIFI_CONNECTION_SUCCESSFUL", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the LEDs if system is waiting for WIFI smart config", + "type": "int", + "target": "WIFI_SMART_CONFIG", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the LEDs signaling an error of some sort", + "type": "int", + "target": "ERROR", + "min": 0, + "max": 16777215 + } + ], + "HWSettings": [ + { + "label": "Pin to which the led strip data pin is connected to", + "type": "int", + "target": "LED_DATA_PIN", + "min": 0, + "max": 42 + }, + { + "label": "Total number of segments that have LEDs in the shelf", + "type": "int", + "target": "NUM_SEGMENTS", + "min": 1, + "max": 65535 + }, + { + "label": "Number of LEDs in each segment", + "type": "int", + "target": "NUM_LEDS_PER_SEGMENT", + "min": 1, + "max": 255 + }, + { + "label": "If you wired the down lighter LEDs to the end of the LED strips set this to true", + "type": "bool", + "target": "APPEND_DOWN_LIGHTERS" + }, + { + "label": "If you wired the down lighter LEDs to the end of the LED strips enable this", + "type": "int", + "target": "DOWNLIGHT_LED_DATA_PIN", + "min": 0, + "max": 42, + "onlyIf": "!APPEND_DOWN_LIGHTERS" + }, + { + "label": "Number of LEDs For interior lights", + "type": "int", + "target": "ADDITIONAL_LEDS", + "min": 0, + "max": 65535 + }, + { + "label": "Number of seven segment displays on the clock", + "type": "int", + "target": "NUM_DISPLAYS", + "min": 4, + "max": 7 + }, + { + "label": "The number of segments to use for displaying a progress bar for the OTA updates. ATTENTION: this also depends on the configured animation which is used", + "type": "int", + "target": "NUM_SEGMENTS_PROGRESS", + "min": 1, + "max": 65535 + }, + { + "label": "The display on which the separation dot should be shown", + "type": "enum selector", + "target": "DISPLAY_FOR_SEPARATION_DOT", + "enum": "DisplayIDs", + "min": 1, + "max": 7 + }, + { + "label": "The indices of the displays in the order they are configured in", + "type": "customizable enum", + "elements": [ + {"target": "HIGHER_DIGIT_HOUR_DISPLAY", "label": "Higher digit hour display"}, + {"target": "FIRST_INTERMEDIATE_DISPLAY", "label": "First intermediate display"}, + {"target": "LOWER_DIGIT_HOUR_DISPLAY", "label": "Lower digit hour display"}, + {"target": "SECOND_INTERMEDIATE_DISPLAY", "label": "Second intermediate display"}, + {"target": "HIGHER_DIGIT_MINUTE_DISPLAY", "label": "Higher digit minute display"}, + {"target": "THIRD_INTERMEDIATE_DISPLAY", "label": "Third intermediate display"}, + {"target": "LOWER_DIGIT_MINUTE_DISPLAY", "label": "Lower digit minute display"} + ], + "target": "DisplayIDs", + "deactivated": -1, + "min": 1, + "max": 7 + }, + { + "label": "Enable automatic brightness adjustments based on a light sensor", + "type": "bool", + "target": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "ADC pin to which the light sensor is connected to", + "type": "int", + "target": "LIGHT_SENSOR_PIN", + "min": 0, + "max": 42, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "How many measurements shall be averaged. Higher number -> smoother but slower change", + "type": "int", + "target": "LIGHT_SENSOR_AVERAGE", + "min": 0, + "max": 255, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "Width of the median calculation. Higher number -> smoother change Should never be higher than the LIGHT_SENSOR_AVERAGE", + "type": "int", + "target": "LIGHT_SENSOR_MEDIAN_WIDTH", + "min": 0, + "max": "LIGHT_SENSOR_AVERAGE", + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "Time that should pass in MS before the light sensor is read again. Higher number -> slower adjustments but also changes will be more sudden", + "type": "int", + "target": "LIGHT_SENSOR_READ_DELAY", + "min": 0, + "max": 65535, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "AnalogRead value if the light sensor reads complete darkness", + "type": "int", + "target": "LIGHT_SENSOR_MIN", + "min": 0, + "max": 4095, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "AnalogRead value if the light sensor reads the brightest", + "type": "int", + "target": "LIGHT_SENSOR_MAX", + "min": 0, + "max": 4095, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "How much the can the light sensor values influence the led brightness", + "type": "int", + "target": "LIGHT_SENSOR_SENSITIVITY", + "min": 0, + "max": 255, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "(Debugging Feature) Enable for a demo of all the digit transitions", + "type": "bool", + "target": "TIME_MANAGER_DEMO_MODE" + }, + { + "label": "(Debugging Feature) The minimum delay between led updates in MS. Usually anything faster than 20MS causes problems. Increase if your LEDs do not refresh properly", + "type": "int", + "target": "FASTLED_SAFE_DELAY_MS", + "min": 0, + "max": 65535 + } + ] } diff --git a/WebConfigUI/src/sites/Navigation.jsx b/WebConfigUI/src/sites/Navigation.jsx index 9779584..4233c45 100644 --- a/WebConfigUI/src/sites/Navigation.jsx +++ b/WebConfigUI/src/sites/Navigation.jsx @@ -3,37 +3,37 @@ import { Menu } from "@headlessui/react"; import React from "react"; const pages = [ - {link: "/WIFI", label: "WIFI Setup"}, - {link: "/BaseSettings", label: "Base Settings"}, - {link: "/Colors", label: "Colors"}, - {link: "/HWSetup", label: "Hardware Setup"}, - {link: "/", label: "Display Setup"}, - {link: "/", label: "Animation Setup"}, + {link: "/WIFI", label: "WIFI Setup"}, + {link: "/BaseSettings", label: "Base Settings"}, + {link: "/Colors", label: "Colors"}, + {link: "/HWSetup", label: "Hardware Setup"}, + {link: "/", label: "Display Setup"}, + {link: "/", label: "Animation Setup"}, ]; function NavigationMenu(props) { - return ( -
-

LED Pixel Clock

-
- - - {pages.map((page) =>( - - {({ active }) => ( - - {page.label} - - )} - - ))} - - -
-
- ); + return ( +
+

LED Pixel Clock

+
+ + + {pages.map((page) =>( + + {({ active }) => ( + + {page.label} + + )} + + ))} + + +
+
+ ); } export default withRouter(NavigationMenu); diff --git a/WebConfigUI/src/sites/Settings.jsx b/WebConfigUI/src/sites/Settings.jsx index c6cb20f..27f2f61 100644 --- a/WebConfigUI/src/sites/Settings.jsx +++ b/WebConfigUI/src/sites/Settings.jsx @@ -2,7 +2,7 @@ import React from "react"; import SettingsForm from '../components/SettingsForm' const Settings = (props) => { - return ( + return (
@@ -11,7 +11,7 @@ const Settings = (props) => {
- ); + ); } export default Settings; diff --git a/WebConfigUI/src/sites/WIFI-setup.jsx b/WebConfigUI/src/sites/WIFI-setup.jsx index ed085c2..cbe3791 100644 --- a/WebConfigUI/src/sites/WIFI-setup.jsx +++ b/WebConfigUI/src/sites/WIFI-setup.jsx @@ -28,7 +28,7 @@ const WIFI_new = (props) => { setDialogShown(false); if(result == true) { - setSSID(""); + setSSID(""); setPW(""); setCreateAP(false); props.onSubmit(props.domain, JSON.stringify({SSID: SSID, PW: PW, CreateAP: createAP})); diff --git a/WebConfigUI/tailwind.config.js b/WebConfigUI/tailwind.config.js index 92aa104..9057575 100644 --- a/WebConfigUI/tailwind.config.js +++ b/WebConfigUI/tailwind.config.js @@ -1,15 +1,15 @@ module.exports = { - purge: { - enabled: process.env.PURGE_FILES == 1, - content: ["./src/**/*.jsx", "./index.html"], - }, + purge: { + enabled: process.env.PURGE_FILES == 1, + content: ["./src/**/*.jsx", "./index.html"], + }, - darkMode: false, // or 'media' or 'class' - theme: {}, - variants: { - extend: { - backgroundColor: ["active"], - }, - }, - plugins: [require('@tailwindcss/forms')], + darkMode: false, // or 'media' or 'class' + theme: {}, + variants: { + extend: { + backgroundColor: ["active"], + }, + }, + plugins: [require('@tailwindcss/forms')], }; diff --git a/WebConfigUI/webpack.config.js b/WebConfigUI/webpack.config.js index 0e38f2d..30a3c65 100644 --- a/WebConfigUI/webpack.config.js +++ b/WebConfigUI/webpack.config.js @@ -3,47 +3,47 @@ const HtmlWebpackPlugin = require("html-webpack-plugin"); const CompressionPlugin = require("compression-webpack-plugin"); module.exports = { - module: { - rules: [ - { - test: /\.css$/i, - use: ['style-loader', 'css-loader', 'postcss-loader'] - }, - { - test: /\.jsx$/, - exclude: /node_modules/, - use: ["babel-loader"] - }, - { - test: /\.js$/, - exclude: /node_modules/, - use: ["babel-loader"] - } - ] - }, - optimization: { - // splitChunks: { chunks: "all" }, - minimize: true - }, - entry: { - index: path.resolve(__dirname, "src", "main.jsx") - }, - output: { - path: path.resolve(__dirname, "build"), - publicPath: '/' - }, - devServer: { - historyApiFallback: true, - }, - plugins: [ - new CompressionPlugin({ - test: /\.(js|jsx|css|svg)$/ - }), - new HtmlWebpackPlugin({ - template: path.resolve(__dirname, "index.html") - }) - ], - resolve: { - extensions: ['', '.js', '.jsx'], - } + module: { + rules: [ + { + test: /\.css$/i, + use: ['style-loader', 'css-loader', 'postcss-loader'] + }, + { + test: /\.jsx$/, + exclude: /node_modules/, + use: ["babel-loader"] + }, + { + test: /\.js$/, + exclude: /node_modules/, + use: ["babel-loader"] + } + ] + }, + optimization: { + // splitChunks: { chunks: "all" }, + minimize: true + }, + entry: { + index: path.resolve(__dirname, "src", "main.jsx") + }, + output: { + path: path.resolve(__dirname, "build"), + publicPath: '/' + }, + devServer: { + historyApiFallback: true, + }, + plugins: [ + new CompressionPlugin({ + test: /\.(js|jsx|css|svg)$/ + }), + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, "index.html") + }) + ], + resolve: { + extensions: ['', '.js', '.jsx'], + } }; diff --git a/WebConfigUI/workspace.code-workspace b/WebConfigUI/workspace.code-workspace new file mode 100644 index 0000000..e64d661 --- /dev/null +++ b/WebConfigUI/workspace.code-workspace @@ -0,0 +1,12 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "editor.detectIndentation": false, + "editor.insertSpaces": false, + "editor.tabSize": 2 + } +} \ No newline at end of file diff --git a/data/BaseConfig.json b/data/BaseConfig.json index aac1d86..180d34a 100644 --- a/data/BaseConfig.json +++ b/data/BaseConfig.json @@ -1,80 +1,28 @@ { - "ENABLE_WIFI": true, - "WIFI": { - "ENABLE_BLYNK": true, - "BLYNK": { - "AUTH_TOKEN": "AUTH_TOKEN_GOES_HERE", - "SERVER": "blynk-cloud.com" - }, - "OTA_UPLOAD": true, - "HOST_NAME": "LED-Shelf", - "NUM_RETRIES": 50, - "SMART_CONFIG": true, - "WIFI_SSID": "SET_YOUR_SSID_HERE", - "WIFI_PW": "SET_YOUR_PASSWORD_HERE" - }, - "COLORS": - { - "DEFAULT_HOUR": 16777215, - "DEFAULT_MINUTE": 15794175, - "DEFAULT_INTERNAL": 255, - "DEFAULT_SEPARATION_DOT": 255, - "OTA_UPDATE": 16753920, - "WIFI_CONNECTING": 255, - "WIFI_CONNECTION_SUCCESSFUL": 32768, - "WIFI_SMART_CONFIG": 16776960, - "ERROR": 16711680 - }, - "NTP_SERVER": "pool.ntp.org", - "TIMEZONE_INFO": "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00", - "TIME_SYNC_INTERVAL": 1800, - "TIMER_FLASH_TIME": false, - "TIMER_FLASH_COUNT": 10, - "ALARM_NOTIFICATION_PERIOD": 600, - "NOTIFICATION_BRIGHTNESS": 125, - "TIME_UPDATE_INTERVAL": 500, - "LED": { - "LED_DATA_PIN": 21, - "NUM_SEGMENTS": 32, - "NUM_LEDS_PER_SEGMENT": 12, - "APPEND_DOWN_LIGHTERS": true, - "ADDITIONAL_LEDS": 12, - "DOWNLIGHT_LED_DATA_PIN": 22, - "NUM_DISPLAYS": 7, - "DISPLAY_0_AT_MIDNIGHT": false, - "DISPLAY_SWITCH_OFF_AT_0": false, - "USE_24_HOUR_FORMAT": false, - "NUM_SEGMENTS_PROGRESS": 16, - "LOADING_ANIMATION_DURATION": 3000, - "BRIGHTNESS_INTERPOLATION": 3000, - "DISPLAY_FOR_SEPARATION_DOT": 3, - "ANIMATION_TARGET_FPS": 60, - "ANIMATION_AFTERGLOW": 0.2, - "DOT_FLASH_SPEED": 2000, - "DOT_FLASH_INTERVAL": 4000, - "NUM_SEPARATION_DOTS": 2, - "DISPLAY_IDS": - { - "HIGHER_DIGIT_HOUR": 0, - "FIRST_INTERMEDIATE": 1, - "LOWER_DIGIT_HOUR": 2, - "SECOND_INTERMEDIATE": 3, - "HIGHER_DIGIT_MINUTE": 4, - "THIRD_INTERMEDIATE": 5, - "LOWER_DIGIT_MINUTE": 6 - } - }, - "ENABLE_LIGHT_SENSOR": false, - "LIGHT_SENSOR": { - "LIGHT_SENSOR_PIN": 34, - "LIGHT_SENSOR_AVERAGE": 15, - "LIGHT_SENSOR_MEDIAN_WIDTH": 5, - "LIGHT_SENSOR_READ_DELAY": 500, - "LIGHT_SENSOR_MIN": 0, - "LIGHT_SENSOR_MAX": 4095, - "LIGHT_SENSOR_SENSITIVITY": 100 - }, - "TIME_MANAGER_DEMO_MODE": false, - "DIGIT_ANIMATION_SPEED": 900, - "FASTLED_SAFE_DELAY_MS": 20 + "ENABLE_BLYNK": true, + "BLYNK_AUTH_TOKEN": "AUTH_TOKEN_GOES_HERE", + "BLYNK_SERVER": "blynk-cloud.com", + "OTA_UPLOAD": true, + "HOST_NAME": "LED-Shelf", + "NUM_RETRIES": 50, + "SMART_CONFIG": true, + "NTP_SERVER": "pool.ntp.org", + "TIMEZONE_INFO": "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00", + "TIME_SYNC_INTERVAL": 1800, + "TIMER_FLASH_TIME": false, + "TIMER_FLASH_COUNT": 10, + "ALARM_NOTIFICATION_PERIOD": 600, + "NOTIFICATION_BRIGHTNESS": 125, + "TIME_UPDATE_INTERVAL": 500, + "DISPLAY_0_AT_MIDNIGHT": false, + "DISPLAY_SWITCH_OFF_AT_0": false, + "USE_24_HOUR_FORMAT": false, + "LOADING_ANIMATION_DURATION": 3000, + "BRIGHTNESS_INTERPOLATION": 3000, + "ANIMATION_TARGET_FPS": 60, + "ANIMATION_AFTERGLOW": 0.2, + "DOT_FLASH_SPEED": 2000, + "DOT_FLASH_INTERVAL": 4000, + "NUM_SEPARATION_DOTS": 2, + "DIGIT_ANIMATION_SPEED": 900 } \ No newline at end of file diff --git a/data/ColorConfig.json b/data/ColorConfig.json new file mode 100644 index 0000000..7425c52 --- /dev/null +++ b/data/ColorConfig.json @@ -0,0 +1,11 @@ +{ + "HOUR": 16777215, + "MINUTE": 15794175, + "INTERNAL": 255, + "SEPARATION_DOT": 255, + "OTA_UPDATE": 16753920, + "WIFI_CONNECTING": 255, + "WIFI_CONNECTION_SUCCESSFUL": 32768, + "WIFI_SMART_CONFIG": 16776960, + "ERROR": 16711680 +} diff --git a/data/HWConfig.json b/data/HWConfig.json new file mode 100644 index 0000000..b8a8f78 --- /dev/null +++ b/data/HWConfig.json @@ -0,0 +1,28 @@ +{ + "LED_DATA_PIN": 21, + "NUM_SEGMENTS": 32, + "NUM_LEDS_PER_SEGMENT": 12, + "APPEND_DOWN_LIGHTERS": true, + "ADDITIONAL_LEDS": 12, + "DOWNLIGHT_LED_DATA_PIN": 22, + "NUM_DISPLAYS": 7, + "NUM_SEGMENTS_PROGRESS": 16, + "DISPLAY_FOR_SEPARATION_DOT": 3, + "HIGHER_DIGIT_HOUR_DISPLAY": 0, + "FIRST_INTERMEDIATE_DISPLAY": 1, + "LOWER_DIGIT_HOUR_DISPLAY": 2, + "SECOND_INTERMEDIATE_DISPLAY": 3, + "HIGHER_DIGIT_MINUTE_DISPLAY": 4, + "THIRD_INTERMEDIATE_DISPLAY": 5, + "LOWER_DIGIT_MINUTE_DISPLAY": 6, + "ENABLE_LIGHT_SENSOR": false, + "LIGHT_SENSOR_PIN": 34, + "LIGHT_SENSOR_AVERAGE": 15, + "LIGHT_SENSOR_MEDIAN_WIDTH": 5, + "LIGHT_SENSOR_READ_DELAY": 500, + "LIGHT_SENSOR_MIN": 0, + "LIGHT_SENSOR_MAX": 4095, + "LIGHT_SENSOR_SENSITIVITY": 100, + "TIME_MANAGER_DEMO_MODE": false, + "FASTLED_SAFE_DELAY_MS": 20 +} From 8bac1255184ac3476dcd056309f39cb77233783b Mon Sep 17 00:00:00 2001 From: "flo.laschober@gmail.com" Date: Tue, 27 Apr 2021 00:31:28 +0200 Subject: [PATCH 06/13] Loading the json from ESP SPIFS is now working Proof of concept for parsing json put requests on the ESP for base config --- ESP source.code-workspace | 33 ++ WebConfigUI/.vscode/tasks.json | 28 ++ ...ce.code-workspace => WebUI.code-workspace} | 3 +- WebConfigUI/babel.config.json | 3 +- WebConfigUI/package.json | 1 + WebConfigUI/src/App.jsx | 56 +-- data/UI_definitions.json | 384 ++++++++++++++++++ data/index.html | 2 +- .../Config/Setup/clock-only/Configuration.h | 9 +- .../Modules/ConfigManager/inc/ConfigManager.h | 61 ++- .../ConfigManager/src/ConfigManager.cpp | 217 +++++----- src/main.cpp | 39 +- 12 files changed, 629 insertions(+), 207 deletions(-) create mode 100644 ESP source.code-workspace create mode 100644 WebConfigUI/.vscode/tasks.json rename WebConfigUI/{workspace.code-workspace => WebUI.code-workspace} (79%) create mode 100644 data/UI_definitions.json diff --git a/ESP source.code-workspace b/ESP source.code-workspace new file mode 100644 index 0000000..2716877 --- /dev/null +++ b/ESP source.code-workspace @@ -0,0 +1,33 @@ +{ + "folders": [ + { + "path": ".", + "name": "ESP source" + }, + ], + "settings": { + "files.exclude": { + "**/.git": true, + "WebConfigUI": true + }, + "files.associations": { + "array": "cpp", + "deque": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "initializer_list": "cpp", + "functional": "cpp", + "bitset": "cpp", + "regex": "cpp", + "utility": "cpp", + "*.tcc": "cpp", + "cmath": "cpp", + "cstdio": "cpp", + "new": "cpp" + }, + "editor.detectIndentation": false, + "editor.insertSpaces": false + } +} \ No newline at end of file diff --git a/WebConfigUI/.vscode/tasks.json b/WebConfigUI/.vscode/tasks.json new file mode 100644 index 0000000..11a0f08 --- /dev/null +++ b/WebConfigUI/.vscode/tasks.json @@ -0,0 +1,28 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "build", + "label": "npm: build", + "detail": "set NODE_ENV=production && set PURGE_FILES=1 && webpack --mode production", + }, + { + "type": "shell", + "label": "Build and copy", + "command": "copy", + "dependsOn": "npm: build", + "dependsOrder": "sequence", + "options": { + "cwd": "${workspaceFolder}" + }, + "args": [ + "${workspaceFolder}\\build\\index.js.gz", + "\"${workspaceFolder}\\../data/index.js.gz\"", + "/Y" + ], + "group": "build", + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/WebConfigUI/workspace.code-workspace b/WebConfigUI/WebUI.code-workspace similarity index 79% rename from WebConfigUI/workspace.code-workspace rename to WebConfigUI/WebUI.code-workspace index e64d661..df281b1 100644 --- a/WebConfigUI/workspace.code-workspace +++ b/WebConfigUI/WebUI.code-workspace @@ -1,7 +1,8 @@ { "folders": [ { - "path": "." + "path": ".", + "name": "Web UI" } ], "settings": { diff --git a/WebConfigUI/babel.config.json b/WebConfigUI/babel.config.json index e07bf46..b5c4c08 100644 --- a/WebConfigUI/babel.config.json +++ b/WebConfigUI/babel.config.json @@ -1,3 +1,4 @@ { - "presets": ["@babel/preset-env", "@babel/preset-react"] + "presets": ["@babel/preset-env", "@babel/preset-react"], + "plugins": ["@babel/plugin-transform-runtime"] } diff --git a/WebConfigUI/package.json b/WebConfigUI/package.json index cad4407..13e6797 100644 --- a/WebConfigUI/package.json +++ b/WebConfigUI/package.json @@ -12,6 +12,7 @@ "license": "ISC", "devDependencies": { "@babel/core": "^7.13.15", + "@babel/plugin-transform-runtime": "^7.13.15", "@babel/preset-env": "^7.13.15", "@babel/preset-react": "^7.13.13", "@headlessui/react": "^1.0.0", diff --git a/WebConfigUI/src/App.jsx b/WebConfigUI/src/App.jsx index ac910fc..eafdace 100644 --- a/WebConfigUI/src/App.jsx +++ b/WebConfigUI/src/App.jsx @@ -5,34 +5,33 @@ import Navigation from "./sites/Navigation"; import WIFI_setup from "./sites/WIFI-setup"; import Settings from "./sites/Settings"; -import {BaseSettings, ColorSettings, HWSettings} from './data/UI_definitions' - - // Development function for testing locally -const getConfigFile = async (fileUrl, hook) => { - import(/* @vite-ignore */"./data" + fileUrl ).then(config => { - hook(config); - }) -} +// const getConfigFile = async (fileUrl, hook) => { +// import(/* @vite-ignore */"./data" + fileUrl ).then(config => { +// hook(config); +// }) +// } export default function App() { + const [UI_definitions, setUI_definitions] = useState({}); const [BaseConfig, setBaseConfig] = useState({}); const [ColorConfig, setColorConfig] = useState({}); const [HWConfig, setHWConfig] = useState({}); // Function for fetching the config file from the server - // const getConfigFile = async (fileUrl, hook) => { - // const requestOptions = { - // method: 'GET', - // headers: { 'Content-Type': 'application/json' } - // }; - // const response = await fetch(fileUrl, requestOptions); - // if (!response.ok) { - // console.log("Error while fetching config file: " + response.status); - // return; - // } - // hook(response.json()); - // } + const getConfigFile = async (fileUrl, hook) => { + const requestOptions = { + method: 'GET', + headers: { 'Content-Type': 'application/json' } + }; + const response = await fetch(fileUrl, requestOptions); + if (!response.ok) { + console.log("Error while getting config file: " + response.statusText); + return; + } + const parsedJson = await response.json(); + hook(parsedJson); + } const putConfigFile = async (fileUrl, value) => { console.log("Putting: " + value) @@ -44,16 +43,17 @@ export default function App() { }; const response = await fetch(fileUrl, requestOptions); if (!response.ok) { - console.log("Error while fetching config file: " + response.status); + console.log("Error while putting config file: " + response.statusText); return null; } - return await response.json(); + return await response.text(); } useEffect(() => { - getConfigFile('/BaseConfig.json', setBaseConfig); - getConfigFile('/ColorConfig.json', setColorConfig); - getConfigFile('/HWConfig.json', setHWConfig); + getConfigFile('UI_definitions.json', setUI_definitions); + getConfigFile('BaseConfig.json', setBaseConfig); + getConfigFile('ColorConfig.json', setColorConfig); + getConfigFile('HWConfig.json', setHWConfig); }, []); const handleSubmit = (domain, values) => { @@ -68,15 +68,15 @@ export default function App() { } /> - + General Settings } /> - + Color Settings } /> - + Hardware Settings } /> diff --git a/data/UI_definitions.json b/data/UI_definitions.json new file mode 100644 index 0000000..f89985b --- /dev/null +++ b/data/UI_definitions.json @@ -0,0 +1,384 @@ +{ + "BaseSettings": [ + { + "label": "Enable Blynk functionality", + "type": "bool", + "target": "ENABLE_BLYNK" + }, + { + "label": "Blynk Auth token", + "type": "string", + "target": "BLYNK_AUTH_TOKEN", + "onlyIf": "ENABLE_BLYNK" + }, + { + "label": "Blynk server URL", + "type": "string", + "target": "BLYNK_SERVER", + "onlyIf": "ENABLE_BLYNK" + }, + { + "label": "Enable OTA Updates", + "type": "bool", + "target": "OTA_UPLOAD" + }, + { + "label": "Host name", + "type": "string", + "target": "HOST_NAME" + }, + { + "label": "Number of retries for WIFI reconnection before smart config is started (if enabled)", + "type": "int", + "target": "NUM_RETRIES", + "min": 1, + "max": 1000 + }, + { + "label": "Enable Smart config if WIFI connection was lost", + "type": "bool", + "target": "SMART_CONFIG" + }, + { + "label": "NTP server URL", + "type": "string", + "target": "NTP_SERVER" + }, + { + "label": "Enter a configuration string for you timezone according to this website: https://remotemonitoringsystems.ca/time-zone-abbreviations.php", + "type": "string", + "target": "TIMEZONE_INFO" + }, + { + "label": "Time in seconds for the interval in which the time should be synchronized with the time (NTP) server", + "type": "int", + "target": "TIME_SYNC_INTERVAL", + "min": 1, + "max": 4294967295 + }, + { + "label": "Flash the current time in case a timer is expired instead of flashing 00:00", + "type": "bool", + "target": "TIMER_FLASH_TIME" + }, + { + "label": "Number of flashes until an alarm is considered expired and the system goes back to normal", + "type": "int", + "target": "TIMER_FLASH_COUNT", + "min": 1, + "max": 65535 + }, + { + "label": "For how long the Display should flash when an alarm was fired in seconds. For example: An Alarm for 12:00 would stop flashing at 12:01 if this was set to 60", + "type": "int", + "target": "ALARM_NOTIFICATION_PERIOD", + "min": 1, + "max": 65535 + }, + { + "label": "How bright the clock should flash when an alarm or timer was triggered", + "type": "int", + "target": "NOTIFICATION_BRIGHTNESS", + "min": 0, + "max": 255 + }, + { + "label": "How often the time on the displays is updated in MS", + "type": "int", + "target": "TIME_UPDATE_INTERVAL", + "min": 1, + "max": 65535 + }, + { + "label": "If turned on the display will show 0 at midnight and 12 otherwise", + "type": "bool", + "target": "DISPLAY_0_AT_MIDNIGHT" + }, + { + "label": "If turned on the higher digit displays will turn off in case they would show 0", + "type": "bool", + "target": "DISPLAY_SWITCH_OFF_AT_0" + }, + { + "label": "If set to true 24 hour format will be used. Only usable if the hour display has all 7 segments", + "type": "bool", + "target": "USE_24_HOUR_FORMAT" + }, + { + "label": "The time it shall take for one iteration of the loading animation", + "type": "int", + "target": "LOADING_ANIMATION_DURATION", + "min": 1, + "max": 65535 + }, + { + "label": "How fast the brightness interpolation shall react to brightness changes", + "type": "int", + "target": "BRIGHTNESS_INTERPOLATION", + "min": 1, + "max": 65535 + }, + { + "label": "Target frames per second for the smoothness of animations", + "type": "int", + "target": "ANIMATION_TARGET_FPS", + "min": 1, + "max": 255 + }, + { + "label": "Length of sooth animation transition from fully on to black and vice versa in percent. NOTE: The higher this number the less obvious easing effects like bounce or elastic will be", + "type": "float", + "target": "ANIMATION_AFTERGLOW", + "min": 0, + "max": 1 + }, + { + "label": "Length of the dot/s fading animation in MS. One flash fades in and out", + "type": "int", + "target": "DOT_FLASH_SPEED", + "min": 1, + "max": 65535 + }, + { + "label": "Interval in MS in which the dot/s should flash", + "type": "int", + "target": "DOT_FLASH_INTERVAL", + "min": 1, + "max": 65535 + }, + { + "label": "Number of separation dots that shall be displayed", + "type": "int", + "target": "NUM_SEPARATION_DOTS", + "min": 0, + "max": 2 + }, + { + "label": "The time it takes in MS for one digit to morph into another", + "type": "int", + "target": "DIGIT_ANIMATION_SPEED", + "min": 1, + "max": 65535 + } + ], + "ColorSettings": [ + { + "label": "Color of the hour segments", + "type": "color", + "target": "HOUR", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the minute segments", + "type": "int", + "target": "MINUTE", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the internal LEDs", + "type": "int", + "target": "INTERNAL", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the separation dot LEDs", + "type": "int", + "target": "SEPARATION_DOT", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the LEDs for the OTA update progress bar", + "type": "int", + "target": "OTA_UPDATE", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the LEDs while searching for a WIFI network", + "type": "int", + "target": "WIFI_CONNECTING", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the LEDs signaling a successful WIFI connection", + "type": "int", + "target": "WIFI_CONNECTION_SUCCESSFUL", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the LEDs if system is waiting for WIFI smart config", + "type": "int", + "target": "WIFI_SMART_CONFIG", + "min": 0, + "max": 16777215 + }, + { + "label": "Color of the LEDs signaling an error of some sort", + "type": "int", + "target": "ERROR", + "min": 0, + "max": 16777215 + } + ], + "HWSettings": [ + { + "label": "Pin to which the led strip data pin is connected to", + "type": "int", + "target": "LED_DATA_PIN", + "min": 0, + "max": 42 + }, + { + "label": "Total number of segments that have LEDs in the shelf", + "type": "int", + "target": "NUM_SEGMENTS", + "min": 1, + "max": 65535 + }, + { + "label": "Number of LEDs in each segment", + "type": "int", + "target": "NUM_LEDS_PER_SEGMENT", + "min": 1, + "max": 255 + }, + { + "label": "If you wired the down lighter LEDs to the end of the LED strips set this to true", + "type": "bool", + "target": "APPEND_DOWN_LIGHTERS" + }, + { + "label": "If you wired the down lighter LEDs to the end of the LED strips enable this", + "type": "int", + "target": "DOWNLIGHT_LED_DATA_PIN", + "min": 0, + "max": 42, + "onlyIf": "!APPEND_DOWN_LIGHTERS" + }, + { + "label": "Number of LEDs For interior lights", + "type": "int", + "target": "ADDITIONAL_LEDS", + "min": 0, + "max": 65535 + }, + { + "label": "Number of seven segment displays on the clock", + "type": "int", + "target": "NUM_DISPLAYS", + "min": 4, + "max": 7 + }, + { + "label": "The number of segments to use for displaying a progress bar for the OTA updates. ATTENTION: this also depends on the configured animation which is used", + "type": "int", + "target": "NUM_SEGMENTS_PROGRESS", + "min": 1, + "max": 65535 + }, + { + "label": "The display on which the separation dot should be shown", + "type": "enum selector", + "target": "DISPLAY_FOR_SEPARATION_DOT", + "enum": "DisplayIDs", + "min": 1, + "max": 7 + }, + { + "label": "The indices of the displays in the order they are configured in", + "type": "customizable enum", + "elements": [ + {"target": "HIGHER_DIGIT_HOUR_DISPLAY", "label": "Higher digit hour display"}, + {"target": "FIRST_INTERMEDIATE_DISPLAY", "label": "First intermediate display"}, + {"target": "LOWER_DIGIT_HOUR_DISPLAY", "label": "Lower digit hour display"}, + {"target": "SECOND_INTERMEDIATE_DISPLAY", "label": "Second intermediate display"}, + {"target": "HIGHER_DIGIT_MINUTE_DISPLAY", "label": "Higher digit minute display"}, + {"target": "THIRD_INTERMEDIATE_DISPLAY", "label": "Third intermediate display"}, + {"target": "LOWER_DIGIT_MINUTE_DISPLAY", "label": "Lower digit minute display"} + ], + "target": "DisplayIDs", + "deactivated": -1, + "min": 1, + "max": 7 + }, + { + "label": "Enable automatic brightness adjustments based on a light sensor", + "type": "bool", + "target": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "ADC pin to which the light sensor is connected to", + "type": "int", + "target": "LIGHT_SENSOR_PIN", + "min": 0, + "max": 42, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "How many measurements shall be averaged. Higher number -> smoother but slower change", + "type": "int", + "target": "LIGHT_SENSOR_AVERAGE", + "min": 0, + "max": 255, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "Width of the median calculation. Higher number -> smoother change Should never be higher than the LIGHT_SENSOR_AVERAGE", + "type": "int", + "target": "LIGHT_SENSOR_MEDIAN_WIDTH", + "min": 0, + "max": "LIGHT_SENSOR_AVERAGE", + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "Time that should pass in MS before the light sensor is read again. Higher number -> slower adjustments but also changes will be more sudden", + "type": "int", + "target": "LIGHT_SENSOR_READ_DELAY", + "min": 0, + "max": 65535, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "AnalogRead value if the light sensor reads complete darkness", + "type": "int", + "target": "LIGHT_SENSOR_MIN", + "min": 0, + "max": 4095, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "AnalogRead value if the light sensor reads the brightest", + "type": "int", + "target": "LIGHT_SENSOR_MAX", + "min": 0, + "max": 4095, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "How much the can the light sensor values influence the led brightness", + "type": "int", + "target": "LIGHT_SENSOR_SENSITIVITY", + "min": 0, + "max": 255, + "onlyIf": "ENABLE_LIGHT_SENSOR" + }, + { + "label": "(Debugging Feature) Enable for a demo of all the digit transitions", + "type": "bool", + "target": "TIME_MANAGER_DEMO_MODE" + }, + { + "label": "(Debugging Feature) The minimum delay between led updates in MS. Usually anything faster than 20MS causes problems. Increase if your LEDs do not refresh properly", + "type": "int", + "target": "FASTLED_SAFE_DELAY_MS", + "min": 0, + "max": 65535 + } + ] +} diff --git a/data/index.html b/data/index.html index 820d4d4..c8d2b2b 100644 --- a/data/index.html +++ b/data/index.html @@ -5,7 +5,7 @@ LED Pixel Clock Configuration - + diff --git a/lib/LED_clock/Config/Setup/clock-only/Configuration.h b/lib/LED_clock/Config/Setup/clock-only/Configuration.h index 5c87a88..a3857bd 100644 --- a/lib/LED_clock/Config/Setup/clock-only/Configuration.h +++ b/lib/LED_clock/Config/Setup/clock-only/Configuration.h @@ -11,9 +11,14 @@ //Place all new configs here: #define BASE_CONFIG_FILE_NAME "/BaseConfig.json" -#define JSON_BUFFER_CHUNK_SIZE 1024 +#define COLOR_CONFIG_FILE_NAME "/ColorConfig.json" +#define HW_CONFIG_FILE_NAME "/HWConfig.json" +#define JSON_BUFFER_BASE_CONFIG 1024 +#define JSON_BUFFER_COLOR_CONFIG 300 +#define JSON_BUFFER_HW_CONFIG 1024 + +#define JSON_BUFFER_CHUNK_SIZE 256 #define JSON_BUFFER_INITIAL_SIZE 200 -#define JSON_BUFFER_BASE_CONFIG 3072 #define MAX_JSON_BUFFER 5000 diff --git a/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h index 0d67455..4cb279f 100644 --- a/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h +++ b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h @@ -30,46 +30,19 @@ class ConfigManager static ConfigManager* instance; DynamicList eventCallbacks; static char UNDEFINED[]; + DynamicJsonDocument* BaseConfig; + DynamicJsonDocument* ColorConfig; + DynamicJsonDocument* HWConfig; ConfigManager(); DynamicJsonDocument* deserializeDynamically(fs::File &input, uint64_t initialDocSize); + bool deserializeDynamically(fs::File &input, DynamicJsonDocument* initialDocument); - template - bool parseValue(jsonObjectType* root, String key, valueType* value, uint8_t ConfigClass); + template + T getProperty(DynamicJsonDocument* root, char* key); - template - bool parseValue(jsonObjectType* root, String key, valueType* value); - void checkCallbacks(void* Property, uint8_t ConfigClass); + bool loadConfigFromMemory(const char* filename, DynamicJsonDocument* doc); public: - struct WIFI_config { - bool enabled; - bool blynk_enabled; - bool ota_enabled; - bool smart_config_enabled; - uint16_t connection_retries; - char* blynk_token; - char* blynk_server; - char* host_name; - char* ssid; - char* pw; - }; - - struct Color_config { - uint32_t default_hour; - uint32_t default_minute; - uint32_t default_internal; - uint32_t default_separation_dot; - uint32_t ota_update; - uint32_t wifi_connecting; - uint32_t wifi_connection_successful; - uint32_t wifi_smart_config; - uint32_t error; - }; - - - enum ConfigChanges {C_WIFI = 0x01, C_COLORS = 0x02, C_LED = 0x04, C_LIGHT_SENSOR = 0x08, C_TIME = 0x10, C_OTHER = 0x20}; - WIFI_config WIFI; - Color_config Colors; /** * \brief Destroys the Config Manager object and cause #ConfigManager::getInstance to create a new object the next time it is called @@ -83,10 +56,22 @@ class ConfigManager */ static ConfigManager* getInstance(); - bool loadConfigFromMemory(); - void registerOnChangedCallback(void* Property, propertyChangedCallback Callback); - void registerOnChangedCallback(uint8_t ConfigClass, propertyChangedCallback Callback); - void unregisterOnChangedCallback(propertyChangedCallback Callback, void* Property = nullptr); + + + template + T getBaseProperty(char* key); + + template + T getColorProperty(char* key); + + template + T getHWProperty(char* key); + + template + bool setBaseProperty(char* key, T value); + + bool setBaseProperty(JsonPair& keyValuePair); + }; diff --git a/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp b/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp index 3ede038..e93eccc 100644 --- a/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp +++ b/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp @@ -20,19 +20,18 @@ ConfigManager::ConfigManager() Serial.printf("Filesystem mount successful; %d out of %d bytes used\n\r", SPIFFS.usedBytes(), SPIFFS.totalBytes()); - // writing some default values: - WIFI.enabled = false; - WIFI.blynk_enabled = false; - WIFI.ota_enabled = false; - WIFI.smart_config_enabled = false; - WIFI.connection_retries = 0; - WIFI.blynk_token = UNDEFINED; - WIFI.blynk_server = UNDEFINED; - WIFI.host_name = UNDEFINED; - WIFI.ssid = UNDEFINED; - WIFI.pw = UNDEFINED; - - loadConfigFromMemory(); + BaseConfig = new DynamicJsonDocument(JSON_BUFFER_BASE_CONFIG); + ColorConfig = new DynamicJsonDocument(JSON_BUFFER_COLOR_CONFIG); + HWConfig = new DynamicJsonDocument(JSON_BUFFER_HW_CONFIG); + + bool loadSuccessful = true; + loadSuccessful &= loadConfigFromMemory(BASE_CONFIG_FILE_NAME, BaseConfig); + loadSuccessful &= loadConfigFromMemory(COLOR_CONFIG_FILE_NAME, ColorConfig); + loadSuccessful &= loadConfigFromMemory(HW_CONFIG_FILE_NAME, HWConfig); + if(loadSuccessful == false) + { + Serial.println("[E]: Loading of json config from filesystem unsuccessful"); + } } ConfigManager::~ConfigManager() @@ -87,161 +86,131 @@ DynamicJsonDocument* ConfigManager::deserializeDynamically(fs::File &input, uint return nullptr; } -void ConfigManager::checkCallbacks(void* property, uint8_t ConfigClass) -{ - for (int i = 0; i < eventCallbacks.size(); i++) - { - if(eventCallbacks.get(i)->property != nullptr) - { - if(eventCallbacks.get(i)->property == property) - { - eventCallbacks.get(i)->callback(); - } - } - else - { - if((eventCallbacks.get(i)->configClass & ConfigClass) != 0x00) - { - eventCallbacks.get(i)->callback(); - } - } - } -} - -template -bool ConfigManager::parseValue(jsonObjectType* root, String key, valueType* value) +bool ConfigManager::deserializeDynamically(fs::File &input, DynamicJsonDocument* doc) { + if(doc == nullptr) + { + return false; + } bool successful = false; - JsonVariant jvalue = (*root)[key]; - if (!jvalue.isNull()) + bool firstTry = true; + size_t DocumentSize = (*doc).size(); + + do { - if(!jvalue.is()) + if(firstTry == true) { - Serial.println("E: " + key + " is not the right type"); + firstTry = false; } else { - *value = jvalue.as(); - successful = true; + doc = new DynamicJsonDocument(DocumentSize); } - } - return successful; -} -template -bool ConfigManager::parseValue(jsonObjectType* root, String key, valueType* value, uint8_t ConfigClass) -{ - bool successful = false; - JsonVariant jvalue = (*root)[key]; - if (!jvalue.isNull()) - { - if(!jvalue.is()) + DeserializationError error = deserializeJson(*doc, input); + if (error) { - Serial.println("E: " + key + " is not the right type"); + if (error.code() == DeserializationError::NoMemory) + { + delete doc; + DocumentSize += JSON_BUFFER_CHUNK_SIZE; + } + else + { + Serial.println("E: json:" + String(error.c_str())); + break; + } } else { - valueType newValue = (valueType)jvalue.as(); - if(*value != newValue) - { - *value = newValue; - checkCallbacks(value, ConfigClass); - } successful = true; + break; //no error detected -> memory size is okay } - } - return successful; + } while (DocumentSize < MAX_JSON_BUFFER); + + return successful; } -bool ConfigManager::loadConfigFromMemory() +bool ConfigManager::loadConfigFromMemory(const char* filename, DynamicJsonDocument* doc) { #define ERROR_HANDLING(x, key) if(x == false) { Serial.printf("E: Key %s is mandatory", key); delete doc; return false;} bool loadOK = false; - if(SPIFFS.exists(BASE_CONFIG_FILE_NAME)) + if(SPIFFS.exists(filename)) { - File jsonFile = SPIFFS.open(BASE_CONFIG_FILE_NAME, "r"); + File jsonFile = SPIFFS.open(filename, "r"); if (!jsonFile) { Serial.println("E: Open config file failed"); } else { - DynamicJsonDocument* doc = deserializeDynamically(jsonFile, JSON_BUFFER_BASE_CONFIG); - if(doc != nullptr) + if(deserializeDynamically(jsonFile, doc) == true) { - ERROR_HANDLING(parseValue(doc, "ENABLE_WIFI", &(WIFI.enabled), C_WIFI), "ENABLE_WIFI") - - if(WIFI.enabled == true) - { - JsonObject WIFI_object; - ERROR_HANDLING(parseValue(doc, "WIFI", &WIFI_object), "WIFI") - - ERROR_HANDLING(parseValue(&WIFI_object, "ENABLE_BLYNK", &(WIFI.blynk_enabled), C_WIFI), "ENABLE_BLYNK") - - if(WIFI.blynk_enabled == true) - { - JsonObject blynk_object; - ERROR_HANDLING(parseValue(&WIFI_object, "BLYNK", &blynk_object), "BLYNK") - ERROR_HANDLING(parseValue(&blynk_object, "AUTH_TOKEN", &(WIFI.blynk_token), C_WIFI), "AUTH_TOKEN") - ERROR_HANDLING(parseValue(&blynk_object, "SERVER", &(WIFI.blynk_server), C_WIFI), "SERVER") - } - - ERROR_HANDLING(parseValue(&WIFI_object, "OTA_UPLOAD", &(WIFI.ota_enabled), C_WIFI), "OTA_UPLOAD") - ERROR_HANDLING(parseValue(&WIFI_object, "NUM_RETRIES", &(WIFI.connection_retries), C_WIFI), "NUM_RETRIES") - ERROR_HANDLING(parseValue(&WIFI_object, "SMART_CONFIG", &(WIFI.smart_config_enabled), C_WIFI), "SMART_CONFIG") - ERROR_HANDLING(parseValue(&WIFI_object, "HOST_NAME", &(WIFI.host_name), C_WIFI), "HOST_NAME") - ERROR_HANDLING(parseValue(&WIFI_object, "WIFI_SSID", &(WIFI.ssid), C_WIFI), "WIFI_SSID") - ERROR_HANDLING(parseValue(&WIFI_object, "WIFI_PW", &(WIFI.pw), C_WIFI), "WIFI_PW") - } - { // Color config - JsonObject colors_object; - ERROR_HANDLING(parseValue(doc, "COLORS", &colors_object), "COLORS") - ERROR_HANDLING(parseValue(&colors_object, "DEFAULT_HOUR", &(Colors.default_hour), C_COLORS), "DEFAULT_HOUR") - ERROR_HANDLING(parseValue(&colors_object, "DEFAULT_MINUTE", &(Colors.default_minute), C_COLORS), "DEFAULT_MINUTE") - ERROR_HANDLING(parseValue(&colors_object, "DEFAULT_INTERNAL", &(Colors.default_internal), C_COLORS), "DEFAULT_INTERNAL") - ERROR_HANDLING(parseValue(&colors_object, "DEFAULT_SEPARATION_DOT", &(Colors.default_separation_dot), C_COLORS), "DEFAULT_SEPARATION_DOT") - ERROR_HANDLING(parseValue(&colors_object, "OTA_UPDATE", &(Colors.ota_update), C_COLORS), "OTA_UPDATE") - ERROR_HANDLING(parseValue(&colors_object, "WIFI_CONNECTING", &(Colors.wifi_connecting), C_COLORS), "WIFI_CONNECTING") - ERROR_HANDLING(parseValue(&colors_object, "WIFI_CONNECTION_SUCCESSFUL", &(Colors.wifi_connection_successful), C_COLORS), "WIFI_CONNECTION_SUCCESSFUL") - ERROR_HANDLING(parseValue(&colors_object, "WIFI_SMART_CONFIG", &(Colors.wifi_smart_config), C_COLORS), "WIFI_SMART_CONFIG") - ERROR_HANDLING(parseValue(&colors_object, "ERROR", &(Colors.error), C_COLORS), "ERROR") - } - - Serial.println("Config parsed successfully"); - delete doc; //delete the dynamically allocated document after all values have been stored in variables - loadOK = true; + loadOK = true; } jsonFile.close(); } } else { - Serial.printf("E: Config file %s does not exist on the filesystem yet. Please create it first\n\r", BASE_CONFIG_FILE_NAME); + Serial.printf("E: Config file %s does not exist on the filesystem yet. Please create it first\n\r", filename); loadOK = false; } return loadOK; } -void ConfigManager::registerOnChangedCallback(void* Property, propertyChangedCallback Callback) + +template +T ConfigManager::getProperty(DynamicJsonDocument* root, char* key) +{ + JsonVariant jvalue = (*root)[key]; + if (!jvalue.isNull()) + { + if(!jvalue.is()) + { + Serial.printf("[E]: BaseConfig key \"%s\" is not the correct type", key); + } + else + { + return jvalue.as(); + } + } + return nullptr; +} + +template +T ConfigManager::getBaseProperty(char* key) { - changedEventCallback* newCallback = new changedEventCallback{.callback = Callback, .property = Property, .configClass = 0}; - eventCallbacks.add(newCallback); + return getProperty(BaseConfig, key); } -void ConfigManager::registerOnChangedCallback(uint8_t ConfigClass, propertyChangedCallback Callback) +template +T ConfigManager::getColorProperty(char* key) { - changedEventCallback* newCallback = new changedEventCallback{.callback = Callback, .property = nullptr, .configClass = ConfigClass}; - eventCallbacks.add(newCallback); + return getProperty(ColorConfig, key); } -void ConfigManager::unregisterOnChangedCallback(propertyChangedCallback Callback, void* Property) +template +T ConfigManager::getHWProperty(char* key) { - for (int i = 0; i < eventCallbacks.size(); i++) - { - if(eventCallbacks.get(i)->callback == Callback && (Property == nullptr || eventCallbacks.get(i)->property == Property)) - { - eventCallbacks.remove(i); - return; - } - } + return getProperty(HWConfig, key); +} + +template +bool ConfigManager::setBaseProperty(char* key, T value) +{ + if(BaseConfig->containsKey(key)) + { + return (*BaseConfig)[key].set(key); + } + return false; +} + +bool ConfigManager::setBaseProperty(JsonPair& keyValuePair) +{ + if(BaseConfig->containsKey(keyValuePair.key())) + { + return (*BaseConfig)[keyValuePair.key()].set(keyValuePair.value()); + } + return false; } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 5e29b62..3494874 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,8 @@ #include "ClockState.h" #include "ConfigManager.h" #include "ESPAsyncWebServer.h" +#include "AsyncJson.h" +#include #if RUN_WITHOUT_WIFI == false #include "WiFi.h" @@ -25,6 +27,7 @@ DisplayManager* ShelfDisplays = DisplayManager::getInstance(); BlynkConfig* BlynkConfiguration = BlynkConfig::getInstance(); TimeManager* timeM = TimeManager::getInstance(); ClockState* states = ClockState::getInstance(); +ConfigManager* config; AsyncWebServer server(80); #if ENABLE_OTA_UPLOAD == true @@ -82,21 +85,33 @@ void startupAnimation() } } -void sendWebFile(AsyncWebServerRequest *request) -{ - request->send(SPIFFS, "/index.html", "text/html"); -} - void startWebServer() { - server.on("/", HTTP_GET, sendWebFile); + server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html"); - server.on("/index.js.gz", HTTP_GET, [](AsyncWebServerRequest *request){ - AsyncWebServerResponse* response = request->beginResponse(SPIFFS, "/index.js.gz", "text/html"); - response->addHeader("Content-Encoding", "gzip"); - request->send(response); + server.onNotFound([](AsyncWebServerRequest *request){ + if(request->url() == "/WIFI" || request->url() == "/BaseSettings" || request->url() == "/Colors" || request->url() == "/HWSetup") + { + request->redirect("/"); + } else + { + request->send(404, "text/plain", "Not found"); + } }); + AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/BaseConfig", [](AsyncWebServerRequest *request, JsonVariant &json) + { + for (JsonPair kv : json.as()) + { + if(!config->setBaseProperty(kv)) + { + Serial.printf("[W]: Property %s not set\n\r", kv.key().c_str()); + } + } + request->send(200, "text/plain", "Config OK"); + }); + server.addHandler(handler); + server.begin(); } @@ -104,6 +119,8 @@ void setup() { Serial.begin(115200); + config = ConfigManager::getInstance(); + ShelfDisplays->InitSegments(0, NUM_LEDS_PER_SEGMENT, WIFI_CONNECTING_COLOR, 50); ShelfDisplays->setHourSegmentColors(HOUR_COLOR); @@ -111,8 +128,6 @@ void setup() ShelfDisplays->setInternalLEDColor(INTERNAL_COLOR); ShelfDisplays->setDotLEDColor(SEPERATION_DOT_COLOR); - ConfigManager::getInstance(); - #if RUN_WITHOUT_WIFI == false wifiSetup(); #endif From 836b6b46d1be6c15841b5e3091b082f66aa114d9 Mon Sep 17 00:00:00 2001 From: "flo.laschober@gmail.com" Date: Tue, 27 Apr 2021 19:24:19 +0200 Subject: [PATCH 07/13] Using the web UI settings can now be changed and saved on the ESP file system persistently. --- README.md | 2 + WebConfigUI/src/App.jsx | 33 +- data/BaseConfig.json | 1 - data/ConfigChangeEventIDs.json | 62 ++ data/UI_definitions.json | 5 - .../Config/Blynk/default/BlynkConfig.cpp | 702 +++++++++--------- .../Config/Blynk/default/BlynkConfig.h | 172 +++-- .../Config/Setup/clock-only/Configuration.h | 92 +-- .../Config/Setup/default/Configuration.h | 82 +- .../Config/Setup/diy-machines/Configuration.h | 81 +- .../Modules/Animator/src/Animator.cpp | 1 + .../Modules/ClockState/src/ClockState.cpp | 1 + .../Modules/ConfigManager/inc/ConfigManager.h | 84 ++- .../ConfigManager/src/ConfigManager.cpp | 172 ++++- .../DisplayManager/src/DisplayManager.cpp | 1 + .../Modules/TimeManager/inc/TimeManager.h | 5 +- .../Modules/TimeManager/src/TimeManager.cpp | 11 +- src/main.cpp | 427 +++++------ 18 files changed, 1061 insertions(+), 873 deletions(-) create mode 100644 data/ConfigChangeEventIDs.json diff --git a/README.md b/README.md index 447aa8f..3fede00 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ ATTENTION: You are looking at version 2.0 of the project. Documentation for this For Version 1.0 take a look at [this snapshot branch](https://github.com/florianL21/LED-ClockShelf/tree/Snapshot-v1) You wil also find the original version of the documentation there. +Please note that updating from version 1 to version 2 via OTA update might not work correctly. + ------------------ This is the software of a variation of the LED clock from [DIY Machines](https://www.instructables.com/id/How-to-Build-a-Giant-Hidden-Shelf-Edge-Clock/) diff --git a/WebConfigUI/src/App.jsx b/WebConfigUI/src/App.jsx index eafdace..debd55a 100644 --- a/WebConfigUI/src/App.jsx +++ b/WebConfigUI/src/App.jsx @@ -4,6 +4,7 @@ import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import Navigation from "./sites/Navigation"; import WIFI_setup from "./sites/WIFI-setup"; import Settings from "./sites/Settings"; +import Dialog from './components/Dialog'; // Development function for testing locally // const getConfigFile = async (fileUrl, hook) => { @@ -14,9 +15,12 @@ import Settings from "./sites/Settings"; export default function App() { const [UI_definitions, setUI_definitions] = useState({}); - const [BaseConfig, setBaseConfig] = useState({}); - const [ColorConfig, setColorConfig] = useState({}); - const [HWConfig, setHWConfig] = useState({}); + const [BaseConfig, setBaseConfig] = useState({}); + const [ColorConfig, setColorConfig] = useState({}); + const [HWConfig, setHWConfig] = useState({}); + + const [saveResponse, setSaveResponse] = useState("ERROR: Not saved"); + const [dialogShown, setDialogShown] = useState(false); // Function for fetching the config file from the server const getConfigFile = async (fileUrl, hook) => { @@ -44,9 +48,11 @@ export default function App() { const response = await fetch(fileUrl, requestOptions); if (!response.ok) { console.log("Error while putting config file: " + response.statusText); - return null; + return false; } - return await response.text(); + setSaveResponse(await response.text()); + setDialogShown(true); + return true; } useEffect(() => { @@ -56,8 +62,12 @@ export default function App() { getConfigFile('HWConfig.json', setHWConfig); }, []); - const handleSubmit = (domain, values) => { + const handleSubmit = (domain, values, setter) => { putConfigFile(domain, values); + if(setter != null) + { + getConfigFile(domain + '.json', setter); + } } return ( @@ -66,21 +76,24 @@ export default function App() { } /> - } /> + handleSubmit(d, v, null)}/>} /> - + handleSubmit(d, v, setBaseConfig)} UIDefinition={UI_definitions.BaseSettings} InitialValues={BaseConfig}> General Settings } /> - + handleSubmit(d, v, setColorConfig)} UIDefinition={UI_definitions.ColorSettings} InitialValues={ColorConfig}> Color Settings } /> - + handleSubmit(d, v, setHWConfig)} UIDefinition={UI_definitions.HWSettings} InitialValues={HWConfig}> Hardware Settings } /> + setDialogShown(false)} confirmMessage="OK" title="Info" type="OK"> + {saveResponse} +
); } diff --git a/data/BaseConfig.json b/data/BaseConfig.json index 180d34a..65f5742 100644 --- a/data/BaseConfig.json +++ b/data/BaseConfig.json @@ -2,7 +2,6 @@ "ENABLE_BLYNK": true, "BLYNK_AUTH_TOKEN": "AUTH_TOKEN_GOES_HERE", "BLYNK_SERVER": "blynk-cloud.com", - "OTA_UPLOAD": true, "HOST_NAME": "LED-Shelf", "NUM_RETRIES": 50, "SMART_CONFIG": true, diff --git a/data/ConfigChangeEventIDs.json b/data/ConfigChangeEventIDs.json new file mode 100644 index 0000000..3df1599 --- /dev/null +++ b/data/ConfigChangeEventIDs.json @@ -0,0 +1,62 @@ +{ + "ENABLE_BLYNK": 1, + "BLYNK_AUTH_TOKEN": 1, + "BLYNK_SERVER": 1, + "HOST_NAME": 0, + "NUM_RETRIES": 0, + "SMART_CONFIG": 0, + "NTP_SERVER": 2, + "TIMEZONE_INFO": 2, + "TIME_SYNC_INTERVAL": 2, + "TIMER_FLASH_TIME": 2, + "TIMER_FLASH_COUNT": 2, + "ALARM_NOTIFICATION_PERIOD": 2, + "NOTIFICATION_BRIGHTNESS": 2, + "TIME_UPDATE_INTERVAL": 2, + "DISPLAY_0_AT_MIDNIGHT": 2, + "DISPLAY_SWITCH_OFF_AT_0": 2, + "USE_24_HOUR_FORMAT": 2, + "LOADING_ANIMATION_DURATION": 2, + "BRIGHTNESS_INTERPOLATION": 2, + "ANIMATION_TARGET_FPS": 2, + "ANIMATION_AFTERGLOW": 2, + "DOT_FLASH_SPEED": 2, + "DOT_FLASH_INTERVAL": 2, + "NUM_SEPARATION_DOTS": 2, + "DIGIT_ANIMATION_SPEED": 2, + "HOUR": 3, + "MINUTE": 3, + "INTERNAL": 3, + "SEPARATION_DOT": 3, + "OTA_UPDATE": 3, + "WIFI_CONNECTING": 3, + "WIFI_CONNECTION_SUCCESSFUL": 3, + "WIFI_SMART_CONFIG": 3, + "ERROR": 3, + "LED_DATA_PIN": 4, + "NUM_SEGMENTS": 4, + "NUM_LEDS_PER_SEGMENT": 4, + "APPEND_DOWN_LIGHTERS": 4, + "ADDITIONAL_LEDS": 4, + "DOWNLIGHT_LED_DATA_PIN": 4, + "NUM_DISPLAYS": 4, + "NUM_SEGMENTS_PROGRESS": 4, + "DISPLAY_FOR_SEPARATION_DOT": 4, + "HIGHER_DIGIT_HOUR_DISPLAY": 4, + "FIRST_INTERMEDIATE_DISPLAY": 4, + "LOWER_DIGIT_HOUR_DISPLAY": 4, + "SECOND_INTERMEDIATE_DISPLAY": 4, + "HIGHER_DIGIT_MINUTE_DISPLAY": 4, + "THIRD_INTERMEDIATE_DISPLAY": 4, + "LOWER_DIGIT_MINUTE_DISPLAY": 4, + "ENABLE_LIGHT_SENSOR": 4, + "LIGHT_SENSOR_PIN": 4, + "LIGHT_SENSOR_AVERAGE": 4, + "LIGHT_SENSOR_MEDIAN_WIDTH": 4, + "LIGHT_SENSOR_READ_DELAY": 4, + "LIGHT_SENSOR_MIN": 4, + "LIGHT_SENSOR_MAX": 4, + "LIGHT_SENSOR_SENSITIVITY": 4, + "TIME_MANAGER_DEMO_MODE": 4, + "FASTLED_SAFE_DELAY_MS": 4 +} \ No newline at end of file diff --git a/data/UI_definitions.json b/data/UI_definitions.json index f89985b..6610054 100644 --- a/data/UI_definitions.json +++ b/data/UI_definitions.json @@ -17,11 +17,6 @@ "target": "BLYNK_SERVER", "onlyIf": "ENABLE_BLYNK" }, - { - "label": "Enable OTA Updates", - "type": "bool", - "target": "OTA_UPLOAD" - }, { "label": "Host name", "type": "string", diff --git a/lib/LED_clock/Config/Blynk/default/BlynkConfig.cpp b/lib/LED_clock/Config/Blynk/default/BlynkConfig.cpp index 47202d3..98c074d 100644 --- a/lib/LED_clock/Config/Blynk/default/BlynkConfig.cpp +++ b/lib/LED_clock/Config/Blynk/default/BlynkConfig.cpp @@ -6,402 +6,432 @@ #include "BlynkConfig.h" -#if IS_BLYNK_ACTIVE == true +/** + * \note if you use a different controller make sure to change the include here + */ +#include - /** - * \note if you use a different controller make sure to change the include here - */ - #include +BlynkConfig* BlynkConfig::instance = nullptr; +BlynkConfig* BlynkC = nullptr; +ClockState* ClockS = nullptr; +TimeManager* TimeM = nullptr; - BlynkConfig* BlynkConfig::instance = nullptr; - BlynkConfig* BlynkC = BlynkConfig::getInstance(); - ClockState* ClockS = ClockState::getInstance(); - TimeManager* TimeM = TimeManager::getInstance(); +void blynkReconfigure(ConfigManager* config); - /** - * \brief Construct a new Blynk Config object. Also poulate all variables of the class with meaningful values - * - */ - BlynkConfig::BlynkConfig() - { - ShelfDisplays = DisplayManager::getInstance(); - isClearAction = false; - ColorSelection = CHANGE_HOURS_COLOR; - blynkUIUpdateRequired = false; - } +/** + * \brief Construct a new Blynk Config object. Also poulate all variables of the class with meaningful values + * + */ +BlynkConfig::BlynkConfig() +{ + BlynkC = this; + ClockS = ClockState::getInstance(); + TimeM = TimeManager::getInstance(); + ShelfDisplays = DisplayManager::getInstance(); + config = ConfigManager::getInstance(); + isClearAction = false; + ColorSelection = CHANGE_HOURS_COLOR; + blynkUIUpdateRequired = false; + isTaskRunning = false; + config->registerOnChangedCallback(ConfigManager::BLYNK_CONFIG_CHANGED, blynkReconfigure); +} - /** - * \brief Destroy the Blynk Config object. Ensure proper deletion - * - */ - BlynkConfig::~BlynkConfig() - { - instance = nullptr; - } +/** + * \brief Destroy the Blynk Config object. Ensure proper deletion + * + */ +BlynkConfig::~BlynkConfig() +{ + instance = nullptr; +} - /** - * \brief Either instantiate a new BlynkConfig object by calling the private constructor and return it's address - * or it an instance of it already exists just return that. - * - * \return BlynkConfig* address of the new/already existing blynk config object - */ - BlynkConfig* BlynkConfig::getInstance() +/** + * \brief Either instantiate a new BlynkConfig object by calling the private constructor and return it's address + * or it an instance of it already exists just return that. + * + * \return BlynkConfig* address of the new/already existing blynk config object + */ +BlynkConfig* BlynkConfig::getInstance() +{ + if(instance == nullptr) { - if(instance == nullptr) - { - instance = new BlynkConfig(); - } - return instance; + instance = new BlynkConfig(); } + return instance; +} - /** - * \brief Terminates the blynk task running on the second core - * - */ - void BlynkConfig::stop() +/** + * \brief Terminates the blynk task running on the second core + * + */ +void BlynkConfig::stop() +{ + if(isTaskRunning == true) { vTaskDelete(BlynkLoop); + isTaskRunning = false; } +} - /** - * \brief Code for the second thread running on the second core of the ESP handling all the blynk code since - * all of it is coded in a blocking way and we don't want to influence the animation smoothness - * - */ - void BlynkLoopCode(void* pvParameters) +/** + * \brief Code for the second thread running on the second core of the ESP handling all the blynk code since + * all of it is coded in a blocking way and we don't want to influence the animation smoothness + * + */ +void BlynkLoopCode(void* pvParameters) +{ + Serial.printf("Loop task running on core %d\n\r", xPortGetCoreID()); + BlynkConfig* blynkC = BlynkConfig::getInstance(); + for(;;) { - Serial.printf("Loop task running on core %d\n\r", xPortGetCoreID()); - BlynkConfig* blynkC = BlynkConfig::getInstance(); - for(;;) - { - #if IS_BLYNK_ACTIVE == true - Blynk.run(); - if(blynkC->blynkUIUpdateRequired == true) + Blynk.run(); + if(blynkC->blynkUIUpdateRequired == true) + { + blynkC->blynkUIUpdateRequired = false; + if(ClockS->getMode() == ClockState::CLOCK_MODE) { - blynkC->blynkUIUpdateRequired = false; - if(ClockS->getMode() == ClockState::CLOCK_MODE) - { - Blynk.virtualWrite(BLYNK_CHANNEL_TIMER_START_BUTTON, 0); - } - else if(ClockS->getMode() == ClockState::ALARM_NOTIFICATION) - { - Blynk.setProperty(BLYNK_CHANNEL_ALARM_START_BUTTON, "onLabel", "Clear"); - } - else - { - // TimeManager::TimeInfo currentTimerValue = timeM->getCurrentTime(); - // int time = currentTimerValue.hours * 3600 + currentTimerValue.minutes * 60 + currentTimerValue.seconds; - // Blynk.virtualWrite(BLYNK_CHANNEL_TIMER_START_BUTTON, time, 0); - } + Blynk.virtualWrite(BLYNK_CHANNEL_TIMER_START_BUTTON, 0); } - #endif - delay(1); //needed to serve the wdg - } + else if(ClockS->getMode() == ClockState::ALARM_NOTIFICATION) + { + Blynk.setProperty(BLYNK_CHANNEL_ALARM_START_BUTTON, "onLabel", "Clear"); + } + else + { + // TimeManager::TimeInfo currentTimerValue = timeM->getCurrentTime(); + // int time = currentTimerValue.hours * 3600 + currentTimerValue.minutes * 60 + currentTimerValue.seconds; + // Blynk.virtualWrite(BLYNK_CHANNEL_TIMER_START_BUTTON, time, 0); + } + } + delay(1); //needed to serve the wdg } +} - /** - * \brief call Blynk.config and start the separate thread on the second core - * - */ - void BlynkConfig::setup() +/** + * \brief call Blynk.config and start the separate thread on the second core + * + */ +void BlynkConfig::setup() +{ + if(config->getProperty(ConfigManager::BASE_CONFIG, BLYNK_ACTIVE_KEY) == true && isTaskRunning == false) { - Blynk.config(BLYNK_AUTH_TOKEN, BLYNK_SERVER, 80); + Blynk.config(config->getProperty(ConfigManager::BASE_CONFIG, BLYNK_TOKEN_KEY), + config->getProperty(ConfigManager::BASE_CONFIG, BLYNK_SERVER_KEY), 80); Serial.println("Starting blynk on core 0..."); - //Setup the loop task on the second core + //Setup Blynk in another task because it is coded in a blocking way xTaskCreatePinnedToCore( BlynkLoopCode, // Task function. "BlynkLoop", // name of task. - 10000, // Stack size of task + 8192, // Stack size of task NULL, // parameter of the task 1, // priority of the task &BlynkLoop, // Task handle to keep track of created task 0); // pin task to core 0 + isTaskRunning = true; } +} - /** - * \brief Notify the Blynk thread that a UI update is needed. - * What exactly needs to be updated will be figured out in the thread loop itself. - * - */ - void BlynkConfig::updateUI() +void BlynkConfig::setEnabled(bool enabled) +{ + if(enabled == true) { - blynkUIUpdateRequired = true; - } - - /** - * \brief Sync all channels as soon as blynk connects to the server - * Setup default UI states - * - */ - BLYNK_CONNECTED() + setup(); + } else if(enabled == false) { - Blynk.syncVirtual(BLYNK_CHANNEL_BRIGHTNESS_SLIDER); - Blynk.syncVirtual(BLYNK_CHANNEL_LIGHT_GROUP_SELECTOR); - Blynk.syncVirtual(BLYNK_CHANNEL_CURRENT_COLOR_PICKER); - Blynk.syncVirtual(BLYNK_CHANNEL_TIMER_TIME_INPUT); - Blynk.syncVirtual(BLYNK_CHANNEL_HOUR_COLOR_SAVE); - Blynk.syncVirtual(BLYNK_CHANNEL_MINUTE_COLOR_SAVE); - Blynk.syncVirtual(BLYNK_CHANNEL_INTERNAL_COLOR_SAVE); - Blynk.syncVirtual(BLYNK_CHANNEL_DOT_COLOR_SAVE); - Blynk.syncVirtual(BLYNK_CHANNEL_NIGHT_MODE_BRIGHTNESS); - Blynk.syncVirtual(BLYNK_CHANNEL_NIGHT_MODE_TIME_INPUT); - Blynk.syncVirtual(BLYNK_CHANNEL_NUM_SEPERATION_DOTS); - Blynk.syncVirtual(BLYNK_CHANNEL_ALARM_START_BUTTON); - Blynk.syncVirtual(BLYNK_CHANNEL_ALARM_TIME_INPUT); - Blynk.virtualWrite(BLYNK_CHANNEL_TIMER_START_BUTTON, 0); + stop(); } +} - /** - * \brief Handle a brigness change by the user through the blynk app - * - */ - BLYNK_WRITE(BLYNK_CHANNEL_BRIGHTNESS_SLIDER) - { - ClockS->clockBrightness = param[0].asInt(); - BlynkC->ShelfDisplays->setGlobalBrightness(ClockS->clockBrightness); - } +void blynkReconfigure(ConfigManager* config) +{ + Blynk.config(config->getProperty(ConfigManager::BASE_CONFIG, BLYNK_TOKEN_KEY), + config->getProperty(ConfigManager::BASE_CONFIG, BLYNK_SERVER_KEY), 80); + BlynkC->setEnabled(config->getProperty(ConfigManager::BASE_CONFIG, BLYNK_ACTIVE_KEY)); +} - /** - * \brief Handle the change of the light group selector by the user through the blynk app - * - */ - BLYNK_WRITE(BLYNK_CHANNEL_LIGHT_GROUP_SELECTOR) - { - switch (param.asInt()) - { - case 1: - BlynkC->ColorSelection = BlynkConfig::CHANGE_HOURS_COLOR; - Blynk.virtualWrite(BLYNK_CHANNEL_CURRENT_COLOR_PICKER, BlynkC->HourColor.r, BlynkC->HourColor.g, BlynkC->HourColor.b); - break; - case 2: - BlynkC->ColorSelection = BlynkConfig::CHANGE_MINUTES_COLOR; - Blynk.virtualWrite(BLYNK_CHANNEL_CURRENT_COLOR_PICKER, BlynkC->MinuteColor.r, BlynkC->MinuteColor.g, BlynkC->MinuteColor.b); - break; - case 3: - BlynkC->ColorSelection = BlynkConfig::CHANGE_INTERRIOR_COLOR; - Blynk.virtualWrite(BLYNK_CHANNEL_CURRENT_COLOR_PICKER, BlynkC->InternalColor.r, BlynkC->InternalColor.g, BlynkC->InternalColor.b); - break; - case 4: - BlynkC->ColorSelection = BlynkConfig::CHANGE_DOT_COLOR; - Blynk.virtualWrite(BLYNK_CHANNEL_CURRENT_COLOR_PICKER, BlynkC->DotColor.r, BlynkC->DotColor.g, BlynkC->DotColor.b); - break; - } - } +/** + * \brief Notify the Blynk thread that a UI update is needed. + * What exactly needs to be updated will be figured out in the thread loop itself. + * + */ +void BlynkConfig::updateUI() +{ + blynkUIUpdateRequired = true; +} - /** - * \brief Handle a color change for the currently selected light group by the user through the blynk app - * - */ - BLYNK_WRITE(BLYNK_CHANNEL_CURRENT_COLOR_PICKER) - { - CRGB currentColor; - currentColor.r = param[0].asInt(); - currentColor.g = param[1].asInt(); - currentColor.b = param[2].asInt(); - switch (BlynkC->ColorSelection) - { - case BlynkConfig::CHANGE_HOURS_COLOR: - BlynkC->ShelfDisplays->setHourSegmentColors(currentColor); - Blynk.virtualWrite(BLYNK_CHANNEL_HOUR_COLOR_SAVE, currentColor.r, currentColor.g, currentColor.b); - BlynkC->HourColor = currentColor; - break; - case BlynkConfig::CHANGE_MINUTES_COLOR: - BlynkC->ShelfDisplays->setMinuteSegmentColors(currentColor); - Blynk.virtualWrite(BLYNK_CHANNEL_MINUTE_COLOR_SAVE, currentColor.r, currentColor.g, currentColor.b); - BlynkC->MinuteColor = currentColor; - break; - case BlynkConfig::CHANGE_INTERRIOR_COLOR: - BlynkC->ShelfDisplays->setInternalLEDColor(currentColor); - Blynk.virtualWrite(BLYNK_CHANNEL_INTERNAL_COLOR_SAVE, currentColor.r, currentColor.g, currentColor.b); - BlynkC->InternalColor = currentColor; - break; - case BlynkConfig::CHANGE_DOT_COLOR: - BlynkC->ShelfDisplays->setDotLEDColor(currentColor); - Blynk.virtualWrite(BLYNK_CHANNEL_DOT_COLOR_SAVE, currentColor.r, currentColor.g, currentColor.b); - BlynkC->DotColor = currentColor; - break; - } - } +/** + * \brief Sync all channels as soon as blynk connects to the server + * Setup default UI states + * + */ +BLYNK_CONNECTED() +{ + Blynk.syncVirtual(BLYNK_CHANNEL_BRIGHTNESS_SLIDER); + Blynk.syncVirtual(BLYNK_CHANNEL_LIGHT_GROUP_SELECTOR); + Blynk.syncVirtual(BLYNK_CHANNEL_CURRENT_COLOR_PICKER); + Blynk.syncVirtual(BLYNK_CHANNEL_TIMER_TIME_INPUT); + Blynk.syncVirtual(BLYNK_CHANNEL_HOUR_COLOR_SAVE); + Blynk.syncVirtual(BLYNK_CHANNEL_MINUTE_COLOR_SAVE); + Blynk.syncVirtual(BLYNK_CHANNEL_INTERNAL_COLOR_SAVE); + Blynk.syncVirtual(BLYNK_CHANNEL_DOT_COLOR_SAVE); + Blynk.syncVirtual(BLYNK_CHANNEL_NIGHT_MODE_BRIGHTNESS); + Blynk.syncVirtual(BLYNK_CHANNEL_NIGHT_MODE_TIME_INPUT); + Blynk.syncVirtual(BLYNK_CHANNEL_NUM_SEPERATION_DOTS); + Blynk.syncVirtual(BLYNK_CHANNEL_ALARM_START_BUTTON); + Blynk.syncVirtual(BLYNK_CHANNEL_ALARM_TIME_INPUT); + Blynk.virtualWrite(BLYNK_CHANNEL_TIMER_START_BUTTON, 0); +} - /** - * \brief Use an unused virtual channel to permanently store the color for the hours light group to be able - * to restore it's state after a reset. - * - */ - BLYNK_WRITE(BLYNK_CHANNEL_HOUR_COLOR_SAVE) - { - CRGB SavedColor; - SavedColor.r = param[0].asInt(); - SavedColor.g = param[1].asInt(); - SavedColor.b = param[2].asInt(); - BlynkC->ShelfDisplays->setHourSegmentColors(SavedColor); - BlynkC->HourColor = SavedColor; - } +/** + * \brief Handle a brigness change by the user through the blynk app + * + */ +BLYNK_WRITE(BLYNK_CHANNEL_BRIGHTNESS_SLIDER) +{ + ClockS->clockBrightness = param[0].asInt(); + BlynkC->ShelfDisplays->setGlobalBrightness(ClockS->clockBrightness); +} - /** - * \brief Use an unused virtual channel to permanently store the color for the minutes light group to be able - * to restore it's state after a reset. - * - */ - BLYNK_WRITE(BLYNK_CHANNEL_MINUTE_COLOR_SAVE) +/** + * \brief Handle the change of the light group selector by the user through the blynk app + * + */ +BLYNK_WRITE(BLYNK_CHANNEL_LIGHT_GROUP_SELECTOR) +{ + switch (param.asInt()) { - CRGB SavedColor; - SavedColor.r = param[0].asInt(); - SavedColor.g = param[1].asInt(); - SavedColor.b = param[2].asInt(); - BlynkC->ShelfDisplays->setMinuteSegmentColors(SavedColor); - BlynkC->MinuteColor = SavedColor; + case 1: + BlynkC->ColorSelection = BlynkConfig::CHANGE_HOURS_COLOR; + Blynk.virtualWrite(BLYNK_CHANNEL_CURRENT_COLOR_PICKER, BlynkC->HourColor.r, BlynkC->HourColor.g, BlynkC->HourColor.b); + break; + case 2: + BlynkC->ColorSelection = BlynkConfig::CHANGE_MINUTES_COLOR; + Blynk.virtualWrite(BLYNK_CHANNEL_CURRENT_COLOR_PICKER, BlynkC->MinuteColor.r, BlynkC->MinuteColor.g, BlynkC->MinuteColor.b); + break; + case 3: + BlynkC->ColorSelection = BlynkConfig::CHANGE_INTERRIOR_COLOR; + Blynk.virtualWrite(BLYNK_CHANNEL_CURRENT_COLOR_PICKER, BlynkC->InternalColor.r, BlynkC->InternalColor.g, BlynkC->InternalColor.b); + break; + case 4: + BlynkC->ColorSelection = BlynkConfig::CHANGE_DOT_COLOR; + Blynk.virtualWrite(BLYNK_CHANNEL_CURRENT_COLOR_PICKER, BlynkC->DotColor.r, BlynkC->DotColor.g, BlynkC->DotColor.b); + break; } +} - /** - * \brief Use an unused virtual channel to permanently store the color for the internal light group to be able - * to restore it's state after a reset. - * - */ - BLYNK_WRITE(BLYNK_CHANNEL_INTERNAL_COLOR_SAVE) +/** + * \brief Handle a color change for the currently selected light group by the user through the blynk app + * + */ +BLYNK_WRITE(BLYNK_CHANNEL_CURRENT_COLOR_PICKER) +{ + CRGB currentColor; + currentColor.r = param[0].asInt(); + currentColor.g = param[1].asInt(); + currentColor.b = param[2].asInt(); + switch (BlynkC->ColorSelection) { - CRGB SavedColor; - SavedColor.r = param[0].asInt(); - SavedColor.g = param[1].asInt(); - SavedColor.b = param[2].asInt(); - BlynkC->ShelfDisplays->setInternalLEDColor(SavedColor); - BlynkC->InternalColor = SavedColor; + case BlynkConfig::CHANGE_HOURS_COLOR: + BlynkC->ShelfDisplays->setHourSegmentColors(currentColor); + Blynk.virtualWrite(BLYNK_CHANNEL_HOUR_COLOR_SAVE, currentColor.r, currentColor.g, currentColor.b); + BlynkC->HourColor = currentColor; + break; + case BlynkConfig::CHANGE_MINUTES_COLOR: + BlynkC->ShelfDisplays->setMinuteSegmentColors(currentColor); + Blynk.virtualWrite(BLYNK_CHANNEL_MINUTE_COLOR_SAVE, currentColor.r, currentColor.g, currentColor.b); + BlynkC->MinuteColor = currentColor; + break; + case BlynkConfig::CHANGE_INTERRIOR_COLOR: + BlynkC->ShelfDisplays->setInternalLEDColor(currentColor); + Blynk.virtualWrite(BLYNK_CHANNEL_INTERNAL_COLOR_SAVE, currentColor.r, currentColor.g, currentColor.b); + BlynkC->InternalColor = currentColor; + break; + case BlynkConfig::CHANGE_DOT_COLOR: + BlynkC->ShelfDisplays->setDotLEDColor(currentColor); + Blynk.virtualWrite(BLYNK_CHANNEL_DOT_COLOR_SAVE, currentColor.r, currentColor.g, currentColor.b); + BlynkC->DotColor = currentColor; + break; } +} - /** - * \brief Use an unused virtual channel to permanently store the color for the dot light group to be able - * to restore it's state after a reset. - * - */ - BLYNK_WRITE(BLYNK_CHANNEL_DOT_COLOR_SAVE) - { - CRGB SavedColor; - SavedColor.r = param[0].asInt(); - SavedColor.g = param[1].asInt(); - SavedColor.b = param[2].asInt(); - BlynkC->ShelfDisplays->setDotLEDColor(SavedColor); - BlynkC->DotColor = SavedColor; - } +/** + * \brief Use an unused virtual channel to permanently store the color for the hours light group to be able + * to restore it's state after a reset. + * + */ +BLYNK_WRITE(BLYNK_CHANNEL_HOUR_COLOR_SAVE) +{ + CRGB SavedColor; + SavedColor.r = param[0].asInt(); + SavedColor.g = param[1].asInt(); + SavedColor.b = param[2].asInt(); + BlynkC->ShelfDisplays->setHourSegmentColors(SavedColor); + BlynkC->HourColor = SavedColor; +} + +/** + * \brief Use an unused virtual channel to permanently store the color for the minutes light group to be able + * to restore it's state after a reset. + * + */ +BLYNK_WRITE(BLYNK_CHANNEL_MINUTE_COLOR_SAVE) +{ + CRGB SavedColor; + SavedColor.r = param[0].asInt(); + SavedColor.g = param[1].asInt(); + SavedColor.b = param[2].asInt(); + BlynkC->ShelfDisplays->setMinuteSegmentColors(SavedColor); + BlynkC->MinuteColor = SavedColor; +} + +/** + * \brief Use an unused virtual channel to permanently store the color for the internal light group to be able + * to restore it's state after a reset. + * + */ +BLYNK_WRITE(BLYNK_CHANNEL_INTERNAL_COLOR_SAVE) +{ + CRGB SavedColor; + SavedColor.r = param[0].asInt(); + SavedColor.g = param[1].asInt(); + SavedColor.b = param[2].asInt(); + BlynkC->ShelfDisplays->setInternalLEDColor(SavedColor); + BlynkC->InternalColor = SavedColor; +} + +/** + * \brief Use an unused virtual channel to permanently store the color for the dot light group to be able + * to restore it's state after a reset. + * + */ +BLYNK_WRITE(BLYNK_CHANNEL_DOT_COLOR_SAVE) +{ + CRGB SavedColor; + SavedColor.r = param[0].asInt(); + SavedColor.g = param[1].asInt(); + SavedColor.b = param[2].asInt(); + BlynkC->ShelfDisplays->setDotLEDColor(SavedColor); + BlynkC->DotColor = SavedColor; +} - /** - * \brief Handle a timer duration change by the user through the blynk app - * - */ - BLYNK_WRITE(BLYNK_CHANNEL_TIMER_TIME_INPUT) +/** + * \brief Handle a timer duration change by the user through the blynk app + * + */ +BLYNK_WRITE(BLYNK_CHANNEL_TIMER_TIME_INPUT) +{ + TimeManager::TimeInfo TimerDuration; + TimeInputParam t(param); + TimerDuration.hours = t.getStartHour(); + TimerDuration.minutes = t.getStartMinute(); + TimerDuration.seconds = t.getStartSecond(); + //Serial.printf("StartTime: %d:%d:%d\n\r", TimerDuration.hours, TimerDuration.minutes, TimerDuration.seconds); + TimeM->setTimerDuration(TimerDuration); +} + +/** + * \brief Handle the start of the timer by the user through the blynk app + * + */ +BLYNK_WRITE(BLYNK_CHANNEL_TIMER_START_BUTTON) +{ + if(param[0].asInt() == 1) { - TimeManager::TimeInfo TimerDuration; - TimeInputParam t(param); - TimerDuration.hours = t.getStartHour(); - TimerDuration.minutes = t.getStartMinute(); - TimerDuration.seconds = t.getStartSecond(); - //Serial.printf("StartTime: %d:%d:%d\n\r", TimerDuration.hours, TimerDuration.minutes, TimerDuration.seconds); - TimeM->setTimerDuration(TimerDuration); + TimeM->startTimer(); + Serial.println("Timer Started"); + ClockS->switchMode(ClockState::TIMER_MODE); } - - /** - * \brief Handle the start of the timer by the user through the blynk app - * - */ - BLYNK_WRITE(BLYNK_CHANNEL_TIMER_START_BUTTON) + else { - if(param[0].asInt() == 1) - { - TimeM->startTimer(); - Serial.println("Timer Started"); - ClockS->switchMode(ClockState::TIMER_MODE); - } - else - { - TimeM->stopTimer(); - Serial.println("Timer Stopped"); - Blynk.syncVirtual(BLYNK_CHANNEL_TIMER_TIME_INPUT); - BlynkC->ShelfDisplays->setGlobalBrightness(ClockS->clockBrightness); - ClockS->switchMode(ClockState::CLOCK_MODE); - } + TimeM->stopTimer(); + Serial.println("Timer Stopped"); + Blynk.syncVirtual(BLYNK_CHANNEL_TIMER_TIME_INPUT); + BlynkC->ShelfDisplays->setGlobalBrightness(ClockS->clockBrightness); + ClockS->switchMode(ClockState::CLOCK_MODE); } +} + +/** + * \brief Handle the nightime window setting change by the user through the blynk app + * + */ +BLYNK_WRITE(BLYNK_CHANNEL_NIGHT_MODE_TIME_INPUT) +{ + TimeInputParam t(param); + ClockS->NightModeStartTime.hours = t.getStartHour(); + ClockS->NightModeStartTime.minutes = t.getStartMinute(); + ClockS->NightModeStartTime.seconds = t.getStartSecond(); + ClockS->NightModeStopTime.hours = t.getStopHour(); + ClockS->NightModeStopTime.minutes = t.getStopMinute(); + ClockS->NightModeStopTime.seconds = t.getStopSecond(); +} - /** - * \brief Handle the nightime window setting change by the user through the blynk app - * - */ - BLYNK_WRITE(BLYNK_CHANNEL_NIGHT_MODE_TIME_INPUT) +/** + * \brief Handle the nighttime brightness setting change by the user through the blynk app + * + */ +BLYNK_WRITE(BLYNK_CHANNEL_NIGHT_MODE_BRIGHTNESS) +{ + ClockS->nightModeBrightness = param[0].asInt(); + if(TimeM->isInBetween(ClockS->NightModeStartTime, ClockS->NightModeStopTime)) { - TimeInputParam t(param); - ClockS->NightModeStartTime.hours = t.getStartHour(); - ClockS->NightModeStartTime.minutes = t.getStartMinute(); - ClockS->NightModeStartTime.seconds = t.getStartSecond(); - ClockS->NightModeStopTime.hours = t.getStopHour(); - ClockS->NightModeStopTime.minutes = t.getStopMinute(); - ClockS->NightModeStopTime.seconds = t.getStopSecond(); + BlynkC->ShelfDisplays->setGlobalBrightness(ClockS->nightModeBrightness); } +} - /** - * \brief Handle the nighttime brightness setting change by the user through the blynk app - * - */ - BLYNK_WRITE(BLYNK_CHANNEL_NIGHT_MODE_BRIGHTNESS) +/** + * \brief Handle the change of the dots display setting by the user through the blynk app + * + */ +BLYNK_WRITE(BLYNK_CHANNEL_NUM_SEPERATION_DOTS) +{ + ClockS->numDots = param[0].asInt() - 1; +} + +/** + * \brief Handle an alarm time change by the user through the blynk app + * + */ +BLYNK_WRITE(BLYNK_CHANNEL_ALARM_TIME_INPUT) +{ + TimeInputParam t(param); + TimeManager::TimeInfo AlarmTime + { + .hours = (uint8_t)t.getStartHour(), + .minutes = (uint8_t)t.getStartMinute(), + .seconds = (uint8_t)t.getStartSecond() + }; + uint8_t activeDays = 0x00; + for (uint8_t i = 0; i < 7; i++) { - ClockS->nightModeBrightness = param[0].asInt(); - if(TimeM->isInBetween(ClockS->NightModeStartTime, ClockS->NightModeStopTime)) + if (t.isWeekdaySelected(i + 1)) // 1 to 7 for every weekday in blynk config { - BlynkC->ShelfDisplays->setGlobalBrightness(ClockS->nightModeBrightness); + activeDays |= (1 << i); } } + TimeM->setAlarmTime(AlarmTime, (TimeManager::Weekdays)activeDays); +} - /** - * \brief Handle the change of the dots display setting by the user through the blynk app - * - */ - BLYNK_WRITE(BLYNK_CHANNEL_NUM_SEPERATION_DOTS) +/** + * \brief Handle the activation/deactivation/clearing of the alarm function by the user through the blynk app + * + */ +BLYNK_WRITE(BLYNK_CHANNEL_ALARM_START_BUTTON) +{ + if(ClockS->getMode() == ClockState::ALARM_NOTIFICATION) { - ClockS->numDots = param[0].asInt() - 1; + Blynk.setProperty(BLYNK_CHANNEL_ALARM_START_BUTTON, "onLabel", "Deactivate"); + TimeM->clearAlarm(); + Blynk.virtualWrite(BLYNK_CHANNEL_ALARM_START_BUTTON, 1); + BlynkC->isClearAction = true; } - - /** - * \brief Handle an alarm time change by the user through the blynk app - * - */ - BLYNK_WRITE(BLYNK_CHANNEL_ALARM_TIME_INPUT) + else if(BlynkC->isClearAction == true) { - TimeInputParam t(param); - TimeManager::TimeInfo AlarmTime - { - .hours = (uint8_t)t.getStartHour(), - .minutes = (uint8_t)t.getStartMinute(), - .seconds = (uint8_t)t.getStartSecond() - }; - uint8_t activeDays = 0x00; - for (uint8_t i = 0; i < 7; i++) - { - if (t.isWeekdaySelected(i + 1)) // 1 to 7 for every weekday in blynk config - { - activeDays |= (1 << i); - } - } - TimeM->setAlarmTime(AlarmTime, (TimeManager::Weekdays)activeDays); + BlynkC->isClearAction = false; } - - /** - * \brief Handle the activation/deactivation/clearing of the alarm function by the user through the blynk app - * - */ - BLYNK_WRITE(BLYNK_CHANNEL_ALARM_START_BUTTON) + else { - if(ClockS->getMode() == ClockState::ALARM_NOTIFICATION) - { - Blynk.setProperty(BLYNK_CHANNEL_ALARM_START_BUTTON, "onLabel", "Deactivate"); - TimeM->clearAlarm(); - Blynk.virtualWrite(BLYNK_CHANNEL_ALARM_START_BUTTON, 1); - BlynkC->isClearAction = true; - } - else if(BlynkC->isClearAction == true) - { - BlynkC->isClearAction = false; - } - else - { - TimeM->setAlarmMode(param[0].asInt() == 1); - } + TimeM->setAlarmMode(param[0].asInt() == 1); } -#endif +} diff --git a/lib/LED_clock/Config/Blynk/default/BlynkConfig.h b/lib/LED_clock/Config/Blynk/default/BlynkConfig.h index 2ccd68b..1ed4ab9 100644 --- a/lib/LED_clock/Config/Blynk/default/BlynkConfig.h +++ b/lib/LED_clock/Config/Blynk/default/BlynkConfig.h @@ -8,98 +8,106 @@ #define _BLYNK_CONFIG_H_ #include "Configuration.h" +#include "ConfigManager.h" +#include +#define FASTLED_INTERNAL +#include "FastLED.h" +#include "ClockState.h" -#if IS_BLYNK_ACTIVE == true - #include - #define FASTLED_INTERNAL - #include "FastLED.h" - #include "ClockState.h" +/** + * \addtogroup BlynkChannels + * \brief These are the channel definitions for Blynk + * \{ + */ +#define BLYNK_CHANNEL_BRIGHTNESS_SLIDER V0 +#define BLYNK_CHANNEL_LIGHT_GROUP_SELECTOR V1 +#define BLYNK_CHANNEL_CURRENT_COLOR_PICKER V2 +#define BLYNK_CHANNEL_TIMER_TIME_INPUT V3 +#define BLYNK_CHANNEL_TIMER_START_BUTTON V4 +#define BLYNK_CHANNEL_ALARM_TIME_INPUT V5 +#define BLYNK_CHANNEL_ALARM_START_BUTTON V6 +#define BLYNK_CHANNEL_NIGHT_MODE_TIME_INPUT V7 +#define BLYNK_CHANNEL_NIGHT_MODE_BRIGHTNESS V8 +#define BLYNK_CHANNEL_NUM_SEPERATION_DOTS V9 +#define BLYNK_CHANNEL_HOUR_COLOR_SAVE V10 +#define BLYNK_CHANNEL_MINUTE_COLOR_SAVE V11 +#define BLYNK_CHANNEL_INTERNAL_COLOR_SAVE V12 +#define BLYNK_CHANNEL_DOT_COLOR_SAVE V13 +/** \} */ + +/** + * \brief Loop function which is executed on the second core of the ESP + * + */ +void BlynkLoopCode(void* pvParameters); - /** - * \addtogroup BlynkChannels - * \brief These are the channel definitions for Blynk - * \{ - */ - #define BLYNK_CHANNEL_BRIGHTNESS_SLIDER V0 - #define BLYNK_CHANNEL_LIGHT_GROUP_SELECTOR V1 - #define BLYNK_CHANNEL_CURRENT_COLOR_PICKER V2 - #define BLYNK_CHANNEL_TIMER_TIME_INPUT V3 - #define BLYNK_CHANNEL_TIMER_START_BUTTON V4 - #define BLYNK_CHANNEL_ALARM_TIME_INPUT V5 - #define BLYNK_CHANNEL_ALARM_START_BUTTON V6 - #define BLYNK_CHANNEL_NIGHT_MODE_TIME_INPUT V7 - #define BLYNK_CHANNEL_NIGHT_MODE_BRIGHTNESS V8 - #define BLYNK_CHANNEL_NUM_SEPERATION_DOTS V9 - #define BLYNK_CHANNEL_HOUR_COLOR_SAVE V10 - #define BLYNK_CHANNEL_MINUTE_COLOR_SAVE V11 - #define BLYNK_CHANNEL_INTERNAL_COLOR_SAVE V12 - #define BLYNK_CHANNEL_DOT_COLOR_SAVE V13 - /** \} */ +/** + * \brief Configuration class for storing all Blynk related information + * + */ +class BlynkConfig +{ +private: + static BlynkConfig* instance; + ConfigManager* config; + TaskHandle_t BlynkLoop; + bool isTaskRunning; - /** - * \brief Loop function which is executed on the second core of the ESP - * - */ - void BlynkLoopCode(void* pvParameters); - /** - * \brief Configuration class for storing all Blynk related information - * - */ - class BlynkConfig - { - private: - static BlynkConfig* instance; - TaskHandle_t BlynkLoop; + BlynkConfig(); +public: + /** + * \brief possible selection options of the segmented switch responsible for selecting which color should be + * changed in the App + * + */ + enum ColorSelector {CHANGE_INTERRIOR_COLOR, CHANGE_HOURS_COLOR, CHANGE_MINUTES_COLOR, CHANGE_DOT_COLOR}; + ColorSelector ColorSelection; + CRGB InternalColor; + CRGB HourColor; + CRGB MinuteColor; + CRGB DotColor; + bool blynkUIUpdateRequired; + DisplayManager* ShelfDisplays; + bool isClearAction; - BlynkConfig(); - public: - /** - * \brief possible selection options of the segmented switch responsible for selecting which color should be - * changed in the App - * - */ - enum ColorSelector {CHANGE_INTERRIOR_COLOR, CHANGE_HOURS_COLOR, CHANGE_MINUTES_COLOR, CHANGE_DOT_COLOR}; - ColorSelector ColorSelection; - CRGB InternalColor; - CRGB HourColor; - CRGB MinuteColor; - CRGB DotColor; - bool blynkUIUpdateRequired; - DisplayManager* ShelfDisplays; - bool isClearAction; - ~BlynkConfig(); - /** - * \brief Get the Instance object. - * Use this to get an instance to the Blynk config sigelton instead of its constructor. - * - * \return BlynkConfig* pointer to the already existing or newly created BlynkConfig object. - */ - static BlynkConfig* getInstance(); + ~BlynkConfig(); + /** + * \brief Get the Instance object. + * Use this to get an instance to the Blynk config sigelton instead of its constructor. + * + * \return BlynkConfig* pointer to the already existing or newly created BlynkConfig object. + */ + static BlynkConfig* getInstance(); - /** - * \brief to be called as part of the setup function - * - * \pre WIFI connection has to be established already by calling #wifiSetup before this method - */ - void setup(); + /** + * \brief to be called as part of the setup function + * + * \pre WIFI connection has to be established already by calling #wifiSetup before this method + */ + void setup(); - /** - * \brief Stop the execution of Blynk - * - */ - void stop(); + /** + * \brief Stop the execution of Blynk + * + */ + void stop(); - /** - * \brief Call this if a UI update is needed - * Possible cases for this are that the timer ticked or was triggered or the alarm was triggered - * - */ - void updateUI(); - }; + /** + * \brief Call this if a UI update is needed + * Possible cases for this are that the timer ticked or was triggered or the alarm was triggered + * + */ + void updateUI(); -#endif + /** + * \brief Enable or disable the blynk functionality on runtime + * + * \param enabled if true blynk will be started if not running yet. If false blynk will be stopped if already running + */ + void setEnabled(bool enabled); +}; #endif diff --git a/lib/LED_clock/Config/Setup/clock-only/Configuration.h b/lib/LED_clock/Config/Setup/clock-only/Configuration.h index a3857bd..b460b60 100644 --- a/lib/LED_clock/Config/Setup/clock-only/Configuration.h +++ b/lib/LED_clock/Config/Setup/clock-only/Configuration.h @@ -10,16 +10,6 @@ #define _CONFIGURATIONS_H_ //Place all new configs here: -#define BASE_CONFIG_FILE_NAME "/BaseConfig.json" -#define COLOR_CONFIG_FILE_NAME "/ColorConfig.json" -#define HW_CONFIG_FILE_NAME "/HWConfig.json" -#define JSON_BUFFER_BASE_CONFIG 1024 -#define JSON_BUFFER_COLOR_CONFIG 300 -#define JSON_BUFFER_HW_CONFIG 1024 - -#define JSON_BUFFER_CHUNK_SIZE 256 -#define JSON_BUFFER_INITIAL_SIZE 200 -#define MAX_JSON_BUFFER 5000 @@ -31,74 +21,36 @@ * \{ */ + /** - * \brief If you want to run the system in a minimal mode to test some basic functionality or - * debug something it could be useful to disable wifi functionality completley. + * \brief In case the blynk communication is not working this line causes Blynk to send debug output to the serial port. + * If you are not worried about Blynk or have to diagnose some other issue you can comment this line out. */ -#define RUN_WITHOUT_WIFI false - -#if RUN_WITHOUT_WIFI == false - /** - * \brief If you want Blynk functionality set this to true and set your authentication token. Otherwise set it to false - */ - #define IS_BLYNK_ACTIVE true - - #if IS_BLYNK_ACTIVE == true - /** - * \brief If you want Blynk functionality paste your authentication token here. - */ - #define BLYNK_AUTH_TOKEN "AUTH_TOKEN_GOES_HERE" +#define BLYNK_PRINT Serial - /** - * \brief In case the blynk communication is not working this line causes Blynk to send debug output to the serial port. - * If you are not worried about Blynk or have to diagnose some other issue you can comment this line out. - */ - #define BLYNK_PRINT Serial - - /** - * \brief Set the Blynk server address. - * \note I had troubles with using the proper blynk domain so I am using the IP address instead. - * Maybe this could create problems in the future so it is recommended to use the official domain. - */ - #define BLYNK_SERVER "blynk-cloud.com" // "139.59.206.133" - - #endif - - /** - * \brief If you want to use OTA upload instead or in addition to the normal cable upload set this option to true. - * To actually flash something via OTA you have to uncomment the OTA flash lines in the \ref platformio.ini file - * This is a nice addition to cable upload but it doesn't replace it completely. - * If the microcontroller crashes because of bad configuration you still have to use a cable - */ - #define ENABLE_OTA_UPLOAD true +/** + * \brief The host name that shall be used for OTA updates. If you change this here it must also be changed + * in the \ref platformio.ini file + */ +#define OTA_UPDATE_HOST_NAME "LED-Clock" - #if ENABLE_OTA_UPLOAD == true - /** - * \brief The host name that shall be used for OTA updates. If you change this here it must also be changed - * in the \ref platformio.ini file - */ - #define OTA_UPDATE_HOST_NAME "LED-Clock" - #endif +/** + * \brief The number of times the controller tries to connect to wifi before it fails and goes into + * smartConfig mode (if that is enabled) + */ +#define NUM_RETRIES 50 - /** - * \brief The number of times the controller tries to connect to wifi before it fails and goes into - * smartConfig mode (if that is enabled) - */ - #define NUM_RETRIES 50 +/** + * \brief Use the ESP smart config to setup the wifi network. If you want to set it manually set this to false + */ +#define USE_ESPTOUCH_SMART_CONFIG true +#if USE_ESPTOUCH_SMART_CONFIG == false /** - * \brief Use the ESP smart config to setup the wifi network. If you want to set it manually set this to false + * \brief WIFI_SSID and WIFI_PW are only needed if smart setup is disabled */ - #define USE_ESPTOUCH_SMART_CONFIG true - - #if USE_ESPTOUCH_SMART_CONFIG == false - /** - * \brief WIFI_SSID and WIFI_PW are only needed if smart setup is disabled - */ - #define WIFI_SSID "SET_YOUR_SSID_HERE" - #define WIFI_PW "SET_YOUR_PASSWORD_HERE" - #endif - + #define WIFI_SSID "SET_YOUR_SSID_HERE" + #define WIFI_PW "SET_YOUR_PASSWORD_HERE" #endif /** diff --git a/lib/LED_clock/Config/Setup/default/Configuration.h b/lib/LED_clock/Config/Setup/default/Configuration.h index 07f911e..bfe08b4 100644 --- a/lib/LED_clock/Config/Setup/default/Configuration.h +++ b/lib/LED_clock/Config/Setup/default/Configuration.h @@ -15,74 +15,36 @@ * \{ */ + /** - * \brief If you want to run the system in a minimal mode to test some basic functionality or - * debug something it could be useful to disable wifi functionality completley. + * \brief In case the blynk communication is not working this line causes Blynk to send debug output to the serial port. + * If you are not worried about Blynk or have to diagnose some other issue you can comment this line out. */ -#define RUN_WITHOUT_WIFI false - -#if RUN_WITHOUT_WIFI == false - /** - * \brief If you want Blynk functionality set this to true and set your authentication token. Otherwise set it to false - */ - #define IS_BLYNK_ACTIVE true - - #if IS_BLYNK_ACTIVE == true - /** - * \brief If you want Blynk functionality paste your authentication token here. - */ - #define BLYNK_AUTH_TOKEN "AUTH_TOKEN_GOES_HERE" +#define BLYNK_PRINT Serial - /** - * \brief In case the blynk communication is not working this line causes Blynk to send debug output to the serial port. - * If you are not worried about Blynk or have to diagnose some other issue you can comment this line out. - */ - #define BLYNK_PRINT Serial - - /** - * \brief Set the Blynk server address. - * \note I had troubles with using the proper blynk domain so I am using the IP address instead. - * Maybe this could create problems in the future so it is recommended to use the official domain. - */ - #define BLYNK_SERVER "blynk-cloud.com" // "139.59.206.133" - - #endif - - /** - * \brief If you want to use OTA upload instead or in addition to the normal cable upload set this option to true. - * To actually flash something via OTA you have to uncomment the OTA flash lines in the \ref platformio.ini file - * This is a nice addition to cable upload but it doesn't replace it completely. - * If the microcontroller crashes because of bad configuration you still have to use a cable - */ - #define ENABLE_OTA_UPLOAD true +/** + * \brief The host name that shall be used for OTA updates. If you change this here it must also be changed + * in the \ref platformio.ini file + */ +#define OTA_UPDATE_HOST_NAME "LED-Shelf" - #if ENABLE_OTA_UPLOAD == true - /** - * \brief The host name that shall be used for OTA updates. If you change this here it must also be changed - * in the \ref platformio.ini file - */ - #define OTA_UPDATE_HOST_NAME "LED-Shelf" - #endif +/** + * \brief The number of times the controller tries to connect to wifi before it fails and goes into + * smartConfig mode (if that is enabled) + */ +#define NUM_RETRIES 50 - /** - * \brief The number of times the controller tries to connect to wifi before it fails and goes into - * smartConfig mode (if that is enabled) - */ - #define NUM_RETRIES 50 +/** + * \brief Use the ESP smart config to setup the wifi network. If you want to set it manually set this to false + */ +#define USE_ESPTOUCH_SMART_CONFIG true +#if USE_ESPTOUCH_SMART_CONFIG == false /** - * \brief Use the ESP smart config to setup the wifi network. If you want to set it manually set this to false + * \brief WIFI_SSID and WIFI_PW are only needed if smart setup is disabled */ - #define USE_ESPTOUCH_SMART_CONFIG true - - #if USE_ESPTOUCH_SMART_CONFIG == false - /** - * \brief WIFI_SSID and WIFI_PW are only needed if smart setup is disabled - */ - #define WIFI_SSID "SET_YOUR_SSID_HERE" - #define WIFI_PW "SET_YOUR_PASSWORD_HERE" - #endif - + #define WIFI_SSID "SET_YOUR_SSID_HERE" + #define WIFI_PW "SET_YOUR_PASSWORD_HERE" #endif /** diff --git a/lib/LED_clock/Config/Setup/diy-machines/Configuration.h b/lib/LED_clock/Config/Setup/diy-machines/Configuration.h index e486ac7..d1959c4 100644 --- a/lib/LED_clock/Config/Setup/diy-machines/Configuration.h +++ b/lib/LED_clock/Config/Setup/diy-machines/Configuration.h @@ -16,73 +16,34 @@ */ /** - * \brief If you want to run the system in a minimal mode to test some basic functionality or - * debug something it could be useful to disable wifi functionality completley. + * \brief In case the blynk communication is not working this line causes Blynk to send debug output to the serial port. + * If you are not worried about Blynk or have to diagnose some other issue you can comment this line out. */ -#define RUN_WITHOUT_WIFI false +#define BLYNK_PRINT Serial -#if RUN_WITHOUT_WIFI == false - /** - * \brief If you want Blynk functionality set this to true and set your authentication token. Otherwise set it to false - */ - #define IS_BLYNK_ACTIVE true - - #if IS_BLYNK_ACTIVE == true - /** - * \brief If you want Blynk functionality paste your authentication token here. - */ - #define BLYNK_AUTH_TOKEN "AUTH_TOKEN_GOES_HERE" - - /** - * \brief In case the blynk communication is not working this line causes Blynk to send debug output to the serial port. - * If you are not worried about Blynk or have to diagnose some other issue you can comment this line out. - */ - #define BLYNK_PRINT Serial - - /** - * \brief Set the Blynk server address. - * \note I had troubles with using the proper blynk domain so I am using the IP address instead. - * Maybe this could create problems in the future so it is recommended to use the official domain. - */ - #define BLYNK_SERVER "blynk-cloud.com" // "139.59.206.133" - - #endif - - /** - * \brief If you want to use OTA upload instead or in addition to the normal cable upload set this option to true. - * To actually flash something via OTA you have to uncomment the OTA flash lines in the \ref platformio.ini file - * This is a nice addition to cable upload but it doesn't replace it completely. - * If the microcontroller crashes because of bad configuration you still have to use a cable - */ - #define ENABLE_OTA_UPLOAD true +/** + * \brief The host name that shall be used for OTA updates. If you change this here it must also be changed + * in the \ref platformio.ini file + */ +#define OTA_UPDATE_HOST_NAME "LED-Shelf" - #if ENABLE_OTA_UPLOAD == true - /** - * \brief The host name that shall be used for OTA updates. If you change this here it must also be changed - * in the \ref platformio.ini file - */ - #define OTA_UPDATE_HOST_NAME "LED-Shelf" - #endif +/** + * \brief The number of times the controller tries to connect to wifi before it fails and goes into + * smartConfig mode (if that is enabled) + */ +#define NUM_RETRIES 50 - /** - * \brief The number of times the controller tries to connect to wifi before it fails and goes into - * smartConfig mode (if that is enabled) - */ - #define NUM_RETRIES 50 +/** + * \brief Use the ESP smart config to setup the wifi network. If you want to set it manually set this to false + */ +#define USE_ESPTOUCH_SMART_CONFIG true +#if USE_ESPTOUCH_SMART_CONFIG == false /** - * \brief Use the ESP smart config to setup the wifi network. If you want to set it manually set this to false + * \brief WIFI_SSID and WIFI_PW are only needed if smart setup is disabled */ - #define USE_ESPTOUCH_SMART_CONFIG true - - #if USE_ESPTOUCH_SMART_CONFIG == false - /** - * \brief WIFI_SSID and WIFI_PW are only needed if smart setup is disabled - */ - #define WIFI_SSID "SET_YOUR_SSID_HERE" - #define WIFI_PW "SET_YOUR_PASSWORD_HERE" - #endif - + #define WIFI_SSID "SET_YOUR_SSID_HERE" + #define WIFI_PW "SET_YOUR_PASSWORD_HERE" #endif /** diff --git a/lib/LED_clock/Modules/Animator/src/Animator.cpp b/lib/LED_clock/Modules/Animator/src/Animator.cpp index 07953e0..fcb06ad 100644 --- a/lib/LED_clock/Modules/Animator/src/Animator.cpp +++ b/lib/LED_clock/Modules/Animator/src/Animator.cpp @@ -16,6 +16,7 @@ Animator* Animator::getInstance() if(currentInstance == nullptr) { currentInstance = new Animator(); + Serial.println("[D]: Animator instance created"); } return currentInstance; } diff --git a/lib/LED_clock/Modules/ClockState/src/ClockState.cpp b/lib/LED_clock/Modules/ClockState/src/ClockState.cpp index bb8a176..f215fbf 100644 --- a/lib/LED_clock/Modules/ClockState/src/ClockState.cpp +++ b/lib/LED_clock/Modules/ClockState/src/ClockState.cpp @@ -28,6 +28,7 @@ ClockState* ClockState::getInstance() if(instance == nullptr) { instance = new ClockState(); + Serial.println("[D]: ClockState instance created"); } return instance; } diff --git a/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h index 4cb279f..a27dde5 100644 --- a/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h +++ b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h @@ -13,6 +13,33 @@ #include "LinkedList.h" #include "Configuration.h" +/** + * \addtogroup Config_manager_config + * \brief Configuration for the config manager + * \{ + */ + +#define BASE_CONFIG_FILE_NAME "/BaseConfig.json" +#define COLOR_CONFIG_FILE_NAME "/ColorConfig.json" +#define HW_CONFIG_FILE_NAME "/HWConfig.json" +#define EVENT_IDS_FILE_NAME "/ConfigChangeEventIDs.json" +#define JSON_BUFFER_BASE_CONFIG 1536 +#define JSON_BUFFER_COLOR_CONFIG 384 +#define JSON_BUFFER_HW_CONFIG 1536 +#define JSON_BUFFER_EVENT_IDS 2106 + +#define JSON_BUFFER_CHUNK_SIZE 256 +#define JSON_BUFFER_INITIAL_SIZE 200 +#define MAX_JSON_BUFFER 5000 + +#define BLYNK_ACTIVE_KEY "ENABLE_BLYNK" +#define BLYNK_TOKEN_KEY "BLYNK_AUTH_TOKEN" +#define BLYNK_SERVER_KEY "BLYNK_SERVER" + +#define DEMO_MODE_KEY "TIME_MANAGER_DEMO_MODE" + +/** \} */ + /** * \brief The config manager is responsible to handle the json configuration files. * It stores all config values in variables to be able to use them in the code @@ -20,30 +47,60 @@ class ConfigManager { public: - typedef void (*propertyChangedCallback)(void); + typedef void (*propertyChangedCallback)(ConfigManager* config); + enum ConfigType {BASE_CONFIG, COLOR_CONFIG, HW_CONFIG, _CONFIG_TYPE_LENGTH}; private: struct changedEventCallback { propertyChangedCallback callback; - void* property; - uint8_t configClass; + uint8_t configEvent; }; + + enum ConfigClass_id { + WIFI_CONFIG_CLASS_ID = 0, + BLYNK_CONFIG_CLASS_ID = 1, + BASE_CONFIG_CLASS_ID = 2, + COLOR_CONFIG_CLASS_ID = 3, + HW_CONFIG_CLASS_ID = 4, + _CONFIG_CLASS_ID_LENGTH = 5 + }; + static ConfigManager* instance; DynamicList eventCallbacks; - static char UNDEFINED[]; DynamicJsonDocument* BaseConfig; DynamicJsonDocument* ColorConfig; DynamicJsonDocument* HWConfig; + DynamicJsonDocument* EventIDs; + + bool configClassWasUpdated[_CONFIG_CLASS_ID_LENGTH]; + bool configNeedsSaving[_CONFIG_TYPE_LENGTH]; ConfigManager(); DynamicJsonDocument* deserializeDynamically(fs::File &input, uint64_t initialDocSize); bool deserializeDynamically(fs::File &input, DynamicJsonDocument* initialDocument); + // helpers for returning default types on error + bool getDefault(bool* input); + int getDefault(int* input); + const char* getDefault(const char** input); template - T getProperty(DynamicJsonDocument* root, char* key); + T getProperty(DynamicJsonDocument* root, const char* key); bool loadConfigFromMemory(const char* filename, DynamicJsonDocument* doc); + + DynamicJsonDocument* getDocument(ConfigType config); + const char* getFileName(ConfigType config); + + void checkCallbacks(uint8_t ConfigEvent); public: + enum ConfigEvents { + WIFI_CONFIG_CHANGED = 0x01 << WIFI_CONFIG_CLASS_ID, + BLYNK_CONFIG_CHANGED = 0x01 << BLYNK_CONFIG_CLASS_ID, + BASE_CONFIG_CHANGED = 0x01 << BASE_CONFIG_CLASS_ID, + COLOR_CONFIG_CHANGED = 0x01 << COLOR_CONFIG_CLASS_ID, + HW_CONFIG_CHANGED = 0x01 << HW_CONFIG_CLASS_ID + }; + /** * \brief Destroys the Config Manager object and cause #ConfigManager::getInstance to create a new object the next time it is called */ @@ -56,23 +113,20 @@ class ConfigManager */ static ConfigManager* getInstance(); - - template - T getBaseProperty(char* key); + T getProperty(ConfigType config, const char* key); template - T getColorProperty(char* key); + bool setProperty(ConfigType config, char* key, T value); - template - T getHWProperty(char* key); + bool setProperty(JsonPair& keyValuePair, ConfigType config); - template - bool setBaseProperty(char* key, T value); + void saveConfigPersistent(ConfigType config); + void applyChanges(); - bool setBaseProperty(JsonPair& keyValuePair); + void registerOnChangedCallback(uint8_t ConfigEvent, propertyChangedCallback Callback); + void unregisterOnChangedCallback(propertyChangedCallback Callback); }; - #endif diff --git a/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp b/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp index e93eccc..c93584a 100644 --- a/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp +++ b/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp @@ -8,7 +8,12 @@ #include "ConfigManager.h" ConfigManager* ConfigManager::instance = nullptr; -char ConfigManager::UNDEFINED[] = "UNDEFINED"; + +//Template types that can be used + +template bool ConfigManager::getProperty(ConfigType config, const char* key); +template int ConfigManager::getProperty(ConfigType config, const char* key); +template const char* ConfigManager::getProperty(ConfigType config, const char* key); ConfigManager::ConfigManager() { @@ -23,15 +28,25 @@ ConfigManager::ConfigManager() BaseConfig = new DynamicJsonDocument(JSON_BUFFER_BASE_CONFIG); ColorConfig = new DynamicJsonDocument(JSON_BUFFER_COLOR_CONFIG); HWConfig = new DynamicJsonDocument(JSON_BUFFER_HW_CONFIG); + EventIDs = new DynamicJsonDocument(JSON_BUFFER_EVENT_IDS); bool loadSuccessful = true; loadSuccessful &= loadConfigFromMemory(BASE_CONFIG_FILE_NAME, BaseConfig); loadSuccessful &= loadConfigFromMemory(COLOR_CONFIG_FILE_NAME, ColorConfig); loadSuccessful &= loadConfigFromMemory(HW_CONFIG_FILE_NAME, HWConfig); + loadSuccessful &= loadConfigFromMemory(EVENT_IDS_FILE_NAME, EventIDs); if(loadSuccessful == false) { Serial.println("[E]: Loading of json config from filesystem unsuccessful"); } + for (uint8_t i = 0; i < _CONFIG_CLASS_ID_LENGTH; i++) + { + configClassWasUpdated[i] = false; + } + for (uint8_t i = 0; i < _CONFIG_TYPE_LENGTH; i++) + { + configNeedsSaving[i] = false; + } } ConfigManager::~ConfigManager() @@ -44,10 +59,26 @@ ConfigManager* ConfigManager::getInstance() if(instance == nullptr) { instance = new ConfigManager(); + Serial.println("[D]: ConfigManager instance created"); } return instance; } +bool ConfigManager::getDefault(bool* input) +{ + return false; +} + +int ConfigManager::getDefault(int* input) +{ + return -1; +} + +const char* ConfigManager::getDefault(const char** input) +{ + return nullptr; +} + DynamicJsonDocument* ConfigManager::deserializeDynamically(fs::File &input, uint64_t initialDocSize) { //try to initialize a json document big enough to fit all the data @@ -161,7 +192,7 @@ bool ConfigManager::loadConfigFromMemory(const char* filename, DynamicJsonDocume template -T ConfigManager::getProperty(DynamicJsonDocument* root, char* key) +T ConfigManager::getProperty(DynamicJsonDocument* root, const char* key) { JsonVariant jvalue = (*root)[key]; if (!jvalue.isNull()) @@ -175,42 +206,147 @@ T ConfigManager::getProperty(DynamicJsonDocument* root, char* key) return jvalue.as(); } } - return nullptr; + T* helper = nullptr; + return getDefault(helper); } template -T ConfigManager::getBaseProperty(char* key) +T ConfigManager::getProperty(ConfigType config, const char* key) { - return getProperty(BaseConfig, key); + return getProperty(getDocument(config), key); } template -T ConfigManager::getColorProperty(char* key) +bool ConfigManager::setProperty(ConfigType config, char* key, T value) { - return getProperty(ColorConfig, key); + DynamicJsonDocument* doc = getDocument(config); + if(doc->containsKey(key)) + { + return (*doc)[key].set(key); + } + Serial.printf("[E]: Config does not contain key %s\n\r", key); + return false; } -template -T ConfigManager::getHWProperty(char* key) +DynamicJsonDocument* ConfigManager::getDocument(ConfigType config) { - return getProperty(HWConfig, key); + switch (config) + { + case BASE_CONFIG: + return BaseConfig; + case COLOR_CONFIG: + return ColorConfig; + case HW_CONFIG: + return HWConfig; + default: + return nullptr; + } } -template -bool ConfigManager::setBaseProperty(char* key, T value) +const char* ConfigManager::getFileName(ConfigType config) { - if(BaseConfig->containsKey(key)) + switch (config) { - return (*BaseConfig)[key].set(key); + case BASE_CONFIG: + return BASE_CONFIG_FILE_NAME; + case COLOR_CONFIG: + return COLOR_CONFIG_FILE_NAME; + case HW_CONFIG: + return HW_CONFIG_FILE_NAME; + default: + return nullptr; } - return false; } -bool ConfigManager::setBaseProperty(JsonPair& keyValuePair) +bool ConfigManager::setProperty(JsonPair& keyValuePair, ConfigType config) { - if(BaseConfig->containsKey(keyValuePair.key())) + if(config >= _CONFIG_TYPE_LENGTH) { - return (*BaseConfig)[keyValuePair.key()].set(keyValuePair.value()); + Serial.printf("[E]: Config id %d is higher than maximum allowed of %d\n\r", config, _CONFIG_TYPE_LENGTH); + return false; + } + DynamicJsonDocument* doc = getDocument(config); + if(doc == nullptr) + { + Serial.printf("[E]: Config with config id %d was invalid\n\r", config); + return false; + } + if(doc->containsKey(keyValuePair.key())) + { + if((*doc)[keyValuePair.key()] != keyValuePair.value()) + { + //TODO: find a better solution for this + uint8_t eventID = (*EventIDs)[keyValuePair.key()]; + configClassWasUpdated[eventID] = true; + configNeedsSaving[config] = true; + return (*doc)[keyValuePair.key()].set(keyValuePair.value()); + } + else + { + return true; + } } + Serial.printf("[E]: Config does not contain key %s\n\r", keyValuePair.key().c_str()); return false; +} + +void ConfigManager::saveConfigPersistent(ConfigType config) +{ + if(config >= _CONFIG_TYPE_LENGTH) + { + return; + } + if(configNeedsSaving[config]) + { + File configFile = SPIFFS.open(getFileName(config), FILE_WRITE); + serializeJson(*getDocument(config), configFile); + configFile.close(); + configNeedsSaving[config] = false; + } + else + { + Serial.printf("[I]: Skipped saving of %s to memory as there was no change.\n\r", getFileName(config)); + } +} + +void ConfigManager::applyChanges() +{ + uint8_t updatedConfigClasses = 0x00; + for (uint8_t i = 0; i < _CONFIG_CLASS_ID_LENGTH; i++) + { + if(configClassWasUpdated[i] == true) + { + updatedConfigClasses |= 1 << i; + configClassWasUpdated[i] = false; + } + } + checkCallbacks(updatedConfigClasses); +} + +void ConfigManager::checkCallbacks(uint8_t ConfigEvent) +{ + for (int i = 0; i < eventCallbacks.size(); i++) + { + if((eventCallbacks.get(i)->configEvent & ConfigEvent) != 0x00) + { + eventCallbacks.get(i)->callback(this); + } + } +} + +void ConfigManager::registerOnChangedCallback(uint8_t ConfigEvent, propertyChangedCallback Callback) +{ + changedEventCallback* newCallback = new changedEventCallback{.callback = Callback, .configEvent = ConfigEvent}; + eventCallbacks.add(newCallback); +} + +void ConfigManager::unregisterOnChangedCallback(propertyChangedCallback Callback) +{ + for (int i = 0; i < eventCallbacks.size(); i++) + { + if(eventCallbacks.get(i)->callback == Callback) + { + eventCallbacks.remove(i); + } + } } \ No newline at end of file diff --git a/lib/LED_clock/Modules/DisplayManager/src/DisplayManager.cpp b/lib/LED_clock/Modules/DisplayManager/src/DisplayManager.cpp index 111ae99..072e144 100644 --- a/lib/LED_clock/Modules/DisplayManager/src/DisplayManager.cpp +++ b/lib/LED_clock/Modules/DisplayManager/src/DisplayManager.cpp @@ -64,6 +64,7 @@ DisplayManager* DisplayManager::getInstance() if(instance == nullptr) { instance = new DisplayManager(); + Serial.println("[D]: DisplayManager instance created"); } return instance; } diff --git a/lib/LED_clock/Modules/TimeManager/inc/TimeManager.h b/lib/LED_clock/Modules/TimeManager/inc/TimeManager.h index 353e3da..f4126b5 100644 --- a/lib/LED_clock/Modules/TimeManager/inc/TimeManager.h +++ b/lib/LED_clock/Modules/TimeManager/inc/TimeManager.h @@ -10,9 +10,8 @@ #include #include "time.h" #include "Configuration.h" -#if RUN_WITHOUT_WIFI == false - #include "WiFi.h" -#endif +#include "WiFi.h" +#include "ConfigManager.h" /** * \brief The TimeManager is responsible for synchronizing the time to the NTP servers and keeping track of it diff --git a/lib/LED_clock/Modules/TimeManager/src/TimeManager.cpp b/lib/LED_clock/Modules/TimeManager/src/TimeManager.cpp index bbca0c7..a067ed7 100644 --- a/lib/LED_clock/Modules/TimeManager/src/TimeManager.cpp +++ b/lib/LED_clock/Modules/TimeManager/src/TimeManager.cpp @@ -43,6 +43,7 @@ TimeManager* TimeManager::getInstance() if(TimeManagerSingelton == nullptr) { TimeManagerSingelton = new TimeManager(); + Serial.println("[D]: TimeManager instance created"); } return TimeManagerSingelton; } @@ -251,8 +252,10 @@ void TimeManager::clearAlarm() void IRAM_ATTR onTimer() { TimeManager* timeM = TimeManager::getInstance(); + ConfigManager* config = ConfigManager::getInstance(); // Time code, use this for normal operation - #if TIME_MANAGER_DEMO_MODE == false + if(!config->getProperty(ConfigManager::HW_CONFIG, DEMO_MODE_KEY)) + { if(timeM->offlineTimeCounter++ >= TIME_SYNC_INTERVALL && WiFi.status() == WL_CONNECTED) { if(timeM->synchronize() == true) @@ -307,7 +310,9 @@ void IRAM_ATTR onTimer() timeM->AlarmCleared = false; } } - #else + } + else + { //DEMO CODE: Useful for testing animations timeM->currentTime.minutes++; if(timeM->currentTime.minutes > 59) @@ -319,5 +324,5 @@ void IRAM_ATTR onTimer() { timeM->currentTime.hours = 0; } - #endif + } } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 3494874..7d8795b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,30 +12,20 @@ #include "ESPAsyncWebServer.h" #include "AsyncJson.h" #include - -#if RUN_WITHOUT_WIFI == false - #include "WiFi.h" -#endif -#if ENABLE_OTA_UPLOAD == true - #include -#endif -#if IS_BLYNK_ACTIVE == true - #include "BlynkConfig.h" -#endif - -DisplayManager* ShelfDisplays = DisplayManager::getInstance(); -BlynkConfig* BlynkConfiguration = BlynkConfig::getInstance(); -TimeManager* timeM = TimeManager::getInstance(); -ClockState* states = ClockState::getInstance(); +#include "WiFi.h" +#include +#include "BlynkConfig.h" + +DisplayManager* ShelfDisplays; +BlynkConfig* BlynkConfiguration; +TimeManager* timeM; +ClockState* states; ConfigManager* config; AsyncWebServer server(80); +bool OTAprogressFirstStep = true; -#if ENABLE_OTA_UPLOAD == true - void setupOTA(); -#endif -#if RUN_WITHOUT_WIFI == false - void wifiSetup(); -#endif +void setupOTA(); +void wifiSetup(); void TimerTick(); void TimerDone(); @@ -85,6 +75,37 @@ void startupAnimation() } } +String saveSettings(ConfigManager::ConfigType type, JsonObject* json) +{ + String FailedKeys = ""; + bool anyErrors = false; + for (JsonPair kv : *json) + { + if(!config->setProperty(kv, type)) + { + if(anyErrors != false) + { + FailedKeys += ", "; + } + FailedKeys += kv.key().c_str(); + anyErrors = true; + } + } + config->applyChanges(); + config->saveConfigPersistent(type); + if(anyErrors == true) + { + String ErrorMessage = "ERROR: Following base config properties were not set: " + FailedKeys; + Serial.print("[W]: "); + Serial.println(ErrorMessage); + return ErrorMessage; + } + else + { + return "Config Saved without errors"; + } +} + void startWebServer() { server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html"); @@ -99,18 +120,26 @@ void startWebServer() } }); - AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/BaseConfig", [](AsyncWebServerRequest *request, JsonVariant &json) + AsyncCallbackJsonWebHandler* baseConfigHandler = new AsyncCallbackJsonWebHandler("/BaseConfig", [](AsyncWebServerRequest *request, JsonVariant &json) { - for (JsonPair kv : json.as()) - { - if(!config->setBaseProperty(kv)) - { - Serial.printf("[W]: Property %s not set\n\r", kv.key().c_str()); - } - } - request->send(200, "text/plain", "Config OK"); + JsonObject temp = json.as(); + request->send(200, "text/plain", saveSettings(ConfigManager::BASE_CONFIG, &temp)); + }); + server.addHandler(baseConfigHandler); + + AsyncCallbackJsonWebHandler* colorConfigHandler = new AsyncCallbackJsonWebHandler("/ColorConfig", [](AsyncWebServerRequest *request, JsonVariant &json) + { + JsonObject temp = json.as(); + request->send(200, "text/plain", saveSettings(ConfigManager::COLOR_CONFIG, &temp)); + }); + server.addHandler(colorConfigHandler); + + AsyncCallbackJsonWebHandler* hwConfigHandler = new AsyncCallbackJsonWebHandler("/HWConfig", [](AsyncWebServerRequest *request, JsonVariant &json) + { + JsonObject temp = json.as(); + request->send(200, "text/plain", saveSettings(ConfigManager::HW_CONFIG, &temp)); }); - server.addHandler(handler); + server.addHandler(hwConfigHandler); server.begin(); } @@ -119,8 +148,15 @@ void setup() { Serial.begin(115200); - config = ConfigManager::getInstance(); + Serial.println("Loading config..."); + config = ConfigManager::getInstance(); + Serial.println("Initializing modules..."); + ShelfDisplays = DisplayManager::getInstance(); + BlynkConfiguration = BlynkConfig::getInstance(); + timeM = TimeManager::getInstance(); + states = ClockState::getInstance(); + Serial.println("Initializing LEDs..."); ShelfDisplays->InitSegments(0, NUM_LEDS_PER_SEGMENT, WIFI_CONNECTING_COLOR, 50); ShelfDisplays->setHourSegmentColors(HOUR_COLOR); @@ -128,19 +164,12 @@ void setup() ShelfDisplays->setInternalLEDColor(INTERNAL_COLOR); ShelfDisplays->setDotLEDColor(SEPERATION_DOT_COLOR); - #if RUN_WITHOUT_WIFI == false - wifiSetup(); - #endif - #if ENABLE_OTA_UPLOAD == true - setupOTA(); - #endif - #if ENABLE_OTA_UPLOAD == true - ArduinoOTA.handle(); //give ota the opportunity to update before the main loop starts in case we have a crash in there - #endif + Serial.println("Running wifi setup..."); + wifiSetup(); + setupOTA(); + ArduinoOTA.handle(); //give ota the opportunity to update before the main loop starts in case we have a crash in there - #if IS_BLYNK_ACTIVE == true - BlynkConfiguration->setup(); - #endif + BlynkConfiguration->setup(); startWebServer(); @@ -161,210 +190,188 @@ void setup() void loop() { - #if ENABLE_OTA_UPLOAD == true - ArduinoOTA.handle(); - #endif + ArduinoOTA.handle(); states->handleStates(); //updates display states, switches between modes etc. - - // Test code: - // if((millis()-last)>= 1500) - // { - // ShelfDisplays->test(); - // last = millis(); - // } ShelfDisplays->handle(); } void AlarmTriggered() { states->switchMode(ClockState::ALARM_NOTIFICATION); - #if IS_BLYNK_ACTIVE == true - BlynkConfiguration->updateUI(); - #endif + BlynkConfiguration->updateUI(); } void TimerTick() { - #if IS_BLYNK_ACTIVE == true - BlynkConfiguration->updateUI(); - #endif + BlynkConfiguration->updateUI(); } void TimerDone() { states->switchMode(ClockState::TIMER_NOTIFICATION); - #if IS_BLYNK_ACTIVE == true - BlynkConfiguration->updateUI(); - #endif + BlynkConfiguration->updateUI(); } -#if RUN_WITHOUT_WIFI == false - void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) - { - Serial.print("WiFi lost connection. Reason: "); - Serial.println(info.disconnected.reason); - Serial.println("Trying to Reconnect"); - WiFi.disconnect(); - WiFi.reconnect(); - } +void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) +{ + Serial.print("WiFi lost connection. Reason: "); + Serial.println(info.disconnected.reason); + Serial.println("Trying to Reconnect"); + WiFi.disconnect(); + WiFi.reconnect(); +} - void wifiSetup() +void wifiSetup() +{ + #if USE_ESPTOUCH_SMART_CONFIG == true + WiFi.reconnect(); //try to reconnect + Serial.println("Trying to reconnect to previous wifi network"); + #else + WiFi.begin(WIFI_SSID, WIFI_PW); + #endif + ShelfDisplays->setAllSegmentColors(WIFI_CONNECTING_COLOR); + ShelfDisplays->showLoadingAnimation(); + for (int i = 0; i < NUM_RETRIES; i++) { + Serial.print("."); #if USE_ESPTOUCH_SMART_CONFIG == true - WiFi.reconnect(); //try to reconnect - Serial.println("Trying to reconnect to previous wifi network"); + if(WiFi.begin() == WL_CONNECTED) #else - WiFi.begin(WIFI_SSID, WIFI_PW); + if(WiFi.status() == WL_CONNECTED) #endif - ShelfDisplays->setAllSegmentColors(WIFI_CONNECTING_COLOR); - ShelfDisplays->showLoadingAnimation(); - for (int i = 0; i < NUM_RETRIES; i++) { - Serial.print("."); - #if USE_ESPTOUCH_SMART_CONFIG == true - if(WiFi.begin() == WL_CONNECTED) - #else - if(WiFi.status() == WL_CONNECTED) - #endif + Serial.println("Reconnect successful"); + ShelfDisplays->setAllSegmentColors(WIFI_CONNECTION_SUCCESSFUL_COLOR); + break; + } + ShelfDisplays->delay(500); + } + + if(WiFi.status() != WL_CONNECTED) + { + #if USE_ESPTOUCH_SMART_CONFIG == true + Serial.println("Reconnect failed. starting smart config"); + WiFi.mode(WIFI_AP_STA); + // start SmartConfig + WiFi.beginSmartConfig(); + + // Wait for SmartConfig packet from mobile + Serial.println("Waiting for SmartConfig."); + ShelfDisplays->setAllSegmentColors(WIFI_SMART_CONFIG_COLOR); + while (!WiFi.smartConfigDone()) { - Serial.println("Reconnect successful"); - ShelfDisplays->setAllSegmentColors(WIFI_CONNECTION_SUCCESSFUL_COLOR); - break; + Serial.print("."); + ShelfDisplays->delay(500); } - ShelfDisplays->delay(500); - } + ShelfDisplays->setAllSegmentColors(WIFI_CONNECTING_COLOR); + Serial.println(""); + Serial.println("SmartConfig done."); - if(WiFi.status() != WL_CONNECTED) - { - #if USE_ESPTOUCH_SMART_CONFIG == true - Serial.println("Reconnect failed. starting smart config"); - WiFi.mode(WIFI_AP_STA); - // start SmartConfig - WiFi.beginSmartConfig(); - - // Wait for SmartConfig packet from mobile - Serial.println("Waiting for SmartConfig."); - ShelfDisplays->setAllSegmentColors(WIFI_SMART_CONFIG_COLOR); - while (!WiFi.smartConfigDone()) - { - Serial.print("."); - ShelfDisplays->delay(500); - } - ShelfDisplays->setAllSegmentColors(WIFI_CONNECTING_COLOR); - Serial.println(""); - Serial.println("SmartConfig done."); - - // Wait for WiFi to connect to AP - Serial.println("Waiting for WiFi"); - while (WiFi.status() != WL_CONNECTED) - { - Serial.print("."); - ShelfDisplays->setAllSegmentColors(WIFI_CONNECTION_SUCCESSFUL_COLOR); - ShelfDisplays->delay(500); - } - Serial.println("WiFi Connected."); - Serial.print("IP Address: "); - Serial.println(WiFi.localIP()); - #else - Serial.println("WIFI connection failed"); - ShelfDisplays->setAllSegmentColors(ERROR_COLOR); - #endif - if(WiFi.status() != WL_CONNECTED) + // Wait for WiFi to connect to AP + Serial.println("Waiting for WiFi"); + while (WiFi.status() != WL_CONNECTED) { - Serial.println("WIFI connection failed. Aborting execution."); - abort(); + Serial.print("."); + ShelfDisplays->setAllSegmentColors(WIFI_CONNECTION_SUCCESSFUL_COLOR); + ShelfDisplays->delay(500); } + Serial.println("WiFi Connected."); + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + #else + Serial.println("WIFI connection failed"); + ShelfDisplays->setAllSegmentColors(ERROR_COLOR); + #endif + if(WiFi.status() != WL_CONNECTED) + { + Serial.println("WIFI connection failed. Aborting execution."); + abort(); } - WiFi.onEvent(WiFiStationDisconnected, SYSTEM_EVENT_STA_DISCONNECTED); - ShelfDisplays->stopLoadingAnimation(); - Serial.println("Waiting for loading animation to finish..."); - ShelfDisplays->waitForLoadingAnimationFinish(); - ShelfDisplays->turnAllSegmentsOff(); } -#endif + WiFi.onEvent(WiFiStationDisconnected, SYSTEM_EVENT_STA_DISCONNECTED); + ShelfDisplays->stopLoadingAnimation(); + Serial.println("Waiting for loading animation to finish..."); + ShelfDisplays->waitForLoadingAnimationFinish(); + ShelfDisplays->turnAllSegmentsOff(); +} -#if ENABLE_OTA_UPLOAD == true - bool progressFirstStep = true; - void setupOTA() - { - // Port defaults to 3232 - // ArduinoOTA.setPort(3232); +void setupOTA() +{ + // Port defaults to 3232 + // ArduinoOTA.setPort(3232); - // Hostname defaults to esp3232-[MAC] - ArduinoOTA.setHostname(OTA_UPDATE_HOST_NAME); + // Hostname defaults to esp3232-[MAC] + ArduinoOTA.setHostname(OTA_UPDATE_HOST_NAME); - // No authentication by default - //ArduinoOTA.setPassword("admin"); + // No authentication by default + //ArduinoOTA.setPassword("admin"); - // Password can be set with it's md5 value as well - // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 - // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); + // Password can be set with it's md5 value as well + // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 + // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); - ArduinoOTA.onStart([]() { - String type; - if (ArduinoOTA.getCommand() == U_FLASH) - { - type = "sketch"; - } - else // U_SPIFFS - { - type = "filesystem"; - SPIFFS.end(); - } - // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() - Serial.println("Start updating " + type); - timeM->disableTimer(); - #if IS_BLYNK_ACTIVE == true - BlynkConfiguration->stop(); - #endif - ShelfDisplays->setAllSegmentColors(OTA_UPDATE_COLOR); - ShelfDisplays->turnAllLEDsOff(); //instead of the loading animation show a progress bar - ShelfDisplays->setGlobalBrightness(50); - }) - .onEnd([]() + ArduinoOTA.onStart([]() { + String type; + if (ArduinoOTA.getCommand() == U_FLASH) { - Serial.println("\nOTA End"); - }) - .onProgress([](unsigned int progress, unsigned int total) + type = "sketch"; + } + else // U_SPIFFS { - Serial.printf("Progress: %u%%\r", (progress / (total / 100))); - if(progressFirstStep == true) - { - ShelfDisplays->displayProgress(total); - progressFirstStep = false; - } - ShelfDisplays->updateProgress(progress); - }) - .onError([](ota_error_t error) + type = "filesystem"; + SPIFFS.end(); + } + // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() + Serial.println("Start updating " + type); + timeM->disableTimer(); + BlynkConfiguration->stop(); + ShelfDisplays->setAllSegmentColors(OTA_UPDATE_COLOR); + ShelfDisplays->turnAllLEDsOff(); //instead of the loading animation show a progress bar + ShelfDisplays->setGlobalBrightness(50); + }) + .onEnd([]() + { + Serial.println("\nOTA End"); + }) + .onProgress([](unsigned int progress, unsigned int total) + { + Serial.printf("Progress: %u%%\r", (progress / (total / 100))); + if(OTAprogressFirstStep == true) { - Serial.printf("Error[%u]: ", error); - if (error == OTA_AUTH_ERROR) - { - Serial.println("Auth Failed"); - } - else if (error == OTA_BEGIN_ERROR) - { - Serial.println("Begin Failed"); - } - else if (error == OTA_CONNECT_ERROR) - { - Serial.println("Connect Failed"); - } - else if (error == OTA_RECEIVE_ERROR) - { - Serial.println("Receive Failed"); - } - else if (error == OTA_END_ERROR) - { - Serial.println("End Failed"); - } - ShelfDisplays->setAllSegmentColors(ERROR_COLOR); - }); + ShelfDisplays->displayProgress(total); + OTAprogressFirstStep = false; + } + ShelfDisplays->updateProgress(progress); + }) + .onError([](ota_error_t error) + { + Serial.printf("Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) + { + Serial.println("Auth Failed"); + } + else if (error == OTA_BEGIN_ERROR) + { + Serial.println("Begin Failed"); + } + else if (error == OTA_CONNECT_ERROR) + { + Serial.println("Connect Failed"); + } + else if (error == OTA_RECEIVE_ERROR) + { + Serial.println("Receive Failed"); + } + else if (error == OTA_END_ERROR) + { + Serial.println("End Failed"); + } + ShelfDisplays->setAllSegmentColors(ERROR_COLOR); + }); - ArduinoOTA.begin(); + ArduinoOTA.begin(); - Serial.println("OTA update functionality is ready"); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - } -#endif + Serial.println("OTA update functionality is ready"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); +} From adc6b16e58af4ef1fffd924f556e323aa378f430 Mon Sep 17 00:00:00 2001 From: "flo.laschober@gmail.com" Date: Tue, 18 May 2021 21:40:12 +0200 Subject: [PATCH 08/13] Some work on #10 Improved wifi connection code and first experiment with captive portals (NOT WORKING) #6 Fixed a lot of JS errors. Web interface is now working correctly without issues. #7 Running the web app on the dev server now deos not require any code change anymore. --- ESP source.code-workspace | 3 +- WebConfigUI/src/App.jsx | 24 +-- WebConfigUI/src/components/SettingsForm.jsx | 133 ++++++++++---- WebConfigUI/src/css/tailwind.css | 16 ++ WebConfigUI/src/data/BaseConfig.json | 1 - WebConfigUI/src/data/UI_definitions.json | 27 ++- WebConfigUI/src/sites/Settings.jsx | 4 +- WebConfigUI/vite.config.js | 6 + data/UI_definitions.json | 22 +-- .../Modules/Animator/inc/AnimatableObject.h | 10 +- lib/LED_clock/Modules/Animator/inc/Animator.h | 1 + .../Modules/Animator/src/AnimatableObject.cpp | 4 +- .../Modules/Animator/src/Animator.cpp | 9 +- .../Modules/ConfigManager/inc/ConfigManager.h | 2 + .../Modules/SevenSegment/inc/SevenSegment.h | 1 + .../Modules/SevenSegment/src/SevenSegment.cpp | 5 + platformio.ini | 7 +- src/main.cpp | 162 +++++++++++------- 18 files changed, 276 insertions(+), 161 deletions(-) create mode 100644 WebConfigUI/vite.config.js diff --git a/ESP source.code-workspace b/ESP source.code-workspace index 2716877..7e6bb4e 100644 --- a/ESP source.code-workspace +++ b/ESP source.code-workspace @@ -25,7 +25,8 @@ "*.tcc": "cpp", "cmath": "cpp", "cstdio": "cpp", - "new": "cpp" + "new": "cpp", + "algorithm": "cpp" }, "editor.detectIndentation": false, "editor.insertSpaces": false diff --git a/WebConfigUI/src/App.jsx b/WebConfigUI/src/App.jsx index debd55a..dcbfcab 100644 --- a/WebConfigUI/src/App.jsx +++ b/WebConfigUI/src/App.jsx @@ -6,19 +6,7 @@ import WIFI_setup from "./sites/WIFI-setup"; import Settings from "./sites/Settings"; import Dialog from './components/Dialog'; -// Development function for testing locally -// const getConfigFile = async (fileUrl, hook) => { -// import(/* @vite-ignore */"./data" + fileUrl ).then(config => { -// hook(config); -// }) -// } - export default function App() { - const [UI_definitions, setUI_definitions] = useState({}); - const [BaseConfig, setBaseConfig] = useState({}); - const [ColorConfig, setColorConfig] = useState({}); - const [HWConfig, setHWConfig] = useState({}); - const [saveResponse, setSaveResponse] = useState("ERROR: Not saved"); const [dialogShown, setDialogShown] = useState(false); @@ -55,12 +43,6 @@ export default function App() { return true; } - useEffect(() => { - getConfigFile('UI_definitions.json', setUI_definitions); - getConfigFile('BaseConfig.json', setBaseConfig); - getConfigFile('ColorConfig.json', setColorConfig); - getConfigFile('HWConfig.json', setHWConfig); - }, []); const handleSubmit = (domain, values, setter) => { putConfigFile(domain, values); @@ -78,15 +60,15 @@ export default function App() { handleSubmit(d, v, null)}/>} /> - handleSubmit(d, v, setBaseConfig)} UIDefinition={UI_definitions.BaseSettings} InitialValues={BaseConfig}> + getConfigFile('UI_definitions.json', setter)} getValues={(setter)=>getConfigFile('BaseConfig.json', setter)}> General Settings } /> - handleSubmit(d, v, setColorConfig)} UIDefinition={UI_definitions.ColorSettings} InitialValues={ColorConfig}> + getConfigFile('UI_definitions.json', setter)} getValues={(setter)=>getConfigFile('ColorConfig.json', setter)}> Color Settings } /> - handleSubmit(d, v, setHWConfig)} UIDefinition={UI_definitions.HWSettings} InitialValues={HWConfig}> + getConfigFile('UI_definitions.json', setter)} getValues={(setter)=>getConfigFile('HWConfig.json', setter)}> Hardware Settings } /> diff --git a/WebConfigUI/src/components/SettingsForm.jsx b/WebConfigUI/src/components/SettingsForm.jsx index 98a5346..bce2101 100644 --- a/WebConfigUI/src/components/SettingsForm.jsx +++ b/WebConfigUI/src/components/SettingsForm.jsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import Dialog from '../components/Dialog' @@ -11,9 +11,17 @@ const loadConfig = (UIDefinition, InitialValues) => { } const SettingsForm = (props) => { - const [Settings, setSettings] = useState(loadConfig(props.UIDefinition, props.InitialValues)); + const [Settings, setSettings] = useState([]); + const [Values, setValues] = useState([]); const [dialogShown, setDialogShown] = useState(false); + useEffect(() => { + props.getUI((value)=>{ + setSettings(value[props.domain]); + }); + props.getValues(setValues); + }, []); + const handleSubmit = (event) => { event.preventDefault(); setDialogShown(true); @@ -24,39 +32,60 @@ const SettingsForm = (props) => { if(result == true) { let results = {}; - Settings.forEach(setting => { - results[setting.target] = setting.value + Settings.forEach((setting, i) => { + if(Settings[i].type == "color") + { + results[setting.target] = toColorValue(Values[setting.target]); + } + else + { + results[setting.target] = Values[setting.target]; + } }); props.onSubmit(props.domain, JSON.stringify(results)); } } const valueChangedHandler = (event, target) => { + const newSettingValue = {...Values}; const targetSettingIndex = Settings.findIndex(p => p.target === target); - - const setting = { - ...Settings[targetSettingIndex] - }; + const setting = Settings[targetSettingIndex]; const newValue = event.target.value; - switch(event.target.type) + switch(setting.type) { - case "checkbox": - setting.value = event.target.checked; + case "bool": + newSettingValue[target] = event.target.checked; break; - case "number": + case "float": + case "int": if(isNaN(newValue) || (newValue < setting.min || newValue > setting.max) && newValue != "" || newValue.includes("e") || (newValue.includes(".") && setting.type === "int")) { + console.log("Invalid value"); return; } + newSettingValue[target] = newValue; + case "color": + parseRGBColor(target); + switch (event.target.id) { + case "r": + newSettingValue[target].r = event.target.value; + break; + case "g": + newSettingValue[target].g = event.target.value; + break; + case "b": + newSettingValue[target].b = event.target.value; + break; + default: + break; + } + break; default: - setting.value = newValue; + newSettingValue[target] = newValue; break; } - - const settings = [...Settings]; - settings[targetSettingIndex] = setting; - setSettings(settings); + setValues(newSettingValue); } const isDisabled = (setting) => { @@ -70,27 +99,47 @@ const SettingsForm = (props) => { { if(invert) { - return Settings[targetSettingIndex].value; + return Values[onlyIfTarget]; } else { - return !Settings[targetSettingIndex].value; + return !Values[onlyIfTarget]; } } } return false; } + const parseRGBColor = (target) => { + if(typeof(Values[target]) !== 'object' && Values[target] !== null) + { + const newSettingValue = {...Values}; + newSettingValue[target] = toColor(Values[target]); + setValues(newSettingValue); + return newSettingValue[target]; + } + return Values[target]; + } + const toColor = (num) => { - num >>>= 0; - var b = num & 0xFF, - g = (num & 0xFF00) >>> 8, - r = (num & 0xFF0000) >>> 16; - return "rgba(" + [r, g, b, 1].join(",") + ")"; + const rgbColor = { + b: num & 0xFF, + g: (num & 0xFF00) >> 8, + r: (num & 0xFF0000) >> 16 + } + return rgbColor; + } + + const toColorStyle = (rgbColor) => { + return "rgba(" + [rgbColor.r, rgbColor.g, rgbColor.b, 1].join(",") + ")"; + } + + const toColorValue = (rgbColor) => { + return (rgbColor.r << 16) + (rgbColor.g << 8) + rgbColor.b; } const settingsForm = Settings.map((settingEntry, index) => { - let Disabled = isDisabled(settingEntry); + const Disabled = isDisabled(settingEntry); let stepSize = 1; switch(settingEntry.type) { @@ -99,14 +148,14 @@ const SettingsForm = (props) => {
- valueChangedHandler(e, settingEntry.target)} autoComplete="off" type="text"/> + valueChangedHandler(e, settingEntry.target)} autoComplete="off" type="text"/>
) case "bool": return (
- valueChangedHandler(e, settingEntry.target)}/> + valueChangedHandler(e, settingEntry.target)}/>
) @@ -117,23 +166,31 @@ const SettingsForm = (props) => {
- valueChangedHandler(e, settingEntry.target)} autoComplete="off" type="number"/> + valueChangedHandler(e, settingEntry.target)} autoComplete="off" type="number"/>
) case "color": - const test = { - backgroundColor: toColor(Settings[index].value) - } + let rgbColor = parseRGBColor(settingEntry.target); return (
- -
-
- - valueChangedHandler(e, settingEntry.target)} type="range"/> - valueChangedHandler(e, settingEntry.target)} type="range"/> - valueChangedHandler(e, settingEntry.target)} type="range"/> + +
+
+
+
+ + valueChangedHandler(e, settingEntry.target)}/> +
+
+ + valueChangedHandler(e, settingEntry.target)}/> +
+
+ + valueChangedHandler(e, settingEntry.target)}/> +
+
) diff --git a/WebConfigUI/src/css/tailwind.css b/WebConfigUI/src/css/tailwind.css index 3fa03a9..362e141 100644 --- a/WebConfigUI/src/css/tailwind.css +++ b/WebConfigUI/src/css/tailwind.css @@ -39,4 +39,20 @@ .btn-secondary { @apply text-indigo-900 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-400 } + + [type='range'] { + @apply appearance-none w-full h-3 rounded-md bg-gray-200 outline-none opacity-70 transition-opacity; + } + + [type='range']:hover { + @apply opacity-100; + } + + [type='range']::-webkit-slider-thumb { + @apply h-6 w-5 bg-indigo-600 appearance-none rounded-md cursor-pointer; + } + + [type='range']::-moz-range-thumb { + @apply h-6 w-5 bg-indigo-600 rounded-md cursor-pointer; + } } diff --git a/WebConfigUI/src/data/BaseConfig.json b/WebConfigUI/src/data/BaseConfig.json index 180d34a..65f5742 100644 --- a/WebConfigUI/src/data/BaseConfig.json +++ b/WebConfigUI/src/data/BaseConfig.json @@ -2,7 +2,6 @@ "ENABLE_BLYNK": true, "BLYNK_AUTH_TOKEN": "AUTH_TOKEN_GOES_HERE", "BLYNK_SERVER": "blynk-cloud.com", - "OTA_UPLOAD": true, "HOST_NAME": "LED-Shelf", "NUM_RETRIES": 50, "SMART_CONFIG": true, diff --git a/WebConfigUI/src/data/UI_definitions.json b/WebConfigUI/src/data/UI_definitions.json index f89985b..6d9eda3 100644 --- a/WebConfigUI/src/data/UI_definitions.json +++ b/WebConfigUI/src/data/UI_definitions.json @@ -1,5 +1,5 @@ { - "BaseSettings": [ + "BaseConfig": [ { "label": "Enable Blynk functionality", "type": "bool", @@ -17,11 +17,6 @@ "target": "BLYNK_SERVER", "onlyIf": "ENABLE_BLYNK" }, - { - "label": "Enable OTA Updates", - "type": "bool", - "target": "OTA_UPLOAD" - }, { "label": "Host name", "type": "string", @@ -161,7 +156,7 @@ "max": 65535 } ], - "ColorSettings": [ + "ColorConfig": [ { "label": "Color of the hour segments", "type": "color", @@ -171,62 +166,62 @@ }, { "label": "Color of the minute segments", - "type": "int", + "type": "color", "target": "MINUTE", "min": 0, "max": 16777215 }, { "label": "Color of the internal LEDs", - "type": "int", + "type": "color", "target": "INTERNAL", "min": 0, "max": 16777215 }, { "label": "Color of the separation dot LEDs", - "type": "int", + "type": "color", "target": "SEPARATION_DOT", "min": 0, "max": 16777215 }, { "label": "Color of the LEDs for the OTA update progress bar", - "type": "int", + "type": "color", "target": "OTA_UPDATE", "min": 0, "max": 16777215 }, { "label": "Color of the LEDs while searching for a WIFI network", - "type": "int", + "type": "color", "target": "WIFI_CONNECTING", "min": 0, "max": 16777215 }, { "label": "Color of the LEDs signaling a successful WIFI connection", - "type": "int", + "type": "color", "target": "WIFI_CONNECTION_SUCCESSFUL", "min": 0, "max": 16777215 }, { "label": "Color of the LEDs if system is waiting for WIFI smart config", - "type": "int", + "type": "color", "target": "WIFI_SMART_CONFIG", "min": 0, "max": 16777215 }, { "label": "Color of the LEDs signaling an error of some sort", - "type": "int", + "type": "color", "target": "ERROR", "min": 0, "max": 16777215 } ], - "HWSettings": [ + "HWConfig": [ { "label": "Pin to which the led strip data pin is connected to", "type": "int", diff --git a/WebConfigUI/src/sites/Settings.jsx b/WebConfigUI/src/sites/Settings.jsx index 27f2f61..f429b73 100644 --- a/WebConfigUI/src/sites/Settings.jsx +++ b/WebConfigUI/src/sites/Settings.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import SettingsForm from '../components/SettingsForm' const Settings = (props) => { @@ -8,7 +8,7 @@ const Settings = (props) => {

{props.children}

- +
); diff --git a/WebConfigUI/vite.config.js b/WebConfigUI/vite.config.js new file mode 100644 index 0000000..74284c7 --- /dev/null +++ b/WebConfigUI/vite.config.js @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + root: ".", + publicDir: "./src/data" +}) diff --git a/data/UI_definitions.json b/data/UI_definitions.json index 6610054..6d9eda3 100644 --- a/data/UI_definitions.json +++ b/data/UI_definitions.json @@ -1,5 +1,5 @@ { - "BaseSettings": [ + "BaseConfig": [ { "label": "Enable Blynk functionality", "type": "bool", @@ -156,7 +156,7 @@ "max": 65535 } ], - "ColorSettings": [ + "ColorConfig": [ { "label": "Color of the hour segments", "type": "color", @@ -166,62 +166,62 @@ }, { "label": "Color of the minute segments", - "type": "int", + "type": "color", "target": "MINUTE", "min": 0, "max": 16777215 }, { "label": "Color of the internal LEDs", - "type": "int", + "type": "color", "target": "INTERNAL", "min": 0, "max": 16777215 }, { "label": "Color of the separation dot LEDs", - "type": "int", + "type": "color", "target": "SEPARATION_DOT", "min": 0, "max": 16777215 }, { "label": "Color of the LEDs for the OTA update progress bar", - "type": "int", + "type": "color", "target": "OTA_UPDATE", "min": 0, "max": 16777215 }, { "label": "Color of the LEDs while searching for a WIFI network", - "type": "int", + "type": "color", "target": "WIFI_CONNECTING", "min": 0, "max": 16777215 }, { "label": "Color of the LEDs signaling a successful WIFI connection", - "type": "int", + "type": "color", "target": "WIFI_CONNECTION_SUCCESSFUL", "min": 0, "max": 16777215 }, { "label": "Color of the LEDs if system is waiting for WIFI smart config", - "type": "int", + "type": "color", "target": "WIFI_SMART_CONFIG", "min": 0, "max": 16777215 }, { "label": "Color of the LEDs signaling an error of some sort", - "type": "int", + "type": "color", "target": "ERROR", "min": 0, "max": 16777215 } ], - "HWSettings": [ + "HWConfig": [ { "label": "Pin to which the led strip data pin is connected to", "type": "int", diff --git a/lib/LED_clock/Modules/Animator/inc/AnimatableObject.h b/lib/LED_clock/Modules/Animator/inc/AnimatableObject.h index 475bf69..2a5a1ec 100644 --- a/lib/LED_clock/Modules/Animator/inc/AnimatableObject.h +++ b/lib/LED_clock/Modules/Animator/inc/AnimatableObject.h @@ -42,7 +42,7 @@ class AnimatableObject /** * \brief Callback typedef to use when an animation starts/finishes */ - typedef void AnimationCallBack(void); + typedef void (*AnimationCallBack)(void); /** * \brief Typedef for animation effect functions. These should be implemented explicitly for every object that inherits from #AnimatableObject @@ -66,8 +66,8 @@ class AnimatableObject bool animationStarted; void* complexAnimationInst; - AnimationCallBack* finishedCallback; - AnimationCallBack* startCallback; + AnimationCallBack finishedCallback; + AnimationCallBack startCallback; Animator* ComplexAnimationManager; ComplexAnimationCallBack ComplexAnimStartCallback; ComplexAnimationCallBack ComplexAnimDoneCallback; @@ -161,14 +161,14 @@ class AnimatableObject * * \param callback function to call */ - void setAnimationDoneCallback(AnimationCallBack* callback); + void setAnimationDoneCallback(AnimationCallBack callback); /** * \brief Set a callback to be executued once an animation is started * * \param callback function to call */ - void setAnimationStartCallback(AnimationCallBack* callback); + void setAnimationStartCallback(AnimationCallBack callback); }; diff --git a/lib/LED_clock/Modules/Animator/inc/Animator.h b/lib/LED_clock/Modules/Animator/inc/Animator.h index 8168e60..22f9058 100644 --- a/lib/LED_clock/Modules/Animator/inc/Animator.h +++ b/lib/LED_clock/Modules/Animator/inc/Animator.h @@ -60,6 +60,7 @@ class Animator uint16_t counter; AnimatableObject** objects; bool running; + AnimatableObject::AnimationCallBack complexAnimationFinishedCallBack; }; private: diff --git a/lib/LED_clock/Modules/Animator/src/AnimatableObject.cpp b/lib/LED_clock/Modules/Animator/src/AnimatableObject.cpp index 9d19e1f..f5bf360 100644 --- a/lib/LED_clock/Modules/Animator/src/AnimatableObject.cpp +++ b/lib/LED_clock/Modules/Animator/src/AnimatableObject.cpp @@ -139,12 +139,12 @@ int32_t AnimatableObject::getState() return map(currentAnimationTime, 0, AnimationDuration, 0, numStates); } -void AnimatableObject::setAnimationDoneCallback(AnimationCallBack* callback) +void AnimatableObject::setAnimationDoneCallback(AnimationCallBack callback) { finishedCallback = callback; } -void AnimatableObject::setAnimationStartCallback(AnimationCallBack* callback) +void AnimatableObject::setAnimationStartCallback(AnimationCallBack callback) { startCallback = callback; } diff --git a/lib/LED_clock/Modules/Animator/src/Animator.cpp b/lib/LED_clock/Modules/Animator/src/Animator.cpp index fcb06ad..9e2ea4c 100644 --- a/lib/LED_clock/Modules/Animator/src/Animator.cpp +++ b/lib/LED_clock/Modules/Animator/src/Animator.cpp @@ -154,6 +154,10 @@ void Animator::animationIterationDoneCallback(AnimatableObject* sourceObject) currentObject->complexAnimationInst = nullptr; } } + if(currentAnimation->complexAnimationFinishedCallBack != nullptr) + { + currentAnimation->complexAnimationFinishedCallBack(); + } delete currentAnimation; } } @@ -170,7 +174,7 @@ void Animator::startAnimationStep(uint16_t stepindex, ComplexAnimationInstance* animationStep* StepToStart = animationInst->animation->animations->get(stepindex); if(StepToStart == nullptr) { - Serial.printf("[E] Complex animation stepindex wa invalid. Animation step %d was not started\n\r", stepindex); + Serial.printf("[E] Complex animation stepindex was invalid. Animation step %d was not started\n\r", stepindex); return; } bool hasCallbacks = false; @@ -270,7 +274,8 @@ Animator::ComplexAnimationInstance* Animator::BuildComplexAnimation(ComplexAmina .loop = looping, .counter = 0, .objects = animationObjectsArray, - .running = false + .running = false, + .complexAnimationFinishedCallBack = nullptr }; if(ComplexAnimation == nullptr) { diff --git a/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h index a27dde5..8375ba5 100644 --- a/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h +++ b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h @@ -40,6 +40,8 @@ /** \} */ +// const char* Wifi_config_classes[] = {}; + /** * \brief The config manager is responsible to handle the json configuration files. * It stores all config values in variables to be able to use them in the code diff --git a/lib/LED_clock/Modules/SevenSegment/inc/SevenSegment.h b/lib/LED_clock/Modules/SevenSegment/inc/SevenSegment.h index 2c10ed2..63e8813 100644 --- a/lib/LED_clock/Modules/SevenSegment/inc/SevenSegment.h +++ b/lib/LED_clock/Modules/SevenSegment/inc/SevenSegment.h @@ -75,6 +75,7 @@ class SevenSegment bool isConfigComplete(); void DisplayNumberWithoutAnim(uint8_t value); Animator::ComplexAmination* getTransition(uint8_t from, uint8_t to); + void animationDone(); public: diff --git a/lib/LED_clock/Modules/SevenSegment/src/SevenSegment.cpp b/lib/LED_clock/Modules/SevenSegment/src/SevenSegment.cpp index af1e599..311692d 100644 --- a/lib/LED_clock/Modules/SevenSegment/src/SevenSegment.cpp +++ b/lib/LED_clock/Modules/SevenSegment/src/SevenSegment.cpp @@ -257,3 +257,8 @@ void SevenSegment::DisplayChar(char value) DisplayNumber(value - '0'); } } + +void SevenSegment::animationDone() +{ + DisplayNumberWithoutAnim(currentValue); +} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 5518fc7..77d5e04 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,6 +22,11 @@ lib_deps = me-no-dev/ESP Async WebServer@^1.2.3 ivanseidel/LinkedList@0.0.0-alpha+sha.dac3874d28 + +; Uncomment these lines to use the debugger. +; debug_tool = esp-prog +; upload_protocol = esp-prog +; debug_init_break = tbreak setup ; Uncomment these to use OTA as a flash method. This only works if the ESp already has code running on it where OTA update is enabled. ; upload_protocol = espota -; upload_port = 192.168.0.5 ;LED-Shelf.local \ No newline at end of file +; upload_port = 192.168.1.116 ;LED-Shelf.local \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 7d8795b..9e44009 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,9 +23,14 @@ ClockState* states; ConfigManager* config; AsyncWebServer server(80); bool OTAprogressFirstStep = true; +wifi_event_id_t reconnectEvent; +bool wifiReconnectFlag = false; +String WIFI_SSID; +String WIFI_PW; void setupOTA(); void wifiSetup(); +void connectToWIFI(const char* SSID = nullptr, const char* PW = nullptr); void TimerTick(); void TimerDone(); @@ -110,14 +115,9 @@ void startWebServer() { server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html"); - server.onNotFound([](AsyncWebServerRequest *request){ - if(request->url() == "/WIFI" || request->url() == "/BaseSettings" || request->url() == "/Colors" || request->url() == "/HWSetup") - { - request->redirect("/"); - } else - { - request->send(404, "text/plain", "Not found"); - } + server.onNotFound([](AsyncWebServerRequest *request) + { + request->send(SPIFFS, "/index.html", "text/html"); }); AsyncCallbackJsonWebHandler* baseConfigHandler = new AsyncCallbackJsonWebHandler("/BaseConfig", [](AsyncWebServerRequest *request, JsonVariant &json) @@ -141,6 +141,25 @@ void startWebServer() }); server.addHandler(hwConfigHandler); + AsyncCallbackJsonWebHandler* WIFIConfigHandler = new AsyncCallbackJsonWebHandler("/WIFISettings", [](AsyncWebServerRequest *request, JsonVariant &json) + { + JsonObject temp = json.as(); + if(temp.containsKey("SSID") && temp.containsKey("PW")) + { + const char* SSID = temp["SSID"]; + const char* PW = temp["PW"]; + WIFI_SSID = String(SSID); + WIFI_PW = String(PW); + wifiReconnectFlag = true; + request->send(200, "text/plain", "The device will now attempt to connect to \"" + WIFI_SSID + "\" Connection to the web interface will be lost."); + } + else + { + request->send(200, "text/plain", "ERROR: All two keys (SSID, PW are mandatory but at least one of them was missing."); + } + }); + server.addHandler(WIFIConfigHandler); + server.begin(); } @@ -182,10 +201,10 @@ void setup() timeM->setTimerDoneCallback(TimerDone); timeM->setAlarmCallback(AlarmTriggered); + Serial.printf("\tMax allocated heap: %d/%d\n\r\tMax allocatedPSram %d/%d\n\r", ESP.getMaxAllocHeap(), ESP.getHeapSize(), ESP.getMaxAllocPsram(), ESP.getPsramSize()); Serial.println("Displaying startup animation..."); startupAnimation(); Serial.println("Setup done. Main Loop starting..."); - Serial.printf("Free heap: %d\n\r", esp_get_free_heap_size()); } void loop() @@ -193,6 +212,24 @@ void loop() ArduinoOTA.handle(); states->handleStates(); //updates display states, switches between modes etc. ShelfDisplays->handle(); + if(wifiReconnectFlag == true) + { + Serial.printf("Trying to connect to %s with password %s\n\r", WIFI_SSID.c_str(), WIFI_PW.c_str()); + server.end(); + WiFi.removeEvent(reconnectEvent); + WiFi.disconnect(); + connectToWIFI(WIFI_SSID.c_str(), WIFI_PW.c_str()); + if(WiFi.status() == WL_CONNECTED) + { + Serial.println("Connect successful"); + Serial.println("Resetting contoller to reconnect to the right WIFI network"); + } + else + { + Serial.println("Reconnect failed, maximum number of retries exhausted"); + } + ESP.restart(); + } } void AlarmTriggered() @@ -218,77 +255,80 @@ void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) Serial.println(info.disconnected.reason); Serial.println("Trying to Reconnect"); WiFi.disconnect(); - WiFi.reconnect(); + connectToWIFI(); } -void wifiSetup() +void connectToWIFI(const char* SSID, const char* PW) { - #if USE_ESPTOUCH_SMART_CONFIG == true - WiFi.reconnect(); //try to reconnect - Serial.println("Trying to reconnect to previous wifi network"); - #else - WiFi.begin(WIFI_SSID, WIFI_PW); - #endif + Serial.println("Trying to connect to wifi network"); ShelfDisplays->setAllSegmentColors(WIFI_CONNECTING_COLOR); ShelfDisplays->showLoadingAnimation(); + wl_status_t connectionStatus; + if(SSID != nullptr && PW != nullptr) + { + connectionStatus = WiFi.begin(SSID, PW); + } + else + { + connectionStatus = WiFi.begin(); + } for (int i = 0; i < NUM_RETRIES; i++) { Serial.print("."); - #if USE_ESPTOUCH_SMART_CONFIG == true - if(WiFi.begin() == WL_CONNECTED) - #else - if(WiFi.status() == WL_CONNECTED) - #endif + if(SSID != nullptr && PW != nullptr) + { + connectionStatus = WiFi.begin(SSID, PW); + } + else + { + connectionStatus = WiFi.begin(); + } + if(connectionStatus == WL_CONNECTED) { Serial.println("Reconnect successful"); ShelfDisplays->setAllSegmentColors(WIFI_CONNECTION_SUCCESSFUL_COLOR); break; } + ShelfDisplays->delay(1000); + } +} + +#include + +void wifiAPMode() +{ + IPAddress apIP(8,8,4,4); // The default android DNS + DNSServer dnsServer; + const byte DNS_PORT = 53; + + WiFi.mode(WIFI_AP); + WiFi.softAP("LED-Pixel clock fallback AP"); + WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); + + dnsServer.start(DNS_PORT, "*", apIP); + + startWebServer(); + + server.onNotFound([](AsyncWebServerRequest *request) + { + request->redirect("/WIFI"); + }); + while(true) { ShelfDisplays->delay(500); } +} + +void wifiSetup() +{ + connectToWIFI(); if(WiFi.status() != WL_CONNECTED) { - #if USE_ESPTOUCH_SMART_CONFIG == true - Serial.println("Reconnect failed. starting smart config"); - WiFi.mode(WIFI_AP_STA); - // start SmartConfig - WiFi.beginSmartConfig(); - - // Wait for SmartConfig packet from mobile - Serial.println("Waiting for SmartConfig."); - ShelfDisplays->setAllSegmentColors(WIFI_SMART_CONFIG_COLOR); - while (!WiFi.smartConfigDone()) - { - Serial.print("."); - ShelfDisplays->delay(500); - } - ShelfDisplays->setAllSegmentColors(WIFI_CONNECTING_COLOR); - Serial.println(""); - Serial.println("SmartConfig done."); - - // Wait for WiFi to connect to AP - Serial.println("Waiting for WiFi"); - while (WiFi.status() != WL_CONNECTED) - { - Serial.print("."); - ShelfDisplays->setAllSegmentColors(WIFI_CONNECTION_SUCCESSFUL_COLOR); - ShelfDisplays->delay(500); - } - Serial.println("WiFi Connected."); - Serial.print("IP Address: "); - Serial.println(WiFi.localIP()); - #else - Serial.println("WIFI connection failed"); - ShelfDisplays->setAllSegmentColors(ERROR_COLOR); - #endif - if(WiFi.status() != WL_CONNECTED) - { - Serial.println("WIFI connection failed. Aborting execution."); - abort(); - } + Serial.println("Reconnect failed Starting fallback AP"); + ShelfDisplays->setAllSegmentColors(WIFI_SMART_CONFIG_COLOR); + wifiAPMode(); } - WiFi.onEvent(WiFiStationDisconnected, SYSTEM_EVENT_STA_DISCONNECTED); + reconnectEvent = WiFi.onEvent(WiFiStationDisconnected, SYSTEM_EVENT_STA_DISCONNECTED); ShelfDisplays->stopLoadingAnimation(); Serial.println("Waiting for loading animation to finish..."); ShelfDisplays->waitForLoadingAnimationFinish(); From 516446e1c6aea12c624517574ebf54e2bc434b78 Mon Sep 17 00:00:00 2001 From: "flo.laschober@gmail.com" Date: Sun, 23 May 2021 14:40:50 +0200 Subject: [PATCH 09/13] More work on #9 and #6 WIFI AP is now created more reliably when connection to previous wifi network failed Portal UI is now working correctly. Fixed color UI element for the web interface --- ESP source.code-workspace | 31 +- WebConfigUI/package.json | 1 + WebConfigUI/src/components/SettingsForm.jsx | 74 +++- WebConfigUI/src/css/tailwind.css | 2 +- WebConfigUI/src/data/UI_definitions.json | 22 +- .../Modules/WiFiManager/inc/WiFiManager.h | 57 +++ .../Modules/WiFiManager/src/WiFiManager.cpp | 298 +++++++++++++++ lib/LED_clock/library.json | 1 + lib/LED_clock/library_clock-only.json | 1 + lib/LED_clock/library_default.json | 1 + lib/LED_clock/library_diy-machines.json | 1 + src/main.cpp | 352 ++++++++---------- 12 files changed, 636 insertions(+), 205 deletions(-) create mode 100644 lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h create mode 100644 lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp diff --git a/ESP source.code-workspace b/ESP source.code-workspace index 7e6bb4e..1c9b3a9 100644 --- a/ESP source.code-workspace +++ b/ESP source.code-workspace @@ -26,7 +26,36 @@ "cmath": "cpp", "cstdio": "cpp", "new": "cpp", - "algorithm": "cpp" + "algorithm": "cpp", + "random": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "exception": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "fstream": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "memory": "cpp", + "ostream": "cpp", + "numeric": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp" }, "editor.detectIndentation": false, "editor.insertSpaces": false diff --git a/WebConfigUI/package.json b/WebConfigUI/package.json index 13e6797..e6bf72c 100644 --- a/WebConfigUI/package.json +++ b/WebConfigUI/package.json @@ -34,6 +34,7 @@ "webpack-dev-server": "^3.11.2" }, "dependencies": { + "@heroicons/react": "^1.0.1", "esbuild": "^0.11.13", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/WebConfigUI/src/components/SettingsForm.jsx b/WebConfigUI/src/components/SettingsForm.jsx index bce2101..708eb17 100644 --- a/WebConfigUI/src/components/SettingsForm.jsx +++ b/WebConfigUI/src/components/SettingsForm.jsx @@ -1,5 +1,7 @@ -import React, { useEffect, useState } from "react"; +import React, { Fragment, useEffect, useState } from "react"; import Dialog from '../components/Dialog' +import { Listbox, Transition } from '@headlessui/react' +import { CheckIcon, SelectorIcon } from '@heroicons/react/solid' const loadConfig = (UIDefinition, InitialValues) => { @@ -175,25 +177,77 @@ const SettingsForm = (props) => { return (
-
-
-
+
+
+
- - valueChangedHandler(e, settingEntry.target)}/> + + valueChangedHandler(e, settingEntry.target)}/>
- - valueChangedHandler(e, settingEntry.target)}/> + + valueChangedHandler(e, settingEntry.target)}/>
- - valueChangedHandler(e, settingEntry.target)}/> + + valueChangedHandler(e, settingEntry.target)}/>
) + case "dropdown": + var selectedItem = Values[settingEntry.target] || ''; + var listboxOptions; + if(Array.isArray(settingEntry.list)) + { + listboxOptions = settingEntry.list.map((entry) => ( + `${active ? 'text-amber-900 bg-amber-100' : 'text-gray-900'} cursor-default select-none relative py-2 pl-10 pr-4`} + value={entry.value} + > + {({ selected, active }) => ( + <> + + {entry.label} + + {selected ? ( + + + ) : null} + + )} + + )) + } + return ( +
+ +
+ valueChangedHandler(e, settingEntry.target)}> +
+ + {selectedItem} + + + + + {listboxOptions} + +
+
+
+
+ ); + default: + console.log(settingEntry.target + ": has invalid type: " + settingEntry.type); + return (
); } }) return ( diff --git a/WebConfigUI/src/css/tailwind.css b/WebConfigUI/src/css/tailwind.css index 362e141..f9ea511 100644 --- a/WebConfigUI/src/css/tailwind.css +++ b/WebConfigUI/src/css/tailwind.css @@ -41,7 +41,7 @@ } [type='range'] { - @apply appearance-none w-full h-3 rounded-md bg-gray-200 outline-none opacity-70 transition-opacity; + @apply appearance-none w-full h-6 rounded-md bg-gray-200 outline-none opacity-70 transition-opacity; } [type='range']:hover { diff --git a/WebConfigUI/src/data/UI_definitions.json b/WebConfigUI/src/data/UI_definitions.json index 6d9eda3..2f51c4b 100644 --- a/WebConfigUI/src/data/UI_definitions.json +++ b/WebConfigUI/src/data/UI_definitions.json @@ -279,15 +279,29 @@ }, { "label": "The display on which the separation dot should be shown", - "type": "enum selector", + "type": "dropdown", "target": "DISPLAY_FOR_SEPARATION_DOT", - "enum": "DisplayIDs", + "list": [ + {"value": 0, "label": "Higher digit hour display"}, + {"value": 1, "label": "First intermediate display"}, + {"value": 2, "label": "Lower digit hour display"}, + {"value": 3, "label": "Second intermediate display"}, + {"value": 4, "label": "Higher digit minute display"}, + {"value": 5, "label": "Third intermediate display"}, + {"value": 6, "label": "Lower digit minute display"} + ] + }, + { + "label": "The display on which the separation dot should be shown", + "type": "dropdown_invalid", + "target": "DISPLAY_FOR_SEPARATION_DOT1", + "list": "DisplayIDs", "min": 1, "max": 7 }, { "label": "The indices of the displays in the order they are configured in", - "type": "customizable enum", + "type": "ListSelection", "elements": [ {"target": "HIGHER_DIGIT_HOUR_DISPLAY", "label": "Higher digit hour display"}, {"target": "FIRST_INTERMEDIATE_DISPLAY", "label": "First intermediate display"}, @@ -297,7 +311,7 @@ {"target": "THIRD_INTERMEDIATE_DISPLAY", "label": "Third intermediate display"}, {"target": "LOWER_DIGIT_MINUTE_DISPLAY", "label": "Lower digit minute display"} ], - "target": "DisplayIDs", + "id": "DisplayIDs", "deactivated": -1, "min": 1, "max": 7 diff --git a/lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h b/lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h new file mode 100644 index 0000000..be85810 --- /dev/null +++ b/lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h @@ -0,0 +1,57 @@ +/** + * \file WiFiManager.h + * \author Florian Laschober + * \brief Adaption of the code from https://github.com/Hraph/ESP32WiFiManager + */ + +#ifndef __WIFI_MANAGER_H_ +#define __WIFI_MANAGER_H_ + +#include +#include +#include +#include +#include +#include +#include "ConfigManager.h" + +#define WEBSERVER_PORT 80 +#define DNS_PORT 53 +#define AP_SSID "LED-Pixel clock fallback AP" +#define AP_PW "0123456789" +#define CONNECTION_TIMEOUT 3000 //20000 + +class WiFiManager +{ +private: + AsyncWebServer* server; + DNSServer* dnsServer; + ConfigManager* config; + + bool connect; + String WIFI_SSID; + String WIFI_PW; + + static WiFiManager* currentInstance; + WiFiManager(); + + void handleRoot(AsyncWebServerRequest *request); + void handleNotFound(AsyncWebServerRequest *request); + String saveWiFiSettings(ConfigManager::ConfigType type, JsonObject* json); + void handleWifiSubmit(AsyncWebServerRequest *request, JsonVariant &json); + void handleSaveUnavailable(AsyncWebServerRequest *request, JsonVariant &json); + void startWebInterface(); +public: + static WiFiManager* getInstance(); + ~WiFiManager(); + void setupConfigPortal(); + bool captivePortal(AsyncWebServerRequest *request); + bool autoConnect(); + uint8_t waitForConnectResult(); + int connectWifi(String ssid, String pass); + bool startConfigPortal(); +}; + + + +#endif \ No newline at end of file diff --git a/lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp b/lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp new file mode 100644 index 0000000..9a18339 --- /dev/null +++ b/lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp @@ -0,0 +1,298 @@ +#include "WiFiManager.h" + +WiFiManager* WiFiManager::currentInstance = nullptr; + +WiFiManager* WiFiManager::getInstance() +{ + if(currentInstance == nullptr) + { + currentInstance = new WiFiManager(); + Serial.println("[D]: WiFiManager instance created"); + } + return currentInstance; +} + +WiFiManager::WiFiManager() +{ + server = new AsyncWebServer(WEBSERVER_PORT); + dnsServer = new DNSServer(); + config = ConfigManager::getInstance(); +} + +WiFiManager::~WiFiManager() +{ + delete server; + delete dnsServer; +} + +bool WiFiManager::autoConnect() +{ + // attempt to connect; should it fail, fall back to AP + WiFi.mode(WIFI_STA); + + if (connectWifi("", "") == WL_CONNECTED) + { + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + //connected + return true; + } + + return startConfigPortal(); +} + +int WiFiManager::connectWifi(String ssid, String pass) +{ + Serial.println("Connecting as wifi client..."); + + //fix for auto connect racing issue + if (WiFi.status() == WL_CONNECTED) + { + Serial.println("Already connected. Bailing out."); + return WL_CONNECTED; + } + //check if we have ssid and pass and force those, if not, try with last saved values + if (ssid != "") + { + WiFi.begin(ssid.c_str(), pass.c_str()); + } + else + { + if (WiFi.SSID()) + { + Serial.println("Using last saved values"); + //trying to fix connection in progress hanging + WiFi.disconnect(); + + WiFi.begin(); + } + else + { + Serial.println("No saved credentials"); + } + } + + int connRes = waitForConnectResult(); + Serial.print("Connection result: "); + Serial.println(connRes); + return connRes; +} + +uint8_t WiFiManager::waitForConnectResult() +{ + Serial.println("Waiting for connection result with time out"); + unsigned long start = millis(); + bool keepConnecting = true; + uint8_t status; + while (keepConnecting) + { + status = WiFi.status(); + if (millis() - start > CONNECTION_TIMEOUT) + { + keepConnecting = false; + Serial.println("Connection timed out"); + } + if (status == WL_CONNECTED || status == WL_CONNECT_FAILED) + { + keepConnecting = false; + } + delay(100); + } + return status; +} + +void WiFiManager::setupConfigPortal() +{ + server->reset(); + + + #ifdef AP_PW + WiFi.softAP(AP_SSID, AP_PW); + #else + WiFi.softAP(AP_SSID); + #endif + + delay(500); + + /* Setup the DNS server redirecting all the domains to the apIP */ + dnsServer->setErrorReplyCode(DNSReplyCode::NoError); + dnsServer->start(DNS_PORT, "*", WiFi.softAPIP()); + + server->serveStatic("/", SPIFFS, "/").setDefaultFile("index.html"); + // /* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */ + server->on("/", std::bind(&WiFiManager::handleRoot, this, std::placeholders::_1)); + server->onNotFound(std::bind(&WiFiManager::handleNotFound, this, std::placeholders::_1)); + AsyncCallbackJsonWebHandler* WIFIConfigHandler = new AsyncCallbackJsonWebHandler("/WIFISettings", std::bind(&WiFiManager::handleWifiSubmit, this, std::placeholders::_1, std::placeholders::_2)); + server->addHandler(WIFIConfigHandler); + AsyncCallbackJsonWebHandler* baseConfigHandler = new AsyncCallbackJsonWebHandler("/BaseConfig", std::bind(&WiFiManager::handleSaveUnavailable, this, std::placeholders::_1, std::placeholders::_2)); + server->addHandler(baseConfigHandler); + AsyncCallbackJsonWebHandler* colorConfigHandler = new AsyncCallbackJsonWebHandler("/ColorConfig", std::bind(&WiFiManager::handleSaveUnavailable, this, std::placeholders::_1, std::placeholders::_2)); + server->addHandler(colorConfigHandler); + AsyncCallbackJsonWebHandler* hwConfigHandler = new AsyncCallbackJsonWebHandler("/HWConfig", std::bind(&WiFiManager::handleSaveUnavailable, this, std::placeholders::_1, std::placeholders::_2)); + server->addHandler(hwConfigHandler); + server->begin(); // Web server start + Serial.println("HTTP server started"); +} + +void WiFiManager::handleSaveUnavailable(AsyncWebServerRequest *request, JsonVariant &json) +{ + request->send(200, "text/plain", "ERROR: Changes not saved. Please connect to a WIFI network first."); +} + +void WiFiManager::handleWifiSubmit(AsyncWebServerRequest *request, JsonVariant &json) +{ + JsonObject temp = json.as(); + if(temp.containsKey("SSID") && temp.containsKey("PW")) + { + const char* SSID = temp["SSID"]; + const char* PW = temp["PW"]; + WIFI_SSID = String(SSID); + WIFI_PW = String(PW); + connect = true; + request->send(200, "text/plain", "The device will now attempt to connect to \"" + WIFI_SSID + "\" Connection to the web interface will be lost."); + } + else + { + request->send(200, "text/plain", "ERROR: All two keys (SSID, PW are mandatory but at least one of them was missing."); + } +} + +void WiFiManager::handleRoot(AsyncWebServerRequest *request) +{ + if(captivePortal(request)) + { + return; + } + request->send(SPIFFS, "/index.html", "text/html"); +} + +void WiFiManager::handleNotFound(AsyncWebServerRequest *request) +{ + if(captivePortal(request)) + { + return; + } + request->send(SPIFFS, "/index.html", "text/html"); +} + +bool WiFiManager::startConfigPortal() +{ + //setup AP + WiFi.mode(WIFI_AP_STA); + Serial.println("SET AP STA"); + + connect = false; + setupConfigPortal(); + + while (1) + { + //DNS + dnsServer->processNextRequest(); + + if (connect) + { + connect = false; + delay(2000); + Serial.println("Connecting to new AP"); + + if (connectWifi(AP_SSID, AP_PW) != WL_CONNECTED) + { + Serial.println("Failed to connect."); + } + else + { + //connected + WiFi.mode(WIFI_STA); + break; + } + } + yield(); + } + + server->reset(); + dnsServer->stop(); + + return WiFi.status() == WL_CONNECTED; +} + +/** Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */ +bool WiFiManager::captivePortal(AsyncWebServerRequest *request) +{ + if(request->host() != WiFi.softAPIP().toString()) //TODO: check if requested url is not IP + { + Serial.printf("Redirecting %s to %s\n\r", request->host().c_str(), WiFi.softAPIP().toString().c_str()); + request->redirect(String("http://") + WiFi.softAPIP().toString() + String("/WIFI")); + return true; + } + return false; +} + +String WiFiManager::saveWiFiSettings(ConfigManager::ConfigType type, JsonObject* json) +{ + String FailedKeys = ""; + bool anyErrors = false; + for (JsonPair kv : *json) + { + if(!config->setProperty(kv, type)) + { + if(anyErrors != false) + { + FailedKeys += ", "; + } + FailedKeys += kv.key().c_str(); + anyErrors = true; + } + } + config->applyChanges(); + config->saveConfigPersistent(type); + if(anyErrors == true) + { + String ErrorMessage = "ERROR: Following base config properties were not set: " + FailedKeys; + Serial.print("[W]: "); + Serial.println(ErrorMessage); + return ErrorMessage; + } + else + { + return "Config Saved without errors"; + } +} + + +// void WiFiManager::startWebInterface() +// { +// server->reset(); +// server->serveStatic("/", SPIFFS, "/").setDefaultFile("index.html"); + +// server->onNotFound([](AsyncWebServerRequest *request) +// { +// request->send(SPIFFS, "/index.html", "text/html"); +// }); + +// AsyncCallbackJsonWebHandler* baseConfigHandler = new AsyncCallbackJsonWebHandler("/BaseConfig", [](AsyncWebServerRequest *request, JsonVariant &json) +// { +// JsonObject temp = json.as(); +// request->send(200, "text/plain", saveSettings(ConfigManager::BASE_CONFIG, &temp)); +// }); +// server->addHandler(baseConfigHandler); + +// AsyncCallbackJsonWebHandler* colorConfigHandler = new AsyncCallbackJsonWebHandler("/ColorConfig", [](AsyncWebServerRequest *request, JsonVariant &json) +// { +// JsonObject temp = json.as(); +// request->send(200, "text/plain", saveSettings(ConfigManager::COLOR_CONFIG, &temp)); +// }); +// server->addHandler(colorConfigHandler); + +// AsyncCallbackJsonWebHandler* hwConfigHandler = new AsyncCallbackJsonWebHandler("/HWConfig", [](AsyncWebServerRequest *request, JsonVariant &json) +// { +// JsonObject temp = json.as(); +// request->send(200, "text/plain", saveSettings(ConfigManager::HW_CONFIG, &temp)); +// }); +// server->addHandler(hwConfigHandler); + +// AsyncCallbackJsonWebHandler* WIFIConfigHandler = new AsyncCallbackJsonWebHandler("/WIFISettings", std::bind(&WiFiManager::handleWifiSubmit, this, std::placeholders::_1, std::placeholders::_2)); +// server->addHandler(WIFIConfigHandler); + +// server->begin(); +// Serial.println("Web interface should be up and running"); +// } \ No newline at end of file diff --git a/lib/LED_clock/library.json b/lib/LED_clock/library.json index 29bdc2b..1675797 100644 --- a/lib/LED_clock/library.json +++ b/lib/LED_clock/library.json @@ -24,6 +24,7 @@ "-I Modules/SevenSegment/inc", "-I Modules/TimeManager/inc", "-I Modules/ConfigManager/inc", + "-I Modules/WiFiManager/inc", "-I Config/Setup/clock-only", "-I Config/Blynk/default", "-I Config/Animations/clock-only", diff --git a/lib/LED_clock/library_clock-only.json b/lib/LED_clock/library_clock-only.json index 87b5471..75db5b7 100644 --- a/lib/LED_clock/library_clock-only.json +++ b/lib/LED_clock/library_clock-only.json @@ -22,6 +22,7 @@ "-I Modules/DisplayManager/inc", "-I Modules/SevenSegment/inc", "-I Modules/TimeManager/inc", + "-I Modules/WiFiManager/inc", "-I Config/Setup/clock-only", "-I Config/Blynk/default", "-I Config/Animations/clock-only", diff --git a/lib/LED_clock/library_default.json b/lib/LED_clock/library_default.json index 866baf5..b060f18 100644 --- a/lib/LED_clock/library_default.json +++ b/lib/LED_clock/library_default.json @@ -22,6 +22,7 @@ "-I Modules/DisplayManager/inc", "-I Modules/SevenSegment/inc", "-I Modules/TimeManager/inc", + "-I Modules/WiFiManager/inc", "-I Config/Setup/default", "-I Config/Blynk/default", "-I Config/Animations/default", diff --git a/lib/LED_clock/library_diy-machines.json b/lib/LED_clock/library_diy-machines.json index 4327dda..c63660b 100644 --- a/lib/LED_clock/library_diy-machines.json +++ b/lib/LED_clock/library_diy-machines.json @@ -22,6 +22,7 @@ "-I Modules/DisplayManager/inc", "-I Modules/SevenSegment/inc", "-I Modules/TimeManager/inc", + "-I Modules/WiFiManager/inc", "-I Config/Setup/diy-machines", "-I Config/Blynk/default", "-I Config/Animations/diy-machines", diff --git a/src/main.cpp b/src/main.cpp index 9e44009..4464aae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,18 +9,23 @@ #include "DisplayManager.h" #include "ClockState.h" #include "ConfigManager.h" +#include "WiFiManager.h" +#include +#include "BlynkConfig.h" + #include "ESPAsyncWebServer.h" #include "AsyncJson.h" #include #include "WiFi.h" -#include -#include "BlynkConfig.h" +#include DisplayManager* ShelfDisplays; BlynkConfig* BlynkConfiguration; TimeManager* timeM; ClockState* states; ConfigManager* config; +WiFiManager* WIFIManager; + AsyncWebServer server(80); bool OTAprogressFirstStep = true; wifi_event_id_t reconnectEvent; @@ -80,88 +85,57 @@ void startupAnimation() } } -String saveSettings(ConfigManager::ConfigType type, JsonObject* json) -{ - String FailedKeys = ""; - bool anyErrors = false; - for (JsonPair kv : *json) - { - if(!config->setProperty(kv, type)) - { - if(anyErrors != false) - { - FailedKeys += ", "; - } - FailedKeys += kv.key().c_str(); - anyErrors = true; - } - } - config->applyChanges(); - config->saveConfigPersistent(type); - if(anyErrors == true) - { - String ErrorMessage = "ERROR: Following base config properties were not set: " + FailedKeys; - Serial.print("[W]: "); - Serial.println(ErrorMessage); - return ErrorMessage; - } - else - { - return "Config Saved without errors"; - } -} - -void startWebServer() -{ - server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html"); - - server.onNotFound([](AsyncWebServerRequest *request) - { - request->send(SPIFFS, "/index.html", "text/html"); - }); - - AsyncCallbackJsonWebHandler* baseConfigHandler = new AsyncCallbackJsonWebHandler("/BaseConfig", [](AsyncWebServerRequest *request, JsonVariant &json) - { - JsonObject temp = json.as(); - request->send(200, "text/plain", saveSettings(ConfigManager::BASE_CONFIG, &temp)); - }); - server.addHandler(baseConfigHandler); - - AsyncCallbackJsonWebHandler* colorConfigHandler = new AsyncCallbackJsonWebHandler("/ColorConfig", [](AsyncWebServerRequest *request, JsonVariant &json) - { - JsonObject temp = json.as(); - request->send(200, "text/plain", saveSettings(ConfigManager::COLOR_CONFIG, &temp)); - }); - server.addHandler(colorConfigHandler); - - AsyncCallbackJsonWebHandler* hwConfigHandler = new AsyncCallbackJsonWebHandler("/HWConfig", [](AsyncWebServerRequest *request, JsonVariant &json) - { - JsonObject temp = json.as(); - request->send(200, "text/plain", saveSettings(ConfigManager::HW_CONFIG, &temp)); - }); - server.addHandler(hwConfigHandler); - - AsyncCallbackJsonWebHandler* WIFIConfigHandler = new AsyncCallbackJsonWebHandler("/WIFISettings", [](AsyncWebServerRequest *request, JsonVariant &json) - { - JsonObject temp = json.as(); - if(temp.containsKey("SSID") && temp.containsKey("PW")) - { - const char* SSID = temp["SSID"]; - const char* PW = temp["PW"]; - WIFI_SSID = String(SSID); - WIFI_PW = String(PW); - wifiReconnectFlag = true; - request->send(200, "text/plain", "The device will now attempt to connect to \"" + WIFI_SSID + "\" Connection to the web interface will be lost."); - } - else - { - request->send(200, "text/plain", "ERROR: All two keys (SSID, PW are mandatory but at least one of them was missing."); - } - }); - server.addHandler(WIFIConfigHandler); - - server.begin(); -} +// void startWebServer() +// { +// server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html"); + +// server.onNotFound([](AsyncWebServerRequest *request) +// { +// request->send(SPIFFS, "/index.html", "text/html"); +// }); + +// AsyncCallbackJsonWebHandler* baseConfigHandler = new AsyncCallbackJsonWebHandler("/BaseConfig", [](AsyncWebServerRequest *request, JsonVariant &json) +// { +// JsonObject temp = json.as(); +// request->send(200, "text/plain", saveSettings(ConfigManager::BASE_CONFIG, &temp)); +// }); +// server.addHandler(baseConfigHandler); + +// AsyncCallbackJsonWebHandler* colorConfigHandler = new AsyncCallbackJsonWebHandler("/ColorConfig", [](AsyncWebServerRequest *request, JsonVariant &json) +// { +// JsonObject temp = json.as(); +// request->send(200, "text/plain", saveSettings(ConfigManager::COLOR_CONFIG, &temp)); +// }); +// server.addHandler(colorConfigHandler); + +// AsyncCallbackJsonWebHandler* hwConfigHandler = new AsyncCallbackJsonWebHandler("/HWConfig", [](AsyncWebServerRequest *request, JsonVariant &json) +// { +// JsonObject temp = json.as(); +// request->send(200, "text/plain", saveSettings(ConfigManager::HW_CONFIG, &temp)); +// }); +// server.addHandler(hwConfigHandler); + +// AsyncCallbackJsonWebHandler* WIFIConfigHandler = new AsyncCallbackJsonWebHandler("/WIFISettings", [](AsyncWebServerRequest *request, JsonVariant &json) +// { +// JsonObject temp = json.as(); +// if(temp.containsKey("SSID") && temp.containsKey("PW")) +// { +// const char* SSID = temp["SSID"]; +// const char* PW = temp["PW"]; +// WIFI_SSID = String(SSID); +// WIFI_PW = String(PW); +// wifiReconnectFlag = true; +// request->send(200, "text/plain", "The device will now attempt to connect to \"" + WIFI_SSID + "\" Connection to the web interface will be lost."); +// } +// else +// { +// request->send(200, "text/plain", "ERROR: All two keys (SSID, PW are mandatory but at least one of them was missing."); +// } +// }); +// server.addHandler(WIFIConfigHandler); + +// server.begin(); +// } void setup() { @@ -174,6 +148,7 @@ void setup() BlynkConfiguration = BlynkConfig::getInstance(); timeM = TimeManager::getInstance(); states = ClockState::getInstance(); + WIFIManager = WiFiManager::getInstance(); Serial.println("Initializing LEDs..."); ShelfDisplays->InitSegments(0, NUM_LEDS_PER_SEGMENT, WIFI_CONNECTING_COLOR, 50); @@ -184,13 +159,14 @@ void setup() ShelfDisplays->setDotLEDColor(SEPERATION_DOT_COLOR); Serial.println("Running wifi setup..."); - wifiSetup(); + // wifiSetup(); + WIFIManager->autoConnect(); setupOTA(); ArduinoOTA.handle(); //give ota the opportunity to update before the main loop starts in case we have a crash in there BlynkConfiguration->setup(); - startWebServer(); + // startWebServer(); Serial.println("Fetching time from NTP server..."); if(timeM->init() == false) @@ -212,24 +188,24 @@ void loop() ArduinoOTA.handle(); states->handleStates(); //updates display states, switches between modes etc. ShelfDisplays->handle(); - if(wifiReconnectFlag == true) - { - Serial.printf("Trying to connect to %s with password %s\n\r", WIFI_SSID.c_str(), WIFI_PW.c_str()); - server.end(); - WiFi.removeEvent(reconnectEvent); - WiFi.disconnect(); - connectToWIFI(WIFI_SSID.c_str(), WIFI_PW.c_str()); - if(WiFi.status() == WL_CONNECTED) - { - Serial.println("Connect successful"); - Serial.println("Resetting contoller to reconnect to the right WIFI network"); - } - else - { - Serial.println("Reconnect failed, maximum number of retries exhausted"); - } - ESP.restart(); - } + // if(wifiReconnectFlag == true) + // { + // Serial.printf("Trying to connect to %s with password %s\n\r", WIFI_SSID.c_str(), WIFI_PW.c_str()); + // server.end(); + // WiFi.removeEvent(reconnectEvent); + // WiFi.disconnect(); + // connectToWIFI(WIFI_SSID.c_str(), WIFI_PW.c_str()); + // if(WiFi.status() == WL_CONNECTED) + // { + // Serial.println("Connect successful"); + // Serial.println("Resetting contoller to reconnect to the right WIFI network"); + // } + // else + // { + // Serial.println("Reconnect failed, maximum number of retries exhausted"); + // } + // ESP.restart(); + // } } void AlarmTriggered() @@ -249,91 +225,89 @@ void TimerDone() BlynkConfiguration->updateUI(); } -void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) -{ - Serial.print("WiFi lost connection. Reason: "); - Serial.println(info.disconnected.reason); - Serial.println("Trying to Reconnect"); - WiFi.disconnect(); - connectToWIFI(); -} - -void connectToWIFI(const char* SSID, const char* PW) -{ - Serial.println("Trying to connect to wifi network"); - ShelfDisplays->setAllSegmentColors(WIFI_CONNECTING_COLOR); - ShelfDisplays->showLoadingAnimation(); - wl_status_t connectionStatus; - if(SSID != nullptr && PW != nullptr) - { - connectionStatus = WiFi.begin(SSID, PW); - } - else - { - connectionStatus = WiFi.begin(); - } - for (int i = 0; i < NUM_RETRIES; i++) - { - Serial.print("."); - if(SSID != nullptr && PW != nullptr) - { - connectionStatus = WiFi.begin(SSID, PW); - } - else - { - connectionStatus = WiFi.begin(); - } - if(connectionStatus == WL_CONNECTED) - { - Serial.println("Reconnect successful"); - ShelfDisplays->setAllSegmentColors(WIFI_CONNECTION_SUCCESSFUL_COLOR); - break; - } - ShelfDisplays->delay(1000); - } -} - -#include - -void wifiAPMode() -{ - IPAddress apIP(8,8,4,4); // The default android DNS - DNSServer dnsServer; - const byte DNS_PORT = 53; - - WiFi.mode(WIFI_AP); - WiFi.softAP("LED-Pixel clock fallback AP"); - WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); - - dnsServer.start(DNS_PORT, "*", apIP); - - startWebServer(); - - server.onNotFound([](AsyncWebServerRequest *request) - { - request->redirect("/WIFI"); - }); - while(true) { - ShelfDisplays->delay(500); - } -} - -void wifiSetup() -{ - connectToWIFI(); - - if(WiFi.status() != WL_CONNECTED) - { - Serial.println("Reconnect failed Starting fallback AP"); - ShelfDisplays->setAllSegmentColors(WIFI_SMART_CONFIG_COLOR); - wifiAPMode(); - } - reconnectEvent = WiFi.onEvent(WiFiStationDisconnected, SYSTEM_EVENT_STA_DISCONNECTED); - ShelfDisplays->stopLoadingAnimation(); - Serial.println("Waiting for loading animation to finish..."); - ShelfDisplays->waitForLoadingAnimationFinish(); - ShelfDisplays->turnAllSegmentsOff(); -} +// void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) +// { +// Serial.print("WiFi lost connection. Reason: "); +// Serial.println(info.disconnected.reason); +// Serial.println("Trying to Reconnect"); +// WiFi.disconnect(); +// connectToWIFI(); +// } + +// void connectToWIFI(const char* SSID, const char* PW) +// { +// Serial.println("Trying to connect to wifi network"); +// ShelfDisplays->setAllSegmentColors(WIFI_CONNECTING_COLOR); +// ShelfDisplays->showLoadingAnimation(); +// wl_status_t connectionStatus; +// if(SSID != nullptr && PW != nullptr) +// { +// connectionStatus = WiFi.begin(SSID, PW); +// } +// else +// { +// connectionStatus = WiFi.begin(); +// } +// for (int i = 0; i < NUM_RETRIES; i++) +// { +// Serial.print("."); +// if(SSID != nullptr && PW != nullptr) +// { +// connectionStatus = WiFi.begin(SSID, PW); +// } +// else +// { +// connectionStatus = WiFi.begin(); +// } +// if(connectionStatus == WL_CONNECTED) +// { +// Serial.println("Reconnect successful"); +// ShelfDisplays->setAllSegmentColors(WIFI_CONNECTION_SUCCESSFUL_COLOR); +// break; +// } +// ShelfDisplays->delay(1000); +// } +// } + +// void wifiAPMode() +// { +// IPAddress apIP(8,8,4,4); // The default android DNS +// DNSServer dnsServer; +// const byte DNS_PORT = 53; + +// WiFi.mode(WIFI_AP); +// WiFi.softAP("LED-Pixel clock fallback AP"); +// WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); + +// dnsServer.start(DNS_PORT, "*", apIP); + +// startWebServer(); + +// server.onNotFound([](AsyncWebServerRequest *request) +// { +// request->redirect("/WIFI"); +// }); +// while(true) { +// ShelfDisplays->delay(500); +// } +// } + +// void wifiSetup() +// { +// connectToWIFI(); + +// if(WiFi.status() != WL_CONNECTED) +// { +// Serial.println("Reconnect failed Starting fallback AP"); +// ShelfDisplays->setAllSegmentColors(WIFI_SMART_CONFIG_COLOR); +// wifiAPMode(); +// } +// reconnectEvent = WiFi.onEvent(WiFiStationDisconnected, SYSTEM_EVENT_STA_DISCONNECTED); +// ShelfDisplays->stopLoadingAnimation(); +// Serial.println("Waiting for loading animation to finish..."); +// ShelfDisplays->waitForLoadingAnimationFinish(); +// ShelfDisplays->turnAllSegmentsOff(); +// } void setupOTA() { From 6e5c297d3bf2e023c17d3c0f2a536690175e0428 Mon Sep 17 00:00:00 2001 From: "flo.laschober@gmail.com" Date: Thu, 27 May 2021 22:57:10 +0200 Subject: [PATCH 10/13] Added animations while connection back in Hopefully also fixed Wifi not connecting from portal submit bug --- .../Modules/ConfigManager/inc/ConfigManager.h | 5 +++ .../Modules/WiFiManager/inc/WiFiManager.h | 7 +++- .../Modules/WiFiManager/src/WiFiManager.cpp | 42 ++++++++++++++++++- src/main.cpp | 3 +- 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h index 8375ba5..b23587b 100644 --- a/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h +++ b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h @@ -36,6 +36,11 @@ #define BLYNK_TOKEN_KEY "BLYNK_AUTH_TOKEN" #define BLYNK_SERVER_KEY "BLYNK_SERVER" +#define WIFI_CONNECTING_COLOR_KEY "WIFI_CONNECTING" +#define WIFI_CONNECTED_COLOR_KEY "WIFI_CONNECTION_SUCCESSFUL" +#define WIFI_CONFIG_COLOR_KEY "WIFI_SMART_CONFIG" +#define ERROR_COLOR_KEY "ERROR" + #define DEMO_MODE_KEY "TIME_MANAGER_DEMO_MODE" /** \} */ diff --git a/lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h b/lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h index be85810..9f70046 100644 --- a/lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h +++ b/lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h @@ -14,12 +14,13 @@ #include #include #include "ConfigManager.h" +#include "DisplayManager.h" #define WEBSERVER_PORT 80 #define DNS_PORT 53 #define AP_SSID "LED-Pixel clock fallback AP" #define AP_PW "0123456789" -#define CONNECTION_TIMEOUT 3000 //20000 +#define CONNECTION_TIMEOUT 30000 class WiFiManager { @@ -27,6 +28,7 @@ class WiFiManager AsyncWebServer* server; DNSServer* dnsServer; ConfigManager* config; + DisplayManager* ShelfDisplays; bool connect; String WIFI_SSID; @@ -41,12 +43,15 @@ class WiFiManager void handleWifiSubmit(AsyncWebServerRequest *request, JsonVariant &json); void handleSaveUnavailable(AsyncWebServerRequest *request, JsonVariant &json); void startWebInterface(); + void stopAnimation(); public: static WiFiManager* getInstance(); ~WiFiManager(); void setupConfigPortal(); bool captivePortal(AsyncWebServerRequest *request); bool autoConnect(); + bool autoConnect(DisplayManager* display); + void setDisplay(DisplayManager* display); uint8_t waitForConnectResult(); int connectWifi(String ssid, String pass); bool startConfigPortal(); diff --git a/lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp b/lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp index 9a18339..8d4da25 100644 --- a/lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp +++ b/lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp @@ -17,6 +17,7 @@ WiFiManager::WiFiManager() server = new AsyncWebServer(WEBSERVER_PORT); dnsServer = new DNSServer(); config = ConfigManager::getInstance(); + ShelfDisplays = nullptr; } WiFiManager::~WiFiManager() @@ -25,8 +26,35 @@ WiFiManager::~WiFiManager() delete dnsServer; } +void WiFiManager::setDisplay(DisplayManager* display) +{ + ShelfDisplays = display; +} + +bool WiFiManager::autoConnect(DisplayManager* display) +{ + setDisplay(display); + return autoConnect(); +} + +void WiFiManager::stopAnimation() +{ + if(ShelfDisplays != nullptr) + { + ShelfDisplays->stopLoadingAnimation(); + Serial.println("Waiting for loading animation to finish..."); + ShelfDisplays->waitForLoadingAnimationFinish(); + ShelfDisplays->turnAllSegmentsOff(); + } +} + bool WiFiManager::autoConnect() { + if(ShelfDisplays != nullptr) + { + ShelfDisplays->setAllSegmentColors(config->getProperty(ConfigManager::COLOR_CONFIG, WIFI_CONNECTING_COLOR_KEY)); + ShelfDisplays->showLoadingAnimation(); + } // attempt to connect; should it fail, fall back to AP WiFi.mode(WIFI_STA); @@ -35,6 +63,7 @@ bool WiFiManager::autoConnect() Serial.print("IP Address: "); Serial.println(WiFi.localIP()); //connected + stopAnimation(); return true; } @@ -43,6 +72,10 @@ bool WiFiManager::autoConnect() int WiFiManager::connectWifi(String ssid, String pass) { + if(ShelfDisplays != nullptr) + { + ShelfDisplays->setAllSegmentColors(config->getProperty(ConfigManager::COLOR_CONFIG, WIFI_CONNECTING_COLOR_KEY)); + } Serial.println("Connecting as wifi client..."); //fix for auto connect racing issue @@ -177,6 +210,10 @@ void WiFiManager::handleNotFound(AsyncWebServerRequest *request) bool WiFiManager::startConfigPortal() { + if(ShelfDisplays != nullptr) + { + ShelfDisplays->setAllSegmentColors(config->getProperty(ConfigManager::COLOR_CONFIG, WIFI_CONFIG_COLOR_KEY)); + } //setup AP WiFi.mode(WIFI_AP_STA); Serial.println("SET AP STA"); @@ -195,7 +232,7 @@ bool WiFiManager::startConfigPortal() delay(2000); Serial.println("Connecting to new AP"); - if (connectWifi(AP_SSID, AP_PW) != WL_CONNECTED) + if (connectWifi(WIFI_SSID, WIFI_PW) != WL_CONNECTED) { Serial.println("Failed to connect."); } @@ -203,6 +240,7 @@ bool WiFiManager::startConfigPortal() { //connected WiFi.mode(WIFI_STA); + stopAnimation(); break; } } @@ -218,7 +256,7 @@ bool WiFiManager::startConfigPortal() /** Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */ bool WiFiManager::captivePortal(AsyncWebServerRequest *request) { - if(request->host() != WiFi.softAPIP().toString()) //TODO: check if requested url is not IP + if(request->host() != WiFi.softAPIP().toString()) { Serial.printf("Redirecting %s to %s\n\r", request->host().c_str(), WiFi.softAPIP().toString().c_str()); request->redirect(String("http://") + WiFi.softAPIP().toString() + String("/WIFI")); diff --git a/src/main.cpp b/src/main.cpp index 4464aae..4dc98f0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -159,8 +159,7 @@ void setup() ShelfDisplays->setDotLEDColor(SEPERATION_DOT_COLOR); Serial.println("Running wifi setup..."); - // wifiSetup(); - WIFIManager->autoConnect(); + WIFIManager->autoConnect(ShelfDisplays); setupOTA(); ArduinoOTA.handle(); //give ota the opportunity to update before the main loop starts in case we have a crash in there From ed3f9085f24a46fe7a9b6001b158743190fc680d Mon Sep 17 00:00:00 2001 From: "flo.laschober@gmail.com" Date: Sat, 29 May 2021 21:38:26 +0200 Subject: [PATCH 11/13] Web UI is now working again with the new portal feature --- .../Config/Blynk/default/BlynkConfig.cpp | 1 + .../Modules/WiFiManager/inc/WiFiManager.h | 14 +- .../Modules/WiFiManager/src/WiFiManager.cpp | 105 +++++++----- src/main.cpp | 155 +----------------- 4 files changed, 78 insertions(+), 197 deletions(-) diff --git a/lib/LED_clock/Config/Blynk/default/BlynkConfig.cpp b/lib/LED_clock/Config/Blynk/default/BlynkConfig.cpp index 98c074d..77a35fc 100644 --- a/lib/LED_clock/Config/Blynk/default/BlynkConfig.cpp +++ b/lib/LED_clock/Config/Blynk/default/BlynkConfig.cpp @@ -148,6 +148,7 @@ void blynkReconfigure(ConfigManager* config) Blynk.config(config->getProperty(ConfigManager::BASE_CONFIG, BLYNK_TOKEN_KEY), config->getProperty(ConfigManager::BASE_CONFIG, BLYNK_SERVER_KEY), 80); BlynkC->setEnabled(config->getProperty(ConfigManager::BASE_CONFIG, BLYNK_ACTIVE_KEY)); + ESP.restart(); } /** diff --git a/lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h b/lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h index 9f70046..7d88699 100644 --- a/lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h +++ b/lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h @@ -44,17 +44,19 @@ class WiFiManager void handleSaveUnavailable(AsyncWebServerRequest *request, JsonVariant &json); void startWebInterface(); void stopAnimation(); + void setDisplay(DisplayManager* display); + uint8_t waitForConnectResult(); + int connectWifi(String ssid, String pass); + bool startConfigPortal(); + void setupConfigPortal(); + bool captivePortal(AsyncWebServerRequest *request); + String saveSettings(ConfigManager::ConfigType type, JsonObject* json); public: static WiFiManager* getInstance(); ~WiFiManager(); - void setupConfigPortal(); - bool captivePortal(AsyncWebServerRequest *request); bool autoConnect(); bool autoConnect(DisplayManager* display); - void setDisplay(DisplayManager* display); - uint8_t waitForConnectResult(); - int connectWifi(String ssid, String pass); - bool startConfigPortal(); + void startWebUI(); }; diff --git a/lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp b/lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp index 8d4da25..e722a8c 100644 --- a/lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp +++ b/lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp @@ -296,41 +296,72 @@ String WiFiManager::saveWiFiSettings(ConfigManager::ConfigType type, JsonObject* } } +String WiFiManager::saveSettings(ConfigManager::ConfigType type, JsonObject* json) +{ + String FailedKeys = ""; + bool anyErrors = false; + for (JsonPair kv : *json) + { + if(!config->setProperty(kv, type)) + { + if(anyErrors != false) + { + FailedKeys += ", "; + } + FailedKeys += kv.key().c_str(); + anyErrors = true; + } + } + config->applyChanges(); + config->saveConfigPersistent(type); + if(anyErrors == true) + { + String ErrorMessage = "ERROR: Following base config properties were not set: " + FailedKeys; + Serial.print("[W]: "); + Serial.println(ErrorMessage); + return ErrorMessage; + } + else + { + return "Config Saved without errors"; + } +} + +void WiFiManager::startWebUI() +{ + Serial.println("Starting web UI"); + server->reset(); + server->serveStatic("/", SPIFFS, "/").setDefaultFile("index.html"); + + server->onNotFound([](AsyncWebServerRequest *request) + { + request->send(SPIFFS, "/index.html", "text/html"); + }); + + AsyncCallbackJsonWebHandler* baseConfigHandler = new AsyncCallbackJsonWebHandler("/BaseConfig", [this](AsyncWebServerRequest *request, JsonVariant &json) + { + JsonObject temp = json.as(); + request->send(200, "text/plain", saveSettings(ConfigManager::BASE_CONFIG, &temp)); + }); + server->addHandler(baseConfigHandler); + + AsyncCallbackJsonWebHandler* colorConfigHandler = new AsyncCallbackJsonWebHandler("/ColorConfig", [this](AsyncWebServerRequest *request, JsonVariant &json) + { + JsonObject temp = json.as(); + request->send(200, "text/plain", saveSettings(ConfigManager::COLOR_CONFIG, &temp)); + }); + server->addHandler(colorConfigHandler); + + AsyncCallbackJsonWebHandler* hwConfigHandler = new AsyncCallbackJsonWebHandler("/HWConfig", [this](AsyncWebServerRequest *request, JsonVariant &json) + { + JsonObject temp = json.as(); + request->send(200, "text/plain", saveSettings(ConfigManager::HW_CONFIG, &temp)); + }); + server->addHandler(hwConfigHandler); + + AsyncCallbackJsonWebHandler* WIFIConfigHandler = new AsyncCallbackJsonWebHandler("/WIFISettings", std::bind(&WiFiManager::handleWifiSubmit, this, std::placeholders::_1, std::placeholders::_2)); + server->addHandler(WIFIConfigHandler); -// void WiFiManager::startWebInterface() -// { -// server->reset(); -// server->serveStatic("/", SPIFFS, "/").setDefaultFile("index.html"); - -// server->onNotFound([](AsyncWebServerRequest *request) -// { -// request->send(SPIFFS, "/index.html", "text/html"); -// }); - -// AsyncCallbackJsonWebHandler* baseConfigHandler = new AsyncCallbackJsonWebHandler("/BaseConfig", [](AsyncWebServerRequest *request, JsonVariant &json) -// { -// JsonObject temp = json.as(); -// request->send(200, "text/plain", saveSettings(ConfigManager::BASE_CONFIG, &temp)); -// }); -// server->addHandler(baseConfigHandler); - -// AsyncCallbackJsonWebHandler* colorConfigHandler = new AsyncCallbackJsonWebHandler("/ColorConfig", [](AsyncWebServerRequest *request, JsonVariant &json) -// { -// JsonObject temp = json.as(); -// request->send(200, "text/plain", saveSettings(ConfigManager::COLOR_CONFIG, &temp)); -// }); -// server->addHandler(colorConfigHandler); - -// AsyncCallbackJsonWebHandler* hwConfigHandler = new AsyncCallbackJsonWebHandler("/HWConfig", [](AsyncWebServerRequest *request, JsonVariant &json) -// { -// JsonObject temp = json.as(); -// request->send(200, "text/plain", saveSettings(ConfigManager::HW_CONFIG, &temp)); -// }); -// server->addHandler(hwConfigHandler); - -// AsyncCallbackJsonWebHandler* WIFIConfigHandler = new AsyncCallbackJsonWebHandler("/WIFISettings", std::bind(&WiFiManager::handleWifiSubmit, this, std::placeholders::_1, std::placeholders::_2)); -// server->addHandler(WIFIConfigHandler); - -// server->begin(); -// Serial.println("Web interface should be up and running"); -// } \ No newline at end of file + server->begin(); + Serial.println("Web interface should be up and running"); +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 4dc98f0..20656e8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -85,58 +85,6 @@ void startupAnimation() } } -// void startWebServer() -// { -// server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html"); - -// server.onNotFound([](AsyncWebServerRequest *request) -// { -// request->send(SPIFFS, "/index.html", "text/html"); -// }); - -// AsyncCallbackJsonWebHandler* baseConfigHandler = new AsyncCallbackJsonWebHandler("/BaseConfig", [](AsyncWebServerRequest *request, JsonVariant &json) -// { -// JsonObject temp = json.as(); -// request->send(200, "text/plain", saveSettings(ConfigManager::BASE_CONFIG, &temp)); -// }); -// server.addHandler(baseConfigHandler); - -// AsyncCallbackJsonWebHandler* colorConfigHandler = new AsyncCallbackJsonWebHandler("/ColorConfig", [](AsyncWebServerRequest *request, JsonVariant &json) -// { -// JsonObject temp = json.as(); -// request->send(200, "text/plain", saveSettings(ConfigManager::COLOR_CONFIG, &temp)); -// }); -// server.addHandler(colorConfigHandler); - -// AsyncCallbackJsonWebHandler* hwConfigHandler = new AsyncCallbackJsonWebHandler("/HWConfig", [](AsyncWebServerRequest *request, JsonVariant &json) -// { -// JsonObject temp = json.as(); -// request->send(200, "text/plain", saveSettings(ConfigManager::HW_CONFIG, &temp)); -// }); -// server.addHandler(hwConfigHandler); - -// AsyncCallbackJsonWebHandler* WIFIConfigHandler = new AsyncCallbackJsonWebHandler("/WIFISettings", [](AsyncWebServerRequest *request, JsonVariant &json) -// { -// JsonObject temp = json.as(); -// if(temp.containsKey("SSID") && temp.containsKey("PW")) -// { -// const char* SSID = temp["SSID"]; -// const char* PW = temp["PW"]; -// WIFI_SSID = String(SSID); -// WIFI_PW = String(PW); -// wifiReconnectFlag = true; -// request->send(200, "text/plain", "The device will now attempt to connect to \"" + WIFI_SSID + "\" Connection to the web interface will be lost."); -// } -// else -// { -// request->send(200, "text/plain", "ERROR: All two keys (SSID, PW are mandatory but at least one of them was missing."); -// } -// }); -// server.addHandler(WIFIConfigHandler); - -// server.begin(); -// } - void setup() { Serial.begin(115200); @@ -160,6 +108,7 @@ void setup() Serial.println("Running wifi setup..."); WIFIManager->autoConnect(ShelfDisplays); + WIFIManager->startWebUI(); setupOTA(); ArduinoOTA.handle(); //give ota the opportunity to update before the main loop starts in case we have a crash in there @@ -187,24 +136,6 @@ void loop() ArduinoOTA.handle(); states->handleStates(); //updates display states, switches between modes etc. ShelfDisplays->handle(); - // if(wifiReconnectFlag == true) - // { - // Serial.printf("Trying to connect to %s with password %s\n\r", WIFI_SSID.c_str(), WIFI_PW.c_str()); - // server.end(); - // WiFi.removeEvent(reconnectEvent); - // WiFi.disconnect(); - // connectToWIFI(WIFI_SSID.c_str(), WIFI_PW.c_str()); - // if(WiFi.status() == WL_CONNECTED) - // { - // Serial.println("Connect successful"); - // Serial.println("Resetting contoller to reconnect to the right WIFI network"); - // } - // else - // { - // Serial.println("Reconnect failed, maximum number of retries exhausted"); - // } - // ESP.restart(); - // } } void AlarmTriggered() @@ -224,90 +155,6 @@ void TimerDone() BlynkConfiguration->updateUI(); } -// void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) -// { -// Serial.print("WiFi lost connection. Reason: "); -// Serial.println(info.disconnected.reason); -// Serial.println("Trying to Reconnect"); -// WiFi.disconnect(); -// connectToWIFI(); -// } - -// void connectToWIFI(const char* SSID, const char* PW) -// { -// Serial.println("Trying to connect to wifi network"); -// ShelfDisplays->setAllSegmentColors(WIFI_CONNECTING_COLOR); -// ShelfDisplays->showLoadingAnimation(); -// wl_status_t connectionStatus; -// if(SSID != nullptr && PW != nullptr) -// { -// connectionStatus = WiFi.begin(SSID, PW); -// } -// else -// { -// connectionStatus = WiFi.begin(); -// } -// for (int i = 0; i < NUM_RETRIES; i++) -// { -// Serial.print("."); -// if(SSID != nullptr && PW != nullptr) -// { -// connectionStatus = WiFi.begin(SSID, PW); -// } -// else -// { -// connectionStatus = WiFi.begin(); -// } -// if(connectionStatus == WL_CONNECTED) -// { -// Serial.println("Reconnect successful"); -// ShelfDisplays->setAllSegmentColors(WIFI_CONNECTION_SUCCESSFUL_COLOR); -// break; -// } -// ShelfDisplays->delay(1000); -// } -// } - -// void wifiAPMode() -// { -// IPAddress apIP(8,8,4,4); // The default android DNS -// DNSServer dnsServer; -// const byte DNS_PORT = 53; - -// WiFi.mode(WIFI_AP); -// WiFi.softAP("LED-Pixel clock fallback AP"); -// WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); - -// dnsServer.start(DNS_PORT, "*", apIP); - -// startWebServer(); - -// server.onNotFound([](AsyncWebServerRequest *request) -// { -// request->redirect("/WIFI"); -// }); -// while(true) { -// ShelfDisplays->delay(500); -// } -// } - -// void wifiSetup() -// { -// connectToWIFI(); - -// if(WiFi.status() != WL_CONNECTED) -// { -// Serial.println("Reconnect failed Starting fallback AP"); -// ShelfDisplays->setAllSegmentColors(WIFI_SMART_CONFIG_COLOR); -// wifiAPMode(); -// } -// reconnectEvent = WiFi.onEvent(WiFiStationDisconnected, SYSTEM_EVENT_STA_DISCONNECTED); -// ShelfDisplays->stopLoadingAnimation(); -// Serial.println("Waiting for loading animation to finish..."); -// ShelfDisplays->waitForLoadingAnimationFinish(); -// ShelfDisplays->turnAllSegmentsOff(); -// } - void setupOTA() { // Port defaults to 3232 From 70e414a59bca356a582ea62115a58c4cd45f9792 Mon Sep 17 00:00:00 2001 From: "flo.laschober@gmail.com" Date: Sun, 30 May 2021 12:39:32 +0200 Subject: [PATCH 12/13] Changed init mechanic for digit morph animations --- .../Config/Blynk/default/BlynkConfig.cpp | 6 +- .../default/SegmentTransitions.cpp | 122 +++++++++++------- .../Transitions/default/SegmentTransitions.h | 34 +---- .../Modules/ConfigManager/inc/ConfigManager.h | 11 +- .../ConfigManager/src/ConfigManager.cpp | 27 ++-- .../DisplayManager/inc/DisplayManager.h | 3 + .../DisplayManager/src/DisplayManager.cpp | 12 ++ platformio.ini | 2 +- 8 files changed, 112 insertions(+), 105 deletions(-) diff --git a/lib/LED_clock/Config/Blynk/default/BlynkConfig.cpp b/lib/LED_clock/Config/Blynk/default/BlynkConfig.cpp index 77a35fc..f9c2aa5 100644 --- a/lib/LED_clock/Config/Blynk/default/BlynkConfig.cpp +++ b/lib/LED_clock/Config/Blynk/default/BlynkConfig.cpp @@ -16,7 +16,7 @@ BlynkConfig* BlynkC = nullptr; ClockState* ClockS = nullptr; TimeManager* TimeM = nullptr; -void blynkReconfigure(ConfigManager* config); +bool blynkReconfigure(ConfigManager* config); /** * \brief Construct a new Blynk Config object. Also poulate all variables of the class with meaningful values @@ -143,12 +143,12 @@ void BlynkConfig::setEnabled(bool enabled) } } -void blynkReconfigure(ConfigManager* config) +bool blynkReconfigure(ConfigManager* config) { Blynk.config(config->getProperty(ConfigManager::BASE_CONFIG, BLYNK_TOKEN_KEY), config->getProperty(ConfigManager::BASE_CONFIG, BLYNK_SERVER_KEY), 80); BlynkC->setEnabled(config->getProperty(ConfigManager::BASE_CONFIG, BLYNK_ACTIVE_KEY)); - ESP.restart(); + return SYSTEM_RESTART_REQUIRED; } /** diff --git a/lib/LED_clock/Config/Transitions/default/SegmentTransitions.cpp b/lib/LED_clock/Config/Transitions/default/SegmentTransitions.cpp index b26bca6..fe1ea0d 100644 --- a/lib/LED_clock/Config/Transitions/default/SegmentTransitions.cpp +++ b/lib/LED_clock/Config/Transitions/default/SegmentTransitions.cpp @@ -13,6 +13,8 @@ CubicEase* cubicEaseIn = new CubicEase(EASE_IN); CubicEase* cubicEaseOut = new CubicEase(EASE_OUT); /** \} */ +typedef Animator::ComplexAmination* (*AnimationInitFunction)(uint16_t totalAnimationLength); + /** * \brief Function prototypes for all segment transition animation init functions * \addtogroup TransitionAnimationInitFunctions @@ -45,38 +47,6 @@ Animator::ComplexAmination* InitAnimate0to9(uint16_t totalAnimationLength); Animator::ComplexAmination* InitAnimate0to5(uint16_t totalAnimationLength); /** \} */ -/** - * \brief Global variables for all segment transition animations - * \addtogroup TransitionAnimations - * \{ - */ -Animator::ComplexAmination* Animate0to1 = InitAnimate0to1(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate1to2 = InitAnimate1to2(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate2to3 = InitAnimate2to3(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate3to4 = InitAnimate3to4(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate2to0 = InitAnimate2to0(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate4to5 = InitAnimate4to5(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate5to6 = InitAnimate5to6(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate5to0 = InitAnimate5to0(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate6to7 = InitAnimate6to7(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate7to8 = InitAnimate7to8(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate8to9 = InitAnimate8to9(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate9to0 = InitAnimate9to0(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* AnimateOFFto1 = InitAnimateOFFto1(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate1toOFF = InitAnimate1toOFF(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate9to8 = InitAnimate9to8(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate8to7 = InitAnimate8to7(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate7to6 = InitAnimate7to6(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate6to5 = InitAnimate6to5(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate5to4 = InitAnimate5to4(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate4to3 = InitAnimate4to3(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate3to2 = InitAnimate3to2(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate2to1 = InitAnimate2to1(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate1to0 = InitAnimate1to0(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate0to9 = InitAnimate0to9(DIGIT_ANIMATION_SPEED); -Animator::ComplexAmination* Animate0to5 = InitAnimate0to5(DIGIT_ANIMATION_SPEED); -/** \} */ - /** * \brief This transformation lookup table defines which animation to call for which transition. * Every row decides from which digits we want to morph and than the column of the digit we want to morph to is selected. @@ -84,20 +54,84 @@ Animator::ComplexAmination* Animate0to5 = InitAnimate0to5(DIGIT_ANIMATION_SPEED * */ Animator::ComplexAmination* TransformationLookupTable[11][11] = { - //To:0 1 2 3 4 5 6 7 8 9 OFF -/*from 0 */{nullptr , Animate0to1 , nullptr , nullptr , nullptr , Animate0to5 , nullptr , nullptr , nullptr , Animate0to9 , nullptr }, -/*from 1 */{Animate1to0 , nullptr , Animate1to2 , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , Animate1toOFF}, -/*from 2 */{Animate2to0 , Animate2to1 , nullptr , Animate2to3 , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr }, -/*from 3 */{nullptr , nullptr , Animate3to2 , nullptr , Animate3to4 , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr }, -/*from 4 */{nullptr , nullptr , nullptr , Animate4to3 , nullptr , Animate4to5 , nullptr , nullptr , nullptr , nullptr , nullptr }, -/*from 5 */{Animate5to0 , nullptr , nullptr , nullptr , Animate5to4 , nullptr , Animate5to6 , nullptr , nullptr , nullptr , nullptr }, -/*from 6 */{nullptr , nullptr , nullptr , nullptr , nullptr , Animate6to5 , nullptr , Animate6to7 , nullptr , nullptr , nullptr }, -/*from 7 */{nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , Animate7to6 , nullptr , Animate7to8 , nullptr , nullptr }, -/*from 8 */{nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , Animate8to7 , nullptr , Animate8to9 , nullptr }, -/*from 9 */{Animate9to0 , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , Animate9to8 , nullptr , nullptr }, -/*from OFF */{nullptr , AnimateOFFto1, nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr } + //To:0 1 2 3 4 5 6 7 8 9 OFF +/*from 0 */{nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr }, +/*from 1 */{nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr }, +/*from 2 */{nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr }, +/*from 3 */{nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr }, +/*from 4 */{nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr }, +/*from 5 */{nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr }, +/*from 6 */{nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr }, +/*from 7 */{nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr }, +/*from 8 */{nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr }, +/*from 9 */{nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr }, +/*from OFF */{nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr } +}; + +AnimationInitFunction TransformationInitLookupTable[11][11] = { + //To:0 1 2 3 4 5 6 7 8 9 OFF +/*from 0 */{nullptr , InitAnimate0to1 , nullptr , nullptr , nullptr , InitAnimate0to5 , nullptr , nullptr , nullptr , InitAnimate0to9 , nullptr }, +/*from 1 */{InitAnimate1to0 , nullptr , InitAnimate1to2 , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , InitAnimate1toOFF }, +/*from 2 */{InitAnimate2to0 , InitAnimate2to1 , nullptr , InitAnimate2to3 , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr }, +/*from 3 */{nullptr , nullptr , InitAnimate3to2 , nullptr , InitAnimate3to4 , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr }, +/*from 4 */{nullptr , nullptr , nullptr , InitAnimate4to3 , nullptr , InitAnimate4to5 , nullptr , nullptr , nullptr , nullptr , nullptr }, +/*from 5 */{InitAnimate5to0 , nullptr , nullptr , nullptr , InitAnimate5to4 , nullptr , InitAnimate5to6 , nullptr , nullptr , nullptr , nullptr }, +/*from 6 */{nullptr , nullptr , nullptr , nullptr , nullptr , InitAnimate6to5 , nullptr , InitAnimate6to7 , nullptr , nullptr , nullptr }, +/*from 7 */{nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , InitAnimate7to6 , nullptr , InitAnimate7to8 , nullptr , nullptr }, +/*from 8 */{nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , InitAnimate8to7 , nullptr , InitAnimate8to9 , nullptr }, +/*from 9 */{InitAnimate9to0 , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , InitAnimate9to8 , nullptr , nullptr }, +/*from OFF */{nullptr , InitAnimateOFFto1 , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr , nullptr } }; + +void cleanupMorphAnimation(Animator::ComplexAmination* animation) +{ + int animationChainLength = animation->animations->size(); + for (int i = 0; i < animationChainLength; i++) + { + Animator::animationStep* currentStep = animation->animations->get(i); + delete currentStep->animationEffects; + delete currentStep->arrayIndex; + delete currentStep->easingEffects; + delete currentStep; + } + animation->animations->clear(); + delete animation->animations; //TODO: check if problematic + delete animation; +} + +void SetupAllMorphAnimations(uint16_t animationLength) +{ + for (uint8_t i = 0; i < 11; i++) + { + for (uint8_t j = 0; j < 11; j++) + { + if(TransformationInitLookupTable[i][j] != nullptr) + { + if(TransformationLookupTable[i][j] != nullptr) + { + cleanupMorphAnimation(TransformationLookupTable[i][j]); + } + TransformationLookupTable[i][j] = TransformationInitLookupTable[i][j](animationLength); + } + } + } +} + +void ChangeMorphAnimationSpeed(uint16_t newAnimationLength) +{ + for (uint8_t i = 0; i < 11; i++) + { + for (uint8_t j = 0; j < 11; j++) + { + if(TransformationLookupTable[i][j] != nullptr) + { + TransformationLookupTable[i][j]->LengthPerAnimation = newAnimationLength / (TransformationLookupTable[i][j]->animations->size() + 1); + } + } + } +} + Animator::ComplexAmination* InitAnimate0to1(uint16_t totalAnimationLength) { #undef LENGTH diff --git a/lib/LED_clock/Config/Transitions/default/SegmentTransitions.h b/lib/LED_clock/Config/Transitions/default/SegmentTransitions.h index 6d79e03..7c5e0e8 100644 --- a/lib/LED_clock/Config/Transitions/default/SegmentTransitions.h +++ b/lib/LED_clock/Config/Transitions/default/SegmentTransitions.h @@ -16,36 +16,6 @@ */ extern Animator::ComplexAmination* TransformationLookupTable[11][11]; -/** - * \brief All avaliable animations to morph between digits - * \addtogroup DigitMorphAnimations - * \{ - */ -extern Animator::ComplexAmination* Animate0to1; -extern Animator::ComplexAmination* Animate1to2; -extern Animator::ComplexAmination* Animate2to3; -extern Animator::ComplexAmination* Animate3to4; -extern Animator::ComplexAmination* Animate2to0; -extern Animator::ComplexAmination* Animate4to5; -extern Animator::ComplexAmination* Animate5to6; -extern Animator::ComplexAmination* Animate5to0; -extern Animator::ComplexAmination* Animate6to7; -extern Animator::ComplexAmination* Animate7to8; -extern Animator::ComplexAmination* Animate8to9; -extern Animator::ComplexAmination* Animate9to0; -extern Animator::ComplexAmination* AnimateOFFto1; -extern Animator::ComplexAmination* Animate1toOFF; -extern Animator::ComplexAmination* Animate9to8; -extern Animator::ComplexAmination* Animate8to7; -extern Animator::ComplexAmination* Animate7to6; -extern Animator::ComplexAmination* Animate6to5; -extern Animator::ComplexAmination* Animate5to4; -extern Animator::ComplexAmination* Animate4to3; -extern Animator::ComplexAmination* Animate3to2; -extern Animator::ComplexAmination* Animate2to1; -extern Animator::ComplexAmination* Animate1to0; -extern Animator::ComplexAmination* Animate0to9; -extern Animator::ComplexAmination* Animate0to5; - -/** \} */ +void SetupAllMorphAnimations(uint16_t animationLength); +void ChangeMorphAnimationSpeed(uint16_t newAnimationLength); #endif \ No newline at end of file diff --git a/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h index b23587b..bc2c6af 100644 --- a/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h +++ b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h @@ -35,6 +35,7 @@ #define BLYNK_ACTIVE_KEY "ENABLE_BLYNK" #define BLYNK_TOKEN_KEY "BLYNK_AUTH_TOKEN" #define BLYNK_SERVER_KEY "BLYNK_SERVER" +#define DIGIT_ANIMATION_SPEED_KEY "DIGIT_ANIMATION_SPEED" #define WIFI_CONNECTING_COLOR_KEY "WIFI_CONNECTING" #define WIFI_CONNECTED_COLOR_KEY "WIFI_CONNECTION_SUCCESSFUL" @@ -47,6 +48,9 @@ // const char* Wifi_config_classes[] = {}; +#define SYSTEM_RESTART_REQUIRED true +#define SYSTEM_NO_RESTART_REQUIRED false + /** * \brief The config manager is responsible to handle the json configuration files. * It stores all config values in variables to be able to use them in the code @@ -54,7 +58,7 @@ class ConfigManager { public: - typedef void (*propertyChangedCallback)(ConfigManager* config); + typedef bool (*propertyChangedCallback)(ConfigManager* config); enum ConfigType {BASE_CONFIG, COLOR_CONFIG, HW_CONFIG, _CONFIG_TYPE_LENGTH}; private: struct changedEventCallback { @@ -84,16 +88,11 @@ class ConfigManager ConfigManager(); DynamicJsonDocument* deserializeDynamically(fs::File &input, uint64_t initialDocSize); bool deserializeDynamically(fs::File &input, DynamicJsonDocument* initialDocument); - // helpers for returning default types on error - bool getDefault(bool* input); - int getDefault(int* input); - const char* getDefault(const char** input); template T getProperty(DynamicJsonDocument* root, const char* key); bool loadConfigFromMemory(const char* filename, DynamicJsonDocument* doc); - DynamicJsonDocument* getDocument(ConfigType config); const char* getFileName(ConfigType config); diff --git a/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp b/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp index c93584a..ec727fb 100644 --- a/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp +++ b/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp @@ -64,21 +64,6 @@ ConfigManager* ConfigManager::getInstance() return instance; } -bool ConfigManager::getDefault(bool* input) -{ - return false; -} - -int ConfigManager::getDefault(int* input) -{ - return -1; -} - -const char* ConfigManager::getDefault(const char** input) -{ - return nullptr; -} - DynamicJsonDocument* ConfigManager::deserializeDynamically(fs::File &input, uint64_t initialDocSize) { //try to initialize a json document big enough to fit all the data @@ -199,15 +184,14 @@ T ConfigManager::getProperty(DynamicJsonDocument* root, const char* key) { if(!jvalue.is()) { - Serial.printf("[E]: BaseConfig key \"%s\" is not the correct type", key); + Serial.printf("[E]: BaseConfig key \"%s\" is not the correct type\n\r", key); } else { return jvalue.as(); } } - T* helper = nullptr; - return getDefault(helper); + return T(); } template @@ -325,13 +309,18 @@ void ConfigManager::applyChanges() void ConfigManager::checkCallbacks(uint8_t ConfigEvent) { + bool needsRestart = false; for (int i = 0; i < eventCallbacks.size(); i++) { if((eventCallbacks.get(i)->configEvent & ConfigEvent) != 0x00) { - eventCallbacks.get(i)->callback(this); + needsRestart |= eventCallbacks.get(i)->callback(this); } } + if(needsRestart == true) + { + ESP.restart(); + } } void ConfigManager::registerOnChangedCallback(uint8_t ConfigEvent, propertyChangedCallback Callback) diff --git a/lib/LED_clock/Modules/DisplayManager/inc/DisplayManager.h b/lib/LED_clock/Modules/DisplayManager/inc/DisplayManager.h index 6743880..bace530 100644 --- a/lib/LED_clock/Modules/DisplayManager/inc/DisplayManager.h +++ b/lib/LED_clock/Modules/DisplayManager/inc/DisplayManager.h @@ -17,6 +17,7 @@ #include "Configuration.h" #include "LinkedList.h" #include "Animations.h" +#include "ConfigManager.h" /** * \brief Macro to shorten then name of the function to make usage easier in the animation config files. @@ -39,6 +40,7 @@ class DisplayManager static uint8_t diplayIndex[NUM_SEGMENTS]; static DisplayManager* instance; + ConfigManager* config; Animator* animationManager; Segment* allSegments[NUM_SEGMENTS]; SevenSegment* Displays[NUM_DISPLAYS]; @@ -75,6 +77,7 @@ class DisplayManager void AnimationManagersTemporaryOverride(Animator* OverrideanimationManager); void restoreAnimationManagers(); + // bool reconfigureDisplays(ConfigManager* config); DisplayManager(); public: diff --git a/lib/LED_clock/Modules/DisplayManager/src/DisplayManager.cpp b/lib/LED_clock/Modules/DisplayManager/src/DisplayManager.cpp index 072e144..870a619 100644 --- a/lib/LED_clock/Modules/DisplayManager/src/DisplayManager.cpp +++ b/lib/LED_clock/Modules/DisplayManager/src/DisplayManager.cpp @@ -10,8 +10,11 @@ DisplayManager* DisplayManager::instance = nullptr; DynamicList* DisplayManager::SegmentIndexErrorList = nullptr; +bool reconfigureDisplays(ConfigManager* config); + DisplayManager::DisplayManager() { + config = ConfigManager::getInstance(); FastLED.addLeds(leds, NUM_LEDS); // GRB ordering is typical #if APPEND_DOWN_LIGHTERS == false @@ -51,6 +54,8 @@ DisplayManager::DisplayManager() progressTotal = 0; currentProgressOffset = 0; currentProgressStep = 0; + + config->registerOnChangedCallback(ConfigManager::BASE_CONFIG_CHANGED, reconfigureDisplays); } DisplayManager::~DisplayManager() @@ -59,6 +64,12 @@ DisplayManager::~DisplayManager() instance = nullptr; } +bool reconfigureDisplays(ConfigManager* config) +{ + ChangeMorphAnimationSpeed(config->getProperty(ConfigManager::BASE_CONFIG, DIGIT_ANIMATION_SPEED_KEY)); + return SYSTEM_NO_RESTART_REQUIRED; +} + DisplayManager* DisplayManager::getInstance() { if(instance == nullptr) @@ -147,6 +158,7 @@ void DisplayManager::setMinuteSegmentColors(CRGB color) void DisplayManager::InitSegments(uint16_t indexOfFirstLed, uint8_t ledsPerSegment, CRGB initialColor, uint8_t initBrightness) { + SetupAllMorphAnimations(DIGIT_ANIMATION_SPEED); for (uint8_t i = 0; i < NUM_DISPLAYS; i++) { if(Displays[i] != nullptr) diff --git a/platformio.ini b/platformio.ini index 77d5e04..7365ad2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,7 +24,7 @@ lib_deps = ; Uncomment these lines to use the debugger. -; debug_tool = esp-prog +debug_tool = esp-prog ; upload_protocol = esp-prog ; debug_init_break = tbreak setup ; Uncomment these to use OTA as a flash method. This only works if the ESp already has code running on it where OTA update is enabled. From 0d7e5ebf0f16fbf4e03c450ac729e84a769e3555 Mon Sep 17 00:00:00 2001 From: "flo.laschober@gmail.com" Date: Tue, 1 Jun 2021 18:51:42 +0200 Subject: [PATCH 13/13] Wifi connection improvements Fixed not the right type issue when trying to change any setting other than a string type --- WebConfigUI/src/components/SettingsForm.jsx | 2 +- .../Config/Setup/clock-only/Configuration.h | 5 +- .../Config/Setup/default/Configuration.h | 7 ++ .../Modules/ConfigManager/inc/ConfigManager.h | 4 ++ .../ConfigManager/src/ConfigManager.cpp | 72 +++++++++++++++++-- .../Modules/TimeManager/inc/TimeManager.h | 1 + .../Modules/TimeManager/src/TimeManager.cpp | 16 +++-- .../Modules/WiFiManager/inc/WiFiManager.h | 2 + .../Modules/WiFiManager/src/WiFiManager.cpp | 34 +++++++-- lib/LED_clock/library_default.json | 2 + src/main.cpp | 4 +- 11 files changed, 127 insertions(+), 22 deletions(-) diff --git a/WebConfigUI/src/components/SettingsForm.jsx b/WebConfigUI/src/components/SettingsForm.jsx index 708eb17..df3a3a9 100644 --- a/WebConfigUI/src/components/SettingsForm.jsx +++ b/WebConfigUI/src/components/SettingsForm.jsx @@ -66,7 +66,7 @@ const SettingsForm = (props) => { console.log("Invalid value"); return; } - newSettingValue[target] = newValue; + newSettingValue[target] = parseInt(newValue, 10); case "color": parseRGBColor(target); switch (event.target.id) { diff --git a/lib/LED_clock/Config/Setup/clock-only/Configuration.h b/lib/LED_clock/Config/Setup/clock-only/Configuration.h index b460b60..3e16d70 100644 --- a/lib/LED_clock/Config/Setup/clock-only/Configuration.h +++ b/lib/LED_clock/Config/Setup/clock-only/Configuration.h @@ -11,7 +11,10 @@ //Place all new configs here: - +/** + * \brief Time period in which to retry connecting to the WIFI if the connection got lost + */ +#define BROKEN_WIFI_CONNECT_RETRY 120000 // Legacy configs: diff --git a/lib/LED_clock/Config/Setup/default/Configuration.h b/lib/LED_clock/Config/Setup/default/Configuration.h index bfe08b4..51cfc2b 100644 --- a/lib/LED_clock/Config/Setup/default/Configuration.h +++ b/lib/LED_clock/Config/Setup/default/Configuration.h @@ -9,6 +9,13 @@ #ifndef _CONFIGURATIONS_H_ #define _CONFIGURATIONS_H_ +//Place all new configs here: +#define BROKEN_WIFI_CONNECT_RETRY 120000 + + +// Legacy configs: + + /** * \addtogroup MainConfiguration * \brief Main configuration settings diff --git a/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h index bc2c6af..d33f850 100644 --- a/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h +++ b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h @@ -81,6 +81,7 @@ class ConfigManager DynamicJsonDocument* ColorConfig; DynamicJsonDocument* HWConfig; DynamicJsonDocument* EventIDs; + bool restartRequested; bool configClassWasUpdated[_CONFIG_CLASS_ID_LENGTH]; bool configNeedsSaving[_CONFIG_TYPE_LENGTH]; @@ -97,6 +98,7 @@ class ConfigManager const char* getFileName(ConfigType config); void checkCallbacks(uint8_t ConfigEvent); + bool isSameValueType(JsonVariant type1, JsonVariant type2); public: enum ConfigEvents { @@ -129,6 +131,8 @@ class ConfigManager void saveConfigPersistent(ConfigType config); void applyChanges(); + void applySaveAndRestart(ConfigType config); + void restartIfRequired(); void registerOnChangedCallback(uint8_t ConfigEvent, propertyChangedCallback Callback); void unregisterOnChangedCallback(propertyChangedCallback Callback); diff --git a/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp b/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp index ec727fb..8576f21 100644 --- a/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp +++ b/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp @@ -17,6 +17,7 @@ template const char* ConfigManager::getProperty(ConfigType config, ConfigManager::ConfigManager() { + restartRequested = false; if(!SPIFFS.begin(true)) { Serial.println("Filesystem mount failed aborting..."); @@ -259,11 +260,19 @@ bool ConfigManager::setProperty(JsonPair& keyValuePair, ConfigType config) { if((*doc)[keyValuePair.key()] != keyValuePair.value()) { - //TODO: find a better solution for this - uint8_t eventID = (*EventIDs)[keyValuePair.key()]; - configClassWasUpdated[eventID] = true; - configNeedsSaving[config] = true; - return (*doc)[keyValuePair.key()].set(keyValuePair.value()); + if(isSameValueType((*doc)[keyValuePair.key()], keyValuePair.value()) == true) + { + //TODO: find a better solution for this + uint8_t eventID = (*EventIDs)[keyValuePair.key()]; + configClassWasUpdated[eventID] = true; + configNeedsSaving[config] = true; + return (*doc)[keyValuePair.key()].set(keyValuePair.value()); + } + else + { + Serial.printf("[E]: value of key %s is not the right type\n\r", keyValuePair.key().c_str()); + return false; + } } else { @@ -274,6 +283,41 @@ bool ConfigManager::setProperty(JsonPair& keyValuePair, ConfigType config) return false; } +bool ConfigManager::isSameValueType(JsonVariant type1, JsonVariant type2) +{ + uint8_t typeOf1 = 0; + uint8_t typeOf2 = 0; + if(type1.is()) + { + typeOf1 = 1; + } else if(type1.is()) + { + typeOf1 = 2; + } else if(type1.is()) + { + typeOf1 = 3; + } else if(type1.is()) + { + typeOf1 = 4; + } + + if(type1.is()) + { + typeOf2 = 1; + } else if(type1.is()) + { + typeOf2 = 2; + } else if(type1.is()) + { + typeOf2 = 3; + } else if(type1.is()) + { + typeOf2 = 4; + } + + return typeOf1 == typeOf2 && typeOf1 != 0 && typeOf2 != 0; +} + void ConfigManager::saveConfigPersistent(ConfigType config) { if(config >= _CONFIG_TYPE_LENGTH) @@ -307,6 +351,21 @@ void ConfigManager::applyChanges() checkCallbacks(updatedConfigClasses); } +void ConfigManager::applySaveAndRestart(ConfigType config) +{ + applyChanges(); + saveConfigPersistent(config); + restartIfRequired(); +} + +void ConfigManager::restartIfRequired() +{ + if(restartRequested == true) + { + ESP.restart(); + } +} + void ConfigManager::checkCallbacks(uint8_t ConfigEvent) { bool needsRestart = false; @@ -319,7 +378,8 @@ void ConfigManager::checkCallbacks(uint8_t ConfigEvent) } if(needsRestart == true) { - ESP.restart(); + restartRequested = true; + Serial.println("System restart was requested due to config change."); } } diff --git a/lib/LED_clock/Modules/TimeManager/inc/TimeManager.h b/lib/LED_clock/Modules/TimeManager/inc/TimeManager.h index f4126b5..4544ebc 100644 --- a/lib/LED_clock/Modules/TimeManager/inc/TimeManager.h +++ b/lib/LED_clock/Modules/TimeManager/inc/TimeManager.h @@ -54,6 +54,7 @@ class TimeManager TimeInfo currentTime; hw_timer_t* timer; uint32_t offlineTimeCounter; + uint32_t lastWifiConnectionTime; static TimeManager* TimeManagerSingelton; TimeInfo TimerInitialDuration; TimeInfo TimerDuration; diff --git a/lib/LED_clock/Modules/TimeManager/src/TimeManager.cpp b/lib/LED_clock/Modules/TimeManager/src/TimeManager.cpp index a067ed7..4d9fc75 100644 --- a/lib/LED_clock/Modules/TimeManager/src/TimeManager.cpp +++ b/lib/LED_clock/Modules/TimeManager/src/TimeManager.cpp @@ -30,6 +30,7 @@ TimeManager::TimeManager() AlarmActive = false; AlarmTriggered = false; AlarmCleared = false; + lastWifiConnectionTime = millis(); } TimeManager::~TimeManager() @@ -50,9 +51,7 @@ TimeManager* TimeManager::getInstance() bool TimeManager::init() { - #if TIME_MANAGER_DEMO_MODE == false - configTzTime(TIMEZONE_INFO, NTP_SERVER); - #endif + configTzTime(TIMEZONE_INFO, NTP_SERVER); // timer 0 divider 80 because 80Mhz and count up timer = timerBegin(0, 80, true); @@ -256,7 +255,7 @@ void IRAM_ATTR onTimer() // Time code, use this for normal operation if(!config->getProperty(ConfigManager::HW_CONFIG, DEMO_MODE_KEY)) { - if(timeM->offlineTimeCounter++ >= TIME_SYNC_INTERVALL && WiFi.status() == WL_CONNECTED) + if(timeM->offlineTimeCounter++ >= TIME_SYNC_INTERVALL) { if(timeM->synchronize() == true) { @@ -310,6 +309,15 @@ void IRAM_ATTR onTimer() timeM->AlarmCleared = false; } } + //handle wifi lost + if(WiFi.status() != WL_CONNECTED) + { + if(timeM->lastWifiConnectionTime + BROKEN_WIFI_CONNECT_RETRY < millis()) + { + timeM->lastWifiConnectionTime = millis(); + WiFi.begin(); // try to reconnect in a non blocking way + } + } } else { diff --git a/lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h b/lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h index 7d88699..40e1cf4 100644 --- a/lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h +++ b/lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h @@ -33,6 +33,7 @@ class WiFiManager bool connect; String WIFI_SSID; String WIFI_PW; + wifi_event_id_t reconnectEvent; static WiFiManager* currentInstance; WiFiManager(); @@ -51,6 +52,7 @@ class WiFiManager void setupConfigPortal(); bool captivePortal(AsyncWebServerRequest *request); String saveSettings(ConfigManager::ConfigType type, JsonObject* json); + void tryToReconnect(WiFiEvent_t event, WiFiEventInfo_t info); public: static WiFiManager* getInstance(); ~WiFiManager(); diff --git a/lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp b/lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp index e722a8c..748b85f 100644 --- a/lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp +++ b/lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp @@ -60,6 +60,10 @@ bool WiFiManager::autoConnect() if (connectWifi("", "") == WL_CONNECTED) { + if(ShelfDisplays != nullptr) + { + ShelfDisplays->setAllSegmentColors(config->getProperty(ConfigManager::COLOR_CONFIG, WIFI_CONNECTED_COLOR_KEY)); + } Serial.print("IP Address: "); Serial.println(WiFi.localIP()); //connected @@ -72,10 +76,6 @@ bool WiFiManager::autoConnect() int WiFiManager::connectWifi(String ssid, String pass) { - if(ShelfDisplays != nullptr) - { - ShelfDisplays->setAllSegmentColors(config->getProperty(ConfigManager::COLOR_CONFIG, WIFI_CONNECTING_COLOR_KEY)); - } Serial.println("Connecting as wifi client..."); //fix for auto connect racing issue @@ -129,7 +129,14 @@ uint8_t WiFiManager::waitForConnectResult() { keepConnecting = false; } - delay(100); + if(ShelfDisplays != nullptr) + { + ShelfDisplays->delay(100); + } + else + { + delay(100); + } } return status; } @@ -236,10 +243,11 @@ bool WiFiManager::startConfigPortal() { Serial.println("Failed to connect."); } - else + else //connected { - //connected + //TODO: Display the new IP in the UI and wait for user confirmation before switching off the fallback AP WiFi.mode(WIFI_STA); + reconnectEvent = WiFi.onEvent(std::bind(&WiFiManager::tryToReconnect, this, std::placeholders::_1, std::placeholders::_2), SYSTEM_EVENT_STA_DISCONNECTED); stopAnimation(); break; } @@ -253,6 +261,15 @@ bool WiFiManager::startConfigPortal() return WiFi.status() == WL_CONNECTED; } +void WiFiManager::tryToReconnect(WiFiEvent_t event, WiFiEventInfo_t info) +{ + Serial.print("WiFi lost connection. Reason: "); + Serial.println(info.disconnected.reason); + Serial.println("Trying to Reconnect"); + WiFi.disconnect(); + connectWifi("",""); +} + /** Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */ bool WiFiManager::captivePortal(AsyncWebServerRequest *request) { @@ -342,6 +359,7 @@ void WiFiManager::startWebUI() { JsonObject temp = json.as(); request->send(200, "text/plain", saveSettings(ConfigManager::BASE_CONFIG, &temp)); + config->restartIfRequired(); }); server->addHandler(baseConfigHandler); @@ -349,6 +367,7 @@ void WiFiManager::startWebUI() { JsonObject temp = json.as(); request->send(200, "text/plain", saveSettings(ConfigManager::COLOR_CONFIG, &temp)); + config->restartIfRequired(); }); server->addHandler(colorConfigHandler); @@ -356,6 +375,7 @@ void WiFiManager::startWebUI() { JsonObject temp = json.as(); request->send(200, "text/plain", saveSettings(ConfigManager::HW_CONFIG, &temp)); + config->restartIfRequired(); }); server->addHandler(hwConfigHandler); diff --git a/lib/LED_clock/library_default.json b/lib/LED_clock/library_default.json index b060f18..346798a 100644 --- a/lib/LED_clock/library_default.json +++ b/lib/LED_clock/library_default.json @@ -20,8 +20,10 @@ "-I Modules/Animator/inc", "-I Modules/ClockState/inc", "-I Modules/DisplayManager/inc", + "-I Modules/Misc/inc", "-I Modules/SevenSegment/inc", "-I Modules/TimeManager/inc", + "-I Modules/ConfigManager/inc", "-I Modules/WiFiManager/inc", "-I Config/Setup/default", "-I Config/Blynk/default", diff --git a/src/main.cpp b/src/main.cpp index 20656e8..ce8ecc3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -94,7 +94,6 @@ void setup() Serial.println("Initializing modules..."); ShelfDisplays = DisplayManager::getInstance(); BlynkConfiguration = BlynkConfig::getInstance(); - timeM = TimeManager::getInstance(); states = ClockState::getInstance(); WIFIManager = WiFiManager::getInstance(); @@ -114,8 +113,7 @@ void setup() BlynkConfiguration->setup(); - // startWebServer(); - + timeM = TimeManager::getInstance(); Serial.println("Fetching time from NTP server..."); if(timeM->init() == false) {