Replace arduino-timer with PositionWatcher
This commit is contained in:
234
src/main.cpp
234
src/main.cpp
@@ -1,6 +1,5 @@
|
||||
#include <Arduino.h>
|
||||
#include <EspMqttClient.h>
|
||||
#include <arduino-timer.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <RFPowerView.h>
|
||||
#include <random>
|
||||
@@ -10,6 +9,9 @@
|
||||
#include "ShadeRepository.h"
|
||||
#include "HADiscovery.h"
|
||||
#include "secrets.h"
|
||||
#include "ShadeCommand.h"
|
||||
#include "PositionWatcher.h"
|
||||
#include <queue>
|
||||
|
||||
#define SER_BAUDRATE (115200)
|
||||
|
||||
@@ -49,13 +51,15 @@ Configurator configurator = Configurator();
|
||||
|
||||
ShadeRepository shadeRepository = ShadeRepository();
|
||||
|
||||
using Command = std::variant<OpenCommand, CloseCommand, StopCommand, SetPositionCommand, RefreshCommand>;
|
||||
std::queue<Command> commands;
|
||||
|
||||
std::map<uint16_t, PositionWatcher> positionWatchers;
|
||||
|
||||
HADiscovery haDiscovery(topic_prefix.c_str(), [] (const char* topic, const char* message) {
|
||||
client.publish(topic, message);
|
||||
});
|
||||
|
||||
#define MAX_FETCH_COUNT (20)
|
||||
auto timer = Timer<10, millis, uint16_t>();
|
||||
|
||||
void processPacket(const Packet*);
|
||||
void processCommandMessage(const String& topic, const String &payload);
|
||||
void processSetPositionMessage(const String& topic, const String &payload);
|
||||
@@ -67,13 +71,12 @@ bool sendSetPosition(uint16_t destination, float percentage);
|
||||
bool sendFetchPosition(uint16_t destination);
|
||||
bool sendPacket(Packet *packet);
|
||||
|
||||
bool checkPosition(uint16_t shadeID);
|
||||
void startFetchingPosition(uint16_t shadeID, int8_t targetPosition);
|
||||
|
||||
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);
|
||||
|
||||
void startPositionWatcher(uint16_t shadeID, uint8_t targetPosition);
|
||||
|
||||
void publishDiscoveryTopics();
|
||||
|
||||
void setup() {
|
||||
@@ -118,7 +121,97 @@ void setup() {
|
||||
void loop() {
|
||||
powerView.loop();
|
||||
client.loop();
|
||||
timer.tick();
|
||||
|
||||
if (client.isConnected()) {
|
||||
if (!commands.empty()) {
|
||||
auto command = commands.front();
|
||||
commands.pop();
|
||||
|
||||
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)) {
|
||||
auto shade = shadeRepository.findById(openCommand.shadeID);
|
||||
if (shade != nullptr) {
|
||||
shade->state = "opening";
|
||||
shade->modified = true;
|
||||
}
|
||||
startPositionWatcher(openCommand.shadeID, 100);
|
||||
}
|
||||
|
||||
} 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)) {
|
||||
auto shade = shadeRepository.findById(closeCommand.shadeID);
|
||||
if (shade != nullptr) {
|
||||
shade->state = "closing";
|
||||
shade->modified = true;
|
||||
}
|
||||
startPositionWatcher(closeCommand.shadeID, 0);
|
||||
}
|
||||
} 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);
|
||||
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)) {
|
||||
auto shade = shadeRepository.findById(setPositionCommand.shadeID);
|
||||
if (shade != nullptr) {
|
||||
if (setPositionCommand.percentage > shade->lastPosition / 100.0f) {
|
||||
shade->state = "opening";
|
||||
shade->modified = true;
|
||||
} else {
|
||||
shade->state = "closing";
|
||||
shade->modified = true;
|
||||
}
|
||||
}
|
||||
startPositionWatcher(setPositionCommand.shadeID, setPositionCommand.percentage * 100);
|
||||
}
|
||||
} else if (std::holds_alternative<RefreshCommand>(command)) {
|
||||
auto refreshCommand = std::get<RefreshCommand>(command);
|
||||
Serial.printf("Refresh command received for shade 0x%04x\n", refreshCommand.shadeID);
|
||||
startPositionWatcher(refreshCommand.shadeID, -1);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& [shadeID, watcher] : positionWatchers) {
|
||||
if (watcher.shouldFetch()) {
|
||||
if (sendFetchPosition(shadeID)) {
|
||||
watcher.fetchQueued();
|
||||
}
|
||||
} else if (!watcher.isWatching()) {
|
||||
positionWatchers.erase(shadeID);
|
||||
auto shade = shadeRepository.findById(shadeID);
|
||||
if (shade != nullptr) {
|
||||
if (shade->lastPosition == 0) {
|
||||
shade->state = "closed";
|
||||
shade->modified = true;
|
||||
} else {
|
||||
shade->state = "open";
|
||||
shade->modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto shade = shadeRepository.begin(); shade != shadeRepository.end(); shade++) {
|
||||
if (shade->modified) {
|
||||
if (shade->lastPosition != -1) {
|
||||
publishPosition(shade->key, shade->lastPosition);
|
||||
}
|
||||
if (shade->lastBattery != -1) {
|
||||
publishBattery(shade->key, shade->lastBattery);
|
||||
}
|
||||
publishState(shade->key, shade->state.c_str());
|
||||
shade->modified = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processPacket(const Packet *packet) {
|
||||
@@ -148,31 +241,33 @@ void processPacket(const Packet *packet) {
|
||||
}
|
||||
|
||||
if (packet->type == PacketType::FIELDS) {
|
||||
FieldsParameters parameters = std::get<FieldsParameters>(packet->parameters);
|
||||
for (size_t i = 0; i < parameters.fields.size(); i++) {
|
||||
Field field = parameters.fields[i];
|
||||
if (field.identifier == 0x50) {
|
||||
auto shade = shadeRepository.findById(source);
|
||||
if (shade != nullptr) {
|
||||
auto shade = shadeRepository.findById(source);
|
||||
if (shade != nullptr) {
|
||||
FieldsParameters parameters = std::get<FieldsParameters>(packet->parameters);
|
||||
for (size_t i = 0; i < parameters.fields.size(); i++) {
|
||||
Field field = parameters.fields[i];
|
||||
if (field.identifier == 0x50) {
|
||||
uint16_t value = std::get<uint16_t>(field.value);
|
||||
uint8_t position = (uint8_t)std::round(((float)value / 0xFFFF) * 100);
|
||||
|
||||
if (shade->lastPosition == position) {
|
||||
shade->samePositionCount++;
|
||||
} else {
|
||||
shade->samePositionCount = 1;
|
||||
}
|
||||
shade->lastPosition = position;
|
||||
shade->modified = true;
|
||||
|
||||
publishPosition(shade->key, position);
|
||||
}
|
||||
} else if (field.identifier == 0x42) {
|
||||
auto shade = shadeRepository.findById(source);
|
||||
if (shade != nullptr) {
|
||||
auto it = positionWatchers.find(shade->ID);
|
||||
if (it != positionWatchers.end()) {
|
||||
auto& watcher = it->second;
|
||||
watcher.fetchReceived(position);
|
||||
}
|
||||
|
||||
// TODO: set updated flag?
|
||||
} else if (field.identifier == 0x42) {
|
||||
uint8_t value = std::get<uint8_t>(field.value);
|
||||
uint8_t battery = uint8_t(((float)value / 200) * 100);
|
||||
|
||||
publishBattery(shade->key, battery);
|
||||
shade->lastBattery = battery;
|
||||
shade->modified = true;
|
||||
|
||||
// TODO: set updated flag?
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,92 +383,45 @@ bool sendPacket(Packet *packet) {
|
||||
}
|
||||
}
|
||||
|
||||
void startPositionWatcher(uint16_t shadeID, uint8_t targetValue) {
|
||||
auto& watcher = positionWatchers.try_emplace(shadeID).first->second;
|
||||
watcher.start(targetValue);
|
||||
}
|
||||
|
||||
void processCommandMessage(const String &topic, const String &payload) {
|
||||
int startIndex = topic.indexOf("/") + 1;
|
||||
int endIndex = topic.indexOf("/", startIndex);
|
||||
auto key = topic.substring(startIndex, endIndex).c_str();
|
||||
String key = topic.substring(startIndex, endIndex);
|
||||
|
||||
auto shade = shadeRepository.findByKey(key);
|
||||
Serial.printf("Received command '%s' for shade '%s'\n", payload.c_str(), key.c_str());
|
||||
|
||||
auto shade = shadeRepository.findByKey(key.c_str());
|
||||
if (shade != nullptr) {
|
||||
if (payload == "OPEN") {
|
||||
sendOpenPacket(shade->ID);
|
||||
|
||||
startFetchingPosition(shade->ID, 100);
|
||||
|
||||
publishState(shade->key, "opening");
|
||||
commands.push(OpenCommand {shade->ID});
|
||||
} else if (payload == "CLOSE") {
|
||||
sendClosePacket(shade->ID);
|
||||
|
||||
startFetchingPosition(shade->ID, 0);
|
||||
|
||||
publishState(shade->key, "closing");
|
||||
commands.push(CloseCommand {shade->ID});
|
||||
} else if (payload == "STOP") {
|
||||
sendStopPacket(shade->ID);
|
||||
|
||||
startFetchingPosition(shade->ID, -1);
|
||||
timer.in(100, sendFetchPosition, shade->ID);
|
||||
commands.push(StopCommand {shade->ID});
|
||||
} else if (payload == "REFRESH") {
|
||||
startFetchingPosition(shade->ID, -1);
|
||||
commands.push(RefreshCommand {shade->ID});
|
||||
}
|
||||
} else {
|
||||
Serial.println("Failed to find shade");
|
||||
}
|
||||
}
|
||||
|
||||
void processSetPositionMessage(const String& topic, const String &payload) {
|
||||
int startIndex = topic.indexOf("/") + 1;
|
||||
int endIndex = topic.indexOf("/", startIndex);
|
||||
auto key = topic.substring(startIndex, endIndex).c_str();
|
||||
String key = topic.substring(startIndex, endIndex);
|
||||
|
||||
auto shade = shadeRepository.findByKey(key);
|
||||
Serial.printf("Received set position command for shade '%s' (%s)\n", key.c_str(), payload.c_str());
|
||||
|
||||
auto shade = shadeRepository.findByKey(key.c_str());
|
||||
if (shade != nullptr) {
|
||||
float percentage = payload.toInt() / 100.0f;
|
||||
sendSetPosition(shade->ID, percentage);
|
||||
|
||||
if (payload.toInt() > shade->lastPosition) {
|
||||
publishState(shade->key, "opening");
|
||||
} else if (payload.toInt() < shade->lastPosition) {
|
||||
publishState(shade->key, "closing");
|
||||
}
|
||||
|
||||
startFetchingPosition(shade->ID, payload.toInt());
|
||||
}
|
||||
}
|
||||
|
||||
bool checkPosition(uint16_t shadeID) {
|
||||
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(shade->key, shade->lastPosition > 0 ? "open" : "closed");
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void startFetchingPosition(uint16_t shadeID, int8_t targetPosition) {
|
||||
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);
|
||||
commands.push(SetPositionCommand {shade->ID, percentage});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,4 +441,4 @@ void publishDiscoveryTopics() {
|
||||
for (auto shade = shadeRepository.begin(); shade != shadeRepository.end(); shade++) {
|
||||
haDiscovery.publish(*shade);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user