diff --git a/config.example.json b/config.example.json new file mode 100644 index 0000000..cb1b2dd --- /dev/null +++ b/config.example.json @@ -0,0 +1,9 @@ +{ + "shades": [ + { + "radioId": "13AC", + "mqttId": "example_blind", + "friendly_name": "Example Blind" + } + ] +} diff --git a/lib/configurator/Configurator.cpp b/lib/configurator/Configurator.cpp index a393e9e..240a9bc 100644 --- a/lib/configurator/Configurator.cpp +++ b/lib/configurator/Configurator.cpp @@ -1,62 +1,68 @@ #include "Configurator.h" #include +#include Configurator::Configurator() { // TODO: Should be using MQTT directly here (and methods for handling methods should be private) } -std::string Configurator::extractKey(std::string topic) { - int startIndex = topic.find("/") + 1; - int endIndex = topic.find("/", startIndex); - // TODO: need to verify that startIndex and endIndex are valid before substr() - return topic.substr(startIndex, endIndex - startIndex); -} - -void Configurator::processIdMessage(std::string topic, std::string payload) { - auto key = extractKey(topic); - - if (payload.length() != 4) { - // Ignore payloads that aren't exactly 4 characters - return; +uint16_t Configurator::parseRadioId(const char* rawRadioId) { + if (!rawRadioId) { + return 0; // Or another appropriate error value/exception } uint16_t id = 0; - for (char c : payload) { - // Check if valid hex digit (0-9, A-F, a-f) + std::string radioId = std::string(rawRadioId); + + if (radioId.size() != 4) { + return 0; // Invalid length + } + + for (char c : radioId) { if (!std::isxdigit(c)) { - return; // Invalid character, not a hex digit + return 0; // Invalid character, not a hex digit } - // Convert hex digit to numerical value (0-9, A-B=10-11, ...) int digit = std::toupper(c) - (c <= '9' ? '0' : 'A' - 10); - id = (id << 4) | digit; } - discoveredIds[key] = id; - tryBuild(key); + return id; } -void Configurator::processFriendlyNameMessage(std::string topic, std::string friendlyName) { - auto key = extractKey(topic); +void Configurator::processJson(std::string json) { + JsonDocument doc; - discoveredFriendlyNames[key] = friendlyName; - tryBuild(key); + DeserializationError error = deserializeJson(doc, json); + + if (error) { + return; + } + + JsonArray shades = doc["shades"]; + + for (JsonObject shade : shades) { + const char* rawRadioId = shade["radioId"]; + const char* mqttId = shade["mqttId"]; + const char* friendlyName = shade["friendly_name"]; + + if (rawRadioId && mqttId && friendlyName && std::char_traits::length(mqttId) > 0) { + uint16_t id = parseRadioId(rawRadioId); + + if (id != 0) { + Shade s{id, mqttId, friendlyName, "stopped", -1, -1}; + for (const auto& callback : shadeConfiguredCallbacks) { + callback(s); + } + } + } + else + { + // Invalid JSON - missing radioId, mqttId, or friendly_name" + } + } } void Configurator::addShadeConfiguredCallback(std::function callback) { shadeConfiguredCallbacks.push_back(callback); } - -bool Configurator::tryBuild(std::string key) { - if (auto id = discoveredIds.find(key); id != discoveredIds.end()) { - if (auto friendlyName = discoveredFriendlyNames.find(key); friendlyName != discoveredFriendlyNames.end()) { - auto shade = Shade{id->second, key, friendlyName->second, "stopped", -1, -1}; - for (size_t i = 0; i < shadeConfiguredCallbacks.size(); i++) { - shadeConfiguredCallbacks[i](shade); - } - return true; - } - } - return false; -} \ No newline at end of file diff --git a/lib/configurator/Configurator.h b/lib/configurator/Configurator.h index d404b5c..a9644e7 100644 --- a/lib/configurator/Configurator.h +++ b/lib/configurator/Configurator.h @@ -12,19 +12,15 @@ class Configurator { private: std::vector> shadeConfiguredCallbacks; - std::map discoveredIds; - std::map discoveredFriendlyNames; - std::string extractKey(std::string topic); - bool tryBuild(std::string topic); + uint16_t parseRadioId(const char* rawRadioId); public: Configurator(); void addShadeConfiguredCallback(std::function callback); - void processIdMessage(std::string topic, std::string id); - void processFriendlyNameMessage(std::string topic, std::string friendlyName); + void processJson(std::string json); }; #endif // CONFIGURATOR_H \ No newline at end of file diff --git a/src/.gitignore b/src/.gitignore index 92001b5..3f50fe4 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1 +1,2 @@ -secrets.h \ No newline at end of file +secrets.h +config.h \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 0b64d5d..45f4dc4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include "config.h" #include "Shade.h" #include "Configurator.h" #include "ShadeRepository.h" @@ -113,6 +114,8 @@ void setup() { publishDiscoveryTopics(); }); + configurator.processJson(config_json); + delay(100); Serial.println("Ready"); diff --git a/test/test_configurator/test_configurator.cpp b/test/test_configurator/test_configurator.cpp index 7d28d7d..374e319 100644 --- a/test/test_configurator/test_configurator.cpp +++ b/test/test_configurator/test_configurator.cpp @@ -10,6 +10,11 @@ void tearDown() { } +std::string singleBlindJson = R"({"shades": [{"radioId": "ABCD","mqttId": "test_shade","friendly_name": "Test Shade"}]})"; +std::string doubleBlindJson = R"({"shades": [{"radioId": "ABCD","mqttId": "test_shade","friendly_name": "Test Shade"}, {"radioId": "1234","mqttId": "test_shade2","friendly_name": "Test Shade 2"}]})"; +std::string invalidRadioIdBlindJson = R"({"shades": [{"radioId": "XYZ9","mqttId": "test_shade","friendly_name": "Test Shade"}, {"mqttId": "test_shade","friendly_name": "Test Shade"}, {"radioId": "","mqttId": "test_shade","friendly_name": "Test Shade"}, {"radioId": "ABCDE","mqttId": "test_shade","friendly_name": "Test Shade"}]})"; +std::string invalidMqttIdBlindJson = R"({"shades": [{"radioId": "ABCD","mqttId": "","friendly_name": "Test Shade"}, {"radioId": "ABCD","friendly_name": "Test Shade"}]})"; + void test_shade_is_configured_with_id_and_friendly_name() { int callbackInvokedCount = 0; @@ -23,71 +28,26 @@ void test_shade_is_configured_with_id_and_friendly_name() TEST_ASSERT_EQUAL_STRING("test_shade", shade.key.c_str()); }); - configurator.processIdMessage("hotdog/test_shade/id", "ABCD"); - configurator.processFriendlyNameMessage("hotdog/test_shade/friendly_name", "Test Shade"); + configurator.processJson(singleBlindJson); TEST_ASSERT_EQUAL_INT(1, callbackInvokedCount); } -void test_shade_is_configured_again_with_new_friendly_name() +void test_multiple_shades_are_configured() { int callbackInvokedCount = 0; Configurator configurator = Configurator(); configurator.addShadeConfiguredCallback([&] (Shade shade) { callbackInvokedCount++; - - if (callbackInvokedCount == 1) { - TEST_ASSERT_EQUAL_STRING("Test Shade", shade.friendlyName.c_str()); - } else if (callbackInvokedCount == 2) { - TEST_ASSERT_EQUAL_STRING("Updated Test Shade", shade.friendlyName.c_str()); - } else { - TEST_ABORT(); - } - TEST_ASSERT_EQUAL_HEX16(0xABCD, shade.ID); - TEST_ASSERT_EQUAL_STRING("test_shade", shade.key.c_str()); }); - configurator.processIdMessage("hotdog/test_shade/id", "ABCD"); - configurator.processFriendlyNameMessage("hotdog/test_shade/friendly_name", "Test Shade"); - - TEST_ASSERT_EQUAL_INT(1, callbackInvokedCount); - - configurator.processFriendlyNameMessage("hotdog/test_shade/friendly_name", "Updated Test Shade"); + configurator.processJson(doubleBlindJson); TEST_ASSERT_EQUAL_INT(2, callbackInvokedCount); } -void test_shade_is_configured_again_with_new_id() -{ - int callbackInvokedCount = 0; - - Configurator configurator = Configurator(); - configurator.addShadeConfiguredCallback([&] (Shade shade) { - callbackInvokedCount++; - - TEST_ASSERT_EQUAL_STRING("Test Shade", shade.friendlyName.c_str()); - if (callbackInvokedCount == 1) { - TEST_ASSERT_EQUAL_HEX16(0xABCD, shade.ID); - } else if (callbackInvokedCount == 2) { - TEST_ASSERT_EQUAL_HEX16(0x9876, shade.ID); - } else { - TEST_ABORT(); - } - TEST_ASSERT_EQUAL_STRING("test_shade", shade.key.c_str()); - }); - - configurator.processIdMessage("hotdog/test_shade/id", "ABCD"); - configurator.processFriendlyNameMessage("hotdog/test_shade/friendly_name", "Test Shade"); - - TEST_ASSERT_EQUAL_INT(1, callbackInvokedCount); - - configurator.processIdMessage("hotdog/test_shade/id", "9876"); - - TEST_ASSERT_EQUAL_INT(2, callbackInvokedCount); -} - -void test_shade_is_not_configured_without_friendly_name() +void test_invalid_radio_id_is_ignored() { int callbackInvokedCount = 0; @@ -96,12 +56,12 @@ void test_shade_is_not_configured_without_friendly_name() callbackInvokedCount++; }); - configurator.processIdMessage("hotdog/test_shade/id", "ABCD"); + configurator.processJson(invalidRadioIdBlindJson); TEST_ASSERT_EQUAL_INT(0, callbackInvokedCount); } -void test_shade_is_not_configured_without_id() +void test_invalid_mqtt_id_is_ignored() { int callbackInvokedCount = 0; @@ -110,35 +70,18 @@ void test_shade_is_not_configured_without_id() callbackInvokedCount++; }); - configurator.processFriendlyNameMessage("hotdog/test_shade/friendly_name", "Test Shade"); + configurator.processJson(invalidMqttIdBlindJson); TEST_ASSERT_EQUAL_INT(0, callbackInvokedCount); } -void test_shade_is_not_configured_with_invalid_id() -{ - int callbackInvokedCount = 0; - - Configurator configurator = Configurator(); - configurator.addShadeConfiguredCallback([&] (Shade shade) { - callbackInvokedCount++; - }); - - configurator.processIdMessage("hotdog/test_shade/id", "ZZZZ"); - configurator.processFriendlyNameMessage("hotdog/test_shade/friendly_name", "Test Shade"); - - TEST_ASSERT_EQUAL_INT(0, callbackInvokedCount); -} - int runUnityTests(void) { UNITY_BEGIN(); RUN_TEST(test_shade_is_configured_with_id_and_friendly_name); - RUN_TEST(test_shade_is_configured_again_with_new_friendly_name); - RUN_TEST(test_shade_is_configured_again_with_new_id); - RUN_TEST(test_shade_is_not_configured_without_friendly_name); - RUN_TEST(test_shade_is_not_configured_without_id); - RUN_TEST(test_shade_is_not_configured_with_invalid_id); + RUN_TEST(test_multiple_shades_are_configured); + RUN_TEST(test_invalid_radio_id_is_ignored); + RUN_TEST(test_invalid_mqtt_id_is_ignored); return UNITY_END(); }