Move configuration to json
This commit is contained in:
9
config.example.json
Normal file
9
config.example.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"shades": [
|
||||||
|
{
|
||||||
|
"radioId": "13AC",
|
||||||
|
"mqttId": "example_blind",
|
||||||
|
"friendly_name": "Example Blind"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,62 +1,68 @@
|
|||||||
#include "Configurator.h"
|
#include "Configurator.h"
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
Configurator::Configurator() {
|
Configurator::Configurator() {
|
||||||
// TODO: Should be using MQTT directly here (and methods for handling methods should be private)
|
// TODO: Should be using MQTT directly here (and methods for handling methods should be private)
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Configurator::extractKey(std::string topic) {
|
uint16_t Configurator::parseRadioId(const char* rawRadioId) {
|
||||||
int startIndex = topic.find("/") + 1;
|
if (!rawRadioId) {
|
||||||
int endIndex = topic.find("/", startIndex);
|
return 0; // Or another appropriate error value/exception
|
||||||
// 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 id = 0;
|
uint16_t id = 0;
|
||||||
for (char c : payload) {
|
std::string radioId = std::string(rawRadioId);
|
||||||
// Check if valid hex digit (0-9, A-F, a-f)
|
|
||||||
|
if (radioId.size() != 4) {
|
||||||
|
return 0; // Invalid length
|
||||||
|
}
|
||||||
|
|
||||||
|
for (char c : radioId) {
|
||||||
if (!std::isxdigit(c)) {
|
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);
|
int digit = std::toupper(c) - (c <= '9' ? '0' : 'A' - 10);
|
||||||
|
|
||||||
id = (id << 4) | digit;
|
id = (id << 4) | digit;
|
||||||
}
|
}
|
||||||
|
|
||||||
discoveredIds[key] = id;
|
return id;
|
||||||
tryBuild(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Configurator::processFriendlyNameMessage(std::string topic, std::string friendlyName) {
|
void Configurator::processJson(std::string json) {
|
||||||
auto key = extractKey(topic);
|
JsonDocument doc;
|
||||||
|
|
||||||
discoveredFriendlyNames[key] = friendlyName;
|
DeserializationError error = deserializeJson(doc, json);
|
||||||
tryBuild(key);
|
|
||||||
|
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<char>::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<void (Shade)> callback) {
|
void Configurator::addShadeConfiguredCallback(std::function<void (Shade)> callback) {
|
||||||
shadeConfiguredCallbacks.push_back(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;
|
|
||||||
}
|
|
||||||
@@ -12,19 +12,15 @@ class Configurator
|
|||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
std::vector<std::function<void (Shade)>> shadeConfiguredCallbacks;
|
std::vector<std::function<void (Shade)>> shadeConfiguredCallbacks;
|
||||||
std::map<std::string, uint16_t> discoveredIds;
|
|
||||||
std::map<std::string, std::string> discoveredFriendlyNames;
|
|
||||||
|
|
||||||
std::string extractKey(std::string topic);
|
uint16_t parseRadioId(const char* rawRadioId);
|
||||||
bool tryBuild(std::string topic);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Configurator();
|
Configurator();
|
||||||
|
|
||||||
void addShadeConfiguredCallback(std::function<void (Shade)> callback);
|
void addShadeConfiguredCallback(std::function<void (Shade)> callback);
|
||||||
|
|
||||||
void processIdMessage(std::string topic, std::string id);
|
void processJson(std::string json);
|
||||||
void processFriendlyNameMessage(std::string topic, std::string friendlyName);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONFIGURATOR_H
|
#endif // CONFIGURATOR_H
|
||||||
1
src/.gitignore
vendored
1
src/.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
secrets.h
|
secrets.h
|
||||||
|
config.h
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <RFPowerView.h>
|
#include <RFPowerView.h>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include "config.h"
|
||||||
#include "Shade.h"
|
#include "Shade.h"
|
||||||
#include "Configurator.h"
|
#include "Configurator.h"
|
||||||
#include "ShadeRepository.h"
|
#include "ShadeRepository.h"
|
||||||
@@ -113,6 +114,8 @@ void setup() {
|
|||||||
publishDiscoveryTopics();
|
publishDiscoveryTopics();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
configurator.processJson(config_json);
|
||||||
|
|
||||||
delay(100);
|
delay(100);
|
||||||
|
|
||||||
Serial.println("Ready");
|
Serial.println("Ready");
|
||||||
|
|||||||
@@ -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()
|
void test_shade_is_configured_with_id_and_friendly_name()
|
||||||
{
|
{
|
||||||
int callbackInvokedCount = 0;
|
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());
|
TEST_ASSERT_EQUAL_STRING("test_shade", shade.key.c_str());
|
||||||
});
|
});
|
||||||
|
|
||||||
configurator.processIdMessage("hotdog/test_shade/id", "ABCD");
|
configurator.processJson(singleBlindJson);
|
||||||
configurator.processFriendlyNameMessage("hotdog/test_shade/friendly_name", "Test Shade");
|
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(1, callbackInvokedCount);
|
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;
|
int callbackInvokedCount = 0;
|
||||||
|
|
||||||
Configurator configurator = Configurator();
|
Configurator configurator = Configurator();
|
||||||
configurator.addShadeConfiguredCallback([&] (Shade shade) {
|
configurator.addShadeConfiguredCallback([&] (Shade shade) {
|
||||||
callbackInvokedCount++;
|
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.processJson(doubleBlindJson);
|
||||||
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");
|
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(2, callbackInvokedCount);
|
TEST_ASSERT_EQUAL_INT(2, callbackInvokedCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_shade_is_configured_again_with_new_id()
|
void test_invalid_radio_id_is_ignored()
|
||||||
{
|
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
int callbackInvokedCount = 0;
|
int callbackInvokedCount = 0;
|
||||||
|
|
||||||
@@ -96,12 +56,12 @@ void test_shade_is_not_configured_without_friendly_name()
|
|||||||
callbackInvokedCount++;
|
callbackInvokedCount++;
|
||||||
});
|
});
|
||||||
|
|
||||||
configurator.processIdMessage("hotdog/test_shade/id", "ABCD");
|
configurator.processJson(invalidRadioIdBlindJson);
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, callbackInvokedCount);
|
TEST_ASSERT_EQUAL_INT(0, callbackInvokedCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_shade_is_not_configured_without_id()
|
void test_invalid_mqtt_id_is_ignored()
|
||||||
{
|
{
|
||||||
int callbackInvokedCount = 0;
|
int callbackInvokedCount = 0;
|
||||||
|
|
||||||
@@ -110,22 +70,7 @@ void test_shade_is_not_configured_without_id()
|
|||||||
callbackInvokedCount++;
|
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);
|
TEST_ASSERT_EQUAL_INT(0, callbackInvokedCount);
|
||||||
}
|
}
|
||||||
@@ -134,11 +79,9 @@ int runUnityTests(void)
|
|||||||
{
|
{
|
||||||
UNITY_BEGIN();
|
UNITY_BEGIN();
|
||||||
RUN_TEST(test_shade_is_configured_with_id_and_friendly_name);
|
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_multiple_shades_are_configured);
|
||||||
RUN_TEST(test_shade_is_configured_again_with_new_id);
|
RUN_TEST(test_invalid_radio_id_is_ignored);
|
||||||
RUN_TEST(test_shade_is_not_configured_without_friendly_name);
|
RUN_TEST(test_invalid_mqtt_id_is_ignored);
|
||||||
RUN_TEST(test_shade_is_not_configured_without_id);
|
|
||||||
RUN_TEST(test_shade_is_not_configured_with_invalid_id);
|
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user