mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 22:02:17 +01:00
test: cover the radio queue rules, shade TX state, rolling codes and web command hold
Move the pieces of the radio TX path that don't need FreeRTOS or NVS out of Somfy.cpp and Web.cpp so they build in the native test env: the TX job and event types and job encoding (TxBuffer), the queue rules (TxQueuePolicy.h), a shade's pending radio jobs (ShadeTxState), the next rolling code (RollingCode.h) and the web command queue types (WebCommand.h). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6a3769406f
commit
1962778912
15 changed files with 912 additions and 186 deletions
59
test/test_rollingcode/test_rollingcode.cpp
Normal file
59
test/test_rollingcode/test_rollingcode.cpp
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#include <unity.h>
|
||||
#include "RollingCode.h"
|
||||
|
||||
// Stands in for NVS and counts how often it is read.
|
||||
struct FakeStore {
|
||||
uint16_t code = 0;
|
||||
int reads = 0;
|
||||
uint16_t operator()() { this->reads++; return this->code; }
|
||||
};
|
||||
// Passes the store by reference so the test sees the read count.
|
||||
static uint16_t next(bool synced, uint16_t last, FakeStore &store) {
|
||||
return nextRollingCode(synced, last, [&store]() { return store(); });
|
||||
}
|
||||
|
||||
void setUp() {}
|
||||
void tearDown() {}
|
||||
|
||||
static void test_unsynced_reads_the_store() {
|
||||
FakeStore store;
|
||||
store.code = 41;
|
||||
TEST_ASSERT_EQUAL_UINT16(42, next(false, 7, store));
|
||||
TEST_ASSERT_EQUAL_INT(1, store.reads);
|
||||
}
|
||||
static void test_synced_never_reads_the_store() {
|
||||
FakeStore store;
|
||||
store.code = 41;
|
||||
TEST_ASSERT_EQUAL_UINT16(8, next(true, 7, store));
|
||||
TEST_ASSERT_EQUAL_INT(0, store.reads);
|
||||
}
|
||||
static void test_reading_does_not_use_up_a_code() {
|
||||
// A command the radio refuses must leave the remote on the same code.
|
||||
FakeStore store;
|
||||
store.code = 41;
|
||||
TEST_ASSERT_EQUAL_UINT16(42, next(false, 0, store));
|
||||
TEST_ASSERT_EQUAL_UINT16(42, next(false, 0, store));
|
||||
TEST_ASSERT_EQUAL_UINT16(8, next(true, 7, store));
|
||||
TEST_ASSERT_EQUAL_UINT16(8, next(true, 7, store));
|
||||
}
|
||||
static void test_wraps_to_zero_after_0xFFFF() {
|
||||
// Current behaviour, kept on purpose for now: the code after 0xFFFF is 0.
|
||||
// Our own decoder rejects a zero rolling code (see
|
||||
// test_rejects_zero_rolling_code_except_sensor in test_frame), so a remote
|
||||
// that gets this far sends one frame other receivers may ignore.
|
||||
FakeStore store;
|
||||
store.code = 0xFFFF;
|
||||
TEST_ASSERT_EQUAL_UINT16(0, next(false, 0, store));
|
||||
TEST_ASSERT_EQUAL_UINT16(0, next(true, 0xFFFF, store));
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_unsynced_reads_the_store);
|
||||
RUN_TEST(test_synced_never_reads_the_store);
|
||||
RUN_TEST(test_reading_does_not_use_up_a_code);
|
||||
RUN_TEST(test_wraps_to_zero_after_0xFFFF);
|
||||
return UNITY_END();
|
||||
}
|
||||
245
test/test_shadetx/test_shadetx.cpp
Normal file
245
test/test_shadetx/test_shadetx.cpp
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
#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 seq, uint32_t at = 0) {
|
||||
somfy_tx_event_t e;
|
||||
e.type = type;
|
||||
e.seq = seq;
|
||||
e.at = at;
|
||||
return e;
|
||||
}
|
||||
// A shade whose idle motor was set moving by radio job seq.
|
||||
static ShadeTxState heldBy(uint32_t seq) {
|
||||
ShadeTxState tx;
|
||||
tx.hold.mark(seq, 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.mySeq);
|
||||
}
|
||||
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.mySeq);
|
||||
}
|
||||
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.mySeq);
|
||||
}
|
||||
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.stopSeq);
|
||||
}
|
||||
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.stopSeq);
|
||||
}
|
||||
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.stopSeq);
|
||||
// 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.mySeq);
|
||||
}
|
||||
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.seq);
|
||||
TEST_ASSERT_EQUAL_UINT32(9, tx.mySeq);
|
||||
TEST_ASSERT_EQUAL_UINT32(12, tx.stopSeq);
|
||||
}
|
||||
|
||||
/*********************************************************************
|
||||
* 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();
|
||||
}
|
||||
216
test/test_txqueue/test_txqueue.cpp
Normal file
216
test/test_txqueue/test_txqueue.cpp
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
#include <unity.h>
|
||||
#include "TxQueuePolicy.h"
|
||||
#include "TxBuffer.h"
|
||||
|
||||
static const uint32_t ADDR = 0x123456;
|
||||
|
||||
// A frame as SomfyRemote::sendCommandAfter builds it.
|
||||
static somfy_frame_t makeFrame(somfy_commands cmd, uint8_t bitLength = 56) {
|
||||
somfy_frame_t f;
|
||||
f.cmd = cmd;
|
||||
f.remoteAddress = ADDR;
|
||||
f.rollingCode = 0x0102;
|
||||
f.encKey = 0xA2;
|
||||
f.bitLength = bitLength;
|
||||
f.valid = true;
|
||||
return f;
|
||||
}
|
||||
|
||||
void setUp() {}
|
||||
void tearDown() {}
|
||||
|
||||
/*********************************************************************
|
||||
* txAdmits
|
||||
********************************************************************/
|
||||
static void test_normal_job_leaves_the_reserved_slots() {
|
||||
TEST_ASSERT_FALSE(txAdmits(0, false));
|
||||
TEST_ASSERT_FALSE(txAdmits(TX_RESERVED_SLOTS, false));
|
||||
TEST_ASSERT_TRUE(txAdmits(TX_RESERVED_SLOTS + 1, false));
|
||||
TEST_ASSERT_TRUE(txAdmits(TX_JOB_QUEUE_SIZE, false));
|
||||
}
|
||||
static void test_priority_job_uses_the_reserved_slots() {
|
||||
TEST_ASSERT_FALSE(txAdmits(0, true));
|
||||
TEST_ASSERT_TRUE(txAdmits(1, true));
|
||||
TEST_ASSERT_TRUE(txAdmits(TX_RESERVED_SLOTS, true));
|
||||
}
|
||||
|
||||
/*********************************************************************
|
||||
* Sequence numbers
|
||||
********************************************************************/
|
||||
static void test_nextTxSeq_counts_up() {
|
||||
TEST_ASSERT_EQUAL_UINT32(1, nextTxSeq(0));
|
||||
TEST_ASSERT_EQUAL_UINT32(2, nextTxSeq(1));
|
||||
}
|
||||
static void test_nextTxSeq_skips_zero_on_wrap() {
|
||||
TEST_ASSERT_EQUAL_UINT32(1, nextTxSeq(0xFFFFFFFFu));
|
||||
}
|
||||
static void test_txSeqQueued_zero_is_never_queued() {
|
||||
TEST_ASSERT_FALSE(txSeqQueued(0, 0));
|
||||
TEST_ASSERT_FALSE(txSeqQueued(0, 10));
|
||||
TEST_ASSERT_FALSE(txSeqQueued(0, 0xFFFFFFF0u));
|
||||
}
|
||||
static void test_txSeqQueued_after_taken() {
|
||||
TEST_ASSERT_FALSE(txSeqQueued(9, 10));
|
||||
TEST_ASSERT_FALSE(txSeqQueued(10, 10));
|
||||
TEST_ASSERT_TRUE(txSeqQueued(11, 10));
|
||||
}
|
||||
static void test_txSeqQueued_across_wrap() {
|
||||
// Job 5 was numbered after the counter wrapped, so it is still waiting.
|
||||
TEST_ASSERT_TRUE(txSeqQueued(5, 0xFFFFFFF0u));
|
||||
TEST_ASSERT_TRUE(txSeqQueued(0xFFFFFFFFu, 0xFFFFFFF0u));
|
||||
TEST_ASSERT_FALSE(txSeqQueued(0xFFFFFFF0u, 5));
|
||||
}
|
||||
static void test_txSeqAfter_never_moves_back() {
|
||||
// A flush marks every job up to 20 as taken. The radio task then reports
|
||||
// job 15, which it took off the queue before the flush. That must not move
|
||||
// the mark back or jobs 16-20 would look queued again.
|
||||
uint32_t taken = 20;
|
||||
if(txSeqAfter(15, taken)) taken = 15;
|
||||
TEST_ASSERT_EQUAL_UINT32(20, taken);
|
||||
if(txSeqAfter(21, taken)) taken = 21;
|
||||
TEST_ASSERT_EQUAL_UINT32(21, taken);
|
||||
TEST_ASSERT_FALSE(txSeqAfter(21, 21));
|
||||
}
|
||||
|
||||
/*********************************************************************
|
||||
* stopGoesToFront
|
||||
********************************************************************/
|
||||
static void test_stop_goes_to_front_when_nothing_waits() {
|
||||
TEST_ASSERT_TRUE(stopGoesToFront(true, false));
|
||||
}
|
||||
static void test_stop_queues_behind_its_remote() {
|
||||
// Overtaking the remote's waiting frame would send the rolling codes out of
|
||||
// order.
|
||||
TEST_ASSERT_FALSE(stopGoesToFront(true, true));
|
||||
}
|
||||
static void test_normal_job_never_goes_to_front() {
|
||||
TEST_ASSERT_FALSE(stopGoesToFront(false, false));
|
||||
TEST_ASSERT_FALSE(stopGoesToFront(false, true));
|
||||
}
|
||||
|
||||
/*********************************************************************
|
||||
* txDueInMs
|
||||
********************************************************************/
|
||||
static void test_txDueInMs() {
|
||||
TEST_ASSERT_EQUAL_INT32(500, txDueInMs(1500, 1000));
|
||||
TEST_ASSERT_EQUAL_INT32(0, txDueInMs(1000, 1000));
|
||||
TEST_ASSERT_EQUAL_INT32(-200, txDueInMs(1000, 1200));
|
||||
}
|
||||
static void test_txDueInMs_across_millis_wrap() {
|
||||
// Queued 0x100ms before the wrap with a 0x200ms delay.
|
||||
const uint32_t notBefore = 0xFFFFFF00u + 0x200u;
|
||||
TEST_ASSERT_EQUAL_INT32(0x200, txDueInMs(notBefore, 0xFFFFFF00u));
|
||||
TEST_ASSERT_EQUAL_INT32(0x100, txDueInMs(notBefore, 0));
|
||||
TEST_ASSERT_EQUAL_INT32(-0x10, txDueInMs(notBefore, 0x110u));
|
||||
}
|
||||
|
||||
/*********************************************************************
|
||||
* encodeTxJob
|
||||
********************************************************************/
|
||||
static void test_encode_56bit_job() {
|
||||
somfy_frame_t f = makeFrame(somfy_commands::Up);
|
||||
somfy_tx_job_t job;
|
||||
encodeTxJob(job, f, 3);
|
||||
byte expected[10] = {};
|
||||
somfy_frame_t copy = makeFrame(somfy_commands::Up);
|
||||
copy.encodeFrame(expected);
|
||||
TEST_ASSERT_EQUAL_UINT8(2, job.tx.hwsync);
|
||||
TEST_ASSERT_EQUAL_UINT8(56, job.tx.bit_length);
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, job.tx.payload, sizeof(expected));
|
||||
TEST_ASSERT_EQUAL_UINT8(3, job.repeats);
|
||||
}
|
||||
static void test_encode_80bit_job() {
|
||||
somfy_frame_t f = makeFrame(somfy_commands::Down, 80);
|
||||
somfy_tx_job_t job;
|
||||
encodeTxJob(job, f, 1);
|
||||
byte expected[10] = {};
|
||||
somfy_frame_t copy = makeFrame(somfy_commands::Down, 80);
|
||||
copy.encodeFrame(expected);
|
||||
TEST_ASSERT_EQUAL_UINT8(12, job.tx.hwsync);
|
||||
TEST_ASSERT_EQUAL_UINT8(80, job.tx.bit_length);
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, job.tx.payload, sizeof(expected));
|
||||
}
|
||||
static void test_encode_job_copies_the_frame() {
|
||||
somfy_frame_t f = makeFrame(somfy_commands::My);
|
||||
somfy_tx_job_t job;
|
||||
encodeTxJob(job, f, 1);
|
||||
TEST_ASSERT_EQUAL_UINT32(ADDR, job.frame.remoteAddress);
|
||||
TEST_ASSERT_EQUAL_UINT16(0x0102, job.frame.rollingCode);
|
||||
// The radio task uses its own copy, so later changes to the remote's frame
|
||||
// don't reach it.
|
||||
f.rollingCode = 0x0200;
|
||||
TEST_ASSERT_EQUAL_UINT16(0x0102, job.frame.rollingCode);
|
||||
}
|
||||
static void test_encode_job_limits_repeats() {
|
||||
somfy_frame_t f = makeFrame(somfy_commands::My);
|
||||
somfy_tx_job_t job;
|
||||
encodeTxJob(job, f, TX_MAX_REPEATS);
|
||||
TEST_ASSERT_EQUAL_UINT8(TX_MAX_REPEATS, job.repeats);
|
||||
encodeTxJob(job, f, TX_MAX_REPEATS + 1);
|
||||
TEST_ASSERT_EQUAL_UINT8(TX_MAX_REPEATS, job.repeats);
|
||||
encodeTxJob(job, f, 255);
|
||||
TEST_ASSERT_EQUAL_UINT8(TX_MAX_REPEATS, job.repeats);
|
||||
encodeTxJob(job, f, 0);
|
||||
TEST_ASSERT_EQUAL_UINT8(0, job.repeats);
|
||||
}
|
||||
|
||||
/*********************************************************************
|
||||
* Repeat jobs
|
||||
********************************************************************/
|
||||
static void test_repeat_job_counts_on_from_the_frame() {
|
||||
somfy_frame_t f = makeFrame(somfy_commands::Up, 80);
|
||||
f.repeats = 4;
|
||||
somfy_tx_job_t job;
|
||||
encodeRepeatJob(job, f, 2);
|
||||
TEST_ASSERT_EQUAL_UINT8(6, job.firstRepeat);
|
||||
TEST_ASSERT_EQUAL_UINT8(2, job.repeats);
|
||||
// Nothing moves until the radio takes the job.
|
||||
TEST_ASSERT_EQUAL_UINT8(4, f.repeats);
|
||||
}
|
||||
static void test_repeat_queued_advances_the_counter() {
|
||||
somfy_frame_t f = makeFrame(somfy_commands::Up, 80);
|
||||
f.repeats = 4;
|
||||
somfy_tx_job_t job;
|
||||
encodeRepeatJob(job, f, 2);
|
||||
noteRepeatQueued(f, job);
|
||||
// The first frame plus two repeats went out.
|
||||
TEST_ASSERT_EQUAL_UINT8(7, f.repeats);
|
||||
// The next repeat job picks up where this one stops.
|
||||
somfy_tx_job_t next;
|
||||
encodeRepeatJob(next, f, 1);
|
||||
TEST_ASSERT_EQUAL_UINT8(9, next.firstRepeat);
|
||||
}
|
||||
static void test_repeat_queued_uses_the_limited_repeats() {
|
||||
somfy_frame_t f = makeFrame(somfy_commands::Up);
|
||||
somfy_tx_job_t job;
|
||||
encodeRepeatJob(job, f, 200);
|
||||
noteRepeatQueued(f, job);
|
||||
TEST_ASSERT_EQUAL_UINT8(TX_MAX_REPEATS + 1, f.repeats);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_normal_job_leaves_the_reserved_slots);
|
||||
RUN_TEST(test_priority_job_uses_the_reserved_slots);
|
||||
RUN_TEST(test_nextTxSeq_counts_up);
|
||||
RUN_TEST(test_nextTxSeq_skips_zero_on_wrap);
|
||||
RUN_TEST(test_txSeqQueued_zero_is_never_queued);
|
||||
RUN_TEST(test_txSeqQueued_after_taken);
|
||||
RUN_TEST(test_txSeqQueued_across_wrap);
|
||||
RUN_TEST(test_txSeqAfter_never_moves_back);
|
||||
RUN_TEST(test_stop_goes_to_front_when_nothing_waits);
|
||||
RUN_TEST(test_stop_queues_behind_its_remote);
|
||||
RUN_TEST(test_normal_job_never_goes_to_front);
|
||||
RUN_TEST(test_txDueInMs);
|
||||
RUN_TEST(test_txDueInMs_across_millis_wrap);
|
||||
RUN_TEST(test_encode_56bit_job);
|
||||
RUN_TEST(test_encode_80bit_job);
|
||||
RUN_TEST(test_encode_job_copies_the_frame);
|
||||
RUN_TEST(test_encode_job_limits_repeats);
|
||||
RUN_TEST(test_repeat_job_counts_on_from_the_frame);
|
||||
RUN_TEST(test_repeat_queued_advances_the_counter);
|
||||
RUN_TEST(test_repeat_queued_uses_the_limited_repeats);
|
||||
return UNITY_END();
|
||||
}
|
||||
48
test/test_webcmd/test_webcmd.cpp
Normal file
48
test/test_webcmd/test_webcmd.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#include <unity.h>
|
||||
#include "WebCommand.h"
|
||||
|
||||
static const web_cmd_t RADIO_COMMANDS[] = {
|
||||
web_cmd_t::shade_command, web_cmd_t::group_command, web_cmd_t::tilt_command,
|
||||
web_cmd_t::shade_repeat, web_cmd_t::group_repeat, web_cmd_t::shade_sensor,
|
||||
web_cmd_t::group_sensor, web_cmd_t::set_my_position, web_cmd_t::unpair_shade
|
||||
};
|
||||
|
||||
void setUp() {}
|
||||
void tearDown() {}
|
||||
|
||||
static void test_only_set_positions_stays_off_the_radio() {
|
||||
int offRadio = 0;
|
||||
for(uint8_t t = 0; t <= static_cast<uint8_t>(web_cmd_t::unpair_shade); t++) {
|
||||
if(!webCommandUsesRadio(static_cast<web_cmd_t>(t))) offRadio++;
|
||||
}
|
||||
TEST_ASSERT_EQUAL_INT(1, offRadio);
|
||||
TEST_ASSERT_FALSE(webCommandUsesRadio(web_cmd_t::set_positions));
|
||||
}
|
||||
static void test_set_positions_is_never_held() {
|
||||
TEST_ASSERT_FALSE(holdWebCommand(web_cmd_t::set_positions, false, false));
|
||||
TEST_ASSERT_FALSE(holdWebCommand(web_cmd_t::set_positions, true, false));
|
||||
}
|
||||
static void test_radio_command_waits_for_room() {
|
||||
for(web_cmd_t t : RADIO_COMMANDS) {
|
||||
TEST_ASSERT_TRUE(holdWebCommand(t, false, false));
|
||||
TEST_ASSERT_FALSE(holdWebCommand(t, false, true));
|
||||
}
|
||||
}
|
||||
static void test_nothing_is_held_while_suspended() {
|
||||
// Nothing goes on air during a frequency scan or OTA anyway.
|
||||
for(web_cmd_t t : RADIO_COMMANDS) {
|
||||
TEST_ASSERT_FALSE(holdWebCommand(t, true, false));
|
||||
TEST_ASSERT_FALSE(holdWebCommand(t, true, true));
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_only_set_positions_stays_off_the_radio);
|
||||
RUN_TEST(test_set_positions_is_never_held);
|
||||
RUN_TEST(test_radio_command_waits_for_room);
|
||||
RUN_TEST(test_nothing_is_held_while_suspended);
|
||||
return UNITY_END();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue