Extract Shade struct and create ShadeRepository

This commit is contained in:
2024-05-04 15:40:58 +10:00
parent eee6cbef46
commit a9dccae331
5 changed files with 386 additions and 123 deletions

18
lib/shade/Shade.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef SHADE_H
#define SHADE_H
#include <stdint.h>
#include <string>
struct Shade {
uint16_t ID;
std::string key;
std::string friendlyName;
int8_t lastTargetPosition;
int8_t lastPosition;
uint8_t samePositionCount;
uint8_t positionFetchCount;
void* timer;
};
#endif // SHADE_H

View File

@@ -0,0 +1,66 @@
#include "ShadeRepository.h"
#include "Shade.h"
void ShadeRepository::upsert(Shade shade)
{
for (Shade &existing_shade : shades)
{
if (existing_shade.key == shade.key)
{
existing_shade.friendlyName = shade.friendlyName;
existing_shade.ID = shade.ID;
for (auto &callback : shadeChangedCallbacks) {
callback(shade);
}
return;
}
}
// Didn't find it in the repository, add it
shades.push_back(shade);
for (auto &callback : shadeAddedCallbacks) {
callback(shade);
}
}
Shade* ShadeRepository::findById(const uint16_t id)
{
for (Shade &shade : shades)
{
if (shade.ID == id)
{
return &shade;
}
}
return nullptr;
}
Shade* ShadeRepository::findByKey(const std::string &key)
{
for (Shade &shade : shades)
{
if (shade.key == key)
{
return &shade;
}
}
return nullptr;
}
std::vector<Shade>::iterator ShadeRepository::begin()
{
return shades.begin();
}
std::vector<Shade>::iterator ShadeRepository::end()
{
return shades.end();
}
void ShadeRepository::addShadeAddedCallback(std::function<void(Shade&)> callback) {
shadeAddedCallbacks.push_back(callback);
}
void ShadeRepository::addShadeChangedCallback(std::function<void(Shade&)> callback) {
shadeChangedCallbacks.push_back(callback);
}

View File

@@ -0,0 +1,25 @@
#ifndef SHADE_REPOSITORY_H
#define SHADE_REPOSITORY_H
#include <vector>
#include <functional>
#include "Shade.h"
class ShadeRepository {
private:
std::vector<Shade> shades;
std::vector<std::function<void(Shade&)>> shadeAddedCallbacks;
std::vector<std::function<void(Shade&)>> shadeChangedCallbacks;
public:
void upsert(Shade shade);
std::vector<Shade>::iterator begin();
std::vector<Shade>::iterator end();
Shade* findById(const uint16_t id);
Shade* findByKey(const std::string& key);
void addShadeAddedCallback(std::function<void(Shade&)> callback);
void addShadeChangedCallback(std::function<void(Shade&)> callback);
};
#endif // SHADE_REPOSITORY_H

View File

