Compare commits
4 Commits
94f00c0cef
...
32e0462a69
| Author | SHA1 | Date | |
|---|---|---|---|
| 32e0462a69 | |||
| b50edfa4aa | |||
| 41af7b508f | |||
| 680b6b3b6a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
.vscode/settings.json
|
||||
|
||||
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 <cctype>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
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)
|
||||
if (!std::isxdigit(c)) {
|
||||
return; // Invalid character, not a hex digit
|
||||
std::string radioId = std::string(rawRadioId);
|
||||
|
||||
if (radioId.size() != 4) {
|
||||
return 0; // Invalid length
|
||||
}
|
||||
|
||||
// Convert hex digit to numerical value (0-9, A-B=10-11, ...)
|
||||
int digit = std::toupper(c) - (c <= '9' ? '0' : 'A' - 10);
|
||||
for (char c : radioId) {
|
||||
if (!std::isxdigit(c)) {
|
||||
return 0; // Invalid character, not a hex digit
|
||||
}
|
||||
|
||||
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<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) {
|
||||
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:
|
||||
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);
|
||||
bool tryBuild(std::string topic);
|
||||
uint16_t parseRadioId(const char* rawRadioId);
|
||||
|
||||
public:
|
||||
Configurator();
|
||||
|
||||
void addShadeConfiguredCallback(std::function<void (Shade)> 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
|
||||
84
lib/packet_builder/PacketBuilder.cpp
Normal file
84
lib/packet_builder/PacketBuilder.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#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;
|
||||
}
|
||||
17
lib/packet_builder/PacketBuilder.h
Normal file
17
lib/packet_builder/PacketBuilder.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#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
|
||||
27
lib/packet_sender/PacketSender.cpp
Normal file
27
lib/packet_sender/PacketSender.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#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;
|
||||
}
|
||||
20
lib/packet_sender/PacketSender.h
Normal file
20
lib/packet_sender/PacketSender.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#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
|
||||
@@ -37,7 +37,6 @@ lib_deps =
|
||||
[env:test_desktop]
|
||||
platform = native
|
||||
build_type = test
|
||||
lib_compat_mode = off
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
ArduinoFake
|
||||
|
||||
1
src/.gitignore
vendored
1
src/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
secrets.h
|
||||
config.h
|
||||
126
src/main.cpp
126
src/main.cpp
@@ -4,6 +4,7 @@
|
||||
#include <RFPowerView.h>
|
||||
#include <random>
|
||||
#include <chrono>
|
||||
#include "config.h"
|
||||
#include "Shade.h"
|
||||
#include "Configurator.h"
|
||||
#include "ShadeRepository.h"
|
||||
@@ -11,15 +12,14 @@
|
||||
#include "secrets.h"
|
||||
#include "ShadeCommand.h"
|
||||
#include "PositionWatcher.h"
|
||||
#include "PacketBuilder.h"
|
||||
#include "PacketSender.h"
|
||||
#include <queue>
|
||||
|
||||
#define SER_BAUDRATE (115200)
|
||||
|
||||
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());
|
||||
|
||||
std::string generate_client_id_suffix(int length) {
|
||||
@@ -60,17 +60,14 @@ HADiscovery haDiscovery(topic_prefix.c_str(), [] (const char* topic, const char*
|
||||
client.publish(topic, message);
|
||||
});
|
||||
|
||||
PacketBuilder packetBuilder;
|
||||
|
||||
PacketSender packetSender(powerView);
|
||||
|
||||
void processPacket(const Packet*);
|
||||
void processCommandMessage(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 publishState(const std::string& shadeKey, const String& state);
|
||||
void publishBattery(const std::string& shadeKey, const uint8_t battery);
|
||||
@@ -113,15 +110,13 @@ void setup() {
|
||||
publishDiscoveryTopics();
|
||||
});
|
||||
|
||||
delay(100);
|
||||
configurator.processJson(config_json);
|
||||
|
||||
shadeRepository.upsert(Shade{0x4EF1, "study_blind", "Study Blind", "stopped", -1, -1, false});
|
||||
delay(100);
|
||||
|
||||
Serial.println("Ready");
|
||||
}
|
||||
|
||||
int loopCounter = 0;
|
||||
|
||||
void loop() {
|
||||
powerView.loop();
|
||||
client.loop();
|
||||
@@ -134,7 +129,7 @@ void loop() {
|
||||
if (std::holds_alternative<OpenCommand>(command)) {
|
||||
auto openCommand = std::get<OpenCommand>(command);
|
||||
Serial.printf("Open command received for shade 0x%04x\n", openCommand.shadeID);
|
||||
if (sendOpenPacket(openCommand.shadeID)) {
|
||||
if (packetSender.send(packetBuilder.buildOpenPacket(openCommand.shadeID))) {
|
||||
auto shade = shadeRepository.findById(openCommand.shadeID);
|
||||
if (shade != nullptr) {
|
||||
shade->state = "opening";
|
||||
@@ -146,7 +141,7 @@ void loop() {
|
||||
} else if (std::holds_alternative<CloseCommand>(command)) {
|
||||
auto closeCommand = std::get<CloseCommand>(command);
|
||||
Serial.printf("Close command received for shade 0x%04x\n", closeCommand.shadeID);
|
||||
if (sendClosePacket(closeCommand.shadeID)) {
|
||||
if (packetSender.send(packetBuilder.buildClosePacket(closeCommand.shadeID))) {
|
||||
auto shade = shadeRepository.findById(closeCommand.shadeID);
|
||||
if (shade != nullptr) {
|
||||
shade->state = "closing";
|
||||
@@ -157,13 +152,13 @@ void loop() {
|
||||
} else if (std::holds_alternative<StopCommand>(command)) {
|
||||
auto stopCommand = std::get<StopCommand>(command);
|
||||
Serial.printf("Stop command received for shade 0x%04x\n", stopCommand.shadeID);
|
||||
sendStopPacket(stopCommand.shadeID);
|
||||
packetSender.send(packetBuilder.buildStopPacket(stopCommand.shadeID));
|
||||
startPositionWatcher(stopCommand.shadeID, -1);
|
||||
|
||||
} else if (std::holds_alternative<SetPositionCommand>(command)) {
|
||||
auto setPositionCommand = std::get<SetPositionCommand>(command);
|
||||
Serial.printf("Set Position command received for shade 0x%04x (%.2f)\n", setPositionCommand.shadeID, setPositionCommand.percentage);
|
||||
if (sendSetPosition(setPositionCommand.shadeID, setPositionCommand.percentage)) {
|
||||
if (packetSender.send(packetBuilder.buildSetPositionPacket(setPositionCommand.shadeID, setPositionCommand.percentage))) {
|
||||
auto shade = shadeRepository.findById(setPositionCommand.shadeID);
|
||||
if (shade != nullptr) {
|
||||
if (setPositionCommand.percentage > shade->lastPosition / 100.0f) {
|
||||
@@ -185,7 +180,7 @@ void loop() {
|
||||
|
||||
for (auto& [shadeID, watcher] : positionWatchers) {
|
||||
if (watcher.shouldFetch()) {
|
||||
if (sendFetchPosition(shadeID)) {
|
||||
if (packetSender.send(packetBuilder.buildFetchPositionPacket(shadeID))) {
|
||||
watcher.fetchQueued();
|
||||
}
|
||||
} else if (!watcher.isWatching()) {
|
||||
@@ -240,8 +235,7 @@ void processPacket(const Packet *packet) {
|
||||
// Update last rolling codes each time a packet from a real hub is detected
|
||||
if (source == 0x0000) {
|
||||
Serial.println("Updating rolling codes");
|
||||
lastRollingCode1 = packet->rollingCode1;
|
||||
lastRollingCode2 = packet->rollingCode2;
|
||||
packetSender.setLastRollingCodes(packet->rollingCode1, packet->rollingCode2);
|
||||
}
|
||||
|
||||
if (packet->type == PacketType::FIELDS) {
|
||||
@@ -297,96 +291,6 @@ 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) {
|
||||
auto& watcher = positionWatchers.try_emplace(shadeID).first->second;
|
||||
watcher.start(targetValue);
|
||||
|
||||
@@ -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,22 +70,7 @@ void test_shade_is_not_configured_without_id()
|
||||
callbackInvokedCount++;
|
||||
});
|
||||
|
||||
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");
|
||||
configurator.processJson(invalidMqttIdBlindJson);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, callbackInvokedCount);
|
||||
}
|
||||
@@ -134,11 +79,9 @@ 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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user