Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <exception>
#include <fmt/core.h>
#include <fstream>
#include <iterator>
#include <mutex>
#include <nlohmann/detail/conversions/to_json.hpp>
#include <nlohmann/detail/exceptions.hpp>
Expand All @@ -42,6 +43,12 @@ void MqttManagerConfig::load() {
boost::algorithm::trim(timezone_str);
MqttManagerConfig::timezone = timezone_str;
SPDLOG_INFO("Read timezone {} from /etc/timezone.", timezone_str);
MqttManagerConfig::timezone_posix_rule = MqttManagerConfig::read_posix_tz_rule(fmt::format("/usr/share/zoneinfo/{}", timezone_str));
if (MqttManagerConfig::timezone_posix_rule.empty()) {
SPDLOG_WARN("Could not read a POSIX TZ rule for timezone {}. NSPanels will show the time sent by the manager.", timezone_str);
} else {
SPDLOG_INFO("Using POSIX TZ rule {} for timezone {}.", MqttManagerConfig::timezone_posix_rule, timezone_str);
}

SPDLOG_DEBUG("Clearing config values cache.");
MqttManagerConfig::_settings_values_cache.clear();
Expand Down Expand Up @@ -529,6 +536,30 @@ std::expected<std::string, bool> MqttManagerConfig::get_us_horizontal_mirrored_t
return MqttManagerConfig::_md5_checksum_us_horizontal_mirrored_tft4;
}

std::string MqttManagerConfig::read_posix_tz_rule(const std::string &tzif_path) {
Comment thread
tpanajott marked this conversation as resolved.
try {
std::ifstream file(tzif_path, std::ios::in | std::ios::binary);
if (!file) {
return "";
}
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());

// Version 2 and later files end with the rule between two newlines (RFC 8536, section 3.3).
// Version 1 files have a zero version byte and no rule.
if (content.size() < 5 || content.compare(0, 4, "TZif") != 0 || content[4] < '2' || content.back() != '\n') {
return "";
}
size_t rule_start = content.rfind('\n', content.size() - 2);
if (rule_start == std::string::npos) {
return "";
}
return content.substr(rule_start + 1, content.size() - rule_start - 2);
} catch (std::exception &ex) {
SPDLOG_WARN("Caught exception while reading POSIX TZ rule from '{}': {}", tzif_path, ex.what());
return "";
}
}

// TESTING
#if defined(TEST_MODE) && TEST_MODE == 1
#include <gtest/gtest.h>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ class MqttManagerConfig {
static void populate_default_and_clean();

static inline std::string timezone;
// POSIX TZ rule for timezone, read from its tzdata file. Empty if it could not be read.
static inline std::string timezone_posix_rule;

/**
* Read the POSIX TZ rule from the end of a tzdata (TZif) file, ex. "CET-1CEST,M3.5.0,M10.5.0/3".
* @param tzif_path: Path of the file, ex. /usr/share/zoneinfo/Europe/Stockholm.
* @return The rule, or empty if the file is missing, not version 2 or later, or has no rule.
*/
static std::string read_posix_tz_rule(const std::string &tzif_path);

