STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
idle_e2e.cpp
Go to the documentation of this file.
1/*
2 * idle_e2e: the Idle state end to end, on a laptop.
3 *
4 * PA0 counts + SW_ST -> REAL idle.c (PR #162) -> REAL heartbeat encoding
5 * (Ryan's frame.cpp) -> REAL Sx1262Setup::send -> fake SX1262 on the air
6 * -> captured packets decoded with Ryan's decode_frame
7 * ground Arm packets -> fake SX1262 RxDone/DIO1 -> REAL Sx1262::receive +
8 * Link + ArmGate -> REAL mode_core / flight_state -> ARMED, eMMC log
9 * (REAL emmc_log_on_state) recording
10 * RTC on an LSI that is not 32 kHz -> REAL sleep_math (WUT reload,
11 * elapsed time) -> the board's clock
12 *
13 * The harness plays the RTOS exactly as src/app/idle_task.c and the radio task
14 * do: run the radio task on DIO1, call idle_on_wake(), sleep until the RTC
15 * wakeup or an earlier DIO1. Only the radio chip, RTC, ADC and the ground
16 * station are fake.
17 *
18 * Output, one line per check (parsed by ./flight_test):
19 * CHECK <id> PASS|FAIL <description> | <detail>
20 * Usage: idle_e2e [--list] [--only=id,id]
21 */
22#include <stdarg.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26
27#include "emmc_log.h"
28#include "fake_air.h"
29#include "frame.h"
30#include "idle.h"
31#include "link.h"
32#include "mode_core.h"
33#include "sleep_math.h"
34#include "sx1262.h"
35#include "sx1262_setup.h"
36
37using namespace radio_link;
38
39namespace {
40
41constexpr uint32_t kTrueLsiHz = 30500; // this board's LSI, not 32 kHz
42constexpr uint32_t kBootMs = 5000; // self-test done, Idle starts
43constexpr uint32_t kCounts7V92 = 48853; // PA0 for a 7.92 V pack
44constexpr uint8_t kTargetId = 0x01; // radio_task.cpp
45
47 uint32_t* now = nullptr;
48 bool rx_switch = false;
49 void set_rx_switch(bool receive) override {
50 rx_switch = receive;
51 }
52 void delay_ms(uint32_t ms) override {
53 *now += ms;
54 }
55};
56
57struct World {
58 uint32_t now = kBootMs; // true time, ms
59 uint32_t board = kBootMs; // the board's RTOS clock
67 uint8_t queue[8];
68 unsigned queued = 0;
69 bool on_usb = false;
71 // ground packets to deliver: time and bytes
72 struct Ground {
73 uint32_t t;
74 uint8_t len;
75 uint8_t data[32];
76 } ground[4];
77 unsigned n_ground = 0, next_ground = 0;
78 // what the harness saw
79 unsigned sleeps = 0, sleeps_deaf = 0, early_wakes = 0;
80 uint32_t armed_at = 0;
81 uint16_t tx_seq = 0;
85};
86
87World* W = nullptr; // the idle_port callbacks reach the world through this
88
89// Ryan's seam, on the fake chip (radio_task.cpp does the same on SPI1).
90} // namespace
91
92namespace radio_link {
94 return RadioStatus::Ok;
95}
96RadioStatus radio_receive(uint8_t* buf, uint8_t max_len, uint8_t& len) {
97 return W->receiver.receive(buf, max_len, len);
98}
99RadioStatus radio_send(const uint8_t* bytes, uint8_t len) {
100 return W->setup.send(bytes, len);
101}
102} // namespace radio_link
103
104namespace {
105
106// --- idle_port, as src/app/idle_task.c wires it ------------------------------
107self_test_battery_t read_battery(void*, uint32_t now_ms, uint32_t) {
109 b.reply.result = SELF_TEST_PASS;
110 b.valid = true;
111 b.raw_counts = W->battery_counts;
112 b.sampled_at_ms = now_ms;
113 return b;
114}
115bool on_usb(void*) {
116 return W->on_usb;
117}
119 bool pack = hb->battery == IDLE_BATT_OK || hb->battery == IDLE_BATT_WARN ||
120 hb->battery == IDLE_BATT_FAIL;
121 uint16_t mv = pack ? hb->supply_mv : 0;
122 uint32_t up = hb->uptime_ms / 1000u;
123 const uint8_t p[7] = {FLIGHT_IDLE,
124 static_cast<uint8_t>(mv),
125 static_cast<uint8_t>(mv >> 8),
126 static_cast<uint8_t>(up),
127 static_cast<uint8_t>(up >> 8),
128 static_cast<uint8_t>(up >> 16),
129 static_cast<uint8_t>(up >> 24)};
130 uint8_t frame[32];
131 size_t n = 0;
132 encode_frame(FrameType::Heartbeat, W->tx_seq++, p, 7, frame, sizeof frame,
133 n);
134 uint32_t before = W->now;
135 RadioStatus rs = radio_send(frame, static_cast<uint8_t>(n));
136 W->board += W->now - before; // awake: both clocks run together
137 if (rs == RadioStatus::Ok)
138 return {SELF_TEST_PASS, 0};
139 if (rs == RadioStatus::ErrTimeout)
140 return {SELF_TEST_TIMEOUT, 0};
141 return {SELF_TEST_FAIL, 0};
142}
144 if (W->queued == 0)
145 return IDLE_CMD_NONE;
146 uint8_t c = W->queue[0];
147 memmove(W->queue, W->queue + 1, --W->queued);
149}
150
152
153// --- the radio task: DIO1 -> Link::poll -> ARM gate -> app_command -------
155 while (W->air.dio1()) {
156 ArmAction a = W->link.poll(W->board);
157 // radio_task.cpp: after a DIO1 event, restart the sniff duty cycle
158 // (no-op in continuous RX).
159 (void)W->setup.rearm_rx();
160 flight_state_t next = W->state;
161 if (a == ArmAction::Arm || a == ArmAction::Disarm) {
164 switch (mode_route_command(W->state, c, &next)) {
166 if (W->queued < sizeof W->queue)
167 W->queue[W->queued++] = c;
168 break;
169 case MODE_TRANSITION:
170 set_state(next);
171 break;
172 default:
173 break;
174 }
175 }
176 if (a == ArmAction::None && !W->air.dio1())
177 break;
178 }
179}
180
182 emmc_log_on_state(&W->log, W->state, to, W->board);
183 W->state = to;
184 // mode.c: the radio sniffs in Idle and listens continuously otherwise.
185 if (mode_outputs(to).radio_sniff)
186 (void)W->setup.start_rx_duty_cycle(MODE_SNIFF_RX_MS,
188 else
189 (void)W->setup.start_rx_continuous();
190}
191
192// --- the ground station ---------------------------------------------------
193void ground_arm(World& w, uint32_t t, uint16_t seq) {
194 uint8_t p[9];
195 put_u32(p, kArmKey);
196 put_u32(p + 4, ~kArmKey);
197 p[8] = kTargetId;
198 size_t n = 0;
199 World::Ground& g = w.ground[w.n_ground++];
200 encode_frame(FrameType::Arm, seq, p, 9, g.data, sizeof g.data, n);
201 g.t = t;
202 g.len = static_cast<uint8_t>(n);
203}
204
205emmc_status_t no_card(void*, uint32_t* n) {
206 *n = 1u << 20;
207 return EMMC_OK;
208}
209emmc_status_t no_io(void*, uint32_t, void*, uint32_t, uint32_t) {
210 return EMMC_OK;
211}
212emmc_status_t no_write(void*, uint32_t, const void*, uint32_t, uint32_t) {
213 return EMMC_OK;
214}
215
216// --- the Idle loop, as src/app/idle_task.c runs it ------------------------
218 W = &w;
219 w.control.now = &w.now;
220 static const emmc_port_t port = {no_card, no_io, no_write, nullptr,
221 nullptr};
223 emmc_log_init(&w.log, &port, &w, &cfg, w.chunks, 2, w.scratch);
224 static const idle_config_t icfg = {10000, 500, 500, 7000, 6600, 100};
225 static const idle_port_t iport = {read_battery, on_usb, send_heartbeat,
227 idle_init(&w.idle, &icfg, &iport, nullptr);
228}
229
230void run(World& w, uint32_t until) {
231 if (w.setup.configure() != SetupStep::Ok)
232 return;
233 set_state(FLIGHT_IDLE); // self-test done: Idle, the radio sniffing
234 idle_enter(&w.idle, w.board);
235 // board_sleep_init: LSI measured against TIM2 (exact here)
236 const uint32_t lsi = kTrueLsiHz;
237 while (w.now < until && w.state == FLIGHT_IDLE) {
238 radio_task();
240 if (r.event == IDLE_EVENT_ARM_REQUESTED) {
242 w.armed_at = w.now;
243 break;
244 }
245 if (w.state != FLIGHT_IDLE)
246 break; // DISARM etc. moved it
247 uint32_t ms = r.event == IDLE_EVENT_SLEEP ? r.sleep_s * 1000u : 1000u;
248 if (w.queued)
249 continue; // board_sleep's wake_now
250 // Asleep: the chip must be sniffing with RF_SW on receive.
251 w.sleeps++;
252 if (!w.air.listening || !w.air.sniffing || !w.control.rx_switch)
253 w.sleeps_deaf++;
254 // The WUT fires after (reload + 1) x 16 true LSI cycles.
255 uint32_t ticks = sleep_wut_reload(ms, lsi) + 1u;
256 uint32_t wake =
257 w.now + static_cast<uint32_t>(static_cast<uint64_t>(ticks) *
258 16000u / kTrueLsiHz);
259 if (w.next_ground < w.n_ground && w.ground[w.next_ground].t < wake) {
260 wake = w.ground[w.next_ground].t; // DIO1 wakes the MCU first
261 w.early_wakes++;
262 }
263 // The RTC counts at the true LSI; the board converts with what it
264 // measured (sleep_math), then FreeRTOS catches up.
265 uint32_t steps = static_cast<uint32_t>(
266 static_cast<uint64_t>(wake - w.now) * kTrueLsiHz / (128u * 1000u));
267 w.board += sleep_elapsed_ms(0, steps, lsi);
268 w.now = wake;
269 while (w.next_ground < w.n_ground &&
270 w.ground[w.next_ground].t <= w.now) {
272 w.ground[w.next_ground].len);
273 w.next_ground++;
274 }
275 }
276}
277
278// --- checks ---------------------------------------------------------------
280char detail[256];
281
282#define EXPECT(cond, ...) \
283 do { \
284 if (!(cond)) { \
285 if (!failed) \
286 snprintf(detail, sizeof detail, __VA_ARGS__); \
287 failed = 1; \
288 } \
289 } while (0)
290
291void note(const char* fmt, ...) {
292 if (failed)
293 return;
294 va_list ap;
295 va_start(ap, fmt);
296 vsnprintf(detail, sizeof detail, fmt, ap);
297 va_end(ap);
298}
299
300struct Beat {
301 uint32_t t, uptime_s;
302 uint8_t state;
303 uint16_t mv;
304};
305unsigned beats(const World& w, Beat* out, unsigned cap) {
306 unsigned n = 0;
307 for (unsigned i = 0; i < w.air.n_sent && n < cap; ++i) {
308 Frame f;
309 if (decode_frame(w.air.sent[i].data, w.air.sent[i].len, f) !=
312 continue;
313 out[n].t = w.air.sent[i].t_ms;
314 out[n].state = f.payload[0];
315 out[n].mv = get_u16(f.payload + 1);
316 out[n].uptime_s = get_u32(f.payload + 3);
317 n++;
318 }
319 return n;
320}
321
323 static World w;
324 world_init(w);
325 run(w, kBootMs + 120000);
326 static Beat b[32];
327 unsigned n = beats(w, b, 32);
328 EXPECT(w.air.n_sent == w.air.tx_attempts, "%u sends, %u decoded",
330 EXPECT(n == w.air.n_sent, "%u of %u packets are not valid heartbeats",
331 w.air.n_sent - n, w.air.n_sent);
332 EXPECT(n >= 12 && n <= 13, "%u heartbeats in 120 s", n);
333 uint32_t worst = 0;
334 for (unsigned i = 1; i < n; ++i) {
335 uint32_t gap = b[i].t - b[i - 1].t;
336 uint32_t err = gap > 10000 ? gap - 10000 : 10000 - gap;
337 if (err > worst)
338 worst = err;
339 }
340 EXPECT(worst <= 500, "a heartbeat interval was off by %u ms", worst);
341 for (unsigned i = 0; i < n; ++i) {
342 EXPECT(b[i].state == FLIGHT_IDLE && b[i].mv == 7920,
343 "heartbeat %u: state %u, %u mV", i, b[i].state, b[i].mv);
344 uint32_t true_s = b[i].t / 1000u;
345 EXPECT(b[i].uptime_s + 1 >= true_s && b[i].uptime_s <= true_s + 1,
346 "heartbeat %u uptime %u s at %u s", i, b[i].uptime_s, true_s);
347 }
348 note(
349 "%u heartbeats, every 10 s +/- %u ms, decoded by Ryan's decode_frame,"
350 " 7.92 V, uptime counts sleep",
351 n, worst);
352}
353
355 static World w;
356 world_init(w);
357 run(w, kBootMs + 60000);
358 EXPECT(w.sleeps >= 5, "only %u sleeps", w.sleeps);
359 EXPECT(w.sleeps_deaf == 0, "%u of %u sleeps with the radio not listening",
360 w.sleeps_deaf, w.sleeps);
361 note(
362 "%u sleeps, radio sniffing (SetRxDutyCycle) with RF_SW on receive "
363 "in all",
364 w.sleeps);
365}
366
368 static World w;
369 world_init(w);
370 // ArmGate needs a second ARM within its confirm window (arm_gate.h).
371 ground_arm(w, kBootMs + 23400, 100);
372 ground_arm(w, kBootMs + 23900, 101);
373 run(w, kBootMs + 60000);
374 EXPECT(w.state == FLIGHT_ARMED, "state %d, not ARMED", w.state);
375 EXPECT(w.armed_at >= kBootMs + 23900 && w.armed_at <= kBootMs + 24000,
376 "armed at %u ms, confirm sent at %u", w.armed_at, kBootMs + 23900);
377 EXPECT(w.early_wakes == 2, "%u DIO1 wakes, expected 2", w.early_wakes);
379 EXPECT(o.sensors && o.gps && !o.idle_loop, "ARMED does not start sensors");
380 EXPECT(w.log.recording, "the eMMC log is not recording in ARMED");
381 // Both Arms were heard although the first one ended the sniff cycle
382 // (re-armed on DIO1); armed, the radio listens all the time.
383 EXPECT(w.air.listening && !w.air.sniffing, "ARMED but the radio is %s",
384 w.air.sniffing ? "still sniffing" : "deaf");
385 static Beat b[32];
386 unsigned n = beats(w, b, 32);
387 EXPECT(n == 3, "%u heartbeats before the Arm (expected 0, 10, 20 s)", n);
388 note(
389 "Arm confirmed at 28.9 s: woke on DIO1 twice, ARMED %u ms later, "
390 "sensors + eMMC on, heartbeats stop",
391 w.armed_at - (kBootMs + 23900));
392}
393
395 static World w;
396 world_init(w);
397 w.on_usb = true;
398 w.battery_counts = 30840; // USB_5V on PA0
399 run(w, kBootMs + 30000);
400 static Beat b[8];
401 unsigned n = beats(w, b, 8);
402 EXPECT(n >= 3, "%u heartbeats", n);
403 for (unsigned i = 0; i < n; ++i) {
404 EXPECT(b[i].mv == 0, "heartbeat %u reports %u mV on USB", i, b[i].mv);
405 }
407 "Idle judged USB as a battery");
408 note("on USB: %u heartbeats with battery_mV 0, Idle state USB", n);
409}
410
412 static World w;
413 world_init(w);
414 run(w, kBootMs + 15000); // 2 good heartbeats
415 w.air.tx_never_done = true;
416 run(w, kBootMs + 35000); // 2 that never finish
418 w.air.tx_never_done = false;
419 unsigned attempts_before = w.air.tx_attempts;
420 run(w, kBootMs + 46000);
421 EXPECT(failures == 2, "%u consecutive failures counted, expected 2",
422 failures);
423 EXPECT(w.setup.tx_timeouts() == 2, "%u TX timeouts", w.setup.tx_timeouts());
424 EXPECT(w.air.tx_attempts == attempts_before + 1,
425 "%u sends after recovery: a retry storm",
426 w.air.tx_attempts - attempts_before);
428 "failure count did not reset after a good send");
429 EXPECT(w.sleeps_deaf == 0, "radio left deaf after a failed send");
430 note(
431 "2 TxDone timeouts counted, no retries, back in RX each time, "
432 "recovered on the next heartbeat");
433}
434
436 static World w;
437 world_init(w);
438 run(w, kBootMs + 120000);
439 int32_t err = static_cast<int32_t>(w.board - w.now);
440 // Had the board assumed 32 kHz, 115 s of sleep would be off by ~4.7 %.
441 uint32_t naive =
442 static_cast<uint32_t>(115000ull * 32000u / kTrueLsiHz) - 115000u;
443 EXPECT(err >= -50 && err <= 50, "board clock off by %d ms after %u s", err,
444 (w.now - kBootMs) / 1000u);
445 note(
446 "LSI %u Hz: board clock within %d ms of true time over %u s "
447 "(assuming 32 kHz would be ~%u ms off)",
448 kTrueLsiHz, err, (w.now - kBootMs) / 1000u, naive);
449}
450
451struct Check {
452 const char* id;
453 const char* what;
454 void (*run)();
455};
456
457const Check checks[] = {
458 {"idle_heartbeat",
459 "Idle: a LoRa heartbeat every 10 s, 7.92 V, decoded by Ryan's frames",
461 {"idle_rx_asleep",
462 "Idle: radio sniffing (duty cycle, RF_SW) through every sleep",
464 {"idle_arm_wake",
465 "Idle: ground Arm wakes on DIO1 -> ARMED, sensors + eMMC start",
467 {"idle_usb", "Idle on USB: heartbeats never report USB's 5 V as the pack",
469 {"idle_tx_fail",
470 "Idle: TxDone timeouts counted, no retry storm, radio back in RX",
472 {"idle_clock", "Idle: RTC on a 30.5 kHz LSI, board clock still true time",
474};
475
476} // namespace
477
478int main(int argc, char** argv) {
479 const char* only = nullptr;
480 for (int i = 1; i < argc; ++i) {
481 if (strcmp(argv[i], "--list") == 0) {
482 for (const Check& c : checks)
483 printf("%s\t%s\n", c.id, c.what);
484 return 0;
485 }
486 if (strncmp(argv[i], "--only=", 7) == 0)
487 only = argv[i] + 7;
488 }
489 int bad = 0;
490 for (const Check& c : checks) {
491 if (only) {
492 char list[256];
493 snprintf(list, sizeof list, ",%s,", only);
494 char key[64];
495 snprintf(key, sizeof key, ",%s,", c.id);
496 if (!strstr(list, key))
497 continue;
498 }
499 failed = 0;
500 detail[0] = 0;
501 c.run();
502 printf("CHECK %s %s %s | %s\n", c.id, failed ? "FAIL" : "PASS", c.what,
503 detail);
504 bad |= failed;
505 }
506 return bad;
507}
flight_state_t
@ FLIGHT_ARMED
@ FLIGHT_IDLE
@ FLIGHT_EV_ARM
bool emmc_log_on_state(emmc_log_t *log, flight_state_t from, flight_state_t to, uint32_t now_ms)
Definition emmc_log.c:200
emmc_status_t emmc_log_init(emmc_log_t *log, const emmc_port_t *port, void *context, const emmc_log_config_t *config, emmc_log_chunk_t *chunks, uint8_t chunk_count, uint8_t *scratch)
Definition emmc_log.c:177
emmc_log_config_t emmc_log_default_config(void)
Definition emmc_log.c:167
#define EMMC_BLOCK_SIZE
Definition emmc_port.h:20
emmc_status_t
Definition emmc_port.h:22
@ EMMC_OK
Definition emmc_port.h:23
static const emmc_port_t port
Definition emmc_stm32.c:210
static ActuatorControllerState state
Definition main.cpp:57
int32_t p
Definition flight_e2e.c:78
static const check_t checks[]
Definition flight_e2e.c:710
static world_t W
Definition flight_e2e.c:451
flight_state_t flight_state_next(flight_state_t state, flight_event_t event)
Definition flight_state.c:3
Over-the-air frame format for the LoRa command link.
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
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
#define EXPECT(cond,...)
Definition idle_e2e.cpp:282
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_USB
Definition idle_port.h:36
@ IDLE_BATT_OK
Definition idle_port.h:33
@ IDLE_BATT_WARN
Definition idle_port.h:34
*brief create JSON pointer *sa reference_tokens const string_t const string_t & b
Definition json.hpp:13878
*brief create JSON pointer *sa reference_tokens const string_t & a
Definition json.hpp:13877
return o
Definition json.hpp:13897
mode_route_t mode_route_command(flight_state_t state, mode_command_t command, flight_state_t *next)
Definition mode_core.c:23
mode_outputs_t mode_outputs(flight_state_t state)
Definition mode_core.c:3
@ MODE_TRANSITION
Definition mode_core.h:47
@ MODE_TO_IDLE_LOOP
Definition mode_core.h:46
#define MODE_SNIFF_SLEEP_MS
Definition mode_core.h:39
mode_command_t
Definition mode_core.h:43
@ MODE_CMD_DISARM
Definition mode_core.h:43
@ MODE_CMD_ARM
Definition mode_core.h:43
#define MODE_SNIFF_RX_MS
Definition mode_core.h:38
unsigned beats(const World &w, Beat *out, unsigned cap)
Definition idle_e2e.cpp:305
emmc_status_t no_io(void *, uint32_t, void *, uint32_t, uint32_t)
Definition idle_e2e.cpp:209
emmc_status_t no_card(void *, uint32_t *n)
Definition idle_e2e.cpp:205
constexpr uint32_t kTrueLsiHz
Definition idle_e2e.cpp:41
constexpr uint32_t kCounts7V92
Definition idle_e2e.cpp:43
void note(const char *fmt,...)
Definition idle_e2e.cpp:291
void ground_arm(World &w, uint32_t t, uint16_t seq)
Definition idle_e2e.cpp:193
void set_state(flight_state_t to)
Definition idle_e2e.cpp:181
idle_command_t poll_command(void *)
Definition idle_e2e.cpp:143
self_test_reply_t send_heartbeat(void *, const idle_heartbeat_t *hb, uint32_t)
Definition idle_e2e.cpp:118
emmc_status_t no_write(void *, uint32_t, const void *, uint32_t, uint32_t)
Definition idle_e2e.cpp:212
self_test_battery_t read_battery(void *, uint32_t now_ms, uint32_t)
Definition idle_e2e.cpp:107
detail namespace with internal helper functions
Definition json.hpp:249
*brief user defined string literal for JSON values *sa std::size_t n
Definition json.hpp:24474
Definition run.py:1
@ SELF_TEST_FAIL
@ SELF_TEST_PASS
@ SELF_TEST_TIMEOUT
static void failed(sensors_device_t *d, int err, uint32_t now_us)
static uint32_t until(uint32_t now, uint32_t t)
uint32_t sleep_elapsed_ms(uint32_t from_steps, uint32_t to_steps, uint32_t lsi_hz)
Definition sleep_math.c:23
uint16_t sleep_wut_reload(uint32_t ms, uint32_t lsi_hz)
Definition sleep_math.c:3
void set_rx_switch(bool receive) override
Definition idle_e2e.cpp:49
void delay_ms(uint32_t ms) override
Definition idle_e2e.cpp:52
struct anonymous_namespace{idle_e2e.cpp}::World::Ground ground[4]
uint8_t scratch[EMMC_BLOCK_SIZE]
Definition idle_e2e.cpp:84
bool recording
Definition emmc_log.h:124
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
int main()
static ArmGate g(kMe)
static boot_report_t r
static int failures
static std::vector< uint8_t > frame(uint8_t hi, uint8_t lo, size_t payload_len, uint8_t fill)
One wire packet: len(4, LE) then len bytes of body, where the first 4 body bytes are ty/packet_id(2)/...