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..8ba36523 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); + /** * 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/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/Plugin/src/PluginWithConfig.hpp b/lib/Plugin/src/PluginWithConfig.hpp index 6c0cec3e..ded1cff6 100644 --- a/lib/Plugin/src/PluginWithConfig.hpp +++ b/lib/Plugin/src/PluginWithConfig.hpp @@ -332,7 +332,6 @@ class PluginWithConfig : public Plugin 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? */ - }; /****************************************************************************** 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/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..a6425690 100644 --- a/lib/Views/src/IDateTimeView.h +++ b/lib/Views/src/IDateTimeView.h @@ -42,6 +42,7 @@ /****************************************************************************** * Includes *****************************************************************************/ +#include #include #include #include @@ -81,38 +82,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. */ @@ -219,27 +188,41 @@ class IDateTimeView virtual bool setViewMode(ViewMode mode) = 0; /** - * Get the analog clock clonfiguration. + * @brief Update current time values in view. * - * @return AnalogClockConfig or nullptr if unsupported. + * @param[in] now current time */ - virtual const AnalogClockConfig* getAnalogClockConfig() const = 0; + virtual void setCurrentTime(const tm& now) = 0; - /** - * Set the analog clock configuration. - * - * @param[in] cfg The new configuration to apply. + /** + * Get current active configuration in JSON format. * - * @return success or failure + * @param[out] cfg Configuration */ - virtual bool setAnalogClockConfig(const AnalogClockConfig& cfg) = 0; + virtual void getConfiguration(JsonObject& jsonCfg) const = 0; /** - * @brief Update current time values in view. + * Apply configuration from JSON. * - * @param[in] now current time + * @param[in] cfg Configuration + * + * @return If successful set, it will return true otherwise false. */ - virtual void setCurrentTime(const tm& now) = 0; + 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: 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..65726db8 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,99 @@ 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 attribute %s not found or invalid type.", "secondsMode"); + 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); + } + } + } + } + else + { + LOG_WARNING("JSON attribute %s not found or invalid type.", "analogClock"); + result = false; + } + + return result; +} + +bool DateTimeView64x64::mergeConfiguration(JsonObject& jsonMerged, const JsonObjectConst& jsonSource) +{ + bool result = false; + JsonObjectConst jsonAnalogClock = jsonSource["analogClock"]; + + if (false == jsonAnalogClock.isNull()) + { + /* 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; +} + /****************************************************************************** * 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.