29#ifndef SENSOR_HOTFIRE_MAX_PACKET_SIZE
30#define SENSOR_HOTFIRE_MAX_PACKET_SIZE 512
32#ifndef SENSOR_HOTFIRE_NO_CONN_ABORT_TYPE
33#define SENSOR_HOTFIRE_NO_CONN_ABORT_TYPE 11
35#ifndef SENSOR_UDP_LISTEN_PORT
36#define SENSOR_UDP_LISTEN_PORT 5005
82 bool also_to_abort_controller,
84 int abort_controller_port);
88 uint8_t reference_voltage);
96 std::vector<daq::SelfTestResult>& results_out);
139 daq::PacketHeader& hdr_out) {
140 if (len <
sizeof(daq::PacketHeader))
142 memcpy(&hdr_out, buffer,
sizeof(daq::PacketHeader));
149 return "BOARD_HEARTBEAT";
151 return "SERVER_HEARTBEAT";
153 return "SENSOR_DATA";
155 return "ACTUATOR_COMMAND";
157 return "SENSOR_CONFIG";
159 return "ACTUATOR_CONFIG";
165 return "CLEAR_ABORT";
167 return "PWM_ACTUATOR_COMMAND";
169 return "NO_CONNECTION_ABORT";
178 const uint8_t* buffer,
181 daq::PacketHeader hdr;
186 daq::PacketHeader dummy;
187 daq::ServerHeartbeatPacket data;
189#ifdef SENSOR_ETH_USE_DHCP
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;
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)");
225 if (cfg.on_reference_voltage_config)
226 cfg.on_reference_voltage_config(cfg.user_data,
231 const uint8_t log_mode = enable_serial_printing;
235 HF_LOGLN(
"SENSOR_CONFIG received:");
240 if (!sensor_ids.empty()) {
241 for (
size_t i = 0; i < sensor_ids.size(); ++i) {
244 HF_VERBOSE(
static_cast<unsigned>(sensor_ids[i]));
256 const uint8_t* ip_bytes =
reinterpret_cast<const uint8_t*
>(
258 IPAddress actuatorIP(ip_bytes[0], ip_bytes[1], ip_bytes[2],
267#ifdef SENSOR_ETH_USE_DHCP
277 if (len >=
sizeof(daq::PacketHeader))
289 if (cfg.run_self_test) {
291 Serial.println(
"State -> SelfTest");
294 Serial.println(
"State -> Active");
303 Serial.println(
"State -> StandaloneAbort");
310 Serial.println(
"State -> Active");
324 daq::BoardHeartbeatPacket hb;
328 hb.board_state = board_state;
339 s.
udp.beginPacket(dest_ip, dest_port);
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;
386 static uint8_t pkt[
sizeof(daq::PacketHeader) + 3 +
LOG_BUF_SIZE];
403 HF_LOGLN(
" Hotfire state machine starting...");
405#ifndef SENSOR_ETH_USE_DHCP
415 cfg.init_adc(cfg.user_data);
417 ESP_ERROR_CHECK(esp_read_mac(s.
mac, ESP_MAC_ETH));
424#ifdef SENSOR_ETH_USE_DHCP
441 HF_LOGLN(
"[ETH] Ethernet initialized (SPI WIZnet)");
442 HF_LOG(
"Address source: ");
444 HF_LOG(
"Stack IP (Ethernet.localIP): ");
447 switch (
Ethernet.hardwareStatus()) {
448 case EthernetNoHardware:
449 HF_LOGLN(
"no chip detected — check CS / SPI wiring");
465 if (
Ethernet.linkStatus() == LinkON) {
467 }
else if (
Ethernet.linkStatus() == LinkOFF) {
473 HF_LOGLN(
"[ETH] WARNING: stack IP is 0.0.0.0 — check cable / W5500");
478 Serial.print(
"UDP listening on port ");
481 "(Expect SENSOR_CONFIG packet type 5 to transition to Active)");
489#ifdef SENSOR_ETH_USE_DHCP
491 "[NET] address assigned by the server; broadcasting until one is "
494 Serial.println(
"State -> WaitingForServer");
496 Serial.print(
"Config target: send SENSOR_CONFIG to ");
500 HF_LOGLN(
"Setup complete. State: WaitingForServer");
514#ifdef SENSOR_ETH_USE_DHCP
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;
541 int packetSize = s.
udp.parsePacket();
542 s_last_parse_result = packetSize;
544 if (packetSize > 0) {
546 int remotePort = s.
udp.remotePort();
549 if (bytesRead <= 0) {
550 HF_LOG(
"WARNING: parsePacket returned ");
565 for (
int i = 0; i < bytesRead && i < 12; i++) {
575 Serial.println(
"SENSOR_CONFIG parsed -> transitioning");
577 }
else if (
static_cast<uint8_t
>(
packetBuffer[0]) == 6) {
579 "(Packet type 6 = ACTUATOR_CONFIG; PT expects "
580 "SENSOR_CONFIG type 5)");
582 }
else if (
static_cast<uint8_t
>(
packetBuffer[0]) == 5) {
584 "(Type 5 received but parse failed - check header/length)");
591#ifdef SENSOR_ETH_USE_DHCP
598 Serial.println(
"[NET] server silent -- resuming discovery");
604 if (cfg.collect_chunk)
605 cfg.collect_chunk(cfg.user_data);
607 if (cfg.run_self_test) {
608 std::vector<daq::SelfTestResult> results;
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());
621 if (packetSize > 0) {
632 Serial.println(
"State -> Active");
640 if (cfg.send_chunks_to)
645 if (cfg.send_chunks_to) {
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]);
663 int state_num =
static_cast<int>(s.
state);
666 unsigned long now =
millis();
668 s_last_debug_ms = now;
669 HF_VERBOSE(
"PT debug: state=WaitingForServer IP=");
697 const uint8_t* ip_bytes =
reinterpret_cast<const uint8_t*
>(
699 IPAddress actuatorIP(ip_bytes[0], ip_bytes[1], ip_bytes[2],
707#ifdef SENSOR_ETH_USE_DHCP
726 static unsigned long s_last_log_flush_ms = 0;
728 (
g_logbuf.nearFull() || now - s_last_log_flush_ms >= 1000)) {
729 s_last_log_flush_ms = now;
void digitalWrite(int, int)
#define ETHERNET_INIT_DELAY_MS
#define ETHERNET_BEGIN_DELAY_MS
#define BOARD_HEARTBEAT_INTERVAL_MS
#define ETHERNET_SPI_DELAY_MS
static EthernetClass Ethernet
#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
#define MAX_SENSORS_PER_BOARD
constexpr const Layout & Pins
static void sendBoardHeartbeat()
char packetBuffer[UDP_TX_PACKET_MAX_SIZE]
static BoardNet::State net
#define HOTFIRE_SERVER_PORT
#define HOTFIRE_SERVER_IP_OCTET_4
#define SERIAL_MONITOR_READY_DELAY_MS
uint8_t g_log_stream_level
#define HF_VERBOSELN(...)
static const idle_config_t config
void configure(State &s, const byte mac[6])
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 ...
void printMac(const State &s)
Print the MAC as aa:bb:cc:dd:ee:ff.
uint8_t boardId(const State &s)
The board's ID: the last octet of the address the server assigned (192.168.2.31 -> 31).
void beginOtaHold(State &s)
Called when an OTA transfer starts, so the address cannot move under it.
bool retryDhcp(State &s, IPAddress dns, IPAddress gateway)
On the recovery address, ask for a lease again every SENSOR_ETH_DHCP_RETRY_INTERVAL_MS.
bool serverWentSilent(const State &s)
Server has stopped talking — forget it and resume discovery broadcasts.
bool onServerPacket(State &s, IPAddress remote_ip, IPAddress &serverIP)
A server packet arrived from remote_ip; adopt it as the server address.
void maintainLease(State &s)
Renew the lease as it expires.
const char * sourceName(AddressSource s)
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.
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
bool serverLearned
server IP adopted from an inbound packet
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)
unsigned long last_led_cycle_millis
unsigned long lastHeartbeatMillis
const int serverPortDefault
StoredSensorConfig stored_config
unsigned long last_led_edge_millis
uint8_t reference_voltage
uint8_t sensor_ids[MAX_SENSORS_PER_BOARD]
uint32_t actuator_controller_ip