diff --git a/config/configNormal.ini b/config/configNormal.ini
index 264ecd5c..9982b4ad 100644
--- a/config/configNormal.ini
+++ b/config/configNormal.ini
@@ -13,6 +13,7 @@ lib_deps =
TopicHandlerService @ ~0.1.0 # Mandatory, can not be removed.
FileMgrService @ ~0.1.0 # Mandatory, can not be removed.
AudioService @ ~0.1.0
+ TimerService @ ~0.1.0
# ********** Topic handlers **********
RestApiTopicHandler @ ~0.1.0 # Mandatory, can not be removed. Used by webinterface.
MqttApiTopicHandler @ ~0.1.0 # Requires MqttService
diff --git a/config/configSmall.ini b/config/configSmall.ini
index c69e6c35..152811cc 100644
--- a/config/configSmall.ini
+++ b/config/configSmall.ini
@@ -13,6 +13,7 @@ lib_deps =
TopicHandlerService @ ~0.1.0 # Mandatory, can not be removed.
FileMgrService @ ~0.1.0 # Mandatory, can not be removed.
;AudioService @ ~0.1.0
+ ;TimerService @ ~0.1.0
# ********** Topic handlers **********
RestApiTopicHandler @ ~0.1.0 # Mandatory, can not be removed. Used by webinterface.
;MqttApiTopicHandler @ ~0.1.0 # Requires MqttService
diff --git a/config/configSmallNoI2s.ini b/config/configSmallNoI2s.ini
index e40fb047..e0471b89 100644
--- a/config/configSmallNoI2s.ini
+++ b/config/configSmallNoI2s.ini
@@ -13,6 +13,7 @@ lib_deps =
TopicHandlerService @ ~0.1.0 # Mandatory, can not be removed.
FileMgrService @ ~0.1.0 # Mandatory, can not be removed.
;AudioService @ ~0.1.0
+ ;TimerService @ ~0.1.0
# ********** Topic handlers **********
RestApiTopicHandler @ ~0.1.0 # Mandatory, can not be removed. Used by webinterface.
;MqttApiTopicHandler @ ~0.1.0 # Requires MqttService
diff --git a/config/configSmallUlanzi.ini b/config/configSmallUlanzi.ini
index f3cc5705..17ffa136 100644
--- a/config/configSmallUlanzi.ini
+++ b/config/configSmallUlanzi.ini
@@ -13,6 +13,7 @@ lib_deps =
TopicHandlerService @ ~0.1.0 # Mandatory, can not be removed.
FileMgrService @ ~0.1.0 # Mandatory, can not be removed.
;AudioService @ ~0.1.0
+ ;TimerService @ ~0.1.0
# ********** Topic handlers **********
RestApiTopicHandler @ ~0.1.0 # Mandatory, can not be removed. Used by webinterface.
MqttApiTopicHandler @ ~0.1.0 # Requires MqttService
diff --git a/config/configTiny.ini b/config/configTiny.ini
index f7d05d35..dd4df579 100644
--- a/config/configTiny.ini
+++ b/config/configTiny.ini
@@ -13,6 +13,7 @@ lib_deps =
;MqttService @ ~0.1.0
TopicHandlerService @ ~0.1.0 # Mandatory, can not be removed.
;AudioService @ ~0.1.0
+ ;TimerService @ ~0.1.0
# ********** Topic handlers **********
RestApiTopicHandler @ ~0.1.0 # Mandatory, can not be removed. Used by webinterface.
;MqttApiTopicHandler @ ~0.1.0 # Requires MqttService
diff --git a/data/debug.html b/data/debug.html
index 634314d4..490a1a01 100644
--- a/data/debug.html
+++ b/data/debug.html
@@ -122,6 +122,7 @@
Debug
+
@@ -301,6 +302,7 @@ Debug
/* Execute after page is ready. */
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
$("#buttonInfo").click(function(e) {
diff --git a/data/display.html b/data/display.html
index bb5c4983..8fd874e9 100644
--- a/data/display.html
+++ b/data/display.html
@@ -128,6 +128,7 @@ Display
+
@@ -769,6 +770,7 @@ Display
ctx = displayCanvas.getContext("2d");
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
/* Connect to pixelix */
diff --git a/data/edit.html b/data/edit.html
index 1544d9a2..8f97298b 100644
--- a/data/edit.html
+++ b/data/edit.html
@@ -110,6 +110,7 @@ Files
+
@@ -391,6 +392,7 @@ Files
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
$("#uploadFile").on("change", function() {
diff --git a/data/error.html b/data/error.html
index 0199906e..5940901f 100644
--- a/data/error.html
+++ b/data/error.html
@@ -54,10 +54,12 @@ Error
+
diff --git a/data/icons.html b/data/icons.html
index 47c517df..92f44e6a 100644
--- a/data/icons.html
+++ b/data/icons.html
@@ -91,6 +91,7 @@ Overview
+
@@ -226,6 +227,7 @@ Overview
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
downloadFileList("overview");
diff --git a/data/index.html b/data/index.html
index c50cdc48..0d27c2db 100644
--- a/data/index.html
+++ b/data/index.html
@@ -56,10 +56,12 @@ Welcome to
+
diff --git a/data/info.html b/data/info.html
index 5b702261..4595261a 100644
--- a/data/info.html
+++ b/data/info.html
@@ -253,6 +253,7 @@ Filesystem
+
@@ -307,6 +308,7 @@ Filesystem
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
/* Draw the chart, showing the current situation on the heap. */
diff --git a/data/js/menu.js b/data/js/menu.js
index 7ad60b91..152e0aa2 100644
--- a/data/js/menu.js
+++ b/data/js/menu.js
@@ -13,6 +13,10 @@ menu.data = [{
}, {
"title": "Icons",
"hyperRef": "/icons.html"
+}, {
+ "title": "Services",
+ "hyperRef": "#",
+ "subMenu": []
}, {
"title": "Settings",
"hyperRef": "/settings.html"
diff --git a/data/settings.html b/data/settings.html
index 70291472..042e6328 100644
--- a/data/settings.html
+++ b/data/settings.html
@@ -68,6 +68,7 @@ Settings
+
@@ -101,6 +102,7 @@ Settings
var index = 0;
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
restClient.getSettingKeys().then(function(result) {
diff --git a/data/update.html b/data/update.html
index efca4b69..f3902796 100644
--- a/data/update.html
+++ b/data/update.html
@@ -108,6 +108,7 @@ Update
+
@@ -358,6 +359,7 @@ Update
/* Execute after page is ready. */
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
$("#inputFile").on("change", function() {
diff --git a/include/PluginList.h b/include/PluginList.h
index 9944c9cd..3a67d681 100644
--- a/include/PluginList.h
+++ b/include/PluginList.h
@@ -62,8 +62,8 @@ namespace PluginList
*/
typedef struct
{
- const char* name; /**< Name of plugin type. */
- IPluginMaintenance::CreateFunc createFunc; /**< Plugin creation function */
+ const char* name; /**< Name of plugin type. */
+ IPluginMaintenance::CreateFunc createFunc; /**< Plugin creation function */
} Element;
@@ -73,15 +73,15 @@ typedef struct
/**
* Get list of provided plugins and their creation function.
- *
+ *
* @param[out] length Length of list.
- *
+ *
* @return List of plugins.
*/
const Element* getList(uint8_t& length);
-};
+}; /* namespace PluginList */
-#endif /* PLUGIN_LIST_HPP */
+#endif /* PLUGIN_LIST_HPP */
/** @} */
\ No newline at end of file
diff --git a/include/Services.h b/include/Services.h
index 2b64323f..715684dc 100644
--- a/include/Services.h
+++ b/include/Services.h
@@ -27,7 +27,7 @@
/**
* @brief Services
* @author Andreas Merkle
- *
+ *
* @addtogroup service
*
* @{
@@ -59,13 +59,22 @@ namespace Services
* Types and Classes
*****************************************************************************/
+/**
+ * Service list element.
+ */
+typedef struct
+{
+ const char* name; /**< Name of service. */
+
+} Element;
+
/******************************************************************************
* Functions
*****************************************************************************/
/**
* Start all services.
- *
+ *
* @return If successful started, it will return true otherwise false.
*/
extern bool startAll();
@@ -80,8 +89,17 @@ extern void stopAll();
*/
extern void processAll();
-}
+/**
+ * Get list of provided plugins and their creation function.
+ *
+ * @param[out] length Length of list.
+ *
+ * @return List of plugins.
+ */
+const Element* getList(uint8_t& length);
+
+} /* namespace Services */
-#endif /* SERVICES_H */
+#endif /* SERVICES_H */
/** @} */
\ No newline at end of file
diff --git a/lib/BTCQuotePlugin/web/BTCQuotePlugin.html b/lib/BTCQuotePlugin/web/BTCQuotePlugin.html
index 7e44a67a..bcad0742 100644
--- a/lib/BTCQuotePlugin/web/BTCQuotePlugin.html
+++ b/lib/BTCQuotePlugin/web/BTCQuotePlugin.html
@@ -57,10 +57,12 @@ REST API
+
diff --git a/lib/BatteryPlugin/web/BatteryPlugin.html b/lib/BatteryPlugin/web/BatteryPlugin.html
index a39554cb..dd57a6f2 100644
--- a/lib/BatteryPlugin/web/BatteryPlugin.html
+++ b/lib/BatteryPlugin/web/BatteryPlugin.html
@@ -55,10 +55,12 @@ REST API
+
diff --git a/lib/CountdownPlugin/web/CountdownPlugin.html b/lib/CountdownPlugin/web/CountdownPlugin.html
index d5c52311..011ff4bb 100644
--- a/lib/CountdownPlugin/web/CountdownPlugin.html
+++ b/lib/CountdownPlugin/web/CountdownPlugin.html
@@ -97,6 +97,7 @@ Target Date
+
@@ -198,6 +199,7 @@ Target Date
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
utils.injectOrigin("injectOrigin", "{{ORIGIN}}");
diff --git a/lib/DDPPlugin/web/DDPPlugin.html b/lib/DDPPlugin/web/DDPPlugin.html
index f453e58f..313713e6 100644
--- a/lib/DDPPlugin/web/DDPPlugin.html
+++ b/lib/DDPPlugin/web/DDPPlugin.html
@@ -94,10 +94,12 @@ REST API
+
diff --git a/lib/DateTimePlugin/web/DateTimePlugin.html b/lib/DateTimePlugin/web/DateTimePlugin.html
index 7875ab30..c031b07d 100644
--- a/lib/DateTimePlugin/web/DateTimePlugin.html
+++ b/lib/DateTimePlugin/web/DateTimePlugin.html
@@ -216,6 +216,7 @@ Display
+
@@ -350,6 +351,7 @@ Display
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
utils.injectOrigin("injectOrigin", "{{ORIGIN}}");
diff --git a/lib/FirePlugin/web/FirePlugin.html b/lib/FirePlugin/web/FirePlugin.html
index 9c36982b..0089f616 100644
--- a/lib/FirePlugin/web/FirePlugin.html
+++ b/lib/FirePlugin/web/FirePlugin.html
@@ -55,10 +55,12 @@ REST API
+
diff --git a/lib/GameOfLifePlugin/web/GameOfLifePlugin.html b/lib/GameOfLifePlugin/web/GameOfLifePlugin.html
index c74c02e6..7791a6b8 100644
--- a/lib/GameOfLifePlugin/web/GameOfLifePlugin.html
+++ b/lib/GameOfLifePlugin/web/GameOfLifePlugin.html
@@ -55,10 +55,12 @@ REST API
+
diff --git a/lib/GrabViaMqttPlugin/web/GrabViaMqttPlugin.html b/lib/GrabViaMqttPlugin/web/GrabViaMqttPlugin.html
index d5813d7a..82e2350f 100644
--- a/lib/GrabViaMqttPlugin/web/GrabViaMqttPlugin.html
+++ b/lib/GrabViaMqttPlugin/web/GrabViaMqttPlugin.html
@@ -122,6 +122,7 @@ User
+
@@ -224,6 +225,7 @@ User
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
utils.injectOrigin("injectOrigin", "{{ORIGIN}}");
diff --git a/lib/GrabViaRestPlugin/web/GrabViaRestPlugin.html b/lib/GrabViaRestPlugin/web/GrabViaRestPlugin.html
index dda5e2ac..c736ff26 100644
--- a/lib/GrabViaRestPlugin/web/GrabViaRestPlugin.html
+++ b/lib/GrabViaRestPlugin/web/GrabViaRestPlugin.html
@@ -127,6 +127,7 @@ User
+
@@ -231,6 +232,7 @@ User
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
utils.injectOrigin("injectOrigin", "{{ORIGIN}}");
diff --git a/lib/GruenbeckPlugin/web/GruenbeckPlugin.html b/lib/GruenbeckPlugin/web/GruenbeckPlugin.html
index 374b03c7..a506bc48 100644
--- a/lib/GruenbeckPlugin/web/GruenbeckPlugin.html
+++ b/lib/GruenbeckPlugin/web/GruenbeckPlugin.html
@@ -81,6 +81,7 @@ IP-address
+
@@ -172,6 +173,7 @@ IP-address
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
utils.injectOrigin("injectOrigin", "{{ORIGIN}}");
diff --git a/lib/IconTextLampPlugin/web/IconTextLampPlugin.html b/lib/IconTextLampPlugin/web/IconTextLampPlugin.html
index 58f37e15..0a13e598 100644
--- a/lib/IconTextLampPlugin/web/IconTextLampPlugin.html
+++ b/lib/IconTextLampPlugin/web/IconTextLampPlugin.html
@@ -149,6 +149,7 @@ Lamp
+
@@ -315,6 +316,7 @@ Lamp
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
utils.injectOrigin("injectOrigin", "{{ORIGIN}}");
diff --git a/lib/IconTextPlugin/web/IconTextPlugin.html b/lib/IconTextPlugin/web/IconTextPlugin.html
index 37793193..e5b4ca1a 100644
--- a/lib/IconTextPlugin/web/IconTextPlugin.html
+++ b/lib/IconTextPlugin/web/IconTextPlugin.html
@@ -122,6 +122,7 @@ Text
+
@@ -245,6 +246,7 @@ Text
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
utils.injectOrigin("injectOrigin", "{{ORIGIN}}");
diff --git a/lib/JustTextPlugin/web/JustTextPlugin.html b/lib/JustTextPlugin/web/JustTextPlugin.html
index bfd7c606..151597ce 100644
--- a/lib/JustTextPlugin/web/JustTextPlugin.html
+++ b/lib/JustTextPlugin/web/JustTextPlugin.html
@@ -104,6 +104,7 @@ Text
+
@@ -225,6 +226,7 @@ Text
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
utils.injectOrigin("injectOrigin", "{{ORIGIN}}");
diff --git a/lib/MatrixPlugin/web/MatrixPlugin.html b/lib/MatrixPlugin/web/MatrixPlugin.html
index d6ac782e..a4df2893 100644
--- a/lib/MatrixPlugin/web/MatrixPlugin.html
+++ b/lib/MatrixPlugin/web/MatrixPlugin.html
@@ -55,10 +55,12 @@ REST API
+
diff --git a/lib/MqttApiTopicHandler/src/MqttApiTopicHandler.cpp b/lib/MqttApiTopicHandler/src/MqttApiTopicHandler.cpp
index 291a747f..b9ff0639 100644
--- a/lib/MqttApiTopicHandler/src/MqttApiTopicHandler.cpp
+++ b/lib/MqttApiTopicHandler/src/MqttApiTopicHandler.cpp
@@ -344,7 +344,7 @@ void MqttApiTopicHandler::publishTopicStatesOnDemand()
void MqttApiTopicHandler::write(const String& deviceId, const String& entityId, const String& topic, const uint8_t* payload, size_t size, SetTopicFunc setTopicFunc, UploadReqFunc uploadReqFunc)
{
- const size_t JSON_DOC_SIZE = 1024U;
+ const size_t JSON_DOC_SIZE = 4096U;
DynamicJsonDocument jsonDoc(JSON_DOC_SIZE);
DeserializationError error = deserializeJson(jsonDoc, payload, size);
@@ -436,7 +436,7 @@ void MqttApiTopicHandler::publish(const String& deviceId, const String& entityId
{
if (nullptr != getTopicFunc)
{
- const size_t JSON_DOC_SIZE = 1024U;
+ const size_t JSON_DOC_SIZE = 4096U;
DynamicJsonDocument jsonDoc(JSON_DOC_SIZE);
JsonObject jsonObj = jsonDoc.createNestedObject("data");
String mqttTopicNameBase = deviceId + "/" + entityId + topic;
diff --git a/lib/MultiIconPlugin/web/MultiIconPlugin.html b/lib/MultiIconPlugin/web/MultiIconPlugin.html
index f2043ad9..e8f1bb39 100644
--- a/lib/MultiIconPlugin/web/MultiIconPlugin.html
+++ b/lib/MultiIconPlugin/web/MultiIconPlugin.html
@@ -115,6 +115,7 @@ Slot icons
+
@@ -286,6 +287,7 @@ Slot icons
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
utils.injectOrigin("injectOrigin", "{{ORIGIN}}");
diff --git a/lib/OpenWeatherPlugin/web/OpenWeatherPlugin.html b/lib/OpenWeatherPlugin/web/OpenWeatherPlugin.html
index 7b54d878..b2e1b286 100644
--- a/lib/OpenWeatherPlugin/web/OpenWeatherPlugin.html
+++ b/lib/OpenWeatherPlugin/web/OpenWeatherPlugin.html
@@ -139,6 +139,7 @@ Configuration
+
@@ -282,6 +283,7 @@ Configuration
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
utils.injectOrigin("injectOrigin", "{{ORIGIN}}");
diff --git a/lib/RainbowPlugin/web/RainbowPlugin.html b/lib/RainbowPlugin/web/RainbowPlugin.html
index ff95f163..ba5a718b 100644
--- a/lib/RainbowPlugin/web/RainbowPlugin.html
+++ b/lib/RainbowPlugin/web/RainbowPlugin.html
@@ -55,10 +55,12 @@ REST API
+
diff --git a/lib/RestApiTopicHandler/src/RestApiTopicHandler.cpp b/lib/RestApiTopicHandler/src/RestApiTopicHandler.cpp
index a6d9b42e..80a56990 100644
--- a/lib/RestApiTopicHandler/src/RestApiTopicHandler.cpp
+++ b/lib/RestApiTopicHandler/src/RestApiTopicHandler.cpp
@@ -165,7 +165,7 @@ String RestApiTopicHandler::getBaseUri(const String& entityId)
void RestApiTopicHandler::webReqHandler(AsyncWebServerRequest *request, TopicMetaData* topicMetaData)
{
String content;
- const size_t JSON_DOC_SIZE = 2048U;
+ const size_t JSON_DOC_SIZE = 4096U;
DynamicJsonDocument jsonDoc(JSON_DOC_SIZE);
JsonObject dataObj = jsonDoc.createNestedObject("data");
uint32_t httpStatusCode = HttpStatus::STATUS_CODE_OK;
diff --git a/lib/SensorPlugin/web/SensorPlugin.html b/lib/SensorPlugin/web/SensorPlugin.html
index 2c8d45e8..47965492 100644
--- a/lib/SensorPlugin/web/SensorPlugin.html
+++ b/lib/SensorPlugin/web/SensorPlugin.html
@@ -86,6 +86,7 @@ Sensor and Channel
+
@@ -252,6 +253,7 @@ Sensor and Channel
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
utils.injectOrigin("injectOrigin", "{{ORIGIN}}");
diff --git a/lib/SignalDetectorPlugin/web/SignalDetectorPlugin.html b/lib/SignalDetectorPlugin/web/SignalDetectorPlugin.html
index d7b109bd..05faac33 100644
--- a/lib/SignalDetectorPlugin/web/SignalDetectorPlugin.html
+++ b/lib/SignalDetectorPlugin/web/SignalDetectorPlugin.html
@@ -127,6 +127,7 @@ Configuration
+
@@ -240,6 +241,7 @@ Configuration
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
utils.injectOrigin("injectOrigin", "{{ORIGIN}}");
diff --git a/lib/SoundReactivePlugin/web/SoundReactivePlugin.html b/lib/SoundReactivePlugin/web/SoundReactivePlugin.html
index b9652cfa..b5a1e81f 100644
--- a/lib/SoundReactivePlugin/web/SoundReactivePlugin.html
+++ b/lib/SoundReactivePlugin/web/SoundReactivePlugin.html
@@ -90,6 +90,7 @@ Number of frequency bands
+
@@ -180,6 +181,7 @@ Number of frequency bands
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
utils.injectOrigin("injectOrigin", "{{ORIGIN}}");
diff --git a/lib/SunrisePlugin/web/SunrisePlugin.html b/lib/SunrisePlugin/web/SunrisePlugin.html
index 408f11be..50a6b0d4 100644
--- a/lib/SunrisePlugin/web/SunrisePlugin.html
+++ b/lib/SunrisePlugin/web/SunrisePlugin.html
@@ -91,6 +91,7 @@ Location
+
@@ -184,6 +185,7 @@ Location
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
utils.injectOrigin("injectOrigin", "{{ORIGIN}}");
diff --git a/lib/SysMsgPlugin/web/SysMsgPlugin.html b/lib/SysMsgPlugin/web/SysMsgPlugin.html
index cec81a16..e660dbdf 100644
--- a/lib/SysMsgPlugin/web/SysMsgPlugin.html
+++ b/lib/SysMsgPlugin/web/SysMsgPlugin.html
@@ -55,10 +55,12 @@ REST API
+
diff --git a/lib/TempHumidPlugin/web/TempHumidPlugin.html b/lib/TempHumidPlugin/web/TempHumidPlugin.html
index a754152c..42e9f1d1 100644
--- a/lib/TempHumidPlugin/web/TempHumidPlugin.html
+++ b/lib/TempHumidPlugin/web/TempHumidPlugin.html
@@ -56,10 +56,12 @@ REST API
+
diff --git a/lib/TestPlugin/web/TestPlugin.html b/lib/TestPlugin/web/TestPlugin.html
index 07daa3a6..7377ad95 100644
--- a/lib/TestPlugin/web/TestPlugin.html
+++ b/lib/TestPlugin/web/TestPlugin.html
@@ -55,10 +55,12 @@ REST API
+
diff --git a/lib/TimerService/src/TimerService.cpp b/lib/TimerService/src/TimerService.cpp
new file mode 100644
index 00000000..a7375bba
--- /dev/null
+++ b/lib/TimerService/src/TimerService.cpp
@@ -0,0 +1,350 @@
+/* MIT License
+ *
+ * Copyright (c) 2019 - 2024 Andreas Merkle
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*******************************************************************************
+ DESCRIPTION
+*******************************************************************************/
+/**
+ * @brief Timer service
+ * @author Andreas Merkle
+ */
+
+/******************************************************************************
+ * Includes
+ *****************************************************************************/
+#include "TimerService.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/******************************************************************************
+ * Compiler Switches
+ *****************************************************************************/
+
+/******************************************************************************
+ * Macros
+ *****************************************************************************/
+
+/******************************************************************************
+ * Types and classes
+ *****************************************************************************/
+
+/******************************************************************************
+ * Prototypes
+ *****************************************************************************/
+
+/******************************************************************************
+ * Local Variables
+ *****************************************************************************/
+
+/* Initialize constant values. */
+const char* TimerService::FILE_NAME = "/configuration/timerService.json";
+const char* TimerService::TOPIC = "/timer";
+const char* TimerService::ENTITY = "timerService";
+
+/******************************************************************************
+ * Public Methods
+ *****************************************************************************/
+
+bool TimerService::start()
+{
+ bool isSuccessful = true;
+ SettingsService& settings = SettingsService::getInstance();
+ JsonObjectConst jsonExtra;
+ ITopicHandler::GetTopicFunc getTopicFunc =
+ [this](const String& topic, JsonObject& jsonValue) -> bool {
+ return this->getTopic(topic, jsonValue);
+ };
+ TopicHandlerService::HasChangedFunc hasChangedFunc =
+ [this](const String& topic) -> bool {
+ return this->hasTopicChanged(topic);
+ };
+ ITopicHandler::SetTopicFunc setTopicFunc =
+ [this](const String& topic, const JsonObjectConst& jsonValue) -> bool {
+ return this->setTopic(topic, jsonValue);
+ };
+
+ if (false == settings.open(true))
+ {
+ m_deviceId = settings.getHostname().getDefault();
+ }
+ else
+ {
+ m_deviceId = settings.getHostname().getValue();
+
+ settings.close();
+ }
+
+ if (false == loadSettings())
+ {
+ saveSettings();
+ }
+
+ TopicHandlerService::getInstance().registerTopic(m_deviceId, ENTITY, TOPIC, jsonExtra, getTopicFunc, hasChangedFunc, setTopicFunc, nullptr);
+
+ if (false == isSuccessful)
+ {
+ stop();
+ }
+ else
+ {
+ LOG_INFO("Timer service started.");
+ }
+
+ return isSuccessful;
+}
+
+void TimerService::stop()
+{
+ TopicHandlerService::getInstance().unregisterTopic(m_deviceId, ENTITY, TOPIC);
+
+ LOG_INFO("Timer service stopped.");
+}
+
+void TimerService::process()
+{
+ ClockDrv& clockDrv = ClockDrv::getInstance();
+ struct tm time;
+
+ if (true == clockDrv.getTime(time))
+ {
+ MutexGuard guard(m_mutex);
+ size_t idx;
+
+ for (idx = 0U; idx < MAX_TIMER_COUNT; ++idx)
+ {
+ if (true == m_settings[idx].isEnabled())
+ {
+ if (true == m_settings[idx].isSignalling(time))
+ {
+ TimerSetting::DisplayState displayState = m_settings[idx].getDisplayState();
+ int16_t brightness = m_settings[idx].getBrightness();
+
+ if (TimerSetting::DISPLAY_STATE_ON == displayState)
+ {
+ LOG_INFO("Timer %u is switching display on.", idx);
+
+ Display::getInstance().on();
+ }
+ else if (TimerSetting::DISPLAY_STATE_OFF == displayState)
+ {
+ LOG_INFO("Timer %u is switching display off.", idx);
+
+ Display::getInstance().off();
+ }
+ else
+ {
+ ;
+ }
+
+ if ((0 <= brightness) && (255 >= brightness))
+ {
+ LOG_INFO("Timer %u is setting brightness to %d.", idx, brightness);
+
+ DisplayMgr::getInstance().setBrightness(static_cast(brightness));
+ }
+ }
+ }
+ }
+ }
+}
+
+/******************************************************************************
+ * Protected Methods
+ *****************************************************************************/
+
+/******************************************************************************
+ * Private Methods
+ *****************************************************************************/
+
+void TimerService::clear()
+{
+ size_t idx;
+
+ for (idx = 0U; idx < MAX_TIMER_COUNT; ++idx)
+ {
+ m_settings[idx].clear();
+ }
+}
+
+bool TimerService::loadSettings()
+{
+ bool isSuccessful = false;
+ const size_t JSON_SIZE = 1024U;
+ DynamicJsonDocument jsonDoc(JSON_SIZE);
+ JsonFile jsonFile(FILESYSTEM);
+
+ if (false == jsonFile.load(FILE_NAME, jsonDoc))
+ {
+ LOG_WARNING("Failed to load timer settings.");
+ }
+ else
+ {
+ JsonVariantConst jsonTimerSettings = jsonDoc["timerSettings"];
+
+ if (false == jsonTimerSettings.is())
+ {
+ LOG_ERROR("No timer settings found.");
+ }
+ else
+ {
+ JsonArrayConst jsonTimerSettingsArray = jsonTimerSettings.as();
+ size_t idx = 0U;
+
+ clear();
+
+ for (JsonObjectConst jsonTimerSetting : jsonTimerSettingsArray)
+ {
+ if (false == m_settings[idx].fromJson(jsonTimerSetting))
+ {
+ LOG_WARNING("Failed to load timer setting %u.", idx);
+ }
+ else
+ {
+ ++idx;
+ }
+
+ if (MAX_TIMER_COUNT <= idx)
+ {
+ break;
+ }
+ }
+
+ m_hasSettingsChanged = true;
+
+ isSuccessful = true;
+ }
+ }
+
+ return isSuccessful;
+}
+
+bool TimerService::saveSettings()
+{
+ bool isSuccessful = false;
+ const size_t JSON_SIZE = 1024U;
+ DynamicJsonDocument jsonDoc(JSON_SIZE);
+ JsonArray jsonTimerSettings = jsonDoc.createNestedArray("timerSettings");
+ JsonFile jsonFile(FILESYSTEM);
+ size_t idx;
+
+ for (idx = 0U; idx < MAX_TIMER_COUNT; ++idx)
+ {
+ JsonObject jsonTimerSetting = jsonTimerSettings.createNestedObject();
+
+ m_settings[idx].toJson(jsonTimerSetting);
+ }
+
+ if (false == jsonFile.save(FILE_NAME, jsonDoc))
+ {
+ LOG_ERROR("Failed to save timer settings.");
+ }
+ else
+ {
+ isSuccessful = true;
+ }
+
+ return isSuccessful;
+}
+
+bool TimerService::getTopic(const String& topic, JsonObject& jsonValue)
+{
+ size_t idx;
+ JsonArray jsonTimerSettings = jsonValue.createNestedArray("timerSettings");
+ MutexGuard guard(m_mutex);
+
+ /* The callback is dedicated to a topic, therefore the
+ * topic parameter is not used.
+ */
+ UTIL_NOT_USED(topic);
+
+ for (idx = 0U; idx < MAX_TIMER_COUNT; ++idx)
+ {
+ JsonObject jsonTimerSetting = jsonTimerSettings.createNestedObject();
+
+ m_settings[idx].toJson(jsonTimerSetting);
+ }
+
+ return true;
+}
+
+bool TimerService::hasTopicChanged(const String& topic)
+{
+ bool hasChanged = m_hasSettingsChanged;
+
+ m_hasSettingsChanged = false;
+
+ return hasChanged;
+}
+
+bool TimerService::setTopic(const String& topic, const JsonObjectConst& jsonValue)
+{
+ bool isSuccessful = false;
+ size_t idx;
+ JsonVariantConst jsonTimerSettings = jsonValue["timerSettings"];
+ MutexGuard guard(m_mutex);
+
+ /* The callback is dedicated to a topic, therefore the
+ * topic parameter is not used.
+ */
+ UTIL_NOT_USED(topic);
+
+ if (true == jsonTimerSettings.is())
+ {
+ JsonArrayConst jsonTimerSettingsArray = jsonTimerSettings.as();
+ size_t count = (MAX_TIMER_COUNT >= jsonTimerSettingsArray.size()) ? jsonTimerSettingsArray.size() : MAX_TIMER_COUNT;
+
+ for (idx = 0U; idx < count; ++idx)
+ {
+ if (false == m_settings[idx].fromJson(jsonTimerSettingsArray[idx]))
+ {
+ LOG_WARNING("Failed to set timer setting %u.", idx);
+ }
+ }
+
+ m_hasSettingsChanged = true;
+ isSuccessful = true;
+ }
+
+ if (true == isSuccessful)
+ {
+ isSuccessful = saveSettings();
+ }
+
+ return isSuccessful;
+}
+
+/******************************************************************************
+ * External Functions
+ *****************************************************************************/
+
+/******************************************************************************
+ * Local Functions
+ *****************************************************************************/
diff --git a/lib/TimerService/src/TimerService.h b/lib/TimerService/src/TimerService.h
new file mode 100644
index 00000000..9c7df7b9
--- /dev/null
+++ b/lib/TimerService/src/TimerService.h
@@ -0,0 +1,189 @@
+/* MIT License
+ *
+ * Copyright (c) 2019 - 2024 Andreas Merkle
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*******************************************************************************
+ DESCRIPTION
+*******************************************************************************/
+/**
+ * @brief Timer service
+ * @author Andreas Merkle
+ *
+ * @addtogroup timer_service
+ *
+ * @{
+ */
+
+#ifndef TIMER_SERVICE_H
+#define TIMER_SERVICE_H
+
+/******************************************************************************
+ * Includes
+ *****************************************************************************/
+#include
+#include
+#include
+
+#include "TimerSetting.h"
+
+/******************************************************************************
+ * Compiler Switches
+ *****************************************************************************/
+
+/******************************************************************************
+ * Macros
+ *****************************************************************************/
+
+/******************************************************************************
+ * Types and Classes
+ *****************************************************************************/
+
+/**
+ * The timer service provides a timer functionality to switch on/off the
+ * display and set the brightness level on specific times.
+ */
+class TimerService : public IService
+{
+public:
+
+ /**
+ * Get the timer service instance.
+ *
+ * @return Timer service instance
+ */
+ static TimerService& getInstance()
+ {
+ static TimerService instance; /* idiom */
+
+ return instance;
+ }
+
+ /**
+ * Start the timer service.
+ */
+ bool start() final;
+
+ /**
+ * Stop the timer service.
+ */
+ void stop() final;
+
+ /**
+ * Process the service.
+ */
+ void process() final;
+
+private:
+
+ static const uint8_t MAX_TIMER_COUNT = 8U; /**< Maximum number of timer. */
+ static const char* FILE_NAME; /**< File name of the timer settings. */
+ static const char* TOPIC; /**< Topic for timer settings. */
+ static const char* ENTITY; /**< Entity for timer settings. */
+
+ String m_deviceId; /**< Device id. */
+ TimerSetting m_settings[MAX_TIMER_COUNT]; /**< Timer settings. */
+ bool m_hasSettingsChanged; /**< Has any timer setting changed since last request? */
+ Mutex m_mutex; /**< Mutex to protect the settings. */
+
+ TimerService(const TimerService& drv);
+ TimerService& operator=(const TimerService& drv);
+
+ /**
+ * Constructs the timer service instance.
+ */
+ TimerService() :
+ IService(),
+ m_deviceId(),
+ m_settings(),
+ m_hasSettingsChanged(true),
+ m_mutex()
+ {
+ }
+
+ /**
+ * Destroys the timer service instance.
+ */
+ ~TimerService()
+ {
+ /* Never called. */
+ }
+
+ /**
+ * Clear all timer settings.
+ */
+ void clear();
+
+ /**
+ * Load timer settings from file.
+ *
+ * @return If successful, it will return true otherwise false.
+ */
+ bool loadSettings();
+
+ /**
+ * Save timer settings to file.
+ *
+ * @return If successful, it will return true otherwise false.
+ */
+ bool saveSettings();
+
+ /**
+ * Get timer settings.
+ *
+ * @param[in] topic The topic name.
+ * @param[in,out] jsonValue The JSON value.
+ *
+ * @return If successful, it will return true otherwise false.
+ */
+ bool getTopic(const String& topic, JsonObject& jsonValue);
+
+ /**
+ * Has any timer setting changed since last request?
+ *
+ * @param[in] topic The topic name.
+ *
+ * @return If changed, it will return true otherwise false.
+ */
+ bool hasTopicChanged(const String& topic);
+
+ /**
+ * Set timer settings.
+ *
+ * @param[in] topic The topic name.
+ * @param[in] value The JSON value.
+ *
+ * @return If successful, it will return true otherwise false.
+ */
+ bool setTopic(const String& topic, const JsonObjectConst& value);
+};
+
+/******************************************************************************
+ * Variables
+ *****************************************************************************/
+
+/******************************************************************************
+ * Functions
+ *****************************************************************************/
+
+#endif /* TIMER_SERVICE_H */
+
+/** @} */
\ No newline at end of file
diff --git a/lib/TimerService/src/TimerSetting.cpp b/lib/TimerService/src/TimerSetting.cpp
new file mode 100644
index 00000000..1b440ff6
--- /dev/null
+++ b/lib/TimerService/src/TimerSetting.cpp
@@ -0,0 +1,221 @@
+/* MIT License
+ *
+ * Copyright (c) 2019 - 2024 Andreas Merkle
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*******************************************************************************
+ DESCRIPTION
+*******************************************************************************/
+/**
+ * @brief Timer setting
+ * @author Andreas Merkle
+ */
+
+/******************************************************************************
+ * Includes
+ *****************************************************************************/
+#include "TimerSetting.h"
+
+/******************************************************************************
+ * Compiler Switches
+ *****************************************************************************/
+
+/******************************************************************************
+ * Macros
+ *****************************************************************************/
+
+/******************************************************************************
+ * Types and classes
+ *****************************************************************************/
+
+/******************************************************************************
+ * Prototypes
+ *****************************************************************************/
+
+/******************************************************************************
+ * Local Variables
+ *****************************************************************************/
+
+/******************************************************************************
+ * Public Methods
+ *****************************************************************************/
+
+void TimerSetting::clear()
+{
+ m_isEnabled = false;
+ m_hour = 0U;
+ m_minute = 0U;
+ m_daysOfWeek = 0U;
+ m_displayState = DISPLAY_STATE_NONE;
+ m_brightness = -1;
+}
+
+void TimerSetting::toJson(JsonObject& jsonTimerSetting) const
+{
+ jsonTimerSetting["enabled"] = m_isEnabled;
+ jsonTimerSetting["hour"] = m_hour;
+ jsonTimerSetting["minute"] = m_minute;
+ jsonTimerSetting["sunday"] = isDayOfWeek(0U);
+ jsonTimerSetting["monday"] = isDayOfWeek(1U);
+ jsonTimerSetting["tuesday"] = isDayOfWeek(2U);
+ jsonTimerSetting["wednesday"] = isDayOfWeek(3U);
+ jsonTimerSetting["thursday"] = isDayOfWeek(4U);
+ jsonTimerSetting["friday"] = isDayOfWeek(5U);
+ jsonTimerSetting["saturday"] = isDayOfWeek(6U);
+ jsonTimerSetting["displayState"] = m_displayState;
+ jsonTimerSetting["brightness"] = m_brightness;
+}
+
+bool TimerSetting::fromJson(const JsonObjectConst& jsonTimerSetting)
+{
+ bool isSuccessful = false;
+ JsonVariantConst jsonEnabled = jsonTimerSetting["enabled"];
+ JsonVariantConst jsonHour = jsonTimerSetting["hour"];
+ JsonVariantConst jsonMinute = jsonTimerSetting["minute"];
+ JsonVariantConst jsonSunday = jsonTimerSetting["sunday"];
+ JsonVariantConst jsonMonday = jsonTimerSetting["monday"];
+ JsonVariantConst jsonTuesday = jsonTimerSetting["tuesday"];
+ JsonVariantConst jsonWednesday = jsonTimerSetting["wednesday"];
+ JsonVariantConst jsonThursday = jsonTimerSetting["thursday"];
+ JsonVariantConst jsonFriday = jsonTimerSetting["friday"];
+ JsonVariantConst jsonSaturday = jsonTimerSetting["saturday"];
+ JsonVariantConst jsonDisplayState = jsonTimerSetting["displayState"];
+ JsonVariantConst jsonBrightness = jsonTimerSetting["brightness"];
+
+ if ((false == jsonEnabled.isNull()) &&
+ (false == jsonHour.isNull()) &&
+ (false == jsonMinute.isNull()) &&
+ (false == jsonSunday.isNull()) &&
+ (false == jsonMonday.isNull()) &&
+ (false == jsonTuesday.isNull()) &&
+ (false == jsonWednesday.isNull()) &&
+ (false == jsonThursday.isNull()) &&
+ (false == jsonFriday.isNull()) &&
+ (false == jsonSaturday.isNull()) &&
+ (false == jsonDisplayState.isNull()) &&
+ (false == jsonBrightness.isNull()))
+ {
+ clear();
+
+ m_hour = jsonHour.as();
+ m_minute = jsonMinute.as();
+ m_displayState = static_cast(jsonDisplayState.as());
+ m_brightness = jsonBrightness.as();
+
+ if (true == jsonEnabled.is())
+ {
+ m_isEnabled = jsonEnabled.as();
+ }
+ else if (true == jsonEnabled.as().equals("false"))
+ {
+ m_isEnabled = false;
+ }
+ else
+ {
+ m_isEnabled = true;
+ }
+
+ if (((true == jsonSunday.is()) && (true == jsonSunday.as())) ||
+ (true == jsonSunday.as().equals("true")))
+ {
+ m_daysOfWeek |= (1U << 0U);
+ }
+
+ if (((true == jsonMonday.is()) && (true == jsonMonday.as())) ||
+ (true == jsonMonday.as().equals("true")))
+ {
+ m_daysOfWeek |= (1U << 1U);
+ }
+
+ if (((true == jsonTuesday.is()) && (true == jsonTuesday.as())) ||
+ (true == jsonTuesday.as().equals("true")))
+ {
+ m_daysOfWeek |= (1U << 2U);
+ }
+
+ if (((true == jsonWednesday.is()) && (true == jsonWednesday.as())) ||
+ (true == jsonWednesday.as().equals("true")))
+ {
+ m_daysOfWeek |= (1U << 3U);
+ }
+
+ if (((true == jsonThursday.is()) && (true == jsonThursday.as())) ||
+ (true == jsonThursday.as().equals("true")))
+ {
+ m_daysOfWeek |= (1U << 4U);
+ }
+
+ if (((true == jsonFriday.is()) && (true == jsonFriday.as())) ||
+ (true == jsonFriday.as().equals("true")))
+ {
+ m_daysOfWeek |= (1U << 5U);
+ }
+
+ if (((true == jsonSaturday.is()) && (true == jsonSaturday.as())) ||
+ (true == jsonSaturday.as().equals("true")))
+ {
+ m_daysOfWeek |= (1U << 6U);
+ }
+
+ isSuccessful = true;
+ }
+
+ return isSuccessful;
+}
+
+bool TimerSetting::isSignalling(const struct tm& currentTime)
+{
+ bool isSignalling = (m_hour == currentTime.tm_hour) && (m_minute == currentTime.tm_min) && isDayOfWeek(currentTime.tm_wday);
+
+ if (true == isSignalling)
+ {
+ if (false == m_isSignalling)
+ {
+ m_isSignalling = true;
+ }
+ else
+ {
+ isSignalling = false;
+ }
+ }
+ else
+ {
+ m_isSignalling = false;
+ }
+
+ return isSignalling;
+}
+
+/******************************************************************************
+ * Protected Methods
+ *****************************************************************************/
+
+/******************************************************************************
+ * Private Methods
+ *****************************************************************************/
+
+/******************************************************************************
+ * External Functions
+ *****************************************************************************/
+
+/******************************************************************************
+ * Local Functions
+ *****************************************************************************/
diff --git a/lib/TimerService/src/TimerSetting.h b/lib/TimerService/src/TimerSetting.h
new file mode 100644
index 00000000..29d1f460
--- /dev/null
+++ b/lib/TimerService/src/TimerSetting.h
@@ -0,0 +1,187 @@
+/* MIT License
+ *
+ * Copyright (c) 2019 - 2024 Andreas Merkle
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*******************************************************************************
+ DESCRIPTION
+*******************************************************************************/
+/**
+ * @brief Timer setting
+ * @author Andreas Merkle
+ *
+ * @addtogroup timer_service
+ *
+ * @{
+ */
+
+#ifndef TIMER_SETTING_H
+#define TIMER_SETTING_H
+
+/******************************************************************************
+ * Includes
+ *****************************************************************************/
+#include
+#include
+#include
+
+/******************************************************************************
+ * Compiler Switches
+ *****************************************************************************/
+
+/******************************************************************************
+ * Macros
+ *****************************************************************************/
+
+/******************************************************************************
+ * Types and Classes
+ *****************************************************************************/
+
+/**
+ * Single timer setting.
+ */
+class TimerSetting
+{
+public:
+
+ /** Generic display state which is request to be set. */
+ enum DisplayState : uint8_t
+ {
+ DISPLAY_STATE_NONE = 0, /**< No action */
+ DISPLAY_STATE_ON, /**< Switch on */
+ DISPLAY_STATE_OFF /**< Switch off */
+ };
+
+ /**
+ * Constructs a timer setting.
+ */
+ TimerSetting() :
+ m_isEnabled(false),
+ m_hour(0U),
+ m_minute(0U),
+ m_daysOfWeek(0U),
+ m_displayState(DISPLAY_STATE_NONE),
+ m_brightness(-1),
+ m_isSignalling(false)
+ {
+ }
+
+ /**
+ * Destroys a timer setting.
+ */
+ ~TimerSetting()
+ {
+ }
+
+ /**
+ * Clear timer setting to default values.
+ */
+ void clear();
+
+ /**
+ * Convert setting to JSON.
+ *
+ * @param[out] json JSON object destination.
+ */
+ void toJson(JsonObject& jsonTimerSetting) const;
+
+ /**
+ * Convert from JSON to setting.
+ *
+ * @param[in] jsonTimerSetting JSON object source.
+ *
+ * @return If successful, it will return true otherwise false.
+ */
+ bool fromJson(const JsonObjectConst& jsonTimerSetting);
+
+ /**
+ * Is timer enabled?
+ *
+ * @return If enabled, it will return true otherwise false.
+ */
+ bool isEnabled() const
+ {
+ return m_isEnabled;
+ }
+
+ /**
+ * Is timer signalling?
+ *
+ * @param[in] currentTime Current time.
+ *
+ * @return If signalling, it will return true otherwise false.
+ */
+ bool isSignalling(const struct tm& currentTime);
+
+ /**
+ * Get display state.
+ *
+ * @return Display state.
+ */
+ DisplayState getDisplayState() const
+ {
+ return m_displayState;
+ }
+
+ /**
+ * Get brightness level.
+ *
+ * @return Brightness level ([0; 255], otherwise disabled).
+ */
+ int16_t getBrightness() const
+ {
+ return m_brightness;
+ }
+
+private:
+
+ bool m_isEnabled; /**< Is timer enabled? */
+ uint8_t m_hour; /**< Hour */
+ uint8_t m_minute; /**< Minute */
+ uint32_t m_daysOfWeek; /**< Days of week (bit 0: Su, bit 1: Mo and etc.) */
+ DisplayState m_displayState; /**< Display state to set. */
+ int16_t m_brightness; /**< Brightness level ([0; 255], otherwise disabled) to set. */
+ bool m_isSignalling; /**< Is timer signalling? Used to signal just once. */
+
+ /**
+ * Get the day of week.
+ *
+ * @param[in] dayOfWeek Day of week.
+ *
+ * @return If day is set, it will return true otherwise false.
+ */
+ bool isDayOfWeek(uint32_t dayOfWeek) const
+ {
+ return (0 != (m_daysOfWeek & (1U << dayOfWeek)));
+ }
+};
+
+/******************************************************************************
+ * Variables
+ *****************************************************************************/
+
+/******************************************************************************
+ * Functions
+ *****************************************************************************/
+
+#endif /* TIMER_SETTING_H */
+
+/** @} */
\ No newline at end of file
diff --git a/lib/TimerService/web/TimerService.html b/lib/TimerService/web/TimerService.html
new file mode 100644
index 00000000..1a9b381c
--- /dev/null
+++ b/lib/TimerService/web/TimerService.html
@@ -0,0 +1,359 @@
+
+
+
+
+
+
+
+
+
+
+
+ PIXELIX
+
+
+
+
+
+
+
+
+
+
+
+
TimerService
+
Use the timers to control the display on/off state and its brightness level in %.
+
Notes:
+
+ - Timer settings are only active, if the timer is enabled.
+ - For the brightness level, the value range is from 0% to 100%.
+ - To avoid any brightness change, use the value -1.
+ - The brightness adjustment will be applied only if the automatic brightness adjustment is disabled.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/VolumioPlugin/web/VolumioPlugin.html b/lib/VolumioPlugin/web/VolumioPlugin.html
index 385539a2..a42a7645 100644
--- a/lib/VolumioPlugin/web/VolumioPlugin.html
+++ b/lib/VolumioPlugin/web/VolumioPlugin.html
@@ -82,6 +82,7 @@ Host Address
+
@@ -173,6 +174,7 @@ Host Address
$(document).ready(function() {
menu.addSubMenu(menu.data, "Plugins", pluginSubMenu);
+ menu.addSubMenu(menu.data, "Services", serviceSubMenu);
menu.create("menu", menu.data);
utils.injectOrigin("injectOrigin", "{{ORIGIN}}");
diff --git a/lib/WifiStatusPlugin/web/WifiStatusPlugin.html b/lib/WifiStatusPlugin/web/WifiStatusPlugin.html
index 78c350de..5abf98d1 100644
--- a/lib/WifiStatusPlugin/web/WifiStatusPlugin.html
+++ b/lib/WifiStatusPlugin/web/WifiStatusPlugin.html
@@ -55,10 +55,12 @@ REST API
+
diff --git a/lib/WormPlugin/web/WormPlugin.html b/lib/WormPlugin/web/WormPlugin.html
index 41ea3229..43c24517 100644
--- a/lib/WormPlugin/web/WormPlugin.html
+++ b/lib/WormPlugin/web/WormPlugin.html
@@ -55,10 +55,12 @@ REST API
+
diff --git a/scripts/Services.cpp b/scripts/Services.cpp
index b4d55b5c..355ab55b 100644
--- a/scripts/Services.cpp
+++ b/scripts/Services.cpp
@@ -58,6 +58,14 @@
* Local Variables
*****************************************************************************/
+/**
+ * List of services.
+ */
+static const Services::Element gServiceList[] =
+{
+$LIST_ENTRIES
+};
+
/******************************************************************************
* Public Methods
*****************************************************************************/
@@ -98,6 +106,13 @@ extern void Services::processAll()
$PROCESS_SERVICES
}
+const Services::Element* Services::getList(uint8_t& length)
+{
+ length = sizeof(gServiceList) / sizeof(gServiceList[0]);
+
+ return gServiceList;
+}
+
/******************************************************************************
* Local Functions
*****************************************************************************/
diff --git a/scripts/configure.py b/scripts/configure.py
index 27daf3d0..a8d27a6a 100644
--- a/scripts/configure.py
+++ b/scripts/configure.py
@@ -62,7 +62,7 @@ def configure(config_full_path, layout):
topic_handler_list = config_model.get_topic_handler_list()
configure_plugins(plugin_list, layout)
- configure_services(service_list)
+ configure_services(service_list, layout)
configure_topic_handlers(topic_handler_list)
################################################################################
diff --git a/scripts/configure_plugins.py b/scripts/configure_plugins.py
index 0271dce0..b688532c 100644
--- a/scripts/configure_plugins.py
+++ b/scripts/configure_plugins.py
@@ -303,7 +303,7 @@ def configure_plugins(plugin_list, layout):
(os.path.exists(_MENU_FULL_PATH) is False) or \
(os.path.exists(_PLUGIN_LIST_FULL_PATH) is False):
- print("\tGenerating web menu.")
+ print("\tGenerating plugins web menu.")
_generate_web_menu(_MENU_FULL_PATH, plugin_list)
print("\tGenerating plugin list.")
_generate_cpp_plugin_list(_PLUGIN_LIST_FULL_PATH, plugin_list)
diff --git a/scripts/configure_services.py b/scripts/configure_services.py
index 9eb31aef..9953bffe 100644
--- a/scripts/configure_services.py
+++ b/scripts/configure_services.py
@@ -26,6 +26,9 @@
# Imports
################################################################################
import os
+import shutil
+import json
+import hashlib
from string import Template
################################################################################
@@ -33,6 +36,10 @@
################################################################################
_LIB_PATH = "./lib"
+_WEB_DATA_PATH = "./data/services"
+
+_MENU_FULL_PATH = "./data/js/servicesSubMenu.js"
+
_SERVICE_LIST_FULL_PATH = "./src/Generated/Services.cpp"
_SERVICE_LIST_TEMPLATE_FULL_PATH = "./scripts/Services.cpp"
@@ -44,6 +51,137 @@
# Functions
################################################################################
+def _sort_key(file_path):
+ return os.path.basename(file_path)
+
+def calculate_path_checksum(paths):
+ """Recursively calculates a checksum representing the contents of all files
+ found with a sequence of file and/or directory paths.
+
+ Args:
+ paths (list[str]): List of paths
+
+ Returns:
+ int: MD5 checksum
+ """
+ hasher = hashlib.md5()
+ file_list = []
+
+ for path in paths:
+ if os.path.isdir(path):
+ filenames = os.listdir(path)
+ for filename in filenames:
+ file_path = os.path.normpath(path + "/" + filename)
+ file_list.append(file_path)
+ elif os.path.isfile(path):
+ file_list.append(path)
+
+ file_list_sorted = sorted(file_list, key=_sort_key)
+
+ for file_path in file_list_sorted:
+ with open(file_path, "rb") as file:
+ while chunk := file.read(8192):
+ hasher.update(chunk)
+
+ return hasher.hexdigest()
+
+def _load_json_file(full_path):
+ """Load JSON file.
+
+ Args:
+ full_path (str): Full path to JSON file.
+
+ Returns:
+ dict|None: Data dictionary or None
+ """
+ data = None
+
+ try:
+ with open(full_path, encoding="utf-8") as json_data:
+ data = json.load(json_data)
+
+ except FileNotFoundError:
+ pass
+
+ return data
+
+def _clean_up_folders(service_list, dst_path):
+ """Remove folders in destination path, which are not present in the source path.
+
+ Args:
+ service_list (list): List with all service names
+ dst_path (str): Destination path
+
+ Return:
+ bool: If cleaned something, it will return True otherwise False.
+ """
+ is_cleaned_up = False
+
+ if os.path.isdir(dst_path) is False:
+ pass
+ else:
+
+ dst_subfolders = []
+ for dst_file in os.listdir(dst_path):
+ if os.path.isdir(dst_path + "/" + dst_file) is True:
+ dst_subfolders.append(dst_file)
+
+ # Remove folders which exists only in the destination
+ for folder in dst_subfolders:
+ if folder not in service_list:
+ dst_full_path = dst_path + "/" + folder
+ try:
+ shutil.rmtree(dst_full_path)
+ print(f"\t\"{dst_full_path}\" removed.")
+ except FileNotFoundError:
+ pass
+ is_cleaned_up = True
+
+ return is_cleaned_up
+
+def _copy_files(src_files, dst_path):
+ """Copy service web related files from /lib/ to /data/services/.
+ If no destination folder exists, it will be created.
+
+ Args:
+ service_name (str): service name
+ """
+ if os.path.exists(dst_path) is False:
+ os.mkdir(dst_path)
+
+ # Copy source files to destination.
+ for src_file in src_files:
+ print(f"\t\t{src_file} -> {dst_path}", end="")
+ try:
+ shutil.copy2(src_file, dst_path)
+ print("")
+ except FileNotFoundError:
+ print(" -> file not found!")
+
+def _generate_web_menu(menu_full_path, service_list):
+ """Generate the menu.json file.
+
+ Args:
+ menu_full_path (str): Full path to menu.json where it shall be created.
+ service_list (list): List of all service names
+ """
+ with open(menu_full_path, 'w', encoding="utf-8") as file_desc:
+ file_desc.write("var serviceSubMenu = [\n")
+
+ for idx, service_name in enumerate(service_list):
+
+ file_desc.write(" {\n")
+ file_desc.write(f" title: \"{service_name}\",\n")
+ file_desc.write(f" hyperRef: \"/services/{service_name}/{service_name}.html\"\n")
+ file_desc.write(" }")
+
+ if idx == (len(service_list) - 1):
+ file_desc.write("\n")
+ else:
+ file_desc.write(",\n")
+
+ file_desc.write("]\n")
+
def _generate_cpp_service(service_list_full_path, service_list):
"""Generate the Service.cpp source file.
@@ -55,6 +193,7 @@ def _generate_cpp_service(service_list_full_path, service_list):
start_calls = ""
stop_calls = ""
process_calls = ""
+ list_entries = ""
# Handle includes, start and process calls.
for idx, service_name in enumerate(service_list):
@@ -63,6 +202,7 @@ def _generate_cpp_service(service_list_full_path, service_list):
includes += "\n"
start_calls += "\n"
process_calls += "\n"
+ list_entries += ",\n"
includes += f"#include <{service_name}.h>"
@@ -73,6 +213,8 @@ def _generate_cpp_service(service_list_full_path, service_list):
process_calls += f" {service_name}::getInstance().process();"
+ list_entries += f" \"{service_name}\""
+
# Handle stop calls in reverse order.
for idx, service_name in enumerate(reversed(service_list)):
@@ -85,7 +227,8 @@ def _generate_cpp_service(service_list_full_path, service_list):
"INCLUDES": includes,
"START_SERVICES": start_calls,
"STOP_SERVICES": stop_calls,
- "PROCESS_SERVICES": process_calls
+ "PROCESS_SERVICES": process_calls,
+ "LIST_ENTRIES": list_entries
}
with open(_SERVICE_LIST_TEMPLATE_FULL_PATH, "r", encoding="utf-8") as file_desc:
@@ -95,24 +238,99 @@ def _generate_cpp_service(service_list_full_path, service_list):
with open(service_list_full_path, "w", encoding="utf-8") as file_desc:
file_desc.write(result)
-def configure_services(service_list):
+def configure_services(service_list, layout):
"""Generate all service related artifacts.
Args:
service_list (list): List of service names
+ layout (str): The display layout.
"""
+ # Avoid generation if possible, because it will cause a compilation step.
+ is_generation_required = False
+
+ if os.path.isdir(_WEB_DATA_PATH) is False:
+ os.mkdir(_WEB_DATA_PATH)
+ is_generation_required = True
+
+ else:
+ # Remove all obsolete services in the web data if there are any.
+ if _clean_up_folders(service_list, _WEB_DATA_PATH) is True:
+ is_generation_required = True
+
skip_list = []
for service_name in service_list:
service_lib_path = _LIB_PATH + "/" + service_name
+ service_lib_web_path = service_lib_path + "/web"
if os.path.isdir(service_lib_path) is False:
print(f"\tSkipping {service_name}, because {service_lib_path} doesn't exist.")
skip_list.append(service_name)
+ elif os.path.isdir(service_lib_web_path) is False:
+ pass
+
+ else:
+ data_web_service_path = _WEB_DATA_PATH + "/" + service_name
+
+ data_web_service_path_checksum = calculate_path_checksum([data_web_service_path])
+
+ src_files = []
+
+ service_lib_data = _load_json_file(service_lib_path + "/pixelix.json")
+
+ if service_lib_data is None:
+ src_files = os.listdir(service_lib_web_path)
+ src_files = [service_lib_web_path + "/" + src_file for src_file in src_files]
+ else:
+
+ if service_lib_data["pixelix"]["type"] != "service":
+ print(f"\tSkipping {service_name}, because its type isn't service in pixelix.json.")
+ skip_list.append(service_name)
+
+ elif service_lib_data["pixelix"]["name"] != service_name:
+ print(f"\tSkipping {service_name}, because service name doesn't match with pixelix.json.")
+
+ else:
+ web_files = service_lib_data["pixelix"]["web"]["files"]
+ layout_specific_files = []
+ src_files = [service_lib_path + "/" + file_rel_path for file_rel_path in web_files]
+
+ # Search for layout specific files or use generic file list.
+ for lib_layout in service_lib_data["pixelix"]["web"]["layouts"]:
+
+ if lib_layout["name"] == layout:
+ layout_specific_files = lib_layout["files"]
+
+ elif lib_layout["name"] == "LAYOUT_GENERIC":
+ if layout_specific_files is None:
+ layout_specific_files = lib_layout["files"]
+
+ layout_specific_files_full_path = [service_lib_path + "/" + file_rel_path for file_rel_path in layout_specific_files]
+ src_files = src_files + layout_specific_files_full_path
+
+ if src_files:
+ service_lib_web_path_checksum = calculate_path_checksum(src_files)
+
+ if data_web_service_path_checksum != service_lib_web_path_checksum:
+ print("\tCopy web data:")
+ try:
+ shutil.rmtree(data_web_service_path)
+ except FileNotFoundError:
+ pass
+ _copy_files(src_files, data_web_service_path)
+ is_generation_required = True
+
# Remove skipped services from list
for service_name in skip_list:
service_list.remove(service_name)
+ if (is_generation_required is True) or \
+ (os.path.exists(_MENU_FULL_PATH) is False) or \
+ (os.path.exists(_SERVICE_LIST_FULL_PATH) is False):
+
+ print("\tGenerating services web menu.")
+ _generate_web_menu(_MENU_FULL_PATH, service_list)
+
print("\tGenerating services.")
_generate_cpp_service(_SERVICE_LIST_FULL_PATH, service_list)
diff --git a/src/Web/Pages.cpp b/src/Web/Pages.cpp
index ab48036e..8d1f9229 100644
--- a/src/Web/Pages.cpp
+++ b/src/Web/Pages.cpp
@@ -39,6 +39,7 @@
#include "DisplayMgr.h"
#include "RestApi.h"
#include "PluginList.h"
+#include "Services.h"
#include "WiFiUtil.h"
#include
@@ -72,8 +73,8 @@
*/
struct TmplKeyWordFunc
{
- const char* keyword; /**< Keyword */
- String (*func)(void); /**< Function to call */
+ const char* keyword; /**< Keyword */
+ String (*func)(void); /**< Function to call */
};
/**
@@ -81,8 +82,8 @@ struct TmplKeyWordFunc
*/
struct HtmlPageRoute
{
- const char* page; /**< Page in the filesystem. */
- WebRequestMethodComposite reqMethodComposite; /**< Request method composite */
+ const char* page; /**< Page in the filesystem. */
+ WebRequestMethodComposite reqMethodComposite; /**< Request method composite */
};
/******************************************************************************
@@ -90,101 +91,67 @@ struct HtmlPageRoute
*****************************************************************************/
static String tmplPageProcessor(const String& var);
-static void htmlPage(AsyncWebServerRequest* request);
-static void uploadPage(AsyncWebServerRequest* request);
-static void uploadHandler(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final);
+static void htmlPage(AsyncWebServerRequest* request);
+static void uploadPage(AsyncWebServerRequest* request);
+static void uploadHandler(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final);
namespace tmpl
{
- static String getEspChipId();
- static String getEspType();
- static String getFlashChipMode();
- static String getHostname();
- static String getIPAddress();
- static String getRSSI();
- static String getSSID();
-};
+static String getEspChipId();
+static String getEspType();
+static String getFlashChipMode();
+static String getHostname();
+static String getIPAddress();
+static String getRSSI();
+static String getSSID();
+}; /* namespace tmpl */
/******************************************************************************
* Local Variables
*****************************************************************************/
/** Firmware binary filename, used for update. */
-static const char* FIRMWARE_FILENAME = "firmware.bin";
+static const char* FIRMWARE_FILENAME = "firmware.bin";
/** Bootloader binary filename, used for update. */
-static const char* BOOTLOADER_FILENAME = "bootloader.bin";
+static const char* BOOTLOADER_FILENAME = "bootloader.bin";
/** Path to the plugin webpages. */
-static const String PLUGIN_PAGE_PATH = "/plugins/";
+static const String PLUGIN_PAGE_PATH = "/plugins/";
+
+/** Path to the service webpages. */
+static const String SERVICE_PAGE_PATH = "/services/";
/** Flag used to signal any kind of file upload error. */
-static bool gIsUploadError = false;
+static bool gIsUploadError = false;
/**
* List of all used template keywords and the function how to retrieve the information.
* The list is alphabetic sorted in ascending order.
*/
-static const TmplKeyWordFunc gTmplKeyWordToFunc[] =
-{
- "ARDUINO_IDF_BRANCH", []() -> String { return CONFIG_ARDUINO_IDF_BRANCH; },
- "BOOTLOADER_FILENAME", []() -> String { return BOOTLOADER_FILENAME; },
- "ESP_CHIP_ID", tmpl::getEspChipId,
- "ESP_CHIP_REV", []() -> String { return String(ESP.getChipRevision()); },
- "ESP_CPU_FREQ", []() -> String { return String(ESP.getCpuFreqMHz()); },
- "ESP_SDK_VERSION", []() -> String { return ESP.getSdkVersion(); },
- "ESP_TYPE", tmpl::getEspType,
- "FILESYSTEM_FILENAME", []() -> String { return FILESYSTEM_FILENAME; },
- "FIRMWARE_FILENAME", []() -> String { return FIRMWARE_FILENAME; },
- "FLASH_CHIP_MODE", tmpl::getFlashChipMode,
- "FLASH_CHIP_SIZE", []() -> String { return String(ESP.getFlashChipSize() / (1024U * 1024U)); },
- "FLASH_CHIP_SPEED", []() -> String { return String(ESP.getFlashChipSpeed() / (1000U * 1000U)); },
- "FREERTOS_VERSION", []() -> String { return tskKERNEL_VERSION_NUMBER; },
- "FS_SIZE", []() -> String { return String(FILESYSTEM.totalBytes()); },
- "FS_SIZE_USED", []() -> String { return String(FILESYSTEM.usedBytes()); },
- "HEAP_SIZE", []() -> String { return String(ESP.getHeapSize()); },
- "HEAP_SIZE_AVAILABLE", []() -> String { return String(ESP.getFreeHeap()); },
- "MBED_TLS_VERSION", []() -> String { return String(MBEDTLS_VERSION_STRING); },
- "PSRAM_SIZE", []() -> String { return String(ESP.getPsramSize()); },
- "PSRAM_SIZE_AVAILABLE", []() -> String { return String(ESP.getFreePsram()); },
- "HOSTNAME", tmpl::getHostname,
- "IPV4", tmpl::getIPAddress,
- "LWIP_VERSION", []() -> String { return LWIP_VERSION_STRING; },
- "MAC_ADDR", []() -> String { return WiFi.macAddress(); },
- "RSSI", tmpl::getRSSI,
- "SSID", tmpl::getSSID,
- "SW_BRANCH", []() -> String { return Version::getSoftwareBranchName(); },
- "SW_REVISION", []() -> String { return Version::getSoftwareRevision(); },
- "SW_VERSION", []() -> String { return Version::getSoftwareVersion(); },
- "TARGET", []() -> String { return Version::getTargetName(); },
- "WS_ENDPOINT", []() -> String { return WebConfig::WEBSOCKET_PATH; },
- "WS_PORT", []() -> String { return String(WebConfig::WEBSOCKET_PORT); },
- "WS_PROTOCOL", []() -> String { return WebConfig::WEBSOCKET_PROTOCOL; },
- "DISPLAY_HEIGHT", []() -> String { return String(CONFIG_LED_MATRIX_HEIGHT); },
- "DISPLAY_WIDTH", []() -> String { return String(CONFIG_LED_MATRIX_WIDTH); }
+static const TmplKeyWordFunc gTmplKeyWordToFunc[] = {
+ "ARDUINO_IDF_BRANCH", []() -> String { return CONFIG_ARDUINO_IDF_BRANCH; }, "BOOTLOADER_FILENAME", []() -> String { return BOOTLOADER_FILENAME; }, "ESP_CHIP_ID", tmpl::getEspChipId, "ESP_CHIP_REV", []() -> String { return String(ESP.getChipRevision()); }, "ESP_CPU_FREQ", []() -> String { return String(ESP.getCpuFreqMHz()); }, "ESP_SDK_VERSION", []() -> String { return ESP.getSdkVersion(); }, "ESP_TYPE", tmpl::getEspType, "FILESYSTEM_FILENAME", []() -> String { return FILESYSTEM_FILENAME; }, "FIRMWARE_FILENAME", []() -> String { return FIRMWARE_FILENAME; }, "FLASH_CHIP_MODE", tmpl::getFlashChipMode, "FLASH_CHIP_SIZE", []() -> String { return String(ESP.getFlashChipSize() / (1024U * 1024U)); }, "FLASH_CHIP_SPEED", []() -> String { return String(ESP.getFlashChipSpeed() / (1000U * 1000U)); }, "FREERTOS_VERSION", []() -> String { return tskKERNEL_VERSION_NUMBER; }, "FS_SIZE", []() -> String { return String(FILESYSTEM.totalBytes()); }, "FS_SIZE_USED", []() -> String { return String(FILESYSTEM.usedBytes()); }, "HEAP_SIZE", []() -> String { return String(ESP.getHeapSize()); }, "HEAP_SIZE_AVAILABLE", []() -> String { return String(ESP.getFreeHeap()); }, "MBED_TLS_VERSION", []() -> String { return String(MBEDTLS_VERSION_STRING); }, "PSRAM_SIZE", []() -> String { return String(ESP.getPsramSize()); }, "PSRAM_SIZE_AVAILABLE", []() -> String { return String(ESP.getFreePsram()); }, "HOSTNAME", tmpl::getHostname, "IPV4", tmpl::getIPAddress, "LWIP_VERSION", []() -> String { return LWIP_VERSION_STRING; }, "MAC_ADDR", []() -> String { return WiFi.macAddress(); }, "RSSI", tmpl::getRSSI, "SSID", tmpl::getSSID, "SW_BRANCH", []() -> String { return Version::getSoftwareBranchName(); }, "SW_REVISION", []() -> String { return Version::getSoftwareRevision(); }, "SW_VERSION", []() -> String { return Version::getSoftwareVersion(); }, "TARGET", []() -> String { return Version::getTargetName(); }, "WS_ENDPOINT", []() -> String { return WebConfig::WEBSOCKET_PATH; }, "WS_PORT", []() -> String { return String(WebConfig::WEBSOCKET_PORT); }, "WS_PROTOCOL", []() -> String { return WebConfig::WEBSOCKET_PROTOCOL; }, "DISPLAY_HEIGHT", []() -> String { return String(CONFIG_LED_MATRIX_HEIGHT); }, "DISPLAY_WIDTH", []() -> String { return String(CONFIG_LED_MATRIX_WIDTH); }
};
/**
* Standard HTML page routes.
*/
-static const HtmlPageRoute gHtmlPageRoutes[] =
-{
- { "/about.html", HTTP_GET },
- { "/debug.html", HTTP_GET },
- { "/display.html", HTTP_GET },
- { "/edit.html", HTTP_GET },
- { "/icons.html", HTTP_GET },
- { "/index.html", HTTP_GET },
- { "/info.html", HTTP_GET },
- { "/settings.html", HTTP_GET | HTTP_POST },
- { "/update.html", HTTP_GET }
+static const HtmlPageRoute gHtmlPageRoutes[] = {
+ { "/about.html", HTTP_GET },
+ { "/debug.html", HTTP_GET },
+ { "/display.html", HTTP_GET },
+ { "/edit.html", HTTP_GET },
+ { "/icons.html", HTTP_GET },
+ { "/index.html", HTTP_GET },
+ { "/info.html", HTTP_GET },
+ { "/settings.html", HTTP_GET | HTTP_POST },
+ { "/update.html", HTTP_GET }
};
/**
* Static routes to files with enabled cache.
*/
-static const char* gStaticRoutesWithCache[] =
-{
+static const char* gStaticRoutesWithCache[] = {
"/favicon.png",
"/images/",
"/js/",
@@ -209,28 +176,30 @@ static const char* gStaticRoutesWithCache[] =
void Pages::init(AsyncWebServer& srv)
{
- uint8_t pluginTypeListLength = 0U;
- const PluginList::Element* pluginTypeList = PluginList::getList(pluginTypeListLength);
- uint8_t idx = 0U;
- String webLoginUser;
- String webLoginPassword;
- SettingsService& settings = SettingsService::getInstance();
+ uint8_t pluginTypeListLength = 0U;
+ const PluginList::Element* pluginTypeList = PluginList::getList(pluginTypeListLength);
+ uint8_t serviceListLength = 0U;
+ const Services::Element* serviceList = Services::getList(serviceListLength);
+ uint8_t idx = 0U;
+ String webLoginUser;
+ String webLoginPassword;
+ SettingsService& settings = SettingsService::getInstance();
if (false == settings.open(true))
{
- webLoginUser = settings.getWebLoginUser().getDefault();
- webLoginPassword = settings.getWebLoginPassword().getDefault();
+ webLoginUser = settings.getWebLoginUser().getDefault();
+ webLoginPassword = settings.getWebLoginPassword().getDefault();
}
else
{
- webLoginUser = settings.getWebLoginUser().getValue();
- webLoginPassword = settings.getWebLoginPassword().getValue();
+ webLoginUser = settings.getWebLoginUser().getValue();
+ webLoginPassword = settings.getWebLoginPassword().getValue();
settings.close();
}
/* Serve standard HTML pages. */
- while(UTIL_ARRAY_NUM(gHtmlPageRoutes) > idx)
+ while (UTIL_ARRAY_NUM(gHtmlPageRoutes) > idx)
{
const HtmlPageRoute& route = gHtmlPageRoutes[idx];
@@ -260,7 +229,7 @@ void Pages::init(AsyncWebServer& srv)
* The client may cache files from filesystem for 1 hour.
*/
idx = 0U;
- while(UTIL_ARRAY_NUM(gStaticRoutesWithCache) > idx)
+ while (UTIL_ARRAY_NUM(gStaticRoutesWithCache) > idx)
{
const char* route = gStaticRoutesWithCache[idx];
@@ -272,30 +241,58 @@ void Pages::init(AsyncWebServer& srv)
/* Add one page per plugin. */
idx = 0U;
- while(pluginTypeListLength > idx)
+ while (pluginTypeListLength > idx)
+ {
+ const PluginList::Element* elem = &pluginTypeList[idx];
+ String uri = PLUGIN_PAGE_PATH + elem->name;
+
+ (void)srv.on(uri.c_str(),
+ HTTP_GET,
+ [](AsyncWebServerRequest* request) {
+ if (nullptr == request)
+ {
+ return;
+ }
+
+ if (0U != request->url().endsWith(".html"))
+ {
+ request->send(FILESYSTEM, request->url(), "text/html", false, tmplPageProcessor);
+ }
+ else
+ {
+ request->send(FILESYSTEM, request->url());
+ }
+ })
+ .setAuthentication(webLoginUser.c_str(), webLoginPassword.c_str());
+
+ ++idx;
+ }
+
+ /* Add one page per service. */
+ idx = 0U;
+ while (serviceListLength > idx)
{
- const PluginList::Element* elem = &pluginTypeList[idx];
- String uri = PLUGIN_PAGE_PATH + elem->name;
-
- (void)srv.on( uri.c_str(),
- HTTP_GET,
- [](AsyncWebServerRequest* request)
- {
- if (nullptr == request)
- {
- return;
- }
-
- if (0U != request->url().endsWith(".html"))
- {
- request->send(FILESYSTEM, request->url(), "text/html", false, tmplPageProcessor);
- }
- else
- {
- request->send(FILESYSTEM, request->url());
- }
-
- }).setAuthentication(webLoginUser.c_str(), webLoginPassword.c_str());;
+ const Services::Element* elem = &serviceList[idx];
+ String uri = SERVICE_PAGE_PATH + elem->name;
+
+ (void)srv.on(uri.c_str(),
+ HTTP_GET,
+ [](AsyncWebServerRequest* request) {
+ if (nullptr == request)
+ {
+ return;
+ }
+
+ if (0U != request->url().endsWith(".html"))
+ {
+ request->send(FILESYSTEM, request->url(), "text/html", false, tmplPageProcessor);
+ }
+ else
+ {
+ request->send(FILESYSTEM, request->url());
+ }
+ })
+ .setAuthentication(webLoginUser.c_str(), webLoginPassword.c_str());
++idx;
}
@@ -389,11 +386,9 @@ static void uploadPage(AsyncWebServerRequest* request)
* device will be restarted as well.
*/
request->onDisconnect(
- []()
- {
+ []() {
UpdateMgr::getInstance().reqRestart(0U);
- }
- );
+ });
}
/**
@@ -406,7 +401,7 @@ static void uploadPage(AsyncWebServerRequest* request)
* @param[in] len Data part size in byte.
* @param[in] final Is final packet or not.
*/
-static void uploadHandler(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final)
+static void uploadHandler(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final)
{
/* Begin of upload? */
if (0 == index)
@@ -423,20 +418,20 @@ static void uploadHandler(AsyncWebServerRequest *request, const String& filename
/* Upload firmware, bootloader or filesystem? */
int cmd = U_FLASH;
AsyncWebHeader* headerXFileSize = nullptr;
-
+
if (filename == FIRMWARE_FILENAME)
{
- cmd = U_FLASH;
+ cmd = U_FLASH;
headerXFileSize = request->getHeader("X-File-Size-Firmware");
}
else if (filename == BOOTLOADER_FILENAME)
{
- cmd = U_FLASH;
+ cmd = U_FLASH;
headerXFileSize = request->getHeader("X-File-Size-Bootloader");
}
else if (filename == FILESYSTEM_FILENAME)
{
- cmd = U_SPIFFS;
+ cmd = U_SPIFFS;
headerXFileSize = request->getHeader("X-File-Size-Filesystem");
}
else
@@ -500,7 +495,7 @@ static void uploadHandler(AsyncWebServerRequest *request, const String& filename
{
if (false == gIsUploadError)
{
- if(len != Update.write(data, len))
+ if (len != Update.write(data, len))
{
LOG_ERROR("Upload failed: %s", Update.errorString());
gIsUploadError = true;
@@ -561,167 +556,167 @@ static void uploadHandler(AsyncWebServerRequest *request, const String& filename
*/
namespace tmpl
{
- /**
- * Get ESP chip id.
- *
- * @return ESP chip id
- */
- static String getEspChipId()
- {
- String chipId;
+/**
+ * Get ESP chip id.
+ *
+ * @return ESP chip id
+ */
+static String getEspChipId()
+{
+ String chipId;
- /* Chip id is the same as the factory programmed wifi MAC address. */
- WiFiUtil::getChipId(chipId);
+ /* Chip id is the same as the factory programmed wifi MAC address. */
+ WiFiUtil::getChipId(chipId);
- return chipId;
- }
+ return chipId;
+}
- /**
- * Get ESP type.
- *
- * @return ESP type
- */
- static String getEspType()
- {
- String result = CONFIG_IDF_TARGET;
+/**
+ * Get ESP type.
+ *
+ * @return ESP type
+ */
+static String getEspType()
+{
+ String result = CONFIG_IDF_TARGET;
- return result;
- }
+ return result;
+}
- /**
- * Get flash chip mode.
- *
- * @return Flash chip mode.
- */
- static String getFlashChipMode()
+/**
+ * Get flash chip mode.
+ *
+ * @return Flash chip mode.
+ */
+static String getFlashChipMode()
+{
+ String result;
+
+ switch (ESP.getFlashChipMode())
{
- String result;
+ case FM_QIO:
+ result = "QUIO";
+ break;
- switch(ESP.getFlashChipMode())
- {
- case FM_QIO:
- result = "QUIO";
- break;
+ case FM_QOUT:
+ result = "QOUT";
+ break;
- case FM_QOUT:
- result = "QOUT";
- break;
+ case FM_DIO:
+ result = "DIO";
+ break;
- case FM_DIO:
- result = "DIO";
- break;
+ case FM_DOUT:
+ result = "DOUT";
+ break;
- case FM_DOUT:
- result = "DOUT";
- break;
+ case FM_FAST_READ:
+ result = "FAST_READ";
+ break;
- case FM_FAST_READ:
- result = "FAST_READ";
- break;
+ case FM_SLOW_READ:
+ result = "SLOW_READ";
+ break;
- case FM_SLOW_READ:
- result = "SLOW_READ";
- break;
+ case FM_UNKNOWN:
+ /* fallthrough */
- case FM_UNKNOWN:
- /* fallthrough */
+ default:
+ result = "UNKNOWN";
+ break;
+ }
- default:
- result = "UNKNOWN";
- break;
- }
+ return result;
+}
+
+/**
+ * Get hostname, depended on current WiFi mode.
+ *
+ * @return Hostname
+ */
+static String getHostname()
+{
+ String result;
+ const char* hostname = nullptr;
- return result;
+ if (WIFI_MODE_AP == WiFi.getMode())
+ {
+ hostname = WiFi.softAPgetHostname();
+ }
+ else
+ {
+ hostname = WiFi.getHostname();
}
- /**
- * Get hostname, depended on current WiFi mode.
- *
- * @return Hostname
- */
- static String getHostname()
+ if (nullptr != hostname)
{
- String result;
- const char* hostname = nullptr;
+ result = hostname;
+ }
- if (WIFI_MODE_AP == WiFi.getMode())
- {
- hostname = WiFi.softAPgetHostname();
- }
- else
- {
- hostname = WiFi.getHostname();
- }
+ return result;
+}
- if (nullptr != hostname)
- {
- result = hostname;
- }
+/**
+ * Get IP address, depended on WiFi mode.
+ *
+ * @return IPv4
+ */
+static String getIPAddress()
+{
+ String result;
- return result;
+ if (WIFI_MODE_AP == WiFi.getMode())
+ {
+ result = WiFi.softAPIP().toString();
}
-
- /**
- * Get IP address, depended on WiFi mode.
- *
- * @return IPv4
- */
- static String getIPAddress()
+ else
{
- String result;
+ result = WiFi.localIP().toString();
+ }
- if (WIFI_MODE_AP == WiFi.getMode())
- {
- result = WiFi.softAPIP().toString();
- }
- else
- {
- result = WiFi.localIP().toString();
- }
+ return result;
+}
- return result;
- }
+/**
+ * Get wifi RSSI.
+ *
+ * @return WiFi station SSID
+ */
+static String getRSSI()
+{
+ String result;
- /**
- * Get wifi RSSI.
- *
- * @return WiFi station SSID
+ /* Only in station mode it makes sense to retrieve the RSSI.
+ * Otherwise keep it -100 dbm.
*/
- static String getRSSI()
+ if (WIFI_MODE_STA == WiFi.getMode())
{
- String result;
-
- /* Only in station mode it makes sense to retrieve the RSSI.
- * Otherwise keep it -100 dbm.
- */
- if (WIFI_MODE_STA == WiFi.getMode())
- {
- result = WiFi.RSSI();
- }
- else
- {
- result = "-100";
- }
-
- return result;
+ result = WiFi.RSSI();
}
-
- /**
- * Get wifi station SSID.
- *
- * @return WiFi station SSID
- */
- static String getSSID()
+ else
{
- String result;
- SettingsService& settings = SettingsService::getInstance();
+ result = "-100";
+ }
- if (true == settings.open(true))
- {
- result = settings.getWifiSSID().getValue();
- settings.close();
- }
+ return result;
+}
+
+/**
+ * Get wifi station SSID.
+ *
+ * @return WiFi station SSID
+ */
+static String getSSID()
+{
+ String result;
+ SettingsService& settings = SettingsService::getInstance();
- return result;
+ if (true == settings.open(true))
+ {
+ result = settings.getWifiSSID().getValue();
+ settings.close();
}
-};
+
+ return result;
+}
+}; /* namespace tmpl */