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

View File

@@ -3,6 +3,8 @@
#include <arduino-timer.h>
#include <ArduinoJson.h>
#include <RFPowerView.h>
#include "Shade.h"
#include "ShadeRepository.h"
#include "secrets.h"
#define SER_BAUDRATE (115200)
@@ -26,6 +28,8 @@ EspMQTTClient client(
1883
);
ShadeRepository shadeRepository = ShadeRepository();
#define MAX_FETCH_COUNT (20)
auto timer = Timer<10, millis, uint16_t>();
@@ -49,40 +53,11 @@ void publishBattery(const String& shadeName, const uint8_t battery);
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() {
Serial.begin(SER_BAUDRATE);
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);
if (!powerView.begin()) {
Serial.println("Failed to start RFPowerView");
@@ -94,7 +69,37 @@ void setup() {
client.enableDebuggingMessages();
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);
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");
}
@@ -135,29 +140,27 @@ void processPacket(const Packet *packet) {
for (size_t i = 0; i < parameters.fields.size(); i++) {
Field field = parameters.fields[i];
if (field.identifier == 0x50) {
for (size_t i = 0; i < shades.size(); i++) {
if (source == shades[i].ID) {
uint16_t value = std::get<uint16_t>(field.value);
uint8_t position = (uint8_t)std::round(((float)value / 0xFFFF) * 100);
auto shade = shadeRepository.findById(source);
if (shade != nullptr) {
uint16_t value = std::get<uint16_t>(field.value);
uint8_t position = (uint8_t)std::round(((float)value / 0xFFFF) * 100);
if (shades[i].lastPosition == position) {
shades[i].samePositionCount++;
} else {
shades[i].samePositionCount = 1;
}
shades[i].lastPosition = position;
publishPosition(shades[i].name, position);
if (shade->lastPosition == position) {
shade->samePositionCount++;
} else {
shade->samePositionCount = 1;
}
shade->lastPosition = position;
publishPosition(shade->key.c_str(), position);
}
} else if (field.identifier == 0x42) {
for (size_t i = 0; i < shades.size(); i++) {
if (source == shades[i].ID) {
uint8_t value = std::get<uint8_t>(field.value);
uint8_t battery = uint8_t(((float)value / 200) * 100);
publishBattery(shades[i].name, battery);
}
auto shade = shadeRepository.findById(source);
if (shade != nullptr) {
uint8_t value = std::get<uint8_t>(field.value);
uint8_t battery = uint8_t(((float)value / 200) * 100);
publishBattery(shade->key.c_str(), battery);
}
}
}
@@ -167,9 +170,9 @@ void processPacket(const Packet *packet) {
void onConnectionEstablished() {
Serial.println("Connection established");
for (size_t i = 0; i < shades.size(); i++) {
client.subscribe("hotdog/" + shades[i].name + "/command", processCommandMessage);
client.subscribe("hotdog/" + shades[i].name + "/set_position", processSetPositionMessage);
for (auto shade = shadeRepository.begin(); shade != shadeRepository.end(); shade++) {
client.subscribe(("hotdog/" + shade->key + "/command").c_str(), processCommandMessage);
client.subscribe(("hotdog/" + shade->key + "/set_position").c_str(), processSetPositionMessage);
}
client.publish("hotdog/availability", "online", true);
@@ -276,30 +279,29 @@ bool sendPacket(Packet *packet) {
void processCommandMessage(const String &topic, const String &payload) {
int startIndex = topic.indexOf("/") + 1;
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++) {
if (shades[i].name == shadeName) {
if (payload == "OPEN") {
sendOpenPacket(shades[i].ID);
auto shade = shadeRepository.findByKey(key);
if (shade != nullptr) {
if (payload == "OPEN") {
sendOpenPacket(shade->ID);
startFetchingPosition(shades[i].ID, 100);
startFetchingPosition(shade->ID, 100);
publishState(shades[i].name, "opening");
} else if (payload == "CLOSE") {
sendClosePacket(shades[i].ID);
publishState(shade->key.c_str(), "opening");
} else if (payload == "CLOSE") {
sendClosePacket(shade->ID);
startFetchingPosition(shades[i].ID, 0);
startFetchingPosition(shade->ID, 0);
publishState(shades[i].name, "closing");
} else if (payload == "STOP") {
sendStopPacket(shades[i].ID);
publishState(shade->key.c_str(), "closing");
} else if (payload == "STOP") {
sendStopPacket(shade->ID);
startFetchingPosition(shades[i].ID, -1);
timer.in(100, sendFetchPosition, shades[i].ID);
} else if (payload == "REFRESH") {
startFetchingPosition(shades[i].ID, -1);
}
startFetchingPosition(shade->ID, -1);
timer.in(100, sendFetchPosition, shade->ID);
} else if (payload == "REFRESH") {
startFetchingPosition(shade->ID, -1);
}
}
}
@@ -307,62 +309,59 @@ void processCommandMessage(const String &topic, const String &payload) {
void processSetPositionMessage(const String& topic, const String &payload) {
int startIndex = topic.indexOf("/") + 1;
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++) {
if (shades[i].name == shadeName) {
float percentage = payload.toInt() / 100.0f;
sendSetPosition(shades[i].ID, percentage);
auto shade = shadeRepository.findByKey(key);
if (shade != nullptr) {
float percentage = payload.toInt() / 100.0f;
sendSetPosition(shade->ID, percentage);
if (payload.toInt() > shades[i].lastPosition) {
publishState(shades[i].name, "opening");
} else if (payload.toInt() < shades[i].lastPosition) {
publishState(shades[i].name, "closing");
}
startFetchingPosition(shades[i].ID, payload.toInt());
if (payload.toInt() > shade->lastPosition) {
publishState(shade->key.c_str(), "opening");
} else if (payload.toInt() < shade->lastPosition) {
publishState(shade->key.c_str(), "closing");
}
startFetchingPosition(shade->ID, payload.toInt());
}
}
bool checkPosition(uint16_t shadeID) {
for (size_t i = 0; i < shades.size(); i++) {
if (shades[i].ID == shadeID) {
// Keep fetching position if:
// - the last reported position doesn't match the target position
// - a position hasn't been reported yet
// - there is no target position
if (shades[i].lastTargetPosition != shades[i].lastPosition || shades[i].lastPosition == -1 || shades[i].lastTargetPosition == -1) {
// Keep fetching position if the count limits have not been reached
if (shades[i].positionFetchCount < MAX_FETCH_COUNT && shades[i].samePositionCount < 2) {
shades[i].positionFetchCount++;
sendFetchPosition(shadeID);
return true;
}
auto shade = shadeRepository.findById(shadeID);
if (shade != nullptr) {
// Keep fetching position if:
// - the last reported position doesn't match the target position
// - a position hasn't been reported yet
// - there is no target position
if (shade->lastTargetPosition != shade->lastPosition || shade->lastPosition == -1 || shade->lastTargetPosition == -1) {
// Keep fetching position if the count limits have not been reached
if (shade->positionFetchCount < MAX_FETCH_COUNT && shade->samePositionCount < 2) {
shade->positionFetchCount++;
sendFetchPosition(shadeID);
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;
}
void startFetchingPosition(uint16_t shadeID, int8_t targetPosition) {
for (size_t i = 0; i < shades.size(); i++) {
if (shades[i].ID == shadeID) {
// Cancel any existing timer
if (shades[i].timer != nullptr) {
timer.cancel(shades[i].timer);
shades[i].timer = nullptr;
}
shades[i].lastTargetPosition = targetPosition;
shades[i].positionFetchCount = 0;
shades[i].samePositionCount = 1;
shades[i].timer = timer.every(2000, checkPosition, shades[i].ID);
auto shade = shadeRepository.findById(shadeID);
if (shade != nullptr) {
// Cancel any existing timer
if (shade->timer != nullptr) {
timer.cancel(shade->timer);
shade->timer = nullptr;
}
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["unique_id"] = entityID;
doc["availability_topic"] = "hotdog/availability";
doc["state_topic"] = "hotdog/" + shade.name + "/state";
doc["command_topic"] = "hotdog/" + shade.name + "/command";
doc["position_topic"] = "hotdog/" + shade.name + "/position";
doc["set_position_topic"] = "hotdog/" + shade.name + "/set_position";
doc["state_topic"] = "hotdog/" + shade.key + "/state";
doc["command_topic"] = "hotdog/" + shade.key + "/command";
doc["position_topic"] = "hotdog/" + shade.key + "/position";
doc["set_position_topic"] = "hotdog/" + shade.key + "/set_position";
doc["position_open"] = 100;
doc["position_closed"] = 0;
doc["optimistic"] = false;
@@ -425,7 +424,7 @@ void publishBatteryDiscoveryTopic(const Shade& shade) {
doc["unique_id"] = entityID;
doc["availability_topic"] = "hotdog/availability";
doc["device_class"] = "battery";
doc["state_topic"] = "hotdog/" + shade.name + "/battery";
doc["state_topic"] = "hotdog/" + shade.key + "/battery";
addDeviceObject(doc, shade);
@@ -443,7 +442,7 @@ void publishRefreshButtonDiscoveryTopic(const Shade& shade) {
doc["name"] = "Refresh";
doc["unique_id"] = entityID;
doc["availability_topic"] = "hotdog/availability";
doc["command_topic"] = "hotdog/" + shade.name + "/command";
doc["command_topic"] = "hotdog/" + shade.key + "/command";
doc["payload_press"] = "REFRESH";
addDeviceObject(doc, shade);
@@ -454,12 +453,9 @@ void publishRefreshButtonDiscoveryTopic(const Shade& shade) {
}
void publishDiscoveryTopics() {
for (size_t i = 0; i < shades.size(); i++) {
Shade shade = shades[i];
String objectID = String(shade.ID, HEX);
publishCoverDiscoveryTopic(shade);
publishBatteryDiscoveryTopic(shade);
publishRefreshButtonDiscoveryTopic(shade);
for (auto shade = shadeRepository.begin(); shade != shadeRepository.end(); shade++) {
publishCoverDiscoveryTopic(*shade);
publishBatteryDiscoveryTopic(*shade);
publishRefreshButtonDiscoveryTopic(*shade);
}
}