From a990dd0fa3ecfcf792efc0b230eda9079c4b778e Mon Sep 17 00:00:00 2001 From: nhjschulz Date: Sun, 17 Nov 2024 12:01:18 +0100 Subject: [PATCH 1/4] Plugins: Allow view specific config Add a method for view specfic configuration keys via json. The goal is to allow additional settings (i.e. 64x64 layouts) without increasing the footprint for others. A new interface called IJsonConfig defines the API to use. Both plugins and views can implement it. The plugins then forward config calls to the views to have view specific settings covered (if needed). --- lib/Common/src/IJsonConfig.h | 109 +++++ lib/DateTimePlugin/src/DateTimePlugin.cpp | 444 ++++++++------------ lib/DateTimePlugin/src/DateTimePlugin.h | 45 +- lib/Plugin/src/PluginWithConfig.hpp | 26 +- lib/Utilities/src/Util.cpp | 21 + lib/Utilities/src/Util.h | 19 + lib/Views/src/IDateTimeView.h | 52 +-- lib/Views/src/layouts/DateTimeView32x8.h | 40 +- lib/Views/src/layouts/DateTimeView64x64.cpp | 96 ++++- lib/Views/src/layouts/DateTimeView64x64.h | 81 ++-- 10 files changed, 527 insertions(+), 406 deletions(-) create mode 100644 lib/Common/src/IJsonConfig.h diff --git a/lib/Common/src/IJsonConfig.h b/lib/Common/src/IJsonConfig.h new file mode 100644 index 00000000..e6d5840a --- /dev/null +++ b/lib/Common/src/IJsonConfig.h @@ -0,0 +1,109 @@ +/* MIT License + * + * Copyright (c) 2024 - 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 Json Configuration interface + * @author Andreas Merkle + * + * @addtogroup hal + * + * @{ + */ + +#ifndef IJSON_CONFIG_H +#define IJSON_CONFIG_H + +/****************************************************************************** + * Compile Switches + *****************************************************************************/ + +/****************************************************************************** + * Includes + *****************************************************************************/ +#include + +/****************************************************************************** + * Macros + *****************************************************************************/ + +/****************************************************************************** + * Types and Classes + *****************************************************************************/ + +/** + * This interface defines the functions required for JSON bases configuration + * updates. It is used to provide and apply configuration settings to and from + * a REST API. + */ +class IJsonConfig +{ +public: + + /** + * Destroys the interface object + */ + virtual ~IJsonConfig() + { + } + + /** + * Get current active configuration in JSON format. + * + * @param[out] cfg Configuration + */ + virtual void getConfiguration(JsonObject& jsonCfg) const = 0; + + /** + * Apply configuration from JSON. + * + * @param[in] cfg Configuration + * + * @return If successful set, it will return true otherwise false. + */ + virtual bool setConfiguration(const JsonObjectConst& jsonCfg) = 0; + + /** + * Merge JSON configuration with local settings to create a complete set. + * + * The received configuration may not contain all single key/value pair. + * Therefore create a complete internal configuration and overwrite it + * with the received one. + * + * @param[out] jsonMerged The complete config set with merge content from jsonSource. + * @param[in] jsonSource The recevied congi set, which may not cover all keys. + * @return true Keys needed merging. + * @return false Nothing needed merging. + */ + virtual bool mergeConfiguration(JsonObject& jsonMerged, const JsonObjectConst& jsonSource) = 0; +}; + +/****************************************************************************** + * Functions + *****************************************************************************/ + +#endif /* IJSON_CONFIG_H */ + +/** @} */ \ No newline at end of file diff --git a/lib/DateTimePlugin/src/DateTimePlugin.cpp b/lib/DateTimePlugin/src/DateTimePlugin.cpp index aaf6ceba..ec38869e 100644 --- a/lib/DateTimePlugin/src/DateTimePlugin.cpp +++ b/lib/DateTimePlugin/src/DateTimePlugin.cpp @@ -67,16 +67,6 @@ const char DateTimePlugin::TIME_FORMAT_DEFAULT[] = "%I:%M %p"; /* Initialize default date format. */ const char DateTimePlugin::DATE_FORMAT_DEFAULT[] = "%m/%d"; -/* Color key names for the analog clock configuration. */ -const char* DateTimePlugin::ANALOG_CLOCK_COLOR_KEYS[IDateTimeView::ANA_CLK_COL_MAX] = -{ - "handHourCol", - "handMinCol", - "handSecCol", - "ringFiveMinCol", - "ringMinDotCol" -}; - /****************************************************************************** * Public Methods *****************************************************************************/ @@ -105,87 +95,11 @@ bool DateTimePlugin::setTopic(const String& topic, const JsonObjectConst& value) if (true == topic.equals(TOPIC_CONFIG)) { - const IDateTimeView::AnalogClockConfig* analogClockCfg = nullptr; - - const size_t JSON_DOC_SIZE = 768U; + const size_t JSON_DOC_SIZE = 768U; DynamicJsonDocument jsonDoc(JSON_DOC_SIZE); - JsonObject jsonCfg = jsonDoc.to(); - JsonVariantConst jsonMode = value["mode"]; - JsonVariantConst jsonViewMode = value["viewMode"]; - JsonVariantConst jsonTimeFormat = value["timeFormat"]; - JsonVariantConst jsonDateFormat = value["dateFormat"]; - JsonVariantConst jsonTimeZone = value["timeZone"]; - JsonVariantConst jsonStartOfWeek = value["startOfWeek"]; - JsonVariantConst jsonDayOnColor = value["dayOnColor"]; - JsonVariantConst jsonDayOffColor = value["dayOffColor"]; - JsonObjectConst jsonAnalogClock = value["analogClock"]; - - /* The received configuration may not contain all single key/value pair. - * Therefore read first the complete internal configuration and - * overwrite them with the received ones. - */ - getConfiguration(jsonCfg); - - /* Note: - * Check only for the key/value pair availability. - * The type check will follow in the setConfiguration(). - */ - - if (false == jsonMode.isNull()) - { - jsonCfg["mode"] = jsonMode.as(); - isSuccessful = true; - } - - if (false == jsonViewMode.isNull()) - { - jsonCfg["viewMode"] = jsonViewMode.as(); - isSuccessful = true; - } - - if (false == jsonTimeFormat.isNull()) - { - jsonCfg["timeFormat"] = jsonTimeFormat.as(); - isSuccessful = true; - } - - if (false == jsonDateFormat.isNull()) - { - jsonCfg["dateFormat"] = jsonDateFormat.as(); - isSuccessful = true; - } - - if (false == jsonTimeZone.isNull()) - { - jsonCfg["timeZone"] = jsonTimeZone.as(); - isSuccessful = true; - } - - if (false == jsonStartOfWeek.isNull()) - { - jsonCfg["startOfWeek"] = jsonStartOfWeek.as(); - isSuccessful = true; - } - - if (false == jsonDayOnColor.isNull()) - { - jsonCfg["dayOnColor"] = jsonDayOnColor.as(); - isSuccessful = true; - } - - if (false == jsonDayOffColor.isNull()) - { - jsonCfg["dayOffColor"] = jsonDayOffColor.as(); - isSuccessful = true; - } - - if (false == jsonAnalogClock.isNull()) - { - jsonCfg["analogClock"] = jsonAnalogClock; - isSuccessful = true; - } + JsonObject jsonCfg = jsonDoc.to(); - if (true == isSuccessful) + if (true == mergeConfiguration(jsonCfg, value)) { JsonObjectConst jsonCfgConst = jsonCfg; @@ -203,8 +117,8 @@ bool DateTimePlugin::setTopic(const String& topic, const JsonObjectConst& value) bool DateTimePlugin::hasTopicChanged(const String& topic) { - MutexGuard guard(m_mutex); - bool hasTopicChanged = m_hasTopicChanged; + MutexGuard guard(m_mutex); + bool hasTopicChanged = m_hasTopicChanged; /* Only a single topic, therefore its not necessary to check. */ PLUGIN_NOT_USED(topic); @@ -221,7 +135,7 @@ void DateTimePlugin::setSlot(const ISlotPlugin* slotInterf) void DateTimePlugin::start(uint16_t width, uint16_t height) { - MutexGuard guard(m_mutex); + MutexGuard guard(m_mutex); m_view.init(width, height); @@ -230,7 +144,7 @@ void DateTimePlugin::start(uint16_t width, uint16_t height) void DateTimePlugin::stop() { - MutexGuard guard(m_mutex); + MutexGuard guard(m_mutex); PluginWithConfig::stop(); } @@ -274,7 +188,7 @@ void DateTimePlugin::inactive() void DateTimePlugin::update(YAGfx& gfx) { MutexGuard guard(m_mutex); - + m_view.update(gfx); } @@ -288,48 +202,31 @@ void DateTimePlugin::update(YAGfx& gfx) void DateTimePlugin::getConfiguration(JsonObject& jsonCfg) const { - const IDateTimeView::AnalogClockConfig* analogClockCfg = nullptr; - MutexGuard guard(m_mutex); - jsonCfg["mode"] = m_mode; - jsonCfg["viewMode"] = m_view.getViewMode(); - jsonCfg["timeFormat"] = m_timeFormat; - jsonCfg["dateFormat"] = m_dateFormat; - jsonCfg["timeZone"] = m_timeZone; - jsonCfg["startOfWeek"] = m_view.getStartOfWeek(); - jsonCfg["dayOnColor"] = colorToHtml(m_view.getDayOnColor()); - jsonCfg["dayOffColor"] = colorToHtml(m_view.getDayOffColor()); - - analogClockCfg = m_view.getAnalogClockConfig(); - if (nullptr != analogClockCfg) - { - /* View supports analog clock, add the additinal config elements for it. - */ - JsonObject jsonAnalogClock = jsonCfg.createNestedObject("analogClock"); - jsonAnalogClock["secondsMode"] = analogClockCfg->m_secondsMode; - for (uint32_t index = 0U; index < IDateTimeView::ANA_CLK_COL_MAX; ++index) - { - jsonAnalogClock[ANALOG_CLOCK_COLOR_KEYS[index]]= colorToHtml(analogClockCfg->m_colors[index]); - } - } + jsonCfg["mode"] = m_mode; + jsonCfg["viewMode"] = m_view.getViewMode(); + jsonCfg["timeFormat"] = m_timeFormat; + jsonCfg["dateFormat"] = m_dateFormat; + jsonCfg["timeZone"] = m_timeZone; + jsonCfg["startOfWeek"] = m_view.getStartOfWeek(); + jsonCfg["dayOnColor"] = Util::colorToHtml(m_view.getDayOnColor()); + jsonCfg["dayOffColor"] = Util::colorToHtml(m_view.getDayOffColor()); + m_view.getConfiguration(jsonCfg); } bool DateTimePlugin::setConfiguration(const JsonObjectConst& jsonCfg) { - bool status = false; - JsonVariantConst jsonMode = jsonCfg["mode"]; - JsonVariantConst jsonViewMode = jsonCfg["viewMode"]; - JsonVariantConst jsonTimeFormat = jsonCfg["timeFormat"]; - JsonVariantConst jsonDateFormat = jsonCfg["dateFormat"]; - JsonVariantConst jsonTimeZone = jsonCfg["timeZone"]; - JsonVariantConst jsonStartOfWeek = jsonCfg["startOfWeek"]; - JsonVariantConst jsonDayOnColor = jsonCfg["dayOnColor"]; - JsonVariantConst jsonDayOffColor = jsonCfg["dayOffColor"]; - JsonVariantConst jsonAnalogClock = jsonCfg["analogClock"]; - - IDateTimeView::AnalogClockConfig analogClockConfig; + bool status = false; + JsonVariantConst jsonMode = jsonCfg["mode"]; + JsonVariantConst jsonViewMode = jsonCfg["viewMode"]; + JsonVariantConst jsonTimeFormat = jsonCfg["timeFormat"]; + JsonVariantConst jsonDateFormat = jsonCfg["dateFormat"]; + JsonVariantConst jsonTimeZone = jsonCfg["timeZone"]; + JsonVariantConst jsonStartOfWeek = jsonCfg["startOfWeek"]; + JsonVariantConst jsonDayOnColor = jsonCfg["dayOnColor"]; + JsonVariantConst jsonDayOffColor = jsonCfg["dayOffColor"]; if ((false == jsonMode.is()) && (MODE_MAX <= jsonMode.as())) @@ -337,7 +234,7 @@ bool DateTimePlugin::setConfiguration(const JsonObjectConst& jsonCfg) LOG_WARNING("JSON mode not found or invalid type."); } else if ((false == jsonViewMode.is()) && - (IDateTimeView::VIEW_MODE_MAX <= jsonViewMode.as())) + (IDateTimeView::VIEW_MODE_MAX <= jsonViewMode.as())) { LOG_WARNING("JSON view mode not found or invalid type."); } @@ -365,40 +262,115 @@ bool DateTimePlugin::setConfiguration(const JsonObjectConst& jsonCfg) { LOG_WARNING("JSON day off color not found or invalid type."); } - else if (false == checkAnalogClockConfig(jsonAnalogClock, analogClockConfig)) + else if (false == m_view.setConfiguration(jsonCfg)) { - /* Error printed inside checkAnalogClockConfig() already. */ + /* Error printed inside m_view.setConfiguration() already. */ } else { MutexGuard guard(m_mutex); - m_mode = static_cast(jsonMode.as()); - m_timeFormat = jsonTimeFormat.as(); - m_dateFormat = jsonDateFormat.as(); - m_timeZone = jsonTimeZone.as(); + m_mode = static_cast(jsonMode.as()); + m_timeFormat = jsonTimeFormat.as(); + m_dateFormat = jsonDateFormat.as(); + m_timeZone = jsonTimeZone.as(); - status = m_view.setStartOfWeek(jsonStartOfWeek.as()); - m_view.setDayOnColor(colorFromHtml(jsonDayOnColor.as())); - m_view.setDayOffColor(colorFromHtml(jsonDayOffColor.as())); + status = m_view.setStartOfWeek(jsonStartOfWeek.as()); + m_view.setDayOnColor(Util::colorFromHtml(jsonDayOnColor.as())); + m_view.setDayOffColor(Util::colorFromHtml(jsonDayOffColor.as())); m_view.setViewMode(static_cast(jsonViewMode.as())); - if (false == jsonAnalogClock.isNull()) - { - m_view.setAnalogClockConfig(analogClockConfig); - } - m_hasTopicChanged = true; } return status; } +bool DateTimePlugin::mergeConfiguration(JsonObject& jsonMerged, const JsonObjectConst& jsonSource) +{ + bool isSuccessful = false; + JsonVariantConst jsonMode = jsonSource["mode"]; + JsonVariantConst jsonViewMode = jsonSource["viewMode"]; + JsonVariantConst jsonTimeFormat = jsonSource["timeFormat"]; + JsonVariantConst jsonDateFormat = jsonSource["dateFormat"]; + JsonVariantConst jsonTimeZone = jsonSource["timeZone"]; + JsonVariantConst jsonStartOfWeek = jsonSource["startOfWeek"]; + JsonVariantConst jsonDayOnColor = jsonSource["dayOnColor"]; + JsonVariantConst jsonDayOffColor = jsonSource["dayOffColor"]; + + /* The received configuration may not contain all single key/value pair. + * Therefore read first the complete internal configuration and + * overwrite them with the received ones. + */ + getConfiguration(jsonMerged); + + /* Note: + * Check only for the key/value pair availability. + * The type check will follow in the setConfiguration(). + */ + + if (false == jsonMode.isNull()) + { + jsonMerged["mode"] = jsonMode.as(); + isSuccessful = true; + } + + if (false == jsonViewMode.isNull()) + { + jsonMerged["viewMode"] = jsonViewMode.as(); + isSuccessful = true; + } + + if (false == jsonTimeFormat.isNull()) + { + jsonMerged["timeFormat"] = jsonTimeFormat.as(); + isSuccessful = true; + } + + if (false == jsonDateFormat.isNull()) + { + jsonMerged["dateFormat"] = jsonDateFormat.as(); + isSuccessful = true; + } + + if (false == jsonTimeZone.isNull()) + { + jsonMerged["timeZone"] = jsonTimeZone.as(); + isSuccessful = true; + } + + if (false == jsonStartOfWeek.isNull()) + { + jsonMerged["startOfWeek"] = jsonStartOfWeek.as(); + isSuccessful = true; + } + + if (false == jsonDayOnColor.isNull()) + { + jsonMerged["dayOnColor"] = jsonDayOnColor.as(); + isSuccessful = true; + } + + if (false == jsonDayOffColor.isNull()) + { + jsonMerged["dayOffColor"] = jsonDayOffColor.as(); + isSuccessful = true; + } + + /* Check if view configuration needed merging */ + if (true == m_view.mergeConfiguration(jsonMerged, jsonSource)) + { + isSuccessful = true; + } + + return isSuccessful; +} + void DateTimePlugin::updateDateTime(bool force) { - ClockDrv& clockDrv = ClockDrv::getInstance(); - struct tm timeInfo = { 0 }; - bool isClockAvailable = false; + ClockDrv& clockDrv = ClockDrv::getInstance(); + struct tm timeInfo = { 0 }; + bool isClockAvailable = false; /* If no other timezone is given, the local time shall be used. */ if (true == m_timeZone.isEmpty()) @@ -409,67 +381,66 @@ void DateTimePlugin::updateDateTime(bool force) { isClockAvailable = clockDrv.getTzTime(m_timeZone.c_str(), timeInfo); } - + if (true == isClockAvailable) { - bool showDate = false; - bool showTime = false; + bool showDate = false; + bool showTime = false; /* Decide what to show. */ - switch(m_mode) + switch (m_mode) { - case MODE_DATE_TIME: - { - uint32_t duration = (nullptr == m_slotInterf) ? 0U : m_slotInterf->getDuration(); - uint8_t halfDurationTicks = 0U; - uint8_t fullDurationTicks = 0U; + case MODE_DATE_TIME: { + uint32_t duration = (nullptr == m_slotInterf) ? 0U : m_slotInterf->getDuration(); + uint8_t halfDurationTicks = 0U; + uint8_t fullDurationTicks = 0U; - /* If infinite duration was set, switch between time and date with a fix period. */ - if (0U == duration) - { - duration = DURATION_DEFAULT; - } + /* If infinite duration was set, switch between time and date with a fix period. */ + if (0U == duration) + { + duration = DURATION_DEFAULT; + } - halfDurationTicks = (duration / (2U * MS_TO_SEC_DIVIDER)); - fullDurationTicks = 2U * halfDurationTicks; + halfDurationTicks = (duration / (2U * MS_TO_SEC_DIVIDER)); + fullDurationTicks = 2U * halfDurationTicks; - /* The time shall be shown in the first half slot duration. */ - if ((halfDurationTicks >= m_durationCounter) || - (fullDurationTicks < m_durationCounter)) - { - showTime = true; - } - else - { - showDate = true; - } + /* The time shall be shown in the first half slot duration. */ + if ((halfDurationTicks >= m_durationCounter) || + (fullDurationTicks < m_durationCounter)) + { + showTime = true; + } + else + { + showDate = true; + } - /* Reset duration counter after a complete plugin slot duration is finished. */ - if (fullDurationTicks < m_durationCounter) - { - m_durationCounter = 0U; - } + /* Reset duration counter after a complete plugin slot duration is finished. */ + if (fullDurationTicks < m_durationCounter) + { + m_durationCounter = 0U; + } - /* Force the update in case it changes from time to date or vice versa. - * This must be done, because we can not rely on the comparison whether - * the date/time changed and a update is necessary anyway. - */ - if ((0U == m_durationCounter) || - ((halfDurationTicks + 1U) == m_durationCounter)) - { - force = true; - } + /* Force the update in case it changes from time to date or vice versa. + * This must be done, because we can not rely on the comparison whether + * the date/time changed and a update is necessary anyway. + */ + if ((0U == m_durationCounter) || + ((halfDurationTicks + 1U) == m_durationCounter)) + { + force = true; } - break; + } + break; case MODE_DATE_ONLY: showDate = true; break; - + case MODE_TIME_ONLY: showTime = true; break; - + default: /* Should never happen. */ m_mode = MODE_DATE_TIME; @@ -492,15 +463,15 @@ void DateTimePlugin::updateDateTime(bool force) if ((true == force) || (m_shownSecond != timeInfo.tm_sec)) { - String extTimeFormat = "{hc}" + m_timeFormat; - String timeAsStr; - + String extTimeFormat = "{hc}" + m_timeFormat; + String timeAsStr; + if (true == getTimeAsString(timeAsStr, extTimeFormat, &timeInfo)) { m_view.setFormatText(timeAsStr); m_shownSecond = timeInfo.tm_sec; - } + } } } else if (true == showDate) @@ -512,15 +483,15 @@ void DateTimePlugin::updateDateTime(bool force) if ((true == force) || (m_shownDayOfTheYear != timeInfo.tm_yday)) { - String extDateFormat = "{hc}" + m_dateFormat; - String dateAsStr; - + String extDateFormat = "{hc}" + m_dateFormat; + String dateAsStr; + if (true == getTimeAsString(dateAsStr, extDateFormat, &timeInfo)) { m_view.setFormatText(dateAsStr); m_shownDayOfTheYear = timeInfo.tm_yday; - } + } } } else @@ -531,18 +502,18 @@ void DateTimePlugin::updateDateTime(bool force) } else { - if(true == force) + if (true == force) { m_view.setFormatText("{hc}?"); } } } -bool DateTimePlugin::getTimeAsString(String& time, const String& format, const tm *currentTime) +bool DateTimePlugin::getTimeAsString(String& time, const String& format, const tm* currentTime) { - bool isSuccessful = false; - tm timeStruct; - const tm* timeStructPtr = nullptr; + bool isSuccessful = false; + tm timeStruct; + const tm* timeStructPtr = nullptr; if (nullptr == currentTime) { @@ -560,12 +531,12 @@ bool DateTimePlugin::getTimeAsString(String& time, const String& format, const t if (nullptr != timeStructPtr) { - const uint32_t MAX_TIME_BUFFER_SIZE = 128U; - char buffer[MAX_TIME_BUFFER_SIZE]; + const uint32_t MAX_TIME_BUFFER_SIZE = 128U; + char buffer[MAX_TIME_BUFFER_SIZE]; if (0U != strftime(buffer, sizeof(buffer), format.c_str(), currentTime)) { - time = buffer; + time = buffer; isSuccessful = true; } } @@ -573,67 +544,6 @@ bool DateTimePlugin::getTimeAsString(String& time, const String& format, const t return isSuccessful; } -String DateTimePlugin::colorToHtml(const Color& color) -{ - char buffer[8]; /* '#' + 3x byte in hex + '\0' */ - - (void)snprintf(buffer, sizeof(buffer), "#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue()); - - return String(buffer); -} - -Color DateTimePlugin::colorFromHtml(const String& htmlColor) -{ - Color color; - - if ('#' == htmlColor[0]) - { - color = Util::hexToUInt32(htmlColor.substring(1U)); - } - - return color; -} - -bool DateTimePlugin::checkAnalogClockConfig(JsonVariantConst& jsonCfg, IDateTimeView::AnalogClockConfig & cfg) -{ - bool result = true; - - if (false == jsonCfg.isNull()) - { - JsonVariantConst jsonSecondsMode = jsonCfg["secondsMode"]; - - if ((false == jsonSecondsMode.is()) && - (IDateTimeView::SECONDS_DISP_MAX <= jsonSecondsMode.as())) - { - LOG_WARNING("JSON seconds mode not found or invalid type."); - result = false; - } - else - { - cfg.m_secondsMode = static_cast(jsonSecondsMode.as()); - - for (uint32_t idx = 0U; idx < IDateTimeView::ANA_CLK_COL_MAX; ++ idx) - { - JsonVariantConst color = jsonCfg[ANALOG_CLOCK_COLOR_KEYS[idx]]; - - if (false == color.is()) - { - LOG_WARNING( - "JSON attribute %s not found or invalid type.", - ANALOG_CLOCK_COLOR_KEYS[idx]); - result = false; - } - else - { - cfg.m_colors[idx] = colorFromHtml(color); - } - } - } - } - - return result; -} - /****************************************************************************** * External Functions *****************************************************************************/ diff --git a/lib/DateTimePlugin/src/DateTimePlugin.h b/lib/DateTimePlugin/src/DateTimePlugin.h index 188fe77a..86f3f3dc 100644 --- a/lib/DateTimePlugin/src/DateTimePlugin.h +++ b/lib/DateTimePlugin/src/DateTimePlugin.h @@ -293,9 +293,6 @@ class DateTimePlugin : public PluginWithConfig /** Default date format according to strftime(). */ static const char DATE_FORMAT_DEFAULT[]; - /** Color key names for the analog clock configuration. */ - static const char* ANALOG_CLOCK_COLOR_KEYS[IDateTimeView::ANA_CLK_COL_MAX]; - /** * If the slot duration is infinite (0s), the default duration of 30s shall be assumed as base * for toggling between time and date on the display. @@ -333,6 +330,20 @@ class DateTimePlugin : public PluginWithConfig */ bool setConfiguration(const JsonObjectConst& jsonCfg) final; + /** + * Merge JSON configuration with local settings to create a complete set. + * + * The received configuration may not contain all single key/value pair. + * Therefore create a complete internal configuration and overwrite it + * with the received one. + * + * @param[out] jsonMerged The complete config set with merge content from jsonSource. + * @param[in] jsonSource The recevied congi set, which may not cover all keys. + * @return true Keys needed merging. + * @return false Nothing needed merging. + */ + bool mergeConfiguration(JsonObject& jsonMerged, const JsonObjectConst& jsonSource) final; + /** * Get current date/time and update the text, which to be displayed. * The update takes only place, if the date changed. @@ -355,34 +366,6 @@ class DateTimePlugin : public PluginWithConfig */ bool getTimeAsString(String& time, const String& format, const tm *currentTime = nullptr); - /** - * Convert color to HTML format. - * - * @param[in] color Color - * - * @return Color in HTML format - */ - static String colorToHtml(const Color& color); - - /** - * Convert color from HTML format. - * - * @param[in] htmlColor Color in HTML format - * - * @return Color - */ - static Color colorFromHtml(const String& htmlColor); - - /** - * Check if analog cfg is valid when present. - * - * @param jsonCfg[in] The json configuration, may be isNull(). - * @param cfg[out] The parsed config data if json present and valid. - * @return true If no configuration or valid json values. - */ - static bool checkAnalogClockConfig( - JsonVariantConst& jsonCfg, - IDateTimeView::AnalogClockConfig & cfg); }; /****************************************************************************** diff --git a/lib/Plugin/src/PluginWithConfig.hpp b/lib/Plugin/src/PluginWithConfig.hpp index 6c0cec3e..819fbc80 100644 --- a/lib/Plugin/src/PluginWithConfig.hpp +++ b/lib/Plugin/src/PluginWithConfig.hpp @@ -44,6 +44,7 @@ * Includes *****************************************************************************/ #include "Plugin.hpp" +#include "IJsonConfig.h" #include #include @@ -64,7 +65,7 @@ * Attention: Every derived class must call start(), stop() and process() * of this base class to get the configuration file handling working! */ -class PluginWithConfig : public Plugin +class PluginWithConfig : public Plugin, IJsonConfig { public: @@ -190,22 +191,6 @@ class PluginWithConfig : public Plugin { } - /** - * Get configuration in JSON. - * - * @param[out] cfg Configuration - */ - virtual void getConfiguration(JsonObject& cfg) const = 0; - - /** - * Set configuration in JSON. - * - * @param[in] cfg Configuration - * - * @return If successful set, it will return true otherwise false. - */ - virtual bool setConfiguration(const JsonObjectConst& cfg) = 0; - /** * Request to store configuration to persistent memory. */ @@ -333,6 +318,13 @@ class PluginWithConfig : public Plugin bool m_storeConfigReq; /**< Is requested to store the configuration in persistent memory? */ bool m_reloadConfigReq; /**< Is requested to reload the configuration from persistent memory? */ + /*TODO REMOVE if all plugins have it. Right now only in DateTimePlugin. */ + bool mergeConfiguration(JsonObject& jsonMerged, const JsonObjectConst& jsonSource) + { + return true; + } + + }; /****************************************************************************** diff --git a/lib/Utilities/src/Util.cpp b/lib/Utilities/src/Util.cpp index be15bea1..13365d8b 100644 --- a/lib/Utilities/src/Util.cpp +++ b/lib/Utilities/src/Util.cpp @@ -247,6 +247,27 @@ extern uint32_t Util::hexToUInt32(const String& str) return value; } +String Util::colorToHtml(const Color& color) +{ + char buffer[8]; /* '#' + 3x byte in hex + '\0' */ + + (void)snprintf(buffer, sizeof(buffer), "#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue()); + + return String(buffer); +} + +Color Util::colorFromHtml(const String& htmlColor) +{ + Color color; + + if ('#' == htmlColor[0]) + { + color = Util::hexToUInt32(htmlColor.substring(1U)); + } + + return color; +} + /****************************************************************************** * Local Functions *****************************************************************************/ \ No newline at end of file diff --git a/lib/Utilities/src/Util.h b/lib/Utilities/src/Util.h index 6c987c98..73893e30 100644 --- a/lib/Utilities/src/Util.h +++ b/lib/Utilities/src/Util.h @@ -47,6 +47,8 @@ #include #include +#include + /****************************************************************************** * Macros *****************************************************************************/ @@ -214,6 +216,23 @@ constexpr T max(T valA, T valB) return (valA > valB) ? valA : valB; } +/** + * Convert color to HTML format. + * + * @param[in] color Color + * + * @return Color in HTML format + */ +extern String colorToHtml(const Color& color); + +/** + * Convert color from HTML format. + * + * @param[in] htmlColor Color in HTML format + * + * @return Color + */ +extern Color colorFromHtml(const String& htmlColor); } #endif /* UTILITY_H */ diff --git a/lib/Views/src/IDateTimeView.h b/lib/Views/src/IDateTimeView.h index ebabcf77..57d6b73a 100644 --- a/lib/Views/src/IDateTimeView.h +++ b/lib/Views/src/IDateTimeView.h @@ -47,6 +47,8 @@ #include #include +#include "IJsonConfig.h" + /****************************************************************************** * Macros *****************************************************************************/ @@ -58,7 +60,7 @@ /** * Interface for a view with date and time. */ -class IDateTimeView +class IDateTimeView : public IJsonConfig { public: @@ -81,38 +83,6 @@ class IDateTimeView VIEW_MODE_MAX /**< Number of configurations */ }; - /** - * Options for displaying seconds in analog clock. - */ - enum SecondsDisplayMode - { - SECOND_DISP_OFF = 0U, /**< No second indicator display. */ - SECOND_DISP_HAND = 1U, /**< Draw second clock hand. */ - SECOND_DISP_RING = 2U, /**< Show passed seconds on minute tick ring. */ - SECOND_DISP_BOTH = 3U, /**< Show hand and on ring. */ - SECONDS_DISP_MAX /**< Number of configurations. */ - }; - - /** - * Color array indexes for the analog clock drawing. - */ - enum AnalogClockColor - { - ANA_CLK_COL_HAND_HOUR = 0U, /**< Hour clock hand color. */ - ANA_CLK_COL_HAND_MIN, /**< Minutes clock hand color. */ - ANA_CLK_COL_HAND_SEC, /**< Seconds colock hand color */ - ANA_CLK_COL_RING_MIN5_MARK, /**< Ring five minute marks color. */ - ANA_CLK_COL_RING_MIN_DOT, /**< Ring minut dots color. */ - ANA_CLK_COL_MAX /**< Number of colors. */ - }; - - /** Analog clock appearance configuration. */ - struct AnalogClockConfig - { - SecondsDisplayMode m_secondsMode; /**< Seconds visualisation mode. */ - Color m_colors[ANA_CLK_COL_MAX]; /**< Clock colors to use. */ - }; - /** * Initialize view, which will prepare the widgets and the default values. */ @@ -218,22 +188,6 @@ class IDateTimeView */ virtual bool setViewMode(ViewMode mode) = 0; - /** - * Get the analog clock clonfiguration. - * - * @return AnalogClockConfig or nullptr if unsupported. - */ - virtual const AnalogClockConfig* getAnalogClockConfig() const = 0; - - /** - * Set the analog clock configuration. - * - * @param[in] cfg The new configuration to apply. - * - * @return success or failure - */ - virtual bool setAnalogClockConfig(const AnalogClockConfig& cfg) = 0; - /** * @brief Update current time values in view. * diff --git a/lib/Views/src/layouts/DateTimeView32x8.h b/lib/Views/src/layouts/DateTimeView32x8.h index ea229722..cf5eff21 100644 --- a/lib/Views/src/layouts/DateTimeView32x8.h +++ b/lib/Views/src/layouts/DateTimeView32x8.h @@ -275,23 +275,47 @@ class DateTimeView32x8 : public IDateTimeView } /** - * Get the analog clock seconds display mode (none, ring, hand or both). + * Get current active configuration in JSON format. * - * @return SecondsDisplayMode pointer or nullptr if unsupported. + * @param[out] cfg Configuration */ - const AnalogClockConfig* getAnalogClockConfig() const override + void getConfiguration(JsonObject& jsonCfg) const final { - return nullptr; /* 32X8 layout can only do digital.*/ + (void)jsonCfg; /* No configuration for 32x8 */ } /** - * Set the analog clock seconds display mode (none, ring, hand or both). + * Apply configuration from JSON. * - * @return success of failure + * @param[in] cfg Configuration + * + * @return If successful set, it will return true otherwise false. + */ + bool setConfiguration(const JsonObjectConst& jsonCfg) final + { + (void)jsonCfg; + + return true; + } + + /** + * Merge JSON configuration with local settings to create a complete set. + * + * The received configuration may not contain all single key/value pair. + * Therefore create a complete internal configuration and overwrite it + * with the received one. + * + * @param[out] jsonMerged The complete config set with merge content from jsonSource. + * @param[in] jsonSource The recevied congi set, which may not cover all keys. + * @return true Keys needed merging. + * @return false Nothing needed merging. */ - bool setAnalogClockConfig(const AnalogClockConfig& cfg) override + bool mergeConfiguration(JsonObject& jsonMerged, const JsonObjectConst& jsonSource) final { - return true; /* No analog clock in 32x8 layout, ignore request. */ + (void)jsonMerged; + (void)jsonSource; + + return false; /* Nothing to merge. */ } /** diff --git a/lib/Views/src/layouts/DateTimeView64x64.cpp b/lib/Views/src/layouts/DateTimeView64x64.cpp index d6f66e18..f117d71f 100644 --- a/lib/Views/src/layouts/DateTimeView64x64.cpp +++ b/lib/Views/src/layouts/DateTimeView64x64.cpp @@ -83,6 +83,16 @@ static const int16_t SECOND_HAND_LENGTH = ANALOG_RADIUS - 2; /** Clock hand distance from clock center. */ static const int16_t HAND_CENTER_DISTANCE = 3; +/* Color key names for the analog clock configuration. */ +const char* DateTimeView64x64::ANALOG_CLOCK_COLOR_KEYS[DateTimeView64x64::ANA_CLK_COL_MAX] = +{ + "handHourCol", + "handMinCol", + "handSecCol", + "ringFiveMinCol", + "ringMinDotCol" +}; + /****************************************************************************** * Types and classes *****************************************************************************/ @@ -179,7 +189,7 @@ void DateTimeView64x64::update(YAGfx& gfx) if ((ViewMode::DIGITAL_AND_ANALOG == m_mode) || (ViewMode::ANALOG_ONLY == m_mode)) { - uint32_t centerRingCol = m_analogClockCfg.m_colors[ANA_CLK_COL_HAND_MIN]; + uint32_t centerRingCol = m_analogColors[ANA_CLK_COL_HAND_MIN]; /* Draw analog clock minute circle. */ drawAnalogClockBackground(gfx); @@ -189,17 +199,17 @@ void DateTimeView64x64::update(YAGfx& gfx) gfx, m_now.tm_min, MINUTE_HAND_LENGTH, - m_analogClockCfg.m_colors[ANA_CLK_COL_HAND_MIN]); + m_analogColors[ANA_CLK_COL_HAND_MIN]); drawAnalogClockHand(gfx, getHourHandDestination(m_now.tm_hour, m_now.tm_min), HOUR_HAND_LENGTH, - m_analogClockCfg.m_colors[ANA_CLK_COL_HAND_HOUR]); + m_analogColors[ANA_CLK_COL_HAND_HOUR]); - if (0U != (m_analogClockCfg.m_secondsMode & SECOND_DISP_HAND)) + if (0U != (m_secondsMode & SECOND_DISP_HAND)) { /* Use second hand color also for the middle ring if this hand is enabled. */ - centerRingCol = m_analogClockCfg.m_colors[ANA_CLK_COL_HAND_SEC]; + centerRingCol = m_analogColors[ANA_CLK_COL_HAND_SEC]; drawAnalogClockHand( gfx, m_now.tm_sec, @@ -256,14 +266,14 @@ void DateTimeView64x64::drawAnalogClockBackground(YAGfx& gfx) const int16_t xe = ANALOG_CENTER_X + (dx * HOUR_MARK_LENGTH) / SINUS_VAL_SCALE; const int16_t ye = ANALOG_CENTER_Y + (dy * HOUR_MARK_LENGTH) / SINUS_VAL_SCALE; - gfx.drawLine(xs, ys, xe, ye, m_analogClockCfg.m_colors[ANA_CLK_COL_RING_MIN5_MARK]); + gfx.drawLine(xs, ys, xe, ye, m_analogColors[ANA_CLK_COL_RING_MIN5_MARK]); } - Color tickMarkCol = m_analogClockCfg.m_colors[ANA_CLK_COL_RING_MIN_DOT]; - if ((0U != (SECOND_DISP_RING & m_analogClockCfg.m_secondsMode)) && (angle <= secondAngle)) + Color tickMarkCol = m_analogColors[ANA_CLK_COL_RING_MIN_DOT]; + if ((0U != (SECOND_DISP_RING & m_secondsMode)) && (angle <= secondAngle)) { /* Draw minute tick marks with passed seconds highlighting. */ - tickMarkCol = m_analogClockCfg.m_colors[ANA_CLK_COL_HAND_SEC]; + tickMarkCol = m_analogColors[ANA_CLK_COL_HAND_SEC]; } gfx.drawPixel(xs, ys, tickMarkCol); } @@ -286,6 +296,74 @@ void DateTimeView64x64::drawAnalogClockHand(YAGfx& gfx, int16_t minute, int16_t col); } + +void DateTimeView64x64::getConfiguration(JsonObject& jsonCfg) const +{ + JsonObject jsonAnalogClock = jsonCfg.createNestedObject("analogClock"); + + jsonAnalogClock["secondsMode"] = m_secondsMode; + + for (uint32_t index = 0U; index < ANA_CLK_COL_MAX; ++index) + { + jsonAnalogClock[ANALOG_CLOCK_COLOR_KEYS[index]]= Util::colorToHtml(m_analogColors[index]); + } +} + +bool DateTimeView64x64::setConfiguration(const JsonObjectConst& jsonCfg) +{ + bool result = true; + JsonObjectConst jsonAnalogClock = jsonCfg["analogClock"]; + + if (false == jsonAnalogClock.isNull()) + { + JsonVariantConst jsonSecondsMode = jsonAnalogClock["secondsMode"]; + + if ((false == jsonSecondsMode.is()) && + (SECONDS_DISP_MAX <= jsonSecondsMode.as())) + { + LOG_WARNING("JSON seconds mode not found or invalid type."); + result = false; + } + else + { + m_secondsMode = static_cast(jsonSecondsMode.as()); + + for (uint32_t idx = 0U; idx < ANA_CLK_COL_MAX; ++ idx) + { + JsonVariantConst color = jsonAnalogClock[ANALOG_CLOCK_COLOR_KEYS[idx]]; + + if (false == color.is()) + { + LOG_WARNING( + "JSON attribute %s not found or invalid type.", + ANALOG_CLOCK_COLOR_KEYS[idx]); + result = false; + } + else + { + m_analogColors[idx] = Util::colorFromHtml(color); + } + } + } + } + + return result; +} + +bool DateTimeView64x64::mergeConfiguration(JsonObject& jsonMerged, const JsonObjectConst& jsonSource) +{ + bool result = false; + JsonObjectConst jsonAnalogClock = jsonSource["analogClock"]; + + if (false == jsonAnalogClock.isNull()) + { + jsonMerged["analogClock"] = jsonAnalogClock; + result = true; + } + + return result; +} + /****************************************************************************** * External Functions *****************************************************************************/ diff --git a/lib/Views/src/layouts/DateTimeView64x64.h b/lib/Views/src/layouts/DateTimeView64x64.h index 5ab429b5..712faa07 100644 --- a/lib/Views/src/layouts/DateTimeView64x64.h +++ b/lib/Views/src/layouts/DateTimeView64x64.h @@ -71,17 +71,15 @@ class DateTimeView64x64 : public DateTimeViewGeneric DateTimeView64x64() : DateTimeViewGeneric(), m_mode(ViewMode::DIGITAL_AND_ANALOG), - m_analogClockCfg( - { - SECOND_DISP_RING, + m_secondsMode(SECOND_DISP_RING), + m_analogColors( { ColorDef::WHITE, ColorDef::GRAY, ColorDef::YELLOW, ColorDef::BLUE, ColorDef::YELLOW - } - }), + }), m_lastUpdateSecondVal(-1) { /* Disable fade effect in case the user required to show seconds, @@ -145,35 +143,68 @@ class DateTimeView64x64 : public DateTimeViewGeneric } /** - * Get the analog clock configuration. + * Get current active configuration in JSON format. * - * @return SecondsDisplayMode + * @param[out] cfg Configuration */ - const AnalogClockConfig* getAnalogClockConfig() const override - { - return &m_analogClockCfg; - } + void getConfiguration(JsonObject& jsonCfg) const override; /** - * Set the analog clock configuration. + * Apply configuration from JSON. * - * @return success of failure + * @param[in] cfg Configuration + * + * @return If successful set, it will return true otherwise false. */ - bool setAnalogClockConfig(const AnalogClockConfig& cfg) override - { - if (SecondsDisplayMode::SECONDS_DISP_MAX <= cfg.m_secondsMode) - { - LOG_WARNING("Illegal Seconds Display mode (%hhu)", cfg.m_secondsMode); - return false; - } + bool setConfiguration(const JsonObjectConst& jsonCfg) override; + + /** + * Merge JSON configuration with local settings to create a complete set. + * + * The received configuration may not contain all single key/value pair. + * Therefore create a complete internal configuration and overwrite it + * with the received one. + * + * @param[out] jsonMerged The complete config set with merge content from jsonSource. + * @param[in] jsonSource The recevied congi set, which may not cover all keys. + * @return true Keys needed merging. + * @return false Nothing needed merging. + */ + bool mergeConfiguration(JsonObject& jsonMerged, const JsonObjectConst& jsonSource) override; - m_analogClockCfg = cfg; - return true; - } protected: - ViewMode m_mode; /**< Used View mode analog, digital or both. */ - AnalogClockConfig m_analogClockCfg; /**< The clock drawing configuration options. */ + /** + * Options for displaying seconds in analog clock. + */ + enum SecondsDisplayMode + { + SECOND_DISP_OFF = 0U, /**< No second indicator display. */ + SECOND_DISP_HAND = 1U, /**< Draw second clock hand. */ + SECOND_DISP_RING = 2U, /**< Show passed seconds on minute tick ring. */ + SECOND_DISP_BOTH = 3U, /**< Show hand and on ring. */ + SECONDS_DISP_MAX /**< Number of configurations. */ + }; + + /** + * Color array indexes for the analog clock drawing. + */ + enum AnalogClockColor + { + ANA_CLK_COL_HAND_HOUR = 0U, /**< Hour clock hand color. */ + ANA_CLK_COL_HAND_MIN, /**< Minutes clock hand color. */ + ANA_CLK_COL_HAND_SEC, /**< Seconds colock hand color */ + ANA_CLK_COL_RING_MIN5_MARK, /**< Ring five minute marks color. */ + ANA_CLK_COL_RING_MIN_DOT, /**< Ring minut dots color. */ + ANA_CLK_COL_MAX /**< Number of colors. */ + }; + + /** Color key names for the analog clock configuration. */ + static const char* ANALOG_CLOCK_COLOR_KEYS[ANA_CLK_COL_MAX]; + + ViewMode m_mode; /**< Used View mode analog, digital or both. */ + SecondsDisplayMode m_secondsMode; /**< Seconds visualisation mode. */ + Color m_analogColors[ANA_CLK_COL_MAX]; /**< Clock colors to use. */ /** * Seconds value of last display update. Used to avoid unecessary redrawing. From 1ac2af9f2057e139b7fde0cf81570185ea50e14f Mon Sep 17 00:00:00 2001 From: nhjschulz Date: Mon, 25 Nov 2024 18:40:40 +0100 Subject: [PATCH 2/4] Remove IJsonConfig virtual interface Using it consumes more flash than it was supposed to save. --- lib/Common/src/IJsonConfig.h | 109 ------------------------ lib/DateTimePlugin/src/DateTimePlugin.h | 2 +- lib/Plugin/src/PluginWithConfig.hpp | 27 +++--- lib/Utilities/library.json | 2 + lib/Views/src/IDateTimeView.h | 35 +++++++- 5 files changed, 52 insertions(+), 123 deletions(-) delete mode 100644 lib/Common/src/IJsonConfig.h diff --git a/lib/Common/src/IJsonConfig.h b/lib/Common/src/IJsonConfig.h deleted file mode 100644 index e6d5840a..00000000 --- a/lib/Common/src/IJsonConfig.h +++ /dev/null @@ -1,109 +0,0 @@ -/* MIT License - * - * Copyright (c) 2024 - 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 Json Configuration interface - * @author Andreas Merkle - * - * @addtogroup hal - * - * @{ - */ - -#ifndef IJSON_CONFIG_H -#define IJSON_CONFIG_H - -/****************************************************************************** - * Compile Switches - *****************************************************************************/ - -/****************************************************************************** - * Includes - *****************************************************************************/ -#include - -/****************************************************************************** - * Macros - *****************************************************************************/ - -/****************************************************************************** - * Types and Classes - *****************************************************************************/ - -/** - * This interface defines the functions required for JSON bases configuration - * updates. It is used to provide and apply configuration settings to and from - * a REST API. - */ -class IJsonConfig -{ -public: - - /** - * Destroys the interface object - */ - virtual ~IJsonConfig() - { - } - - /** - * Get current active configuration in JSON format. - * - * @param[out] cfg Configuration - */ - virtual void getConfiguration(JsonObject& jsonCfg) const = 0; - - /** - * Apply configuration from JSON. - * - * @param[in] cfg Configuration - * - * @return If successful set, it will return true otherwise false. - */ - virtual bool setConfiguration(const JsonObjectConst& jsonCfg) = 0; - - /** - * Merge JSON configuration with local settings to create a complete set. - * - * The received configuration may not contain all single key/value pair. - * Therefore create a complete internal configuration and overwrite it - * with the received one. - * - * @param[out] jsonMerged The complete config set with merge content from jsonSource. - * @param[in] jsonSource The recevied congi set, which may not cover all keys. - * @return true Keys needed merging. - * @return false Nothing needed merging. - */ - virtual bool mergeConfiguration(JsonObject& jsonMerged, const JsonObjectConst& jsonSource) = 0; -}; - -/****************************************************************************** - * Functions - *****************************************************************************/ - -#endif /* IJSON_CONFIG_H */ - -/** @} */ \ No newline at end of file diff --git a/lib/DateTimePlugin/src/DateTimePlugin.h b/lib/DateTimePlugin/src/DateTimePlugin.h index 86f3f3dc..8ba36523 100644 --- a/lib/DateTimePlugin/src/DateTimePlugin.h +++ b/lib/DateTimePlugin/src/DateTimePlugin.h @@ -342,7 +342,7 @@ class DateTimePlugin : public PluginWithConfig * @return true Keys needed merging. * @return false Nothing needed merging. */ - bool mergeConfiguration(JsonObject& jsonMerged, const JsonObjectConst& jsonSource) final; + bool mergeConfiguration(JsonObject& jsonMerged, const JsonObjectConst& jsonSource); /** * Get current date/time and update the text, which to be displayed. diff --git a/lib/Plugin/src/PluginWithConfig.hpp b/lib/Plugin/src/PluginWithConfig.hpp index 819fbc80..ded1cff6 100644 --- a/lib/Plugin/src/PluginWithConfig.hpp +++ b/lib/Plugin/src/PluginWithConfig.hpp @@ -44,7 +44,6 @@ * Includes *****************************************************************************/ #include "Plugin.hpp" -#include "IJsonConfig.h" #include #include @@ -65,7 +64,7 @@ * Attention: Every derived class must call start(), stop() and process() * of this base class to get the configuration file handling working! */ -class PluginWithConfig : public Plugin, IJsonConfig +class PluginWithConfig : public Plugin { public: @@ -191,6 +190,22 @@ class PluginWithConfig : public Plugin, IJsonConfig { } + /** + * Get configuration in JSON. + * + * @param[out] cfg Configuration + */ + virtual void getConfiguration(JsonObject& cfg) const = 0; + + /** + * Set configuration in JSON. + * + * @param[in] cfg Configuration + * + * @return If successful set, it will return true otherwise false. + */ + virtual bool setConfiguration(const JsonObjectConst& cfg) = 0; + /** * Request to store configuration to persistent memory. */ @@ -317,14 +332,6 @@ class PluginWithConfig : public Plugin, IJsonConfig SimpleTimer m_cfgReloadTimer; /**< Timer is used to cyclic reload the configuration from persistent memory. */ bool m_storeConfigReq; /**< Is requested to store the configuration in persistent memory? */ bool m_reloadConfigReq; /**< Is requested to reload the configuration from persistent memory? */ - - /*TODO REMOVE if all plugins have it. Right now only in DateTimePlugin. */ - bool mergeConfiguration(JsonObject& jsonMerged, const JsonObjectConst& jsonSource) - { - return true; - } - - }; /****************************************************************************** diff --git a/lib/Utilities/library.json b/lib/Utilities/library.json index 682723e6..35ce89a3 100644 --- a/lib/Utilities/library.json +++ b/lib/Utilities/library.json @@ -17,6 +17,8 @@ "owner": "bblanchon", "name": "ArduinoJson", "version": "~6.21.5" + }, { + "name": "YAGfx" }, { "name": "FS" }, { diff --git a/lib/Views/src/IDateTimeView.h b/lib/Views/src/IDateTimeView.h index 57d6b73a..a6425690 100644 --- a/lib/Views/src/IDateTimeView.h +++ b/lib/Views/src/IDateTimeView.h @@ -42,13 +42,12 @@ /****************************************************************************** * Includes *****************************************************************************/ +#include #include #include #include #include -#include "IJsonConfig.h" - /****************************************************************************** * Macros *****************************************************************************/ @@ -60,7 +59,7 @@ /** * Interface for a view with date and time. */ -class IDateTimeView : public IJsonConfig +class IDateTimeView { public: @@ -195,6 +194,36 @@ class IDateTimeView : public IJsonConfig */ virtual void setCurrentTime(const tm& now) = 0; + /** + * Get current active configuration in JSON format. + * + * @param[out] cfg Configuration + */ + virtual void getConfiguration(JsonObject& jsonCfg) const = 0; + + /** + * Apply configuration from JSON. + * + * @param[in] cfg Configuration + * + * @return If successful set, it will return true otherwise false. + */ + virtual bool setConfiguration(const JsonObjectConst& jsonCfg) = 0; + + /** + * Merge JSON configuration with local settings to create a complete set. + * + * The received configuration may not contain all single key/value pair. + * Therefore create a complete internal configuration and overwrite it + * with the received one. + * + * @param[out] jsonMerged The complete config set with merge content from jsonSource. + * @param[in] jsonSource The recevied congi set, which may not cover all keys. + * @return true Keys needed merging. + * @return false Nothing needed merging. + */ + virtual bool mergeConfiguration(JsonObject& jsonMerged, const JsonObjectConst& jsonSource) = 0; + protected: /** From 705da2d5c4daff85d1c75aa0ffdcc5277c156de5 Mon Sep 17 00:00:00 2001 From: nhjschulz Date: Sun, 1 Dec 2024 16:01:20 +0100 Subject: [PATCH 3/4] Fix analoc clock view topic merge to support REST API Allow incomplete JSON input like comming from REST parameter. --- lib/Views/src/layouts/DateTimeView64x64.cpp | 24 +++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/Views/src/layouts/DateTimeView64x64.cpp b/lib/Views/src/layouts/DateTimeView64x64.cpp index f117d71f..377fdda8 100644 --- a/lib/Views/src/layouts/DateTimeView64x64.cpp +++ b/lib/Views/src/layouts/DateTimeView64x64.cpp @@ -357,8 +357,28 @@ bool DateTimeView64x64::mergeConfiguration(JsonObject& jsonMerged, const JsonObj if (false == jsonAnalogClock.isNull()) { - jsonMerged["analogClock"] = jsonAnalogClock; - result = true; + /* Analog clock data present in jsonSource, patch it into JsonMerged. + * Note: Not all paramters may be present in jsonSoure, test for all individually. + */ + + JsonObject jsonMergedAnalogClock = jsonMerged["analogClock"]; + + JsonVariantConst jsonSecondsMode = jsonAnalogClock["secondsMode"]; + if (true == jsonSecondsMode.is()) + { + jsonMergedAnalogClock["secondsMode"] = jsonSecondsMode; + result = true; + } + + for (uint32_t index = 0U; index < ANA_CLK_COL_MAX; ++index) + { + JsonVariantConst jsonColor = jsonAnalogClock[ANALOG_CLOCK_COLOR_KEYS[index]]; + if (true == jsonColor.is()) + { + jsonMergedAnalogClock[ANALOG_CLOCK_COLOR_KEYS[index]] = jsonColor; + result = true; + } + } } return result; From 2e8ce093e6d65745c32b270dc43fabe186f82db0 Mon Sep 17 00:00:00 2001 From: nhjschulz Date: Wed, 4 Dec 2024 17:42:39 +0100 Subject: [PATCH 4/4] AnalogClock: Report error on missing config data Side: HTML page: Removed dublicated sending of handHourCol value. It was moved to "analogClock" node, but forgotten to get removed at the former position, --- lib/DateTimePlugin/web/DateTimePlugin.html | 2 -- lib/Views/src/layouts/DateTimeView64x64.cpp | 7 ++++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/DateTimePlugin/web/DateTimePlugin.html b/lib/DateTimePlugin/web/DateTimePlugin.html index f7c58900..a7bcd529 100644 --- a/lib/DateTimePlugin/web/DateTimePlugin.html +++ b/lib/DateTimePlugin/web/DateTimePlugin.html @@ -318,9 +318,7 @@

