Compare commits

..

1 Commits

Author SHA1 Message Date
94f00c0cef WIP using new PositionWatcher 2025-01-20 22:53:54 +11:00
12 changed files with 228 additions and 235 deletions

1
.gitignore vendored
View File

@@ -3,4 +3,3 @@
.vscode/c_cpp_properties.json .vscode/c_cpp_properties.json
.vscode/launch.json .vscode/launch.json
.vscode/ipch .vscode/ipch
.vscode/settings.json

View File

@@ -1,9 +0,0 @@
{
"shades": [
{
"radioId": "13AC",
"mqttId": "example_blind",
"friendly_name": "Example Blind"
}
]
}

View File

@@ -1,68 +1,62 @@
#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)
} }
uint16_t Configurator::parseRadioId(const char* rawRadioId) { std::string Configurator::extractKey(std::string topic) {
if (!rawRadioId) { int startIndex = topic.find("/") + 1;
return 0; // Or another appropriate error value/exception 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 id = 0; uint16_t id = 0;
std::string radioId = std::string(rawRadioId); for (char c : payload) {
// 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 0; // Invalid character, not a hex digit return; // 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;
} }
return id; discoveredIds[key] = id;
tryBuild(key);
} }
void Configurator::processJson(std::string json) { void Configurator::processFriendlyNameMessage(std::string topic, std::string friendlyName) {
JsonDocument doc; auto key = extractKey(topic);
DeserializationError error = deserializeJson(doc, json); discoveredFriendlyNames[key] = friendlyName;
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;
}

View File

@@ -12,15 +12,19 @@ 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;
uint16_t parseRadioId(const char* rawRadioId); std::string extractKey(std::string topic);
bool tryBuild(std::string topic);
public: public:
Configurator(); Configurator();
void addShadeConfiguredCallback(std::function<void (Shade)> callback); void addShadeConfiguredCallback(std::function<void (Shade)> callback);
void processJson(std::string json); void processIdMessage(std::string topic, std::string id);
void processFriendlyNameMessage(std::string topic, std::string friendlyName);
}; };
#endif // CONFIGURATOR_H #endif // CONFIGURATOR_H

View File

@@ -1,84 +0,0 @@
#include "PacketBuilder.h"
PacketBuilder::PacketBuilder()
{
}
Packet PacketBuilder::buildOpenPacket(uint16_t destination)
{
Packet packet;
auto header = UnicastHeader{};
header.destination = destination;
header.source = 0x0000;
packet.header = header;
packet.type = PacketType::OPEN;
return packet;
}
Packet PacketBuilder::buildClosePacket(uint16_t destination)
{
Packet packet;
auto header = UnicastHeader{};
header.destination = destination;
header.source = 0x0000;
packet.header = header;
packet.type = PacketType::CLOSE;
return packet;
}
Packet PacketBuilder::buildStopPacket(uint16_t destination)
{
Packet packet;
auto header = UnicastHeader{};
header.destination = destination;
header.source = 0x0000;
packet.header = header;
packet.type = PacketType::STOP;
return packet;
}
Packet PacketBuilder::buildSetPositionPacket(uint16_t destination, float percentage)
{
Packet packet;
auto header = UnicastHeader{};
header.destination = destination;
header.source = 0x0000;
packet.header = header;
packet.type = PacketType::FIELD_COMMAND;
std::vector<Field> fields;
uint8_t identifier = 0x50; // position
FieldType type = FieldType::SET;
uint16_t position = (uint16_t)(0xFFFF * percentage);
fields.push_back(Field{identifier, type, true, position});
packet.parameters = FieldsParameters{fields};
return packet;
}
Packet PacketBuilder::buildFetchPositionPacket(uint16_t destination)
{
Packet packet;
auto header = UnicastHeader{};
header.destination = destination;
header.source = 0x0000;
packet.header = header;
packet.type = PacketType::FIELD_COMMAND;
std::vector<Field> fields;
// position
fields.push_back(Field{0x50, FieldType::FETCH, false, std::monostate{}});
// battery
fields.push_back(Field{0x42, FieldType::FETCH, false, std::monostate{}});
packet.parameters = FieldsParameters{fields};
return packet;
}

View File

@@ -1,17 +0,0 @@
#ifndef PACKET_BUILDER_H
#define PACKET_BUILDER_H
#include <PacketTypes.h>
class PacketBuilder {
public:
PacketBuilder();
Packet buildOpenPacket(uint16_t destination);
Packet buildClosePacket(uint16_t destination);
Packet buildStopPacket(uint16_t destination);
Packet buildSetPositionPacket(uint16_t destination, float percentage);
Packet buildFetchPositionPacket(uint16_t destination);
};
#endif // PACKET_BUILDER_H

View File

