From 5f26340d06670c308e7f4041bbe1bced0d46409f Mon Sep 17 00:00:00 2001 From: cjkas Date: Tue, 13 Oct 2026 21:14:20 +0200 Subject: [PATCH] fix: move only the tilt of a tilt-only shade for sun and wind The three places that act on the sun and wind each set different targets for a tilt-only shade: the sun timer moved its lift, the no-sun and wind timers moved tilt and lift, a group's sun flag moved its lift and only a remote's sun flag moved its tilt. They now share sensorTargets(): a tilt-only shade moves its tilt to the favorite tilt (or fully) and back to 0, and every other shade its lift, as Up and Down already do. Co-Authored-By: Claude Opus 5.5 --- src/ShadeSensors.cpp | 14 +++++++++ src/ShadeSensors.h | 11 +++++++ src/Somfy.h | 2 ++ src/SomfyShade.cpp | 45 +++++----------------------- test/test_sensors/test_sensors.cpp | 48 ++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 37 deletions(-) diff --git a/src/ShadeSensors.cpp b/src/ShadeSensors.cpp index 2f99f37..7575a11 100644 --- a/src/ShadeSensors.cpp +++ b/src/ShadeSensors.cpp @@ -72,6 +72,20 @@ SensorMove ShadeSensors::onSunFlag(bool sunny, bool windy) const { if(!sunny && this->noSunDone) return SensorMove::retract; return SensorMove::none; } +SensorTargets sensorTargets(SensorMove move, tilt_types tilt, float myPos, float myTiltPos) { + SensorTargets t; + if(move == SensorMove::none) return t; + const bool extend = move == SensorMove::extend; + if(tilt == tilt_types::tiltonly) { + t.setTilt = true; + t.tilt = extend ? (myTiltPos >= 0 ? myTiltPos : 100.0f) : 0.0f; + } + else { + t.setLift = true; + t.lift = extend ? (myPos >= 0 ? myPos : 100.0f) : 0.0f; + } + return t; +} bool ShadeSensors::windBlocksDown(uint32_t now) const { return this->windLast != 0 && now - this->windLast < SOMFY_NO_WIND_REMOTE_TIMEOUT; } diff --git a/src/ShadeSensors.h b/src/ShadeSensors.h index 2382c65..651acf8 100644 --- a/src/ShadeSensors.h +++ b/src/ShadeSensors.h @@ -1,6 +1,7 @@ #ifndef SHADE_SENSORS_H #define SHADE_SENSORS_H #include +#include "ShadeTypes.h" // Sun and wind automation for a shade with a sun sensor. Kept free of the // shade, radio and network so it can be unit tested on the PC @@ -49,4 +50,14 @@ struct ShadeSensors { // Whether a remote Down is ignored because the sensor reported wind lately. bool windBlocksDown(uint32_t now) const; }; +// The targets a sensor move sets. A tilt-only shade only moves its tilt and +// every other shade only its lift, as Up and Down do. +struct SensorTargets { + bool setLift = false; + float lift = 0.0f; + bool setTilt = false; + float tilt = 0.0f; +}; +// myPos and myTiltPos are the favorite, -1 when there is none. +SensorTargets sensorTargets(SensorMove move, tilt_types tilt, float myPos, float myTiltPos); #endif diff --git a/src/Somfy.h b/src/Somfy.h index aa97682..a61c97a 100644 --- a/src/Somfy.h +++ b/src/Somfy.h @@ -134,6 +134,8 @@ class SomfyShade : public SomfyRemote { // Repeats for a tilt command, more for a separate tilt motor. uint8_t tiltRepeats() const; void runToLimit(int8_t dir, bool tilt, uint32_t since); + // Sets the targets the sun or wind sends the shade to. + void applySensorMove(SensorMove move); public: uint8_t roomId = 0; int8_t sortOrder = 0; diff --git a/src/SomfyShade.cpp b/src/SomfyShade.cpp index 928a4b1..206215b 100644 --- a/src/SomfyShade.cpp +++ b/src/SomfyShade.cpp @@ -152,17 +152,7 @@ void SomfyShade::checkMovement() { case TxHoldCheck::none: break; } - switch(this->sensors.check(sunFlag, isSunny, isWindy, (uint32_t)curTime)) { - case SensorMove::extend: - this->p_target(this->myPos >= 0 ? this->myPos : 100.0f); - break; - case SensorMove::retract: - if(this->tiltType == tilt_types::tiltonly) this->p_tiltTarget(0.0f); - this->p_target(0.0f); - break; - case SensorMove::none: - break; - } + this->applySensorMove(this->sensors.check(sunFlag, isSunny, isWindy, (uint32_t)curTime)); if(!tilt_first && this->direction > 0) { this->p_currentPos(travelPosition(this->startPos, 1, (uint32_t)(curTime - this->moveStart), downTime)); @@ -609,22 +599,7 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { const bool isSunny = this->flags & static_cast(somfy_flags_t::Sunny); //this->flags |= static_cast(somfy_flags_t::SunFlag); this->p_sunFlag(true); - switch(this->sensors.onSunFlag(isSunny, isWindy)) { - case SensorMove::extend: - if(this->tiltType == tilt_types::tiltonly) - this->p_tiltTarget(this->myTiltPos >= 0 ? this->myTiltPos : 100.0f); - else - this->p_target(this->myPos >= 0 ? this->myPos : 100.0f); - break; - case SensorMove::retract: - if(this->tiltType == tilt_types::tiltonly) - this->p_tiltTarget(0.0f); - else - this->p_target(0.0f); - break; - case SensorMove::none: - break; - } + this->applySensorMove(this->sensors.onSunFlag(isSunny, isWindy)); somfy.isDirty = true; this->emitState(); this->emitCommand(cmd, internal ? "internal" : "remote", frame.remoteAddress); @@ -1007,16 +982,7 @@ void SomfyShade::processInternalCommand(somfy_commands cmd, uint8_t repeat) { const bool isSunny = this->flags & static_cast(somfy_flags_t::Sunny); this->p_sunFlag(true); //this->flags |= static_cast(somfy_flags_t::SunFlag); - switch(this->sensors.onSunFlag(isSunny, isWindy)) { - case SensorMove::extend: - this->p_target(this->myPos >= 0 ? this->myPos : 100.0f); - break; - case SensorMove::retract: - this->p_target(0.0f); - break; - case SensorMove::none: - break; - } + this->applySensorMove(this->sensors.onSunFlag(isSunny, isWindy)); somfy.isDirty = true; this->emitState(); } @@ -1126,6 +1092,11 @@ void SomfyShade::runToLimit(int8_t dir, bool tilt, uint32_t since) { // The favorite can't be set where the shade won't stop. this->settingMyPos = false; } +void SomfyShade::applySensorMove(SensorMove move) { + const SensorTargets t = sensorTargets(move, this->tiltType, this->myPos, this->myTiltPos); + if(t.setLift) this->p_target(t.lift); + if(t.setTilt) this->p_tiltTarget(t.tilt); +} void SomfyShade::setMovement(int8_t dir) { int8_t currDir = this->direction; int8_t currTiltDir = this->tiltDirection; diff --git a/test/test_sensors/test_sensors.cpp b/test/test_sensors/test_sensors.cpp index e58addf..5bba580 100644 --- a/test/test_sensors/test_sensors.cpp +++ b/test/test_sensors/test_sensors.cpp @@ -135,6 +135,49 @@ static void test_timers_run_across_a_millis_rollover() { TEST_ASSERT_TRUE(s.windBlocksDown(start + 2000)); } +/********************************************************************* + * sensorTargets + ********************************************************************/ +#define ASSERT_POS(expected, actual) TEST_ASSERT_FLOAT_WITHIN(0.01f, (expected), (actual)) +static const float NO_FAVORITE = -1.0f; + +static void test_lift_shade_extends_to_its_favorite() { + SensorTargets t = sensorTargets(SensorMove::extend, tilt_types::none, 60.0f, NO_FAVORITE); + TEST_ASSERT_TRUE(t.setLift); + ASSERT_POS(60.0f, t.lift); + TEST_ASSERT_FALSE(t.setTilt); +} +static void test_lift_shade_extends_fully_without_a_favorite() { + SensorTargets t = sensorTargets(SensorMove::extend, tilt_types::integrated, NO_FAVORITE, 30.0f); + TEST_ASSERT_TRUE(t.setLift); + ASSERT_POS(100.0f, t.lift); + TEST_ASSERT_FALSE(t.setTilt); +} +static void test_lift_shade_retracts_fully() { + SensorTargets t = sensorTargets(SensorMove::retract, tilt_types::tiltmotor, 60.0f, 30.0f); + TEST_ASSERT_TRUE(t.setLift); + ASSERT_POS(0.0f, t.lift); + TEST_ASSERT_FALSE(t.setTilt); +} +static void test_tilt_only_shade_only_moves_its_tilt() { + SensorTargets t = sensorTargets(SensorMove::extend, tilt_types::tiltonly, 60.0f, 30.0f); + TEST_ASSERT_FALSE(t.setLift); + TEST_ASSERT_TRUE(t.setTilt); + ASSERT_POS(30.0f, t.tilt); + t = sensorTargets(SensorMove::extend, tilt_types::tiltonly, 60.0f, NO_FAVORITE); + TEST_ASSERT_FALSE(t.setLift); + ASSERT_POS(100.0f, t.tilt); + t = sensorTargets(SensorMove::retract, tilt_types::tiltonly, 60.0f, 30.0f); + TEST_ASSERT_FALSE(t.setLift); + TEST_ASSERT_TRUE(t.setTilt); + ASSERT_POS(0.0f, t.tilt); +} +static void test_no_move_sets_nothing() { + SensorTargets t = sensorTargets(SensorMove::none, tilt_types::tiltonly, 60.0f, 30.0f); + TEST_ASSERT_FALSE(t.setLift); + TEST_ASSERT_FALSE(t.setTilt); +} + int main(int argc, char **argv) { (void)argc; (void)argv; @@ -155,5 +198,10 @@ int main(int argc, char **argv) { RUN_TEST(test_sun_flag_does_nothing_in_wind); RUN_TEST(test_clear_forgets_pending_timers); RUN_TEST(test_timers_run_across_a_millis_rollover); + RUN_TEST(test_lift_shade_extends_to_its_favorite); + RUN_TEST(test_lift_shade_extends_fully_without_a_favorite); + RUN_TEST(test_lift_shade_retracts_fully); + RUN_TEST(test_tilt_only_shade_only_moves_its_tilt); + RUN_TEST(test_no_move_sets_nothing); return UNITY_END(); }