mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 22:02:17 +01:00
Sending commands, repeats and sensor frames, rolling codes and the remote flags move out of Somfy.cpp unchanged, with startsMovement(), which only sendCommandAfter uses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
170 lines
7.4 KiB
C++
170 lines
7.4 KiB
C++
#include <Preferences.h>
|
|
#include "esp_log.h"
|
|
#include "Somfy.h"
|
|
#include "RollingCode.h"
|
|
|
|
static const char *TAG = "Somfy";
|
|
|
|
extern SomfyShadeController somfy;
|
|
|
|
// Commands that can set a shade moving.
|
|
static bool startsMovement(somfy_commands cmd) {
|
|
switch(cmd) {
|
|
case somfy_commands::My:
|
|
case somfy_commands::Up:
|
|
case somfy_commands::Down:
|
|
case somfy_commands::StepUp:
|
|
case somfy_commands::StepDown:
|
|
case somfy_commands::Toggle:
|
|
case somfy_commands::Favorite:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
bool SomfyRemote::simMy() { return (this->flags & static_cast<uint8_t>(somfy_flags_t::SimMy)) > 0; }
|
|
void SomfyRemote::setSimMy(bool bSimMy) { bSimMy ? this->flags |= static_cast<uint8_t>(somfy_flags_t::SimMy) : this->flags &= ~(static_cast<uint8_t>(somfy_flags_t::SimMy)); }
|
|
bool SomfyRemote::hasSunSensor() { return (this->flags & static_cast<uint8_t>(somfy_flags_t::SunSensor)) > 0;}
|
|
bool SomfyRemote::hasLight() { return (this->flags & static_cast<uint8_t>(somfy_flags_t::Light)) > 0; }
|
|
void SomfyRemote::setSunSensor(bool bHasSensor ) { bHasSensor ? this->flags |= static_cast<uint8_t>(somfy_flags_t::SunSensor) : this->flags &= ~(static_cast<uint8_t>(somfy_flags_t::SunSensor)); }
|
|
void SomfyRemote::setLight(bool bHasLight ) { bHasLight ? this->flags |= static_cast<uint8_t>(somfy_flags_t::Light) : this->flags &= ~(static_cast<uint8_t>(somfy_flags_t::Light)); }
|
|
void SomfyRemote::setRemoteAddress(uint32_t address) { this->m_remoteAddress = address; this->m_rollingCodeSynced = false; snprintf(this->m_remotePrefId, sizeof(this->m_remotePrefId), "_%lu", (unsigned long)this->m_remoteAddress); }
|
|
uint32_t SomfyRemote::getRemoteAddress() { return this->m_remoteAddress; }
|
|
somfy_commands SomfyRemote::transformCommand(somfy_commands cmd) { return this->flipCommands ? flipCommand(cmd) : cmd; }
|
|
bool SomfyRemote::sendSensorCommand(int8_t isWindy, int8_t isSunny, uint8_t repeat) {
|
|
uint8_t flags = (this->flags >> 4) & 0x0F;
|
|
if(isWindy > 0) flags |= 0x01;
|
|
if(isSunny > 0) flags |= 0x02;
|
|
if(isWindy == 0) flags &= ~0x01;
|
|
if(isSunny == 0) flags &= ~0x02;
|
|
|
|
// Now ship this off as an 80 bit command. Built aside so lastFrame only
|
|
// changes when the frame is queued.
|
|
somfy_frame_t frame = this->lastFrame;
|
|
frame.remoteAddress = this->getRemoteAddress();
|
|
frame.repeats = repeat;
|
|
frame.bitLength = this->bitLength;
|
|
frame.rollingCode = (uint16_t)flags;
|
|
frame.encKey = 160; // Sensor commands are always encryption code 160.
|
|
frame.cmd = somfy_commands::Sensor;
|
|
frame.processed = false;
|
|
ESP_LOGD(TAG, "CMD:%s ADDR:%d RCODE:%d REPEAT:%d",
|
|
translateSomfyCommand(frame.cmd).c_str(),
|
|
frame.remoteAddress, frame.rollingCode, repeat);
|
|
if(!somfy.sendFrame(frame, repeat)) return false;
|
|
this->lastFrame = frame;
|
|
somfy.processFrame(this->lastFrame, true);
|
|
return true;
|
|
}
|
|
bool SomfyRemote::sendCommand(somfy_commands cmd) { return this->sendCommand(cmd, this->repeats); }
|
|
bool SomfyRemote::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize) { return this->sendCommandAfter(cmd, repeat, stepSize, 0).sent; }
|
|
send_result_t SomfyRemote::sendCommandAfter(somfy_commands cmd, uint8_t repeat, uint8_t stepSize, uint16_t delayMs, bool priority) {
|
|
// Build the frame aside and only take the rolling code once the frame is
|
|
// queued so a dropped command leaves the remote untouched.
|
|
somfy_frame_t frame = this->lastFrame;
|
|
frame.rollingCode = this->peekNextRollingCode();
|
|
frame.remoteAddress = this->getRemoteAddress();
|
|
frame.cmd = this->transformCommand(cmd);
|
|
frame.repeats = repeat;
|
|
frame.bitLength = this->bitLength;
|
|
frame.stepSize = stepSize;
|
|
frame.valid = true;
|
|
// Match the encKey to the rolling code. These keys range from 160 to 175.
|
|
frame.encKey = 0xA0 | static_cast<uint8_t>(frame.rollingCode & 0x000F);
|
|
frame.proto = this->proto;
|
|
if(frame.bitLength == 0) frame.bitLength = radioBitLength;
|
|
if(frame.rollingCode == 0) ESP_LOGE(TAG, "Setting rcode to 0");
|
|
// We have to set the processed to clear this if we are sending
|
|
// another command.
|
|
frame.processed = false;
|
|
send_result_t result;
|
|
if(this->proto == radio_proto::GP_Relay) {
|
|
ESP_LOGD(TAG, "CMD:%s ADDR:%d RCODE:%d SETTING GPIO",
|
|
translateSomfyCommand(frame.cmd).c_str(),
|
|
frame.remoteAddress, frame.rollingCode);
|
|
}
|
|
else if(this->proto == radio_proto::GP_Remote) {
|
|
ESP_LOGD(TAG, "CMD:%s ADDR:%d RCODE:%d TRIGGER GPIO",
|
|
translateSomfyCommand(frame.cmd).c_str(),
|
|
frame.remoteAddress, frame.rollingCode);
|
|
this->triggerGPIOs(frame);
|
|
}
|
|
else {
|
|
ESP_LOGD(TAG, "CMD:%s ADDR:%d RCODE:%d REPEAT:%d",
|
|
translateSomfyCommand(frame.cmd).c_str(),
|
|
frame.remoteAddress, frame.rollingCode, repeat);
|
|
// Only simulate the command if it is actually going on air.
|
|
result.ticket = somfy.sendFrame(frame, repeat, delayMs, priority);
|
|
if(result.ticket == 0) return result;
|
|
}
|
|
result.sent = true;
|
|
this->commitRollingCode(frame.rollingCode);
|
|
this->lastFrame = frame;
|
|
somfy.processFrame(this->lastFrame, true);
|
|
// Idle shades (and group members) this sets moving wait for the frame to go
|
|
// on air. Stops are sent ahead of everything else so they don't need to.
|
|
if(result.ticket != 0 && !priority && startsMovement(frame.cmd)) somfy.markTxPending(frame.remoteAddress, result.ticket);
|
|
return result;
|
|
}
|
|
bool SomfyRemote::isLastCommand(somfy_commands cmd) {
|
|
if(this->lastFrame.cmd != cmd || this->lastFrame.rollingCode != this->lastRollingCode) {
|
|
ESP_LOGD(TAG, "Not the last command %d: %d - %d", static_cast<uint8_t>(this->lastFrame.cmd), this->lastFrame.rollingCode, this->lastRollingCode);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
bool SomfyRemote::repeatFrame(uint8_t repeat) {
|
|
if(this->proto == radio_proto::GP_Relay)
|
|
return true;
|
|
else if(this->proto == radio_proto::GP_Remote) {
|
|
this->triggerGPIOs(this->lastFrame);
|
|
return true;
|
|
}
|
|
somfy_tx_job_t job;
|
|
encodeRepeatJob(job, this->lastFrame, repeat);
|
|
if(!somfy.transceiver.submitTx(job)) return false;
|
|
noteRepeatQueued(this->lastFrame, job);
|
|
//somfy.processFrame(this->lastFrame, true);
|
|
return true;
|
|
}
|
|
// The next rolling code is consumed by commitRollingCode once the frame using
|
|
// it has been queued. After that lastRollingCode holds what NVS does.
|
|
uint16_t SomfyRemote::peekNextRollingCode() {
|
|
return nextRollingCode(this->m_rollingCodeSynced, this->lastRollingCode, [this]() {
|
|
Preferences pref;
|
|
if(!pref.begin("ShadeCodes")) {
|
|
ESP_LOGE(TAG, "peekNextRollingCode() pref.begin(ShadeCodes) FAILED");
|
|
}
|
|
uint16_t code = pref.getUShort(this->m_remotePrefId, 0);
|
|
pref.end();
|
|
return code;
|
|
});
|
|
}
|
|
void SomfyRemote::commitRollingCode(uint16_t code) {
|
|
Preferences pref;
|
|
if(!pref.begin("ShadeCodes")) {
|
|
ESP_LOGE(TAG, "commitRollingCode() pref.begin(ShadeCodes) FAILED");
|
|
}
|
|
pref.putUShort(this->m_remotePrefId, code);
|
|
pref.end();
|
|
this->p_lastRollingCode(code);
|
|
this->m_rollingCodeSynced = true;
|
|
}
|
|
uint16_t SomfyRemote::p_lastRollingCode(uint16_t code) {
|
|
uint16_t old = this->lastRollingCode;
|
|
this->lastRollingCode = code;
|
|
return old;
|
|
}
|
|
uint16_t SomfyRemote::setRollingCode(uint16_t code) {
|
|
Preferences pref;
|
|
if(this->lastRollingCode != code) {
|
|
pref.begin("ShadeCodes");
|
|
pref.putUShort(this->m_remotePrefId, code);
|
|
pref.end();
|
|
this->lastRollingCode = code;
|
|
this->m_rollingCodeSynced = true;
|
|
ESP_LOGD(TAG, "Setting Last Rolling code %d", this->lastRollingCode);
|
|
}
|
|
return code;
|
|
}
|
|
SomfyLinkedRemote::SomfyLinkedRemote() {}
|