@@ -1,27 +0,0 @@
#include "PacketSender.h"
PacketSender::PacketSender(RFPowerView& powerView) : powerView(powerView)
{
}
bool PacketSender::send(Packet packet)
{
Serial.println("Attempting to send a packet");
packet.rollingCode1 = lastRollingCode1 + 1;
packet.rollingCode2 = lastRollingCode2 + 1;
bool didSend = powerView.sendPacket(&packet);
if (!didSend) {
Serial.println("Failed to send");
return false;
} else {
lastRollingCode1++;
lastRollingCode2++;
return true;
}
}
void PacketSender::setLastRollingCodes(uint8_t code1, uint8_t code2) {
lastRollingCode1 = code1;
lastRollingCode2 = code2;
}

View File

@@ -1,20 +0,0 @@
#ifndef PACKET_SENDER_H
#define PACKET_SENDER_H
#include <RFPowerView.h>
#include <PacketTypes.h>
class PacketSender {
public:
PacketSender(RFPowerView& powerView);
bool send(Packet packet);
void setLastRollingCodes(uint8_t code1, uint8_t code2);
private:
RFPowerView& powerView;
uint8_t lastRollingCode1 = 0x3D;
uint8_t lastRollingCode2 = 0x96;
};
#endif // PACKET_SENDER_H

View File

@@ -37,6 +37,7 @@ lib_deps =
[env:test_desktop] [env:test_desktop]
platform = native platform = native
build_type = test build_type = test
lib_compat_mode = off
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
ArduinoFake ArduinoFake

1
src/.gitignore vendored
View File

@@ -1,2 +1 @@
secrets.h secrets.h
config.h

View File

