33#include "actuator_config.h"
35#include "firmware_hash.h"
105static std::vector<bool>
153 return static_cast<uint8_t
>(s);
159 return "WaitingForServer";
163 return "NoConnectionAbort";
169 return "AbortFinished";
171 return "ConnectionLossDetected";
173 return "NoConnAbortFollower";
180 if (
state != new_state) {
181 Serial.print(
"State -> ");
190 const unsigned long now =
millis();
261 daq::BoardHeartbeatPacket hb;
278 udp.beginPacket(dest_ip, dest_port);
295 return static_cast<uint32_t
>(self[0]) << 24 |
296 static_cast<uint32_t
>(self[1]) << 16 |
297 static_cast<uint32_t
>(self[2]) << 8 |
static_cast<uint32_t
>(self[3]);
301 return IPAddress((ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF,
307 if (loc.ip_address == ip)
316 size_t buffer_size) {
317 if (buffer_size <
sizeof(daq::PacketHeader))
319 daq::PacketHeader header;
322 header.timestamp =
millis();
323 memcpy(buffer, &header,
sizeof(header));
324 return sizeof(header);
330 if (buffer_size <
sizeof(daq::PacketHeader))
332 daq::PacketHeader header;
335 header.timestamp =
millis();
336 memcpy(buffer, &header,
sizeof(header));
337 return sizeof(header);
347 if (loc.ip_address == self_ip) {
354 std::vector<daq::ActuatorCommand> cmds;
355 cmds.push_back({loc.actuator_id, loc.vent_state
356 ?
static_cast<uint8_t
>(1)
357 :
static_cast<uint8_t
>(0)});
365 HF_VERBOSE(
"Sent: actuator_command (vent) to ");
378 if (loc.ip_address == self_ip) {
385 std::vector<daq::ActuatorCommand> cmds;
386 cmds.push_back({loc.actuator_id, loc.abort_state
387 ?
static_cast<uint8_t
>(1)
388 :
static_cast<uint8_t
>(0)});
396 HF_VERBOSE(
"Sent: actuator_command (abort) to ");
408 if (loc.ip_address != self_ip)
421 if (loc.ip_address != self_ip)
435 daq::PacketHeader& header_out) {
436 if (buffer_size <
sizeof(daq::PacketHeader))
438 memcpy(&header_out, buffer,
sizeof(daq::PacketHeader));
454 const std::vector<daq::ActuatorCommand>&
commands);
456 const std::vector<daq::PWMActuatorCommand>&
commands);
459 uint32_t sip = (
static_cast<uint32_t
>(
serverIP[0]) << 24) |
460 (
static_cast<uint32_t
>(
serverIP[1]) << 16) |
461 (
static_cast<uint32_t
>(
serverIP[2]) << 8) |
468 remote_ip32 == sip) {
503 return "ACTUATOR_COMMAND";
505 return "PWM_ACTUATOR_COMMAND";
514 daq::PacketHeader hdr;
518 uint32_t remote_ip32 =
static_cast<uint32_t
>(remoteIP[0]) << 24 |
519 static_cast<uint32_t
>(remoteIP[1]) << 16 |
520 static_cast<uint32_t
>(remoteIP[2]) << 8 |
521 static_cast<uint32_t
>(remoteIP[3]);
523 switch (hdr.packet_type) {
525 daq::PacketHeader dummy;
526 daq::ServerHeartbeatPacket data;
529#ifdef SENSOR_ETH_USE_DHCP
532 uint32_t sip = (
static_cast<uint32_t
>(
serverIP[0]) << 24) |
533 (
static_cast<uint32_t
>(
serverIP[1]) << 16) |
534 (
static_cast<uint32_t
>(
serverIP[2]) << 8) |
542 daq::PacketHeader dummy;
543 uint8_t is_controller = 0;
544 uint8_t enable_serial = 0;
545 std::vector<daq::AbortActuatorLocation> act_locs;
546 std::vector<daq::AbortPTLocation> pt_locs;
548 is_controller, act_locs,
549 pt_locs, enable_serial)) {
558 (enable_serial >= 2) ? (enable_serial - 1) : 0;
559#ifdef SENSOR_ETH_USE_DHCP
576 std::vector<daq::SensorDataChunkCollection>
chunks;
577 daq::PacketHeader dummy;
579 for (
const auto& col :
chunks)
580 for (
const auto& dp : col.datapoints)
592 std::vector<daq::SensorDataChunkCollection>
chunks;
593 daq::PacketHeader dummy;
595 for (
const auto& col :
chunks) {
596 for (
const auto& dp : col.datapoints) {
604 .pressure_threshold_adc)
616 daq::PacketHeader cmd_header;
617 std::vector<daq::ActuatorCommand>
commands;
620 HF_VERBOSELN(
"ACTUATOR_COMMAND received (DAQv2-Comms):");
621 for (
size_t i = 0; i <
commands.size(); ++i) {
626 HF_VERBOSE(
static_cast<unsigned>(cmd.actuator_id));
628 HF_VERBOSELN(
static_cast<unsigned>(cmd.actuator_state));
637 daq::PacketHeader cmd_header;
638 std::vector<daq::PWMActuatorCommand>
commands;
641 HF_VERBOSELN(
"PWM_ACTUATOR_COMMAND received (DAQv2-Comms):");
642 for (
size_t i = 0; i <
commands.size(); ++i) {
647 HF_VERBOSE(
static_cast<unsigned>(cmd.actuator_id));
667 const std::vector<daq::ActuatorCommand>&
commands) {
671 uint8_t idx = cmd.actuator_id - 1;
678 uint8_t new_state = (cmd.actuator_state != 0) ? 1 : 0;
687 const std::vector<daq::PWMActuatorCommand>&
commands) {
697 (cmd.frequency > 0) ? (1000000 / cmd.frequency) : 0;
698 uint32_t on_time_us = (uint32_t)(cmd.duty_cycle * (
float)period_us);
708 bool initial_state = (cmd.duty_cycle > 0.0f);
717 unsigned long now_ms =
millis();
718 unsigned long now_us =
micros();
734 unsigned long elapsed_in_cycle =
735 now_us -
pwm_states[i].last_cycle_start_micros;
737 if (elapsed_in_cycle >=
pwm_states[i].period_micros) {
739 (elapsed_in_cycle /
pwm_states[i].period_micros) *
742 now_us -
pwm_states[i].last_cycle_start_micros;
745 if (elapsed_in_cycle >=
pwm_states[i].on_time_micros) {
770 for (uint8_t actuator_id = 1; actuator_id <=
NUM_SENSORS; actuator_id++) {
774 chunk.add_datapoint(actuator_id,
781 std::vector<daq::SensorDataChunkCollection>
chunks;
804 unsigned long now =
millis();
852 if (loc.ip_address == self_ip)
899 bool all_pt_received =
true;
903 all_pt_received =
false;
926 bool all_below =
true;
1041 HF_VERBOSELN(
"All actuators initialized to OFF state");
1045 for (uint8_t actuator_id = 1; actuator_id <=
NUM_SENSORS; actuator_id++) {
1072 static uint8_t pkt[
sizeof(daq::PacketHeader) + 3 +
LOG_BUF_SIZE];
1091 Serial.println(
"Actuator Hotfire state machine starting...");
1102 ESP_ERROR_CHECK(esp_read_mac(
mac, ESP_MAC_ETH));
1110#ifdef SENSOR_ETH_USE_DHCP
1118 Serial.print(
"Board ID: ");
1124 Serial.println(
"[ETH] Ethernet initialized (SPI WIZnet)");
1125 Serial.print(
"Address source: ");
1127 Serial.print(
"Stack IP (Ethernet.localIP): ");
1129 Serial.print(
"Hardware: ");
1130 switch (
Ethernet.hardwareStatus()) {
1131 case EthernetNoHardware:
1132 Serial.println(
"no chip detected — check CS / SPI wiring");
1144 Serial.println(
"unknown");
1147 Serial.print(
"Link status: ");
1148 if (
Ethernet.linkStatus() == LinkON) {
1149 Serial.println(
"Connected");
1150 }
else if (
Ethernet.linkStatus() == LinkOFF) {
1151 Serial.println(
"Disconnected");
1153 Serial.println(
"Unknown");
1157 "[ETH] WARNING: stack IP is 0.0.0.0 — check cable / W5500");
1164 Serial.print(
"UDP listening on port ");
1168 Serial.println(
"State -> WaitingForServer");
1172#ifdef SENSOR_ETH_USE_DHCP
1174 "[NET] address assigned by the server; broadcasting until one is "
1178 Serial.println(
"Setup complete. State: WaitingForServer");
1191#ifdef SENSOR_ETH_USE_DHCP
1215 int packetSize =
udp.parsePacket();
1216 if (packetSize > 0) {
1220 if (bytesRead > 0) {
1221 const uint8_t pktType = bytesRead >= (int)
sizeof(daq::PacketHeader)
1230 if (pktType == 4 || pktType == 10) {
1236 for (
int i = 0; i < bytesRead && i < 32; i++) {
1246 static_cast<uint32_t
>(remoteIP[0]) << 24 |
1247 static_cast<uint32_t
>(remoteIP[1]) << 16 |
1248 static_cast<uint32_t
>(remoteIP[2]) << 8 |
1249 static_cast<uint32_t
>(remoteIP[3]));
1256#ifdef SENSOR_ETH_USE_DHCP
1261 net.serverLearned =
false;
1263 Serial.println(
"[NET] server silent -- resuming discovery");
1295 unsigned long now =
millis();
1305#ifdef SENSOR_ETH_USE_DHCP
1308 if (!
net.serverLearned)
1316 static unsigned long s_last_log_flush_ms = 0;
1318 (
g_logbuf.nearFull() || now - s_last_log_flush_ms >= 1000)) {
1319 s_last_log_flush_ms = now;
void digitalWrite(int, int)
#define NO_CONN_ABORT_PT_WAIT_MS
#define ETHERNET_INIT_DELAY_MS
#define CONNECTION_LOSS_GRACE_MS
#define ETHERNET_BEGIN_DELAY_MS
#define HEARTBEAT_TIMEOUT_MS
#define ADC_READ_INTERVAL_MS
#define BOARD_HEARTBEAT_INTERVAL_MS
#define ETHERNET_SPI_DELAY_MS
static EthernetClass Ethernet
#define PT_ABORT_THRESHOLD_TIMEOUT_MS
#define NO_PT_ABORT_VENT_TO_ABORT_MS
PWMState pwm_states[NUM_ACTUATORS]
std::vector< daq::SensorDataChunkCollection > chunks
static unsigned long lastHeartbeatMillis
static unsigned long last_adc_read_ms
static bool no_conn_abort_pt_sent
static unsigned long led_cycle_start_ms
static unsigned long last_server_heartbeat_ms
static unsigned long led_phase_start_ms
static uint8_t led_blink_index
static std::vector< daq::AbortPTLocation > abort_pt_locations
IPAddress dns(192, 168, 2, 1)
IPAddress subnet(255, 255, 255, 0)
static bool is_abort_controller
IPAddress serverIP(192, 168, 2, 20)
static uint8_t actuator_states[NUM_ACTUATORS]
static ActuatorControllerState last_led_state
IPAddress gateway(0, 0, 0, 0)
static ActuatorControllerState state
static unsigned long state_enter_ms
static std::vector< daq::AbortActuatorLocation > abort_actuator_locations
static LedPhase led_phase
StarOTA::Server otaServer(OTA_TCP_PORT)
char packetBuffer[UDP_TX_PACKET_MAX_SIZE]
static void readCurrentSensePinsAndSend()
static void run_NoPTAbort()
static void run_WaitingForServer()
static StarOTA::Server otaServer(HOTFIRE_OTA_PORT)
static void sendAbortDoneToAllBoards()
static void run_AbortFinished()
static uint32_t getSelfIP()
static bool pt_abort_vent_done
static void applyVentToAllActuators()
static void sendBoardHeartbeatTo(IPAddress dest_ip, int dest_port)
static bool isIPInPTLocations(uint32_t ip)
static bool no_conn_abort_vent_sent
static void initializeCurrentSensePins()
static bool heartbeatTimedOut()
static std::vector< bool > pt_received_during_no_conn
static void run_NoConnectionAbort()
static IncomingPacketKind processIncomingPacket(const uint8_t *buffer, size_t len, IPAddress remoteIP)
static bool stateAcceptsActuatorCommand(uint32_t remote_ip32)
static void applyPacketTransition(IncomingPacketKind kind)
static void updateLedNonBlocking()
IPAddress dns(192, 168, 2, 1)
static void sendNoConnectionAbortToAllPTs()
static uint8_t getStateNumber(ActuatorControllerState s)
IPAddress subnet(255, 255, 255, 0)
static void applyVentLocalOnly()
static void initializeActuators()
static bool readPacketHeader(const uint8_t *buffer, size_t buffer_size, daq::PacketHeader &header_out)
static bool pt_abort_abort_done
static size_t create_abort_done_packet(uint8_t *buffer, size_t buffer_size)
static const char * actuatorPacketTypeName(uint8_t type)
static void setState(ActuatorControllerState new_state)
static std::vector< bool > pt_below_threshold
static void otaFreezeAddress(void *, uint32_t)
Freeze our address before a single byte reaches flash.
static BoardNet::State net
static std::set< uint32_t > connection_loss_received_ips
static void run_ConnectionLossDetected()
static void processPWMActuatorCommand(const std::vector< daq::PWMActuatorCommand > &commands)
static void applyAbortToAllActuators()
static size_t create_no_connection_abort_packet(uint8_t *buffer, size_t buffer_size)
static void run_NoConnAbortFollower()
static const char * stateName(ActuatorControllerState s)
static void sendBoardHeartbeat()
static void run_PTAbort()
static void processActuatorCommands(const std::vector< daq::ActuatorCommand > &commands)
IPAddress gateway(0, 0, 0, 0)
static bool no_pt_abort_abort_done
static void streamSensorDataIfDue()
static IPAddress uint32ToIPAddress(uint32_t ip)
static void applyAbortLocalOnly()
static daq::BoardState getBoardStateForHeartbeat()
static void initializePWMStates()
IPAddress serverIP(192, 168, 2, HOTFIRE_SERVER_IP_OCTET_4)
static bool no_pt_abort_vent_done
#define HOTFIRE_SERVER_PORT
#define ENABLE_ALL_STATE_TRANSITIONS
#define HOTFIRE_SERVER_IP_OCTET_4
#define SERIAL_MONITOR_READY_DELAY_MS
uint8_t g_log_stream_level
#define HF_VERBOSELN(...)
static osMessageQueueId_t commands
*brief create JSON pointer *sa reference_tokens const string_t const string_t & b
static BasicJsonType::size_type array_index(const string_t &s)
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 printTestMessage()
Bench-test helper: prints STAR_OTA_TEST_MESSAGE on a timer as.
int getActuatorPin(uint8_t actuator_id)
constexpr int NUM_ACTUATORS
int getCurrentSensePin(uint8_t actuator_id)
constexpr Layout Actuator_Board
size_t create_sensor_data_packet(const std::vector< SensorDataChunkCollection > &chunks, const uint8_t num_sensors, uint32_t timestamp_ms, uint8_t *buffer, size_t buffer_size)
Creates a complete Sensor Data packet in the provided buffer.
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.
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.
@ CONNECTION_LOSS_DETECTED
bool parse_pwm_actuator_packet(const uint8_t *buffer, size_t buffer_size, PacketHeader &header_out, std::vector< PWMActuatorCommand > &commands_out)
Parses a PWM Actuator Command packet from buffer.
bool parse_actuator_command_packet(const uint8_t *buffer, size_t buffer_size, PacketHeader &header_out, std::vector< ActuatorCommand > &commands_out)
Parses an Actuator Command packet from buffer.
bool parse_actuator_config_packet(const uint8_t *buffer, size_t buffer_size, PacketHeader &header_out, uint8_t &is_abort_controller_out, std::vector< AbortActuatorLocation > &abort_actuators_out, std::vector< AbortPTLocation > &abort_pts_out, uint8_t &enable_serial_printing_out)
Parses an Actuator Config packet from buffer.
bool parse_sensor_data_packet(const uint8_t *buffer, size_t buffer_size, PacketHeader &header_out, std::vector< SensorDataChunkCollection > &chunks_out)
Parses a Sensor Data packet from buffer into chunk collections.
size_t create_actuator_command_packet(const std::vector< ActuatorCommand > &commands, uint32_t timestamp_ms, uint8_t *buffer, size_t buffer_size)
Creates a complete Actuator Command packet in the provided buffer.
*brief user defined string literal for JSON values *sa std::size_t n
static bool elapsed(uint32_t now, uint32_t then, uint32_t interval)
unsigned long last_cycle_start_micros
Represents a single data chunk with timestamp and sensor datapoints.