mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 13:52:15 +01:00
The repeater-frame flag was called raw and the ticket was seq, jobSeq, stopSeq or mySeq depending on the file. They are now repeater and ticket (stopTicket, myTicket, lastTicket) throughout, matching the glossary. Renames only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
245 lines
9.3 KiB
C++
245 lines
9.3 KiB
C++
#include <unity.h>
|
|
#include "ShadeMotion.h"
|
|
|
|
#define ASSERT_POS(expected, actual) TEST_ASSERT_FLOAT_WITHIN(0.01f, (expected), (actual))
|
|
|
|
static const int8_t DOWN = 1;
|
|
static const int8_t UP = -1;
|
|
static const bool IDLE = false;
|
|
static const bool MOVING = true;
|
|
|
|
static somfy_tx_event_t evt(tx_event_t type, uint32_t ticket, uint32_t at = 0) {
|
|
somfy_tx_event_t e;
|
|
e.type = type;
|
|
e.ticket = ticket;
|
|
e.at = at;
|
|
return e;
|
|
}
|
|
// A shade whose idle motor was set moving by radio job ticket.
|
|
static ShadeTxState heldBy(uint32_t ticket) {
|
|
ShadeTxState tx;
|
|
tx.hold.mark(ticket, false, 0);
|
|
return tx;
|
|
}
|
|
|
|
void setUp() {}
|
|
void tearDown() {}
|
|
|
|
/*********************************************************************
|
|
* The job that set the shade moving
|
|
********************************************************************/
|
|
static void test_held_job_started_starts_the_movement() {
|
|
ShadeTxState tx = heldBy(7);
|
|
ShadeTxActions act = tx.onEvent(evt(tx_event_t::started, 7, 1234), MOVING);
|
|
TEST_ASSERT_TRUE(act.startMovement);
|
|
TEST_ASSERT_EQUAL_UINT32(1234, act.startAt);
|
|
TEST_ASSERT_FALSE(act.cancelMovement);
|
|
TEST_ASSERT_FALSE(tx.hold.held());
|
|
}
|
|
static void test_held_job_finished_after_start_does_nothing() {
|
|
ShadeTxState tx = heldBy(7);
|
|
tx.onEvent(evt(tx_event_t::started, 7, 1234), MOVING);
|
|
ShadeTxActions act = tx.onEvent(evt(tx_event_t::finished, 7, 2000), MOVING);
|
|
TEST_ASSERT_FALSE(act.any());
|
|
}
|
|
static void test_held_job_dropped_cancels_the_movement() {
|
|
ShadeTxState tx = heldBy(7);
|
|
ShadeTxActions act = tx.onEvent(evt(tx_event_t::dropped, 7), MOVING);
|
|
TEST_ASSERT_TRUE(act.cancelMovement);
|
|
TEST_ASSERT_FALSE(act.startMovement);
|
|
TEST_ASSERT_FALSE(tx.hold.held());
|
|
}
|
|
static void test_held_job_aborted_before_start_cancels_the_movement() {
|
|
ShadeTxState tx = heldBy(7);
|
|
ShadeTxActions act = tx.onEvent(evt(tx_event_t::aborted, 7), MOVING);
|
|
TEST_ASSERT_TRUE(act.cancelMovement);
|
|
}
|
|
|
|
/*********************************************************************
|
|
* The favorite (long press My)
|
|
********************************************************************/
|
|
static void test_favorite_started_waits_for_the_end() {
|
|
ShadeTxState tx;
|
|
tx.setMySent(9, 40.0f, 10.0f);
|
|
ShadeTxActions act = tx.onEvent(evt(tx_event_t::started, 9), IDLE);
|
|
TEST_ASSERT_FALSE(act.any());
|
|
TEST_ASSERT_EQUAL_UINT32(9, tx.myTicket);
|
|
}
|
|
static void test_favorite_finished_is_committed() {
|
|
ShadeTxState tx;
|
|
tx.setMySent(9, 40.0f, 10.0f);
|
|
tx.onEvent(evt(tx_event_t::started, 9), IDLE);
|
|
ShadeTxActions act = tx.onEvent(evt(tx_event_t::finished, 9), IDLE);
|
|
TEST_ASSERT_TRUE(act.commitMy);
|
|
TEST_ASSERT_FALSE(act.restoreMy);
|
|
TEST_ASSERT_EQUAL_UINT32(0, tx.myTicket);
|
|
}
|
|
static void test_favorite_aborted_restores_the_previous_one() {
|
|
// The motor only stores the favorite when it gets the whole long press.
|
|
ShadeTxState tx;
|
|
tx.setMySent(9, 40.0f, 10.0f);
|
|
tx.onEvent(evt(tx_event_t::started, 9), IDLE);
|
|
ShadeTxActions act = tx.onEvent(evt(tx_event_t::aborted, 9), IDLE);
|
|
TEST_ASSERT_TRUE(act.restoreMy);
|
|
TEST_ASSERT_FALSE(act.commitMy);
|
|
ASSERT_POS(40.0f, act.myOldPos);
|
|
ASSERT_POS(10.0f, act.myOldTiltPos);
|
|
TEST_ASSERT_EQUAL_UINT32(0, tx.myTicket);
|
|
}
|
|
static void test_favorite_dropped_restores_the_previous_one() {
|
|
ShadeTxState tx;
|
|
tx.setMySent(9, -1.0f, -1.0f);
|
|
ShadeTxActions act = tx.onEvent(evt(tx_event_t::dropped, 9), IDLE);
|
|
TEST_ASSERT_TRUE(act.restoreMy);
|
|
ASSERT_POS(-1.0f, act.myOldPos);
|
|
ASSERT_POS(-1.0f, act.myOldTiltPos);
|
|
}
|
|
|
|
/*********************************************************************
|
|
* The stop sent at the target
|
|
********************************************************************/
|
|
static void test_stop_dropped_runs_on_to_the_limit() {
|
|
ShadeTxState tx;
|
|
tx.stopSent(12, 5000, DOWN, false);
|
|
ShadeTxActions act = tx.onEvent(evt(tx_event_t::dropped, 12, 5300), IDLE);
|
|
TEST_ASSERT_TRUE(act.runToLimit);
|
|
TEST_ASSERT_EQUAL_INT8(DOWN, act.limitDir);
|
|
TEST_ASSERT_FALSE(act.limitTilt);
|
|
// From when the motor should have stopped, not when the drop was reported.
|
|
TEST_ASSERT_EQUAL_UINT32(5000, act.limitSince);
|
|
TEST_ASSERT_EQUAL_UINT32(0, tx.stopTicket);
|
|
}
|
|
static void test_tilt_stop_dropped_runs_the_tilt_on() {
|
|
ShadeTxState tx;
|
|
tx.stopSent(12, 5000, UP, true);
|
|
ShadeTxActions act = tx.onEvent(evt(tx_event_t::dropped, 12), IDLE);
|
|
TEST_ASSERT_TRUE(act.runToLimit);
|
|
TEST_ASSERT_EQUAL_INT8(UP, act.limitDir);
|
|
TEST_ASSERT_TRUE(act.limitTilt);
|
|
}
|
|
static void test_stop_dropped_after_moving_again_does_nothing() {
|
|
// Something set the shade moving since, so that command decides where the
|
|
// motor goes.
|
|
ShadeTxState tx;
|
|
tx.stopSent(12, 5000, DOWN, false);
|
|
ShadeTxActions act = tx.onEvent(evt(tx_event_t::dropped, 12), MOVING);
|
|
TEST_ASSERT_FALSE(act.any());
|
|
TEST_ASSERT_EQUAL_UINT32(0, tx.stopTicket);
|
|
}
|
|
static void test_stop_on_air_does_nothing() {
|
|
ShadeTxState tx;
|
|
tx.stopSent(12, 5000, DOWN, false);
|
|
TEST_ASSERT_FALSE(tx.onEvent(evt(tx_event_t::started, 12), IDLE).any());
|
|
TEST_ASSERT_EQUAL_UINT32(0, tx.stopTicket);
|
|
// Nothing is waiting on it any more.
|
|
TEST_ASSERT_FALSE(tx.onEvent(evt(tx_event_t::dropped, 12), IDLE).any());
|
|
}
|
|
static void test_stop_aborted_does_not_run_on() {
|
|
// Part of the stop went on air, which is enough for the motor.
|
|
ShadeTxState tx;
|
|
tx.stopSent(12, 5000, DOWN, false);
|
|
TEST_ASSERT_FALSE(tx.onEvent(evt(tx_event_t::aborted, 12), IDLE).any());
|
|
}
|
|
|
|
/*********************************************************************
|
|
* Edge cases
|
|
********************************************************************/
|
|
static void test_one_event_for_the_held_job_and_the_favorite() {
|
|
// The delayed My that stores the favorite also sets an idle shade moving,
|
|
// so the same job is held and awaited as the favorite.
|
|
ShadeTxState tx = heldBy(9);
|
|
tx.setMySent(9, 40.0f, 10.0f);
|
|
ShadeTxActions act = tx.onEvent(evt(tx_event_t::dropped, 9), MOVING);
|
|
TEST_ASSERT_TRUE(act.cancelMovement);
|
|
TEST_ASSERT_TRUE(act.restoreMy);
|
|
TEST_ASSERT_FALSE(tx.hold.held());
|
|
TEST_ASSERT_EQUAL_UINT32(0, tx.myTicket);
|
|
}
|
|
static void test_seq_zero_is_ignored() {
|
|
ShadeTxState tx;
|
|
tx.setMySent(0, 40.0f, 10.0f);
|
|
tx.stopSent(0, 5000, DOWN, false);
|
|
TEST_ASSERT_FALSE(tx.onEvent(evt(tx_event_t::dropped, 0), IDLE).any());
|
|
TEST_ASSERT_FALSE(tx.onEvent(evt(tx_event_t::finished, 0), IDLE).any());
|
|
}
|
|
static void test_other_jobs_are_ignored() {
|
|
ShadeTxState tx = heldBy(7);
|
|
tx.setMySent(9, 40.0f, 10.0f);
|
|
tx.stopSent(12, 5000, DOWN, false);
|
|
TEST_ASSERT_FALSE(tx.onEvent(evt(tx_event_t::dropped, 8), IDLE).any());
|
|
TEST_ASSERT_FALSE(tx.onEvent(evt(tx_event_t::finished, 13), IDLE).any());
|
|
TEST_ASSERT_EQUAL_UINT32(7, tx.hold.ticket);
|
|
TEST_ASSERT_EQUAL_UINT32(9, tx.myTicket);
|
|
TEST_ASSERT_EQUAL_UINT32(12, tx.stopTicket);
|
|
}
|
|
|
|
/*********************************************************************
|
|
* limitTarget
|
|
********************************************************************/
|
|
static void test_limitTarget() {
|
|
ASSERT_POS(100.0f, limitTarget(DOWN));
|
|
ASSERT_POS(0.0f, limitTarget(UP));
|
|
}
|
|
|
|
/*********************************************************************
|
|
* TxHold::check
|
|
********************************************************************/
|
|
static void test_check_nothing_held() {
|
|
TxHold hold;
|
|
TEST_ASSERT_TRUE(hold.check(true, 1000, 30000) == TxHoldCheck::none);
|
|
}
|
|
static void test_check_idle_shade_drops_the_hold() {
|
|
// The command didn't set the shade moving so there is nothing to hold.
|
|
TxHold hold;
|
|
hold.mark(5, false, 1000);
|
|
TEST_ASSERT_TRUE(hold.check(false, 1000, 30000) == TxHoldCheck::none);
|
|
TEST_ASSERT_FALSE(hold.held());
|
|
}
|
|
static void test_check_holds_a_moving_shade() {
|
|
TxHold hold;
|
|
hold.mark(5, false, 1000);
|
|
TEST_ASSERT_TRUE(hold.check(true, 30999, 30000) == TxHoldCheck::holding);
|
|
TEST_ASSERT_TRUE(hold.held());
|
|
}
|
|
static void test_check_expires() {
|
|
TxHold hold;
|
|
hold.mark(5, false, 1000);
|
|
TEST_ASSERT_TRUE(hold.check(true, 31000, 30000) == TxHoldCheck::expired);
|
|
TEST_ASSERT_FALSE(hold.held());
|
|
TEST_ASSERT_TRUE(hold.check(true, 31001, 30000) == TxHoldCheck::none);
|
|
}
|
|
static void test_check_across_millis_wrap() {
|
|
TxHold hold;
|
|
hold.mark(5, false, 0xFFFFF000u);
|
|
TEST_ASSERT_TRUE(hold.check(true, 0x00006000u, 30000) == TxHoldCheck::holding);
|
|
TEST_ASSERT_TRUE(hold.check(true, 0x00007000u, 30000) == TxHoldCheck::expired);
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
(void)argc;
|
|
(void)argv;
|
|
UNITY_BEGIN();
|
|
RUN_TEST(test_held_job_started_starts_the_movement);
|
|
RUN_TEST(test_held_job_finished_after_start_does_nothing);
|
|
RUN_TEST(test_held_job_dropped_cancels_the_movement);
|
|
RUN_TEST(test_held_job_aborted_before_start_cancels_the_movement);
|
|
RUN_TEST(test_favorite_started_waits_for_the_end);
|
|
RUN_TEST(test_favorite_finished_is_committed);
|
|
RUN_TEST(test_favorite_aborted_restores_the_previous_one);
|
|
RUN_TEST(test_favorite_dropped_restores_the_previous_one);
|
|
RUN_TEST(test_stop_dropped_runs_on_to_the_limit);
|
|
RUN_TEST(test_tilt_stop_dropped_runs_the_tilt_on);
|
|
RUN_TEST(test_stop_dropped_after_moving_again_does_nothing);
|
|
RUN_TEST(test_stop_on_air_does_nothing);
|
|
RUN_TEST(test_stop_aborted_does_not_run_on);
|
|
RUN_TEST(test_one_event_for_the_held_job_and_the_favorite);
|
|
RUN_TEST(test_seq_zero_is_ignored);
|
|
RUN_TEST(test_other_jobs_are_ignored);
|
|
RUN_TEST(test_limitTarget);
|
|
RUN_TEST(test_check_nothing_held);
|
|
RUN_TEST(test_check_idle_shade_drops_the_hold);
|
|
RUN_TEST(test_check_holds_a_moving_shade);
|
|
RUN_TEST(test_check_expires);
|
|
RUN_TEST(test_check_across_millis_wrap);
|
|
return UNITY_END();
|
|
}
|