@@ -4,7 +4,6 @@
#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"
@@ -12,14 +11,15 @@
#include "secrets.h" #include "secrets.h"
#include "ShadeCommand.h" #include "ShadeCommand.h"
#include "PositionWatcher.h" #include "PositionWatcher.h"
#include "PacketBuilder.h"
#include "PacketSender.h"
#include <queue> #include <queue>
#define SER_BAUDRATE (115200) #define SER_BAUDRATE (115200)
RFPowerView powerView(RF_CE_PIN, RF_CS_PIN, RF_IRQ_PIN, RF_ID); RFPowerView powerView(RF_CE_PIN, RF_CS_PIN, RF_IRQ_PIN, RF_ID);
uint8_t lastRollingCode1 = 0x3D;
uint8_t lastRollingCode2 = 0x96;
static std::mt19937 generator(std::chrono::system_clock::now().time_since_epoch().count()); static std::mt19937 generator(std::chrono::system_clock::now().time_since_epoch().count());
std::string generate_client_id_suffix(int length) { std::string generate_client_id_suffix(int length) {
@@ -60,14 +60,17 @@ HADiscovery haDiscovery(topic_prefix.c_str(), [] (const char* topic, const char*
client.publish(topic, message); client.publish(topic, message);
}); });
PacketBuilder packetBuilder;
PacketSender packetSender(powerView);
void processPacket(const Packet*); void processPacket(const Packet*);
void processCommandMessage(const String& topic, const String &payload); void processCommandMessage(const String& topic, const String &payload);
void processSetPositionMessage(const String& topic, const String &payload); void processSetPositionMessage(const String& topic, const String &payload);
bool sendOpenPacket(uint16_t destination);
bool sendClosePacket(uint16_t destination);
bool sendStopPacket(uint16_t destination);
bool sendSetPosition(uint16_t destination, float percentage);
bool sendFetchPosition(uint16_t destination);
bool sendPacket(Packet *packet);
void publishPosition(const std::string& shadeKey, const uint8_t position); void publishPosition(const std::string& shadeKey, const uint8_t position);
void publishState(const std::string& shadeKey, const String& state); void publishState(const std::string& shadeKey, const String& state);
void publishBattery(const std::string& shadeKey, const uint8_t battery); void publishBattery(const std::string& shadeKey, const uint8_t battery);
@@ -110,13 +113,15 @@ void setup() {
publishDiscoveryTopics(); publishDiscoveryTopics();
}); });
configurator.processJson(config_json);
delay(100); delay(100);
shadeRepository.upsert(Shade{0x4EF1, "study_blind", "Study Blind", "stopped", -1, -1, false});
Serial.println("Ready"); Serial.println("Ready");
} }
int loopCounter = 0;
void loop() { void loop() {
powerView.loop(); powerView.loop();
client.loop(); client.loop();
@@ -129,7 +134,7 @@ void loop() {
if (std::holds_alternative<OpenCommand>(command)) { if (std::holds_alternative<OpenCommand>(command)) {
auto openCommand = std::get<OpenCommand>(command); auto openCommand = std::get<OpenCommand>(command);
Serial.printf("Open command received for shade 0x%04x\n", openCommand.shadeID); Serial.printf("Open command received for shade 0x%04x\n", openCommand.shadeID);
if (packetSender.send(packetBuilder.buildOpenPacket(openCommand.shadeID))) { if (sendOpenPacket(openCommand.shadeID)) {
auto shade = shadeRepository.findById(openCommand.shadeID); auto shade = shadeRepository.findById(openCommand.shadeID);
if (shade != nullptr) { if (shade != nullptr) {
shade->state = "opening"; shade->state = "opening";
@@ -141,7 +146,7 @@ void loop() {
} else if (std::holds_alternative<CloseCommand>(command)) { } else if (std::holds_alternative<CloseCommand>(command)) {
auto closeCommand = std::get<CloseCommand>(command); auto closeCommand = std::get<CloseCommand>(command);
Serial.printf("Close command received for shade 0x%04x\n", closeCommand.shadeID); Serial.printf("Close command received for shade 0x%04x\n", closeCommand.shadeID);
if (packetSender.send(packetBuilder.buildClosePacket(closeCommand.shadeID))) { if (sendClosePacket(closeCommand.shadeID)) {
auto shade = shadeRepository.findById(closeCommand.shadeID); auto shade = shadeRepository.findById(closeCommand.shadeID);
if (shade != nullptr) { if (shade != nullptr) {
shade->state = "closing"; shade->state = "closing";
@@ -152,13 +157,13 @@ void loop() {
} else if (std::holds_alternative<StopCommand>(command)) { } else if (std::holds_alternative<StopCommand>(command)) {
auto stopCommand = std::get<StopCommand>(command); auto stopCommand = std::get<StopCommand>(command);
Serial.printf("Stop command received for shade 0x%04x\n", stopCommand.shadeID); Serial.printf("Stop command received for shade 0x%04x\n", stopCommand.shadeID);
packetSender.send(packetBuilder.buildStopPacket(stopCommand.shadeID)); sendStopPacket(stopCommand.shadeID);
startPositionWatcher(stopCommand.shadeID, -1); startPositionWatcher(stopCommand.shadeID, -1);
} else if (std::holds_alternative<SetPositionCommand>(command)) { } else if (std::holds_alternative<SetPositionCommand>(command)) {
auto setPositionCommand = std::get<SetPositionCommand>(command); auto setPositionCommand = std::get<SetPositionCommand>(command);
Serial.printf("Set Position command received for shade 0x%04x (%.2f)\n", setPositionCommand.shadeID, setPositionCommand.percentage); Serial.printf("Set Position command received for shade 0x%04x (%.2f)\n", setPositionCommand.shadeID, setPositionCommand.percentage);
if (packetSender.send(packetBuilder.buildSetPositionPacket(setPositionCommand.shadeID, setPositionCommand.percentage))) { if (sendSetPosition(setPositionCommand.shadeID, setPositionCommand.percentage)) {
auto shade = shadeRepository.findById(setPositionCommand.shadeID); auto shade = shadeRepository.findById(setPositionCommand.shadeID);
if (shade != nullptr) { if (shade != nullptr) {
if (setPositionCommand.percentage > shade->lastPosition / 100.0f) { if (setPositionCommand.percentage > shade->lastPosition / 100.0f) {
@@ -180,7 +185,7 @@ void loop() {
for (auto& [shadeID, watcher] : positionWatchers) { for (auto& [shadeID, watcher] : positionWatchers) {
if (watcher.shouldFetch()) { if (watcher.shouldFetch()) {
if (packetSender.send(packetBuilder.buildFetchPositionPacket(shadeID))) { if (sendFetchPosition(shadeID)) {
watcher.fetchQueued(); watcher.fetchQueued();
} }
} else if (!watcher.isWatching()) { } else if (!watcher.isWatching()) {
@@ -235,7 +240,8 @@ void processPacket(const Packet *packet) {
// Update last rolling codes each time a packet from a real hub is detected // Update last rolling codes each time a packet from a real hub is detected
if (source == 0x0000) { if (source == 0x0000) {
Serial.println("Updating rolling codes"); Serial.println("Updating rolling codes");
packetSender.setLastRollingCodes(packet->rollingCode1, packet->rollingCode2); lastRollingCode1 = packet->rollingCode1;
lastRollingCode2 = packet->rollingCode2;
} }
if (packet->type == PacketType::FIELDS) { if (packet->type == PacketType::FIELDS) {
@@ -291,6 +297,96 @@ void onConnectionEstablished() {
}); });
} }
bool sendOpenPacket(uint16_t destination) {
Packet packet;
auto header = UnicastHeader {};
header.destination = destination;
header.source = 0x0000;
packet.header = header;
packet.type = PacketType::OPEN;
return sendPacket(&packet);
}
bool sendClosePacket(uint16_t destination) {
Packet packet;
auto header = UnicastHeader {};
header.destination = destination;
header.source = 0x0000;
packet.header = header;
packet.type = PacketType::CLOSE;
return sendPacket(&packet);
}
bool sendStopPacket(uint16_t destination) {
Packet packet;
auto header = UnicastHeader {};
header.destination = destination;
header.source = 0x0000;
packet.header = header;
packet.type = PacketType::STOP;
return sendPacket(&packet);
}
bool sendSetPosition(uint16_t destination, float percentage) {
Packet packet;
auto header = UnicastHeader {};
header.destination = destination;
header.source = 0x0000;
packet.header = header;
packet.type = PacketType::FIELD_COMMAND;
std::vector<Field> fields;
uint8_t identifier = 0x50; // position
FieldType type = FieldType::SET;
uint16_t position = (uint16_t)(0xFFFF * percentage);
fields.push_back(Field{identifier, type, true, position});
packet.parameters = FieldsParameters {fields};
return sendPacket(&packet);
}
bool sendFetchPosition(uint16_t destination) {
Packet packet;
auto header = UnicastHeader {};
header.destination = destination;
header.source = 0x0000;
packet.header = header;
packet.type = PacketType::FIELD_COMMAND;
std::vector<Field> fields;
// position
fields.push_back(Field{0x50, FieldType::FETCH, false, std::monostate{}});
// battery
fields.push_back(Field{0x42, FieldType::FETCH, false, std::monostate{}});
packet.parameters = FieldsParameters {fields};
return sendPacket(&packet);
}
bool sendPacket(Packet *packet) {
Serial.println("Attempting to send a packet");
packet->rollingCode1 = lastRollingCode1 + 1;
packet->rollingCode2 = lastRollingCode2 + 1;
bool didSend = powerView.sendPacket(packet);
if (!didSend) {
Serial.println("Failed to send");
return false;
} else {
lastRollingCode1++;
lastRollingCode2++;
return true;
}
}
void startPositionWatcher(uint16_t shadeID, uint8_t targetValue) { void startPositionWatcher(uint16_t shadeID, uint8_t targetValue) {
auto& watcher = positionWatchers.try_emplace(shadeID).first->second; auto& watcher = positionWatchers.try_emplace(shadeID).first->second;
watcher.start(targetValue); watcher.start(targetValue);

View File

@@ -10,11 +10,6 @@ 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;
@@ -28,26 +23,71 @@ 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.processJson(singleBlindJson); configurator.processIdMessage("hotdog/test_shade/id", "ABCD");
configurator.processFriendlyNameMessage("hotdog/test_shade/friendly_name", "Test Shade");
TEST_ASSERT_EQUAL_INT(1, callbackInvokedCount); TEST_ASSERT_EQUAL_INT(1, callbackInvokedCount);
} }
void test_multiple_shades_are_configured() void test_shade_is_configured_again_with_new_friendly_name()
{ {
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.processJson(doubleBlindJson); 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");
TEST_ASSERT_EQUAL_INT(2, callbackInvokedCount); TEST_ASSERT_EQUAL_INT(2, callbackInvokedCount);
} }
void test_invalid_radio_id_is_ignored() 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()
{ {
int callbackInvokedCount = 0; int callbackInvokedCount = 0;
@@ -56,12 +96,12 @@ void test_invalid_radio_id_is_ignored()
callbackInvokedCount++; callbackInvokedCount++;
}); });
configurator.processJson(invalidRadioIdBlindJson); configurator.processIdMessage("hotdog/test_shade/id", "ABCD");
TEST_ASSERT_EQUAL_INT(0, callbackInvokedCount); TEST_ASSERT_EQUAL_INT(0, callbackInvokedCount);
} }
void test_invalid_mqtt_id_is_ignored() void test_shade_is_not_configured_without_id()
{ {
int callbackInvokedCount = 0; int callbackInvokedCount = 0;
@@ -70,7 +110,22 @@ void test_invalid_mqtt_id_is_ignored()
callbackInvokedCount++; callbackInvokedCount++;
}); });
configurator.processJson(invalidMqttIdBlindJson); configurator.processFriendlyNameMessage("hotdog/test_shade/friendly_name", "Test Shade");
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);
} }
@@ -79,9 +134,11 @@ 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_multiple_shades_are_configured); RUN_TEST(test_shade_is_configured_again_with_new_friendly_name);
RUN_TEST(test_invalid_radio_id_is_ignored); RUN_TEST(test_shade_is_configured_again_with_new_id);
RUN_TEST(test_invalid_mqtt_id_is_ignored); 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);
return UNITY_END(); return UNITY_END();
} }