STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_idle.c
Go to the documentation of this file.
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4
5#include "idle.h"
6
7#define CHECK(condition) \
8 do { \
9 if (!(condition)) { \
10 fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, #condition); \
11 exit(1); \
12 } \
13 } while (0)
14
15/* Counts that convert to an exact-ish voltage: mv * 65535 / 10624. */
16static uint32_t counts_for_mv(uint32_t mv) {
17 return mv * 65535U / 10624U + 1U;
18}
19
20typedef struct {
22 bool usb;
25 unsigned command_count;
26 unsigned command_next;
27 unsigned battery_calls;
28 unsigned send_calls;
29 unsigned poll_calls;
31 unsigned order[64]; /* 1 = battery, 2 = send, 3 = usb */
32 unsigned order_count;
34} fake_t;
35
36static void note(fake_t* f, unsigned what) {
37 if (f->order_count < 64) { /* long runs only care about the first calls */
38 f->order[f->order_count++] = what;
39 }
40}
41
42static self_test_battery_t fake_battery(void* context, uint32_t now_ms,
43 uint32_t budget_ms) {
44 fake_t* f = context;
45 CHECK(budget_ms == f->expected_budget);
46 ++f->battery_calls;
47 note(f, 1);
49 b.sampled_at_ms = now_ms;
50 return b;
51}
52
53static bool fake_usb(void* context) {
54 fake_t* f = context;
55 note(f, 3);
56 return f->usb;
57}
58
59static self_test_reply_t fake_send(void* context, const idle_heartbeat_t* hb,
60 uint32_t budget_ms) {
61 fake_t* f = context;
62 CHECK(budget_ms == f->expected_budget);
63 ++f->send_calls;
64 note(f, 2);
65 f->sent = *hb;
66 return f->send_reply;
67}
68
69static idle_command_t fake_poll(void* context) {
70 fake_t* f = context;
71 ++f->poll_calls;
72 if (f->command_next < f->command_count) {
73 return f->commands[f->command_next++];
74 }
75 return IDLE_CMD_NONE;
76}
77
79
80static idle_config_t config(void) {
81 return (idle_config_t){.heartbeat_interval_ms = 10000,
82 .wake_tolerance_ms = 500,
83 .callback_budget_ms = 50,
84 .battery_warn_mv = 7000,
85 .battery_fail_mv = 6600,
86 .battery_hysteresis_mv = 100};
87}
88
89static void set_mv(fake_t* f, uint32_t mv) {
90 f->battery = (self_test_battery_t){.reply = {SELF_TEST_PASS, 0},
91 .valid = true,
92 .raw_counts = counts_for_mv(mv)};
93}
94
95static void setup(idle_t* idle, fake_t* f, uint32_t now) {
96 memset(f, 0, sizeof(*f));
97 set_mv(f, 7800);
99 f->expected_budget = 50;
100 idle_config_t c = config();
101 CHECK(idle_init(idle, &c, &port, f));
102 CHECK(idle_enter(idle, now));
103}
104
105static void push_command(fake_t* f, idle_command_t cmd) {
106 CHECK(f->command_count < 16);
107 f->commands[f->command_count++] = cmd;
108}
109
110/* ---------------------------------------------------------------- tests */
111
112static void test_conversion(void) {
113 CHECK(idle_counts_to_mv(0) == 0);
114 CHECK(idle_counts_to_mv(65535) == 10624);
115 CHECK(idle_counts_to_mv(65536) == 0); /* out of 16-bit range */
116 /* From DEVICE_CHECKS: counts / 65535 * 3.32 / 0.3125. */
117 CHECK(idle_counts_to_mv(40712) == 6600);
118 CHECK(idle_counts_to_mv(43180) == 7000);
119 /* Full 2S pack, 8.4 V, stays inside the ADC range. */
120 CHECK(idle_counts_to_mv(51818) == 8400);
121}
122
124 idle_t idle;
125 fake_t f;
126 idle_config_t c = config();
128
129 CHECK(!idle_init(NULL, &c, &port, &f));
130 CHECK(!idle_init(&idle, NULL, &port, &f));
131 CHECK(!idle_init(&idle, &c, NULL, &f));
132
133 p.on_usb_power = NULL; /* USB detection is mandatory, never assumed */
134 CHECK(!idle_init(&idle, &c, &p, &f));
137
138 c = config();
139 c.battery_fail_mv = 7000; /* fail must be below warn */
140 CHECK(!idle_init(&idle, &c, &port, &f));
141 c = config();
142 c.wake_tolerance_ms = 10000; /* tolerance must be below interval */
143 CHECK(!idle_init(&idle, &c, &port, &f));
144 c = config();
145 c.heartbeat_interval_ms = 999;
146 CHECK(!idle_init(&idle, &c, &port, &f));
147 c = config();
148 c.callback_budget_ms = 0;
149 CHECK(!idle_init(&idle, &c, &port, &f));
150}
151
153 idle_t idle;
154 fake_t f;
155 memset(&f, 0, sizeof(f));
156 f.expected_budget = 50;
158 idle_config_t c = config();
159 CHECK(idle_init(&idle, &c, &port, &f));
160 /* Uptime past 2^31 ms: an unset schedule must not mean "sleep for weeks".
161 */
162 idle_result_t r = idle_on_wake(&idle, 3000000000U);
163 CHECK(r.event == IDLE_EVENT_ERROR);
164 CHECK(f.send_calls == 0 && f.poll_calls == 0);
165 CHECK(idle_enter(&idle, 3000000000U));
166 CHECK(idle_on_wake(&idle, 3000000000U).heartbeat_sent);
167}
168
170 idle_t idle;
171 fake_t f;
172 setup(&idle, &f, 5000);
174 CHECK(r.event == IDLE_EVENT_SLEEP);
175 CHECK(r.heartbeat_sent);
176 CHECK(r.sleep_s == 10);
177 CHECK(f.send_calls == 1);
178 CHECK(f.sent.sequence == 1);
179 CHECK(f.sent.uptime_ms == 5000);
182}
183
185 idle_t idle;
186 fake_t f;
187 uint32_t now = 0;
188 setup(&idle, &f, now);
189 for (int i = 0; i < 6; ++i) {
191 CHECK(r.event == IDLE_EVENT_SLEEP);
192 CHECK(r.heartbeat_sent);
193 CHECK(r.sleep_s == 10);
194 now += r.sleep_s * 1000U; /* the RTC fires when Idle asked */
195 }
196 CHECK(now == 60000);
197 CHECK(f.send_calls == 6);
198 CHECK(f.battery_calls == 6);
199 CHECK(f.sent.sequence == 6);
200}
201
203 idle_t idle;
204 fake_t f;
205 setup(&idle, &f, 0);
206 idle_on_wake(&idle, 0);
207 /* battery, then USB check, then send: never sample during TX. */
208 CHECK(f.order_count == 3);
209 CHECK(f.order[0] == 1);
210 CHECK(f.order[1] == 3);
211 CHECK(f.order[2] == 2);
212}
213
214static void test_thresholds(void) {
215 idle_t idle;
216 fake_t f;
217 uint32_t now = 0;
218
219 setup(&idle, &f, now);
220 set_mv(&f, 7200);
221 idle_on_wake(&idle, now);
223
224 setup(&idle, &f, now);
225 set_mv(&f, 6800);
226 idle_on_wake(&idle, now);
228 CHECK(f.sent.supply_mv >= 6799 && f.sent.supply_mv <= 6801);
229
230 setup(&idle, &f, now);
231 set_mv(&f, 6500);
232 idle_on_wake(&idle, now);
234
235 /* Exactly at the warn level is OK; one step under is WARN. */
236 setup(&idle, &f, now);
237 f.battery.raw_counts = 43180; /* 7000 mV */
238 idle_on_wake(&idle, now);
240}
241
242static void test_hysteresis(void) {
243 idle_t idle;
244 fake_t f;
245 uint32_t now = 0;
246 setup(&idle, &f, now);
247
248 set_mv(&f, 6950); /* drops below warn: immediate */
249 idle_on_wake(&idle, now);
251
252 now += 10000;
253 set_mv(&f, 7050); /* above warn but inside the band: stays WARN */
254 idle_on_wake(&idle, now);
256
257 now += 10000;
258 set_mv(&f, 6990); /* wobble back down: still WARN, no flip */
259 idle_on_wake(&idle, now);
261
262 now += 10000;
263 set_mv(&f, 7100); /* clears warn + 100 mV: recovers */
264 idle_on_wake(&idle, now);
266
267 now += 10000;
268 set_mv(&f, 6500); /* straight to FAIL */
269 idle_on_wake(&idle, now);
271
272 now += 10000;
273 set_mv(&f, 6650); /* above fail but inside its band: stays FAIL */
274 idle_on_wake(&idle, now);
276
277 now += 10000;
278 set_mv(&f, 6750); /* clears fail + 100 mV: WARN */
279 idle_on_wake(&idle, now);
281}
282
284 idle_t idle;
285 fake_t f;
286 setup(&idle, &f, 0);
287 f.usb = true;
288 set_mv(&f, 5000); /* USB-level voltage would read as FAIL if trusted */
289 idle_on_wake(&idle, 0);
291 CHECK(f.sent.supply_mv >= 4999 && f.sent.supply_mv <= 5001);
292
293 f.usb = true;
294 set_mv(&f, 8000); /* and a healthy-looking value is not OK either */
295 idle_on_wake(&idle, 10000);
297}
298
300 idle_t idle;
301 fake_t f;
302 setup(&idle, &f, 0);
303
304 f.battery.valid = false;
306 CHECK(r.heartbeat_sent);
308 CHECK(f.sent.supply_mv == 0);
309
310 set_mv(&f, 7500);
312 idle_on_wake(&idle, 10000);
314
315 set_mv(&f, 7500);
316 f.battery.raw_counts = 70000; /* impossible for a 16-bit ADC */
317 idle_on_wake(&idle, 20000);
319}
320
322 idle_t idle;
323 fake_t f;
324 uint32_t now = 0;
325 setup(&idle, &f, now);
326
327 f.send_reply.result = SELF_TEST_TIMEOUT; /* no TxDone */
329 CHECK(r.event == IDLE_EVENT_SLEEP); /* never stuck awake retrying */
330 CHECK(r.sleep_s == 10);
331 CHECK(f.send_calls == 1); /* no retry burst */
332
333 now += 10000;
335 idle_on_wake(&idle, now);
338
339 now += 10000;
341 idle_on_wake(&idle, now);
344
345 now += 10000;
346 idle_on_wake(&idle, now);
349}
350
352 idle_t idle;
353 fake_t f;
354 setup(&idle, &f, 0);
356 idle_result_t r = idle_on_wake(&idle, 0); /* heartbeat is also due */
358 CHECK(!r.heartbeat_sent);
359 CHECK(f.send_calls == 0);
360 CHECK(f.battery_calls == 0);
361}
362
363static void test_disarm_stays_idle(void) {
364 idle_t idle;
365 fake_t f;
366 setup(&idle, &f, 0);
367 idle_on_wake(&idle, 0);
369 idle_result_t r = idle_on_wake(&idle, 3000); /* radio wake */
370 CHECK(r.event == IDLE_EVENT_SLEEP);
371 CHECK(!r.heartbeat_sent);
372}
373
375 idle_t idle;
376 fake_t f;
377 setup(&idle, &f, 0);
381}
382
384 idle_t idle;
385 fake_t f;
386 setup(&idle, &f, 0);
387 idle_on_wake(&idle, 0);
388 for (int i = 0; i < 10; ++i) {
390 }
391 f.poll_calls = 0;
393 CHECK(r.event == IDLE_EVENT_SLEEP);
395}
396
398 idle_t idle;
399 fake_t f;
400 setup(&idle, &f, 0);
401 idle_on_wake(&idle, 0); /* heartbeat 1, next due at 10000 */
402
403 idle_result_t r = idle_on_wake(&idle, 7300); /* radio wake, no command */
404 CHECK(!r.heartbeat_sent);
405 CHECK(f.battery_calls == 1); /* no extra battery read */
406 CHECK(r.sleep_s == 3); /* ceil(2700 ms): back on the 10 s grid */
407
408 r = idle_on_wake(&idle, 10000);
409 CHECK(r.heartbeat_sent);
410 CHECK(f.send_calls == 2);
411}
412
414 idle_t idle;
415 fake_t f;
416 setup(&idle, &f, 0);
417 idle_on_wake(&idle, 0);
418
419 idle_result_t r = idle_on_wake(&idle, 9600); /* LSI ran 4% fast */
420 CHECK(r.heartbeat_sent);
421 CHECK(r.sleep_s == 10);
422
423 r = idle_on_wake(&idle, 9600 + 9000); /* 1 s early: outside tolerance */
424 CHECK(!r.heartbeat_sent);
425 CHECK(r.sleep_s == 1);
426}
427
429 idle_t idle;
430 fake_t f;
431 setup(&idle, &f, 0);
432 idle_on_wake(&idle, 0);
433 idle_result_t r = idle_on_wake(&idle, 95000); /* missed 8 intervals */
434 CHECK(r.heartbeat_sent);
435 CHECK(f.send_calls == 2); /* no catch-up burst */
436 CHECK(r.sleep_s == 10);
437}
438
440 idle_t idle;
441 fake_t f;
442 uint32_t now = UINT32_MAX - 15000U; /* clock wraps mid-run */
443 setup(&idle, &f, now);
444 for (int i = 0; i < 4; ++i) {
446 CHECK(r.heartbeat_sent);
447 CHECK(r.sleep_s == 10);
448 now += r.sleep_s * 1000U;
449 }
450 CHECK(f.send_calls == 4);
451
452 /* Sequence number wraps modulo 2^32. */
453 idle.last.sequence = UINT32_MAX;
454 idle_on_wake(&idle, now);
455 CHECK(f.sent.sequence == 0);
456}
457
459 idle_t idle;
460 fake_t f;
461 uint32_t now = 0;
462 setup(&idle, &f, now);
464 for (int i = 0; i < 300; ++i) {
465 idle_on_wake(&idle, now);
466 now += 10000;
467 }
468 CHECK(idle_last_heartbeat(&idle)->consecutive_send_failures == 255);
469}
470
static const emmc_port_t port
Definition emmc_stm32.c:210
void setup()
Definition main.cpp:571
int32_t p
Definition flight_e2e.c:78
#define NULL
Definition usbd_def.h:44
idle_result_t idle_on_wake(idle_t *idle, uint32_t now_ms)
Definition idle.c:168
const idle_heartbeat_t * idle_last_heartbeat(const idle_t *idle)
Definition idle.c:211
bool idle_enter(idle_t *idle, uint32_t now_ms)
Definition idle.c:70
uint16_t idle_counts_to_mv(uint32_t raw_counts)
Definition idle.c:13
bool idle_init(idle_t *idle, const idle_config_t *config, const idle_port_t *port, void *context)
Definition idle.c:50
@ IDLE_EVENT_ARM_REQUESTED
Definition idle.h:38
@ IDLE_EVENT_SLEEP
Definition idle.h:35
@ IDLE_EVENT_ERROR
Definition idle.h:41
#define IDLE_MAX_COMMANDS_PER_WAKE
Definition idle.h:21
idle_command_t
Definition idle_port.h:25
@ IDLE_CMD_NONE
Definition idle_port.h:26
@ IDLE_CMD_ARM
Definition idle_port.h:27
@ IDLE_CMD_DISARM
Definition idle_port.h:28
@ IDLE_BATT_FAIL
Definition idle_port.h:35
@ IDLE_BATT_INVALID
Definition idle_port.h:37
@ IDLE_BATT_USB
Definition idle_port.h:36
@ IDLE_BATT_OK
Definition idle_port.h:33
@ IDLE_BATT_WARN
Definition idle_port.h:34
static idle_t idle
Definition idle_task.c:26
*brief create JSON pointer *sa reference_tokens const string_t const string_t & b
Definition json.hpp:13878
@ SELF_TEST_FAIL
@ SELF_TEST_PENDING
@ SELF_TEST_PASS
@ SELF_TEST_TIMEOUT
unsigned command_count
Definition test_idle.c:25
idle_command_t commands[16]
Definition test_idle.c:24
self_test_reply_t send_reply
Definition test_idle.c:23
self_test_battery_t battery
Definition test_idle.c:21
unsigned command_next
Definition test_idle.c:26
uint32_t expected_budget
Definition test_idle.c:33
bool usb
Definition test_idle.c:22
unsigned poll_calls
Definition test_idle.c:29
unsigned order_count
Definition test_idle.c:32
unsigned battery_calls
Definition test_idle.c:27
unsigned send_calls
Definition test_idle.c:28
unsigned order[64]
Definition test_idle.c:31
idle_heartbeat_t sent
Definition test_idle.c:30
uint32_t callback_budget_ms
Definition idle.h:27
uint16_t battery_fail_mv
Definition idle.h:29
uint32_t wake_tolerance_ms
Definition idle.h:26
uint32_t heartbeat_interval_ms
Definition idle.h:24
uint32_t sequence
Definition idle_port.h:42
self_test_result_t previous_send
Definition idle_port.h:47
idle_battery_state_t battery
Definition idle_port.h:45
uint32_t uptime_ms
Definition idle_port.h:43
uint16_t supply_mv
Definition idle_port.h:44
uint8_t consecutive_send_failures
Definition idle_port.h:48
Definition idle.h:51
self_test_reply_t reply
self_test_result_t result
int main()
static boot_report_t r
static void test_arm_requested_without_heartbeat(void)
Definition test_idle.c:351
static void test_radio_wake_does_not_delay_heartbeat(void)
Definition test_idle.c:397
static void test_conversion(void)
Definition test_idle.c:112
static void note(fake_t *f, unsigned what)
Definition test_idle.c:36
static void test_battery_read_before_transmit(void)
Definition test_idle.c:202
static uint32_t counts_for_mv(uint32_t mv)
Definition test_idle.c:16
static void test_send_failure_counted_and_sleep_continues(void)
Definition test_idle.c:321
static void test_thresholds(void)
Definition test_idle.c:214
#define CHECK(condition)
Definition test_idle.c:7
static void test_early_lsi_wake_within_tolerance(void)
Definition test_idle.c:413
static bool fake_usb(void *context)
Definition test_idle.c:53
static void test_wake_before_enter_is_error(void)
Definition test_idle.c:152
static void test_clock_and_sequence_wraparound(void)
Definition test_idle.c:439
static void test_hysteresis(void)
Definition test_idle.c:242
static void test_failure_count_saturates(void)
Definition test_idle.c:458
static void test_sixty_seconds_gives_six_heartbeats(void)
Definition test_idle.c:184
static void test_long_gap_sends_one_heartbeat(void)
Definition test_idle.c:428
static void test_usb_is_never_battery_health(void)
Definition test_idle.c:283
static void test_init_rejects_bad_input(void)
Definition test_idle.c:123
static void test_disarm_stays_idle(void)
Definition test_idle.c:363
static void test_first_wake_sends_heartbeat(void)
Definition test_idle.c:169
static void set_mv(fake_t *f, uint32_t mv)
Definition test_idle.c:89
static void push_command(fake_t *f, idle_command_t cmd)
Definition test_idle.c:105
static idle_command_t fake_poll(void *context)
Definition test_idle.c:69
static self_test_battery_t fake_battery(void *context, uint32_t now_ms, uint32_t budget_ms)
Definition test_idle.c:42
static void test_disarm_then_arm_in_one_wake(void)
Definition test_idle.c:374
static idle_config_t config(void)
Definition test_idle.c:80
static void test_command_drain_is_bounded(void)
Definition test_idle.c:383
static self_test_reply_t fake_send(void *context, const idle_heartbeat_t *hb, uint32_t budget_ms)
Definition test_idle.c:59
static void test_invalid_battery_still_sends(void)
Definition test_idle.c:299