fix: keep a repeater frame until the radio queue takes it

The frame was popped before queueTx(), so whenever the radio queue was busy
it was lost.  Peek it instead, only remove it once it is queued (or TX is
suspended), and don't try while the queue has no room.  The repeater buffer
moves to TxBuffer.h/.cpp so it can be unit tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-12 18:31:39 +02:00
parent e1c76bc2af
commit 7e9687c023
6 changed files with 227 additions and 80 deletions

View file

@ -0,0 +1,114 @@
#include <unity.h>
#include "TxBuffer.h"
void setUp() {}
void tearDown() {}
// Pushes a frame whose payload is all `tag` so frames can be told apart.
static void pushTagged(somfy_tx_queue_t &q, uint8_t tag, uint8_t bitLength = 56) {
uint8_t payload[10];
memset(payload, tag, sizeof(payload));
q.push(bitLength == 56 ? 2 : 12, payload, bitLength);
}
static void assertTagged(const somfy_tx_t &tx, uint8_t tag) {
uint8_t expected[10];
memset(expected, tag, sizeof(expected));
TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, tx.payload, sizeof(expected));
}
static void test_push_then_pop_returns_the_frame() {
somfy_tx_queue_t q;
pushTagged(q, 0x11, 80);
TEST_ASSERT_EQUAL_UINT8(1, q.length);
somfy_tx_t tx;
TEST_ASSERT_TRUE(q.pop(&tx));
TEST_ASSERT_EQUAL_UINT8(12, tx.hwsync);
TEST_ASSERT_EQUAL_UINT8(80, tx.bit_length);
assertTagged(tx, 0x11);
TEST_ASSERT_EQUAL_UINT8(0, q.length);
}
static void test_frames_come_out_oldest_first() {
somfy_tx_queue_t q;
pushTagged(q, 1);
pushTagged(q, 2);
pushTagged(q, 3);
somfy_tx_t tx;
for(uint8_t tag = 1; tag <= 3; tag++) {
TEST_ASSERT_TRUE(q.pop(&tx));
assertTagged(tx, tag);
}
TEST_ASSERT_FALSE(q.pop(&tx));
}
static void test_peek_leaves_the_frame_queued() {
somfy_tx_queue_t q;
pushTagged(q, 1);
pushTagged(q, 2);
somfy_tx_t tx;
TEST_ASSERT_TRUE(q.peek(&tx));
assertTagged(tx, 1);
TEST_ASSERT_TRUE(q.peek(&tx));
assertTagged(tx, 1);
TEST_ASSERT_EQUAL_UINT8(2, q.length);
TEST_ASSERT_TRUE(q.pop(&tx));
assertTagged(tx, 1);
TEST_ASSERT_TRUE(q.peek(&tx));
assertTagged(tx, 2);
}
static void test_empty_buffer_has_nothing() {
somfy_tx_queue_t q;
somfy_tx_t tx;
TEST_ASSERT_FALSE(q.peek(&tx));
TEST_ASSERT_FALSE(q.pop(&tx));
TEST_ASSERT_EQUAL_UINT8(0, q.length);
}
static void test_overflow_drops_the_oldest() {
somfy_tx_queue_t q;
for(uint8_t tag = 1; tag <= MAX_TX_BUFFER + 2; tag++) pushTagged(q, tag);
TEST_ASSERT_EQUAL_UINT8(MAX_TX_BUFFER, q.length);
somfy_tx_t tx;
// The first two fell off.
for(uint8_t tag = 3; tag <= MAX_TX_BUFFER + 2; tag++) {
TEST_ASSERT_TRUE(q.pop(&tx));
assertTagged(tx, tag);
}
TEST_ASSERT_FALSE(q.pop(&tx));
}
static void test_slots_are_reused_after_pop() {
somfy_tx_queue_t q;
somfy_tx_t tx;
// Keep two frames in flight so freed slots are filled again many times over.
for(uint8_t tag = 1; tag <= 4 * MAX_TX_BUFFER; tag++) {
pushTagged(q, tag);
if(tag >= 2) {
TEST_ASSERT_TRUE(q.pop(&tx));
assertTagged(tx, tag - 1);
TEST_ASSERT_EQUAL_UINT8(1, q.length);
}
}
}
static void test_clear_empties_it() {
somfy_tx_queue_t q;
pushTagged(q, 1);
pushTagged(q, 2);
q.clear();
TEST_ASSERT_EQUAL_UINT8(0, q.length);
somfy_tx_t tx;
TEST_ASSERT_FALSE(q.peek(&tx));
pushTagged(q, 3);
TEST_ASSERT_TRUE(q.pop(&tx));
assertTagged(tx, 3);
}
int main(int argc, char **argv) {
(void)argc;
(void)argv;
UNITY_BEGIN();
RUN_TEST(test_push_then_pop_returns_the_frame);
RUN_TEST(test_frames_come_out_oldest_first);
RUN_TEST(test_peek_leaves_the_frame_queued);
RUN_TEST(test_empty_buffer_has_nothing);
RUN_TEST(test_overflow_drops_the_oldest);
RUN_TEST(test_slots_are_reused_after_pop);
RUN_TEST(test_clear_empties_it);
return UNITY_END();
}