fix: keep a tilt-only shade's lift at 100 whatever sends the command

The web and group Up/Down put a tilt-only shade's lift back at 100, but a
remote's Up/Down/My, a group's My and the web's My while moving left a
lift that something had moved, and the simulation then ran it. Every
Up/Down/My path now pins the lift at 100 and acts on the tilt only.
Reconcile Q22 in SHADE_BEHAVIOUR_PLAN.md, which also records Q23 (the
integrated tilt turn-back) as deferred.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-16 22:47:53 +02:00
parent 7220f38fcd
commit 38d4ce5058
4 changed files with 79 additions and 56 deletions

View file

@ -457,58 +457,66 @@ static void test_today_integrated_tilt_turns_back_after_a_lift_target() {
}
/*********************************************************************
* Tilt-only shades: the copies disagree
* Tilt-only shades: the lift stays all the way down
********************************************************************/
static Rig *tiltOnly(Rig &r) {
enum class Source { web, remote, group };
static const char *sourceName(Source src) {
switch(src) {
case Source::web: return "web";
case Source::remote: return "remote";
default: return "group";
}
}
// A tilt-only shade whose favorite tilt is 30, and whose lift something has
// moved to 60. moving starts the tilt turning first.
static void tiltOnly(Rig &r, bool moving) {
r.settings.shadeType = shade_types::blind;
r.settings.tiltType = tilt_types::tiltonly;
r.b.restore(100, 50, -1, -1);
return &r;
r.b.restore(100, 50, -1, 30);
if(moving) {
r.b.onGroupCommand(somfy_commands::Down, 1, r.now);
r.run(500);
}
r.b.setPosition(60, -1);
}
static void test_today_up_on_tilt_only_from_the_web() {
Rig r;
tiltOnly(r);
r.b.sendCommand(somfy_commands::Up, 1, 0, r.now);
ASSERT_POS(100, r.st().target);
ASSERT_POS(100, r.st().currentPos);
ASSERT_POS(0, r.st().tiltTarget);
static void tiltOnlyCommand(Rig &r, Source src, somfy_commands cmd) {
switch(src) {
case Source::web:
r.b.sendCommand(cmd, 1, 0, r.now);
r.onAir();
break;
case Source::remote:
r.press(cmd);
// Past the wait that tells a short press from a long one.
r.wait(600);
break;
case Source::group:
r.b.onGroupCommand(cmd, 1, r.now);
break;
}
}
static void test_today_up_on_tilt_only_from_a_remote() {
// today: a remote only sets the tilt target, where the web and groups also
// put the lift at 100.
Rig r;
tiltOnly(r);
r.b.restore(60, 50, -1, -1);
r.press(somfy_commands::Up);
ASSERT_POS(60, r.st().target);
ASSERT_POS(0, r.st().tiltTarget);
}
static void test_today_up_on_tilt_only_from_a_group() {
Rig r;
tiltOnly(r);
r.b.restore(60, 50, -1, -1);
r.b.onGroupCommand(somfy_commands::Up, 1, r.now);
ASSERT_POS(100, r.st().target);
ASSERT_POS(100, r.st().currentPos);
ASSERT_POS(0, r.st().tiltTarget);
}
static void test_today_my_while_tilt_only_moves() {
// today: a group's My puts the lift target at 100, a remote's leaves it.
Rig g;
tiltOnly(g);
g.b.restore(60, 50, -1, -1);
g.b.onGroupCommand(somfy_commands::Down, 1, g.now);
g.run(1000);
g.b.onGroupCommand(somfy_commands::My, 1, g.now);
ASSERT_POS(100, g.st().target);
Rig r;
tiltOnly(r);
r.b.restore(60, 50, -1, -1);
r.press(somfy_commands::Down);
r.run(1000);
r.press(somfy_commands::My);
ASSERT_POS(60, r.st().target);
static void test_tilt_only_lift_stays_down_whatever_the_source() {
const Source sources[] = {Source::web, Source::remote, Source::group};
const somfy_commands cmds[] = {somfy_commands::Up, somfy_commands::Down, somfy_commands::My};
for(Source src : sources) {
for(somfy_commands cmd : cmds) {
for(bool moving : {false, true}) {
Rig r;
tiltOnly(r, moving);
tiltOnlyCommand(r, src, cmd);
r.run(1000);
char msg[64];
snprintf(msg, sizeof(msg), "%s %s%s", sourceName(src), translateSomfyCommand(cmd).c_str(), moving ? " while moving" : "");
TEST_ASSERT_FLOAT_WITHIN_MESSAGE(0.5f, 100, r.st().target, msg);
TEST_ASSERT_FLOAT_WITHIN_MESSAGE(0.5f, 100, r.st().currentPos, msg);
TEST_ASSERT_EQUAL_INT8_MESSAGE(0, r.st().direction, msg);
// What the tilt does.
if(cmd == somfy_commands::Up) TEST_ASSERT_FLOAT_WITHIN_MESSAGE(0.5f, 0, r.st().tiltTarget, msg);
else if(cmd == somfy_commands::Down) TEST_ASSERT_FLOAT_WITHIN_MESSAGE(0.5f, 100, r.st().tiltTarget, msg);
else if(!moving) TEST_ASSERT_FLOAT_WITHIN_MESSAGE(0.5f, 30, r.st().tiltTarget, msg);
}
}
}
}
/*********************************************************************
@ -650,10 +658,7 @@ int main(int argc, char **argv) {
RUN_TEST(test_sun_flag_frame_turns_it_on_and_saves);
RUN_TEST(test_integrated_tilt_turns_the_slats_first);
RUN_TEST(test_today_integrated_tilt_turns_back_after_a_lift_target);
RUN_TEST(test_today_up_on_tilt_only_from_the_web);
RUN_TEST(test_today_up_on_tilt_only_from_a_remote);
RUN_TEST(test_today_up_on_tilt_only_from_a_group);
RUN_TEST(test_today_my_while_tilt_only_moves);
RUN_TEST(test_tilt_only_lift_stays_down_whatever_the_source);
RUN_TEST(test_group_down_and_step);
RUN_TEST(test_group_sun_flag_needs_a_sensor);
RUN_TEST(test_set_favorite_here_is_committed_when_all_on_air);