template <typename T>
static T get_setting_with_default(MQTT_MANAGER_SETTING key);
Expand Down
1 change: 1 addition & 0 deletions docker/MQTTManager/include/nspanel/nspanel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ void NSPanel::send_config() {
config.set_screensaver_dim_level(std::stoi(this->_get_nspanel_setting_with_default("screensaver_dim_level", MqttManagerConfig::get_setting_with_default<std::string>(MQTT_MANAGER_SETTING::SCREENSAVER_DIM_LEVEL))));
config.set_screensaver_activation_timeout(std::stoi(this->_get_nspanel_setting_with_default("screensaver_activation_timeout", MqttManagerConfig::get_setting_with_default<std::string>(MQTT_MANAGER_SETTING::SCREENSAVER_ACTIVATION_TIMEOUT))));
config.set_clock_us_style(MqttManagerConfig::get_setting_with_default<bool>(MQTT_MANAGER_SETTING::CLOCK_US_STYLE));
config.set_tz_posix(MqttManagerConfig::timezone_posix_rule);
config.set_use_fahrenheit(MqttManagerConfig::get_setting_with_default<bool>(MQTT_MANAGER_SETTING::USE_FAHRENHEIT));
config.set_is_us_panel(this->_get_nspanel_setting_with_default("is_us_panel", "False").compare("True") == 0);
config.set_reverse_relays(this->_get_nspanel_setting_with_default("reverse_relays", "False").compare("True") == 0);
Expand Down
317 changes: 174 additions & 143 deletions docker/MQTTManager/include/protobuf/protobuf_nspanel.pb.cc

Large diffs are not rendered by default.

72 changes: 70 additions & 2 deletions docker/MQTTManager/include/protobuf/protobuf_nspanel.pb.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions docker/MQTTManager/tests/nspanel_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,30 @@ TEST_F(NSPanelTest, panel_settings_are_sent_in_the_config) {
EXPECT_FALSE(config->show_screensaver_outside_temperature());
}

TEST_F(NSPanelTest, timezone_rule_is_sent_in_the_config) {
std::string previous_rule = MqttManagerConfig::timezone_posix_rule;
OnExit restore_rule{[&] { MqttManagerConfig::timezone_posix_rule = previous_rule; }};
MqttManagerConfig::timezone_posix_rule = "CET-1CEST,M3.5.0,M10.5.0/3";

load_panel();

auto config = last_published_config();
ASSERT_TRUE(config.has_value());
EXPECT_EQ(config->tz_posix(), "CET-1CEST,M3.5.0,M10.5.0/3");
}

TEST_F(NSPanelTest, unknown_timezone_rule_is_sent_empty) {
std::string previous_rule = MqttManagerConfig::timezone_posix_rule;
OnExit restore_rule{[&] { MqttManagerConfig::timezone_posix_rule = previous_rule; }};
MqttManagerConfig::timezone_posix_rule = "";

load_panel();

auto config = last_published_config();
ASSERT_TRUE(config.has_value());
EXPECT_EQ(config->tz_posix(), "");
}

TEST_F(NSPanelTest, temperature_calibration_is_sent_in_tenths_of_a_degree) {
set_panel_setting("temperature_calibration", "-1.5"); // Django stores str(float(...)).

Expand Down
71 changes: 71 additions & 0 deletions docker/MQTTManager/tests/posix_tz_rule_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#include <filesystem>
#include <fstream>
#include <gtest/gtest.h>
#include <mqtt_manager_config/mqtt_manager_config.hpp>
#include <string>

namespace {

// A tzdata file of the given version: the header, a stand-in for the binary data (which can hold
// newlines), then, from version 2, the rule between two newlines.
std::string tzif(char version, const std::string &rule) {
std::string content = std::string("TZif") + version + std::string(15, '\0') + "\x01\n\x02\n\x03";
if (version != '\0') {
content += "\n" + rule + "\n";
}
return content;
}

class PosixTzRuleTest : public ::testing::Test {
protected:
void SetUp() override {
path = std::filesystem::temp_directory_path() / "nspm_posix_tz_rule_test";
}

void TearDown() override {
std::filesystem::remove(path);
}

std::string read(const std::string &content) {
std::ofstream(path, std::ios::binary) << content;
return MqttManagerConfig::read_posix_tz_rule(path);
}

std::filesystem::path path;
};

} // namespace

TEST_F(PosixTzRuleTest, reads_the_rule_from_a_version_2_file) {
EXPECT_EQ(read(tzif('2', "CET-1CEST,M3.5.0,M10.5.0/3")), "CET-1CEST,M3.5.0,M10.5.0/3");
}

TEST_F(PosixTzRuleTest, reads_the_rule_from_a_version_3_file) {
EXPECT_EQ(read(tzif('3', "IST-2IDT,M3.4.4/26,M10.5.0")), "IST-2IDT,M3.4.4/26,M10.5.0");
}

TEST_F(PosixTzRuleTest, version_1_file_has_no_rule) {
EXPECT_EQ(read(tzif('\0', "")), "");
}

TEST_F(PosixTzRuleTest, empty_rule_is_empty) {
EXPECT_EQ(read(tzif('2', "")), "");
}

TEST_F(PosixTzRuleTest, file_that_is_not_tzdata_has_no_rule) {
EXPECT_EQ(read("Europe/Stockholm\n"), "");
EXPECT_EQ(read(""), "");
}

TEST_F(PosixTzRuleTest, missing_file_has_no_rule) {
EXPECT_EQ(MqttManagerConfig::read_posix_tz_rule("/nonexistent/zoneinfo/Europe/Stockholm"), "");
}

// Against the real tzdata the manager image ships, if this one has it too.
TEST(PosixTzRuleSystemTest, reads_the_rule_from_installed_tzdata) {
const std::string utc = "/usr/share/zoneinfo/UTC";
if (!std::filesystem::exists(utc)) {
GTEST_SKIP() << "No tzdata installed at " << utc;
}
EXPECT_EQ(MqttManagerConfig::read_posix_tz_rule(utc), "UTC0");
}
4 changes: 4 additions & 0 deletions docker/protobuf/protobuf_nspanel.proto
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ message NSPanelConfig {
string rtttl = 2; // Melody in RTTTL format, ex. "touch:d=32,o=6,b=190:g6,16p,32c7"
}
repeated Sound sounds = 45;

// POSIX TZ rule for the manager's timezone, ex. "CET-1CEST,M3.5.0,M10.5.0/3", for setenv("TZ", ...) and tzset().
// Empty if the manager could not work it out, in which case show the time from the manager's status/time topic.
string tz_posix = 46;
}

enum NSPanelWarningLevel {
Expand Down
Loading