STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1/**
2 * Actuator Hotfire (controller) state machine — ESP32 PCB
3 *
4 * Implements the controller actuator board: coordinates abort sequences,
5 * sends NO_CONNECTION_ABORT to PTs, runs PT Abort / No PT Abort /
6 * StandaloneAbort. Uses DAQv2-Comms over Ethernet/UDP. Based on
7 * Avionics_State_Machine_Drafts/actuator_c.cpp.
8 */
9
10#include <Arduino.h>
11#include <Ethernet.h>
12#include <EthernetUdp.h>
13#include <SPI.h>
14#include <SPIFFS.h>
15#include <daq-protocol.h>
16#include <esp_mac.h>
17
18#include <cstring>
19#include <vector>
20
21#include "actuator_board_pins.h"
22#include "actuator_config.h"
23#include "firmware_hash.h"
24
25using namespace actuator_board_pins;
26
27//-----------------------------------------------------------------------------
28// Board identity and network (IP 192.168.2.XXX and board_id = XXX from SPIFFS)
29//-----------------------------------------------------------------------------
30static uint8_t board_id = BOARD_ID_DEFAULT;
31byte mac[6];
33IPAddress gateway(0, 0, 0, 0);
34IPAddress subnet(255, 255, 255, 0);
35IPAddress dns(192, 168, 2, 1);
36IPAddress serverIP(192, 168, 2, 20);
37const int udpListenPort = 5005;
38const int serverPort = 5006;
39const int ptBoardPort = 5005; // PT boards listen on 5005
40EthernetUDP udp;
41
42//-----------------------------------------------------------------------------
43// State machine
44//-----------------------------------------------------------------------------
56
59static unsigned long last_server_heartbeat_ms = 0;
60static unsigned long state_enter_ms = 0;
61
62// Parsed actuator config from server
63static bool is_abort_controller = false;
64static std::vector<daq::AbortActuatorLocation> abort_actuator_locations;
65static std::vector<daq::AbortPTLocation> abort_pt_locations;
66static bool config_valid = false;
67
68// For ConnectionLossDetected -> StandaloneAbort: last time we got sensor data
69// from a config PT
70static unsigned long last_pt_data_from_config_ms = 0;
71
72// For NoConnectionAbort: did we receive PT data during the wait?
74// Send NO_CONNECTION_ABORT to PTs only once per NoConnectionAbort entry
75static bool no_conn_abort_pt_sent = false;
76
77// Sensor data (current sense pins)
78const uint8_t NUM_SENSORS = NUM_ACTUATORS;
79static unsigned long last_adc_read_ms = 0;
81static unsigned long lastHeartbeatMillis = 0;
82
83//-----------------------------------------------------------------------------
84// Status LED: non-blocking blink count = state number
85//-----------------------------------------------------------------------------
86enum class LedPhase { Idle, On, Off };
88static unsigned long led_cycle_start_ms = 0;
89static uint8_t led_blink_index = 0;
90static unsigned long led_phase_start_ms = 0;
93
95 switch (s) {
97 return 1;
99 return 2;
101 return 3;
103 return 4;
105 return 5;
107 return 6;
109 return 7;
111 return 8;
113 return 9;
114 default:
115 return 1;
116 }
117}
118
119static void updateLedNonBlocking() {
120 const unsigned long now = millis();
121 const uint8_t nblinks = getStateNumber(state);
122 const int pin = Actuator_Board.LED;
123
124 if (state != last_led_state) {
127 led_cycle_start_ms = now;
128 }
129
130 switch (led_phase) {
131 case LedPhase::Idle:
132 digitalWrite(pin, LOW);
133 if ((now - led_cycle_start_ms) >= LED_CYCLE_MS) {
134 led_cycle_start_ms = now;
135 led_blink_index = 0;
136 digitalWrite(pin, HIGH);
138 led_phase_start_ms = now;
139 }
140 break;
141 case LedPhase::On:
142 if ((now - led_phase_start_ms) >= LED_BLINK_ON_MS) {
143 digitalWrite(pin, LOW);
145 led_phase_start_ms = now;
146 }
147 break;
148 case LedPhase::Off:
149 if ((now - led_phase_start_ms) >= LED_BLINK_OFF_MS) {
151 if (led_blink_index < nblinks) {
152 digitalWrite(pin, HIGH);
154 } else {
156 led_cycle_start_ms = now;
157 }
158 led_phase_start_ms = now;
159 }
160 break;
161 }
162}
163
164//-----------------------------------------------------------------------------
165// Helpers: map state to daq::BoardState for heartbeats
166//-----------------------------------------------------------------------------
191
192static void sendBoardHeartbeat() {
193 daq::BoardHeartbeatPacket hb;
194 memcpy(hb.firmware_hash, FirmwareHash::get(), 32);
195 hb.board_id = board_id;
196 hb.engine_state = daq::EngineState::SAFE;
197 hb.board_state = getBoardStateForHeartbeat();
198
201 sizeof(packetBuffer));
202 if (len == 0)
203 return;
204
205 udp.beginPacket(serverIP, serverPort);
206 udp.write(packetBuffer, len);
207 udp.endPacket();
208}
209
210//-----------------------------------------------------------------------------
211// Helpers: IP in config PT list?
212//-----------------------------------------------------------------------------
213static bool isIPInPTLocations(uint32_t ip) {
214 for (const auto& loc : abort_pt_locations) {
215 if (loc.ip_address == ip)
216 return true;
217 }
218 return false;
219}
220
221static IPAddress uint32ToIPAddress(uint32_t ip) {
222 return IPAddress((ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF,
223 ip & 0xFF);
224}
225
226//-----------------------------------------------------------------------------
227// Parse incoming UDP
228//-----------------------------------------------------------------------------
229static bool readPacketHeader(const uint8_t* buffer, size_t buffer_size,
230 daq::PacketHeader& header_out) {
231 if (buffer_size < sizeof(daq::PacketHeader))
232 return false;
233 memcpy(&header_out, buffer, sizeof(daq::PacketHeader));
234 return true;
235}
236
246
247static void processActuatorCommands(
248 const std::vector<daq::ActuatorCommand>& commands);
249
250static IncomingPacketKind processIncomingPacket(const uint8_t* buffer,
251 size_t len,
252 IPAddress remoteIP) {
253 daq::PacketHeader hdr;
254 if (!readPacketHeader(buffer, len, hdr))
256
257 switch (hdr.packet_type) {
259 daq::PacketHeader dummy;
260 daq::ServerHeartbeatPacket data;
261 if (daq::parse_server_heartbeat_packet(buffer, len, dummy, data)) {
264 }
266 }
268 daq::PacketHeader dummy;
269 uint8_t is_controller = 0;
270 std::vector<daq::AbortActuatorLocation> act_locs;
271 std::vector<daq::AbortPTLocation> pt_locs;
272 uint8_t enable_serial_printing = 0;
274 buffer, len, dummy, is_controller, act_locs, pt_locs,
275 enable_serial_printing)) {
276 (void)enable_serial_printing;
277 is_abort_controller = (is_controller != 0);
278 abort_actuator_locations = act_locs;
279 abort_pt_locations = pt_locs;
280 config_valid = true;
282 }
284 }
294 }
295 if (config_valid && !abort_pt_locations.empty()) {
296 uint32_t rip = static_cast<uint32_t>(remoteIP);
297 if (isIPInPTLocations(rip)) {
299 }
300 }
303 daq::PacketHeader cmd_header;
304 std::vector<daq::ActuatorCommand> commands;
305 if (daq::parse_actuator_command_packet(buffer, len, cmd_header,
306 commands) &&
310 }
312 }
313 default:
315 }
316}
317
319 const std::vector<daq::ActuatorCommand>& commands) {
320 for (const auto& cmd : commands) {
321 if (cmd.actuator_id < 1 || cmd.actuator_id > NUM_ACTUATORS)
322 continue;
323 int pin = getActuatorPin(cmd.actuator_id);
324 if (pin < 0)
325 continue;
326 uint8_t new_state = (cmd.actuator_state != 0) ? 1 : 0;
327 uint8_t idx = cmd.actuator_id - 1;
328 if (actuator_states[idx] != new_state) {
329 digitalWrite(pin, new_state ? HIGH : LOW);
330 actuator_states[idx] = new_state;
331 }
332 }
333}
334
335//-----------------------------------------------------------------------------
336// Sensor data: read current sense pins and send (like Actuator_Testing)
337//-----------------------------------------------------------------------------
340 for (uint8_t i = 0; i < NUM_SENSORS; i++) {
341 int pin = getCurrentSensePin(i);
342 if (pin < 0)
343 continue;
344 int adcValue = analogRead(pin);
345 float voltage = (static_cast<float>(adcValue) / 4095.0f) * 3.3f;
346 uint32_t voltage_bits;
347 memcpy(&voltage_bits, &voltage, sizeof(float));
348 chunk.add_datapoint(i, voltage_bits);
349 }
350 if (chunk.empty())
351 return;
352
354 std::vector<daq::SensorDataChunkCollection> chunks;
355 chunks.push_back(chunk);
356 size_t packetSize = daq::create_sensor_data_packet(
358 if (packetSize == 0)
359 return;
360
361 udp.beginPacket(serverIP, serverPort);
362 udp.write(packetBuffer, packetSize);
363 udp.endPacket();
364}
365
366static bool heartbeatTimedOut() {
368 return false;
370}
371
373 unsigned long now = millis();
375 last_adc_read_ms = now;
377 }
378}
379
380//-----------------------------------------------------------------------------
381// NO_CONNECTION_ABORT: header-only (not in DAQv2-Comms serializers)
382//-----------------------------------------------------------------------------
383static size_t create_no_connection_abort_packet(uint8_t* buffer,
384 size_t buffer_size) {
385 if (buffer_size < sizeof(daq::PacketHeader))
386 return 0;
387 daq::PacketHeader header;
388 header.packet_type = daq::PacketType::NO_CONNECTION_ABORT;
389 header.version = DIABLO_COMMS_VERSION;
390 header.timestamp = millis();
391 memcpy(buffer, &header, sizeof(header));
392 return sizeof(header);
393}
394
395//-----------------------------------------------------------------------------
396// Send NO_CONNECTION_ABORT to each PT in config
397//-----------------------------------------------------------------------------
400 size_t len =
402 if (len == 0)
403 return;
404
405 for (const auto& loc : abort_pt_locations) {
406 IPAddress ptIP = uint32ToIPAddress(loc.ip_address);
407 udp.beginPacket(ptIP, ptBoardPort);
408 udp.write(packetBuffer, len);
409 udp.endPacket();
410 }
411}
412
413//-----------------------------------------------------------------------------
414// State handlers
415//-----------------------------------------------------------------------------
416static void run_WaitingForServer() {
417 (void)0;
418}
419
427
435
440 }
441
442 unsigned long elapsed = millis() - state_enter_ms;
445 false; // reset for next time we enter NoConnectionAbort
448 } else {
450 }
452 }
453}
454
455static void run_PTAbort() {
456 // TODO: Run procedure involving receiving PT data and actuating solenoids.
457 // When procedure finishes, transition to AbortFinished.
461 }
462}
463
464static void run_NoPTAbort() {
465 // TODO: Run slightly different procedure involving receiving PT data and
466 // actuating solenoids. When procedure finishes, transition to
467 // AbortFinished.
471 }
472}
473
474static void run_AbortFinished() {
475 (void)0;
476}
477
483 return;
484 }
485 // If connection to one or more necessary-for-abort boards is lost ->
486 // StandaloneAbort
492 }
493 }
494}
495
496static void run_StandaloneAbort() {
497 // TODO: Standalone abort procedure. When done -> AbortFinished.
501 }
502}
503
504//-----------------------------------------------------------------------------
505// Packet-driven transitions
506//-----------------------------------------------------------------------------
543
544//-----------------------------------------------------------------------------
545// Pin setup
546//-----------------------------------------------------------------------------
547static void initializeActuators() {
548 Serial.println("Initializing actuators...");
549 for (int i = 1; i <= NUM_ACTUATORS; i++) {
550 int pin = getActuatorPin(i);
551 if (pin < 0)
552 continue;
553 pinMode(pin, OUTPUT);
554 digitalWrite(pin, LOW);
555 actuator_states[i - 1] = 0;
556 }
557 Serial.println("All actuators initialized to OFF state");
558}
559
561 for (uint8_t i = 0; i < NUM_SENSORS; i++) {
562 int pin = getCurrentSensePin(i);
563 if (pin >= 0)
564 pinMode(pin, INPUT);
565 }
566}
567
568//-----------------------------------------------------------------------------
569// Setup and loop
570//-----------------------------------------------------------------------------
571void setup() {
572 Serial.begin(115200);
573 Serial.println("Actuator Hotfire (controller) starting...");
574
575 if (SPIFFS.begin(false)) {
576 File f = SPIFFS.open(SPIFFS_BOARD_VALUE_PATH, "r");
577 if (f && f.available() >= 1) {
578 uint8_t b;
579 if (f.read(&b, 1) == 1) {
580 board_id = b;
581 staticIP = IPAddress(192, 168, 2, b);
582 Serial.print("Board ID and IP from SPIFFS: ");
583 Serial.print(static_cast<unsigned>(board_id));
584 Serial.print(" / 192.168.2.");
585 Serial.println(static_cast<unsigned>(b));
586 }
587 }
588 if (f)
589 f.close();
590 SPIFFS.end();
591 }
592
595
600
601 ESP_ERROR_CHECK(esp_read_mac(mac, ESP_MAC_ETH));
602 SPI.begin(Actuator_Board.ETH_SCLK, Actuator_Board.ETH_MISO,
603 Actuator_Board.ETH_MOSI, Actuator_Board.ETH_CS);
605 Ethernet.init(Actuator_Board.ETH_CS);
609 udp.begin(udpListenPort);
610
611 Serial.print("Ethernet initialized. IP: ");
612 Serial.println(Ethernet.localIP());
613
617
618 Serial.println("Setup complete. State: WaitingForServer");
619}
620
621void loop() {
623
624 int packetSize = udp.parsePacket();
625 if (packetSize > 0) {
626 IPAddress remoteIP = udp.remoteIP();
628 int bytesRead = udp.read(packetBuffer, sizeof(packetBuffer));
629 if (bytesRead > 0) {
630 IncomingPacketKind kind =
631 processIncomingPacket(packetBuffer, bytesRead, remoteIP);
633 }
634 }
635
636 switch (state) {
639 break;
641 run_Active();
642 break;
645 break;
648 break;
650 run_PTAbort();
651 break;
654 break;
657 break;
660 break;
663 break;
664 }
665
666 unsigned long now = millis();
670 }
671
673}
static SerialStub Serial
Definition Arduino.h:128
#define INPUT
Definition Arduino.h:21
#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
int analogRead(int)
Definition Arduino.h:53
#define LOW
Definition Arduino.h:20
void digitalWrite(int, int)
Definition Arduino.h:48
#define NO_CONNECTION_ABORT_DONE_MS
#define BOARD_ID_DEFAULT
#define NO_CONN_ABORT_PT_WAIT_MS
#define ETHERNET_INIT_DELAY_MS
#define LED_CYCLE_MS
#define LOOP_DELAY_MS
#define CONNECTION_LOSS_GRACE_MS
#define STANDALONE_ABORT_PT_LOSS_MS
#define SPIFFS_BOARD_VALUE_PATH
#define ETHERNET_BEGIN_DELAY_MS
#define HEARTBEAT_TIMEOUT_MS
#define LED_BLINK_OFF_MS
#define ADC_READ_INTERVAL_MS
#define BOARD_HEARTBEAT_INTERVAL_MS
#define LED_BLINK_ON_MS
#define ETHERNET_SPI_DELAY_MS
static EthernetClass Ethernet
Definition Ethernet.h:116
#define MAX_PACKET_SIZE
Definition main.h:6
const int IPAddress
void delay(int ms)
static uint8_t chunk[64]
Definition board_uart9.c:12
#define DIABLO_COMMS_VERSION
Definition daq-protocol.h:4
std::vector< daq::SensorDataChunkCollection > chunks
Definition main.cpp:55
#define NUM_SENSORS
Definition main.cpp:46
static void readCurrentSensePinsAndSend()
Definition main.cpp:338
static void run_NoPTAbort()
Definition main.cpp:464
static void run_Active()
Definition main.cpp:420
static unsigned long lastHeartbeatMillis
Definition main.cpp:81
static unsigned long last_adc_read_ms
Definition main.cpp:79
static void run_WaitingForServer()
Definition main.cpp:416
static bool no_conn_abort_pt_sent
Definition main.cpp:75
static unsigned long led_cycle_start_ms
Definition main.cpp:88
static void run_AbortFinished()
Definition main.cpp:474
static unsigned long last_server_heartbeat_ms
Definition main.cpp:59
static unsigned long led_phase_start_ms
Definition main.cpp:90
ActuatorControllerState
Definition main.cpp:45
const int serverPort
Definition main.cpp:38
static uint8_t led_blink_index
Definition main.cpp:89
static bool isIPInPTLocations(uint32_t ip)
Definition main.cpp:213
static void initializeCurrentSensePins()
Definition main.cpp:560
static bool heartbeatTimedOut()
Definition main.cpp:366
static void run_NoConnectionAbort()
Definition main.cpp:436
static bool pt_data_received_during_no_conn_abort_wait
Definition main.cpp:73
static bool config_valid
Definition main.cpp:66
void setup()
Definition main.cpp:571
static IncomingPacketKind processIncomingPacket(const uint8_t *buffer, size_t len, IPAddress remoteIP)
Definition main.cpp:250
static std::vector< daq::AbortPTLocation > abort_pt_locations
Definition main.cpp:65
static void applyPacketTransition(IncomingPacketKind kind)
Definition main.cpp:507
const int udpListenPort
Definition main.cpp:37
static void updateLedNonBlocking()
Definition main.cpp:119
IPAddress dns(192, 168, 2, 1)
IncomingPacketKind
Definition main.cpp:237
static void sendNoConnectionAbortToAllPTs()
Definition main.cpp:398
static uint8_t getStateNumber(ActuatorControllerState s)
Definition main.cpp:94
byte mac[6]
Definition main.cpp:31
IPAddress subnet(255, 255, 255, 0)
IPAddress staticIP(192, 168, 2, BOARD_ID_DEFAULT)
static bool is_abort_controller
Definition main.cpp:63
LedPhase
Definition main.cpp:86
@ Off
Definition main.cpp:86
@ Idle
Definition main.cpp:86
static void initializeActuators()
Definition main.cpp:547
static bool readPacketHeader(const uint8_t *buffer, size_t buffer_size, daq::PacketHeader &header_out)
Definition main.cpp:229
IPAddress serverIP(192, 168, 2, 20)
static uint8_t board_id
Definition main.cpp:30
static uint8_t actuator_states[NUM_ACTUATORS]
Definition main.cpp:80
static void run_ConnectionLossDetected()
Definition main.cpp:478
EthernetUDP udp
Definition main.cpp:40
static size_t create_no_connection_abort_packet(uint8_t *buffer, size_t buffer_size)
Definition main.cpp:383
static void run_StandaloneAbort()
Definition main.cpp:496
static void sendBoardHeartbeat()
Definition main.cpp:192
static void run_PTAbort()
Definition main.cpp:455
const int ptBoardPort
Definition main.cpp:39
static void processActuatorCommands(const std::vector< daq::ActuatorCommand > &commands)
Definition main.cpp:318
static ActuatorControllerState last_led_state
Definition main.cpp:91
IPAddress gateway(0, 0, 0, 0)
static ActuatorControllerState state
Definition main.cpp:57
static void run_StandardAbort()
Definition main.cpp:428
static void streamSensorDataIfDue()
Definition main.cpp:372
static IPAddress uint32ToIPAddress(uint32_t ip)
Definition main.cpp:221
static unsigned long state_enter_ms
Definition main.cpp:60
static std::vector< daq::AbortActuatorLocation > abort_actuator_locations
Definition main.cpp:64
static LedPhase led_phase
Definition main.cpp:87
static daq::BoardState getBoardStateForHeartbeat()
Definition main.cpp:167
static unsigned long last_pt_data_from_config_ms
Definition main.cpp:70
void loop()
Definition main.cpp:621
char packetBuffer[UDP_TX_PACKET_MAX_SIZE]
Definition main.cpp:15
static osMessageQueueId_t commands
Definition idle_task.c:28
*brief create JSON pointer *sa reference_tokens const string_t const string_t & b
Definition json.hpp:13878
const uint8_t * get()
int getActuatorPin(uint8_t actuator_id)
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_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
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.
static bool elapsed(uint32_t now, uint32_t then, uint32_t interval)
Definition self_test.c:10
Represents a single data chunk with timestamp and sensor datapoints.