From dbff28743732593d9bd3adf0dcf50634df0c88ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Fri, 2 Jun 2023 15:26:43 +0200 Subject: [PATCH] Somfy: add sun/wind awning timings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Álvaro Fernández Rojas --- Somfy.cpp | 221 +++++++++++++++++++++++++++++++++++++++++------------- Somfy.h | 19 +++++ 2 files changed, 187 insertions(+), 53 deletions(-) diff --git a/Somfy.cpp b/Somfy.cpp index 2678dbd..4bc1623 100644 --- a/Somfy.cpp +++ b/Somfy.cpp @@ -599,6 +599,11 @@ bool SomfyShade::unlinkRemote(uint32_t address) { } bool SomfyShade::isAtTarget() { return this->currentPos == this->target && this->currentTiltPos == this->tiltTarget; } void SomfyShade::checkMovement() { + const uint64_t curTime = millis(); + const bool sunFlag = this->flags & static_cast(somfy_flags_t::SunFlag); + const bool isSunny = this->flags & static_cast(somfy_flags_t::Sunny); + const bool isWindy = this->flags & static_cast(somfy_flags_t::Windy); + // We are checking movement for essentially 3 types of motors. // If this is an integrated tilt we need to first tilt in the direction we are moving then move. We know // what needs to be done by the tilt type. Set a tilt first flag to indicate whether we should be tilting or @@ -615,7 +620,56 @@ void SomfyShade::checkMovement() { else if(this->direction != 0) this->tiltDirection = 0; uint8_t currPos = floor(this->currentPos); uint8_t currTiltPos = floor(this->currentTiltPos); - + + if (sunFlag) + { + if (isSunny && !isWindy) + { + if (this->noWindDone + && !this->sunDone + && this->sunStart + && (curTime - this->sunStart) >= SOMFY_SUN_TIMEOUT) + { + this->target = 100.0f; + this->sunDone = true; + + Serial.printf("[%u] Sun -> done\r\n", this->shadeId); + } + + if (!this->noWindDone + && this->noWindStart + && (curTime - this->noWindStart) >= SOMFY_NO_WIND_TIMEOUT) + { + this->target = 100.0f; + this->noWindDone = true; + + Serial.printf("[%u] No Wind -> done\r\n", this->shadeId); + } + } + + if (!isSunny + && !this->noSunDone + && this->noSunStart + && (curTime - this->noSunStart) >= SOMFY_NO_SUN_TIMEOUT) + { + this->target = 0.0f; + this->noSunDone = true; + + Serial.printf("[%u] No Sun -> done\r\n", this->shadeId); + } + } + + if (isWindy + && !this->windDone + && this->windStart + && (curTime - this->windStart) >= SOMFY_WIND_TIMEOUT) + { + this->target = 0.0f; + this->windDone = true; + + Serial.printf("[%u] Wind -> done\r\n", this->shadeId); + } + if(!tilt_first && this->direction > 0) { if(this->downTime == 0) { this->direction = 0; @@ -631,7 +685,7 @@ void SomfyShade::checkMovement() { // So if the start position is .1 it is 10% closed so we have a 1000ms (1sec) of time to account for // before we add any more time. - msFrom0 += (millis() - this->moveStart); + msFrom0 += (curTime - this->moveStart); // Now we should have the total number of ms that the shade moved from the top. But just so we // don't have any rounding errors make sure that it is not greater than the max down time. msFrom0 = min((int32_t)this->downTime, msFrom0); @@ -667,7 +721,7 @@ void SomfyShade::checkMovement() { if(this->target != 100.0) SomfyRemote::sendCommand(somfy_commands::My); } this->direction = 0; - this->tiltStart = millis(); + this->tiltStart = curTime; this->startTiltPos = this->currentTiltPos; if(this->isAtTarget()) this->commitShadePosition(); } @@ -683,7 +737,7 @@ void SomfyShade::checkMovement() { // can be calculated. // 10000ms from 100 to 0; int32_t msFrom100 = (int32_t)this->upTime - (int32_t)floor((this->startPos/100) * this->upTime); - msFrom100 += (millis() - this->moveStart); + msFrom100 += (curTime - this->moveStart); msFrom100 = min((int32_t)this->upTime, msFrom100); if(msFrom100 >= this->upTime) { this->currentPos = 0.0; @@ -712,15 +766,15 @@ void SomfyShade::checkMovement() { if(this->target != 0.0) SomfyRemote::sendCommand(somfy_commands::My); } this->direction = 0; - this->tiltStart = millis(); + this->tiltStart = curTime; this->startTiltPos = this->currentTiltPos; if(this->isAtTarget()) this->commitShadePosition(); } } if(this->tiltDirection > 0) { - if(tilt_first) this->moveStart = millis(); + if(tilt_first) this->moveStart = curTime; int32_t msFrom0 = (int32_t)floor((this->startTiltPos/100) * this->tiltTime); - msFrom0 += (millis() - this->tiltStart); + msFrom0 += (curTime - this->tiltStart); msFrom0 = min((int32_t)this->tiltTime, msFrom0); if(msFrom0 >= this->tiltTime) { this->currentTiltPos = 100.0f; @@ -736,7 +790,7 @@ void SomfyShade::checkMovement() { if(tilt_first) { if(this->currentTiltPos >= 100.0f) { this->currentTiltPos = 100.0f; - this->moveStart = millis(); + this->moveStart = curTime; this->startPos = this->currentPos; this->tiltDirection = 0; } @@ -761,14 +815,14 @@ void SomfyShade::checkMovement() { } } else if(this->tiltDirection < 0) { - if(tilt_first) this->moveStart = millis(); + if(tilt_first) this->moveStart = curTime; if(this->tiltTime == 0) { this->tiltDirection = 0; this->currentTiltPos = 0; } else { int32_t msFrom100 = (int32_t)this->tiltTime - (int32_t)floor((this->startTiltPos/100) * this->tiltTime); - msFrom100 += (millis() - this->tiltStart); + msFrom100 += (curTime - this->tiltStart); msFrom100 = min((int32_t)this->tiltTime, msFrom100); if(msFrom100 >= this->tiltTime) { this->currentTiltPos = 0.0f; @@ -784,7 +838,7 @@ void SomfyShade::checkMovement() { if(tilt_first) { if(this->currentTiltPos <= 0.0f) { this->currentTiltPos = 0.0f; - this->moveStart = millis(); + this->moveStart = curTime; this->startPos = this->currentPos; this->tiltDirection = 0; } @@ -1068,10 +1122,11 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { } } if(!hasRemote) return; + const uint64_t curTime = millis(); this->lastFrame.copy(frame); int8_t dir = 0; int8_t tiltDir = 0; - this->moveStart = this->tiltStart = millis(); + this->moveStart = this->tiltStart = curTime; this->startPos = this->currentPos; this->startTiltPos = this->currentTiltPos; // If the command is coming from a remote then we are aborting all these positioning operations. @@ -1081,29 +1136,81 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { // will need to see what the shade does when you press both. switch(frame.cmd) { case somfy_commands::Sensor: - if ((frame.rollingCode << 4) & static_cast(somfy_flags_t::Sunny)) - this->flags |= static_cast(somfy_flags_t::Sunny); - else - this->flags &= ~(static_cast(somfy_flags_t::Sunny)); - - if ((frame.rollingCode << 4) & static_cast(somfy_flags_t::Windy)) - this->flags |= static_cast(somfy_flags_t::Windy); - else - this->flags &= ~(static_cast(somfy_flags_t::Windy)); - - if (this->flags & static_cast(somfy_flags_t::Windy)) { - this->target = 0.0f; - } - else if (this->flags & static_cast(somfy_flags_t::SunFlag)) - { - if (this->flags & static_cast(somfy_flags_t::Sunny)) - this->target = 100.0f; + const uint8_t prevFlags = this->flags; + const bool wasSunny = prevFlags & static_cast(somfy_flags_t::Sunny); + const bool wasWindy = prevFlags & static_cast(somfy_flags_t::Windy); + const uint16_t status = frame.rollingCode << 4; + + if (status & static_cast(somfy_flags_t::Sunny)) + this->flags |= static_cast(somfy_flags_t::Sunny); else - this->target = 0.0f; - } + this->flags &= ~(static_cast(somfy_flags_t::Sunny)); - this->emitState(); + if (status & static_cast(somfy_flags_t::Windy)) + this->flags |= static_cast(somfy_flags_t::Windy); + else + this->flags &= ~(static_cast(somfy_flags_t::Windy)); + + const bool isSunny = this->flags & static_cast(somfy_flags_t::Sunny); + const bool isWindy = this->flags & static_cast(somfy_flags_t::Windy); + + if (isSunny) + { + this->noSunStart = 0; + this->noSunDone = true; + } + else + { + this->sunStart = 0; + this->sunDone = true; + } + + if (isWindy) + { + this->noWindStart = 0; + this->noWindDone = true; + + this->windLast = curTime; + } + else + { + this->windStart = 0; + this->windDone = true; + } + + if (isSunny && !wasSunny) + { + this->sunStart = curTime; + this->sunDone = false; + + Serial.printf("[%u] Sun -> start\r\n", this->shadeId); + } + else if (!isSunny && wasSunny) + { + this->noSunStart = curTime; + this->noSunDone = false; + + Serial.printf("[%u] No Sun -> start\r\n", this->shadeId); + } + + if (isWindy && !wasWindy) + { + this->windStart = curTime; + this->windDone = false; + + Serial.printf("[%u] Wind -> start\r\n", this->shadeId); + } + else if (!isWindy && wasWindy) + { + this->noWindStart = curTime; + this->noWindDone = false; + + Serial.printf("[%u] No Wind -> start\r\n", this->shadeId); + } + + this->emitState(); + } break; case somfy_commands::Flag: @@ -1111,22 +1218,28 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { this->emitState(); break; case somfy_commands::SunFlag: - this->flags |= static_cast(somfy_flags_t::SunFlag); - - if (!(this->flags & static_cast(somfy_flags_t::Windy))) { - if (this->flags & static_cast(somfy_flags_t::Sunny)) - this->target = 100.0f; - else - this->target = 0.0f; - } + const bool isWindy = this->flags & static_cast(somfy_flags_t::Windy); - this->emitState(); + this->flags |= static_cast(somfy_flags_t::SunFlag); + + if (!isWindy) + { + const bool isSunny = this->flags & static_cast(somfy_flags_t::Sunny); + + if (isSunny && this->sunDone) + this->target = 100.0f; + else if (!isSunny && this->noSunDone) + this->target = 0.0f; + } + + this->emitState(); + } break; case somfy_commands::Up: if(this->tiltType == tilt_types::tiltmotor) { // Wait another half second just in case we are potentially processing a tilt. - if(!internal) this->lastFrame.await = millis() + 500; + if(!internal) this->lastFrame.await = curTime + 500; else this->lastFrame.processed = true; } else { @@ -1136,16 +1249,18 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { } break; case somfy_commands::Down: - if(this->tiltType == tilt_types::tiltmotor) { - // Wait another half seccond just in case we are potentially processing a tilt. - if(!internal) this->lastFrame.await = millis() + 500; - else this->lastFrame.processed = true; - } - else { - this->lastFrame.processed = true; - if(!internal) { - this->target = 100.0f; - if(this->tiltType != tilt_types::none) this->tiltTarget = 100.0f; + if (!this->windLast || (curTime - this->windLast) >= SOMFY_NO_WIND_REMOTE_TIMEOUT) { + if(this->tiltType == tilt_types::tiltmotor) { + // Wait another half seccond just in case we are potentially processing a tilt. + if(!internal) this->lastFrame.await = curTime + 500; + else this->lastFrame.processed = true; + } + else { + this->lastFrame.processed = true; + if(!internal) { + this->target = 100.0f; + if(this->tiltType != tilt_types::none) this->tiltTarget = 100.0f; + } } } break; @@ -1154,7 +1269,7 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { if(!internal) { // This frame is coming from a remote. We are potentially setting // the my position. - this->lastFrame.await = millis() + 500; + this->lastFrame.await = curTime + 500; } else { this->lastFrame.processed = true; diff --git a/Somfy.h b/Somfy.h index 54663c5..287dde3 100644 --- a/Somfy.h +++ b/Somfy.h @@ -4,6 +4,16 @@ #define SOMFY_MAX_SHADES 32 #define SOMFY_MAX_LINKED_REMOTES 7 +#define SECS_TO_MILLIS(x) ((x) * 1000) +#define MINS_TO_MILLIS(x) SECS_TO_MILLIS((x) * 60) + +#define SOMFY_SUN_TIMEOUT MINS_TO_MILLIS(2) +#define SOMFY_NO_SUN_TIMEOUT MINS_TO_MILLIS(15) + +#define SOMFY_WIND_TIMEOUT SECS_TO_MILLIS(2) +#define SOMFY_NO_WIND_TIMEOUT MINS_TO_MILLIS(12) +#define SOMFY_NO_WIND_REMOTE_TIMEOUT SECS_TO_MILLIS(30) + struct appver_t { uint8_t major; uint8_t minor; @@ -152,6 +162,15 @@ class SomfyShade : public SomfyRemote { uint8_t shadeId = 255; uint64_t moveStart = 0; uint64_t tiltStart = 0; + uint64_t noSunStart = 0; + uint64_t sunStart = 0; + uint64_t windStart = 0; + uint64_t windLast = 0; + uint64_t noWindStart = 0; + bool noSunDone = true; + bool sunDone = true; + bool windDone = true; + bool noWindDone = true; float startPos = 0.0f; float startTiltPos = 0.0f; bool settingMyPos = false;