fix: propagate radio TX result to callers instead of pre-checking

- Send functions return whether the radio took the command.
- Shade, group and tilt state only change when the command was queued.
- Rolling code is committed only after the frame is queued.
- repeatFrame advances the repeat counter only on success.
- Remove canTransmit/canQueueTx; queueTx is the single authority.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-11 12:14:29 +02:00
parent a426efe53f
commit ad15ecacd4
2 changed files with 129 additions and 125 deletions

View file

@ -2894,8 +2894,8 @@ void SomfyShade::setMyPosition(int8_t pos, int8_t tilt) {
} }
} }
} }
void SomfyShade::moveToMyPosition() { bool SomfyShade::moveToMyPosition() {
if(!this->isIdle()) return; if(!this->isIdle()) return true;
ESP_LOGI(TAG, "Moving to My Position"); ESP_LOGI(TAG, "Moving to My Position");
if(this->tiltType == tilt_types::tiltonly) { if(this->tiltType == tilt_types::tiltonly) {
this->p_currentPos(100.0f); this->p_currentPos(100.0f);
@ -2903,27 +2903,25 @@ void SomfyShade::moveToMyPosition() {
} }
if(this->currentPos == this->myPos) { if(this->currentPos == this->myPos) {
if(this->tiltType != tilt_types::none) { if(this->tiltType != tilt_types::none) {
if(this->currentTiltPos == this->myTiltPos) return; // Nothing to see here since we are already here. if(this->currentTiltPos == this->myTiltPos) return true; // Nothing to see here since we are already here.
} }
else else
return; return true;
} }
if(this->myPos == -1 && (this->tiltType == tilt_types::none || this->myTiltPos == -1)) return; if(this->myPos == -1 && (this->tiltType == tilt_types::none || this->myTiltPos == -1)) return true;
if(this->tiltType != tilt_types::tiltonly && this->myPos >= 0.0f && this->myPos <= 100.0f) this->p_target(this->myPos);
if(this->myTiltPos >= 0.0f && this->myTiltPos <= 100.0f) this->p_tiltTarget(this->myTiltPos);
this->settingPos = false; this->settingPos = false;
if(this->simMy()) { if(this->simMy()) {
ESP_LOGI(TAG, "Moving to simulated favorite"); ESP_LOGI(TAG, "Moving to simulated favorite");
this->moveToTarget(this->myPos, this->myTiltPos); if(!this->moveToTarget(this->myPos, this->myTiltPos)) return false;
} }
else else if(!SomfyRemote::sendCommand(somfy_commands::My, this->repeats)) return false;
SomfyRemote::sendCommand(somfy_commands::My, this->repeats); if(this->tiltType != tilt_types::tiltonly && this->myPos >= 0.0f && this->myPos <= 100.0f) this->p_target(this->myPos);
if(this->myTiltPos >= 0.0f && this->myTiltPos <= 100.0f) this->p_tiltTarget(this->myTiltPos);
return true;
} }
void SomfyShade::sendCommand(somfy_commands cmd) { this->sendCommand(cmd, this->repeats); } bool SomfyShade::sendCommand(somfy_commands cmd) { return this->sendCommand(cmd, this->repeats); }
void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize) { bool SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize) {
ESP_LOGD(TAG, "Send command start"); ESP_LOGD(TAG, "Send command start");
// Don't move the simulated state for a command the radio can't take.
if(!this->canTransmit()) return;
// This sendCommand function will always be called externally. sendCommand at the remote level // This sendCommand function will always be called externally. sendCommand at the remote level
// is expected to be called internally when the motor needs commanded. // is expected to be called internally when the motor needs commanded.
if(this->bitLength == 0) this->bitLength = somfy.transceiver.config.type; if(this->bitLength == 0) this->bitLength = somfy.transceiver.config.type;
@ -2932,21 +2930,22 @@ void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSiz
if((cmd == somfy_commands::Up && this->direction == -1) || if((cmd == somfy_commands::Up && this->direction == -1) ||
(cmd == somfy_commands::Down && this->direction == 1)) { (cmd == somfy_commands::Down && this->direction == 1)) {
ESP_LOGD(TAG, "Same command as dir"); ESP_LOGD(TAG, "Same command as dir");
SomfyRemote::sendCommand(cmd, repeat); if(!SomfyRemote::sendCommand(cmd, repeat)) return false;
this->p_target(this->currentPos); this->p_target(this->currentPos);
this->p_tiltTarget(this->currentTiltPos); this->p_tiltTarget(this->currentTiltPos);
this->setMovement(0); this->setMovement(0);
return; return true;
} }
// The simulated state is only moved for commands the radio took.
if(cmd == somfy_commands::Up) { if(cmd == somfy_commands::Up) {
if(this->tiltType == tilt_types::euromode) { if(this->tiltType == tilt_types::euromode) {
// In euromode we need to long press for 2 seconds on the // In euromode we need to long press for 2 seconds on the
// up command. // up command.
SomfyRemote::sendCommand(cmd, TILT_REPEATS); if(!SomfyRemote::sendCommand(cmd, TILT_REPEATS)) return false;
this->p_target(0.0f); this->p_target(0.0f);
} }
else { else {
SomfyRemote::sendCommand(cmd, repeat); if(!SomfyRemote::sendCommand(cmd, repeat)) return false;
if(this->tiltType == tilt_types::tiltonly) { if(this->tiltType == tilt_types::tiltonly) {
this->p_target(100.0f); this->p_target(100.0f);
this->p_tiltTarget(0.0f); this->p_tiltTarget(0.0f);
@ -2960,11 +2959,11 @@ void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSiz
if(this->tiltType == tilt_types::euromode) { if(this->tiltType == tilt_types::euromode) {
// In euromode we need to long press for 2 seconds on the // In euromode we need to long press for 2 seconds on the
// down command. // down command.
SomfyRemote::sendCommand(cmd, TILT_REPEATS); if(!SomfyRemote::sendCommand(cmd, TILT_REPEATS)) return false;
this->p_target(100.0f); this->p_target(100.0f);
} }
else { else {
SomfyRemote::sendCommand(cmd, repeat); if(!SomfyRemote::sendCommand(cmd, repeat)) return false;
if(this->tiltType == tilt_types::tiltonly) { if(this->tiltType == tilt_types::tiltonly) {
this->p_target(100.0f); this->p_target(100.0f);
this->p_tiltTarget(100.0f); this->p_tiltTarget(100.0f);
@ -2975,43 +2974,45 @@ void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSiz
} }
} }
else if(cmd == somfy_commands::My) { else if(cmd == somfy_commands::My) {
if(this->isToggle() || this->shadeType == shade_types::drycontact) if(this->isToggle() || this->shadeType == shade_types::drycontact) {
SomfyRemote::sendCommand(cmd, repeat); if(!SomfyRemote::sendCommand(cmd, repeat)) return false;
else if(this->shadeType == shade_types::drycontact2){ }
else if(this->shadeType == shade_types::drycontact2){
ESP_LOGD(TAG, "Send command start 1"); ESP_LOGD(TAG, "Send command start 1");
return; return true;
} }
else if(this->isIdle()) { else if(this->isIdle()) {
this->moveToMyPosition(); bool sent = this->moveToMyPosition();
ESP_LOGD(TAG, "Send command end 2"); ESP_LOGD(TAG, "Send command end 2");
return; return sent;
} }
else { else {
SomfyRemote::sendCommand(cmd, repeat); if(!SomfyRemote::sendCommand(cmd, repeat)) return false;
if(this->tiltType != tilt_types::tiltonly) this->p_target(this->currentPos); if(this->tiltType != tilt_types::tiltonly) this->p_target(this->currentPos);
this->p_tiltTarget(this->currentTiltPos); this->p_tiltTarget(this->currentTiltPos);
} }
} }
else if(cmd == somfy_commands::Toggle) { else if(cmd == somfy_commands::Toggle) {
if(this->bitLength != 80) SomfyRemote::sendCommand(somfy_commands::My, repeat, stepSize); if(this->bitLength != 80) { if(!SomfyRemote::sendCommand(somfy_commands::My, repeat, stepSize)) return false; }
else SomfyRemote::sendCommand(somfy_commands::Toggle, repeat); else if(!SomfyRemote::sendCommand(somfy_commands::Toggle, repeat)) return false;
} }
else if(this->isToggle() && cmd == somfy_commands::Prog) { else if(this->isToggle() && cmd == somfy_commands::Prog) {
SomfyRemote::sendCommand(somfy_commands::Toggle, repeat, stepSize); if(!SomfyRemote::sendCommand(somfy_commands::Toggle, repeat, stepSize)) return false;
} }
else { else {
SomfyRemote::sendCommand(cmd, repeat, stepSize); if(!SomfyRemote::sendCommand(cmd, repeat, stepSize)) return false;
} }
ESP_LOGD(TAG, "Send command end"); ESP_LOGD(TAG, "Send command end");
return true;
} }
void SomfyGroup::sendCommand(somfy_commands cmd) { this->sendCommand(cmd, this->repeats); } bool SomfyGroup::sendCommand(somfy_commands cmd) { return this->sendCommand(cmd, this->repeats); }
void SomfyGroup::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize) { bool SomfyGroup::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize) {
ESP_LOGI(TAG, "[Group %u] sendCommand cmd=%s repeat=%u", this->getGroupId(), translateSomfyCommand(cmd).c_str(), repeat); ESP_LOGI(TAG, "[Group %u] sendCommand cmd=%s repeat=%u", this->getGroupId(), translateSomfyCommand(cmd).c_str(), repeat);
if(!this->canTransmit()) return;
// This sendCommand function will always be called externally. sendCommand at the remote level // This sendCommand function will always be called externally. sendCommand at the remote level
// is expected to be called internally when the motor needs commanded. // is expected to be called internally when the motor needs commanded.
if(this->bitLength == 0) this->bitLength = somfy.transceiver.config.type; if(this->bitLength == 0) this->bitLength = somfy.transceiver.config.type;
SomfyRemote::sendCommand(cmd, repeat, stepSize); // Leave the group and its shades alone if the radio didn't take the command.
if(!SomfyRemote::sendCommand(cmd, repeat, stepSize)) return false;
switch(cmd) { switch(cmd) {
case somfy_commands::My: case somfy_commands::My:
@ -3039,25 +3040,21 @@ void SomfyGroup::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSiz
} }
this->updateFlags(); this->updateFlags();
this->emitState(); this->emitState();
return true;
} }
void SomfyShade::sendTiltCommand(somfy_commands cmd) { void SomfyShade::sendTiltCommand(somfy_commands cmd) {
if(!this->canTransmit()) return; const uint8_t repeat = this->tiltType == tilt_types::tiltmotor ? TILT_REPEATS : this->repeats;
if(cmd == somfy_commands::Up) { if(cmd == somfy_commands::Up) {
SomfyRemote::sendCommand(cmd, this->tiltType == tilt_types::tiltmotor ? TILT_REPEATS : this->repeats); if(SomfyRemote::sendCommand(cmd, repeat)) this->p_tiltTarget(0.0f);
this->p_tiltTarget(0.0f);
} }
else if(cmd == somfy_commands::Down) { else if(cmd == somfy_commands::Down) {
SomfyRemote::sendCommand(cmd, this->tiltType == tilt_types::tiltmotor ? TILT_REPEATS : this->repeats); if(SomfyRemote::sendCommand(cmd, repeat)) this->p_tiltTarget(100.0f);
this->p_tiltTarget(100.0f);
} }
else if(cmd == somfy_commands::My) { else if(cmd == somfy_commands::My) {
SomfyRemote::sendCommand(cmd, this->tiltType == tilt_types::tiltmotor ? TILT_REPEATS : this->repeats); if(SomfyRemote::sendCommand(cmd, repeat)) this->p_tiltTarget(this->currentTiltPos);
this->p_tiltTarget(this->currentTiltPos);
} }
} }
void SomfyShade::moveToTiltTarget(float target) { void SomfyShade::moveToTiltTarget(float target) {
if(!this->canTransmit()) return;
somfy_commands cmd = somfy_commands::My; somfy_commands cmd = somfy_commands::My;
if(target < this->currentTiltPos) if(target < this->currentTiltPos)
cmd = somfy_commands::Up; cmd = somfy_commands::Up;
@ -3069,7 +3066,7 @@ void SomfyShade::moveToTiltTarget(float target) {
if(cmd != somfy_commands::My) { if(cmd != somfy_commands::My) {
ESP_LOGI(TAG, "Moving Tilt to %.2f%% from %.2f%% using %s", ESP_LOGI(TAG, "Moving Tilt to %.2f%% from %.2f%% using %s",
target, this->currentTiltPos, translateSomfyCommand(cmd).c_str()); target, this->currentTiltPos, translateSomfyCommand(cmd).c_str());
SomfyRemote::sendCommand(cmd, this->tiltType == tilt_types::tiltmotor ? TILT_REPEATS : this->repeats); if(!SomfyRemote::sendCommand(cmd, this->tiltType == tilt_types::tiltmotor ? TILT_REPEATS : this->repeats)) return;
} }
// If the blind is currently moving then the command to stop it // If the blind is currently moving then the command to stop it
// will occur on its own when the tilt target is set. // will occur on its own when the tilt target is set.
@ -3078,7 +3075,7 @@ void SomfyShade::moveToTiltTarget(float target) {
} }
if(cmd != somfy_commands::My) this->settingTiltPos = true; if(cmd != somfy_commands::My) this->settingTiltPos = true;
} }
void SomfyShade::moveToTarget(float pos, float tilt) { bool SomfyShade::moveToTarget(float pos, float tilt) {
ESP_LOGI(TAG, "[Shade %u] moveToTarget(pos=%.2f, tilt=%.2f) settingPos=%d direction=%d currentTarget=%.2f currentPos=%.2f", ESP_LOGI(TAG, "[Shade %u] moveToTarget(pos=%.2f, tilt=%.2f) settingPos=%d direction=%d currentTarget=%.2f currentPos=%.2f",
this->getShadeId(), pos, tilt, this->settingPos, this->direction, this->target, this->currentPos); this->getShadeId(), pos, tilt, this->settingPos, this->direction, this->target, this->currentPos);
somfy_commands cmd = somfy_commands::My; somfy_commands cmd = somfy_commands::My;
@ -3087,10 +3084,10 @@ void SomfyShade::moveToTarget(float pos, float tilt) {
this->p_target(pos); this->p_target(pos);
this->p_currentPos(pos); this->p_currentPos(pos);
this->emitState(); this->emitState();
return; return true;
} }
if(!this->canTransmit()) return;
if(this->tiltType == tilt_types::tiltonly) { if(this->tiltType == tilt_types::tiltonly) {
// A tilt only shade is always at 100%.
this->p_target(100.0f); this->p_target(100.0f);
this->p_myPos(-1.0f); this->p_myPos(-1.0f);
this->p_currentPos(100.0f); this->p_currentPos(100.0f);
@ -3116,7 +3113,7 @@ void SomfyShade::moveToTarget(float pos, float tilt) {
ESP_LOGI(TAG, "Moving to %.2f%% from %.2f%% using %s", ESP_LOGI(TAG, "Moving to %.2f%% from %.2f%% using %s",
pos, this->currentPos, translateSomfyCommand(cmd).c_str()); pos, this->currentPos, translateSomfyCommand(cmd).c_str());
} }
SomfyRemote::sendCommand(cmd, this->tiltType == tilt_types::euromode ? TILT_REPEATS : this->repeats); if(!SomfyRemote::sendCommand(cmd, this->tiltType == tilt_types::euromode ? TILT_REPEATS : this->repeats)) return false;
this->settingPos = true; this->settingPos = true;
this->p_target(pos); this->p_target(pos);
if(tilt >= 0) { if(tilt >= 0) {
@ -3124,6 +3121,7 @@ void SomfyShade::moveToTarget(float pos, float tilt) {
this->settingTiltPos = true; this->settingTiltPos = true;
} }
} }
return true;
} }
bool SomfyShade::save() { bool SomfyShade::save() {
#ifdef USE_NVS #ifdef USE_NVS
@ -3863,74 +3861,74 @@ somfy_commands SomfyRemote::transformCommand(somfy_commands cmd) {
} }
return cmd; return cmd;
} }
void SomfyRemote::sendSensorCommand(int8_t isWindy, int8_t isSunny, uint8_t repeat) { bool SomfyRemote::sendSensorCommand(int8_t isWindy, int8_t isSunny, uint8_t repeat) {
if(!this->canTransmit()) return;
uint8_t flags = (this->flags >> 4) & 0x0F; uint8_t flags = (this->flags >> 4) & 0x0F;
if(isWindy > 0) flags |= 0x01; if(isWindy > 0) flags |= 0x01;
if(isSunny > 0) flags |= 0x02; if(isSunny > 0) flags |= 0x02;
if(isWindy == 0) flags &= ~0x01; if(isWindy == 0) flags &= ~0x01;
if(isSunny == 0) flags &= ~0x02; if(isSunny == 0) flags &= ~0x02;
// Now ship this off as an 80 bit command. // Now ship this off as an 80 bit command. Built aside so lastFrame only
this->lastFrame.remoteAddress = this->getRemoteAddress(); // changes when the frame is queued.
this->lastFrame.repeats = repeat; somfy_frame_t frame = this->lastFrame;
this->lastFrame.bitLength = this->bitLength; frame.remoteAddress = this->getRemoteAddress();
this->lastFrame.rollingCode = (uint16_t)flags; frame.repeats = repeat;
this->lastFrame.encKey = 160; // Sensor commands are always encryption code 160. frame.bitLength = this->bitLength;
this->lastFrame.cmd = somfy_commands::Sensor; frame.rollingCode = (uint16_t)flags;
this->lastFrame.processed = false; 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", ESP_LOGD(TAG, "CMD:%s ADDR:%d RCODE:%d REPEAT:%d",
translateSomfyCommand(this->lastFrame.cmd).c_str(), translateSomfyCommand(frame.cmd).c_str(),
this->lastFrame.remoteAddress, this->lastFrame.rollingCode, repeat); frame.remoteAddress, frame.rollingCode, repeat);
if(somfy.sendFrame(this->lastFrame, repeat)) somfy.processFrame(this->lastFrame, true); if(!somfy.sendFrame(frame, repeat)) return false;
this->lastFrame = frame;
somfy.processFrame(this->lastFrame, true);
return true;
} }
bool SomfyRemote::canTransmit() { bool SomfyRemote::sendCommand(somfy_commands cmd) { return this->sendCommand(cmd, this->repeats); }
// GPIO remotes don't use the radio. bool SomfyRemote::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize) { return this->sendCommandAfter(cmd, repeat, stepSize, 0); }
if(this->proto == radio_proto::GP_Relay || this->proto == radio_proto::GP_Remote) return true; bool SomfyRemote::sendCommandAfter(somfy_commands cmd, uint8_t repeat, uint8_t stepSize, uint16_t delayMs) {
if(somfy.transceiver.canQueueTx()) return true; // Build the frame aside and only take the rolling code once the frame is
ESP_LOGE(TAG, "Radio TX unavailable, dropping command for %d", this->getRemoteAddress()); // queued so a dropped command leaves the remote untouched.
return false; somfy_frame_t frame = this->lastFrame;
} frame.rollingCode = this->peekNextRollingCode();
void SomfyRemote::sendCommand(somfy_commands cmd) { this->sendCommand(cmd, this->repeats); } frame.remoteAddress = this->getRemoteAddress();
void SomfyRemote::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize) { this->sendCommandAfter(cmd, repeat, stepSize, 0); } frame.cmd = this->transformCommand(cmd);
void SomfyRemote::sendCommandAfter(somfy_commands cmd, uint8_t repeat, uint8_t stepSize, uint16_t delayMs) { frame.repeats = repeat;
// Check before taking a rolling code so a dropped command doesn't consume one. frame.bitLength = this->bitLength;
if(!this->canTransmit()) return; frame.stepSize = stepSize;
this->lastFrame.rollingCode = this->getNextRollingCode(); frame.valid = true;
this->lastFrame.remoteAddress = this->getRemoteAddress();
this->lastFrame.cmd = this->transformCommand(cmd);
this->lastFrame.repeats = repeat;
this->lastFrame.bitLength = this->bitLength;
this->lastFrame.stepSize = stepSize;
this->lastFrame.valid = true;
// Match the encKey to the rolling code. These keys range from 160 to 175. // Match the encKey to the rolling code. These keys range from 160 to 175.
this->lastFrame.encKey = 0xA0 | static_cast<uint8_t>(this->lastFrame.rollingCode & 0x000F); frame.encKey = 0xA0 | static_cast<uint8_t>(frame.rollingCode & 0x000F);
this->lastFrame.proto = this->proto; frame.proto = this->proto;
if(this->lastFrame.bitLength == 0) this->lastFrame.bitLength = bit_length; if(frame.bitLength == 0) frame.bitLength = bit_length;
if(this->lastFrame.rollingCode == 0) ESP_LOGE(TAG, "Setting rcode to 0"); if(frame.rollingCode == 0) ESP_LOGE(TAG, "Setting rcode to 0");
this->p_lastRollingCode(this->lastFrame.rollingCode);
// We have to set the processed to clear this if we are sending // We have to set the processed to clear this if we are sending
// another command. // another command.
this->lastFrame.processed = false; frame.processed = false;
if(this->proto == radio_proto::GP_Relay) { if(this->proto == radio_proto::GP_Relay) {
ESP_LOGD(TAG, "CMD:%s ADDR:%d RCODE:%d SETTING GPIO", ESP_LOGD(TAG, "CMD:%s ADDR:%d RCODE:%d SETTING GPIO",
translateSomfyCommand(this->lastFrame.cmd).c_str(), translateSomfyCommand(frame.cmd).c_str(),
this->lastFrame.remoteAddress, this->lastFrame.rollingCode); frame.remoteAddress, frame.rollingCode);
} }
else if(this->proto == radio_proto::GP_Remote) { else if(this->proto == radio_proto::GP_Remote) {
ESP_LOGD(TAG, "CMD:%s ADDR:%d RCODE:%d TRIGGER GPIO", ESP_LOGD(TAG, "CMD:%s ADDR:%d RCODE:%d TRIGGER GPIO",
translateSomfyCommand(this->lastFrame.cmd).c_str(), translateSomfyCommand(frame.cmd).c_str(),
this->lastFrame.remoteAddress, this->lastFrame.rollingCode); frame.remoteAddress, frame.rollingCode);
this->triggerGPIOs(this->lastFrame); this->triggerGPIOs(frame);
} }
else { else {
ESP_LOGD(TAG, "CMD:%s ADDR:%d RCODE:%d REPEAT:%d", ESP_LOGD(TAG, "CMD:%s ADDR:%d RCODE:%d REPEAT:%d",
translateSomfyCommand(this->lastFrame.cmd).c_str(), translateSomfyCommand(frame.cmd).c_str(),
this->lastFrame.remoteAddress, this->lastFrame.rollingCode, repeat); frame.remoteAddress, frame.rollingCode, repeat);
// Only simulate the command if it is actually going on air. // Only simulate the command if it is actually going on air.
if(!somfy.sendFrame(this->lastFrame, repeat, delayMs)) return; if(!somfy.sendFrame(frame, repeat, delayMs)) return false;
} }
this->commitRollingCode(frame.rollingCode);
this->lastFrame = frame;
somfy.processFrame(this->lastFrame, true); somfy.processFrame(this->lastFrame, true);
return true;
} }
bool SomfyRemote::isLastCommand(somfy_commands cmd) { bool SomfyRemote::isLastCommand(somfy_commands cmd) {
if(this->lastFrame.cmd != cmd || this->lastFrame.rollingCode != this->lastRollingCode) { if(this->lastFrame.cmd != cmd || this->lastFrame.rollingCode != this->lastRollingCode) {
@ -3939,21 +3937,23 @@ bool SomfyRemote::isLastCommand(somfy_commands cmd) {
} }
return true; return true;
} }
void SomfyRemote::repeatFrame(uint8_t repeat) { bool SomfyRemote::repeatFrame(uint8_t repeat) {
if(this->proto == radio_proto::GP_Relay) if(this->proto == radio_proto::GP_Relay)
return; return true;
else if(this->proto == radio_proto::GP_Remote) { else if(this->proto == radio_proto::GP_Remote) {
this->triggerGPIOs(this->lastFrame); this->triggerGPIOs(this->lastFrame);
return; return true;
} }
// The first frame is encoded with the current repeat counter and the radio // The first frame is encoded with the current repeat counter and the radio
// task counts up from there for each repeat frame. // task counts up from there for each repeat frame.
somfy_tx_job_t job; somfy_tx_job_t job;
encodeTxJob(job, this->lastFrame, repeat); encodeTxJob(job, this->lastFrame, repeat);
job.firstRepeat = this->lastFrame.repeats + 2; job.firstRepeat = this->lastFrame.repeats + 2;
// Only advance the counter for frames that will go on air.
if(!somfy.transceiver.queueTx(job)) return false;
this->lastFrame.repeats += repeat + 1; this->lastFrame.repeats += repeat + 1;
somfy.transceiver.queueTx(job);
//somfy.processFrame(this->lastFrame, true); //somfy.processFrame(this->lastFrame, true);
return true;
} }
bool SomfyShadeController::sendFrame(somfy_frame_t &frame, uint8_t repeat, uint16_t delayMs, bool priority) { bool SomfyShadeController::sendFrame(somfy_frame_t &frame, uint8_t repeat, uint16_t delayMs, bool priority) {
somfy_tx_job_t job; somfy_tx_job_t job;
@ -4027,17 +4027,23 @@ bool SomfyShadeController::deleteGroup(uint8_t groupId) {
} }
bool SomfyShadeController::loadShadesFile(const char *filename) { return ShadeConfigFile::load(this, filename); } bool SomfyShadeController::loadShadesFile(const char *filename) { return ShadeConfigFile::load(this, filename); }
uint16_t SomfyRemote::getNextRollingCode() { // The next rolling code is only read here. It is consumed by commitRollingCode
// once the frame using it has been queued.
uint16_t SomfyRemote::peekNextRollingCode() {
if(!pref.begin("ShadeCodes")) { if(!pref.begin("ShadeCodes")) {
ESP_LOGE(TAG, "getNextRollingCode() pref.begin(ShadeCodes) FAILED"); ESP_LOGE(TAG, "peekNextRollingCode() pref.begin(ShadeCodes) FAILED");
} }
uint16_t code = pref.getUShort(this->m_remotePrefId, 0); uint16_t code = pref.getUShort(this->m_remotePrefId, 0);
code++; pref.end();
return (uint16_t)(code + 1);
}
void SomfyRemote::commitRollingCode(uint16_t code) {
if(!pref.begin("ShadeCodes")) {
ESP_LOGE(TAG, "commitRollingCode() pref.begin(ShadeCodes) FAILED");
}
pref.putUShort(this->m_remotePrefId, code); pref.putUShort(this->m_remotePrefId, code);
pref.end(); pref.end();
this->p_lastRollingCode(code); this->p_lastRollingCode(code);
//Serial.printf("Getting Next Rolling code %d\n", this->lastRollingCode);
return code;
} }
uint16_t SomfyRemote::p_lastRollingCode(uint16_t code) { uint16_t SomfyRemote::p_lastRollingCode(uint16_t code) {
uint16_t old = this->lastRollingCode; uint16_t old = this->lastRollingCode;
@ -5069,7 +5075,7 @@ void Transceiver::loop() {
} }
} }
bool Transceiver::queueTx(somfy_tx_job_t &job) { bool Transceiver::queueTx(somfy_tx_job_t &job) {
if(!txJobQueue) { if(!txJobQueue || !txTaskHandle) {
ESP_LOGE(TAG, "Radio task not started, dropping transmission"); ESP_LOGE(TAG, "Radio task not started, dropping transmission");
return false; return false;
} }
@ -5090,9 +5096,6 @@ bool Transceiver::queueTx(somfy_tx_job_t &job) {
else xQueueSendToBack(txJobQueue, &job, 0); else xQueueSendToBack(txJobQueue, &job, 0);
return true; return true;
} }
bool Transceiver::canQueueTx(bool priority) {
return txJobQueue && txTaskHandle && !txSuspendMask && uxQueueSpacesAvailable(txJobQueue) > (priority ? 0 : TX_RESERVED_SLOTS);
}
void Transceiver::flushTx() { void Transceiver::flushTx() {
TxQueueLock lock; TxQueueLock lock;
++txGen; ++txGen;

View file

@ -273,7 +273,8 @@ class SomfyRemote {
char *getRemotePrefId() {return m_remotePrefId;} char *getRemotePrefId() {return m_remotePrefId;}
virtual void setRemoteAddress(uint32_t address); virtual void setRemoteAddress(uint32_t address);
virtual uint32_t getRemoteAddress(); virtual uint32_t getRemoteAddress();
virtual uint16_t getNextRollingCode(); uint16_t peekNextRollingCode();
void commitRollingCode(uint16_t code);
virtual uint16_t setRollingCode(uint16_t code); virtual uint16_t setRollingCode(uint16_t code);
bool hasSunSensor(); bool hasSunSensor();
bool hasLight(); bool hasLight();
@ -281,12 +282,13 @@ class SomfyRemote {
void setSunSensor(bool bHasSensor); void setSunSensor(bool bHasSensor);
void setLight(bool bHasLight); void setLight(bool bHasLight);
void setSimMy(bool bSimMy); void setSimMy(bool bSimMy);
virtual void sendCommand(somfy_commands cmd); // The send functions return false only when the radio could not take the
virtual void sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize = 0); // command. Nothing about the remote or shade state has changed in that case.
void sendCommandAfter(somfy_commands cmd, uint8_t repeat, uint8_t stepSize, uint16_t delayMs); virtual bool sendCommand(somfy_commands cmd);
void sendSensorCommand(int8_t isWindy, int8_t isSunny, uint8_t repeat); virtual bool sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize = 0);
void repeatFrame(uint8_t repeat); bool sendCommandAfter(somfy_commands cmd, uint8_t repeat, uint8_t stepSize, uint16_t delayMs);
bool canTransmit(); bool sendSensorCommand(int8_t isWindy, int8_t isSunny, uint8_t repeat);
bool repeatFrame(uint8_t repeat);
virtual uint16_t p_lastRollingCode(uint16_t code); virtual uint16_t p_lastRollingCode(uint16_t code);
somfy_commands transformCommand(somfy_commands cmd); somfy_commands transformCommand(somfy_commands cmd);
virtual void triggerGPIOs(somfy_frame_t &frame); virtual void triggerGPIOs(somfy_frame_t &frame);
@ -358,11 +360,11 @@ class SomfyShade : public SomfyRemote {
void setTarget(float target); void setTarget(float target);
bool isAtTarget(); bool isAtTarget();
bool isToggle(); bool isToggle();
void moveToTarget(float pos, float tilt = -1.0f); bool moveToTarget(float pos, float tilt = -1.0f);
void moveToTiltTarget(float target); void moveToTiltTarget(float target);
void sendTiltCommand(somfy_commands cmd); void sendTiltCommand(somfy_commands cmd);
void sendCommand(somfy_commands cmd); bool sendCommand(somfy_commands cmd);
void sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize = 0); bool sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize = 0);
bool linkRemote(uint32_t remoteAddress, uint16_t rollingCode = 0); bool linkRemote(uint32_t remoteAddress, uint16_t rollingCode = 0);
bool unlinkRemote(uint32_t remoteAddress); bool unlinkRemote(uint32_t remoteAddress);
void emitState(const char *evt = "shadeState"); void emitState(const char *evt = "shadeState");
@ -370,7 +372,7 @@ class SomfyShade : public SomfyRemote {
void emitCommand(somfy_commands cmd, const char *source, uint32_t sourceAddress, const char *evt = "shadeCommand"); void emitCommand(somfy_commands cmd, const char *source, uint32_t sourceAddress, const char *evt = "shadeCommand");
void emitCommand(uint8_t num, somfy_commands cmd, const char *source, uint32_t sourceAddress, const char *evt = "shadeCommand"); void emitCommand(uint8_t num, somfy_commands cmd, const char *source, uint32_t sourceAddress, const char *evt = "shadeCommand");
void setMyPosition(int8_t pos, int8_t tilt = -1); void setMyPosition(int8_t pos, int8_t tilt = -1);
void moveToMyPosition(); bool moveToMyPosition();
void processWaitingFrame(); void processWaitingFrame();
void publish(); void publish();
void unpublish(); void unpublish();
@ -437,8 +439,8 @@ class SomfyGroup : public SomfyRemote {
void updateFlags(); void updateFlags();
void emitState(const char *evt = "groupState"); void emitState(const char *evt = "groupState");
void emitState(uint8_t num, const char *evt = "groupState"); void emitState(uint8_t num, const char *evt = "groupState");
void sendCommand(somfy_commands cmd); bool sendCommand(somfy_commands cmd);
void sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize = 0); bool sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize = 0);
int8_t p_direction(int8_t dir); int8_t p_direction(int8_t dir);
bool publish(const char *topic, uint8_t val, bool retain = false); bool publish(const char *topic, uint8_t val, bool retain = false);
bool publish(const char *topic, int8_t val, bool retain = false); bool publish(const char *topic, int8_t val, bool retain = false);
@ -544,7 +546,6 @@ class Transceiver {
somfy_frame_t& lastFrame(); somfy_frame_t& lastFrame();
void sendFrame(byte *frame, uint8_t sync, uint8_t bitLength = 56); void sendFrame(byte *frame, uint8_t sync, uint8_t bitLength = 56);
bool queueTx(somfy_tx_job_t &job); bool queueTx(somfy_tx_job_t &job);
bool canQueueTx(bool priority = false);
void flushTx(); void flushTx();
void suspendTx(uint8_t reason); void suspendTx(uint8_t reason);
void resumeTx(uint8_t reason); void resumeTx(uint8_t reason);