Repository navigation
MQTTManager: send NSPanels the current time #404
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
faa5333
MQTTManager: send NSPanels the current time as Unix milliseconds
jamesmulcahy 76b0fd0
MQTTManager: move the time and date thread into ManagerClock
jamesmulcahy bd2239d
MQTTManager: send the epoch with QoS 2
jamesmulcahy d7567ff
MQTTManager: mirror publish_if_connected() to STOMP while disconnected
jamesmulcahy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
96 changes: 96 additions & 0 deletions
96
docker/MQTTManager/include/manager_clock/manager_clock.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| #include "manager_clock.hpp" | ||
| #include "mqtt_manager/mqtt_manager.hpp" | ||
| #include "mqtt_manager_config/mqtt_manager_config.hpp" | ||
| #include <cstdlib> | ||
| #include <ctime> | ||
| #include <fmt/format.h> | ||
| #include <spdlog/spdlog.h> | ||
|
|
||
| void ManagerClock::start() { | ||
| ManagerClock::init(); | ||
| if (!ManagerClock::_instance.joinable()) { | ||
| ManagerClock::_instance = std::thread(ManagerClock::_run_clock_thread); | ||
| } | ||
| } | ||
|
|
||
| void ManagerClock::init() { | ||
| MQTT_Manager::attach_connected_callback(&ManagerClock::publish_epoch); | ||
| } | ||
|
|
||
| void ManagerClock::_run_clock_thread() { | ||
| // Set timezone | ||
| setenv("TZ", MqttManagerConfig::timezone.c_str(), 1); | ||
| tzset(); | ||
|
|
||
| // Timezone loaded, proceed to update MQTT | ||
| for (;;) { | ||
| char time_buffer[20]; | ||
| char ampm_buffer[20]; | ||
| std::string time_str; | ||
| char date_buffer[100]; | ||
| std::string date_str; | ||
|
|
||
| std::time_t time = std::time({}); | ||
| std::strftime(date_buffer, 100, MqttManagerConfig::get_setting_with_default<std::string>(MQTT_MANAGER_SETTING::DATE_FORMAT).c_str(), std::localtime(&time)); | ||
| date_str = date_buffer; | ||
|
|
||
| if (!MqttManagerConfig::get_setting_with_default<bool>(MQTT_MANAGER_SETTING::CLOCK_US_STYLE)) [[likely]] { | ||
| std::strftime(time_buffer, 20, "%H:%M", std::localtime(&time)); | ||
| time_str = time_buffer; | ||
| } else { | ||
| std::strftime(time_buffer, 20, "%I:%M", std::localtime(&time)); | ||
| std::strftime(ampm_buffer, 20, "%p", std::localtime(&time)); | ||
| time_str = time_buffer; | ||
| } | ||
|
|
||
| MQTT_Manager::publish(fmt::format("nspanel/mqttmanager_{}/status/time", MqttManagerConfig::get_setting_with_default<std::string>(MQTT_MANAGER_SETTING::MANAGER_ADDRESS)), time_buffer, true); | ||
| if (!MqttManagerConfig::get_setting_with_default<bool>(MQTT_MANAGER_SETTING::CLOCK_US_STYLE)) [[likely]] { | ||
| MQTT_Manager::publish(fmt::format("nspanel/mqttmanager_{}/status/ampm", MqttManagerConfig::get_setting_with_default<std::string>(MQTT_MANAGER_SETTING::MANAGER_ADDRESS)), "", true); | ||
| } else { | ||
| MQTT_Manager::publish(fmt::format("nspanel/mqttmanager_{}/status/ampm", MqttManagerConfig::get_setting_with_default<std::string>(MQTT_MANAGER_SETTING::MANAGER_ADDRESS)), ampm_buffer, true); | ||
| } | ||
|
|
||
| MQTT_Manager::publish(fmt::format("nspanel/mqttmanager_{}/status/date", MqttManagerConfig::get_setting_with_default<std::string>(MQTT_MANAGER_SETTING::MANAGER_ADDRESS)), date_buffer, true); | ||
| ManagerClock::publish_epoch(); | ||
|
|
||
| // Sleep until next minute | ||
| auto t_now = std::chrono::system_clock::now(); | ||
| auto t_now_c = std::chrono::system_clock::to_time_t(t_now); | ||
| std::tm next_minute_tm = *std::localtime(&t_now_c); | ||
| next_minute_tm.tm_sec = 1; // Always wait to minute change over and not the exact same second | ||
|
|
||
| next_minute_tm.tm_min++; | ||
| if (next_minute_tm.tm_min >= 60) { // We went over to next hour | ||
| next_minute_tm.tm_min = 0; | ||
| next_minute_tm.tm_hour++; | ||
| if (next_minute_tm.tm_hour >= 24) { // We went over to next day | ||
| next_minute_tm.tm_hour = 0; | ||
| next_minute_tm.tm_mday++; | ||
| // mktime (called below) normalises month/year overflow and handles | ||
| // varying month lengths and leap years, so no manual rollover needed. | ||
| } | ||
| } | ||
|
|
||
| auto t_next_minute = std::chrono::system_clock::from_time_t(std::mktime(&next_minute_tm)); | ||
| std::this_thread::sleep_until(t_next_minute); | ||
| } | ||
| } | ||
|
|
||
| void ManagerClock::publish_epoch() { | ||
| std::string topic = fmt::format("nspanel/mqttmanager_{}/status/epoch", MqttManagerConfig::get_setting_with_default<std::string>(MQTT_MANAGER_SETTING::MANAGER_ADDRESS)); | ||
| MQTT_Manager::publish_if_connected(topic, [] { | ||
| return ManagerClock::epoch_payload(std::chrono::system_clock::now()); | ||
| }); | ||
| } | ||
|
|
||
| std::optional<std::string> ManagerClock::epoch_payload(std::chrono::system_clock::time_point now) { | ||
| if (now < ManagerClock::_earliest_plausible_time) { | ||
| if (!ManagerClock::_warned_about_unset_clock.exchange(true)) { | ||
| SPDLOG_WARN("System clock reads {} ms since the Unix epoch, which is too early to be right. Will not send the time to NSPanels until the clock is set.", std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count()); | ||
| } | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| ManagerClock::_warned_about_unset_clock = false; | ||
| return std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count()); | ||
| } |
58 changes: 58 additions & 0 deletions
58
docker/MQTTManager/include/manager_clock/manager_clock.hpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| #ifndef MQTTMANAGER_MANAGER_CLOCK | ||
| #define MQTTMANAGER_MANAGER_CLOCK | ||
|
|
||
| #include <atomic> | ||
| #include <chrono> | ||
| #include <optional> | ||
| #include <string> | ||
| #include <thread> | ||
|
|
||
| /** | ||
| * Tells NSPanels the current time. | ||
| * | ||
| * Every minute the time and date to display are sent, retained, on nspanel/mqttmanager_<address>/status/time, | ||
| * status/ampm and status/date, formatted as configured. | ||
| * | ||
| * The time is also sent as Unix time in milliseconds on nspanel/mqttmanager_<address>/status/epoch so that | ||
| * panels can set their own clock. It is never retained, so a panel only ever receives a time that was current | ||
| * when it was sent. It is sent every minute, whenever the manager connects to MQTT, and whenever a panel comes | ||
| * online. | ||
| */ | ||
| class ManagerClock { | ||
| public: | ||
| /** | ||
| * Start the thread that sends the time every minute, and send the time each time the manager connects | ||
| * to MQTT. Call before MQTT_Manager::init(). | ||
| */ | ||
| static void start(); | ||
|
|
||
| /** | ||
| * Send the time each time the manager connects to MQTT. Called by start(). | ||
| */ | ||
| static void init(); | ||
|
|
||
| /** | ||
| * Send the current time now, if connected to MQTT. | ||
| */ | ||
| static void publish_epoch(); | ||
|
|
||
| /** | ||
| * The epoch payload for the given time: Unix time in milliseconds. Empty if the time is too early | ||
| * to be right, as when the host clock has not been set yet. | ||
| */ | ||
| static std::optional<std::string> epoch_payload(std::chrono::system_clock::time_point now); | ||
|
|
||
| private: | ||
| static inline std::thread _instance; | ||
|
|
||
| // Send the time and date at the start of every minute. Never returns. | ||
| static void _run_clock_thread(); | ||
|
|
||
| // 2025-01-01T00:00:00Z. Anything earlier means the host clock has not been set. | ||
| static constexpr std::chrono::system_clock::time_point _earliest_plausible_time{std::chrono::seconds(1735689600)}; | ||
|
|
||
| // Warn once when the host clock is not set, not every minute. | ||
| static inline std::atomic<bool> _warned_about_unset_clock = false; | ||
| }; | ||
|
|
||
| #endif // !MQTTMANAGER_MANAGER_CLOCK |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.