mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 13:52:15 +01:00
fix: keep shade simulation in step with what goes on air
- Hold a shade's simulated movement until its command is on air. - Send stops at the target as priority jobs. - If a stop can't be sent or is dropped, simulate the run to the limit. - Roll back targets when a movement command is dropped. - Only store a favorite position when SETMY was queued. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ad15ecacd4
commit
e55d9e48f7
2 changed files with 193 additions and 76 deletions
240
src/Somfy.cpp
240
src/Somfy.cpp
|
|
@ -61,6 +61,11 @@ static std::atomic<uint32_t> txGen{0};
|
|||
static std::atomic<uint32_t> txPreemptWaiters{0};
|
||||
// TX_SUSPEND_* reasons the radio must not transmit for (frequency scan, OTA).
|
||||
static std::atomic<uint32_t> txSuspendMask{0};
|
||||
// Last sequence number handed to a queued job. Only touched under TxQueueLock.
|
||||
static uint32_t txSeq = 0;
|
||||
// How long a shade's simulated movement waits for its command to go on air
|
||||
// before it starts anyway in case the event was lost.
|
||||
#define TX_PENDING_TIMEOUT 30000
|
||||
struct RadioLock {
|
||||
bool locked = true; // Before the mutex exists there is nothing to serialize against.
|
||||
bool owned = false;
|
||||
|
|
@ -84,7 +89,7 @@ static void postTxEvent(tx_event_t type, const somfy_tx_job_t &job, uint32_t at)
|
|||
evt.type = type;
|
||||
evt.cmd = job.frame.cmd;
|
||||
evt.remoteAddress = job.frame.remoteAddress;
|
||||
evt.queuedAt = job.queuedAt;
|
||||
evt.seq = job.seq;
|
||||
evt.at = at;
|
||||
if(xQueueSend(txEventQueue, &evt, 0) != pdTRUE) ESP_LOGW(TAG, "Radio TX event queue full");
|
||||
}
|
||||
|
|
@ -98,6 +103,21 @@ static void encodeTxJob(somfy_tx_job_t &job, somfy_frame_t &frame, uint8_t repea
|
|||
job.frame = frame;
|
||||
job.repeats = repeat;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
int sort_asc(const void *cmp1, const void *cmp2) {
|
||||
int a = *((uint8_t *)cmp1);
|
||||
|
|
@ -1124,6 +1144,17 @@ void SomfyShade::checkMovement() {
|
|||
uint8_t currPos = floor(this->currentPos);
|
||||
uint8_t currTiltPos = floor(this->currentTiltPos);
|
||||
if(this->direction != 0) this->lastMovement = this->direction;
|
||||
if(this->txPendingSeq != 0) {
|
||||
// The command that set us moving hasn't gone on air yet so the motor hasn't
|
||||
// started. Hold the simulated position until it does.
|
||||
if((uint32_t)curTime - this->txPendingSince < TX_PENDING_TIMEOUT) {
|
||||
this->moveStart = this->tiltStart = curTime;
|
||||
if(currDir != this->direction || currTiltDir != this->tiltDirection) this->emitState();
|
||||
return;
|
||||
}
|
||||
ESP_LOGW(TAG, "[Shade %u] Timed out waiting for the radio, moving anyway", this->shadeId);
|
||||
this->txPendingSeq = 0;
|
||||
}
|
||||
if (sunFlag) {
|
||||
if (isSunny && !isWindy) { // It is sunny and there is no wind so we should be extended
|
||||
if (this->noWindDone
|
||||
|
|
@ -1211,14 +1242,15 @@ void SomfyShade::checkMovement() {
|
|||
// If we need to stop the shade do this before we indicate that we are
|
||||
// not moving otherwise the my function will kick in.
|
||||
if(this->settingPos) {
|
||||
if(!isAtTarget()) {
|
||||
ESP_LOGD(TAG, "We are not at our tilt target: %.2f", this->tiltTarget);
|
||||
if(this->target != 100.0) SomfyRemote::sendCommand(somfy_commands::My, this->repeats);
|
||||
// We now need to move the tilt to the position we requested.
|
||||
this->moveToTiltTarget(this->tiltTarget);
|
||||
// At the end limit the motor stops by itself. If the stop doesn't go
|
||||
// out the shade is now running on to the limit so leave the tilt be.
|
||||
if(this->target == 100.0 || this->sendStop(1, false)) {
|
||||
if(!isAtTarget()) {
|
||||
ESP_LOGD(TAG, "We are not at our tilt target: %.2f", this->tiltTarget);
|
||||
// We now need to move the tilt to the position we requested.
|
||||
this->moveToTiltTarget(this->tiltTarget);
|
||||
}
|
||||
}
|
||||
else
|
||||
if(this->target != 100.0) SomfyRemote::sendCommand(somfy_commands::My, this->repeats);
|
||||
}
|
||||
this->p_direction(0);
|
||||
this->tiltStart = curTime;
|
||||
|
|
@ -1262,14 +1294,15 @@ void SomfyShade::checkMovement() {
|
|||
// If we need to stop the shade do this before we indicate that we are
|
||||
// not moving otherwise the my function will kick in.
|
||||
if(this->settingPos) {
|
||||
if(!isAtTarget()) {
|
||||
ESP_LOGD(TAG, "We are not at our tilt target: %.2f", this->tiltTarget);
|
||||
if(this->target != 0.0) SomfyRemote::sendCommand(somfy_commands::My, this->repeats);
|
||||
// We now need to move the tilt to the position we requested.
|
||||
this->moveToTiltTarget(this->tiltTarget);
|
||||
// At the end limit the motor stops by itself. If the stop doesn't go
|
||||
// out the shade is now running on to the limit so leave the tilt be.
|
||||
if(this->target == 0.0 || this->sendStop(-1, false)) {
|
||||
if(!isAtTarget()) {
|
||||
ESP_LOGD(TAG, "We are not at our tilt target: %.2f", this->tiltTarget);
|
||||
// We now need to move the tilt to the position we requested.
|
||||
this->moveToTiltTarget(this->tiltTarget);
|
||||
}
|
||||
}
|
||||
else
|
||||
if(this->target != 0.0) SomfyRemote::sendCommand(somfy_commands::My, this->repeats);
|
||||
}
|
||||
this->p_direction(0);
|
||||
this->tiltStart = curTime;
|
||||
|
|
@ -1314,11 +1347,11 @@ void SomfyShade::checkMovement() {
|
|||
if(this->tiltType == tilt_types::integrated) {
|
||||
// If this is an integrated tilt mechanism the we will simply let it finish. If it is not then we will stop it.
|
||||
//Serial.printf("Sending My -- tiltTarget: %.2f, tiltDirection: %d\n", this->tiltTarget, this->tiltDirection);
|
||||
if(this->tiltTarget != 100.0f || this->currentPos != 100.0f) SomfyRemote::sendCommand(somfy_commands::My, this->repeats);
|
||||
if(this->tiltTarget != 100.0f || this->currentPos != 100.0f) this->sendStop(1, true);
|
||||
}
|
||||
else {
|
||||
// This is a tilt motor so let it complete if it is going to 100.
|
||||
if(this->tiltTarget != 100.0f) SomfyRemote::sendCommand(somfy_commands::My, this->repeats);
|
||||
if(this->tiltTarget != 100.0f) this->sendStop(1, true);
|
||||
}
|
||||
}
|
||||
this->p_tiltDirection(0);
|
||||
|
|
@ -1364,11 +1397,11 @@ void SomfyShade::checkMovement() {
|
|||
if(this->tiltType == tilt_types::integrated) {
|
||||
// If this is an integrated tilt mechanism the we will simply let it finish. If it is not then we will stop it.
|
||||
//Serial.printf("Sending My -- tiltTarget: %.2f, tiltDirection: %d\n", this->tiltTarget, this->tiltDirection);
|
||||
if(this->tiltTarget != 0.0 || this->currentPos != 0.0) SomfyRemote::sendCommand(somfy_commands::My, this->repeats);
|
||||
if(this->tiltTarget != 0.0 || this->currentPos != 0.0) this->sendStop(-1, true);
|
||||
}
|
||||
else {
|
||||
// This is a tilt motor so let it complete if it is going to 0.
|
||||
if(this->tiltTarget != 0.0) SomfyRemote::sendCommand(somfy_commands::My, this->repeats);
|
||||
if(this->tiltTarget != 0.0) this->sendStop(-1, true);
|
||||
}
|
||||
}
|
||||
this->p_tiltDirection(0);
|
||||
|
|
@ -1380,6 +1413,8 @@ void SomfyShade::checkMovement() {
|
|||
if(this->settingMyPos && this->isAtTarget()) {
|
||||
// Set this position before sending the command. If you don't the processFrame function
|
||||
// will send the shade back to its original My position.
|
||||
const float oldMyPos = this->myPos;
|
||||
const float oldMyTiltPos = this->myTiltPos;
|
||||
if(this->tiltType != tilt_types::none) {
|
||||
if(this->myTiltPos == this->currentTiltPos && this->myPos == this->currentPos) this->myPos = this->myTiltPos = -1;
|
||||
else {
|
||||
|
|
@ -1393,9 +1428,13 @@ void SomfyShade::checkMovement() {
|
|||
else this->p_myPos(this->currentPos);
|
||||
}
|
||||
// Give the motor time to settle at the target before it is told to store it.
|
||||
SomfyRemote::sendCommandAfter(somfy_commands::My, SETMY_REPEATS, 0, 200);
|
||||
if(SomfyRemote::sendCommandAfter(somfy_commands::My, SETMY_REPEATS, 0, 200)) this->commitMyPosition();
|
||||
else {
|
||||
// The motor never learned it so keep the favorite it has.
|
||||
this->p_myPos(oldMyPos);
|
||||
this->p_myTiltPos(oldMyTiltPos);
|
||||
}
|
||||
this->settingMyPos = false;
|
||||
this->commitMyPosition();
|
||||
this->emitState();
|
||||
}
|
||||
else if(currDir != this->direction || currPos != floor(this->currentPos) || currTiltDir != this->tiltDirection || currTiltPos != floor(this->currentTiltPos)) {
|
||||
|
|
@ -2777,13 +2816,71 @@ void SomfyShade::setTiltMovement(int8_t dir) {
|
|||
this->emitState();
|
||||
}
|
||||
}
|
||||
void SomfyShade::shiftMovementStart(uint32_t queuedAt, uint32_t startedAt) {
|
||||
// The movement was started when the command was queued but the motor only
|
||||
// starts once the frame is on air. Move the start forward so the simulated
|
||||
// position does not run ahead of the motor.
|
||||
const uint32_t wait = startedAt - queuedAt;
|
||||
if(this->direction != 0 && (uint32_t)((uint32_t)this->moveStart - queuedAt) < wait) this->moveStart = startedAt;
|
||||
if(this->tiltDirection != 0 && (uint32_t)((uint32_t)this->tiltStart - queuedAt) < wait) this->tiltStart = startedAt;
|
||||
void SomfyShade::markTxPending(uint32_t seq) {
|
||||
// Only a shade the command set moving has to wait for it.
|
||||
if(this->isAtTarget()) return;
|
||||
this->txPendingSeq = seq;
|
||||
this->txPendingSince = millis();
|
||||
}
|
||||
void SomfyShade::processTxEvent(const somfy_tx_event_t &evt) {
|
||||
if(evt.seq == 0) return;
|
||||
if(this->txPendingSeq == evt.seq) {
|
||||
this->txPendingSeq = 0;
|
||||
if(evt.type == tx_event_t::started) {
|
||||
// The motor starts now so the simulated movement does too.
|
||||
this->moveStart = this->tiltStart = evt.at;
|
||||
}
|
||||
else {
|
||||
// The motor never got the command so it hasn't moved.
|
||||
ESP_LOGW(TAG, "[Shade %u] Command dropped by the radio, staying at %.2f%%", this->shadeId, this->currentPos);
|
||||
this->settingPos = this->settingTiltPos = this->settingMyPos = false;
|
||||
this->p_target(this->currentPos);
|
||||
this->p_tiltTarget(this->currentTiltPos);
|
||||
this->emitState();
|
||||
}
|
||||
}
|
||||
if(this->txStopSeq == evt.seq) {
|
||||
this->txStopSeq = 0;
|
||||
// Unless something has moved the shade since, the motor never got the stop
|
||||
// and is still running.
|
||||
if(evt.type == tx_event_t::dropped && this->direction == 0 && this->tiltDirection == 0) {
|
||||
ESP_LOGW(TAG, "[Shade %u] Stop dropped by the radio, running on to the limit", this->shadeId);
|
||||
this->runToLimit(this->txStopDir, this->txStopTilt, this->txStopAt);
|
||||
this->emitState();
|
||||
}
|
||||
}
|
||||
}
|
||||
bool SomfyShade::sendStop(int8_t dir, bool tilt) {
|
||||
const uint32_t now = millis();
|
||||
// Stops jump the radio queue so the motor stops as close to the target as it can.
|
||||
if(SomfyRemote::sendCommandAfter(somfy_commands::My, this->repeats, 0, 0, true)) {
|
||||
this->txStopSeq = this->lastTxSeq;
|
||||
this->txStopAt = now;
|
||||
this->txStopDir = dir;
|
||||
this->txStopTilt = tilt;
|
||||
return true;
|
||||
}
|
||||
ESP_LOGW(TAG, "[Shade %u] Stop not sent, running on to the limit", this->shadeId);
|
||||
this->runToLimit(dir, tilt, now);
|
||||
return false;
|
||||
}
|
||||
void SomfyShade::runToLimit(int8_t dir, bool tilt, uint32_t since) {
|
||||
// The motor didn't get a stop at the target so it keeps going until its end
|
||||
// limit. Carry on simulating that from where and when it should have stopped.
|
||||
if(tilt) {
|
||||
this->startTiltPos = this->currentTiltPos;
|
||||
this->tiltStart = since;
|
||||
this->settingTiltPos = false;
|
||||
this->p_tiltTarget(dir > 0 ? 100.0f : 0.0f);
|
||||
}
|
||||
else {
|
||||
this->startPos = this->currentPos;
|
||||
this->moveStart = since;
|
||||
this->settingPos = false;
|
||||
this->p_target(dir > 0 ? 100.0f : 0.0f);
|
||||
}
|
||||
// The favorite can't be set where the shade won't stop.
|
||||
this->settingMyPos = false;
|
||||
}
|
||||
void SomfyShade::setMovement(int8_t dir) {
|
||||
int8_t currDir = this->direction;
|
||||
|
|
@ -2805,28 +2902,26 @@ void SomfyShade::setMyPosition(int8_t pos, int8_t tilt) {
|
|||
if(this->tiltType == tilt_types::tiltonly) {
|
||||
this->p_myPos(-1.0f);
|
||||
if(tilt != floor(this->currentTiltPos)) {
|
||||
this->settingMyPos = true;
|
||||
// The favorite is set once we get there, if the move went out.
|
||||
if(tilt == floor(this->myTiltPos))
|
||||
this->moveToMyPosition();
|
||||
this->settingMyPos = this->moveToMyPosition();
|
||||
else
|
||||
this->moveToTarget(100, tilt);
|
||||
this->settingMyPos = this->moveToTarget(100, tilt);
|
||||
}
|
||||
else if(tilt == floor(this->myTiltPos)) {
|
||||
// Of so we need to clear the my position. These motors are finicky so send
|
||||
// a my command to ensure we are actually at the my position then send the clear
|
||||
// command. There really is no other way to do this.
|
||||
if(this->currentTiltPos != this->myTiltPos) {
|
||||
this->settingMyPos = true;
|
||||
this->moveToMyPosition();
|
||||
this->settingMyPos = this->moveToMyPosition();
|
||||
}
|
||||
else {
|
||||
SomfyRemote::sendCommand(somfy_commands::My, this->repeats);
|
||||
else if(SomfyRemote::sendCommand(somfy_commands::My, this->repeats)) {
|
||||
this->settingPos = false;
|
||||
this->settingMyPos = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
SomfyRemote::sendCommand(somfy_commands::My, SETMY_REPEATS);
|
||||
else if(SomfyRemote::sendCommand(somfy_commands::My, SETMY_REPEATS)) {
|
||||
// Only store a favorite the motor was told about.
|
||||
this->p_myTiltPos(this->currentTiltPos);
|
||||
}
|
||||
this->commitMyPosition();
|
||||
|
|
@ -2835,28 +2930,26 @@ void SomfyShade::setMyPosition(int8_t pos, int8_t tilt) {
|
|||
else if(this->tiltType != tilt_types::none) {
|
||||
if(tilt < 0) tilt = 0;
|
||||
if(pos != floor(this->currentPos) || tilt != floor(this->currentTiltPos)) {
|
||||
this->settingMyPos = true;
|
||||
// The favorite is set once we get there, if the move went out.
|
||||
if(pos == floor(this->myPos) && tilt == floor(this->myTiltPos))
|
||||
this->moveToMyPosition();
|
||||
this->settingMyPos = this->moveToMyPosition();
|
||||
else
|
||||
this->moveToTarget(pos, tilt);
|
||||
this->settingMyPos = this->moveToTarget(pos, tilt);
|
||||
}
|
||||
else if(pos == floor(this->myPos) && tilt == floor(this->myTiltPos)) {
|
||||
// Of so we need to clear the my position. These motors are finicky so send
|
||||
// a my command to ensure we are actually at the my position then send the clear
|
||||
// command. There really is no other way to do this.
|
||||
if(this->currentPos != this->myPos || this->currentTiltPos != this->myTiltPos) {
|
||||
this->settingMyPos = true;
|
||||
this->moveToMyPosition();
|
||||
this->settingMyPos = this->moveToMyPosition();
|
||||
}
|
||||
else {
|
||||
SomfyRemote::sendCommand(somfy_commands::My, this->repeats);
|
||||
else if(SomfyRemote::sendCommand(somfy_commands::My, this->repeats)) {
|
||||
this->settingPos = false;
|
||||
this->settingMyPos = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
SomfyRemote::sendCommand(somfy_commands::My, SETMY_REPEATS);
|
||||
else if(SomfyRemote::sendCommand(somfy_commands::My, SETMY_REPEATS)) {
|
||||
// Only store a favorite the motor was told about.
|
||||
this->p_myPos(this->currentPos);
|
||||
this->p_myTiltPos(this->currentTiltPos);
|
||||
}
|
||||
|
|
@ -2865,28 +2958,26 @@ void SomfyShade::setMyPosition(int8_t pos, int8_t tilt) {
|
|||
}
|
||||
else {
|
||||
if(pos != floor(this->currentPos)) {
|
||||
this->settingMyPos = true;
|
||||
// The favorite is set once we get there, if the move went out.
|
||||
if(pos == floor(this->myPos))
|
||||
this->moveToMyPosition();
|
||||
this->settingMyPos = this->moveToMyPosition();
|
||||
else
|
||||
this->moveToTarget(pos);
|
||||
this->settingMyPos = this->moveToTarget(pos);
|
||||
}
|
||||
else if(pos == floor(this->myPos)) {
|
||||
// Of so we need to clear the my position. These motors are finicky so send
|
||||
// a my command to ensure we are actually at the my position then send the clear
|
||||
// command. There really is no other way to do this.
|
||||
if(this->myPos != this->currentPos) {
|
||||
this->settingMyPos = true;
|
||||
this->moveToMyPosition();
|
||||
this->settingMyPos = this->moveToMyPosition();
|
||||
}
|
||||
else {
|
||||
SomfyRemote::sendCommand(somfy_commands::My, this->repeats);
|
||||
else if(SomfyRemote::sendCommand(somfy_commands::My, this->repeats)) {
|
||||
this->settingPos = false;
|
||||
this->settingMyPos = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
SomfyRemote::sendCommand(somfy_commands::My, SETMY_REPEATS);
|
||||
else if(SomfyRemote::sendCommand(somfy_commands::My, SETMY_REPEATS)) {
|
||||
// Only store a favorite the motor was told about.
|
||||
this->p_myPos(currentPos);
|
||||
this->p_myTiltPos(-1);
|
||||
this->commitMyPosition();
|
||||
|
|
@ -3038,6 +3129,8 @@ bool SomfyGroup::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSiz
|
|||
}
|
||||
}
|
||||
}
|
||||
// The member shades only started moving above.
|
||||
if(this->lastTxSeq != 0 && startsMovement(this->lastFrame.cmd)) somfy.markTxPending(this->getRemoteAddress(), this->lastTxSeq);
|
||||
this->updateFlags();
|
||||
this->emitState();
|
||||
return true;
|
||||
|
|
@ -3496,7 +3589,7 @@ void SomfyShadeController::processFrame(somfy_frame_t &frame, bool internal) {
|
|||
if(this->shades[i].getShadeId() != 255) this->shades[i].processFrame(frame, internal);
|
||||
}
|
||||
}
|
||||
void SomfyShadeController::shiftMovementStart(uint32_t remoteAddress, uint32_t queuedAt, uint32_t startedAt) {
|
||||
void SomfyShadeController::markTxPending(uint32_t remoteAddress, uint32_t seq) {
|
||||
// Same matching as SomfyShade::processFrame for shade and linked remotes.
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
SomfyShade *shade = &this->shades[i];
|
||||
|
|
@ -3504,7 +3597,7 @@ void SomfyShadeController::shiftMovementStart(uint32_t remoteAddress, uint32_t q
|
|||
bool hasRemote = shade->getRemoteAddress() == remoteAddress;
|
||||
for(uint8_t j = 0; !hasRemote && j < SOMFY_MAX_LINKED_REMOTES; j++)
|
||||
hasRemote = shade->linkedRemotes[j].getRemoteAddress() == remoteAddress;
|
||||
if(hasRemote) shade->shiftMovementStart(queuedAt, startedAt);
|
||||
if(hasRemote) shade->markTxPending(seq);
|
||||
}
|
||||
// Group commands move their member shades through processInternalCommand.
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_GROUPS; i++) {
|
||||
|
|
@ -3513,10 +3606,14 @@ void SomfyShadeController::shiftMovementStart(uint32_t remoteAddress, uint32_t q
|
|||
for(uint8_t j = 0; j < SOMFY_MAX_GROUPED_SHADES; j++) {
|
||||
if(group->linkedShades[j] == 0) continue;
|
||||
SomfyShade *shade = this->getShadeById(group->linkedShades[j]);
|
||||
if(shade) shade->shiftMovementStart(queuedAt, startedAt);
|
||||
if(shade) shade->markTxPending(seq);
|
||||
}
|
||||
}
|
||||
}
|
||||
void SomfyShadeController::processTxEvent(const somfy_tx_event_t &evt) {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++)
|
||||
if(this->shades[i].getShadeId() != 255) this->shades[i].processTxEvent(evt);
|
||||
}
|
||||
void SomfyShadeController::processWaitingFrame() {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++)
|
||||
if(this->shades[i].getShadeId() != 255) this->shades[i].processWaitingFrame();
|
||||
|
|
@ -3888,7 +3985,7 @@ bool SomfyRemote::sendSensorCommand(int8_t isWindy, int8_t isSunny, uint8_t repe
|
|||
}
|
||||
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); }
|
||||
bool SomfyRemote::sendCommandAfter(somfy_commands cmd, uint8_t repeat, uint8_t stepSize, uint16_t delayMs) {
|
||||
bool 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;
|
||||
|
|
@ -3907,6 +4004,7 @@ bool SomfyRemote::sendCommandAfter(somfy_commands cmd, uint8_t repeat, uint8_t s
|
|||
// We have to set the processed to clear this if we are sending
|
||||
// another command.
|
||||
frame.processed = false;
|
||||
uint32_t seq = 0;
|
||||
if(this->proto == radio_proto::GP_Relay) {
|
||||
ESP_LOGD(TAG, "CMD:%s ADDR:%d RCODE:%d SETTING GPIO",
|
||||
translateSomfyCommand(frame.cmd).c_str(),
|
||||
|
|
@ -3923,11 +4021,15 @@ bool SomfyRemote::sendCommandAfter(somfy_commands cmd, uint8_t repeat, uint8_t s
|
|||
translateSomfyCommand(frame.cmd).c_str(),
|
||||
frame.remoteAddress, frame.rollingCode, repeat);
|
||||
// Only simulate the command if it is actually going on air.
|
||||
if(!somfy.sendFrame(frame, repeat, delayMs)) return false;
|
||||
if(!somfy.sendFrame(frame, repeat, delayMs, priority, &seq)) return false;
|
||||
}
|
||||
this->commitRollingCode(frame.rollingCode);
|
||||
this->lastFrame = frame;
|
||||
this->lastTxSeq = seq;
|
||||
somfy.processFrame(this->lastFrame, true);
|
||||
// Shades this set moving wait for the frame to go on air. Stops are sent
|
||||
// ahead of everything else so they don't need to.
|
||||
if(seq != 0 && !priority && startsMovement(frame.cmd)) somfy.markTxPending(frame.remoteAddress, seq);
|
||||
return true;
|
||||
}
|
||||
bool SomfyRemote::isLastCommand(somfy_commands cmd) {
|
||||
|
|
@ -3955,13 +4057,15 @@ bool SomfyRemote::repeatFrame(uint8_t repeat) {
|
|||
//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, uint32_t *seq) {
|
||||
somfy_tx_job_t job;
|
||||
encodeTxJob(job, frame, repeat);
|
||||
job.firstRepeat = 1;
|
||||
job.priority = priority;
|
||||
if(delayMs > 0) job.notBefore = millis() + delayMs;
|
||||
return this->transceiver.queueTx(job);
|
||||
if(!this->transceiver.queueTx(job)) return false;
|
||||
if(seq) *seq = job.seq;
|
||||
return true;
|
||||
}
|
||||
bool SomfyShadeController::deleteShade(uint8_t shadeId) {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
|
|
@ -5019,13 +5123,10 @@ void Transceiver::loop() {
|
|||
somfy_rx_t rx;
|
||||
somfy_tx_event_t evt;
|
||||
while(txEventQueue && xQueueReceive(txEventQueue, &evt, 0) == pdTRUE) {
|
||||
if(evt.type == tx_event_t::started) {
|
||||
// The shades were set moving when the job was queued so line their
|
||||
// movement up with the motor.
|
||||
somfy.shiftMovementStart(evt.remoteAddress, evt.queuedAt, evt.at);
|
||||
}
|
||||
else
|
||||
if(evt.type == tx_event_t::dropped)
|
||||
ESP_LOGW(TAG, "Radio TX dropped %s for %d", translateSomfyCommand(evt.cmd).c_str(), evt.remoteAddress);
|
||||
// Line the simulated shades up with what actually went on air.
|
||||
somfy.processTxEvent(evt);
|
||||
}
|
||||
if (noiseDetected && rxmode != 3 && this->config.noiseDetection) {
|
||||
if (millis() - noiseStart > 100) {
|
||||
|
|
@ -5090,7 +5191,8 @@ bool Transceiver::queueTx(somfy_tx_job_t &job) {
|
|||
return false;
|
||||
}
|
||||
job.gen = txGen;
|
||||
job.queuedAt = millis();
|
||||
if(++txSeq == 0) ++txSeq; // 0 means no job.
|
||||
job.seq = txSeq;
|
||||
// We are the only producer while holding the lock so the space check holds.
|
||||
if(job.priority) xQueueSendToFront(txJobQueue, &job, 0);
|
||||
else xQueueSendToBack(txJobQueue, &job, 0);
|
||||
|
|
|
|||
27
src/Somfy.h
27
src/Somfy.h
|
|
@ -218,7 +218,7 @@ struct somfy_tx_job_t {
|
|||
bool raw = false; // Repeater frame. Waits for the receiver to be idle.
|
||||
bool priority = false; // Goes to the front of the queue and may use the reserved slots.
|
||||
uint32_t gen = 0; // Flush generation when queued. Stale jobs are dropped.
|
||||
uint32_t queuedAt = 0; // millis() when queued.
|
||||
uint32_t seq = 0; // Assigned when queued so the job's events can be matched up.
|
||||
uint32_t notBefore = 0; // millis() before which the job must not go on air (0 = none).
|
||||
};
|
||||
enum class tx_event_t : uint8_t {
|
||||
|
|
@ -230,7 +230,7 @@ struct somfy_tx_event_t {
|
|||
tx_event_t type = tx_event_t::started;
|
||||
somfy_commands cmd = somfy_commands::My;
|
||||
uint32_t remoteAddress = 0;
|
||||
uint32_t queuedAt = 0;
|
||||
uint32_t seq = 0;
|
||||
uint32_t at = 0; // millis() when the job started or was dropped.
|
||||
};
|
||||
|
||||
|
|
@ -266,6 +266,7 @@ class SomfyRemote {
|
|||
somfy_frame_t lastFrame;
|
||||
bool flipCommands = false;
|
||||
uint16_t lastRollingCode = 0;
|
||||
uint32_t lastTxSeq = 0; // Radio job of the last command sent, 0 if it didn't use the radio.
|
||||
uint8_t flags = 0;
|
||||
uint8_t bitLength = 0;
|
||||
uint8_t repeats = 1;
|
||||
|
|
@ -286,7 +287,7 @@ class SomfyRemote {
|
|||
// command. Nothing about the remote or shade state has changed in that case.
|
||||
virtual bool sendCommand(somfy_commands cmd);
|
||||
virtual bool sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize = 0);
|
||||
bool sendCommandAfter(somfy_commands cmd, uint8_t repeat, uint8_t stepSize, uint16_t delayMs);
|
||||
bool sendCommandAfter(somfy_commands cmd, uint8_t repeat, uint8_t stepSize, uint16_t delayMs, bool priority = false);
|
||||
bool sendSensorCommand(int8_t isWindy, int8_t isSunny, uint8_t repeat);
|
||||
bool repeatFrame(uint8_t repeat);
|
||||
virtual uint16_t p_lastRollingCode(uint16_t code);
|
||||
|
|
@ -318,6 +319,18 @@ class SomfyShade : public SomfyRemote {
|
|||
bool settingPos = false;
|
||||
bool settingTiltPos = false;
|
||||
uint32_t awaitMy = 0;
|
||||
// Radio job that set the shade moving. The simulated position is held
|
||||
// until it goes on air. 0 when nothing is waiting.
|
||||
uint32_t txPendingSeq = 0;
|
||||
uint32_t txPendingSince = 0;
|
||||
// Stop sent when the simulated target was reached. If it never goes on
|
||||
// air the motor runs on to its end limit. 0 once it is on air.
|
||||
uint32_t txStopSeq = 0;
|
||||
uint32_t txStopAt = 0;
|
||||
int8_t txStopDir = 0;
|
||||
bool txStopTilt = false;
|
||||
bool sendStop(int8_t dir, bool tilt);
|
||||
void runToLimit(int8_t dir, bool tilt, uint32_t since);
|
||||
public:
|
||||
uint8_t roomId = 0;
|
||||
int8_t sortOrder = 0;
|
||||
|
|
@ -354,7 +367,8 @@ class SomfyShade : public SomfyRemote {
|
|||
void checkMovement();
|
||||
void processFrame(somfy_frame_t &frame, bool internal = false);
|
||||
void processInternalCommand(somfy_commands cmd, uint8_t repeat = 1);
|
||||
void shiftMovementStart(uint32_t queuedAt, uint32_t startedAt);
|
||||
void markTxPending(uint32_t seq);
|
||||
void processTxEvent(const somfy_tx_event_t &evt);
|
||||
void setTiltMovement(int8_t dir);
|
||||
void setMovement(int8_t dir);
|
||||
void setTarget(float target);
|
||||
|
|
@ -604,9 +618,10 @@ class SomfyShadeController {
|
|||
SomfyGroup * getGroupById(uint8_t groupId);
|
||||
SomfyShade * findShadeByRemoteAddress(uint32_t address);
|
||||
SomfyGroup * findGroupByRemoteAddress(uint32_t address);
|
||||
bool sendFrame(somfy_frame_t &frame, uint8_t repeats = 0, uint16_t delayMs = 0, bool priority = false);
|
||||
bool sendFrame(somfy_frame_t &frame, uint8_t repeats = 0, uint16_t delayMs = 0, bool priority = false, uint32_t *seq = nullptr);
|
||||
void processFrame(somfy_frame_t &frame, bool internal = false);
|
||||
void shiftMovementStart(uint32_t remoteAddress, uint32_t queuedAt, uint32_t startedAt);
|
||||
void markTxPending(uint32_t remoteAddress, uint32_t seq);
|
||||
void processTxEvent(const somfy_tx_event_t &evt);
|
||||
void emitState(uint8_t num = 255);
|
||||
void publish();
|
||||
void processWaitingFrame();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue