From ad5c746fb33bcde4eb9edc9b8e3e65602361e3f2 Mon Sep 17 00:00:00 2001 From: Dave Davenport Date: Fri, 12 Jan 2024 20:29:24 +0100 Subject: [PATCH 1/7] Add option to set ntp interval. --- software/wordclock/src/src/Iot.cpp | 11 ++++++++--- software/wordclock/src/src/Iot.h | 7 +++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/software/wordclock/src/src/Iot.cpp b/software/wordclock/src/src/Iot.cpp index 37e0267..67cdf5b 100644 --- a/software/wordclock/src/src/Iot.cpp +++ b/software/wordclock/src/src/Iot.cpp @@ -24,8 +24,6 @@ #define HTTP_OK 200 // NTP server. #define NTP_SERVER "pool.ntp.org" -// When NTP is enabled, define how often to update the RTC. -#define NTP_POLL_DELAY_SECONDS 86400 // Once a day is plenty enough. namespace { @@ -264,6 +262,9 @@ Iot::Iot(Display *display, RTC_DS3231 *rtc) boot_animation_param_( "Startup animation", "boot_animation_enabled", boot_animation_enabled_value_, IOT_CONFIG_VALUE_LENGTH, "1", 0, 1, 1, "style='width: 40px;' data-labels='Off|On'"), + ntp_interval_param_( + "Interval for ntp synchronization (requires WiFi)", "ntp_interval", ntp_interval_value_, + IOT_CONFIG_VALUE_LENGTH, "24", 1, 24,1, "data-labels='1'"), ntp_enabled_param_( "Use network time (requires WiFi)", "ntp_enabled", ntp_enabled_value_, IOT_CONFIG_VALUE_LENGTH, "0", 0, 1, 1, "style='width: 40px;' data-labels='Off|On'"), @@ -281,6 +282,7 @@ Iot::Iot(Display *display, RTC_DS3231 *rtc) this->ldr_sensitivity_value_[0] = '\0'; this->color_value_[0] = '\0'; this->ntp_enabled_value_[0] = '\0'; + this->ntp_interval_value_[0] = '\0'; this->timezone_value_[0] = '\0'; } @@ -328,6 +330,7 @@ void Iot::updateClockFromParams_() display_->setShowAmPm(parseBooleanValue(show_ampm_value_)); display_->setSensorSentivity(parseNumberValue(ldr_sensitivity_value_, 0, 10, 5)); + this->ntp_poll_interval_ = parseNumberValue(ntp_enabled_value_, 1,24,24); if (parseBooleanValue(ntp_enabled_value_)) { ntp_poll_timer_.start(); @@ -346,12 +349,13 @@ void Iot::setup() ntp_poll_timer_.setup([this]() { maybeSetRTCfromNTP_(); }, - NTP_POLL_DELAY_SECONDS); + ntp_poll_interval_*60*60); this->show_ampm_value_[0] = '\0'; this->ldr_sensitivity_value_[0] = '\0'; this->color_value_[0] = '\0'; this->ntp_enabled_value_[0] = '\0'; + this->ntp_interval_value_[0] = '\0'; this->timezone_value_[0] = '\0'; iot_web_conf_.setupUpdateServer( @@ -366,6 +370,7 @@ void Iot::setup() iot_web_conf_.addParameterGroup(&display_group_); time_group_.addItem(&ntp_enabled_param_); + time_group_.addItem(&ntp_interval_param_); time_group_.addItem(&timezone_param_); time_group_.addItem(&manual_time_param_); iot_web_conf_.addParameterGroup(&time_group_); diff --git a/software/wordclock/src/src/Iot.h b/software/wordclock/src/src/Iot.h index 8868196..5b71e28 100644 --- a/software/wordclock/src/src/Iot.h +++ b/software/wordclock/src/src/Iot.h @@ -62,6 +62,7 @@ class Iot RTC_DS3231 *rtc_ = nullptr; // NTP poll timer; + int ntp_poll_interval_ = 24; Timer ntp_poll_timer_; // Enables NTP time setting. @@ -104,6 +105,12 @@ class Iot // Value of the boot animation setting option. char boot_animation_enabled_value_[IOT_CONFIG_VALUE_LENGTH]; + + // Enables the ntp interval. + IotRangeValueParameter ntp_interval_param_; + // Value of the ntp interval setting option. + char ntp_interval_value_[IOT_CONFIG_VALUE_LENGTH]; + // Config form groups. iotwebconf::ParameterGroup time_group_; iotwebconf::ParameterGroup display_group_; From fa44d93036cb45e97869dfa99ec9d6944c19cbdf Mon Sep 17 00:00:00 2001 From: Dave Davenport Date: Fri, 12 Jan 2024 20:31:59 +0100 Subject: [PATCH 2/7] Better explanation of option. --- software/wordclock/src/src/Iot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/wordclock/src/src/Iot.cpp b/software/wordclock/src/src/Iot.cpp index 67cdf5b..76f0f4e 100644 --- a/software/wordclock/src/src/Iot.cpp +++ b/software/wordclock/src/src/Iot.cpp @@ -263,7 +263,7 @@ Iot::Iot(Display *display, RTC_DS3231 *rtc) "Startup animation", "boot_animation_enabled", boot_animation_enabled_value_, IOT_CONFIG_VALUE_LENGTH, "1", 0, 1, 1, "style='width: 40px;' data-labels='Off|On'"), ntp_interval_param_( - "Interval for ntp synchronization (requires WiFi)", "ntp_interval", ntp_interval_value_, + "Refresh interval in hours for ntp synchronization (requires WiFi)", "ntp_interval", ntp_interval_value_, IOT_CONFIG_VALUE_LENGTH, "24", 1, 24,1, "data-labels='1'"), ntp_enabled_param_( "Use network time (requires WiFi)", "ntp_enabled", ntp_enabled_value_, From e658c3dbe1bc79b53ad2ea52417878e04cf6f9e3 Mon Sep 17 00:00:00 2001 From: Dave Davenport Date: Fri, 12 Jan 2024 21:14:46 +0100 Subject: [PATCH 3/7] Support not having an RTC chip attached. --- software/wordclock/src/src/Iot.cpp | 66 ++++++++++++++++++++-------- software/wordclock/src/src/Iot.h | 6 +++ software/wordclock/src/wordclock.cpp | 31 ++++++++++--- 3 files changed, 78 insertions(+), 25 deletions(-) diff --git a/software/wordclock/src/src/Iot.cpp b/software/wordclock/src/src/Iot.cpp index 76f0f4e..64e6d5c 100644 --- a/software/wordclock/src/src/Iot.cpp +++ b/software/wordclock/src/src/Iot.cpp @@ -201,7 +201,11 @@ body > div > div:last-child {\ { DLOG("[INFO] Could not parse number value \""); DLOG(str); - DLOGLN("\"."); + DLOG("\" "); + DLOG(min_value); + DLOG(" <, >"); + DLOG(max_value); + DLOGLN("."); return default_value; } return parsed_value; @@ -443,7 +447,10 @@ void Iot::maybeSetRTCfromNTP_() digitalWrite(LED_PIN, HIGH); #endif - rtc_->adjust(DateTime(timeinfo.tm_year, timeinfo.tm_mon, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec)); + if (get_rtc_found()) { + rtc_->adjust(DateTime(timeinfo.tm_year, timeinfo.tm_mon, timeinfo.tm_mday, + timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec)); + } DLOG("RTC set to:"); DLOGLN(&timeinfo, "%A, %B %d %Y %H:%M:%S"); DLOG("Timezone:"); @@ -452,23 +459,46 @@ void Iot::maybeSetRTCfromNTP_() void Iot::setRTCfromConfig_() { - const DateTime now = rtc_->now(); - uint16_t year = now.year(); - uint8_t month = now.month(); - uint8_t day = now.day(); - uint8_t hour = now.hour(); - uint8_t minute = now.minute(); - uint8_t second = now.second(); - bool datetime_changed = false; - - if (manual_time_value_[0] != 0 && parseTimeValue(manual_time_value_, &hour, &minute, &second)) - { - datetime_changed = true; - } + if (get_rtc_found()) { + const DateTime now = rtc_->now(); + uint16_t year = now.year(); + uint8_t month = now.month(); + uint8_t day = now.day(); + uint8_t hour = now.hour(); + uint8_t minute = now.minute(); + uint8_t second = now.second(); + bool datetime_changed = false; + + if (manual_time_value_[0] != 0 && + parseTimeValue(manual_time_value_, &hour, &minute, &second)) { + datetime_changed = true; + } - if (datetime_changed) - { - rtc_->adjust(DateTime(year, month, day, hour, minute, second)); + if (datetime_changed) { + rtc_->adjust(DateTime(year, month, day, hour, minute, second)); + } + } else { + uint16_t year = 0; + uint8_t month = 0; + uint8_t day = 0; + uint8_t hour = 0; + uint8_t minute = 0; + uint8_t second = 0; + if (manual_time_value_[0] != 0 && + parseTimeValue(manual_time_value_, &hour, &minute, &second)) { + struct tm timeinfo; + if (getLocalTime(&timeinfo)) + { + timeinfo.tm_hour = hour; + timeinfo.tm_min = minute; + timeinfo.tm_sec = second; + time_t t = mktime(&timeinfo); + struct timeval tv; + tv.tv_usec = 0; + tv.tv_sec = t; + settimeofday(&tv, NULL); + } + } } } diff --git a/software/wordclock/src/src/Iot.h b/software/wordclock/src/src/Iot.h index 5b71e28..8c0d95a 100644 --- a/software/wordclock/src/src/Iot.h +++ b/software/wordclock/src/src/Iot.h @@ -28,6 +28,10 @@ class Iot void setup(); void loop(); + // accessor for rtc_found + bool get_rtc_found() { return rtc_found_; } + void set_rtc_found(bool b) { rtc_found_ = b; } + private: // Clears the values of transient parameters. void clearTransientParams_(); @@ -60,6 +64,8 @@ class Iot // RTC. RTC_DS3231 *rtc_ = nullptr; + bool rtc_found_ = false; + // NTP poll timer; int ntp_poll_interval_ = 24; diff --git a/software/wordclock/src/wordclock.cpp b/software/wordclock/src/wordclock.cpp index 43cf04a..d4947ae 100644 --- a/software/wordclock/src/wordclock.cpp +++ b/software/wordclock/src/wordclock.cpp @@ -20,6 +20,7 @@ #include "src/Iot.h" #include "src/nodo.h" // Nodo stuff RTC_DS3231 rtc; +boolean rtc_found = false; // TODO: refactor so we don't need to initialize another clockface here. #ifdef NODO @@ -42,12 +43,16 @@ void setup() #ifdef SDA_PIN Wire.begin(SDA_PIN, SCL_PIN); #endif - bool result = rtc.begin(); // pins for RTC swapped over on V1.0 - DCHECK(result, "RTC didn't start"); - if (rtc.lostPower()) - { - DCHECK("RTC lost power. Battery was removed ?"); + rtc_found = rtc.begin(); // pins for RTC swapped over on V1.0 + if (rtc_found) { + DLOG("RTC detected"); + if (rtc.lostPower()) { + DCHECK("RTC lost power. Battery was removed ?"); + } + } else { + DLOG("RTC not found"); } + iot.set_rtc_found(rtc_found); display.setup(); iot.setup(); @@ -58,8 +63,20 @@ void loop() { long ts_now = millis(); if ((ts_now - last_rtc_update) > 99) { - DateTime now = rtc.now(); - display.updateForTime(now.hour(), now.minute(), now.second()); + if ( rtc_found ) { + DateTime now = rtc.now(); + display.updateForTime(now.hour(), now.minute(), now.second()); + } else { + struct tm timeinfo; + // Get the local time from the esp's rtc. + if (getLocalTime(&timeinfo)) { + auto now = DateTime(timeinfo.tm_year, timeinfo.tm_mon, timeinfo.tm_mday, + timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); + display.updateForTime(now.hour(), now.minute(), now.second()); + } else { + display.updateForTime(0, 0, 0); + } + } last_rtc_update = ts_now; } From 44a75051b1320e0fad63f6b6a6aa14ff4a929444 Mon Sep 17 00:00:00 2001 From: Dave Davenport Date: Fri, 12 Jan 2024 21:38:50 +0100 Subject: [PATCH 4/7] Fix setting manual time. --- software/wordclock/src/src/Iot.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/software/wordclock/src/src/Iot.cpp b/software/wordclock/src/src/Iot.cpp index 64e6d5c..4f78bbb 100644 --- a/software/wordclock/src/src/Iot.cpp +++ b/software/wordclock/src/src/Iot.cpp @@ -486,18 +486,21 @@ void Iot::setRTCfromConfig_() uint8_t second = 0; if (manual_time_value_[0] != 0 && parseTimeValue(manual_time_value_, &hour, &minute, &second)) { - struct tm timeinfo; - if (getLocalTime(&timeinfo)) + struct tm timeinfo = {0, }; + if (!getLocalTime(&timeinfo)) { - timeinfo.tm_hour = hour; - timeinfo.tm_min = minute; - timeinfo.tm_sec = second; - time_t t = mktime(&timeinfo); - struct timeval tv; - tv.tv_usec = 0; - tv.tv_sec = t; - settimeofday(&tv, NULL); + DLOG("Local time invalid. set some 'usable' data"); + timeinfo.tm_year = (2016 - 1900)+1; + timeinfo.tm_mon = 1; } + timeinfo.tm_hour = hour; + timeinfo.tm_min = minute; + timeinfo.tm_sec = second; + time_t t = mktime(&timeinfo); + struct timeval tv; + tv.tv_usec = 0; + tv.tv_sec = t; + settimeofday(&tv, NULL); } } } From ee1f331598dbda779eac380edf9685e76a1486fa Mon Sep 17 00:00:00 2001 From: Dave Davenport Date: Sat, 13 Jan 2024 11:42:30 +0100 Subject: [PATCH 5/7] Add Ring clockface --- software/wordclock/platformio.ini | 11 +++++++++++ software/wordclock/src/src/ClockFace.cpp | 22 ++++++++++++++++++++++ software/wordclock/src/src/ClockFace.h | 12 +++++++++++- software/wordclock/src/src/Iot.cpp | 16 ++++++++++++++-- software/wordclock/src/src/clockfaces.h | 3 ++- software/wordclock/src/src/nodo.h | 5 +++++ 6 files changed, 65 insertions(+), 4 deletions(-) diff --git a/software/wordclock/platformio.ini b/software/wordclock/platformio.ini index 5391adf..c023d26 100644 --- a/software/wordclock/platformio.ini +++ b/software/wordclock/platformio.ini @@ -28,6 +28,17 @@ lib_deps = https://github.com/Makuna/NeoPixelBus.git https://github.com/prampec/IotWebConf.git +[env:ring] +platform = espressif32 +board = esp32-c3-devkitm-1 +framework = arduino +monitor_dtr = 0 +build_flags = -DDEBUG -DRING +lib_deps = + https://github.com/adafruit/RTClib.git + https://github.com/Makuna/NeoPixelBus.git + https://github.com/prampec/IotWebConf.git + [env:nodo1] platform = espressif32 board = pico32 diff --git a/software/wordclock/src/src/ClockFace.cpp b/software/wordclock/src/src/ClockFace.cpp index 38b4ff2..8b493e9 100644 --- a/software/wordclock/src/src/ClockFace.cpp +++ b/software/wordclock/src/src/ClockFace.cpp @@ -969,3 +969,25 @@ case 0: } return true; } + + + + + +bool RingClockFace::stateForTime(int hour, int minute, int second, bool show_ampm) +{ + if (hour == _hour && minute == _minute) + { + return false; + } + _hour = hour; + _minute = minute; + + DLOGLN("update state"); + clearDisplay(); + updateSegment(hour,0,1); + updateSegment(24+minute,0,1); + + + return true; +} \ No newline at end of file diff --git a/software/wordclock/src/src/ClockFace.h b/software/wordclock/src/src/ClockFace.h index c0aee54..7e9ad6f 100644 --- a/software/wordclock/src/src/ClockFace.h +++ b/software/wordclock/src/src/ClockFace.h @@ -29,7 +29,7 @@ class ClockFace const std::vector &getState() { return _state; }; // Returns the index of the LED in the strip given a position on the grid. - uint16_t map(int16_t x, int16_t y); + virtual uint16_t map(int16_t x, int16_t y); // The first four LED are the corner ones, counting minutes. They are assumed // to be wired in clockwise order, starting from the light sensor position. @@ -93,3 +93,13 @@ class ItalianClockFace : public ClockFace virtual bool stateForTime(int hour, int minute, int second, bool show_ampm); }; + + +class RingClockFace : public ClockFace +{ +public: + RingClockFace(LightSensorPosition position) : ClockFace(position){}; + + virtual bool stateForTime(int hour, int minute, int second, bool show_ampm); + virtual uint16_t map(int16_t x, int16_t y) {return x;} +}; \ No newline at end of file diff --git a/software/wordclock/src/src/Iot.cpp b/software/wordclock/src/src/Iot.cpp index ba40f4e..0322930 100644 --- a/software/wordclock/src/src/Iot.cpp +++ b/software/wordclock/src/src/Iot.cpp @@ -1,3 +1,6 @@ +extern "C" { +#include +} #include "logging.h" #include "Iot.h" #include @@ -262,13 +265,13 @@ Iot::Iot(Display *display, RTC_DS3231 *rtc) IOT_CONFIG_VALUE_LENGTH, "5", 0, 10, 1, "data-labels='Off'"), clockface_language_param_( "Clock face language", "clockface_language", clockface_language_value_, IOT_CONFIG_VALUE_LENGTH, - DEFAULT_CLOCKFACE_LANGUAGE, DEFAULT_CLOCKFACE_LANGUAGE, "data-options='English|Dutch|French|Italian'"), + DEFAULT_CLOCKFACE_LANGUAGE, DEFAULT_CLOCKFACE_LANGUAGE, "data-options='English|Dutch|French|Italian|Ring'"), boot_animation_param_( "Startup animation", "boot_animation_enabled", boot_animation_enabled_value_, IOT_CONFIG_VALUE_LENGTH, "1", 0, 1, 1, "style='width: 40px;' data-labels='Off|On'"), ntp_interval_param_( "Refresh interval in hours for ntp synchronization (requires WiFi)", "ntp_interval", ntp_interval_value_, - IOT_CONFIG_VALUE_LENGTH, "24", 1, 24,1, "data-labels='1'"), + IOT_CONFIG_VALUE_LENGTH, "24", 1, 24,1, "data-controlledby='ntp_enabled' data-showon='1'"), ntp_enabled_param_( "Use network time (requires WiFi)", "ntp_enabled", ntp_enabled_value_, IOT_CONFIG_VALUE_LENGTH, "0", 0, 1, 1, "style='width: 40px;' data-labels='Off|On'"), @@ -321,6 +324,12 @@ void Iot::updateClockFromParams_() DLOGLN("Language set to Italian"); break; } + case 4: + { + display_->setClockFace(&clockFaceRING); + DLOGLN("Language set to Ring"); + break; + } default: { display_->setClockFace(&clockFaceEN); @@ -431,6 +440,9 @@ void Iot::maybeSetRTCfromNTP_() } int tz = parseNumberValue(timezone_value_, 0, 459, 0); +#if SNTP_GET_SERVERS_FROM_DHCP || SNTP_GET_SERVERS_FROM_DHCPV6 + sntp_servermode_dhcp(1); +#endif configTzTime(posix[tz], NTP_SERVER); struct tm timeinfo; diff --git a/software/wordclock/src/src/clockfaces.h b/software/wordclock/src/src/clockfaces.h index f56fd20..d848033 100644 --- a/software/wordclock/src/src/clockfaces.h +++ b/software/wordclock/src/src/clockfaces.h @@ -9,4 +9,5 @@ auto position = ClockFace::LightSensorPosition::Bottom; EnglishClockFace clockFaceEN(position); FrenchClockFace clockFaceFR(position); DutchClockFace clockFaceNL(position); -ItalianClockFace clockFaceIT(position); \ No newline at end of file +ItalianClockFace clockFaceIT(position); +RingClockFace clockFaceRING(position); \ No newline at end of file diff --git a/software/wordclock/src/src/nodo.h b/software/wordclock/src/src/nodo.h index af36503..1f85ea7 100644 --- a/software/wordclock/src/src/nodo.h +++ b/software/wordclock/src/src/nodo.h @@ -11,4 +11,9 @@ #define LDR_PIN 1 #define NEOPIXEL_PIN 0 #endif +#else +#ifdef RING +#define NEOPIXEL_PIN 1 +#define LDR_PIN 2 +#endif // RING #endif From f1222708c2c6e2df3c3496340f712601e4dbd8db Mon Sep 17 00:00:00 2001 From: Dave Davenport Date: Sat, 13 Jan 2024 18:32:28 +0100 Subject: [PATCH 6/7] Use 12h hour Visualization --- software/wordclock/src/src/ClockFace.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/software/wordclock/src/src/ClockFace.cpp b/software/wordclock/src/src/ClockFace.cpp index 8b493e9..9ef36b9 100644 --- a/software/wordclock/src/src/ClockFace.cpp +++ b/software/wordclock/src/src/ClockFace.cpp @@ -985,7 +985,8 @@ bool RingClockFace::stateForTime(int hour, int minute, int second, bool show_amp DLOGLN("update state"); clearDisplay(); - updateSegment(hour,0,1); + int h = (hour%12)*2+((minute >= 30)?1:0); + updateSegment(h,0,1); updateSegment(24+minute,0,1); From 94ed78419dd5818c00be7dfabb4631ba85b26992 Mon Sep 17 00:00:00 2001 From: Dave Davenport Date: Mon, 15 Jan 2024 22:48:02 +0100 Subject: [PATCH 7/7] Update merge. --- software/wordclock/src/src/Iot.h | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/software/wordclock/src/src/Iot.h b/software/wordclock/src/src/Iot.h index 77b4c11..a895ee8 100644 --- a/software/wordclock/src/src/Iot.h +++ b/software/wordclock/src/src/Iot.h @@ -69,6 +69,10 @@ class Iot WebServer web_server_; // Server for OTA firmware update. HTTPUpdateServer http_updater_; + // MQTT client. + MQTTClient mqtt_client_; + // WiFi client (for MQTT). + WiFiClient net_; // Word clock display. Display *display_ = nullptr; @@ -148,32 +152,6 @@ class Iot // MQTT password value. char mqtt_password_value_[IOT_CONFIG_VALUE_LENGTH]; - // Enable AM/PM display. - IotRangeValueParameter show_ampm_param_; - // Value of the LDR sensitivity parameter. - char show_ampm_value_[IOT_CONFIG_VALUE_LENGTH]; - - // Sensitivity parameter for the LDR. - IotRangeValueParameter ldr_sensitivity_param_; - // Value of the LDR sensitivity parameter. - char ldr_sensitivity_value_[IOT_CONFIG_VALUE_LENGTH]; - - // Text color parameter. - iotwebconf::TextParameter color_param_; - // Value of the color parameter. - char color_value_[IOT_CONFIG_VALUE_LENGTH]; - - // Clockface language selctor. - iotwebconf::NumberParameter clockface_language_param_; - // Value of the LDR sensitivity parameter. - char clockface_language_value_[IOT_CONFIG_VALUE_LENGTH]; - - // Enables the boot animation. - IotRangeValueParameter boot_animation_param_; - // Value of the boot animation setting option. - char boot_animation_enabled_value_[IOT_CONFIG_VALUE_LENGTH]; - - // Enables the ntp interval. IotRangeValueParameter ntp_interval_param_; // Value of the ntp interval setting option.