Display

startOfWeek: $("#startOfWeek").val(), dayOnColor: $("#dayOnColor").val(), dayOffColor: $("#dayOffColor").val(), - handHourCol: $("#handHourCol").val(), viewMode: $('input[name=viewMode]:checked').val() - }; if ((64 <= ~DISPLAY_HEIGHT~) && (64 <= ~DISPLAY_WIDTH~)) diff --git a/lib/Views/src/layouts/DateTimeView64x64.cpp b/lib/Views/src/layouts/DateTimeView64x64.cpp index 377fdda8..65726db8 100644 --- a/lib/Views/src/layouts/DateTimeView64x64.cpp +++ b/lib/Views/src/layouts/DateTimeView64x64.cpp @@ -321,7 +321,7 @@ bool DateTimeView64x64::setConfiguration(const JsonObjectConst& jsonCfg) if ((false == jsonSecondsMode.is()) && (SECONDS_DISP_MAX <= jsonSecondsMode.as())) { - LOG_WARNING("JSON seconds mode not found or invalid type."); + LOG_WARNING("JSON attribute %s not found or invalid type.", "secondsMode"); result = false; } else @@ -346,6 +346,11 @@ bool DateTimeView64x64::setConfiguration(const JsonObjectConst& jsonCfg) } } } + else + { + LOG_WARNING("JSON attribute %s not found or invalid type.", "analogClock"); + result = false; + } return result; }