diff --git a/platformio.ini b/platformio.ini index 432f0d9..1890425 100644 --- a/platformio.ini +++ b/platformio.ini @@ -101,7 +101,7 @@ lib_deps = platform = native test_framework = unity test_build_src = yes -build_src_filter = -<*> + + + +build_src_filter = -<*> + + + + build_flags = -std=gnu++17 -Isrc diff --git a/src/ShadeMotion.cpp b/src/ShadeMotion.cpp new file mode 100644 index 0000000..39c9acb --- /dev/null +++ b/src/ShadeMotion.cpp @@ -0,0 +1,31 @@ +#include +#include "ShadeMotion.h" + +float travelPosition(float startPos, int8_t dir, uint32_t elapsedMs, int32_t travelMs) { + // Work in time travelled from the end we are moving away from: the start + // position is turned into the time it would take to get there, the elapsed + // time is added and the total is turned back into a position. The addition + // wraps like the uint32 millis() so it keeps working across a rollover. + if(dir > 0) { + if(travelMs == 0) return 100.0f; + int32_t ms = (int32_t)floor((startPos / 100) * travelMs); + ms += elapsedMs; + ms = std::min(travelMs, ms); + if(ms >= travelMs) return 100.0f; + return std::min(std::max(0.0f, (float)ms / (float)travelMs), 1.0f) * 100; + } + if(dir < 0) { + if(travelMs == 0) return 0.0f; + int32_t ms = travelMs - (int32_t)floor((startPos / 100) * travelMs); + ms += elapsedMs; + ms = std::min(travelMs, ms); + if(ms >= travelMs) return 0.0f; + float pos = (1.0f - std::min(std::max(0.0f, (float)ms / (float)travelMs), 1.0f)) * 100; + return pos <= 0.0f ? 0.0f : pos; + } + return startPos; +} +int8_t displayPosition(float pos, bool flip) { + if(pos < 0) return -1; + return static_cast(flip && pos >= 0.00f ? floor(100.0f - pos) : floor(pos)); +} diff --git a/src/ShadeMotion.h b/src/ShadeMotion.h new file mode 100644 index 0000000..903d794 --- /dev/null +++ b/src/ShadeMotion.h @@ -0,0 +1,15 @@ +#ifndef SHADE_MOTION_H +#define SHADE_MOTION_H +#include + +// Shade position maths. Kept free of the shade state, radio and network so it +// can be unit tested on the PC (pio test -e native). + +// Where a shade (or its tilt) is after travelling elapsedMs from startPos +// (0-100). dir > 0 moves toward 100 and dir < 0 toward 0. travelMs is the +// time a full travel takes in that direction, 0 meaning it gets there at once. +float travelPosition(float startPos, int8_t dir, uint32_t elapsedMs, int32_t travelMs); +// A position as reported to clients: -1 when unset, otherwise the whole +// percentage, counted from the other end when flipped. +int8_t displayPosition(float pos, bool flip); +#endif diff --git a/src/Somfy.cpp b/src/Somfy.cpp index 06c33d7..181596f 100644 --- a/src/Somfy.cpp +++ b/src/Somfy.cpp @@ -11,6 +11,7 @@ #include "Utils.h" #include "ConfigSettings.h" #include "Somfy.h" +#include "ShadeMotion.h" #include "Sockets.h" #include "MQTT.h" #include "ConfigFile.h" @@ -822,41 +823,7 @@ void SomfyShade::checkMovement() { } if(!tilt_first && this->direction > 0) { - if(downTime == 0) { - this->p_currentPos(100.0); - //this->p_direction(0); - } - else { - // The shade is moving down so we need to calculate its position through the down position. - // 10000ms from 0 to 100 - // The starting posion is a float value from 0-1 that indicates how much the shade is open. So - // if we take the starting position * the total down time then this will tell us how many ms it - // has moved in the down position. - int32_t msFrom0 = (int32_t)floor((this->startPos/100) * downTime); - - // 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 += (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(downTime, msFrom0); - if(msFrom0 >= downTime) { - this->p_currentPos(100.0f); - //this->p_direction(0); - } - else { - // So now we know how much time has elapsed from the 0 position to down. The current position should be - // a ratio of how much time has travelled over the total time to go 100%. - - // We should now have the number of ms it will take to reach the shade fully close. - this->p_currentPos((min(max((float)0.0, (float)msFrom0 / (float)downTime), (float)1.0)) * 100); - // If the current position is >= 1 then we are at the bottom of the shade. - if(this->currentPos >= 100) { - this->p_currentPos(100.0); - //this->p_direction(0); - } - } - } + this->p_currentPos(travelPosition(this->startPos, 1, (uint32_t)(curTime - this->moveStart), downTime)); if(this->currentPos >= this->target) { this->p_currentPos(this->target); //if(this->settingMyPos) Serial.printf("IsAtTarget: %d %f=%f\n", this->isAtTarget(), this->currentPos, this->target); @@ -880,34 +847,8 @@ void SomfyShade::checkMovement() { } } else if(!tilt_first && this->direction < 0) { - if(upTime == 0) { - this->p_currentPos(0); - //this->p_direction(0); - } - else { - // The shade is moving up so we need to calculate its position through the up position. Shades - // often move slower in the up position so since we are using a relative position the up time - // can be calculated. - // 10000ms from 100 to 0; - int32_t msFrom100 = upTime - (int32_t)floor((this->startPos/100) * upTime); - msFrom100 += (curTime - this->moveStart); - msFrom100 = min(upTime, msFrom100); - if(msFrom100 >= upTime) { - this->p_currentPos(0.0f); - //this->p_direction(0); - } - else { - float fpos = ((float)1.0 - min(max((float)0.0, (float)msFrom100 / (float)upTime), (float)1.0)) * 100; - // We should now have the number of ms it will take to reach the shade fully open. - // If we are at the top of the shade then set the movement to 0. - if(fpos <= 0.0) { - this->p_currentPos(0.0f); - //this->p_direction(0); - } - else - this->p_currentPos(fpos); - } - } + // Shades often move slower up so this uses its own travel time. + this->p_currentPos(travelPosition(this->startPos, -1, (uint32_t)(curTime - this->moveStart), upTime)); if(this->currentPos <= this->target) { this->p_currentPos(this->target); //if(this->settingMyPos) Serial.printf("IsAtTarget: %d %f=%f\n", this->isAtTarget(), this->currentPos, this->target); @@ -933,24 +874,7 @@ void SomfyShade::checkMovement() { } if(this->tiltDirection > 0) { if(tilt_first) this->moveStart = curTime; - int32_t msFrom0 = (int32_t)floor((this->startTiltPos/100) * tiltTime); - msFrom0 += (curTime - this->tiltStart); - msFrom0 = min(tiltTime, msFrom0); - if(msFrom0 >= tiltTime) { - this->p_currentTiltPos(100.0f); - //this->p_tiltDirection(0); - //Serial.printf("Setting tiltDirection to 0 (not enough time) %.4f %.4f\n", msFrom0, tiltTime); - } - else { - float fpos = (min(max((float)0.0, (float)msFrom0 / (float)tiltTime), (float)1.0)) * 100; - - if(fpos > 100.0f) { - this->p_currentTiltPos(100.0f); - //this->p_tiltDirection(0); - //Serial.println("Setting tiltDirection to 0 (100%)"); - } - else this->p_currentTiltPos(fpos); - } + this->p_currentTiltPos(travelPosition(this->startTiltPos, 1, (uint32_t)(curTime - this->tiltStart), tiltTime)); if(tilt_first) { if(this->currentTiltPos >= 100.0f) { this->p_currentTiltPos(100.0f); @@ -982,26 +906,8 @@ void SomfyShade::checkMovement() { } else if(this->tiltDirection < 0) { if(tilt_first) this->moveStart = curTime; - if(tiltTime == 0) { - this->p_tiltDirection(0); - this->p_currentTiltPos(0.0f); - } - else { - int32_t msFrom100 = tiltTime - (int32_t)floor((this->startTiltPos/100) * tiltTime); - msFrom100 += (curTime - this->tiltStart); - msFrom100 = min(tiltTime, msFrom100); - if(msFrom100 >= tiltTime) { - this->p_currentTiltPos(0.0f); - //this->p_tiltDirection(0); - } - float fpos = ((float)1.0 - min(max((float)0.0, (float)msFrom100 / (float)tiltTime), (float)1.0)) * 100; - // If we are at the top of the shade then set the movement to 0. - if(fpos <= 0.0f) { - this->p_currentTiltPos(0.0f); - //this->p_tiltDirection(0); - } - else this->p_currentTiltPos(fpos); - } + if(tiltTime == 0) this->p_tiltDirection(0); + this->p_currentTiltPos(travelPosition(this->startTiltPos, -1, (uint32_t)(curTime - this->tiltStart), tiltTime)); if(tilt_first) { if(this->currentTiltPos <= 0.0f) { this->p_currentTiltPos(0.0f); @@ -1743,10 +1649,7 @@ void SomfyGroup::emitState(uint8_t num, const char *evt) { */ this->publish(); } -int8_t SomfyShade::transformPosition(float fpos) { - if(fpos < 0) return -1; - return static_cast(this->flipPosition && fpos >= 0.00f ? floor(100.0f - fpos) : floor(fpos)); -} +int8_t SomfyShade::transformPosition(float fpos) { return displayPosition(fpos, this->flipPosition); } bool SomfyShade::isIdle() { return this->isAtTarget() && this->direction == 0 && this->tiltDirection == 0; } @@ -3596,27 +3499,7 @@ SomfyGroup *SomfyShadeController::addGroup() { } return group; } -somfy_commands SomfyRemote::transformCommand(somfy_commands cmd) { - if(this->flipCommands) { - switch(cmd) { - case somfy_commands::Up: - return somfy_commands::Down; - case somfy_commands::MyUp: - return somfy_commands::MyDown; - case somfy_commands::Down: - return somfy_commands::Up; - case somfy_commands::MyDown: - return somfy_commands::MyUp; - case somfy_commands::StepUp: - return somfy_commands::StepDown; - case somfy_commands::StepDown: - return somfy_commands::StepUp; - default: - break; - } - } - return cmd; -} +somfy_commands SomfyRemote::transformCommand(somfy_commands cmd) { return this->flipCommands ? flipCommand(cmd) : cmd; } bool SomfyRemote::sendSensorCommand(int8_t isWindy, int8_t isSunny, uint8_t repeat) { uint8_t flags = (this->flags >> 4) & 0x0F; if(isWindy > 0) flags |= 0x01; diff --git a/src/SomfyFrame.cpp b/src/SomfyFrame.cpp index adf978f..629fe74 100644 --- a/src/SomfyFrame.cpp +++ b/src/SomfyFrame.cpp @@ -85,6 +85,24 @@ String translateSomfyCommand(const somfy_commands cmd) { return "Unknown(" + String((uint8_t)cmd) + ")"; } } +somfy_commands flipCommand(somfy_commands cmd) { + switch(cmd) { + case somfy_commands::Up: + return somfy_commands::Down; + case somfy_commands::MyUp: + return somfy_commands::MyDown; + case somfy_commands::Down: + return somfy_commands::Up; + case somfy_commands::MyDown: + return somfy_commands::MyUp; + case somfy_commands::StepUp: + return somfy_commands::StepDown; + case somfy_commands::StepDown: + return somfy_commands::StepUp; + default: + return cmd; + } +} byte somfy_frame_t::calc80Checksum(byte b0, byte b1, byte b2) { byte cs80 = 0; cs80 = (((b0 & 0xF0) >> 4) ^ ((b1 & 0xF0) >> 4)); diff --git a/src/SomfyFrame.h b/src/SomfyFrame.h index ad2a249..011de09 100644 --- a/src/SomfyFrame.h +++ b/src/SomfyFrame.h @@ -35,6 +35,8 @@ enum class somfy_commands : byte { }; String translateSomfyCommand(const somfy_commands cmd); somfy_commands translateSomfyCommand(const String& string); +// The command with up and down swapped, for motors wired the other way round. +somfy_commands flipCommand(somfy_commands cmd); struct somfy_rx_t; struct somfy_frame_t {