From 73b390d8cd3883f488665439860076ee39f91d90 Mon Sep 17 00:00:00 2001 From: Markus Ransberger Date: Thu, 4 Apr 2024 16:40:16 +0200 Subject: [PATCH] Introduction of state machine for TimeManager. Minor refactoring. --- include/time_manager.h | 27 ++++++++--- include/wordclock_constants.h | 2 +- src/connectivity/time_manager.cpp | 75 +++++++++++++++++++++---------- src/wordclock_esp8266.cpp | 46 +++++++++++++------ 4 files changed, 105 insertions(+), 45 deletions(-) diff --git a/include/time_manager.h b/include/time_manager.h index 0544300..8c81d41 100644 --- a/include/time_manager.h +++ b/include/time_manager.h @@ -12,17 +12,29 @@ typedef enum NTP_UPDATE_PENDING = 2, NTP_UPDATE_RETRY_DELAY = 3, NTP_UPDATE_TOO_EARLY = 4, -} ntp_update_state; + NTP_UPDATE_SETUP_FAILED = 5, +} NtpUpdateState; + +typedef enum +{ + TM_INIT = 0, + TM_INITIAL_SYNC = 1, + TM_RETRY_SYNC = 2, + TM_NORMAL = 3, + TM_PROLONGED_SYNC_FAIL = 4, + TM_SETUP_FAILED = 5, +} TimeManagerState; class TimeManager { #define NTP_MAX_UPDATE_TIME_US (500 * 1000) // 500ms max update time public: - bool ntp_sync_successful(void) const; // was there a NTP sync once? - ntp_update_state ntp_time_update(bool init = false); - bool ntp_update_failed_prolonged(void) const; // indicates if maximum time since last NTP update was too long + bool ntp_sync_successful(void) const; // was there a NTP sync once? + bool ntp_update_failed_prolonged(void); // indicates if maximum time since last NTP update was too long + NtpUpdateState ntp_time_update(); struct tm time_info(void); + TimeManagerState tm_state(void) const; void increment_time_now_local(void); void log_time() const; // log _time_info void log_time(struct tm time_info) const; // log argument time_info @@ -42,11 +54,12 @@ public: UDPLogger *logger); private: - void set_up_ntp(void) const; // set up NTP server - void set_up_timer_isr(void) const; // set up timer interrupt + void _set_up_ntp(void); // set up NTP server + void _set_up_timer_isr(void); // set up timer interrupt const char *_tz; // timezone - const char *_ntp_server; // used ntp server + const char *_ntp_server; // ntp server address UDPLogger *_logger; // logger instance + TimeManagerState _tm_state = TM_INIT; // Main state struct tm _time_info = {0, 0, 0, 0, 0, 0, 0, 0, 0}; // structure tm holds time information time_t _time_now_local = 0; // local timer value, updated by timer interrupt and synced by NTP when needed time_t _time_now_ntp = 0; // NTP timer value, seconds since Epoch (1970) - UTC, only synced by NTP request. diff --git a/include/wordclock_constants.h b/include/wordclock_constants.h index 7b56bb2..5cd9ad4 100644 --- a/include/wordclock_constants.h +++ b/include/wordclock_constants.h @@ -34,7 +34,7 @@ // Timings in us #define PERIOD_ANIMATION_US (200 * 1000) // 200ms -#define PERIOD_CLOCK_UPDATE_US (1 * 1000 * 1000) // 1s +#define PERIOD_CLOCK_UPDATE_US (1 * 1000 * 1000) // Must be 1s! Do not change! #define PERIOD_HEARTBEAT_US (1 * 1000 * 1000) // 1s #define PERIOD_MATRIX_UPDATE_US (100 * 1000) // 100ms #define PERIOD_NIGHTMODE_CHECK_US (20 * 1000 * 1000) // 20s diff --git a/src/connectivity/time_manager.cpp b/src/connectivity/time_manager.cpp index 417411e..5bca332 100644 --- a/src/connectivity/time_manager.cpp +++ b/src/connectivity/time_manager.cpp @@ -31,6 +31,11 @@ struct tm TimeManager::time_info(void) return _time_info; } +TimeManagerState TimeManager::tm_state(void) const +{ + return _tm_state; +} + bool TimeManager::ntp_sync_successful(void) const { return (_time_now_ntp > 0); @@ -41,26 +46,33 @@ bool TimeManager::ntp_sync_successful(void) const * * @retval true if last update was successful */ -ntp_update_state TimeManager::ntp_time_update(bool init) +NtpUpdateState TimeManager::ntp_time_update() { - // Check if minimum update delay has elapsed - if (!init && ((system_get_time() - _ntp_sync_timestamp_us) <= _ntp_retry_delay_us)) + NtpUpdateState retval = NTP_UPDATE_PENDING; // NTP time update + struct tm time_info; // local NTP time info + + // Set up NTP server once + if (_tm_state == TM_INIT) { - return NTP_UPDATE_RETRY_DELAY; // could be replaced with appropriate enum + _set_up_ntp(); } - if (!init && ((system_get_time() - _ntp_sync_timestamp_us) <= (_ntp_update_period_s * 1000000))) + if (_tm_state == TM_SETUP_FAILED) { - return NTP_UPDATE_TOO_EARLY; // could be replaced with appropriate enum + return NTP_UPDATE_SETUP_FAILED; } - if (init) + // Check if minimum update delay has elapsed. This is always active to prevent too many NTP server requests! + if ((_tm_state != TM_INITIAL_SYNC) && (system_get_time() - _ntp_sync_timestamp_us) <= _ntp_retry_delay_us) { - set_up_ntp(); // set up NTP server once + return NTP_UPDATE_RETRY_DELAY; } - ntp_update_state ntp_update_state = NTP_UPDATE_PENDING; // NTP time update - struct tm time_info; // local NTP time info + // Check if it is time for a NTP sync. + if ((_tm_state != TM_INITIAL_SYNC) && (_tm_state != TM_RETRY_SYNC) && ((system_get_time() - _ntp_sync_timestamp_us) <= (_ntp_update_period_s * 1000000))) + { + return NTP_UPDATE_TOO_EARLY; + } _ntp_sync_timestamp_us = system_get_time(); // NTP update start time @@ -72,15 +84,15 @@ ntp_update_state TimeManager::ntp_time_update(bool init) } while (((system_get_time() - _ntp_sync_timestamp_us) <= NTP_MAX_UPDATE_TIME_US) && (time_info.tm_year < (NTP_MININUM_RX_YEAR - NTP_START_YEAR))); - ntp_update_state = (time_info.tm_year <= (NTP_MININUM_RX_YEAR - NTP_START_YEAR)) ? NTP_UPDATE_FAILED : NTP_UPDATE_OK; // sanity check + retval = (time_info.tm_year <= (NTP_MININUM_RX_YEAR - NTP_START_YEAR)) ? NTP_UPDATE_FAILED : NTP_UPDATE_OK; // sanity check - if (ntp_update_state == NTP_UPDATE_OK) + if (retval == NTP_UPDATE_OK) { _ntp_sync_timestamp_us = system_get_time(); // save NTP update timestamp _time_info = time_info; // take over time_info to member variable log_time(); // log current time - if (!init && (abs(_time_now_ntp - _time_now_local) > 10)) // in the case that the local time drifted more than 10s in _ntp_update_period_s + if ((_tm_state != TM_INITIAL_SYNC) && (abs(_time_now_ntp - _time_now_local) > 10)) // in the case that the local time drifted more than 10s in _ntp_update_period_s { _logger->log_string(String("Difference between local and NTP time was more than 10 seconds!\n")); _logger->log_string("Local time was: " + String(_time_now_local) + ", NTP time is: " + String(_time_now_ntp) + "\n"); @@ -88,22 +100,33 @@ ntp_update_state TimeManager::ntp_time_update(bool init) _time_now_local = _time_now_ntp; // sync local time with NTP time - if (init) // only set up the timer once after NTP update was successful + if (_tm_state == TM_INITIAL_SYNC) // only set up the timer once after NTP update was successful { - set_up_timer_isr(); + _set_up_timer_isr(); + } + else // set state to normal + { + _tm_state = TM_NORMAL; } } else { - logger.log_string("NTP-Update was not successful. Retrying in " + String(_ntp_retry_delay_us / 1000) + "ms.\n"); + _tm_state = TM_RETRY_SYNC; + logger.log_string("NTP-Update was not successful. Retrying in " + String(_ntp_retry_delay_us / 1000) + "ms."); } - return ntp_update_state; + return retval; } -bool TimeManager::ntp_update_failed_prolonged(void) const +bool TimeManager::ntp_update_failed_prolonged(void) { - return _time_now_local >= (_time_now_ntp + (time_t)_ntp_max_offline_time_s); + bool retval = false; + if (_time_now_local >= (_time_now_ntp + (time_t)_ntp_max_offline_time_s)) + { + _tm_state = TM_PROLONGED_SYNC_FAIL; + retval = true; + } + return retval; } int TimeManager::tm_min(void) @@ -146,15 +169,17 @@ bool TimeManager::tm_isdst(void) * @brief Sets up the timer ISR * */ -void TimeManager::set_up_timer_isr(void) const +void TimeManager::_set_up_timer_isr(void) { // set up timer interrupt after NTP update is done - if (ntp_sync_successful()) + if (ITimer.attachInterruptInterval(PERIOD_CLOCK_UPDATE_US, TimerHandler)) { - (void)ITimer.attachInterruptInterval(PERIOD_CLOCK_UPDATE_US, TimerHandler); + _tm_state = TM_NORMAL; + logger.log_string(String("Timer ISR was initialized!")); } else { + _tm_state = TM_SETUP_FAILED; logger.log_string("WARNING: Timer interrupt was not attached!"); } } @@ -163,17 +188,19 @@ void TimeManager::set_up_timer_isr(void) const * @brief Sets up the NTP server config * */ -void TimeManager::set_up_ntp(void) const +void TimeManager::_set_up_ntp(void) { if ((_tz != nullptr) && (_ntp_server != nullptr)) { // set up NTP server and timezone at init configTime(_tz, _ntp_server); + _tm_state = TM_INITIAL_SYNC; logger.log_string(String("NTP server was initialized!")); } else { - logger.log_string(String("Timezone and/or NTP-Server were not given!")); + _tm_state = TM_SETUP_FAILED; + logger.log_string(String("ERROR: Timezone and/or NTP-Server were not given correctly!")); } } diff --git a/src/wordclock_esp8266.cpp b/src/wordclock_esp8266.cpp index 3c898ba..2fa2f06 100644 --- a/src/wordclock_esp8266.cpp +++ b/src/wordclock_esp8266.cpp @@ -195,7 +195,7 @@ void setup() } // get initial time - if (tm_mgr.ntp_time_update(true) == NTP_UPDATE_OK) + if (tm_mgr.ntp_time_update() == NTP_UPDATE_OK) { // show the current time for short time in words String timeMessage = time_to_string(tm_mgr.tm_hour(), tm_mgr.tm_min()); @@ -252,7 +252,7 @@ void loop() { send_heartbeat(); // send heartbeat update - tm_mgr.log_time(); // TODO rm + tm_mgr.log_time(); // TODO rm last_heartbeat_us = system_get_time(); delay(10); @@ -283,14 +283,7 @@ void loop() if ((current_time_us - last_time_update_us) >= PERIOD_TIME_UPDATE_US) { - if (tm_mgr.ntp_sync_successful() == true) // regular case - { - (void)tm_mgr.ntp_time_update(); // NTP time update - } - else // if there was never a NTP time update before (set up failed) - { - (void)tm_mgr.ntp_time_update(true); // NTP time update with init! - } + tm_mgr.ntp_time_update(); // NTP time update if (tm_mgr.ntp_update_failed_prolonged() == true) { @@ -375,13 +368,40 @@ void handle_current_state() { case ST_CLOCK: // state clock { - (void)show_string_on_clock(time_to_string((uint8_t)tm_mgr.tm_hour(), (uint8_t)tm_mgr.tm_min()), main_color_clock); - draw_minute_indicator((uint8_t)tm_mgr.tm_min(), main_color_clock); + if (tm_mgr.ntp_sync_successful() && tm_mgr.tm_state() == TM_NORMAL) + { + (void)show_string_on_clock(time_to_string((uint8_t)tm_mgr.tm_hour(), (uint8_t)tm_mgr.tm_min()), main_color_clock); + draw_minute_indicator((uint8_t)tm_mgr.tm_min(), main_color_clock); + } + else if (tm_mgr.ntp_sync_successful() && tm_mgr.tm_state() == TM_RETRY_SYNC) + { + (void)show_string_on_clock(time_to_string((uint8_t)tm_mgr.tm_hour(), (uint8_t)tm_mgr.tm_min()), main_color_clock); + draw_minute_indicator((uint8_t)tm_mgr.tm_min(), colors_24bit[6]); // in blue to indicate a network problem + } + else + { + // clear matrix + led_matrix.flush(); + // Turn on minutes LEDs (blue) + led_matrix.set_min_indicator(15, colors_24bit[6]); + led_matrix.draw_on_matrix_instant(); + } break; } case ST_DICLOCK: // state diclock { - show_digital_clock((uint8_t)tm_mgr.tm_hour(), (uint8_t)tm_mgr.tm_min(), main_color_clock); + if (tm_mgr.ntp_sync_successful()) + { + show_digital_clock((uint8_t)tm_mgr.tm_hour(), (uint8_t)tm_mgr.tm_min(), main_color_clock); + } + else + { + // clear matrix + led_matrix.flush(); + // Turn on minutes LEDs (blue) + led_matrix.set_min_indicator(15, colors_24bit[6]); + led_matrix.draw_on_matrix_instant(); + } break; } case ST_SPIRAL: // state spiral