ShadeBehaviour.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "esp_log.h" | ||
| 2 | #include "ShadeBehaviour.h" | ||
| 3 | |||
| 4 | static const char *TAG = "Somfy"; | ||
| 5 | |||
| 6 | #define SETMY_REPEATS 35 | ||
| 7 | #define TILT_REPEATS 15 | ||
| 8 | |||
| 9 | // How long a shade's simulated movement waits for its command to go on air | ||
| 10 | // before it starts anyway in case the event was lost. | ||
| 11 | #define TX_PENDING_TIMEOUT 30000 | ||
| 12 | |||
| 13 | 55 | ShadeBehaviour::ShadeBehaviour(const ShadeSettings &settings, const RemoteSettings &remote, somfy_frame_t &lastFrame, | |
| 14 | 55 | MotorLink &link, ShadeEvents &events, const uint8_t &shadeId, const char *name) | |
| 15 | 55 | : settings(settings), remote(remote), lastFrame(lastFrame), link(link), events(events), shadeId(shadeId), name(name) {} | |
| 16 | ✗ | void ShadeBehaviour::clear() { | |
| 17 | ✗ | this->moveStart = 0; | |
| 18 | ✗ | this->tiltStart = 0; | |
| 19 | ✗ | this->sensors.clear(); | |
| 20 | ✗ | this->startPos = 0.0f; | |
| 21 | ✗ | this->startTiltPos = 0.0f; | |
| 22 | ✗ | this->settingMyPos = false; | |
| 23 | ✗ | this->settingPos = false; | |
| 24 | ✗ | this->settingTiltPos = false; | |
| 25 | ✗ | this->awaitMy = 0; | |
| 26 | ✗ | this->s.currentPos = 0.0f; | |
| 27 | ✗ | this->s.currentTiltPos = 0.0f; | |
| 28 | ✗ | this->s.direction = 0; | |
| 29 | ✗ | this->s.tiltDirection = 0; | |
| 30 | ✗ | this->s.target = 0.0f; | |
| 31 | ✗ | this->s.tiltTarget = 0.0f; | |
| 32 | ✗ | this->s.myPos = -1.0f; | |
| 33 | ✗ | this->s.myTiltPos = -1.0f; | |
| 34 | ✗ | } | |
| 35 | 19 | void ShadeBehaviour::setPosition(float pos, float tilt) { | |
| 36 | // Through the setters, so MQTT hears about it too. | ||
| 37 | 19 | if(pos >= 0.0f) { | |
| 38 | 19 | this->p_target(pos); | |
| 39 | 19 | this->p_currentPos(pos); | |
| 40 | } | ||
| 41 | 19 | if(tilt >= 0.0f) { | |
| 42 | ✗ | this->p_tiltTarget(tilt); | |
| 43 | ✗ | this->p_currentTiltPos(tilt); | |
| 44 | } | ||
| 45 | 19 | } | |
| 46 | 32 | void ShadeBehaviour::restore(float pos, float tiltPos, float myPos, float myTiltPos) { | |
| 47 | 32 | this->s.currentPos = pos; | |
| 48 | 32 | this->s.currentTiltPos = tiltPos; | |
| 49 | 32 | this->s.myPos = myPos; | |
| 50 | 32 | this->s.myTiltPos = myTiltPos; | |
| 51 | 32 | this->s.target = floor(pos); | |
| 52 | 32 | this->s.tiltTarget = floor(tiltPos); | |
| 53 | 32 | } | |
| 54 | 1 | void ShadeBehaviour::clearTilt() { | |
| 55 | 1 | this->s.myTiltPos = -1; | |
| 56 | 1 | this->s.currentTiltPos = 0; | |
| 57 | 1 | } | |
| 58 | 139 | somfy_commands ShadeBehaviour::transformCommand(somfy_commands cmd) const { return this->remote.flipCommands ? flipCommand(cmd) : cmd; } | |
| 59 | 41 | send_result_t ShadeBehaviour::transmit(somfy_commands cmd, uint8_t repeat, uint8_t stepSize, uint16_t delayMs, bool priority, uint32_t now) { | |
| 60 | 41 | const send_result_t result = this->link.send(cmd, repeat, stepSize, delayMs, priority); | |
| 61 | 41 | if(!result.sent) return result; | |
| 62 | // The link has put the frame in lastFrame and every other shade has heard | ||
| 63 | // it. The shade hears it too, as if a remote had sent it. | ||
| 64 | 39 | this->onFrame(this->lastFrame, true, now); | |
| 65 | // An idle shade this sets moving waits for the frame to go on air. Stops | ||
| 66 | // are sent ahead of everything else so they don't need to. | ||
| 67 | 39 | if(result.ticket != 0 && !priority && startsMovement(this->lastFrame.cmd)) this->onTxQueued(result.ticket, now); | |
| 68 | 39 | return result; | |
| 69 | } | ||
| 70 | 495 | bool ShadeBehaviour::isAtTarget() const { | |
| 71 | 495 | float epsilon = .00001; | |
| 72 | 495 | if(this->settings.tiltType == tilt_types::tiltonly) return fabs(this->s.currentTiltPos - this->s.tiltTarget) < epsilon; | |
| 73 | 484 | else if(this->settings.tiltType == tilt_types::none) return fabs(this->s.currentPos - this->s.target) < epsilon; | |
| 74 | 9 | return fabs(this->s.currentPos - this->s.target) < epsilon && fabs(this->s.currentTiltPos - this->s.tiltTarget) < epsilon; | |
| 75 | } | ||
| 76 | 16350 | void ShadeBehaviour::tick(uint32_t now) { | |
| 77 | 16350 | const uint32_t curTime = now; | |
| 78 | 16350 | const bool sunFlag = this->s.stateFlags & static_cast<uint8_t>(somfy_flags_t::SunFlag); | |
| 79 | 16350 | const bool isSunny = this->s.stateFlags & static_cast<uint8_t>(somfy_flags_t::Sunny); | |
| 80 | 16350 | const bool isWindy = this->s.stateFlags & static_cast<uint8_t>(somfy_flags_t::Windy); | |
| 81 | // We need to first evaluate the sensor flags as these could be triggering movement from previous sensor inputs. So | ||
| 82 | // we must check this before setting the directional items or it will not get processed until the next loop. | ||
| 83 | 16350 | int32_t downTime = (int32_t)this->settings.downTime; | |
| 84 | 16350 | int32_t upTime = (int32_t)this->settings.upTime; | |
| 85 | 16350 | int32_t tiltTime = (int32_t)this->settings.tiltTime; | |
| 86 | 16350 | if(this->settings.shadeType == shade_types::drycontact || this->settings.shadeType == shade_types::drycontact2) downTime = upTime = tiltTime = 1; | |
| 87 | |||
| 88 | |||
| 89 | // We are checking movement for essentially 3 types of motors. | ||
| 90 | // If this is an integrated tilt we need to first tilt in the direction we are moving then move. We know | ||
| 91 | // what needs to be done by the tilt type. Set a tilt first flag to indicate whether we should be tilting or | ||
| 92 | // moving. If this is only a tilt action then the regular tilt action should operate fine. | ||
| 93 | 16350 | int8_t currDir = this->s.direction; | |
| 94 | 16350 | int8_t currTiltDir = this->s.tiltDirection; | |
| 95 | 16350 | this->p_direction(this->s.currentPos == this->s.target ? 0 : this->s.currentPos > this->s.target ? -1 : 1); | |
| 96 | 16350 | bool tilt_first = this->settings.tiltType == tilt_types::integrated && ((this->s.direction == -1 && this->s.currentTiltPos != 0.0f) || (this->s.direction == 1 && this->s.currentTiltPos != 100.0f)); | |
| 97 | |||
| 98 | 16350 | this->p_tiltDirection(this->s.currentTiltPos == this->s.tiltTarget ? 0 : this->s.currentTiltPos > this->s.tiltTarget ? -1 : 1); | |
| 99 | 16350 | if(tilt_first) this->p_tiltDirection(this->s.direction); | |
| 100 | 15950 | else if(this->s.direction != 0) this->p_tiltDirection(0); | |
| 101 | 16350 | uint8_t currPos = floor(this->s.currentPos); | |
| 102 | 16350 | uint8_t currTiltPos = floor(this->s.currentTiltPos); | |
| 103 | 16350 | if(this->s.direction != 0) this->s.lastMovement = this->s.direction; | |
| 104 | 16350 | switch(this->tx.hold.check(this->s.direction != 0 || this->s.tiltDirection != 0, (uint32_t)curTime, TX_PENDING_TIMEOUT)) { | |
| 105 | 3199 | case TxHoldCheck::holding: | |
| 106 | // The command that set us moving hasn't gone on air yet so the motor | ||
| 107 | // hasn't started. Hold the simulated position until it does. | ||
| 108 | 3199 | this->moveStart = this->tiltStart = curTime; | |
| 109 | 3199 | if(currDir != this->s.direction || currTiltDir != this->s.tiltDirection) this->emitState(); | |
| 110 | 3199 | return; | |
| 111 | 1 | case TxHoldCheck::expired: | |
| 112 | ESP_LOGW(TAG, "[Shade %u] Timed out waiting for the radio, moving anyway", this->shadeId); | ||
| 113 | 1 | break; | |
| 114 | 13150 | case TxHoldCheck::none: | |
| 115 | 13150 | break; | |
| 116 | } | ||
| 117 | 13151 | this->applySensorMove(this->sensors.check(sunFlag, isSunny, isWindy, (uint32_t)curTime)); | |
| 118 | |||
| 119 | 13151 | if(!tilt_first && this->s.direction > 0) { | |
| 120 | 8551 | this->p_currentPos(travelPosition(this->startPos, 1, (uint32_t)(curTime - this->moveStart), downTime)); | |
| 121 | 8551 | if(this->s.currentPos >= this->s.target) { | |
| 122 | 15 | this->p_currentPos(this->s.target); | |
| 123 | //if(this->settingMyPos) Serial.printf("IsAtTarget: %d %f=%f\n", this->isAtTarget(), this->s.currentPos, this->s.target); | ||
| 124 | // If we need to stop the shade do this before we indicate that we are | ||
| 125 | // not moving otherwise the my function will kick in. | ||
| 126 | 15 | if(this->settingPos) { | |
| 127 | // At the end limit the motor stops by itself. If the stop doesn't go | ||
| 128 | // out the shade is now running on to the limit so leave the tilt be. | ||
| 129 | 8 | if(this->s.target == 100.0 || this->sendStop(1, false, now)) { | |
| 130 | 7 | if(!isAtTarget()) { | |
| 131 | ESP_LOGD(TAG, "We are not at our tilt target: %.2f", this->s.tiltTarget); | ||
| 132 | // We now need to move the tilt to the position we requested. | ||
| 133 | 1 | this->moveToTiltTarget(this->s.tiltTarget, now); | |
| 134 | } | ||
| 135 | } | ||
| 136 | } | ||
| 137 | 15 | this->p_direction(0); | |
| 138 | 15 | this->settingPos = false; | |
| 139 | 15 | this->tiltStart = curTime; | |
| 140 | 15 | this->startTiltPos = this->s.currentTiltPos; | |
| 141 | 15 | if(this->isAtTarget()) this->commitShadePosition(); | |
| 142 | } | ||
| 143 | } | ||
| 144 | 4600 | else if(!tilt_first && this->s.direction < 0) { | |
| 145 | // Shades often move slower up so this uses its own travel time. | ||
| 146 | 20 | this->p_currentPos(travelPosition(this->startPos, -1, (uint32_t)(curTime - this->moveStart), upTime)); | |
| 147 | 20 | if(this->s.currentPos <= this->s.target) { | |
| 148 | ✗ | this->p_currentPos(this->s.target); | |
| 149 | //if(this->settingMyPos) Serial.printf("IsAtTarget: %d %f=%f\n", this->isAtTarget(), this->s.currentPos, this->s.target); | ||
| 150 | |||
| 151 | // If we need to stop the shade do this before we indicate that we are | ||
| 152 | // not moving otherwise the my function will kick in. | ||
| 153 | ✗ | if(this->settingPos) { | |
| 154 | // At the end limit the motor stops by itself. If the stop doesn't go | ||
| 155 | // out the shade is now running on to the limit so leave the tilt be. | ||
| 156 | ✗ | if(this->s.target == 0.0 || this->sendStop(-1, false, now)) { | |
| 157 | ✗ | if(!isAtTarget()) { | |
| 158 | ESP_LOGD(TAG, "We are not at our tilt target: %.2f", this->s.tiltTarget); | ||
| 159 | // We now need to move the tilt to the position we requested. | ||
| 160 | ✗ | this->moveToTiltTarget(this->s.tiltTarget, now); | |
| 161 | } | ||
| 162 | } | ||
| 163 | } | ||
| 164 | ✗ | this->p_direction(0); | |
| 165 | ✗ | this->settingPos = false; | |
| 166 | ✗ | this->tiltStart = curTime; | |
| 167 | ✗ | this->startTiltPos = this->s.currentTiltPos; | |
| 168 | ✗ | if(this->isAtTarget()) this->commitShadePosition(); | |
| 169 | } | ||
| 170 | } | ||
| 171 | 13151 | if(this->s.tiltDirection > 0) { | |
| 172 | 1570 | if(tilt_first) this->moveStart = curTime; | |
| 173 | 1570 | this->p_currentTiltPos(travelPosition(this->startTiltPos, 1, (uint32_t)(curTime - this->tiltStart), tiltTime)); | |
| 174 | 1570 | if(tilt_first) { | |
| 175 | 400 | if(this->s.currentTiltPos >= 100.0f) { | |
| 176 | 2 | this->p_currentTiltPos(100.0f); | |
| 177 | 2 | this->moveStart = curTime; | |
| 178 | 2 | this->startPos = this->s.currentPos; | |
| 179 | //this->p_tiltDirection(0); | ||
| 180 | //Serial.println("Setting tiltDirection to 0 (tilt_first)"); | ||
| 181 | } | ||
| 182 | } | ||
| 183 | 1170 | else if(this->s.currentTiltPos >= this->s.tiltTarget) { | |
| 184 | ✗ | this->p_currentTiltPos(this->s.tiltTarget); | |
| 185 | // If we need to stop the shade do this before we indicate that we are | ||
| 186 | // not moving otherwise the my function will kick in. | ||
| 187 | ✗ | if(this->settingTiltPos) { | |
| 188 | ✗ | if(this->settings.tiltType == tilt_types::integrated) { | |
| 189 | // If this is an integrated tilt mechanism the we will simply let it finish. If it is not then we will stop it. | ||
| 190 | //Serial.printf("Sending My -- tiltTarget: %.2f, tiltDirection: %d\n", this->s.tiltTarget, this->s.tiltDirection); | ||
| 191 | ✗ | if(this->s.tiltTarget != 100.0f || this->s.currentPos != 100.0f) this->sendStop(1, true, now); | |
| 192 | } | ||
| 193 | else { | ||
| 194 | // This is a tilt motor so let it complete if it is going to 100. | ||
| 195 | ✗ | if(this->s.tiltTarget != 100.0f) this->sendStop(1, true, now); | |
| 196 | } | ||
| 197 | } | ||
| 198 | ✗ | this->p_tiltDirection(0); | |
| 199 | ✗ | this->settingTiltPos = false; | |
| 200 | ✗ | if(this->isAtTarget()) this->commitShadePosition(); | |
| 201 | } | ||
| 202 | } | ||
| 203 | 11581 | else if(this->s.tiltDirection < 0) { | |
| 204 | 1221 | if(tilt_first) this->moveStart = curTime; | |
| 205 | 1221 | if(tiltTime == 0) this->p_tiltDirection(0); | |
| 206 | 1221 | this->p_currentTiltPos(travelPosition(this->startTiltPos, -1, (uint32_t)(curTime - this->tiltStart), tiltTime)); | |
| 207 | 1221 | if(tilt_first) { | |
| 208 | ✗ | if(this->s.currentTiltPos <= 0.0f) { | |
| 209 | ✗ | this->p_currentTiltPos(0.0f); | |
| 210 | ✗ | this->moveStart = curTime; | |
| 211 | ✗ | this->startPos = this->s.currentPos; | |
| 212 | //this->p_tiltDirection(0); | ||
| 213 | } | ||
| 214 | } | ||
| 215 | 1221 | else if(this->s.currentTiltPos <= this->s.tiltTarget) { | |
| 216 | 2 | this->p_currentTiltPos(this->s.tiltTarget); | |
| 217 | // If we need to stop the shade do this before we indicate that we are | ||
| 218 | // not moving otherwise the my function will kick in. | ||
| 219 | 2 | if(this->settingTiltPos) { | |
| 220 | 1 | if(this->settings.tiltType == tilt_types::integrated) { | |
| 221 | // If this is an integrated tilt mechanism the we will simply let it finish. If it is not then we will stop it. | ||
| 222 | //Serial.printf("Sending My -- tiltTarget: %.2f, tiltDirection: %d\n", this->s.tiltTarget, this->s.tiltDirection); | ||
| 223 | 1 | if(this->s.tiltTarget != 0.0 || this->s.currentPos != 0.0) this->sendStop(-1, true, now); | |
| 224 | } | ||
| 225 | else { | ||
| 226 | // This is a tilt motor so let it complete if it is going to 0. | ||
| 227 | ✗ | if(this->s.tiltTarget != 0.0) this->sendStop(-1, true, now); | |
| 228 | } | ||
| 229 | } | ||
| 230 | 2 | this->p_tiltDirection(0); | |
| 231 | 2 | this->settingTiltPos = false; | |
| 232 | ESP_LOGI(TAG, "Stopping at tilt position"); | ||
| 233 | 2 | if(this->isAtTarget()) this->commitShadePosition(); | |
| 234 | } | ||
| 235 | } | ||
| 236 | 13151 | if(this->settingMyPos && this->isAtTarget()) { | |
| 237 | // Set this position before sending the command. If you don't the processFrame function | ||
| 238 | // will send the shade back to its original My position. | ||
| 239 | 1 | const float oldMyPos = this->s.myPos; | |
| 240 | 1 | const float oldMyTiltPos = this->s.myTiltPos; | |
| 241 | 1 | if(this->settings.tiltType != tilt_types::none) { | |
| 242 | ✗ | if(this->s.myTiltPos == this->s.currentTiltPos && this->s.myPos == this->s.currentPos) this->s.myPos = this->s.myTiltPos = -1; | |
| 243 | else { | ||
| 244 | ✗ | this->p_myPos(this->s.currentPos); | |
| 245 | ✗ | this->p_myTiltPos(this->s.currentTiltPos); | |
| 246 | } | ||
| 247 | } | ||
| 248 | else { | ||
| 249 | 1 | this->p_myTiltPos(-1); | |
| 250 | 1 | if(this->s.myPos == this->s.currentPos) this->p_myPos(-1); | |
| 251 | 1 | else this->p_myPos(this->s.currentPos); | |
| 252 | } | ||
| 253 | // Give the motor time to settle at the target before it is told to store it. | ||
| 254 | 1 | this->sendSetMy(200, oldMyPos, oldMyTiltPos, now); | |
| 255 | 1 | this->settingMyPos = false; | |
| 256 | 1 | this->emitState(); | |
| 257 | } | ||
| 258 | 13150 | else if(currDir != this->s.direction || currPos != floor(this->s.currentPos) || currTiltDir != this->s.tiltDirection || currTiltPos != floor(this->s.currentTiltPos)) { | |
| 259 | // We need to emit on the socket that our state has changed. | ||
| 260 | 1518 | this->emitState(); | |
| 261 | } | ||
| 262 | } | ||
| 263 | |||
| 264 | |||
| 265 | // State Setters. Each marks what clients see as changed when it does change; | ||
| 266 | // the change fan-out publishes it once per loop() pass. | ||
| 267 | 8638 | float ShadeBehaviour::p_currentPos(float pos) { | |
| 268 | 8638 | float old = this->s.currentPos; | |
| 269 | 8638 | this->s.currentPos = pos; | |
| 270 | 8638 | if(floor(old) != floor(pos)) this->events.onChanged(SHADE_CHANGED_POSITION); | |
| 271 | 8638 | return old; | |
| 272 | } | ||
| 273 | 2795 | float ShadeBehaviour::p_currentTiltPos(float pos) { | |
| 274 | 2795 | float old = this->s.currentTiltPos; | |
| 275 | 2795 | this->s.currentTiltPos = pos; | |
| 276 | 2795 | if(floor(old) != floor(pos)) this->events.onChanged(SHADE_CHANGED_TILT_POSITION); | |
| 277 | 2795 | return old; | |
| 278 | } | ||
| 279 | 8 | bool ShadeBehaviour::p_flag(somfy_flags_t flag, bool val) { | |
| 280 | 8 | bool old = !!(this->s.stateFlags & static_cast<uint8_t>(flag)); | |
| 281 | 8 | if(val) | |
| 282 | 4 | this->s.stateFlags |= static_cast<uint8_t>(flag); | |
| 283 | else | ||
| 284 | 4 | this->s.stateFlags &= ~(static_cast<uint8_t>(flag)); | |
| 285 | 8 | return old; | |
| 286 | } | ||
| 287 | 4 | bool ShadeBehaviour::p_sunFlag(bool val) { | |
| 288 | 4 | bool old = this->p_flag(somfy_flags_t::SunFlag, val); | |
| 289 | 4 | if(old != val) this->events.onChanged(SHADE_CHANGED_SUN_FLAG); | |
| 290 | 4 | return old; | |
| 291 | } | ||
| 292 | 2 | bool ShadeBehaviour::p_windy(bool val) { | |
| 293 | 2 | bool old = this->p_flag(somfy_flags_t::Windy, val); | |
| 294 | 2 | if(old != val) this->events.onChanged(SHADE_CHANGED_WINDY); | |
| 295 | 2 | return old; | |
| 296 | } | ||
| 297 | 2 | bool ShadeBehaviour::p_sunny(bool val) { | |
| 298 | 2 | bool old = this->p_flag(somfy_flags_t::Sunny, val); | |
| 299 | 2 | if(old != val) this->events.onChanged(SHADE_CHANGED_SUNNY); | |
| 300 | 2 | return old; | |
| 301 | } | ||
| 302 | 16365 | int8_t ShadeBehaviour::p_direction(int8_t dir) { | |
| 303 | 16365 | int8_t old = this->s.direction; | |
| 304 | 16365 | if(old != dir) { | |
| 305 | 43 | this->s.direction = dir; | |
| 306 | 43 | this->events.onChanged(SHADE_CHANGED_DIRECTION); | |
| 307 | } | ||
| 308 | 16365 | return old; | |
| 309 | } | ||
| 310 | 28523 | int8_t ShadeBehaviour::p_tiltDirection(int8_t dir) { | |
| 311 | 28523 | int8_t old = this->s.tiltDirection; | |
| 312 | 28523 | if(old != dir) { | |
| 313 | 1229 | this->s.tiltDirection = dir; | |
| 314 | 1229 | this->events.onChanged(SHADE_CHANGED_TILT_DIRECTION); | |
| 315 | } | ||
| 316 | 28523 | return old; | |
| 317 | } | ||
| 318 | 97 | float ShadeBehaviour::p_target(float target) { | |
| 319 | 97 | float old = this->s.target; | |
| 320 | 97 | if(old != target) { | |
| 321 | 74 | this->s.target = target; | |
| 322 | 74 | if(this->transformPosition(old) != this->transformPosition(target)) | |
| 323 | 74 | this->events.onChanged(SHADE_CHANGED_TARGET); | |
| 324 | } | ||
| 325 | 97 | return old; | |
| 326 | } | ||
| 327 | 36 | float ShadeBehaviour::p_tiltTarget(float target) { | |
| 328 | 36 | float old = this->s.tiltTarget; | |
| 329 | 36 | if(old != target) { | |
| 330 | 26 | this->s.tiltTarget = target; | |
| 331 | 26 | if(this->transformPosition(old) != this->transformPosition(target)) | |
| 332 | 26 | this->events.onChanged(SHADE_CHANGED_TILT_TARGET); | |
| 333 | } | ||
| 334 | 36 | return old; | |
| 335 | } | ||
| 336 | 6 | float ShadeBehaviour::p_myPos(float pos) { | |
| 337 | 6 | float old = this->s.myPos; | |
| 338 | 6 | if(old != pos) { | |
| 339 | 5 | this->s.myPos = pos; | |
| 340 | 5 | if(this->transformPosition(old) != this->transformPosition(pos)) | |
| 341 | 5 | this->events.onChanged(SHADE_CHANGED_MY_POS); | |
| 342 | } | ||
| 343 | 6 | return old; | |
| 344 | } | ||
| 345 | 5 | float ShadeBehaviour::p_myTiltPos(float pos) { | |
| 346 | 5 | float old = this->s.myTiltPos; | |
| 347 | 5 | if(old != pos) { | |
| 348 | 1 | this->s.myTiltPos = pos; | |
| 349 | 1 | if(this->transformPosition(old) != this->transformPosition(pos)) | |
| 350 | 1 | this->events.onChanged(SHADE_CHANGED_MY_TILT_POS); | |
| 351 | } | ||
| 352 | 5 | return old; | |
| 353 | } | ||
| 354 | 71 | bool ShadeBehaviour::isIdle() const { | |
| 355 | 71 | return this->isAtTarget() && this->s.direction == 0 && this->s.tiltDirection == 0; | |
| 356 | } | ||
| 357 | 760 | void ShadeBehaviour::onWaitingFrame(uint32_t now) { | |
| 358 | 760 | if(this->shadeId == 255) { | |
| 359 | ✗ | this->lastFrame.await = 0; | |
| 360 | ✗ | return; | |
| 361 | } | ||
| 362 | 760 | if(this->lastFrame.processed) return; | |
| 363 | // Wrap-safe: await is now + 500 when the press began. | ||
| 364 | 306 | if(this->lastFrame.await > 0 && static_cast<int32_t>(now - this->lastFrame.await) > 0) { | |
| 365 | 6 | somfy_commands cmd = this->transformCommand(this->lastFrame.cmd); | |
| 366 | 6 | switch(cmd) { | |
| 367 | // A step acts as soon as it arrives (onFrame), so it is never waiting here. | ||
| 368 | 2 | case somfy_commands::Down: | |
| 369 | case somfy_commands::Up: | ||
| 370 | 2 | if(this->settings.tiltType == tilt_types::tiltmotor) { // Theoretically this should get here unless it does have a tilt motor. | |
| 371 | 2 | if(this->lastFrame.repeats >= TILT_REPEATS) { | |
| 372 | 1 | int8_t dir = this->lastFrame.cmd == somfy_commands::Up ? -1 : 1; | |
| 373 | 1 | this->p_tiltTarget(dir > 0 ? 100.0f : 0.0f); | |
| 374 | 1 | this->setTiltMovement(dir, now); | |
| 375 | 1 | this->lastFrame.processed = true; | |
| 376 | ESP_LOGD(TAG, "%s Processing tilt %s after %d repeats", | ||
| 377 | this->name, translateSomfyCommand(this->lastFrame.cmd).c_str(), this->lastFrame.repeats); | ||
| 378 | 1 | this->emitCommand(cmd, "remote", this->lastFrame.remoteAddress); | |
| 379 | } | ||
| 380 | else { | ||
| 381 | 1 | int8_t dir = this->lastFrame.cmd == somfy_commands::Up ? -1 : 1; | |
| 382 | 1 | this->p_target(dir > 0 ? 100 : 0); | |
| 383 | 1 | this->setMovement(dir, now); | |
| 384 | 1 | this->lastFrame.processed = true; | |
| 385 | 1 | this->emitCommand(cmd, "remote", this->lastFrame.remoteAddress); | |
| 386 | } | ||
| 387 | 2 | if(this->lastFrame.repeats > TILT_REPEATS + 2) { | |
| 388 | ✗ | this->lastFrame.processed = true; | |
| 389 | ✗ | this->emitCommand(cmd, "remote", this->lastFrame.remoteAddress); | |
| 390 | } | ||
| 391 | } | ||
| 392 | ✗ | else if(this->settings.tiltType == tilt_types::euromode) { | |
| 393 | ✗ | if(this->lastFrame.repeats >= TILT_REPEATS) { | |
| 394 | ✗ | int8_t dir = this->lastFrame.cmd == somfy_commands::Up ? -1 : 1; | |
| 395 | ✗ | this->p_target(dir > 0 ? 100.0f : 0.0f); | |
| 396 | ✗ | this->setMovement(dir, now); | |
| 397 | ✗ | this->lastFrame.processed = true; | |
| 398 | ESP_LOGD(TAG, "%s Processing %s after %d repeats", | ||
| 399 | this->name, translateSomfyCommand(this->lastFrame.cmd).c_str(), this->lastFrame.repeats); | ||
| 400 | ✗ | this->emitCommand(cmd, "remote", this->lastFrame.remoteAddress); | |
| 401 | } | ||
| 402 | else { | ||
| 403 | ✗ | int8_t dir = this->lastFrame.cmd == somfy_commands::Up ? -1 : 1; | |
| 404 | ✗ | this->p_tiltTarget(dir > 0 ? 100 : 0); | |
| 405 | ✗ | this->setTiltMovement(dir, now); | |
| 406 | ✗ | this->lastFrame.processed = true; | |
| 407 | ✗ | this->emitCommand(cmd, "remote", this->lastFrame.remoteAddress); | |
| 408 | } | ||
| 409 | ✗ | if(this->lastFrame.repeats > TILT_REPEATS + 2) { | |
| 410 | ✗ | this->lastFrame.processed = true; | |
| 411 | ✗ | this->emitCommand(cmd, "remote", this->lastFrame.remoteAddress); | |
| 412 | } | ||
| 413 | } | ||
| 414 | 2 | break; | |
| 415 | 4 | case somfy_commands::My: | |
| 416 | 4 | if(this->lastFrame.repeats >= SETMY_REPEATS && this->isIdle()) { | |
| 417 | 1 | if(floor(this->s.myPos) == floor(this->s.currentPos)) { | |
| 418 | // We are clearing it. | ||
| 419 | ✗ | this->p_myPos(-1); | |
| 420 | ✗ | this->p_myTiltPos(-1); | |
| 421 | } | ||
| 422 | else { | ||
| 423 | 1 | this->p_myPos(this->s.currentPos); | |
| 424 | 1 | this->p_myTiltPos(this->s.currentTiltPos); | |
| 425 | } | ||
| 426 | 1 | this->commitMyPosition(); | |
| 427 | 1 | this->lastFrame.processed = true; | |
| 428 | 1 | this->emitState(); | |
| 429 | } | ||
| 430 | 3 | else if(this->isIdle()) { | |
| 431 | 3 | if(this->settings.simMy) | |
| 432 | ✗ | this->moveToMyPosition(now); // Call out like this (instead of move to target) so that we don't get some of the goofy tilt only problems. | |
| 433 | else { | ||
| 434 | 3 | if(this->s.myPos >= 0.0f && this->s.myPos <= 100.0f) this->p_target(this->s.myPos); | |
| 435 | 3 | if(this->s.myTiltPos >= 0.0f && this->s.myTiltPos <= 100.0f) this->p_tiltTarget(this->s.myTiltPos); | |
| 436 | 3 | this->pinTiltOnlyLift(); | |
| 437 | } | ||
| 438 | 3 | this->setMovement(0, now); | |
| 439 | 3 | this->lastFrame.processed = true; | |
| 440 | 3 | this->emitCommand(cmd, "remote", this->lastFrame.remoteAddress); | |
| 441 | } | ||
| 442 | else { | ||
| 443 | ✗ | this->p_target(this->s.currentPos); | |
| 444 | ✗ | this->p_tiltTarget(this->s.currentTiltPos); | |
| 445 | ✗ | this->pinTiltOnlyLift(); | |
| 446 | } | ||
| 447 | 4 | if(this->lastFrame.repeats > SETMY_REPEATS + 2) this->lastFrame.processed = true; | |
| 448 | 4 | if(this->lastFrame.processed) { | |
| 449 | ESP_LOGD(TAG, "%s Processing MY after %d repeats", this->name, this->lastFrame.repeats); | ||
| 450 | } | ||
| 451 | 4 | break; | |
| 452 | ✗ | default: | |
| 453 | ✗ | break; | |
| 454 | } | ||
| 455 | } | ||
| 456 | } | ||
| 457 | 133 | void ShadeBehaviour::onFrame(somfy_frame_t &frame, bool internal, uint32_t now) { | |
| 458 | // The reason why we are processing all frames here is so | ||
| 459 | // any linked remotes that may happen to be on the same ESPSomfy RTS | ||
| 460 | // device can trigger the appropriate actions. | ||
| 461 | 133 | if(this->shadeId == 255) return; | |
| 462 | 133 | const uint32_t curTime = now; | |
| 463 | 133 | this->lastFrame.copy(frame); | |
| 464 | 133 | int8_t dir = 0; | |
| 465 | 133 | this->moveStart = this->tiltStart = curTime; | |
| 466 | 133 | this->startPos = this->s.currentPos; | |
| 467 | 133 | this->startTiltPos = this->s.currentTiltPos; | |
| 468 | // If the command is coming from a remote then we are aborting all these positioning operations. | ||
| 469 | 133 | if(!internal) this->settingMyPos = this->settingPos = this->settingTiltPos = false; | |
| 470 | 133 | somfy_commands cmd = this->transformCommand(frame.cmd); | |
| 471 | // At this point we are not processing the combo buttons | ||
| 472 | // will need to see what the shade does when you press both. | ||
| 473 | 133 | switch(cmd) { | |
| 474 | 2 | case somfy_commands::Sensor: | |
| 475 | 2 | this->lastFrame.processed = true; | |
| 476 | 2 | if(this->settings.shadeType == shade_types::drycontact || this->settings.shadeType == shade_types::drycontact2) return; | |
| 477 | { | ||
| 478 | 2 | const uint8_t prevFlags = this->s.stateFlags; | |
| 479 | 2 | const bool wasSunny = prevFlags & static_cast<uint8_t>(somfy_flags_t::Sunny); | |
| 480 | 2 | const bool wasWindy = prevFlags & static_cast<uint8_t>(somfy_flags_t::Windy); | |
| 481 | 2 | const uint16_t status = frame.rollingCode << 4; | |
| 482 | 2 | if (status & static_cast<uint8_t>(somfy_flags_t::Sunny)) | |
| 483 | ✗ | this->p_sunny(true); | |
| 484 | //this->flags |= static_cast<uint8_t>(somfy_flags_t::Sunny); | ||
| 485 | else | ||
| 486 | 2 | this->p_sunny(false); | |
| 487 | //this->flags &= ~(static_cast<uint8_t>(somfy_flags_t::Sunny)); | ||
| 488 | 2 | if (status & static_cast<uint8_t>(somfy_flags_t::Windy)) | |
| 489 | 2 | this->p_windy(true); | |
| 490 | //this->flags |= static_cast<uint8_t>(somfy_flags_t::Windy); | ||
| 491 | else | ||
| 492 | ✗ | this->p_windy(false); | |
| 493 | //this->flags &= ~(static_cast<uint8_t>(somfy_flags_t::Windy)); | ||
| 494 | 2 | if(frame.rollingCode & static_cast<uint8_t>(somfy_flags_t::DemoMode)) | |
| 495 | ✗ | this->s.stateFlags |= static_cast<uint8_t>(somfy_flags_t::DemoMode); | |
| 496 | else | ||
| 497 | 2 | this->s.stateFlags &= ~(static_cast<uint8_t>(somfy_flags_t::DemoMode)); | |
| 498 | 2 | const bool isSunny = this->s.stateFlags & static_cast<uint8_t>(somfy_flags_t::Sunny); | |
| 499 | 2 | const bool isWindy = this->s.stateFlags & static_cast<uint8_t>(somfy_flags_t::Windy); | |
| 500 | 2 | this->sensors.onSensorFrame(wasSunny, wasWindy, isSunny, isWindy, (uint32_t)curTime); | |
| 501 | 2 | this->emitState(); | |
| 502 | 2 | this->events.onSensorFlags(); | |
| 503 | } | ||
| 504 | 2 | break; | |
| 505 | ✗ | case somfy_commands::Prog: | |
| 506 | case somfy_commands::MyUp: | ||
| 507 | case somfy_commands::MyDown: | ||
| 508 | case somfy_commands::MyUpDown: | ||
| 509 | case somfy_commands::UpDown: | ||
| 510 | ✗ | this->lastFrame.processed = true; | |
| 511 | ✗ | if(this->settings.shadeType == shade_types::drycontact || this->settings.shadeType == shade_types::drycontact2) return; | |
| 512 | ✗ | this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); | |
| 513 | ✗ | break; | |
| 514 | |||
| 515 | 1 | case somfy_commands::Flag: | |
| 516 | 1 | this->lastFrame.processed = true; | |
| 517 | 1 | if(this->settings.shadeType == shade_types::drycontact || this->settings.shadeType == shade_types::drycontact2) return; | |
| 518 | 1 | if(this->lastFrame.rollingCode & 0x8000) return; // Some sensors send bogus frames with a rollingCode >= 32768 that cause them to change the state. | |
| 519 | 1 | this->p_sunFlag(false); | |
| 520 | //this->flags &= ~(static_cast<uint8_t>(somfy_flags_t::SunFlag)); | ||
| 521 | 1 | this->events.onSaveWanted(); | |
| 522 | 1 | this->emitState(); | |
| 523 | 1 | this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); | |
| 524 | 1 | this->events.onSensorFlags(); | |
| 525 | 1 | break; | |
| 526 | 1 | case somfy_commands::SunFlag: | |
| 527 | 1 | if(this->settings.shadeType == shade_types::drycontact || this->settings.shadeType == shade_types::drycontact2) return; | |
| 528 | 1 | if(this->lastFrame.rollingCode & 0x8000) return; // Some sensors send bogus frames with a rollingCode >= 32768 that cause them to change the state. | |
| 529 | { | ||
| 530 | 1 | const bool isWindy = this->s.stateFlags & static_cast<uint8_t>(somfy_flags_t::Windy); | |
| 531 | 1 | const bool isSunny = this->s.stateFlags & static_cast<uint8_t>(somfy_flags_t::Sunny); | |
| 532 | //this->flags |= static_cast<uint8_t>(somfy_flags_t::SunFlag); | ||
| 533 | 1 | this->p_sunFlag(true); | |
| 534 | 1 | this->applySensorMove(this->sensors.onSunFlag(isSunny, isWindy)); | |
| 535 | 1 | this->events.onSaveWanted(); | |
| 536 | 1 | this->emitState(); | |
| 537 | 1 | this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); | |
| 538 | 1 | this->events.onSensorFlags(); | |
| 539 | } | ||
| 540 | 1 | break; | |
| 541 | 23 | case somfy_commands::Up: | |
| 542 | 23 | if(this->settings.shadeType == shade_types::drycontact) { | |
| 543 | ✗ | this->lastFrame.processed = true; | |
| 544 | ✗ | return; | |
| 545 | } | ||
| 546 | 23 | else if(this->settings.shadeType == shade_types::drycontact2) { | |
| 547 | ✗ | if(this->lastFrame.processed) return; | |
| 548 | ✗ | this->lastFrame.processed = true; | |
| 549 | ✗ | if(this->s.currentPos != 0.0f) this->p_target(0); | |
| 550 | ✗ | this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); | |
| 551 | ✗ | return; | |
| 552 | } | ||
| 553 | 23 | if(this->settings.tiltType == tilt_types::tiltmotor || this->settings.tiltType == tilt_types::euromode) { | |
| 554 | // Wait another half second just in case we are potentially processing a tilt. | ||
| 555 | 17 | if(!internal) this->lastFrame.await = curTime + 500; | |
| 556 | ✗ | else this->lastFrame.processed = true; | |
| 557 | } | ||
| 558 | else { | ||
| 559 | // If from a remote we will simply be going up. | ||
| 560 | 6 | this->pinTiltOnlyLift(); | |
| 561 | 6 | if(this->settings.tiltType == tilt_types::tiltonly && !internal) this->p_tiltTarget(0.0f); | |
| 562 | 4 | else if(!internal) { | |
| 563 | ✗ | if(this->settings.tiltType != tilt_types::tiltonly) this->p_target(0.0f); | |
| 564 | ✗ | this->p_tiltTarget(0.0f); | |
| 565 | } | ||
| 566 | 6 | this->lastFrame.processed = true; | |
| 567 | 6 | this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); | |
| 568 | } | ||
| 569 | 23 | break; | |
| 570 | 22 | case somfy_commands::Down: | |
| 571 | 22 | if(this->settings.shadeType == shade_types::drycontact) { | |
| 572 | ✗ | this->lastFrame.processed = true; | |
| 573 | ✗ | return; | |
| 574 | } | ||
| 575 | 22 | else if(this->settings.shadeType == shade_types::drycontact2) { | |
| 576 | ✗ | if(this->lastFrame.processed) return; | |
| 577 | ✗ | this->lastFrame.processed = true; | |
| 578 | ✗ | if(this->s.currentPos != 100.0f) this->p_target(100); | |
| 579 | ✗ | this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); | |
| 580 | ✗ | return; | |
| 581 | } | ||
| 582 | 22 | if (!this->sensors.windBlocksDown((uint32_t)curTime)) { | |
| 583 | 21 | if(this->settings.tiltType == tilt_types::tiltmotor || this->settings.tiltType == tilt_types::euromode) { | |
| 584 | // Wait another half seccond just in case we are potentially processing a tilt. | ||
| 585 | ✗ | if(!internal) this->lastFrame.await = curTime + 500; | |
| 586 | ✗ | else this->lastFrame.processed = true; | |
| 587 | } | ||
| 588 | else { | ||
| 589 | 21 | this->lastFrame.processed = true; | |
| 590 | 21 | this->pinTiltOnlyLift(); | |
| 591 | 21 | if(!internal) { | |
| 592 | 4 | if(this->settings.tiltType != tilt_types::tiltonly) this->p_target(100.0f); | |
| 593 | 4 | if(this->settings.tiltType != tilt_types::none) this->p_tiltTarget(100.0f); | |
| 594 | } | ||
| 595 | } | ||
| 596 | 21 | this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); | |
| 597 | } | ||
| 598 | 22 | break; | |
| 599 | 57 | case somfy_commands::My: | |
| 600 | 57 | if(this->settings.shadeType == shade_types::drycontact2) return; | |
| 601 | 57 | if(this->isToggle()) { // This is a one button device | |
| 602 | 2 | if(this->lastFrame.processed) return; | |
| 603 | 2 | this->lastFrame.processed = true; | |
| 604 | 2 | if(!this->isIdle()) this->p_target(this->s.currentPos); | |
| 605 | 2 | else if(this->s.currentPos == 100.0f) this->p_target(0.0f); | |
| 606 | 1 | else if(this->s.currentPos == 0.0f) this->p_target(100.0f); | |
| 607 | ✗ | else this->p_target(this->s.lastMovement == -1 ? 100 : 0); | |
| 608 | 2 | this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); | |
| 609 | 2 | return; | |
| 610 | } | ||
| 611 | 55 | else if(this->settings.shadeType == shade_types::drycontact) { | |
| 612 | // In this case we need to toggle the contact but we only should do this if | ||
| 613 | // this is not a repeat. | ||
| 614 | ✗ | if(this->lastFrame.processed) return; | |
| 615 | ✗ | this->lastFrame.processed = true; | |
| 616 | ✗ | if(this->s.currentPos == 100.0f) this->p_target(0); | |
| 617 | ✗ | else if(this->s.currentPos == 0.0f) this->p_target(100); | |
| 618 | ✗ | else this->p_target(this->s.lastMovement == -1 ? 100 : 0); | |
| 619 | ✗ | this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); | |
| 620 | ✗ | return; | |
| 621 | } | ||
| 622 | 55 | if(this->isIdle()) { | |
| 623 | 44 | if(!internal) { | |
| 624 | // This frame is coming from a remote. We are potentially setting | ||
| 625 | // the my position. | ||
| 626 | 39 | this->lastFrame.await = curTime + 500; | |
| 627 | } | ||
| 628 | else { | ||
| 629 | 5 | if(this->lastFrame.processed) return; | |
| 630 | ESP_LOGI(TAG, "Moving to My target"); | ||
| 631 | 5 | this->lastFrame.processed = true; | |
| 632 | 5 | this->pinTiltOnlyLift(); | |
| 633 | 5 | if(this->s.myTiltPos >= 0.0f && this->s.myTiltPos <= 100.0f) this->p_tiltTarget(this->s.myTiltPos); | |
| 634 | 5 | if(this->s.myPos >= 0.0f && this->s.myPos <= 100.0f && this->settings.tiltType != tilt_types::tiltonly) this->p_target(this->s.myPos); | |
| 635 | 5 | this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); | |
| 636 | } | ||
| 637 | } | ||
| 638 | else { | ||
| 639 | 11 | if(this->lastFrame.processed) return; | |
| 640 | 11 | this->lastFrame.processed = true; | |
| 641 | 11 | this->pinTiltOnlyLift(); | |
| 642 | 11 | if(!internal) { | |
| 643 | 2 | if(this->settings.tiltType != tilt_types::tiltonly) this->p_target(this->s.currentPos); | |
| 644 | 2 | this->p_tiltTarget(this->s.currentTiltPos); | |
| 645 | } | ||
| 646 | 11 | this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); | |
| 647 | } | ||
| 648 | 55 | break; | |
| 649 | ✗ | case somfy_commands::StepUp: | |
| 650 | ✗ | if(this->lastFrame.processed) return; | |
| 651 | ✗ | this->lastFrame.processed = true; | |
| 652 | ✗ | if(this->settings.shadeType == shade_types::drycontact || this->settings.shadeType == shade_types::drycontact2) return; | |
| 653 | ✗ | dir = 0; | |
| 654 | // The number of steps the frame asks for. | ||
| 655 | ✗ | if(!this->applyStep(-1, frame.stepSize == 0 ? 1 : frame.stepSize)) return; | |
| 656 | ✗ | this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); | |
| 657 | ✗ | break; | |
| 658 | 25 | case somfy_commands::StepDown: | |
| 659 | 25 | if(this->lastFrame.processed) return; | |
| 660 | 5 | this->lastFrame.processed = true; | |
| 661 | 5 | if(this->settings.shadeType == shade_types::drycontact || this->settings.shadeType == shade_types::drycontact2) return; | |
| 662 | 5 | dir = 1; | |
| 663 | // The number of steps the frame asks for. | ||
| 664 | 5 | if(!this->applyStep(1, frame.stepSize == 0 ? 1 : frame.stepSize)) return; | |
| 665 | 5 | this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); | |
| 666 | 5 | break; | |
| 667 | 1 | case somfy_commands::Toggle: | |
| 668 | 1 | if(this->lastFrame.processed) return; | |
| 669 | 1 | this->lastFrame.processed = true; | |
| 670 | 1 | if(!this->isIdle()) this->p_target(this->s.currentPos); | |
| 671 | 1 | else if(this->s.currentPos == 100.0f) this->p_target(0); | |
| 672 | 1 | else if(this->s.currentPos == 0.0f) this->p_target(100); | |
| 673 | ✗ | else this->p_target(this->s.lastMovement == -1 ? 100 : 0); | |
| 674 | 1 | this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); | |
| 675 | 1 | break; | |
| 676 | 1 | case somfy_commands::Stop: | |
| 677 | 1 | if(this->lastFrame.processed) return; | |
| 678 | 1 | this->lastFrame.processed = true; | |
| 679 | 1 | this->p_target(this->s.currentPos); | |
| 680 | 1 | this->p_tiltTarget(this->s.currentTiltPos); | |
| 681 | 1 | this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); | |
| 682 | 1 | break; | |
| 683 | ✗ | case somfy_commands::Favorite: | |
| 684 | ✗ | if(this->lastFrame.processed) return; | |
| 685 | ✗ | this->lastFrame.processed = true; | |
| 686 | ✗ | if(this->settings.simMy) { | |
| 687 | ✗ | this->moveToMyPosition(now); | |
| 688 | } | ||
| 689 | else { | ||
| 690 | ✗ | if(this->s.myTiltPos >= 0.0f && this->s.myTiltPos <= 100.0f) this->p_tiltTarget(this->s.myTiltPos); | |
| 691 | ✗ | if(this->s.myPos >= 0.0f && this->s.myPos <= 100.0f && this->settings.tiltType != tilt_types::tiltonly) this->p_target(this->s.myPos); | |
| 692 | ✗ | this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); | |
| 693 | } | ||
| 694 | ✗ | break; | |
| 695 | ✗ | default: | |
| 696 | ✗ | dir = 0; | |
| 697 | ✗ | break; | |
| 698 | } | ||
| 699 | //if(dir == 0 && this->settings.tiltType == tilt_types::tiltmotor && this->s.tiltDirection != 0) this->setTiltMovement(0, now); | ||
| 700 | 111 | this->setMovement(dir, now); | |
| 701 | } | ||
| 702 | 21 | void ShadeBehaviour::onGroupCommand(somfy_commands cmd, uint8_t repeat, uint32_t now) { | |
| 703 | // The reason why we are processing all frames here is so | ||
| 704 | // any linked remotes that may happen to be on the same ESPSomfy RTS | ||
| 705 | // device can trigger the appropriate actions. | ||
| 706 | 21 | if(this->shadeId == 255) return; | |
| 707 | 21 | const uint32_t curTime = now; | |
| 708 | 21 | int8_t dir = 0; | |
| 709 | 21 | this->moveStart = this->tiltStart = curTime; | |
| 710 | 21 | this->startPos = this->s.currentPos; | |
| 711 | 21 | this->startTiltPos = this->s.currentTiltPos; | |
| 712 | // If the command is coming from a remote then we are aborting all these positioning operations. | ||
| 713 | 21 | switch(cmd) { | |
| 714 | 2 | case somfy_commands::Up: | |
| 715 | 2 | if(this->settings.tiltType == tilt_types::tiltmotor) { | |
| 716 | ✗ | if(repeat >= TILT_REPEATS) | |
| 717 | ✗ | this->p_tiltTarget(0.0f); | |
| 718 | else | ||
| 719 | ✗ | this->p_target(0.0f); | |
| 720 | } | ||
| 721 | 2 | else if(this->settings.tiltType == tilt_types::tiltonly) { | |
| 722 | 2 | this->p_target(100.0f); | |
| 723 | 2 | this->p_currentPos(100.0f); | |
| 724 | 2 | this->p_tiltTarget(0.0f); | |
| 725 | } | ||
| 726 | else { | ||
| 727 | ✗ | this->p_target(0.0f); | |
| 728 | ✗ | this->p_tiltTarget(0.0f); | |
| 729 | } | ||
| 730 | 2 | break; | |
| 731 | 12 | case somfy_commands::Down: | |
| 732 | 12 | if (!this->sensors.windBlocksDown((uint32_t)curTime)) { | |
| 733 | 12 | if(this->settings.tiltType == tilt_types::tiltmotor) { | |
| 734 | ✗ | if(repeat >= TILT_REPEATS) | |
| 735 | ✗ | this->p_tiltTarget(100.0f); | |
| 736 | else | ||
| 737 | ✗ | this->p_target(100.0f); | |
| 738 | } | ||
| 739 | 12 | else if(this->settings.tiltType == tilt_types::tiltonly) { | |
| 740 | 11 | this->p_target(100.0f); | |
| 741 | 11 | this->p_currentPos(100.0f); | |
| 742 | 11 | this->p_tiltTarget(100.0f); | |
| 743 | } | ||
| 744 | else { | ||
| 745 | 1 | this->p_target(100.0f); | |
| 746 | 1 | if(this->settings.tiltType != tilt_types::none) this->p_tiltTarget(100.0f); | |
| 747 | } | ||
| 748 | } | ||
| 749 | 12 | break; | |
| 750 | 2 | case somfy_commands::My: | |
| 751 | 2 | if(this->isIdle()) { | |
| 752 | ESP_LOGD(TAG, "Shade #%d is idle", this->shadeId); | ||
| 753 | 1 | if(this->settings.simMy) { | |
| 754 | ✗ | this->moveToMyPosition(now); | |
| 755 | } | ||
| 756 | else { | ||
| 757 | 1 | if(this->s.myTiltPos >= 0.0f && this->s.myTiltPos <= 100.0f) this->p_tiltTarget(this->s.myTiltPos); | |
| 758 | 1 | if(this->s.myPos >= 0.0f && this->s.myPos <= 100.0f && this->settings.tiltType != tilt_types::tiltonly) this->p_target(this->s.myPos); | |
| 759 | 1 | this->pinTiltOnlyLift(); | |
| 760 | } | ||
| 761 | } | ||
| 762 | else { | ||
| 763 | 1 | if(this->settings.tiltType == tilt_types::tiltonly) this->pinTiltOnlyLift(); | |
| 764 | ✗ | else this->p_target(this->s.currentPos); | |
| 765 | 1 | this->p_tiltTarget(this->s.currentTiltPos); | |
| 766 | } | ||
| 767 | 2 | break; | |
| 768 | ✗ | case somfy_commands::StepUp: | |
| 769 | // A group step is always one step. | ||
| 770 | ✗ | if(!this->applyStep(-1, 1)) return; | |
| 771 | ✗ | break; | |
| 772 | 2 | case somfy_commands::StepDown: | |
| 773 | 2 | dir = 1; | |
| 774 | 2 | if(!this->applyStep(1, 1)) return; | |
| 775 | 2 | break; | |
| 776 | 1 | case somfy_commands::Flag: | |
| 777 | 1 | this->p_sunFlag(false); | |
| 778 | // Every group works its flags out again, as for a Flag frame. | ||
| 779 | 1 | this->events.onSensorFlags(); | |
| 780 | 1 | if(this->settings.sunSensor) { | |
| 781 | 1 | this->events.onSaveWanted(); | |
| 782 | 1 | this->emitState(); | |
| 783 | } | ||
| 784 | else { | ||
| 785 | ESP_LOGW(TAG, "Shade does not have sensor %d", joinShadeFlags(this->settings, false, this->s.stateFlags)); | ||
| 786 | } | ||
| 787 | 1 | break; | |
| 788 | 2 | case somfy_commands::SunFlag: | |
| 789 | 2 | if(this->settings.sunSensor) { | |
| 790 | 1 | const bool isWindy = this->s.stateFlags & static_cast<uint8_t>(somfy_flags_t::Windy); | |
| 791 | 1 | const bool isSunny = this->s.stateFlags & static_cast<uint8_t>(somfy_flags_t::Sunny); | |
| 792 | 1 | this->p_sunFlag(true); | |
| 793 | //this->flags |= static_cast<uint8_t>(somfy_flags_t::SunFlag); | ||
| 794 | 1 | this->applySensorMove(this->sensors.onSunFlag(isSunny, isWindy)); | |
| 795 | 1 | this->events.onSaveWanted(); | |
| 796 | 1 | this->emitState(); | |
| 797 | 1 | this->events.onSensorFlags(); | |
| 798 | } | ||
| 799 | else | ||
| 800 | ESP_LOGW(TAG, "Shade does not have sensor %d", joinShadeFlags(this->settings, false, this->s.stateFlags)); | ||
| 801 | 2 | break; | |
| 802 | ✗ | default: | |
| 803 | ✗ | dir = 0; | |
| 804 | ✗ | break; | |
| 805 | } | ||
| 806 | 21 | this->setMovement(dir, now); | |
| 807 | } | ||
| 808 | 1 | void ShadeBehaviour::setTiltMovement(int8_t dir, uint32_t now) { | |
| 809 | 1 | int8_t currDir = this->s.tiltDirection; | |
| 810 | 1 | if(dir == 0) { | |
| 811 | // The shade tilt is stopped. | ||
| 812 | ✗ | this->startTiltPos = this->s.currentTiltPos; | |
| 813 | ✗ | this->tiltStart = 0; | |
| 814 | ✗ | this->p_tiltDirection(dir); | |
| 815 | ✗ | if(currDir != dir) { | |
| 816 | ✗ | this->commitTiltPosition(); | |
| 817 | } | ||
| 818 | } | ||
| 819 | 1 | else if(this->s.tiltDirection != dir) { | |
| 820 | 1 | this->tiltStart = now; | |
| 821 | 1 | this->startTiltPos = this->s.currentTiltPos; | |
| 822 | 1 | this->p_tiltDirection(dir); | |
| 823 | } | ||
| 824 | 1 | if(this->s.tiltDirection != currDir) { | |
| 825 | 1 | this->emitState(); | |
| 826 | } | ||
| 827 | 1 | } | |
| 828 | 29 | void ShadeBehaviour::onTxQueued(uint32_t ticket, uint32_t now) { | |
| 829 | // This runs before the caller sets the new target so direction still says | ||
| 830 | // whether the shade was moving. A reversal is simulated as soon as it is | ||
| 831 | // queued even though the motor turns once it is on air. See TxHold::mark | ||
| 832 | // for which of several queued commands is held. | ||
| 833 | 29 | this->tx.hold.mark(ticket, this->s.direction != 0 || this->s.tiltDirection != 0, now); | |
| 834 | 29 | } | |
| 835 | 25 | void ShadeBehaviour::onTxEvent(const somfy_tx_event_t &evt) { | |
| 836 | 25 | const ShadeTxActions act = this->tx.onEvent(evt, this->s.direction != 0 || this->s.tiltDirection != 0); | |
| 837 | 25 | if(act.startMovement) { | |
| 838 | // The motor starts now so the simulated movement does too. | ||
| 839 | 15 | this->moveStart = this->tiltStart = act.startAt; | |
| 840 | } | ||
| 841 | 25 | if(act.cancelMovement) { | |
| 842 | // The motor never got the command so it hasn't moved. | ||
| 843 | ESP_LOGW(TAG, "[Shade %u] Command dropped by the radio, staying at %.2f%%", this->shadeId, this->s.currentPos); | ||
| 844 | 2 | this->settingPos = this->settingTiltPos = this->settingMyPos = false; | |
| 845 | 2 | this->p_target(this->s.currentPos); | |
| 846 | 2 | this->p_tiltTarget(this->s.currentTiltPos); | |
| 847 | 2 | this->emitState(); | |
| 848 | } | ||
| 849 | 25 | if(act.commitMy) this->commitMyPosition(); | |
| 850 | 25 | if(act.restoreMy) { | |
| 851 | ESP_LOGW(TAG, "[Shade %u] Favorite not sent in full, keeping the previous one", this->shadeId); | ||
| 852 | 1 | this->p_myPos(act.myOldPos); | |
| 853 | 1 | this->p_myTiltPos(act.myOldTiltPos); | |
| 854 | 1 | this->emitState(); | |
| 855 | } | ||
| 856 | 25 | if(act.runToLimit) { | |
| 857 | ESP_LOGW(TAG, "[Shade %u] Stop dropped by the radio, running on to the limit", this->shadeId); | ||
| 858 | 2 | this->runToLimit(act.limitDir, act.limitTilt, act.limitSince); | |
| 859 | 2 | this->emitState(); | |
| 860 | } | ||
| 861 | 25 | } | |
| 862 | 9 | bool ShadeBehaviour::sendStop(int8_t dir, bool tilt, uint32_t now) { | |
| 863 | // Stops jump the radio queue so the motor stops as close to the target as it can. | ||
| 864 | 9 | if(const send_result_t sent = this->transmit(somfy_commands::My, this->remote.repeats, 0, 0, true, now)) { | |
| 865 | 8 | this->tx.stopSent(sent.ticket, now, dir, tilt); | |
| 866 | 8 | return true; | |
| 867 | } | ||
| 868 | ESP_LOGW(TAG, "[Shade %u] Stop not sent, running on to the limit", this->shadeId); | ||
| 869 | 1 | this->runToLimit(dir, tilt, now); | |
| 870 | 1 | return false; | |
| 871 | } | ||
| 872 | 3 | bool ShadeBehaviour::sendSetMy(uint16_t delayMs, float oldPos, float oldTiltPos, uint32_t now) { | |
| 873 | // The new favorite is already in myPos/myTiltPos. It is committed once the | ||
| 874 | // whole long press is on air, see processTxEvent. | ||
| 875 | 3 | if(const send_result_t sent = this->transmit(somfy_commands::My, SETMY_REPEATS, 0, delayMs, false, now)) { | |
| 876 | 3 | this->tx.setMySent(sent.ticket, oldPos, oldTiltPos); | |
| 877 | // Without the radio (GPIO remotes) there is nothing to wait for. | ||
| 878 | 3 | if(sent.ticket == 0) this->commitMyPosition(); | |
| 879 | 3 | return true; | |
| 880 | } | ||
| 881 | // The motor never learned it so keep the favorite it has. | ||
| 882 | ✗ | this->p_myPos(oldPos); | |
| 883 | ✗ | this->p_myTiltPos(oldTiltPos); | |
| 884 | ✗ | return false; | |
| 885 | } | ||
| 886 | 49 | void ShadeBehaviour::pinTiltOnlyLift() { | |
| 887 | 49 | if(this->settings.tiltType != tilt_types::tiltonly) return; | |
| 888 | 15 | this->p_target(100.0f); | |
| 889 | 15 | this->p_currentPos(100.0f); | |
| 890 | } | ||
| 891 | 3 | void ShadeBehaviour::runToLimit(int8_t dir, bool tilt, uint32_t since) { | |
| 892 | // The motor didn't get a stop at the target so it keeps going until its end | ||
| 893 | // limit. Carry on simulating that from where and when it should have stopped. | ||
| 894 | 3 | if(tilt) { | |
| 895 | ✗ | this->startTiltPos = this->s.currentTiltPos; | |
| 896 | ✗ | this->tiltStart = since; | |
| 897 | ✗ | this->settingTiltPos = false; | |
| 898 | ✗ | this->p_tiltTarget(limitTarget(dir)); | |
| 899 | } | ||
| 900 | else { | ||
| 901 | 3 | this->startPos = this->s.currentPos; | |
| 902 | 3 | this->moveStart = since; | |
| 903 | 3 | this->settingPos = false; | |
| 904 | 3 | this->p_target(limitTarget(dir)); | |
| 905 | } | ||
| 906 | // The favorite can't be set where the shade won't stop. | ||
| 907 | 3 | this->settingMyPos = false; | |
| 908 | 3 | } | |
| 909 | 13153 | void ShadeBehaviour::applySensorMove(SensorMove move) { | |
| 910 | 13153 | const SensorTargets t = sensorTargets(move, this->settings.tiltType, this->s.myPos, this->s.myTiltPos); | |
| 911 | 13153 | if(t.setLift) this->p_target(t.lift); | |
| 912 | 13153 | if(t.setTilt) this->p_tiltTarget(t.tilt); | |
| 913 | 13153 | } | |
| 914 | 7 | bool ShadeBehaviour::applyStep(int8_t dir, uint8_t frameStepSize) { | |
| 915 | 7 | const StepTargets t = stepTargets(this->settings.tiltType, dir, this->s.currentPos, this->s.currentTiltPos, | |
| 916 | 7 | this->settings.upTime, this->settings.downTime, this->settings.tiltTime, this->settings.stepSize, frameStepSize); | |
| 917 | 7 | if(t.action == StepAction::ignore) return false; | |
| 918 | 7 | if(t.setLift) this->p_target(t.lift); | |
| 919 | 7 | if(t.setTilt) this->p_tiltTarget(t.tilt); | |
| 920 | 7 | return true; | |
| 921 | } | ||
| 922 | 137 | void ShadeBehaviour::setMovement(int8_t dir, uint32_t now) { | |
| 923 | 137 | int8_t currDir = this->s.direction; | |
| 924 | 137 | int8_t currTiltDir = this->s.tiltDirection; | |
| 925 | 137 | if(dir == 0) { | |
| 926 | 129 | if(currDir != dir || currTiltDir != dir) this->commitShadePosition(); | |
| 927 | } | ||
| 928 | else { | ||
| 929 | 8 | this->tiltStart = this->moveStart = now; | |
| 930 | 8 | this->startPos = this->s.currentPos; | |
| 931 | 8 | this->startTiltPos = this->s.currentTiltPos; | |
| 932 | } | ||
| 933 | 137 | if(this->s.direction != currDir || currTiltDir != this->s.tiltDirection) { | |
| 934 | ✗ | this->emitState(); | |
| 935 | } | ||
| 936 | 137 | } | |
| 937 | 3 | bool ShadeBehaviour::setMyPosition(int8_t pos, int8_t tilt, uint32_t now) { | |
| 938 | 3 | if(!this->isIdle()) return true; // Don't do this if it is moving. | |
| 939 | 3 | bool sent = true; | |
| 940 | 3 | if(this->settings.tiltType == tilt_types::tiltonly) { | |
| 941 | ✗ | this->p_myPos(-1.0f); | |
| 942 | ✗ | if(tilt != floor(this->s.currentTiltPos)) { | |
| 943 | // The favorite is set once we get there, if the move went out. | ||
| 944 | ✗ | if(tilt == floor(this->s.myTiltPos)) | |
| 945 | ✗ | sent = this->settingMyPos = this->moveToMyPosition(now); | |
| 946 | else | ||
| 947 | ✗ | sent = this->settingMyPos = this->moveToTarget(100, tilt, now); | |
| 948 | } | ||
| 949 | ✗ | else if(tilt == floor(this->s.myTiltPos)) { | |
| 950 | // Of so we need to clear the my position. These motors are finicky so send | ||
| 951 | // a my command to ensure we are actually at the my position then send the clear | ||
| 952 | // command. There really is no other way to do this. | ||
| 953 | ✗ | if(this->s.currentTiltPos != this->s.myTiltPos) { | |
| 954 | ✗ | sent = this->settingMyPos = this->moveToMyPosition(now); | |
| 955 | } | ||
| 956 | ✗ | else if((sent = this->transmit(somfy_commands::My, this->remote.repeats, 0, 0, false, now).sent)) { | |
| 957 | ✗ | this->settingPos = false; | |
| 958 | ✗ | this->settingMyPos = true; | |
| 959 | } | ||
| 960 | } | ||
| 961 | ✗ | else return this->storeMyPosition(-1.0f, this->s.currentTiltPos, now); | |
| 962 | ✗ | this->commitMyPosition(); | |
| 963 | ✗ | this->emitState(); | |
| 964 | } | ||
| 965 | 3 | else if(this->settings.tiltType != tilt_types::none) { | |
| 966 | ✗ | if(tilt < 0) tilt = 0; | |
| 967 | ✗ | if(pos != floor(this->s.currentPos) || tilt != floor(this->s.currentTiltPos)) { | |
| 968 | // The favorite is set once we get there, if the move went out. | ||
| 969 | ✗ | if(pos == floor(this->s.myPos) && tilt == floor(this->s.myTiltPos)) | |
| 970 | ✗ | sent = this->settingMyPos = this->moveToMyPosition(now); | |
| 971 | else | ||
| 972 | ✗ | sent = this->settingMyPos = this->moveToTarget(pos, tilt, now); | |
| 973 | } | ||
| 974 | ✗ | else if(pos == floor(this->s.myPos) && tilt == floor(this->s.myTiltPos)) { | |
| 975 | // Of so we need to clear the my position. These motors are finicky so send | ||
| 976 | // a my command to ensure we are actually at the my position then send the clear | ||
| 977 | // command. There really is no other way to do this. | ||
| 978 | ✗ | if(this->s.currentPos != this->s.myPos || this->s.currentTiltPos != this->s.myTiltPos) { | |
| 979 | ✗ | sent = this->settingMyPos = this->moveToMyPosition(now); | |
| 980 | } | ||
| 981 | ✗ | else if((sent = this->transmit(somfy_commands::My, this->remote.repeats, 0, 0, false, now).sent)) { | |
| 982 | ✗ | this->settingPos = false; | |
| 983 | ✗ | this->settingMyPos = true; | |
| 984 | } | ||
| 985 | } | ||
| 986 | ✗ | else return this->storeMyPosition(this->s.currentPos, this->s.currentTiltPos, now); | |
| 987 | ✗ | this->commitMyPosition(); | |
| 988 | ✗ | this->emitState(); | |
| 989 | } | ||
| 990 | else { | ||
| 991 | 3 | if(pos != floor(this->s.currentPos)) { | |
| 992 | // The favorite is set once we get there, if the move went out. | ||
| 993 | 1 | if(pos == floor(this->s.myPos)) | |
| 994 | ✗ | sent = this->settingMyPos = this->moveToMyPosition(now); | |
| 995 | else | ||
| 996 | 1 | sent = this->settingMyPos = this->moveToTarget(pos, -1.0f, now); | |
| 997 | } | ||
| 998 | 2 | else if(pos == floor(this->s.myPos)) { | |
| 999 | // Of so we need to clear the my position. These motors are finicky so send | ||
| 1000 | // a my command to ensure we are actually at the my position then send the clear | ||
| 1001 | // command. There really is no other way to do this. | ||
| 1002 | ✗ | if(this->s.myPos != this->s.currentPos) { | |
| 1003 | ✗ | sent = this->settingMyPos = this->moveToMyPosition(now); | |
| 1004 | } | ||
| 1005 | ✗ | else if((sent = this->transmit(somfy_commands::My, this->remote.repeats, 0, 0, false, now).sent)) { | |
| 1006 | ✗ | this->settingPos = false; | |
| 1007 | ✗ | this->settingMyPos = true; | |
| 1008 | } | ||
| 1009 | } | ||
| 1010 | 2 | else sent = this->storeMyPosition(this->s.currentPos, -1.0f, now); | |
| 1011 | } | ||
| 1012 | 3 | return sent; | |
| 1013 | } | ||
| 1014 | 2 | bool ShadeBehaviour::storeMyPosition(float pos, float tiltPos, uint32_t now) { | |
| 1015 | // Set it before sending so processFrame doesn't head for the old one. It is | ||
| 1016 | // only committed once the motor was told about it. | ||
| 1017 | 2 | const float oldPos = this->s.myPos; | |
| 1018 | 2 | const float oldTiltPos = this->s.myTiltPos; | |
| 1019 | 2 | this->p_myPos(pos); | |
| 1020 | 2 | this->p_myTiltPos(tiltPos); | |
| 1021 | 2 | const bool sent = this->sendSetMy(0, oldPos, oldTiltPos, now); | |
| 1022 | 2 | this->emitState(); | |
| 1023 | 2 | return sent; | |
| 1024 | } | ||
| 1025 | 2 | bool ShadeBehaviour::moveToMyPosition(uint32_t now) { | |
| 1026 | 2 | if(!this->isIdle()) return true; | |
| 1027 | ESP_LOGI(TAG, "Moving to My Position"); | ||
| 1028 | 2 | if(this->settings.tiltType == tilt_types::tiltonly) { | |
| 1029 | 1 | this->p_currentPos(100.0f); | |
| 1030 | 1 | this->p_myPos(-1.0f); | |
| 1031 | } | ||
| 1032 | 2 | if(this->s.currentPos == this->s.myPos) { | |
| 1033 | ✗ | if(this->settings.tiltType != tilt_types::none) { | |
| 1034 | ✗ | if(this->s.currentTiltPos == this->s.myTiltPos) return true; // Nothing to see here since we are already here. | |
| 1035 | } | ||
| 1036 | else | ||
| 1037 | ✗ | return true; | |
| 1038 | } | ||
| 1039 | 2 | if(this->s.myPos == -1 && (this->settings.tiltType == tilt_types::none || this->s.myTiltPos == -1)) return true; | |
| 1040 | 2 | this->settingPos = false; | |
| 1041 | 2 | if(this->settings.simMy) { | |
| 1042 | ESP_LOGI(TAG, "Moving to simulated favorite"); | ||
| 1043 | 1 | if(!this->moveToTarget(this->s.myPos, this->s.myTiltPos, now)) return false; | |
| 1044 | } | ||
| 1045 | 1 | else if(!this->transmit(somfy_commands::My, this->remote.repeats, 0, 0, false, now).sent) return false; | |
| 1046 | 2 | if(this->settings.tiltType != tilt_types::tiltonly && this->s.myPos >= 0.0f && this->s.myPos <= 100.0f) this->p_target(this->s.myPos); | |
| 1047 | 2 | if(this->s.myTiltPos >= 0.0f && this->s.myTiltPos <= 100.0f) this->p_tiltTarget(this->s.myTiltPos); | |
| 1048 | 2 | return true; | |
| 1049 | } | ||
| 1050 | 19 | bool ShadeBehaviour::sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize, uint32_t now) { | |
| 1051 | ESP_LOGD(TAG, "Send command start"); | ||
| 1052 | // This sendCommand function will always be called externally. sendCommand at the remote level | ||
| 1053 | // is expected to be called internally when the motor needs commanded. | ||
| 1054 | // If same direction command sent while already moving, stop the state tracking. | ||
| 1055 | // The real motor stops on its own when it receives the same direction again. | ||
| 1056 | 19 | if((cmd == somfy_commands::Up && this->s.direction == -1) || | |
| 1057 | 9 | (cmd == somfy_commands::Down && this->s.direction == 1)) { | |
| 1058 | ESP_LOGD(TAG, "Same command as dir"); | ||
| 1059 | 1 | if(!this->transmit(cmd, repeat, 0, 0, false, now).sent) return false; | |
| 1060 | 1 | this->p_target(this->s.currentPos); | |
| 1061 | 1 | this->p_tiltTarget(this->s.currentTiltPos); | |
| 1062 | 1 | this->setMovement(0, now); | |
| 1063 | 1 | return true; | |
| 1064 | } | ||
| 1065 | // The simulated state is only moved for commands the radio took. | ||
| 1066 | 18 | if(cmd == somfy_commands::Up) { | |
| 1067 | 3 | if(this->settings.tiltType == tilt_types::euromode) { | |
| 1068 | // In euromode we need to long press for 2 seconds on the | ||
| 1069 | // up command. | ||
| 1070 | ✗ | if(!this->transmit(cmd, TILT_REPEATS, 0, 0, false, now).sent) return false; | |
| 1071 | ✗ | this->p_target(0.0f); | |
| 1072 | } | ||
| 1073 | else { | ||
| 1074 | 3 | if(!this->transmit(cmd, repeat, 0, 0, false, now).sent) return false; | |
| 1075 | 3 | if(this->settings.tiltType == tilt_types::tiltonly) { | |
| 1076 | 2 | this->p_target(100.0f); | |
| 1077 | 2 | this->p_tiltTarget(0.0f); | |
| 1078 | 2 | this->p_currentPos(100.0f); | |
| 1079 | } | ||
| 1080 | 1 | else this->p_target(0.0f); | |
| 1081 | 3 | if(this->settings.tiltType == tilt_types::integrated) this->p_tiltTarget(0.0f); | |
| 1082 | } | ||
| 1083 | } | ||
| 1084 | 15 | else if(cmd == somfy_commands::Down) { | |
| 1085 | 8 | if(this->settings.tiltType == tilt_types::euromode) { | |
| 1086 | // In euromode we need to long press for 2 seconds on the | ||
| 1087 | // down command. | ||
| 1088 | ✗ | if(!this->transmit(cmd, TILT_REPEATS, 0, 0, false, now).sent) return false; | |
| 1089 | ✗ | this->p_target(100.0f); | |
| 1090 | } | ||
| 1091 | else { | ||
| 1092 | 8 | if(!this->transmit(cmd, repeat, 0, 0, false, now).sent) return false; | |
| 1093 | 7 | if(this->settings.tiltType == tilt_types::tiltonly) { | |
| 1094 | 2 | this->p_target(100.0f); | |
| 1095 | 2 | this->p_tiltTarget(100.0f); | |
| 1096 | 2 | this->p_currentPos(100.0f); | |
| 1097 | } | ||
| 1098 | 5 | else this->p_target(100.0f); | |
| 1099 | 7 | if(this->settings.tiltType == tilt_types::integrated) this->p_tiltTarget(100.0f); | |
| 1100 | } | ||
| 1101 | } | ||
| 1102 | 7 | else if(cmd == somfy_commands::My) { | |
| 1103 | 2 | if(this->isToggle() || this->settings.shadeType == shade_types::drycontact) { | |
| 1104 | ✗ | if(!this->transmit(cmd, repeat, 0, 0, false, now).sent) return false; | |
| 1105 | } | ||
| 1106 | 2 | else if(this->settings.shadeType == shade_types::drycontact2){ | |
| 1107 | ESP_LOGD(TAG, "Send command start 1"); | ||
| 1108 | ✗ | return true; | |
| 1109 | } | ||
| 1110 | 2 | else if(this->isIdle()) { | |
| 1111 | 1 | bool sent = this->moveToMyPosition(now); | |
| 1112 | ESP_LOGD(TAG, "Send command end 2"); | ||
| 1113 | 1 | return sent; | |
| 1114 | } | ||
| 1115 | else { | ||
| 1116 | 1 | if(!this->transmit(cmd, repeat, 0, 0, false, now).sent) return false; | |
| 1117 | 1 | if(this->settings.tiltType != tilt_types::tiltonly) this->p_target(this->s.currentPos); | |
| 1118 | 1 | this->p_tiltTarget(this->s.currentTiltPos); | |
| 1119 | 1 | this->pinTiltOnlyLift(); | |
| 1120 | } | ||
| 1121 | } | ||
| 1122 | 5 | else if(cmd == somfy_commands::Toggle) { | |
| 1123 | 2 | if(this->remote.bitLength != 80) { if(!this->transmit(somfy_commands::My, repeat, stepSize, 0, false, now).sent) return false; } | |
| 1124 | 1 | else if(!this->transmit(somfy_commands::Toggle, repeat, 0, 0, false, now).sent) return false; | |
| 1125 | } | ||
| 1126 | 3 | else if(this->isToggle() && cmd == somfy_commands::Prog) { | |
| 1127 | ✗ | if(!this->transmit(somfy_commands::Toggle, repeat, stepSize, 0, false, now).sent) return false; | |
| 1128 | } | ||
| 1129 | else { | ||
| 1130 | 3 | if(!this->transmit(cmd, repeat, stepSize, 0, false, now).sent) return false; | |
| 1131 | } | ||
| 1132 | ESP_LOGD(TAG, "Send command end"); | ||
| 1133 | 16 | return true; | |
| 1134 | } | ||
| 1135 | 1 | uint8_t ShadeBehaviour::tiltRepeats() const { return this->settings.tiltType == tilt_types::tiltmotor ? TILT_REPEATS : this->remote.repeats; } | |
| 1136 | ✗ | bool ShadeBehaviour::sendTiltCommand(somfy_commands cmd, uint32_t now) { | |
| 1137 | float target; | ||
| 1138 | ✗ | if(cmd == somfy_commands::Up) target = 0.0f; | |
| 1139 | ✗ | else if(cmd == somfy_commands::Down) target = 100.0f; | |
| 1140 | ✗ | else if(cmd == somfy_commands::My) target = this->s.currentTiltPos; | |
| 1141 | ✗ | else return true; | |
| 1142 | ✗ | if(!this->transmit(cmd, this->tiltRepeats(), 0, 0, false, now).sent) return false; | |
| 1143 | ✗ | this->p_tiltTarget(target); | |
| 1144 | ✗ | return true; | |
| 1145 | } | ||
| 1146 | 1 | bool ShadeBehaviour::moveToTiltTarget(float target, uint32_t now) { | |
| 1147 | 1 | somfy_commands cmd = somfy_commands::My; | |
| 1148 | 1 | if(target < this->s.currentTiltPos) | |
| 1149 | 1 | cmd = somfy_commands::Up; | |
| 1150 | ✗ | else if(target > this->s.currentTiltPos) | |
| 1151 | ✗ | cmd = somfy_commands::Down; | |
| 1152 | 1 | if(target >= 0.0f && target <= 100.0f) { | |
| 1153 | // Only send a command if the lift is not moving. | ||
| 1154 | 1 | if(this->s.currentPos == this->s.target || this->settings.tiltType == tilt_types::tiltmotor) { | |
| 1155 | 1 | if(cmd != somfy_commands::My) { | |
| 1156 | ESP_LOGI(TAG, "Moving Tilt to %.2f%% from %.2f%% using %s", | ||
| 1157 | target, this->s.currentTiltPos, translateSomfyCommand(cmd).c_str()); | ||
| 1158 | 1 | if(!this->transmit(cmd, this->tiltRepeats(), 0, 0, false, now).sent) return false; | |
| 1159 | } | ||
| 1160 | // If the blind is currently moving then the command to stop it | ||
| 1161 | // will occur on its own when the tilt target is set. | ||
| 1162 | } | ||
| 1163 | 1 | this->p_tiltTarget(target); | |
| 1164 | } | ||
| 1165 | 1 | if(cmd != somfy_commands::My) this->settingTiltPos = true; | |
| 1166 | 1 | return true; | |
| 1167 | } | ||
| 1168 | 9 | bool ShadeBehaviour::moveToTarget(float pos, float tilt, uint32_t now) { | |
| 1169 | ESP_LOGI(TAG, "[Shade %u] moveToTarget(pos=%.2f, tilt=%.2f) settingPos=%d direction=%d currentTarget=%.2f currentPos=%.2f", | ||
| 1170 | this->shadeId, pos, tilt, this->settingPos, this->s.direction, this->s.target, this->s.currentPos); | ||
| 1171 | 9 | somfy_commands cmd = somfy_commands::My; | |
| 1172 | 9 | if(this->isToggle()) { | |
| 1173 | // Overload this as we cannot seek a position on a garage door or single button device. | ||
| 1174 | ✗ | this->p_target(pos); | |
| 1175 | ✗ | this->p_currentPos(pos); | |
| 1176 | ✗ | this->emitState(); | |
| 1177 | ✗ | return true; | |
| 1178 | } | ||
| 1179 | 9 | if(this->settings.tiltType == tilt_types::tiltonly) { | |
| 1180 | // A tilt only shade is always at 100%. | ||
| 1181 | ✗ | this->p_target(100.0f); | |
| 1182 | ✗ | this->p_myPos(-1.0f); | |
| 1183 | ✗ | this->p_currentPos(100.0f); | |
| 1184 | ✗ | pos = 100; | |
| 1185 | ✗ | if(tilt < this->s.currentTiltPos) cmd = somfy_commands::Up; | |
| 1186 | ✗ | else if(tilt > this->s.currentTiltPos) cmd = somfy_commands::Down; | |
| 1187 | } | ||
| 1188 | else { | ||
| 1189 | 9 | if(pos < this->s.currentPos) | |
| 1190 | ✗ | cmd = somfy_commands::Up; | |
| 1191 | 9 | else if(pos > this->s.currentPos) | |
| 1192 | 9 | cmd = somfy_commands::Down; | |
| 1193 | ✗ | else if(tilt >= 0 && tilt < this->s.currentTiltPos) | |
| 1194 | ✗ | cmd = somfy_commands::Up; | |
| 1195 | ✗ | else if(tilt >= 0 && tilt > this->s.currentTiltPos) | |
| 1196 | ✗ | cmd = somfy_commands::Down; | |
| 1197 | } | ||
| 1198 | 9 | if(cmd != somfy_commands::My) { | |
| 1199 | if(tilt >= 0) { | ||
| 1200 | ESP_LOGI(TAG, "Moving to %.2f%% from %.2f%% tilt %.2f%% from %.2f%% using %s", | ||
| 1201 | pos, this->s.currentPos, tilt, this->s.currentTiltPos, translateSomfyCommand(cmd).c_str()); | ||
| 1202 | } else { | ||
| 1203 | ESP_LOGI(TAG, "Moving to %.2f%% from %.2f%% using %s", | ||
| 1204 | pos, this->s.currentPos, translateSomfyCommand(cmd).c_str()); | ||
| 1205 | } | ||
| 1206 | 9 | if(!this->transmit(cmd, this->settings.tiltType == tilt_types::euromode ? TILT_REPEATS : this->remote.repeats, 0, 0, false, now).sent) return false; | |
| 1207 | 9 | this->settingPos = true; | |
| 1208 | 9 | this->p_target(pos); | |
| 1209 | 9 | if(tilt >= 0) { | |
| 1210 | 1 | this->p_tiltTarget(tilt); | |
| 1211 | 1 | this->settingTiltPos = true; | |
| 1212 | } | ||
| 1213 | } | ||
| 1214 | 9 | return true; | |
| 1215 | } | ||
| 1216 |