diff --git a/ESP source.code-workspace b/ESP source.code-workspace
new file mode 100644
index 0000000..1c9b3a9
--- /dev/null
+++ b/ESP source.code-workspace
@@ -0,0 +1,63 @@
+{
+ "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",
+ "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
+ }
+}
\ No newline at end of file
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/.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/WebUI.code-workspace b/WebConfigUI/WebUI.code-workspace
new file mode 100644
index 0000000..df281b1
--- /dev/null
+++ b/WebConfigUI/WebUI.code-workspace
@@ -0,0 +1,13 @@
+{
+ "folders": [
+ {
+ "path": ".",
+ "name": "Web UI"
+ }
+ ],
+ "settings": {
+ "editor.detectIndentation": false,
+ "editor.insertSpaces": false,
+ "editor.tabSize": 2
+ }
+}
\ No newline at end of file
diff --git a/WebConfigUI/babel.config.json b/WebConfigUI/babel.config.json
new file mode 100644
index 0000000..b5c4c08
--- /dev/null
+++ b/WebConfigUI/babel.config.json
@@ -0,0 +1,4 @@
+{
+ "presets": ["@babel/preset-env", "@babel/preset-react"],
+ "plugins": ["@babel/plugin-transform-runtime"]
+}
diff --git a/WebConfigUI/index.html b/WebConfigUI/index.html
new file mode 100644
index 0000000..b037a25
--- /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..e6bf72c
--- /dev/null
+++ b/WebConfigUI/package.json
@@ -0,0 +1,43 @@
+{
+ "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/plugin-transform-runtime": "^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": {
+ "@heroicons/react": "^1.0.1",
+ "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
new file mode 100644
index 0000000..d64cc37
--- /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..dcbfcab
--- /dev/null
+++ b/WebConfigUI/src/App.jsx
@@ -0,0 +1,81 @@
+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 Dialog from './components/Dialog';
+
+export default function App() {
+ 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) => {
+ 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)
+ 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 putting config file: " + response.statusText);
+ return false;
+ }
+ setSaveResponse(await response.text());
+ setDialogShown(true);
+ return true;
+ }
+
+
+ const handleSubmit = (domain, values, setter) => {
+ putConfigFile(domain, values);
+ if(setter != null)
+ {
+ getConfigFile(domain + '.json', setter);
+ }
+ }
+
+ return (
+
+
+
+ } />
+
+ handleSubmit(d, v, null)}/>} />
+
+ getConfigFile('UI_definitions.json', setter)} getValues={(setter)=>getConfigFile('BaseConfig.json', setter)}>
+ General Settings
+ } />
+
+ getConfigFile('UI_definitions.json', setter)} getValues={(setter)=>getConfigFile('ColorConfig.json', setter)}>
+ Color Settings
+ } />
+
+ getConfigFile('UI_definitions.json', setter)} getValues={(setter)=>getConfigFile('HWConfig.json', setter)}>
+ Hardware Settings
+ } />
+
+
+
+
+ );
+}
diff --git a/WebConfigUI/src/components/Dialog.jsx b/WebConfigUI/src/components/Dialog.jsx
new file mode 100644
index 0000000..9e3daa8
--- /dev/null
+++ b/WebConfigUI/src/components/Dialog.jsx
@@ -0,0 +1,87 @@
+import { Dialog, Transition } from "@headlessui/react";
+import React, { Fragment, useRef} from "react";
+
+export default function MyModal(props) {
+ let OkayButtonRef = useRef();
+
+ props.type == "OK/Cancel"
+
+ let cancelButton = ""
+ let okayButtonLabel = "OK"
+
+ if(props.confirmMessage)
+ {
+ okayButtonLabel = props.confirmMessage
+ }
+
+ if(props.type == "OK/Cancel")
+ {
+ cancelButton = (
+
+ )
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/WebConfigUI/src/components/SettingsForm.jsx b/WebConfigUI/src/components/SettingsForm.jsx
new file mode 100644
index 0000000..df3a3a9
--- /dev/null
+++ b/WebConfigUI/src/components/SettingsForm.jsx
@@ -0,0 +1,271 @@
+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) => {
+ const settings = [...UIDefinition];
+ UIDefinition.forEach((element, index) => {
+ settings[index].value = InitialValues[element.target];
+ });
+ return settings;
+}
+
+const SettingsForm = (props) => {
+ 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);
+ }
+
+ const handleDialogClose = (result) => {
+ setDialogShown(false);
+ if(result == true)
+ {
+ let results = {};
+ 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 newValue = event.target.value;
+ switch(setting.type)
+ {
+ case "bool":
+ newSettingValue[target] = event.target.checked;
+ break;
+ 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] = parseInt(newValue, 10);
+ 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:
+ newSettingValue[target] = newValue;
+ break;
+ }
+ setValues(newSettingValue);
+ }
+
+ 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 Values[onlyIfTarget];
+ }
+ else
+ {
+ 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) => {
+ 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) => {
+ const 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":
+ let rgbColor = parseRGBColor(settingEntry.target);
+ return (
+
+
+
+
+ )
+ 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 (
+
+
+
+
+
+ );
+}
+
+export default SettingsForm;
diff --git a/WebConfigUI/src/css/tailwind.css b/WebConfigUI/src/css/tailwind.css
new file mode 100644
index 0000000..f9ea511
--- /dev/null
+++ b/WebConfigUI/src/css/tailwind.css
@@ -0,0 +1,58 @@
+@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;
+ }
+}
+
+@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
+ }
+
+ [type='range'] {
+ @apply appearance-none w-full h-6 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
new file mode 100644
index 0000000..65f5742
--- /dev/null
+++ b/WebConfigUI/src/data/BaseConfig.json
@@ -0,0 +1,27 @@
+{
+ "ENABLE_BLYNK": true,
+ "BLYNK_AUTH_TOKEN": "AUTH_TOKEN_GOES_HERE",
+ "BLYNK_SERVER": "blynk-cloud.com",
+ "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/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..b8a8f78
--- /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.json b/WebConfigUI/src/data/UI_definitions.json
new file mode 100644
index 0000000..2f51c4b
--- /dev/null
+++ b/WebConfigUI/src/data/UI_definitions.json
@@ -0,0 +1,393 @@
+{
+ "BaseConfig": [
+ {
+ "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": "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
+ }
+ ],
+ "ColorConfig": [
+ {
+ "label": "Color of the hour segments",
+ "type": "color",
+ "target": "HOUR",
+ "min": 0,
+ "max": 16777215
+ },
+ {
+ "label": "Color of the minute segments",
+ "type": "color",
+ "target": "MINUTE",
+ "min": 0,
+ "max": 16777215
+ },
+ {
+ "label": "Color of the internal LEDs",
+ "type": "color",
+ "target": "INTERNAL",
+ "min": 0,
+ "max": 16777215
+ },
+ {
+ "label": "Color of the separation dot LEDs",
+ "type": "color",
+ "target": "SEPARATION_DOT",
+ "min": 0,
+ "max": 16777215
+ },
+ {
+ "label": "Color of the LEDs for the OTA update progress bar",
+ "type": "color",
+ "target": "OTA_UPDATE",
+ "min": 0,
+ "max": 16777215
+ },
+ {
+ "label": "Color of the LEDs while searching for a WIFI network",
+ "type": "color",
+ "target": "WIFI_CONNECTING",
+ "min": 0,
+ "max": 16777215
+ },
+ {
+ "label": "Color of the LEDs signaling a successful WIFI connection",
+ "type": "color",
+ "target": "WIFI_CONNECTION_SUCCESSFUL",
+ "min": 0,
+ "max": 16777215
+ },
+ {
+ "label": "Color of the LEDs if system is waiting for WIFI smart config",
+ "type": "color",
+ "target": "WIFI_SMART_CONFIG",
+ "min": 0,
+ "max": 16777215
+ },
+ {
+ "label": "Color of the LEDs signaling an error of some sort",
+ "type": "color",
+ "target": "ERROR",
+ "min": 0,
+ "max": 16777215
+ }
+ ],
+ "HWConfig": [
+ {
+ "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": "dropdown",
+ "target": "DISPLAY_FOR_SEPARATION_DOT",
+ "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": "ListSelection",
+ "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"}
+ ],
+ "id": "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/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..4233c45
--- /dev/null
+++ b/WebConfigUI/src/sites/Navigation.jsx
@@ -0,0 +1,39 @@
+import { Link, withRouter } from "react-router-dom";
+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"},
+];
+
+function NavigationMenu(props) {
+ return (
+
+
LED Pixel Clock
+
+
+
+
+ );
+}
+
+export default withRouter(NavigationMenu);
diff --git a/WebConfigUI/src/sites/Settings.jsx b/WebConfigUI/src/sites/Settings.jsx
new file mode 100644
index 0000000..f429b73
--- /dev/null
+++ b/WebConfigUI/src/sites/Settings.jsx
@@ -0,0 +1,17 @@
+import React, { useEffect, useState } from "react";
+import SettingsForm from '../components/SettingsForm'
+
+const Settings = (props) => {
+ return (
+
+
+
+
{props.children}
+
+
+
+
+ );
+}
+
+export default Settings;
diff --git a/WebConfigUI/src/sites/WIFI-setup.jsx b/WebConfigUI/src/sites/WIFI-setup.jsx
new file mode 100644
index 0000000..cbe3791
--- /dev/null
+++ b/WebConfigUI/src/sites/WIFI-setup.jsx
@@ -0,0 +1,75 @@
+import React, { useState } from 'react';
+import Dialog from '../components/Dialog'
+
+const WIFI_new = (props) => {
+ 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();
+ setDialogShown(true);
+ }
+
+ const handleDialogClose = (result) => {
+ setDialogShown(false);
+ if(result == true)
+ {
+ setSSID("");
+ setPW("");
+ setCreateAP(false);
+ props.onSubmit(props.domain, JSON.stringify({SSID: SSID, PW: PW, CreateAP: createAP}));
+ }
+ }
+
+ return (
+
+
+
+
Connect to your WIFI
+
+
+
+
+
+ )
+}
+
+export default WIFI_new;
diff --git a/WebConfigUI/tailwind.config.js b/WebConfigUI/tailwind.config.js
new file mode 100644
index 0000000..9057575
--- /dev/null
+++ b/WebConfigUI/tailwind.config.js
@@ -0,0 +1,15 @@
+module.exports = {
+ 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')],
+};
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/WebConfigUI/webpack.config.js b/WebConfigUI/webpack.config.js
new file mode 100644
index 0000000..30a3c65
--- /dev/null
+++ b/WebConfigUI/webpack.config.js
@@ -0,0 +1,49 @@
+const path = require("path");
+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'],
+ }
+};
diff --git a/data/BaseConfig.json b/data/BaseConfig.json
new file mode 100644
index 0000000..65f5742
--- /dev/null
+++ b/data/BaseConfig.json
@@ -0,0 +1,27 @@
+{
+ "ENABLE_BLYNK": true,
+ "BLYNK_AUTH_TOKEN": "AUTH_TOKEN_GOES_HERE",
+ "BLYNK_SERVER": "blynk-cloud.com",
+ "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/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/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
+}
diff --git a/data/UI_definitions.json b/data/UI_definitions.json
new file mode 100644
index 0000000..6d9eda3
--- /dev/null
+++ b/data/UI_definitions.json
@@ -0,0 +1,379 @@
+{
+ "BaseConfig": [
+ {
+ "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": "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
+ }
+ ],
+ "ColorConfig": [
+ {
+ "label": "Color of the hour segments",
+ "type": "color",
+ "target": "HOUR",
+ "min": 0,
+ "max": 16777215
+ },
+ {
+ "label": "Color of the minute segments",
+ "type": "color",
+ "target": "MINUTE",
+ "min": 0,
+ "max": 16777215
+ },
+ {
+ "label": "Color of the internal LEDs",
+ "type": "color",
+ "target": "INTERNAL",
+ "min": 0,
+ "max": 16777215
+ },
+ {
+ "label": "Color of the separation dot LEDs",
+ "type": "color",
+ "target": "SEPARATION_DOT",
+ "min": 0,
+ "max": 16777215
+ },
+ {
+ "label": "Color of the LEDs for the OTA update progress bar",
+ "type": "color",
+ "target": "OTA_UPDATE",
+ "min": 0,
+ "max": 16777215
+ },
+ {
+ "label": "Color of the LEDs while searching for a WIFI network",
+ "type": "color",
+ "target": "WIFI_CONNECTING",
+ "min": 0,
+ "max": 16777215
+ },
+ {
+ "label": "Color of the LEDs signaling a successful WIFI connection",
+ "type": "color",
+ "target": "WIFI_CONNECTION_SUCCESSFUL",
+ "min": 0,
+ "max": 16777215
+ },
+ {
+ "label": "Color of the LEDs if system is waiting for WIFI smart config",
+ "type": "color",
+ "target": "WIFI_SMART_CONFIG",
+ "min": 0,
+ "max": 16777215
+ },
+ {
+ "label": "Color of the LEDs signaling an error of some sort",
+ "type": "color",
+ "target": "ERROR",
+ "min": 0,
+ "max": 16777215
+ }
+ ],
+ "HWConfig": [
+ {
+ "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
new file mode 100644
index 0000000..c8d2b2b
--- /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/Blynk/default/BlynkConfig.cpp b/lib/LED_clock/Config/Blynk/default/BlynkConfig.cpp
index 47202d3..f9c2aa5 100644
--- a/lib/LED_clock/Config/Blynk/default/BlynkConfig.cpp
+++ b/lib/LED_clock/Config/Blynk/default/BlynkConfig.cpp
@@ -6,402 +6,433 @@
#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();
+bool 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);
- }
+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));
+ return SYSTEM_RESTART_REQUIRED;
+}
- /**
- * \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 4f6a255..3e16d70 100644
--- a/lib/LED_clock/Config/Setup/clock-only/Configuration.h
+++ b/lib/LED_clock/Config/Setup/clock-only/Configuration.h
@@ -9,80 +9,51 @@
#ifndef _CONFIGURATIONS_H_
#define _CONFIGURATIONS_H_
+//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:
+
/**
* \addtogroup MainConfiguration
* \brief Main configuration settings
* \{
*/
+
/**
- * \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"
-
- /**
- * \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
+#define BLYNK_PRINT Serial
- /**
- * \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..51cfc2b 100644
--- a/lib/LED_clock/Config/Setup/default/Configuration.h
+++ b/lib/LED_clock/Config/Setup/default/Configuration.h
@@ -9,80 +9,49 @@
#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
* \{
*/
+
/**
- * \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"
-
- /**
- * \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
+#define BLYNK_PRINT Serial
- /**
- * \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/Config/Transitions/default/SegmentTransitions.cpp b/lib/LED_clock/Config/Transitions/default/SegmentTransitions.cpp
index 99ce914..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
@@ -111,7 +145,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 +173,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 +194,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 +213,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 +232,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 +251,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 +270,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 +289,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 +316,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 +342,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 +362,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 +381,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 +400,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 +419,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 +438,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 +461,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 +485,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 +505,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 +524,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 +543,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 +562,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 +589,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 +614,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 +634,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 +653,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/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/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 055b80f..22f9058 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 {
@@ -60,11 +60,12 @@ class Animator
uint16_t counter;
AnimatableObject** objects;
bool running;
+ AnimatableObject::AnimationCallBack complexAnimationFinishedCallBack;
};
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/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 941789d..9e2ea4c 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;
}
@@ -153,6 +154,10 @@ void Animator::animationIterationDoneCallback(AnimatableObject* sourceObject)
currentObject->complexAnimationInst = nullptr;
}
}
+ if(currentAnimation->complexAnimationFinishedCallBack != nullptr)
+ {
+ currentAnimation->complexAnimationFinishedCallBack();
+ }
delete currentAnimation;
}
}
@@ -167,6 +172,11 @@ void Animator::startAnimationStep(uint16_t stepindex, ComplexAnimationInstance*
}
animationStep* StepToStart = animationInst->animation->animations->get(stepindex);
+ if(StepToStart == nullptr)
+ {
+ Serial.printf("[E] Complex animation stepindex was invalid. Animation step %d was not started\n\r", stepindex);
+ return;
+ }
bool hasCallbacks = false;
bool wasEmpty = true;
AnimatableObject* currentObject;
@@ -175,18 +185,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)
@@ -257,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/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
new file mode 100644
index 0000000..d33f850
--- /dev/null
+++ b/lib/LED_clock/Modules/ConfigManager/inc/ConfigManager.h
@@ -0,0 +1,142 @@
+/**
+ * \file ConfigManager.h
+ * \author Florian Laschober
+ * \brief Config manager class definition
+ */
+
+#ifndef __CONFIG_MANAGER_H_
+#define __CONFIG_MANAGER_H_
+
+#include
+#include
+#include
+#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 DIGIT_ANIMATION_SPEED_KEY "DIGIT_ANIMATION_SPEED"
+
+#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"
+
+/** \} */
+
+// 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
+ */
+class ConfigManager
+{
+public:
+ typedef bool (*propertyChangedCallback)(ConfigManager* config);
+ enum ConfigType {BASE_CONFIG, COLOR_CONFIG, HW_CONFIG, _CONFIG_TYPE_LENGTH};
+private:
+ struct changedEventCallback {
+ propertyChangedCallback callback;
+ 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;
+ DynamicJsonDocument* BaseConfig;
+ DynamicJsonDocument* ColorConfig;
+ DynamicJsonDocument* HWConfig;
+ DynamicJsonDocument* EventIDs;
+ bool restartRequested;
+
+ 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);
+
+ template
+ 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);
+ bool isSameValueType(JsonVariant type1, JsonVariant type2);
+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
+ */
+ ~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();
+
+ template
+ T getProperty(ConfigType config, const char* key);
+
+ template
+ bool setProperty(ConfigType config, char* key, T value);
+
+ bool setProperty(JsonPair& keyValuePair, ConfigType config);
+
+ void saveConfigPersistent(ConfigType config);
+ void applyChanges();
+ void applySaveAndRestart(ConfigType config);
+ void restartIfRequired();
+
+ 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
new file mode 100644
index 0000000..8576f21
--- /dev/null
+++ b/lib/LED_clock/Modules/ConfigManager/src/ConfigManager.cpp
@@ -0,0 +1,401 @@
+/**
+ * \file ConfigManager.cpp
+ * \author Florian Laschober
+ * \brief Implementation of the ConfigManager class member functions
+ */
+
+
+#include "ConfigManager.h"
+
+ConfigManager* ConfigManager::instance = nullptr;
+
+//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()
+{
+ restartRequested = false;
+ 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());
+
+ 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()
+{
+ instance = nullptr;
+}
+
+ConfigManager* ConfigManager::getInstance()
+{
+ if(instance == nullptr)
+ {
+ instance = new ConfigManager();
+ Serial.println("[D]: ConfigManager instance created");
+ }
+ 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;
+}
+
+bool ConfigManager::deserializeDynamically(fs::File &input, DynamicJsonDocument* doc)
+{
+ if(doc == nullptr)
+ {
+ return false;
+ }
+ bool successful = false;
+ bool firstTry = true;
+ size_t DocumentSize = (*doc).size();
+
+ do
+ {
+ if(firstTry == true)
+ {
+ firstTry = false;
+ }
+ else
+ {
+ doc = new DynamicJsonDocument(DocumentSize);
+ }
+
+ DeserializationError error = deserializeJson(*doc, input);
+ if (error)
+ {
+ if (error.code() == DeserializationError::NoMemory)
+ {
+ delete doc;
+ 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);
+
+ return successful;
+}
+
+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(filename))
+ {
+ File jsonFile = SPIFFS.open(filename, "r");
+ if (!jsonFile)
+ {
+ Serial.println("E: Open config file failed");
+ }
+ else
+ {
+ if(deserializeDynamically(jsonFile, doc) == 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", filename);
+ loadOK = false;
+ }
+ return loadOK;
+}
+
+
+template
+T ConfigManager::getProperty(DynamicJsonDocument* root, const char* key)
+{
+ JsonVariant jvalue = (*root)[key];
+ if (!jvalue.isNull())
+ {
+ if(!jvalue.is())
+ {
+ Serial.printf("[E]: BaseConfig key \"%s\" is not the correct type\n\r", key);
+ }
+ else
+ {
+ return jvalue.as();
+ }
+ }
+ return T();
+}
+
+template
+T ConfigManager::getProperty(ConfigType config, const char* key)
+{
+ return getProperty(getDocument(config), key);
+}
+
+template
+bool ConfigManager::setProperty(ConfigType config, char* key, T value)
+{
+ 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;
+}
+
+DynamicJsonDocument* ConfigManager::getDocument(ConfigType config)
+{
+ switch (config)
+ {
+ case BASE_CONFIG:
+ return BaseConfig;
+ case COLOR_CONFIG:
+ return ColorConfig;
+ case HW_CONFIG:
+ return HWConfig;
+ default:
+ return nullptr;
+ }
+}
+
+const char* ConfigManager::getFileName(ConfigType config)
+{
+ switch (config)
+ {
+ 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;
+ }
+}
+
+bool ConfigManager::setProperty(JsonPair& keyValuePair, ConfigType config)
+{
+ if(config >= _CONFIG_TYPE_LENGTH)
+ {
+ 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())
+ {
+ 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
+ {
+ return true;
+ }
+ }
+ Serial.printf("[E]: Config does not contain key %s\n\r", keyValuePair.key().c_str());
+ 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)
+ {
+ 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::applySaveAndRestart(ConfigType config)
+{
+ applyChanges();
+ saveConfigPersistent(config);
+ restartIfRequired();
+}
+
+void ConfigManager::restartIfRequired()
+{
+ if(restartRequested == true)
+ {
+ ESP.restart();
+ }
+}
+
+void ConfigManager::checkCallbacks(uint8_t ConfigEvent)
+{
+ bool needsRestart = false;
+ for (int i = 0; i < eventCallbacks.size(); i++)
+ {
+ if((eventCallbacks.get(i)->configEvent & ConfigEvent) != 0x00)
+ {
+ needsRestart |= eventCallbacks.get(i)->callback(this);
+ }
+ }
+ if(needsRestart == true)
+ {
+ restartRequested = true;
+ Serial.println("System restart was requested due to config change.");
+ }
+}
+
+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/inc/DisplayManager.h b/lib/LED_clock/Modules/DisplayManager/inc/DisplayManager.h
index b7ae746..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];
@@ -59,7 +61,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 +69,7 @@ class DisplayManager
#endif
#if ENABLE_LIGHT_SENSOR == true
- LinkedList lightSensorMeasurements;
+ DynamicList lightSensorMeasurements;
uint64_t lastSensorMeasurement;
uint8_t lightSensorBrightness;
void takeBrightnessMeasurement();
@@ -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 f5d4cdc..870a619 100644
--- a/lib/LED_clock/Modules/DisplayManager/src/DisplayManager.cpp
+++ b/lib/LED_clock/Modules/DisplayManager/src/DisplayManager.cpp
@@ -8,10 +8,13 @@
#include "DisplayManager.h"
DisplayManager* DisplayManager::instance = nullptr;
-LinkedList* DisplayManager::SegmentIndexErrorList = 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,11 +64,18 @@ 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)
{
instance = new DisplayManager();
+ Serial.println("[D]: DisplayManager instance created");
}
return instance;
}
@@ -106,7 +118,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++)
{
@@ -146,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)
@@ -397,9 +410,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 +427,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/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/lib/LED_clock/Modules/TimeManager/inc/TimeManager.h b/lib/LED_clock/Modules/TimeManager/inc/TimeManager.h
index 353e3da..4544ebc 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
@@ -55,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 bbca0c7..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()
@@ -43,15 +44,14 @@ TimeManager* TimeManager::getInstance()
if(TimeManagerSingelton == nullptr)
{
TimeManagerSingelton = new TimeManager();
+ Serial.println("[D]: TimeManager instance created");
}
return TimeManagerSingelton;
}
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);
@@ -251,9 +251,11 @@ 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(timeM->offlineTimeCounter++ >= TIME_SYNC_INTERVALL && WiFi.status() == WL_CONNECTED)
+ if(!config->getProperty(ConfigManager::HW_CONFIG, DEMO_MODE_KEY))
+ {
+ if(timeM->offlineTimeCounter++ >= TIME_SYNC_INTERVALL)
{
if(timeM->synchronize() == true)
{
@@ -307,7 +309,18 @@ void IRAM_ATTR onTimer()
timeM->AlarmCleared = false;
}
}
- #else
+ //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
+ {
//DEMO CODE: Useful for testing animations
timeM->currentTime.minutes++;
if(timeM->currentTime.minutes > 59)
@@ -319,5 +332,5 @@ void IRAM_ATTR onTimer()
{
timeM->currentTime.hours = 0;
}
- #endif
+ }
}
\ No newline at end of file
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..40e1cf4
--- /dev/null
+++ b/lib/LED_clock/Modules/WiFiManager/inc/WiFiManager.h
@@ -0,0 +1,66 @@
+/**
+ * \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"
+#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 30000
+
+class WiFiManager
+{
+private:
+ AsyncWebServer* server;
+ DNSServer* dnsServer;
+ ConfigManager* config;
+ DisplayManager* ShelfDisplays;
+
+ bool connect;
+ String WIFI_SSID;
+ String WIFI_PW;
+ wifi_event_id_t reconnectEvent;
+
+ 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();
+ 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);
+ void tryToReconnect(WiFiEvent_t event, WiFiEventInfo_t info);
+public:
+ static WiFiManager* getInstance();
+ ~WiFiManager();
+ bool autoConnect();
+ bool autoConnect(DisplayManager* display);
+ void startWebUI();
+};
+
+
+
+#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..748b85f
--- /dev/null
+++ b/lib/LED_clock/Modules/WiFiManager/src/WiFiManager.cpp
@@ -0,0 +1,387 @@
+#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();
+ ShelfDisplays = nullptr;
+}
+
+WiFiManager::~WiFiManager()
+{
+ delete server;
+ 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);
+
+ 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
+ stopAnimation();
+ 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;
+ }
+ if(ShelfDisplays != nullptr)
+ {
+ ShelfDisplays->delay(100);
+ }
+ else
+ {
+ 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