ESPSomfy-RTS/test/test_txqueue/test_txqueue.cpp
cjkas 1962778912 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>
2026-10-12 20:26:33 +02:00

216 lines
8 KiB
C++

#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();
}