@@ -3,6 +3,8 @@
#include <arduino-timer.h> #include <arduino-timer.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <RFPowerView.h> #include <RFPowerView.h>
#include "Shade.h"
#include "ShadeRepository.h"
#include "secrets.h" #include "secrets.h"
#define SER_BAUDRATE (115200) #define SER_BAUDRATE (115200)
@@ -26,6 +28,8 @@ EspMQTTClient client(
1883 1883
); );
ShadeRepository shadeRepository = ShadeRepository();
#define MAX_FETCH_COUNT (20) #define MAX_FETCH_COUNT (20)
auto timer = Timer<10, millis, uint16_t>(); auto timer = Timer<10, millis, uint16_t>();
@@ -49,40 +53,11 @@ void publishBattery(const String& shadeName, const uint8_t battery);
void publishDiscoveryTopics(); void publishDiscoveryTopics();
struct Shade {
uint16_t ID;
String name;
String friendlyName;
int8_t lastTargetPosition;
int8_t lastPosition;
uint8_t samePositionCount;
uint8_t positionFetchCount;
void* timer;
};
std::vector<Shade> shades;
void setup() { void setup() {
Serial.begin(SER_BAUDRATE); Serial.begin(SER_BAUDRATE);
Serial.println("Starting up"); Serial.println("Starting up");
shades.push_back(Shade{0x4EF1, "study_blind", "Study Blind", -1, -1, 0, 0, nullptr});
shades.push_back(Shade{0xA51F, "studio_blockout_left_blind", "Studio Blockout Left Blind", -1, -1, 0, 0, nullptr});
shades.push_back(Shade{0xDAEF, "studio_blockout_right_blind", "Studio Blockout Right Blind", -1, -1, 0, 0, nullptr});
shades.push_back(Shade{0x7687, "studio_left_blind", "Studio Left Blind", -1, -1, 0, 0, nullptr});
shades.push_back(Shade{0xB0DE, "studio_right_blind", "Studio Right Blind", -1, -1, 0, 0, nullptr});
shades.push_back(Shade{0xB451, "bedroom_door_blockout_blind", "Bedroom Door Blockout Blind", -1, -1, 0, 0, nullptr});
shades.push_back(Shade{0x48A6, "bedroom_window_blockout_blind", "Bedroom Window Blockout Blind", -1, -1, 0, 0, nullptr});
shades.push_back(Shade{0x9E14, "bedroom_door_blind", "Bedroom Door Blind", -1, -1, 0, 0, nullptr});
shades.push_back(Shade{0x061C, "bedroom_window_blind", "Bedroom Window Blind", -1, -1, 0, 0, nullptr});
shades.push_back(Shade{0x2959, "bathroom_blind", "Bathroom Blind", -1, -1, 0, 0, nullptr});
shades.push_back(Shade{0x6FAD, "kitchen_door_blind", "Kitchen Door Blind", -1, -1, 0, 0, nullptr});
shades.push_back(Shade{0xFB21, "kitchen_window_blind", "Kitchen Window Blind", -1, -1, 0, 0, nullptr});
shades.push_back(Shade{0x8B10, "living_room_big_window_blind", "Living Room Big Window Blind", -1, -1, 0, 0, nullptr});
shades.push_back(Shade{0x3EB8, "living_room_door_blind", "Living Room Door Blind", -1, -1, 0, 0, nullptr});
shades.push_back(Shade{0x5463, "living_room_window_blind", "Living Room Window Blind", -1, -1, 0, 0, nullptr});
powerView.setPacketReceivedCallback(processPacket); powerView.setPacketReceivedCallback(processPacket);
if (!powerView.begin()) { if (!powerView.begin()) {
Serial.println("Failed to start RFPowerView"); Serial.println("Failed to start RFPowerView");
@@ -94,7 +69,37 @@ void setup() {
client.enableDebuggingMessages(); client.enableDebuggingMessages();
client.setMaxPacketSize(2048); client.setMaxPacketSize(2048);
shadeRepository.addShadeAddedCallback([] (Shade& shade) {
// Only add shade if client is already connected
if (client.isConnected()) {
client.subscribe(("hotdog/" + shade.key + "/command").c_str(), processCommandMessage);
client.subscribe(("hotdog/" + shade.key + "/set_position").c_str(), processSetPositionMessage);
}
publishDiscoveryTopics();
});
shadeRepository.addShadeChangedCallback([] (Shade& shade) {
publishDiscoveryTopics();
});
delay(100); delay(100);
shadeRepository.upsert(Shade{0x4EF1, "study_blind", "Study Blind", -1, -1, 0, 0, nullptr});
shadeRepository.upsert(Shade{0xA51F, "studio_blockout_left_blind", "Studio Blockout Left Blind", -1, -1, 0, 0, nullptr});
shadeRepository.upsert(Shade{0xDAEF, "studio_blockout_right_blind", "Studio Blockout Right Blind", -1, -1, 0, 0, nullptr});
shadeRepository.upsert(Shade{0x7687, "studio_left_blind", "Studio Left Blind", -1, -1, 0, 0, nullptr});
shadeRepository.upsert(Shade{0xB0DE, "studio_right_blind", "Studio Right Blind", -1, -1, 0, 0, nullptr});
shadeRepository.upsert(Shade{0xB451, "bedroom_door_blockout_blind", "Bedroom Door Blockout Blind", -1, -1, 0, 0, nullptr});
shadeRepository.upsert(Shade{0x48A6, "bedroom_window_blockout_blind", "Bedroom Window Blockout Blind", -1, -1, 0, 0, nullptr});
shadeRepository.upsert(Shade{0x9E14, "bedroom_door_blind", "Bedroom Door Blind", -1, -1, 0, 0, nullptr});
shadeRepository.upsert(Shade{0x061C, "bedroom_window_blind", "Bedroom Window Blind", -1, -1, 0, 0, nullptr});
shadeRepository.upsert(Shade{0x2959, "bathroom_blind", "Bathroom Blind", -1, -1, 0, 0, nullptr});
shadeRepository.upsert(Shade{0x6FAD, "kitchen_door_blind", "Kitchen Door Blind", -1, -1, 0, 0, nullptr});
shadeRepository.upsert(Shade{0xFB21, "kitchen_window_blind", "Kitchen Window Blind", -1, -1, 0, 0, nullptr});
shadeRepository.upsert(Shade{0x8B10, "living_room_big_window_blind", "Living Room Big Window Blind", -1, -1, 0, 0, nullptr});
shadeRepository.upsert(Shade{0x3EB8, "living_room_door_blind", "Living Room Door Blind", -1, -1, 0, 0, nullptr});
shadeRepository.upsert(Shade{0x5463, "living_room_window_blind", "Living Room Window Blind", -1, -1, 0, 0, nullptr});
Serial.println("Ready"); Serial.println("Ready");
} }
@@ -135,29 +140,27 @@ void processPacket(const Packet *packet) {
for (size_t i = 0; i < parameters.fields.size(); i++) { for (size_t i = 0; i < parameters.fields.size(); i++) {
Field field = parameters.fields[i]; Field field = parameters.fields[i];
if (field.identifier == 0x50) { if (field.identifier == 0x50) {
for (size_t i = 0; i < shades.size(); i++) { auto shade = shadeRepository.findById(source);
if (source == shades[i].ID) { if (shade != nullptr) {
uint16_t value = std::get<uint16_t>(field.value); uint16_t value = std::get<uint16_t>(field.value);
uint8_t position = (uint8_t)std::round(((float)value / 0xFFFF) * 100); uint8_t position = (uint8_t)std::round(((float)value / 0xFFFF) * 100);
if (shades[i].lastPosition == position) { if (shade->lastPosition == position) {
shades[i].samePositionCount++; shade->samePositionCount++;
} else { } else {
shades[i].samePositionCount = 1; shade->samePositionCount = 1;
}
shades[i].lastPosition = position;
publishPosition(shades[i].name, position);
} }
shade->lastPosition = position;
publishPosition(shade->key.c_str(), position);
} }
} else if (field.identifier == 0x42) { } else if (field.identifier == 0x42) {
for (size_t i = 0; i < shades.size(); i++) { auto shade = shadeRepository.findById(source);
if (source == shades[i].ID) { if (shade != nullptr) {
uint8_t value = std::get<uint8_t>(field.value); uint8_t value = std::get<uint8_t>(field.value);
uint8_t battery = uint8_t(((float)value / 200) * 100); uint8_t battery = uint8_t(((float)value / 200) * 100);
publishBattery(shades[i].name, battery); publishBattery(shade->key.c_str(), battery);
}
} }
} }
} }
@@ -167,9 +170,9 @@ void processPacket(const Packet *packet) {
void onConnectionEstablished() { void onConnectionEstablished() {
Serial.println("Connection established"); Serial.println("Connection established");
for (size_t i = 0; i < shades.size(); i++) { for (auto shade = shadeRepository.begin(); shade != shadeRepository.end(); shade++) {
client.subscribe("hotdog/" + shades[i].name + "/command", processCommandMessage); client.subscribe(("hotdog/" + shade->key + "/command").c_str(), processCommandMessage);
client.subscribe("hotdog/" + shades[i].name + "/set_position", processSetPositionMessage); client.subscribe(("hotdog/" + shade->key + "/set_position").c_str(), processSetPositionMessage);
} }
client.publish("hotdog/availability", "online", true); client.publish("hotdog/availability", "online", true);
@@ -276,30 +279,29 @@ bool sendPacket(Packet *packet) {
void processCommandMessage(const String &topic, const String &payload) { void processCommandMessage(const String &topic, const String &payload) {
int startIndex = topic.indexOf("/") + 1; int startIndex = topic.indexOf("/") + 1;
int endIndex = topic.indexOf("/", startIndex); int endIndex = topic.indexOf("/", startIndex);
String shadeName = topic.substring(startIndex, endIndex); auto key = topic.substring(startIndex, endIndex).c_str();
for (size_t i = 0; i < shades.size(); i++) { auto shade = shadeRepository.findByKey(key);
if (shades[i].name == shadeName) { if (shade != nullptr) {
if (payload == "OPEN") { if (payload == "OPEN") {
sendOpenPacket(shades[i].ID); sendOpenPacket(shade->ID);
startFetchingPosition(shades[i].ID, 100); startFetchingPosition(shade->ID, 100);
publishState(shades[i].name, "opening"); publishState(shade->key.c_str(), "opening");
} else if (payload == "CLOSE") { } else if (payload == "CLOSE") {
sendClosePacket(shades[i].ID); sendClosePacket(shade->ID);
startFetchingPosition(shades[i].ID, 0); startFetchingPosition(shade->ID, 0);
publishState(shades[i].name, "closing"); publishState(shade->key.c_str(), "closing");
} else if (payload == "STOP") { } else if (payload == "STOP") {
sendStopPacket(shades[i].ID); sendStopPacket(shade->ID);
startFetchingPosition(shades[i].ID, -1); startFetchingPosition(shade->ID, -1);
timer.in(100, sendFetchPosition, shades[i].ID); timer.in(100, sendFetchPosition, shade->ID);
} else if (payload == "REFRESH") { } else if (payload == "REFRESH") {
startFetchingPosition(shades[i].ID, -1); startFetchingPosition(shade->ID, -1);
}
} }
} }
} }
@@ -307,62 +309,59 @@ void processCommandMessage(const String &topic, const String &payload) {
void processSetPositionMessage(const String& topic, const String &payload) { void processSetPositionMessage(const String& topic, const String &payload) {
int startIndex = topic.indexOf("/") + 1; int startIndex = topic.indexOf("/") + 1;
int endIndex = topic.indexOf("/", startIndex); int endIndex = topic.indexOf("/", startIndex);
String shadeName = topic.substring(startIndex, endIndex); auto key = topic.substring(startIndex, endIndex).c_str();
for (size_t i = 0; i < shades.size(); i++) { auto shade = shadeRepository.findByKey(key);
if (shades[i].name == shadeName) { if (shade != nullptr) {
float percentage = payload.toInt() / 100.0f; float percentage = payload.toInt() / 100.0f;
sendSetPosition(shades[i].ID, percentage); sendSetPosition(shade->ID, percentage);
if (payload.toInt() > shades[i].lastPosition) { if (payload.toInt() > shade->lastPosition) {
publishState(shades[i].name, "opening"); publishState(shade->key.c_str(), "opening");
} else if (payload.toInt() < shades[i].lastPosition) { } else if (payload.toInt() < shade->lastPosition) {
publishState(shades[i].name, "closing"); publishState(shade->key.c_str(), "closing");
}
startFetchingPosition(shades[i].ID, payload.toInt());
} }
startFetchingPosition(shade->ID, payload.toInt());
} }
} }
bool checkPosition(uint16_t shadeID) { bool checkPosition(uint16_t shadeID) {
for (size_t i = 0; i < shades.size(); i++) { auto shade = shadeRepository.findById(shadeID);
if (shades[i].ID == shadeID) { if (shade != nullptr) {
// Keep fetching position if: // Keep fetching position if:
// - the last reported position doesn't match the target position // - the last reported position doesn't match the target position
// - a position hasn't been reported yet // - a position hasn't been reported yet
// - there is no target position // - there is no target position
if (shades[i].lastTargetPosition != shades[i].lastPosition || shades[i].lastPosition == -1 || shades[i].lastTargetPosition == -1) { if (shade->lastTargetPosition != shade->lastPosition || shade->lastPosition == -1 || shade->lastTargetPosition == -1) {
// Keep fetching position if the count limits have not been reached // Keep fetching position if the count limits have not been reached
if (shades[i].positionFetchCount < MAX_FETCH_COUNT && shades[i].samePositionCount < 2) { if (shade->positionFetchCount < MAX_FETCH_COUNT && shade->samePositionCount < 2) {
shades[i].positionFetchCount++; shade->positionFetchCount++;
sendFetchPosition(shadeID); sendFetchPosition(shadeID);
return true; return true;
}
} }
publishState(shades[i].name, shades[i].lastPosition > 0 ? "open" : "closed");
return false;
} }
publishState(shade->key.c_str(), shade->lastPosition > 0 ? "open" : "closed");
return false;
} }
return false; return false;
} }
void startFetchingPosition(uint16_t shadeID, int8_t targetPosition) { void startFetchingPosition(uint16_t shadeID, int8_t targetPosition) {
for (size_t i = 0; i < shades.size(); i++) { auto shade = shadeRepository.findById(shadeID);
if (shades[i].ID == shadeID) { if (shade != nullptr) {
// Cancel any existing timer // Cancel any existing timer
if (shades[i].timer != nullptr) { if (shade->timer != nullptr) {
timer.cancel(shades[i].timer); timer.cancel(shade->timer);
shades[i].timer = nullptr; shade->timer = nullptr;
}
shades[i].lastTargetPosition = targetPosition;
shades[i].positionFetchCount = 0;
shades[i].samePositionCount = 1;
shades[i].timer = timer.every(2000, checkPosition, shades[i].ID);
} }
shade->lastTargetPosition = targetPosition;
shade->positionFetchCount = 0;
shade->samePositionCount = 1;
shade->timer = timer.every(2000, checkPosition, shade->ID);
} }
} }
@@ -400,10 +399,10 @@ void publishCoverDiscoveryTopic(const Shade& shade) {
doc["name"] = nullptr; doc["name"] = nullptr;
doc["unique_id"] = entityID; doc["unique_id"] = entityID;
doc["availability_topic"] = "hotdog/availability"; doc["availability_topic"] = "hotdog/availability";
doc["state_topic"] = "hotdog/" + shade.name + "/state"; doc["state_topic"] = "hotdog/" + shade.key + "/state";
doc["command_topic"] = "hotdog/" + shade.name + "/command"; doc["command_topic"] = "hotdog/" + shade.key + "/command";
doc["position_topic"] = "hotdog/" + shade.name + "/position"; doc["position_topic"] = "hotdog/" + shade.key + "/position";
doc["set_position_topic"] = "hotdog/" + shade.name + "/set_position"; doc["set_position_topic"] = "hotdog/" + shade.key + "/set_position";
doc["position_open"] = 100; doc["position_open"] = 100;
doc["position_closed"] = 0; doc["position_closed"] = 0;
doc["optimistic"] = false; doc["optimistic"] = false;
@@ -425,7 +424,7 @@ void publishBatteryDiscoveryTopic(const Shade& shade) {
doc["unique_id"] = entityID; doc["unique_id"] = entityID;
doc["availability_topic"] = "hotdog/availability"; doc["availability_topic"] = "hotdog/availability";
doc["device_class"] = "battery"; doc["device_class"] = "battery";
doc["state_topic"] = "hotdog/" + shade.name + "/battery"; doc["state_topic"] = "hotdog/" + shade.key + "/battery";
addDeviceObject(doc, shade); addDeviceObject(doc, shade);
@@ -443,7 +442,7 @@ void publishRefreshButtonDiscoveryTopic(const Shade& shade) {
doc["name"] = "Refresh"; doc["name"] = "Refresh";
doc["unique_id"] = entityID; doc["unique_id"] = entityID;
doc["availability_topic"] = "hotdog/availability"; doc["availability_topic"] = "hotdog/availability";
doc["command_topic"] = "hotdog/" + shade.name + "/command"; doc["command_topic"] = "hotdog/" + shade.key + "/command";
doc["payload_press"] = "REFRESH"; doc["payload_press"] = "REFRESH";
addDeviceObject(doc, shade); addDeviceObject(doc, shade);
@@ -454,12 +453,9 @@ void publishRefreshButtonDiscoveryTopic(const Shade& shade) {
} }
void publishDiscoveryTopics() { void publishDiscoveryTopics() {
for (size_t i = 0; i < shades.size(); i++) { for (auto shade = shadeRepository.begin(); shade != shadeRepository.end(); shade++) {
Shade shade = shades[i]; publishCoverDiscoveryTopic(*shade);
String objectID = String(shade.ID, HEX); publishBatteryDiscoveryTopic(*shade);
publishRefreshButtonDiscoveryTopic(*shade);
publishCoverDiscoveryTopic(shade);
publishBatteryDiscoveryTopic(shade);
publishRefreshButtonDiscoveryTopic(shade);
} }
} }

View File

@@ -0,0 +1,158 @@
#include <unity.h>
#include <Arduino.h>
#include "ShadeRepository.h"
void setUp()
{
}
void tearDown()
{
}
void test_shade_is_found_with_id()
{
ShadeRepository shadeRepository = ShadeRepository();
shadeRepository.upsert(Shade{0xABCD, "test_shade", "Test Shade", -1, -1, 0, 0, nullptr});
auto shade = shadeRepository.findById(0xABCD);
TEST_ASSERT_NOT_NULL_MESSAGE(shade, "shade should not be null");
TEST_ASSERT_EQUAL_STRING("Test Shade", shade->friendlyName.c_str());
TEST_ASSERT_EQUAL_HEX16(0xABCD, shade->ID);
TEST_ASSERT_EQUAL_STRING("test_shade", shade->key.c_str());
}
void test_shade_is_found_with_key()
{
ShadeRepository shadeRepository = ShadeRepository();
shadeRepository.upsert(Shade{0xABCD, "test_shade", "Test Shade", -1, -1, 0, 0, nullptr});
auto shade = shadeRepository.findByKey("test_shade");
TEST_ASSERT_NOT_NULL_MESSAGE(shade, "shade should not be null");
TEST_ASSERT_EQUAL_STRING("Test Shade", shade->friendlyName.c_str());
TEST_ASSERT_EQUAL_HEX16(0xABCD, shade->ID);
TEST_ASSERT_EQUAL_STRING("test_shade", shade->key.c_str());
}
void test_adding_shade_twice_only_added_once()
{
ShadeRepository shadeRepository = ShadeRepository();
shadeRepository.upsert(Shade{0xABCD, "test_shade", "Test Shade", -1, -1, 0, 0, nullptr});
shadeRepository.upsert(Shade{0xABCD, "test_shade", "Test Shade", -1, -1, 0, 0, nullptr});
int count = 0;
for (auto iter = shadeRepository.begin(); iter != shadeRepository.end(); iter++) {
count++;
}
TEST_ASSERT_EQUAL_INT(1, count);
}
void test_updating_shade_id()
{
ShadeRepository shadeRepository = ShadeRepository();
shadeRepository.upsert(Shade{0xABCD, "test_shade", "Test Shade", -1, -1, 0, 0, nullptr});
auto shade1 = shadeRepository.findById(0xABCD);
TEST_ASSERT_EQUAL_HEX16(0xABCD, shade1->ID);
shadeRepository.upsert(Shade{0x1234, "test_shade", "Test Shade", -1, -1, 0, 0, nullptr});
auto shade2 = shadeRepository.findById(0x1234);
auto shade3 = shadeRepository.findById(0xABCD);
TEST_ASSERT_EQUAL_HEX16(0x1234, shade2->ID);
TEST_ASSERT_EQUAL_HEX16(0x1234, shade1->ID);
TEST_ASSERT_NULL(shade3);
}
void test_updating_shade_friendly_name()
{
ShadeRepository shadeRepository = ShadeRepository();
shadeRepository.upsert(Shade{0xABCD, "test_shade", "Test Shade", -1, -1, 0, 0, nullptr});
auto shade1 = shadeRepository.findByKey("test_shade");
TEST_ASSERT_EQUAL_STRING("Test Shade", shade1->friendlyName.c_str());
shadeRepository.upsert(Shade{0xABCD, "test_shade", "Updated Test Shade", -1, -1, 0, 0, nullptr});
auto shade2 = shadeRepository.findByKey("test_shade");
TEST_ASSERT_EQUAL_STRING("Updated Test Shade", shade2->friendlyName.c_str());
TEST_ASSERT_EQUAL_STRING("Updated Test Shade", shade1->friendlyName.c_str());
}
void test_shade_added_callback()
{
int callbackInvokedCount = 0;
ShadeRepository shadeRepository = ShadeRepository();
shadeRepository.addShadeAddedCallback([&] (Shade& shade) {
callbackInvokedCount++;
TEST_ASSERT_EQUAL_STRING("Test Shade", shade.friendlyName.c_str());
TEST_ASSERT_EQUAL_HEX16(0xABCD, shade.ID);
TEST_ASSERT_EQUAL_STRING("test_shade", shade.key.c_str());
});
shadeRepository.upsert(Shade{0xABCD, "test_shade", "Test Shade", -1, -1, 0, 0, nullptr});
TEST_ASSERT_EQUAL_INT(1, callbackInvokedCount);
}
void test_shade_changed_callback()
{
int callbackInvokedCount = 0;
ShadeRepository shadeRepository = ShadeRepository();
shadeRepository.addShadeChangedCallback([&] (Shade& shade) {
callbackInvokedCount++;
TEST_ASSERT_EQUAL_STRING("Updated Test Shade", shade.friendlyName.c_str());
TEST_ASSERT_EQUAL_HEX16(0x1234, shade.ID);
TEST_ASSERT_EQUAL_STRING("test_shade", shade.key.c_str());
});
shadeRepository.upsert(Shade{0xABCD, "test_shade", "Test Shade", -1, -1, 0, 0, nullptr});
shadeRepository.upsert(Shade{0x1234, "test_shade", "Updated Test Shade", -1, -1, 0, 0, nullptr});
TEST_ASSERT_EQUAL_INT(1, callbackInvokedCount);
}
int runUnityTests(void)
{
UNITY_BEGIN();
RUN_TEST(test_shade_is_found_with_id);
RUN_TEST(test_shade_is_found_with_key);
RUN_TEST(test_adding_shade_twice_only_added_once);
RUN_TEST(test_updating_shade_id);
RUN_TEST(test_updating_shade_friendly_name);
RUN_TEST(test_shade_added_callback);
RUN_TEST(test_shade_changed_callback);
return UNITY_END();
}
/**
* For Arduino framework
*/
void setup()
{
// Wait ~2 seconds before the Unity test runner
// establishes connection with a board Serial interface
delay(2000);
runUnityTests();
}
void loop() {}
int main(void)
{
return runUnityTests();
}