STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
SensorHotfireCore.h
Go to the documentation of this file.
1/**
2 * Shared sensor hotfire state machine core — PT / TC / LC boards
3 *
4 * Single implementation of: UDP rx, packet processing, 3-state machine,
5 * state LED, board heartbeat, Ethernet init.
6 * Board-specific: ADC init, collect_chunk(), send_chunks_to() via callbacks.
7 *
8 * Include after: Arduino.h, Ethernet, DAQv2-Comms, main.h, sense_board_pins.h
9 * Define before include (or use defaults):
10 * SENSOR_HOTFIRE_MAX_PACKET_SIZE (default 512)
11 * SENSOR_HOTFIRE_NO_CONN_ABORT_TYPE (default 11)
12 */
13
14#pragma once
15
16#include <Arduino.h>
17#include <Ethernet.h>
18#include <EthernetUdp.h>
19#include <SPI.h>
20#include <STAR_EthernetOTA.h>
21#include <daq-protocol.h>
22#include <esp_mac.h>
23
24#include <cstring>
25
26#include "board_net.h"
27#include "firmware_hash.h"
28
29#ifndef SENSOR_HOTFIRE_MAX_PACKET_SIZE
30#define SENSOR_HOTFIRE_MAX_PACKET_SIZE 512
31#endif
32#ifndef SENSOR_HOTFIRE_NO_CONN_ABORT_TYPE
33#define SENSOR_HOTFIRE_NO_CONN_ABORT_TYPE 11
34#endif
35#ifndef SENSOR_UDP_LISTEN_PORT
36#define SENSOR_UDP_LISTEN_PORT 5005
37#endif
38
39// Two-tier serial logging (HF_LOG* = always, HF_VERBOSE* = verbose-only, gated
40// by g_verbose). Each board defines `bool g_verbose = true;` once in its main
41// .cpp.
42#include "hotfire_log.h"
43
44namespace SensorHotfire {
45
47
48enum class State : uint8_t {
49 WaitingForServer = 1, // SETUP
50 Active = 2, // ACTIVE
51 StandaloneAbort = 9, // STANDALONE_ABORT
52 SelfTest = 10 // SELF_TEST
53};
54
62
64 bool valid;
65 uint8_t reference_voltage; // 0 = internal 2.5V, 1 = VDD, 2 = 5V (ignored;
66 // treat as 0)
69 uint8_t num_sensors; // 0 = use board default
70 uint8_t sensor_ids[MAX_SENSORS_PER_BOARD]; // connector IDs to read and
71 // transmit
72};
73
74struct Config {
75 const char* board_name;
77 void (*init_adc)(void* user_data);
78 void (*collect_chunk)(void* user_data);
79 /** Send collected chunks to primary dest; if also_to_abort_controller then
80 * send same data to abort_controller_ip:port as well, then drain. */
81 void (*send_chunks_to)(void* user_data, IPAddress dest_ip, int dest_port,
82 bool also_to_abort_controller,
83 IPAddress abort_controller_ip,
84 int abort_controller_port);
85 /** Called when SENSOR_CONFIG is received with reference_voltage byte
86 * (0=2.5V internal, 1=VDD, 2=ignore). */
88 uint8_t reference_voltage);
89 /**
90 * Called once when entering SelfTest state.
91 * Performs ADC self-test and connector continuity check, fills
92 * `results_out`. After return, core sends the SELF_TEST packet and goes to
93 * Active.
94 */
96 std::vector<daq::SelfTestResult>& results_out);
97 void* user_data;
98};
99
100struct CoreState {
101 uint8_t board_id;
102 byte mac[6];
109 const int udpListenPort = 5005;
110 const int serverPortDefault = 5006;
111 // Address (assigned by the ground station) and server-discovery state.
112 // Always present so this struct has one shape.
114 EthernetUDP udp;
117 unsigned long lastHeartbeatMillis;
119 unsigned long last_led_edge_millis;
121 bool led_on;
122};
123
124/**
125 * Freeze the board's address before a single byte reaches flash. Our address
126 * comes from a DHCP lease, and a renewal that landed mid-flash would move us
127 * and drop the transfer, so this suspends renewals for the duration.
128 */
129inline void otaFreezeAddress(void* user_data, uint32_t image_bytes) {
130 (void)image_bytes;
131 if (user_data)
132 BoardNet::beginOtaHold(static_cast<CoreState*>(user_data)->net);
133}
134
135//-----------------------------------------------------------------------------
136// Packet header / parsing helpers
137//-----------------------------------------------------------------------------
138inline bool readPacketHeader(const uint8_t* buffer, size_t len,
139 daq::PacketHeader& hdr_out) {
140 if (len < sizeof(daq::PacketHeader))
141 return false;
142 memcpy(&hdr_out, buffer, sizeof(daq::PacketHeader));
143 return true;
144}
145
146inline const char* packetTypeName(uint8_t type) {
147 switch (type) {
148 case 1:
149 return "BOARD_HEARTBEAT";
150 case 2:
151 return "SERVER_HEARTBEAT";
152 case 3:
153 return "SENSOR_DATA";
154 case 4:
155 return "ACTUATOR_COMMAND";
156 case 5:
157 return "SENSOR_CONFIG";
158 case 6:
159 return "ACTUATOR_CONFIG";
160 case 7:
161 return "ABORT";
162 case 8:
163 return "ABORT_DONE";
164 case 9:
165 return "CLEAR_ABORT";
166 case 10:
167 return "PWM_ACTUATOR_COMMAND";
169 return "NO_CONNECTION_ABORT";
170 case 12:
171 return "SELF_TEST";
172 default:
173 return "UNKNOWN";
174 }
175}
176
178 const uint8_t* buffer,
179 size_t len, IPAddress remote_ip,
180 int remote_port) {
181 daq::PacketHeader hdr;
182 if (!readPacketHeader(buffer, len, hdr))
184
185 if (hdr.packet_type == daq::PacketType::SERVER_HEARTBEAT) {
186 daq::PacketHeader dummy;
187 daq::ServerHeartbeatPacket data;
188 if (daq::parse_server_heartbeat_packet(buffer, len, dummy, data)) {
189#ifdef SENSOR_ETH_USE_DHCP
190 BoardNet::onServerPacket(s.net, remote_ip, s.serverIP);
191#else
192 // Server IP/port are hardcoded; do not update from packet
193#endif
195 }
197 }
198
199 if (hdr.packet_type == daq::PacketType::SENSOR_CONFIG) {
200 daq::PacketHeader dummy;
201 std::vector<uint8_t> sensor_ids;
202 uint8_t reference_voltage = 0;
203 bool necessary_for_abort = false;
204 uint32_t controller_ip = 0;
205 uint8_t enable_serial_printing = 1;
206
208 buffer, len, dummy, sensor_ids, reference_voltage,
209 necessary_for_abort, controller_ip, enable_serial_printing)) {
210 Serial.println("SENSOR_CONFIG parse failed (DAQv2-Comms)");
211 Serial.flush();
213 }
214
215 s.stored_config.valid = true;
216 s.stored_config.reference_voltage = reference_voltage;
217 s.stored_config.necessary_for_abort = necessary_for_abort;
218 s.stored_config.actuator_controller_ip = controller_ip;
219 s.stored_config.num_sensors = static_cast<uint8_t>(
220 sensor_ids.size() <= MAX_SENSORS_PER_BOARD ? sensor_ids.size()
222 for (uint8_t i = 0; i < s.stored_config.num_sensors; ++i)
223 s.stored_config.sensor_ids[i] = sensor_ids[i];
224
225 if (cfg.on_reference_voltage_config)
226 cfg.on_reference_voltage_config(cfg.user_data,
228
229 // enable_serial_printing is the mode byte (0..3): bit0 = USB verbose,
230 // upper part = server log stream level. See hotfire_log.h.
231 const uint8_t log_mode = enable_serial_printing;
232 g_verbose = (log_mode & 1);
233 g_log_stream_level = (log_mode >= 2) ? (log_mode - 1) : 0;
234
235 HF_LOGLN("SENSOR_CONFIG received:");
236 // Field-by-field breakdown is verbose-only; the header above is Tier 1.
237 HF_VERBOSE(" num_sensors=");
238 HF_VERBOSELN(static_cast<unsigned>(sensor_ids.size()));
239 HF_VERBOSE(" sensor_ids=");
240 if (!sensor_ids.empty()) {
241 for (size_t i = 0; i < sensor_ids.size(); ++i) {
242 if (i)
243 HF_VERBOSE(",");
244 HF_VERBOSE(static_cast<unsigned>(sensor_ids[i]));
245 }
247 } else {
248 HF_VERBOSELN("(none)");
249 }
250 HF_VERBOSE(" reference_voltage=");
251 HF_VERBOSELN(static_cast<unsigned>(s.stored_config.reference_voltage));
252 HF_VERBOSE(" necessary_for_abort=");
254 HF_VERBOSE(" actuator_controller_ip=");
256 const uint8_t* ip_bytes = reinterpret_cast<const uint8_t*>(
258 IPAddress actuatorIP(ip_bytes[0], ip_bytes[1], ip_bytes[2],
259 ip_bytes[3]);
260 HF_VERBOSELN(actuatorIP);
261 } else {
262 HF_VERBOSELN("0.0.0.0");
263 }
264 HF_VERBOSE(" enable_serial_printing=");
265 HF_VERBOSELN(g_verbose ? 1 : 0);
266 Serial.flush();
267#ifdef SENSOR_ETH_USE_DHCP
268 BoardNet::onServerPacket(s.net, remote_ip, s.serverIP);
269#endif
271 }
272
273 if (hdr.packet_type == daq::PacketType::CLEAR_ABORT)
275
276 if (hdr.packet_type == daq::PacketType::NO_CONNECTION_ABORT) {
277 if (len >= sizeof(daq::PacketHeader))
280 }
282}
283
284inline void applyPacketTransition(CoreState& s, const Config& cfg,
285 IncomingPacketKind kind) {
286 switch (s.state) {
289 if (cfg.run_self_test) {
291 Serial.println("State -> SelfTest");
292 } else {
294 Serial.println("State -> Active");
295 }
296 Serial.flush();
297 }
298 break;
299 case State::Active:
303 Serial.println("State -> StandaloneAbort");
304 Serial.flush();
305 }
306 break;
308 if (kind == IncomingPacketKind::ClearAbort) {
310 Serial.println("State -> Active");
311 Serial.flush();
312 }
313 break;
314 case State::SelfTest:
315 // State handled in loop(), it's a transient state returning to
316 // Active
317 break;
318 }
319}
320
321inline void sendBoardHeartbeat(CoreState& s, const Config& cfg,
322 daq::BoardState board_state, IPAddress dest_ip,
323 int dest_port) {
324 daq::BoardHeartbeatPacket hb;
325 memcpy(hb.firmware_hash, FirmwareHash::get(), 32);
326 hb.board_id = s.board_id;
327 hb.engine_state = daq::EngineState::SAFE;
328 hb.board_state = board_state;
329
332 sizeof(packetBuffer));
333 if (n == 0)
334 return;
335 HF_VERBOSE("Sent: heartbeat to ");
336 HF_VERBOSE(dest_ip);
337 HF_VERBOSE(":");
338 HF_VERBOSELN(dest_port);
339 s.udp.beginPacket(dest_ip, dest_port);
340 s.udp.write(packetBuffer, n);
341 s.udp.endPacket();
342}
343
344inline void updateStateLed(CoreState& s, const Config& cfg, int state_num) {
345 if (state_num < 1)
346 state_num = 1;
347 if (state_num > 10)
348 state_num = 10;
349 const int led_pin = cfg.pins->LED;
350 unsigned long now = millis();
351 const unsigned long STATE_LED_CYCLE_MS = 2500;
352 const unsigned long BLINK_ON_MS = 120;
353 const unsigned long BLINK_OFF_MS = 120;
354
355 if (now - s.last_led_cycle_millis >= STATE_LED_CYCLE_MS) {
356 s.last_led_cycle_millis = now;
357 s.last_led_edge_millis = now;
358 s.led_blink_index = 0;
359 s.led_on = true;
360 digitalWrite(led_pin, HIGH);
361 return;
362 }
363 if (s.led_blink_index >= state_num)
364 return;
365 if (s.led_on) {
366 if (now - s.last_led_edge_millis >= BLINK_ON_MS) {
367 digitalWrite(led_pin, LOW);
368 s.led_on = false;
369 s.led_blink_index++;
370 s.last_led_edge_millis = now;
371 }
372 } else {
373 if (now - s.last_led_edge_millis >= BLINK_OFF_MS) {
374 digitalWrite(led_pin, HIGH);
375 s.led_on = true;
376 s.last_led_edge_millis = now;
377 }
378 }
379}
380
381// Flush the accumulated log buffer to the server as one LOGS packet (type 15).
382// The board is identified server-side by UDP source IP, so no board_id is sent.
383inline void flushLogs(CoreState& s) {
384 if (g_logbuf.empty())
385 return;
386 static uint8_t pkt[sizeof(daq::PacketHeader) + 3 + LOG_BUF_SIZE];
387 size_t n =
389 g_logbuf.length(), millis(), pkt, sizeof(pkt));
390 if (n > 0) {
391 s.udp.beginPacket(s.serverIP, s.serverPort);
392 s.udp.write(pkt, n);
393 s.udp.endPacket();
394 }
395 g_logbuf.clear();
396}
397
398inline void setup(CoreState& s, const Config& cfg) {
399 Serial.begin(115200);
402 HF_LOG(cfg.board_name);
403 HF_LOGLN(" Hotfire state machine starting...");
404
405#ifndef SENSOR_ETH_USE_DHCP
406 s.board_id = (uint8_t)BOARD_ID;
407 s.staticIP = IPAddress(192, 168, 2, (uint8_t)BOARD_ID);
408#endif
409
410 const sense_board_pins::Layout& Pins = *cfg.pins;
411 pinMode(Pins.LED, OUTPUT);
412 digitalWrite(Pins.LED, LOW);
413
414 if (cfg.init_adc)
415 cfg.init_adc(cfg.user_data);
416
417 ESP_ERROR_CHECK(esp_read_mac(s.mac, ESP_MAC_ETH));
420 SPI.begin(Pins.ETH_SCLK, Pins.ETH_MISO, Pins.ETH_MOSI, Pins.ETH_CS);
422 Ethernet.init(Pins.ETH_CS);
424#ifdef SENSOR_ETH_USE_DHCP
425 // The ground station decides our address, and our ID with it; we only ask
426 // for one. See board_net.h for what happens when nothing answers.
429#else
430 // Boards that have not opted in keep their compiled-in static address.
431 Ethernet.begin(s.mac, s.staticIP, s.dns, s.gateway, s.subnet);
432#endif
434 HF_LOG("Board ID: ");
435 HF_LOG(static_cast<unsigned>(s.board_id));
436 HF_LOGLN(s.board_id == 0 ? " (none until the server assigns an address)"
437 : "");
438
439 // W5500 / Ethernet status (same pattern as Stream_ADC_Data and other DAQ
440 // sketches)
441 HF_LOGLN("[ETH] Ethernet initialized (SPI WIZnet)");
442 HF_LOG("Address source: ");
444 HF_LOG("Stack IP (Ethernet.localIP): ");
445 HF_LOGLN(Ethernet.localIP());
446 HF_LOG("Hardware: ");
447 switch (Ethernet.hardwareStatus()) {
448 case EthernetNoHardware:
449 HF_LOGLN("no chip detected — check CS / SPI wiring");
450 break;
451 case EthernetW5100:
452 HF_LOGLN("W5100");
453 break;
454 case EthernetW5200:
455 HF_LOGLN("W5200");
456 break;
457 case EthernetW5500:
458 HF_LOGLN("W5500");
459 break;
460 default:
461 HF_LOGLN("unknown");
462 break;
463 }
464 HF_LOG("Link status: ");
465 if (Ethernet.linkStatus() == LinkON) {
466 HF_LOGLN("Connected");
467 } else if (Ethernet.linkStatus() == LinkOFF) {
468 HF_LOGLN("Disconnected");
469 } else {
470 HF_LOGLN("Unknown");
471 }
472 if (Ethernet.localIP() == IPAddress(0, 0, 0, 0)) {
473 HF_LOGLN("[ETH] WARNING: stack IP is 0.0.0.0 — check cable / W5500");
474 }
475 Serial.flush();
476
478 Serial.print("UDP listening on port ");
480 Serial.println(
481 "(Expect SENSOR_CONFIG packet type 5 to transition to Active)");
482 g_ota_server.onStart(otaFreezeAddress, &s);
483 g_ota_server.begin();
484
489#ifdef SENSOR_ETH_USE_DHCP
490 HF_LOGLN(
491 "[NET] address assigned by the server; broadcasting until one is "
492 "heard");
493#endif
494 Serial.println("State -> WaitingForServer");
495 Serial.flush();
496 Serial.print("Config target: send SENSOR_CONFIG to ");
497 Serial.print(Ethernet.localIP());
498 Serial.println(":5005");
499 Serial.flush();
500 HF_LOGLN("Setup complete. State: WaitingForServer");
501
502 // Ethernet is up now, so this is the first log the server can actually
503 // receive (boot logs before Ethernet are unsendable). Announce identity +
504 // IP and flush immediately so the server sees the board come online.
505 HF_LOG("ONLINE board=");
506 HF_LOG(static_cast<unsigned>(s.board_id));
507 HF_LOG(" ip=");
508 HF_LOG(Ethernet.localIP());
509 HF_LOGLN_();
510 flushLogs(s);
511}
512
513inline void loop(CoreState& s, const Config& cfg) {
514#ifdef SENSOR_ETH_USE_DHCP
515 // Renew the lease as it expires; no-op while an OTA is in flight.
517 // On the recovery address, keep asking; a lease brings our ID with it.
518 if (BoardNet::retryDhcp(s.net, s.dns, s.gateway)) {
520 HF_LOG("ONLINE board=");
521 HF_LOG(static_cast<unsigned>(s.board_id));
522 HF_LOG(" ip=");
523 HF_LOG(Ethernet.localIP());
524 HF_LOGLN_();
525 flushLogs(s);
526 }
527#endif
528 // Non-blocking OTA check — blocks only if a client actually connects,
529 // and never returns on success (the board reboots into the new image).
530 g_ota_server.poll();
531 // Bench marker: prints only on builds made with -DSTAR_OTA_TEST_MESSAGE,
532 // so consecutive OTA uploads are visibly different on the serial monitor.
534
535 const size_t maxPacket = SENSOR_HOTFIRE_MAX_PACKET_SIZE;
536 static uint32_t s_udp_check_count = 0;
537 static unsigned long s_last_debug_ms = 0;
538 static int s_last_parse_result = -1;
539
540 s_udp_check_count++;
541 int packetSize = s.udp.parsePacket();
542 s_last_parse_result = packetSize;
543
544 if (packetSize > 0) {
545 IPAddress remoteIP = s.udp.remoteIP();
546 int remotePort = s.udp.remotePort();
548 int bytesRead = s.udp.read(packetBuffer, maxPacket);
549 if (bytesRead <= 0) {
550 HF_LOG("WARNING: parsePacket returned ");
551 HF_LOG(packetSize);
552 HF_LOGLN(" but read() returned 0");
553 } else {
554 HF_VERBOSE("Received packet from ");
555 HF_VERBOSE(remoteIP);
556 HF_VERBOSE(":");
557 HF_VERBOSE(remotePort);
558 HF_VERBOSE(" type ");
559 HF_VERBOSE(static_cast<unsigned>(packetBuffer[0]));
560 HF_VERBOSE(" (");
562 HF_VERBOSE(") len=");
563 HF_VERBOSE(bytesRead);
564 HF_VERBOSE(" hex:");
565 for (int i = 0; i < bytesRead && i < 12; i++) {
566 HF_VERBOSE(" ");
567 if (packetBuffer[i] < 16)
568 HF_VERBOSE("0");
570 }
573 s, cfg, packetBuffer, bytesRead, remoteIP, remotePort);
575 Serial.println("SENSOR_CONFIG parsed -> transitioning");
576 Serial.flush();
577 } else if (static_cast<uint8_t>(packetBuffer[0]) == 6) {
578 Serial.println(
579 "(Packet type 6 = ACTUATOR_CONFIG; PT expects "
580 "SENSOR_CONFIG type 5)");
581 Serial.flush();
582 } else if (static_cast<uint8_t>(packetBuffer[0]) == 5) {
583 Serial.println(
584 "(Type 5 received but parse failed - check header/length)");
585 Serial.flush();
586 }
587 applyPacketTransition(s, cfg, kind);
588 }
589 }
590
591#ifdef SENSOR_ETH_USE_DHCP
592 // Server gone quiet: forget it and resume discovery broadcasts so a new
593 // (or restarted) server can find us. Our address is unaffected -- it is
594 // the server's to change, not ours.
596 s.net.serverLearned = false;
598 Serial.println("[NET] server silent -- resuming discovery");
599 Serial.flush();
600 }
601#endif
602
604 if (cfg.collect_chunk)
605 cfg.collect_chunk(cfg.user_data);
606 } else if (s.state == State::SelfTest) {
607 if (cfg.run_self_test) {
608 std::vector<daq::SelfTestResult> results;
609 cfg.run_self_test(cfg.user_data, s.stored_config, results);
610
611 uint8_t adc_good = 0;
612 if (!results.empty() && results[0].sensor_id == 0) {
613 adc_good = results[0].result;
614 results.erase(results.begin());
615 }
616
618 size_t packetSize = daq::create_self_test_packet(
619 adc_good, results, millis(), packetBuffer,
620 sizeof(packetBuffer));
621 if (packetSize > 0) {
622 s.udp.beginPacket(s.serverIP, s.serverPort);
623 s.udp.write(packetBuffer, packetSize);
624 s.udp.endPacket();
625 HF_VERBOSE("Sent: SELF_TEST to ");
627 HF_VERBOSE(":");
629 }
630 }
632 Serial.println("State -> Active");
633 Serial.flush();
634 }
635
636 switch (s.state) {
638 break;
639 case State::Active:
640 if (cfg.send_chunks_to)
641 cfg.send_chunks_to(cfg.user_data, s.serverIP, s.serverPort,
642 false, IPAddress(0, 0, 0, 0), 0);
643 break;
645 if (cfg.send_chunks_to) {
646 bool also_abort = s.stored_config.valid &&
648 IPAddress actuatorIP(0, 0, 0, 0);
649 if (also_abort) {
650 const uint8_t* ip_bytes = reinterpret_cast<const uint8_t*>(
652 actuatorIP = IPAddress(ip_bytes[0], ip_bytes[1],
653 ip_bytes[2], ip_bytes[3]);
654 }
655 cfg.send_chunks_to(cfg.user_data, s.serverIP, s.serverPort,
656 also_abort, actuatorIP, s.serverPortDefault);
657 }
658 break;
659 case State::SelfTest:
660 break;
661 }
662
663 int state_num = static_cast<int>(s.state);
664 updateStateLed(s, cfg, state_num);
665
666 unsigned long now = millis();
667 if (s.state == State::WaitingForServer && (now - s_last_debug_ms >= 2000)) {
668 s_last_debug_ms = now;
669 HF_VERBOSE("PT debug: state=WaitingForServer IP=");
670 HF_VERBOSE(Ethernet.localIP());
671 HF_VERBOSE(" udp_checks=");
672 HF_VERBOSE(s_udp_check_count);
673 HF_VERBOSE(" last_parse=");
674 HF_VERBOSELN(s_last_parse_result);
675 }
677 s.lastHeartbeatMillis = now;
678 switch (s.state) {
680 HF_VERBOSELN("Setup state: sending heartbeat");
682 s.serverPort);
683 break;
684 case State::Active:
686 s.serverPort);
687 break;
688 case State::SelfTest:
690 s.serverIP, s.serverPort);
691 break;
694 s.serverIP, s.serverPort);
695 if (s.stored_config.valid &&
697 const uint8_t* ip_bytes = reinterpret_cast<const uint8_t*>(
699 IPAddress actuatorIP(ip_bytes[0], ip_bytes[1], ip_bytes[2],
700 ip_bytes[3]);
701 sendBoardHeartbeat(s, cfg,
703 actuatorIP, s.serverPortDefault);
704 }
705 break;
706 }
707#ifdef SENSOR_ETH_USE_DHCP
708 // Discovery: announce to everyone on the wire until a server is
709 // learned. Reuses the normal heartbeat packet on the same 1 s tick.
710 if (!s.net.serverLearned) {
712 if (s.state == State::Active)
714 else if (s.state == State::SelfTest)
716 else if (s.state == State::StandaloneAbort)
718 sendBoardHeartbeat(s, cfg, bs, IPAddress(255, 255, 255, 255),
720 }
721#endif
722 }
723
724 // Flush buffered logs to the server ~1 Hz (or sooner if the buffer is
725 // filling).
726 static unsigned long s_last_log_flush_ms = 0;
727 if (!g_logbuf.empty() &&
728 (g_logbuf.nearFull() || now - s_last_log_flush_ms >= 1000)) {
729 s_last_log_flush_ms = now;
730 flushLogs(s);
731 }
732
734}
735
736} // namespace SensorHotfire
static SerialStub Serial
Definition Arduino.h:128
#define HIGH
Definition Arduino.h:19
void pinMode(int, int)
Definition Arduino.h:46
#define OUTPUT
Definition Arduino.h:22
unsigned long millis()
Definition Arduino.h:29
#define HEX
Definition Arduino.h:24
#define LOW
Definition Arduino.h:20
void digitalWrite(int, int)
Definition Arduino.h:48
#define ETHERNET_INIT_DELAY_MS
#define LOOP_DELAY_MS
#define ETHERNET_BEGIN_DELAY_MS
#define BOARD_HEARTBEAT_INTERVAL_MS
#define ETHERNET_SPI_DELAY_MS
static EthernetClass Ethernet
Definition Ethernet.h:116
#define SENSOR_HOTFIRE_NO_CONN_ABORT_TYPE
#define SENSOR_HOTFIRE_MAX_PACKET_SIZE
Shared sensor hotfire state machine core — PT / TC / LC boards.
#define SENSOR_UDP_LISTEN_PORT
const int IPAddress
void delay(int ms)
The OTA listener.
#define MAX_SENSORS_PER_BOARD
Definition daq-protocol.h:7
constexpr const Layout & Pins
void setup()
Definition main.cpp:571
static void sendBoardHeartbeat()
Definition main.cpp:192
void loop()
Definition main.cpp:621
char packetBuffer[UDP_TX_PACKET_MAX_SIZE]
Definition main.cpp:15
bool g_verbose
Definition main.cpp:65
static BoardNet::State net
Definition main.cpp:57
static void flushLogs()
Definition main.cpp:1069
#define HOTFIRE_SERVER_PORT
#define HOTFIRE_SERVER_IP_OCTET_4
#define HOTFIRE_OTA_PORT
#define BOARD_ID
#define SERIAL_MONITOR_READY_DELAY_MS
LogBuffer g_logbuf
Definition hotfire_log.h:93
uint8_t g_log_stream_level
Definition hotfire_log.h:94
#define LOG_BUF_SIZE
Definition hotfire_log.h:42
#define HF_VERBOSE(...)
#define HF_LOG(...)
Definition hotfire_log.h:99
#define HF_VERBOSELN(...)
#define HF_VERBOSELN_()
#define HF_LOGLN_()
#define HF_LOGLN(...)
static const idle_config_t config
Definition idle_task.c:17
void configure(State &s, const byte mac[6])
Definition board_net.h:93
AddressSource begin(State &s, IPAddress dns, IPAddress gateway)
Bring the interface up: ask the ground station for an address, and take the recovery address only if ...
Definition board_net.h:181
void printMac(const State &s)
Print the MAC as aa:bb:cc:dd:ee:ff.
Definition board_net.h:103
uint8_t boardId(const State &s)
The board's ID: the last octet of the address the server assigned (192.168.2.31 -> 31).
Definition board_net.h:129
void beginOtaHold(State &s)
Called when an OTA transfer starts, so the address cannot move under it.
Definition board_net.h:144
bool retryDhcp(State &s, IPAddress dns, IPAddress gateway)
On the recovery address, ask for a lease again every SENSOR_ETH_DHCP_RETRY_INTERVAL_MS.
Definition board_net.h:213
bool serverWentSilent(const State &s)
Server has stopped talking — forget it and resume discovery broadcasts.
Definition board_net.h:166
bool onServerPacket(State &s, IPAddress remote_ip, IPAddress &serverIP)
A server packet arrived from remote_ip; adopt it as the server address.
Definition board_net.h:152
void maintainLease(State &s)
Renew the lease as it expires.
Definition board_net.h:138
const char * sourceName(AddressSource s)
Definition board_net.h:52
const uint8_t * get()
void updateStateLed(CoreState &s, const Config &cfg, int state_num)
bool readPacketHeader(const uint8_t *buffer, size_t len, daq::PacketHeader &hdr_out)
void otaFreezeAddress(void *user_data, uint32_t image_bytes)
Freeze the board's address before a single byte reaches flash.
StarOTA::Server g_ota_server
void applyPacketTransition(CoreState &s, const Config &cfg, IncomingPacketKind kind)
const char * packetTypeName(uint8_t type)
IncomingPacketKind processIncomingPacket(CoreState &s, const Config &cfg, const uint8_t *buffer, size_t len, IPAddress remote_ip, int remote_port)
void printTestMessage()
Bench-test helper: prints STAR_OTA_TEST_MESSAGE on a timer as.
bool parse_server_heartbeat_packet(const uint8_t *buffer, size_t buffer_size, PacketHeader &header_out, ServerHeartbeatPacket &data_out)
Parses a Server Heartbeat packet from buffer.
bool parse_sensor_config_packet(const uint8_t *buffer, size_t buffer_size, PacketHeader &header_out, std::vector< uint8_t > &sensor_ids_out, uint8_t &reference_voltage_out, bool &necessary_for_abort_out, uint32_t &controller_ip_out, uint8_t &enable_serial_printing_out)
Parses a Sensor Config packet from buffer.
size_t create_log_packet(uint8_t flags, const uint8_t *text, uint16_t text_len, uint32_t timestamp_ms, uint8_t *buffer, size_t buffer_size)
Creates a LOGS packet (type 15): buffered board log text for the server.
size_t create_board_heartbeat_packet(const BoardHeartbeatPacket &data, uint32_t timestamp_ms, uint8_t *buffer, size_t buffer_size)
Creates a complete Board Heartbeat packet in the provided buffer.
BoardState
Defines the operational state of a board.
Definition DiabloEnums.h:31
size_t create_self_test_packet(uint8_t adc_good, const std::vector< SelfTestResult > &results, uint32_t timestamp_ms, uint8_t *buffer, size_t buffer_size)
Creates a complete Self Test packet in the provided buffer.
*brief user defined string literal for JSON values *sa std::size_t n
Definition json.hpp:24474
bool serverLearned
server IP adopted from an inbound packet
Definition board_net.h:87
AddressSource source
Definition board_net.h:86
void(* on_reference_voltage_config)(void *user_data, uint8_t reference_voltage)
Called when SENSOR_CONFIG is received with reference_voltage byte (0=2.5V internal,...
void(* send_chunks_to)(void *user_data, IPAddress dest_ip, int dest_port, bool also_to_abort_controller, IPAddress abort_controller_ip, int abort_controller_port)
Send collected chunks to primary dest; if also_to_abort_controller then send same data to abort_contr...
void(* init_adc)(void *user_data)
void(* run_self_test)(void *user_data, const StoredSensorConfig &config, std::vector< daq::SelfTestResult > &results_out)
Called once when entering SelfTest state.
void(* collect_chunk)(void *user_data)
StoredSensorConfig stored_config
uint8_t sensor_ids[MAX_SENSORS_PER_BOARD]