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 — single firmware for all actuator boards (controller and
3 * designated survivor).
4 *
5 * State machine: WaitingForServer -> Active <-> StandardAbort/NoConnectionAbort
6 * -> PTAbort/NoPTAbort/StandaloneAbort -> AbortFinished. Behavior gated by
7 * is_abort_controller from ACTUATOR_CONFIG. Uses DAQv2-Comms over Ethernet/UDP.
8 * Implements plan: Single Actuator Hotfire Script
9 * (Hotfire_Code/Actuator_Hotfire).
10 *
11 * Networking mirrors the sense-board core (SensorHotfireCore.h): with
12 * -DSENSOR_ETH_USE_DHCP the ground station assigns this board's address from
13 * its MAC -> IP reservation table, and the board broadcasts BOARD_HEARTBEAT
14 * until a server is heard, learning the server's address from its packets.
15 * See common/board_net.h.
16 */
17
18#include "main.h"
19
20#include <Arduino.h>
21#include <Ethernet.h>
22#include <EthernetUdp.h>
23#include <SPI.h>
24#include <STAR_EthernetOTA.h>
25#include <daq-protocol.h>
26#include <esp_mac.h>
27
28#include <cstring>
29#include <set>
30#include <vector>
31
32#include "actuator_board_pins.h"
33#include "actuator_config.h"
34#include "board_net.h"
35#include "firmware_hash.h"
36
37using namespace actuator_board_pins;
38
39//-----------------------------------------------------------------------------
40// Board identity and network
41//-----------------------------------------------------------------------------
42static uint8_t board_id = 0; // from the DHCP-assigned address; see board_net.h
43byte mac[6];
44IPAddress gateway(0, 0, 0, 0);
45IPAddress subnet(255, 255, 255, 0);
46IPAddress dns(192, 168, 2, 1);
48const int udpListenPort = 5005;
50const int ptBoardPort = 5005;
51EthernetUDP udp;
53
54// Address + server-discovery state. The scheme lives in common/board_net.h,
55// shared with SensorHotfireCore.h so the actuator and the sense boards behave
56// identically and cannot drift apart.
58
59//-----------------------------------------------------------------------------
60// Verbose serial gate from ACTUATOR_CONFIG (enable_serial_printing); default on
61// until config says otherwise. Shared two-tier logging: HF_LOG* = always,
62// HF_VERBOSE* = verbose-only (this flag).
63//-----------------------------------------------------------------------------
64#include "hotfire_log.h"
65bool g_verbose = true;
66
67/**
68 * Freeze our address before a single byte reaches flash. Our address is a DHCP
69 * lease, and a renewal landing mid-flash would move us and drop the transfer,
70 * so this suspends renewals for the duration.
71 */
72static void otaFreezeAddress(void*, uint32_t) {
74}
75
76//-----------------------------------------------------------------------------
77// State machine
78//-----------------------------------------------------------------------------
89
92static unsigned long last_server_heartbeat_ms = 0;
93static unsigned long state_enter_ms = 0;
94
95// Config from server
96static bool is_abort_controller = false;
97static std::vector<daq::AbortActuatorLocation> abort_actuator_locations;
98static std::vector<daq::AbortPTLocation> abort_pt_locations;
99static bool config_valid = false;
100
101// NoConnectionAbort: vent sent once, then per-PT tracking (all received ->
102// PTAbort else NoPTAbort)
103static bool no_conn_abort_vent_sent = false;
104static bool no_conn_abort_pt_sent = false;
105static std::vector<bool>
106 pt_received_during_no_conn; // index = abort_pt_locations index
107
108// ConnectionLossDetected: IPs we've received a packet from (any packet =
109// "reachable")
110static std::set<uint32_t> connection_loss_received_ips;
111
112// PT Abort: per-PT "below threshold" and vent/abort phase done
113static std::vector<bool> pt_below_threshold;
114static bool pt_abort_vent_done = false;
115static bool pt_abort_abort_done = false;
116
117// No PT Abort: vent done, then after 10s abort done
118static bool no_pt_abort_vent_done = false;
119static bool no_pt_abort_abort_done = false;
120
121// Sensor data
123static unsigned long last_adc_read_ms = 0;
125static unsigned long lastHeartbeatMillis = 0;
126
127// PWM state (for PWM_ACTUATOR_COMMAND)
128struct PWMState {
129 bool active;
130 unsigned long start_time;
131 uint32_t duration;
137};
139
140//-----------------------------------------------------------------------------
141// Status LED: state numbers 1-11 (plan: 7=PTAbort, 8=NoPTAbort,
142// 9=StandaloneAbort)
143//-----------------------------------------------------------------------------
144enum class LedPhase { Idle, On, Off };
146static unsigned long led_cycle_start_ms = 0;
147static uint8_t led_blink_index = 0;
148static unsigned long led_phase_start_ms = 0;
151
153 return static_cast<uint8_t>(s);
154}
155
156static const char* stateName(ActuatorControllerState s) {
157 switch (s) {
159 return "WaitingForServer";
161 return "Active";
163 return "NoConnectionAbort";
165 return "PTAbort";
167 return "NoPTAbort";
169 return "AbortFinished";
171 return "ConnectionLossDetected";
173 return "NoConnAbortFollower";
174 default:
175 return "?";
176 }
177}
178
179static void setState(ActuatorControllerState new_state) {
180 if (state != new_state) {
181 Serial.print("State -> ");
182 Serial.println(stateName(new_state));
183 Serial.flush();
184 state = new_state;
186 }
187}
188
189static void updateLedNonBlocking() {
190 const unsigned long now = millis();
191 const uint8_t nblinks = getStateNumber(state);
192 const int pin = Actuator_Board.LED;
193
194 if (state != last_led_state) {
197 led_cycle_start_ms = now;
198 }
199
200 switch (led_phase) {
201 case LedPhase::Idle:
202 digitalWrite(pin, LOW);
203 if ((now - led_cycle_start_ms) >= LED_CYCLE_MS) {
204 led_cycle_start_ms = now;
205 led_blink_index = 0;
206 digitalWrite(pin, HIGH);
208 led_phase_start_ms = now;
209 }
210 break;
211 case LedPhase::On:
212 if ((now - led_phase_start_ms) >= LED_BLINK_ON_MS) {
213 digitalWrite(pin, LOW);
215 led_phase_start_ms = now;
216 }
217 break;
218 case LedPhase::Off:
219 if ((now - led_phase_start_ms) >= LED_BLINK_OFF_MS) {
221 if (led_blink_index < nblinks) {
222 digitalWrite(pin, HIGH);
224 } else {
226 led_cycle_start_ms = now;
227 }
228 led_phase_start_ms = now;
229 }
230 break;
231 }
232}
233
234//-----------------------------------------------------------------------------
235// Heartbeat
236//-----------------------------------------------------------------------------
259
260static void sendBoardHeartbeatTo(IPAddress dest_ip, int dest_port) {
261 daq::BoardHeartbeatPacket hb;
262 memcpy(hb.firmware_hash, FirmwareHash::get(), 32);
263 hb.board_id = board_id;
264 hb.engine_state = daq::EngineState::SAFE;
265 hb.board_state = getBoardStateForHeartbeat();
266
269 sizeof(packetBuffer));
270 if (len == 0)
271 return;
272
273 HF_VERBOSE("Sent: heartbeat to ");
274 HF_VERBOSE(dest_ip);
275 HF_VERBOSE(":");
276 HF_VERBOSELN(dest_port);
277 Serial.flush();
278 udp.beginPacket(dest_ip, dest_port);
279 udp.write(packetBuffer, len);
280 udp.endPacket();
281}
282
286
287//-----------------------------------------------------------------------------
288// Helpers: self IP as uint32_t, IP in PT list
289//-----------------------------------------------------------------------------
290static uint32_t getSelfIP() {
291 // Use the live stack address, not staticIP: the board sits on whatever
292 // the server leased it, and self-matching against ACTUATOR_CONFIG
293 // locations must reflect where we actually are.
294 IPAddress self = Ethernet.localIP();
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]);
298}
299
300static IPAddress uint32ToIPAddress(uint32_t ip) {
301 return IPAddress((ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF,
302 ip & 0xFF);
303}
304
305static bool isIPInPTLocations(uint32_t ip) {
306 for (const auto& loc : abort_pt_locations) {
307 if (loc.ip_address == ip)
308 return true;
309 }
310 return false;
311}
312
313// NO_CONNECTION_ABORT: header-only packet (ABORT type) sent to PTs so they
314// enter standalone/abort path
315static size_t create_no_connection_abort_packet(uint8_t* buffer,
316 size_t buffer_size) {
317 if (buffer_size < sizeof(daq::PacketHeader))
318 return 0;
319 daq::PacketHeader header;
320 header.packet_type = daq::PacketType::NO_CONNECTION_ABORT;
321 header.version = 1;
322 header.timestamp = millis();
323 memcpy(buffer, &header, sizeof(header));
324 return sizeof(header);
325}
326
327// ABORT_DONE: header-only packet sent to PTs and generic actuators to signify
328// abort completion
329static size_t create_abort_done_packet(uint8_t* buffer, size_t buffer_size) {
330 if (buffer_size < sizeof(daq::PacketHeader))
331 return 0;
332 daq::PacketHeader header;
333 header.packet_type = daq::PacketType::ABORT_DONE;
334 header.version = 1;
335 header.timestamp = millis();
336 memcpy(buffer, &header, sizeof(header));
337 return sizeof(header);
338}
339
340//-----------------------------------------------------------------------------
341// Apply vent or abort to all actuators (self = local, others = UDP to that IP)
342//-----------------------------------------------------------------------------
344 const uint32_t self_ip = getSelfIP();
346 for (const auto& loc : abort_actuator_locations) {
347 if (loc.ip_address == self_ip) {
348 int pin = getActuatorPin(loc.actuator_id);
349 if (pin >= 0) {
350 digitalWrite(pin, loc.vent_state ? HIGH : LOW);
351 actuator_states[loc.actuator_id - 1] = loc.vent_state ? 1 : 0;
352 }
353 } else {
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)});
359 cmds, millis(), packetBuffer, sizeof(packetBuffer));
360 if (len > 0) {
361 udp.beginPacket(uint32ToIPAddress(loc.ip_address),
363 udp.write(packetBuffer, len);
364 udp.endPacket();
365 HF_VERBOSE("Sent: actuator_command (vent) to ");
366 HF_VERBOSE(uint32ToIPAddress(loc.ip_address));
367 HF_VERBOSE(":");
369 }
370 }
371 }
372}
373
375 const uint32_t self_ip = getSelfIP();
377 for (const auto& loc : abort_actuator_locations) {
378 if (loc.ip_address == self_ip) {
379 int pin = getActuatorPin(loc.actuator_id);
380 if (pin >= 0) {
381 digitalWrite(pin, loc.abort_state ? HIGH : LOW);
382 actuator_states[loc.actuator_id - 1] = loc.abort_state ? 1 : 0;
383 }
384 } else {
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)});
390 cmds, millis(), packetBuffer, sizeof(packetBuffer));
391 if (len > 0) {
392 udp.beginPacket(uint32ToIPAddress(loc.ip_address),
394 udp.write(packetBuffer, len);
395 udp.endPacket();
396 HF_VERBOSE("Sent: actuator_command (abort) to ");
397 HF_VERBOSE(uint32ToIPAddress(loc.ip_address));
398 HF_VERBOSE(":");
400 }
401 }
402 }
403}
404
405static void applyVentLocalOnly() {
406 const uint32_t self_ip = getSelfIP();
407 for (const auto& loc : abort_actuator_locations) {
408 if (loc.ip_address != self_ip)
409 continue;
410 int pin = getActuatorPin(loc.actuator_id);
411 if (pin >= 0) {
412 digitalWrite(pin, loc.vent_state ? HIGH : LOW);
413 actuator_states[loc.actuator_id - 1] = loc.vent_state ? 1 : 0;
414 }
415 }
416}
417
418static void applyAbortLocalOnly() {
419 const uint32_t self_ip = getSelfIP();
420 for (const auto& loc : abort_actuator_locations) {
421 if (loc.ip_address != self_ip)
422 continue;
423 int pin = getActuatorPin(loc.actuator_id);
424 if (pin >= 0) {
425 digitalWrite(pin, loc.abort_state ? HIGH : LOW);
426 actuator_states[loc.actuator_id - 1] = loc.abort_state ? 1 : 0;
427 }
428 }
429}
430
431//-----------------------------------------------------------------------------
432// Parse incoming UDP
433//-----------------------------------------------------------------------------
434static bool readPacketHeader(const uint8_t* buffer, size_t buffer_size,
435 daq::PacketHeader& header_out) {
436 if (buffer_size < sizeof(daq::PacketHeader))
437 return false;
438 memcpy(&header_out, buffer, sizeof(daq::PacketHeader));
439 return true;
440}
441
452
453static void processActuatorCommands(
454 const std::vector<daq::ActuatorCommand>& commands);
455static void processPWMActuatorCommand(
456 const std::vector<daq::PWMActuatorCommand>& commands);
457
458static bool stateAcceptsActuatorCommand(uint32_t remote_ip32) {
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) |
462 static_cast<uint32_t>(serverIP[3]);
463
464 // All boards unconditionally ignore server commands when in AbortFinished
465 // or NoConnAbortFollower
468 remote_ip32 == sip) {
469 return false;
470 }
471
472 // Accept commands unconditionally if Active
474 return true;
475
476 // The designated survivor accepts commands from the server during its abort
477 // sequences
483 return true;
484 }
485 } else {
486 // Generic boards accept commands from non-server IPs (i.e. the
487 // designated survivor) during aborts
493 return true;
494 }
495 }
496
497 return false;
498}
499
500static const char* actuatorPacketTypeName(uint8_t type) {
501 switch (type) {
502 case 4:
503 return "ACTUATOR_COMMAND";
504 case 10:
505 return "PWM_ACTUATOR_COMMAND";
506 default:
507 return "UNKNOWN";
508 }
509}
510
511static IncomingPacketKind processIncomingPacket(const uint8_t* buffer,
512 size_t len,
513 IPAddress remoteIP) {
514 daq::PacketHeader hdr;
515 if (!readPacketHeader(buffer, len, hdr))
517
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]);
522
523 switch (hdr.packet_type) {
525 daq::PacketHeader dummy;
526 daq::ServerHeartbeatPacket data;
527 if (daq::parse_server_heartbeat_packet(buffer, len, dummy, data)) {
529#ifdef SENSOR_ETH_USE_DHCP
531#endif
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) |
535 static_cast<uint32_t>(serverIP[3]);
538 }
540 }
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;
547 if (daq::parse_actuator_config_packet(buffer, len, dummy,
548 is_controller, act_locs,
549 pt_locs, enable_serial)) {
550 is_abort_controller = (is_controller != 0);
551 abort_actuator_locations = act_locs;
552 abort_pt_locations = pt_locs;
553 config_valid = true;
554 // enable_serial is the mode byte (0..3): bit0 = USB verbose,
555 // upper part = server log stream level. See hotfire_log.h.
556 g_verbose = (enable_serial & 1);
558 (enable_serial >= 2) ? (enable_serial - 1) : 0;
559#ifdef SENSOR_ETH_USE_DHCP
561#endif
563 }
565 }
573 connection_loss_received_ips.insert(remote_ip32);
575 config_valid && !abort_pt_locations.empty()) {
576 std::vector<daq::SensorDataChunkCollection> chunks;
577 daq::PacketHeader dummy;
578 if (daq::parse_sensor_data_packet(buffer, len, dummy, chunks)) {
579 for (const auto& col : chunks)
580 for (const auto& dp : col.datapoints)
581 for (size_t i = 0; i < abort_pt_locations.size();
582 i++)
583 if (abort_pt_locations[i].ip_address ==
584 remote_ip32 &&
585 abort_pt_locations[i].sensor_id ==
586 dp.sensor_id)
588 }
589 }
591 !abort_pt_locations.empty()) {
592 std::vector<daq::SensorDataChunkCollection> chunks;
593 daq::PacketHeader dummy;
594 if (daq::parse_sensor_data_packet(buffer, len, dummy, chunks)) {
595 for (const auto& col : chunks) {
596 for (const auto& dp : col.datapoints) {
597 for (size_t i = 0; i < abort_pt_locations.size();
598 i++) {
599 if (abort_pt_locations[i].ip_address ==
600 remote_ip32 &&
601 abort_pt_locations[i].sensor_id ==
602 dp.sensor_id &&
603 dp.data < abort_pt_locations[i]
604 .pressure_threshold_adc)
605 pt_below_threshold[i] = true;
606 }
607 }
608 }
609 }
610 }
612 }
616 daq::PacketHeader cmd_header;
617 std::vector<daq::ActuatorCommand> commands;
618 if (daq::parse_actuator_command_packet(buffer, len, cmd_header,
619 commands)) {
620 HF_VERBOSELN("ACTUATOR_COMMAND received (DAQv2-Comms):");
621 for (size_t i = 0; i < commands.size(); ++i) {
622 const auto& cmd = commands[i];
623 HF_VERBOSE(" [");
624 HF_VERBOSE(static_cast<unsigned>(i));
625 HF_VERBOSE("] actuator_id=");
626 HF_VERBOSE(static_cast<unsigned>(cmd.actuator_id));
627 HF_VERBOSE(" actuator_state=");
628 HF_VERBOSELN(static_cast<unsigned>(cmd.actuator_state));
629 }
630 Serial.flush();
631 if (stateAcceptsActuatorCommand(remote_ip32))
633 }
635 }
637 daq::PacketHeader cmd_header;
638 std::vector<daq::PWMActuatorCommand> commands;
639 if (daq::parse_pwm_actuator_packet(buffer, len, cmd_header,
640 commands)) {
641 HF_VERBOSELN("PWM_ACTUATOR_COMMAND received (DAQv2-Comms):");
642 for (size_t i = 0; i < commands.size(); ++i) {
643 const auto& cmd = commands[i];
644 HF_VERBOSE(" [");
645 HF_VERBOSE(static_cast<unsigned>(i));
646 HF_VERBOSE("] actuator_id=");
647 HF_VERBOSE(static_cast<unsigned>(cmd.actuator_id));
648 HF_VERBOSE(" duration_ms=");
649 HF_VERBOSE(cmd.duration);
650 HF_VERBOSE(" duty_cycle=");
651 HF_VERBOSE(cmd.duty_cycle);
652 HF_VERBOSE(" frequency=");
653 HF_VERBOSELN(cmd.frequency);
654 }
655 Serial.flush();
656 if (stateAcceptsActuatorCommand(remote_ip32))
658 }
660 }
661 default:
663 }
664}
665
667 const std::vector<daq::ActuatorCommand>& commands) {
668 for (const auto& cmd : commands) {
669 if (cmd.actuator_id < 1 || cmd.actuator_id > NUM_ACTUATORS)
670 continue;
671 uint8_t idx = cmd.actuator_id - 1;
672 if (pwm_states[idx].active) {
673 pwm_states[idx].active = false;
674 }
675 int pin = getActuatorPin(cmd.actuator_id);
676 if (pin < 0)
677 continue;
678 uint8_t new_state = (cmd.actuator_state != 0) ? 1 : 0;
679 if (actuator_states[idx] != new_state) {
680 digitalWrite(pin, new_state ? HIGH : LOW);
681 actuator_states[idx] = new_state;
682 }
683 }
684}
685
687 const std::vector<daq::PWMActuatorCommand>& commands) {
688 for (const auto& cmd : commands) {
689 if (cmd.actuator_id < 1 || cmd.actuator_id > NUM_ACTUATORS)
690 continue;
691 int pin = getActuatorPin(cmd.actuator_id);
692 if (pin < 0)
693 continue;
694 uint8_t array_index = cmd.actuator_id - 1;
695
696 uint32_t period_us =
697 (cmd.frequency > 0) ? (1000000 / cmd.frequency) : 0;
698 uint32_t on_time_us = (uint32_t)(cmd.duty_cycle * (float)period_us);
699
700 pwm_states[array_index].active = true;
701 pwm_states[array_index].start_time = millis();
702 pwm_states[array_index].duration = cmd.duration;
703 pwm_states[array_index].duty_cycle = cmd.duty_cycle;
704 pwm_states[array_index].period_micros = period_us;
705 pwm_states[array_index].on_time_micros = on_time_us;
706 pwm_states[array_index].last_cycle_start_micros = micros();
707
708 bool initial_state = (cmd.duty_cycle > 0.0f);
709 pwm_states[array_index].current_pin_state = initial_state;
710 digitalWrite(pin, initial_state ? HIGH : LOW);
711
712 actuator_states[array_index] = initial_state ? 1 : 0;
713 }
714}
715
716static void updatePWM() {
717 unsigned long now_ms = millis();
718 unsigned long now_us = micros();
719 for (int i = 0; i < NUM_ACTUATORS; i++) {
720 if (!pwm_states[i].active)
721 continue;
722 int pin = getActuatorPin(i + 1);
723 if (pin < 0)
724 continue;
725
726 if (now_ms - pwm_states[i].start_time >= pwm_states[i].duration) {
727 digitalWrite(pin, LOW);
728 pwm_states[i].active = false;
729 actuator_states[i] = 0;
730 continue;
731 }
732
733 if (pwm_states[i].period_micros > 0) {
734 unsigned long elapsed_in_cycle =
735 now_us - pwm_states[i].last_cycle_start_micros;
736
737 if (elapsed_in_cycle >= pwm_states[i].period_micros) {
738 pwm_states[i].last_cycle_start_micros +=
739 (elapsed_in_cycle / pwm_states[i].period_micros) *
740 pwm_states[i].period_micros;
741 elapsed_in_cycle =
742 now_us - pwm_states[i].last_cycle_start_micros;
743 }
744
745 if (elapsed_in_cycle >= pwm_states[i].on_time_micros) {
746 if (pwm_states[i].current_pin_state &&
747 pwm_states[i].duty_cycle < 1.0f) {
748 digitalWrite(pin, LOW);
749 pwm_states[i].current_pin_state = false;
750 }
751 } else {
752 if (!pwm_states[i].current_pin_state &&
753 pwm_states[i].duty_cycle > 0.0f) {
754 digitalWrite(pin, HIGH);
755 pwm_states[i].current_pin_state = true;
756 }
757 }
758 }
759 }
760}
761
762//-----------------------------------------------------------------------------
763// Current-sense: 1-based (match Actuator_Testing), send to server only.
764// Raw 12-bit ADC counts, which is what daq_bridge publishes as raw_adc_counts
765// and calibration_service converts (counts / 4095 * 3.3). This used to send
766// float volts as bits, which the server read as a ~1e9 count.
767//-----------------------------------------------------------------------------
770 for (uint8_t actuator_id = 1; actuator_id <= NUM_SENSORS; actuator_id++) {
771 int pin = getCurrentSensePin(actuator_id);
772 if (pin < 0)
773 continue;
774 chunk.add_datapoint(actuator_id,
775 static_cast<uint32_t>(analogRead(pin)));
776 }
777 if (chunk.empty())
778 return;
779
781 std::vector<daq::SensorDataChunkCollection> chunks;
782 chunks.push_back(chunk);
783 size_t packetSize = daq::create_sensor_data_packet(
785 if (packetSize == 0)
786 return;
787
788 udp.beginPacket(serverIP, serverPort);
789 udp.write(packetBuffer, packetSize);
790 udp.endPacket();
791 HF_VERBOSE("Sent: sensor_data to ");
793 HF_VERBOSE(":");
795}
796
797static bool heartbeatTimedOut() {
799 return false;
801}
802
804 unsigned long now = millis();
806 last_adc_read_ms = now;
808 }
809}
810
811//-----------------------------------------------------------------------------
812// Send NO_CONNECTION_ABORT to all PTs
813//-----------------------------------------------------------------------------
816 size_t len =
818 if (len == 0)
819 return;
820 for (const auto& loc : abort_pt_locations) {
821 udp.beginPacket(uint32ToIPAddress(loc.ip_address), ptBoardPort);
822 udp.write(packetBuffer, len);
823 udp.endPacket();
824 HF_VERBOSE("Sent: no_connection_abort to ");
825 HF_VERBOSE(uint32ToIPAddress(loc.ip_address));
826 HF_VERBOSE(":");
828 }
829}
830
831//-----------------------------------------------------------------------------
832// Broadcast ABORT_DONE to all boards (PTs and Actuators)
833//-----------------------------------------------------------------------------
837 if (len == 0)
838 return;
839
840 HF_VERBOSELN("Broadcasting ABORT_DONE to network...");
841
842 // Broadcast to all PTs
843 for (const auto& loc : abort_pt_locations) {
844 udp.beginPacket(uint32ToIPAddress(loc.ip_address), ptBoardPort);
845 udp.write(packetBuffer, len);
846 udp.endPacket();
847 }
848
849 // Broadcast to all actuator boards (excluding self)
850 const uint32_t self_ip = getSelfIP();
851 for (const auto& loc : abort_actuator_locations) {
852 if (loc.ip_address == self_ip)
853 continue;
854 udp.beginPacket(uint32ToIPAddress(loc.ip_address), udpListenPort);
855 udp.write(packetBuffer, len);
856 udp.endPacket();
857 }
858}
859
860//-----------------------------------------------------------------------------
861// State handlers
862//-----------------------------------------------------------------------------
863static void run_WaitingForServer() {
864 (void)0;
865}
866
867static void run_Active() {
869 // Stay in Active: no transition on connection loss (unless
870 // ENABLE_ALL_STATE_TRANSITIONS config allows it).
874 }
875}
876
880 // Broadcast NO_CONNECTION_ABORT to PTs 3 times
882 delay(10);
884 delay(10);
886
888 }
892 }
893 }
894
895 unsigned long elapsed = millis() - state_enter_ms;
897 no_conn_abort_pt_sent = false;
899 bool all_pt_received = true;
900 if (!abort_pt_locations.empty()) {
901 for (size_t i = 0; i < pt_received_during_no_conn.size(); i++)
903 all_pt_received = false;
904 break;
905 }
906 }
907 if (all_pt_received && !abort_pt_locations.empty()) {
909 pt_below_threshold.resize(abort_pt_locations.size(), false);
910 pt_abort_vent_done = false;
911 pt_abort_abort_done = false;
912 } else {
914 no_pt_abort_vent_done = false;
916 }
917 }
918}
919
920static void run_PTAbort() {
923 pt_abort_vent_done = true;
924 }
925 if (!pt_abort_abort_done) {
926 bool all_below = true;
927 if (!pt_below_threshold.empty()) {
928 for (bool b : pt_below_threshold)
929 if (!b) {
930 all_below = false;
931 break;
932 }
933 }
934 unsigned long elapsed = millis() - state_enter_ms;
935 if (all_below || elapsed >= PT_ABORT_THRESHOLD_TIMEOUT_MS) {
938 pt_abort_abort_done = true;
940 }
941 }
942}
943
958
959static void run_AbortFinished() {
960 (void)0;
961}
962
982
984 (void)0; // Passive state; awaits UDP commands from designated survivor
985}
986
987//-----------------------------------------------------------------------------
988// Packet-driven transitions
989//-----------------------------------------------------------------------------
991 // Global Clear Abort recovery
992 if (kind == IncomingPacketKind::ClearAbort) {
995 return;
996 }
997 }
998
999 switch (state) {
1001 if (kind == IncomingPacketKind::Config) {
1003 }
1004 break;
1006 // Stay in Active: no transitions on Abort, AbortDone, or
1007 // NoConnAbort (unless config allows).
1009 if (kind == IncomingPacketKind::Abort ||
1012 } else if (kind == IncomingPacketKind::NoConnAbort &&
1015 }
1016 }
1017 break;
1021 }
1022 break;
1023 default:
1024 break;
1025 }
1026}
1027
1028//-----------------------------------------------------------------------------
1029// Pin setup
1030//-----------------------------------------------------------------------------
1031static void initializeActuators() {
1032 HF_VERBOSELN("Initializing actuators...");
1033 for (int i = 1; i <= NUM_ACTUATORS; i++) {
1034 int pin = getActuatorPin(i);
1035 if (pin < 0)
1036 continue;
1037 pinMode(pin, OUTPUT);
1038 digitalWrite(pin, LOW);
1039 actuator_states[i - 1] = 0;
1040 }
1041 HF_VERBOSELN("All actuators initialized to OFF state");
1042}
1043
1045 for (uint8_t actuator_id = 1; actuator_id <= NUM_SENSORS; actuator_id++) {
1046 int pin = getCurrentSensePin(actuator_id);
1047 if (pin >= 0)
1048 pinMode(pin, INPUT);
1049 }
1050}
1051
1052static void initializePWMStates() {
1053 for (int i = 0; i < NUM_ACTUATORS; i++) {
1054 pwm_states[i].active = false;
1055 pwm_states[i].start_time = 0;
1056 pwm_states[i].duration = 0;
1057 pwm_states[i].duty_cycle = 0.0f;
1058 pwm_states[i].period_micros = 0;
1059 pwm_states[i].on_time_micros = 0;
1060 pwm_states[i].last_cycle_start_micros = 0;
1061 pwm_states[i].current_pin_state = false;
1062 }
1063}
1064
1065//-----------------------------------------------------------------------------
1066// Log streaming: flush the buffer to the server as a LOGS packet (type 15).
1067// Board identified server-side by UDP source IP.
1068//-----------------------------------------------------------------------------
1069static void flushLogs() {
1070 if (g_logbuf.empty())
1071 return;
1072 static uint8_t pkt[sizeof(daq::PacketHeader) + 3 + LOG_BUF_SIZE];
1073 size_t n =
1075 g_logbuf.length(), millis(), pkt, sizeof(pkt));
1076 if (n > 0) {
1077 udp.beginPacket(serverIP, serverPort);
1078 udp.write(pkt, n);
1079 udp.endPacket();
1080 }
1081 g_logbuf.clear();
1082}
1083
1084//-----------------------------------------------------------------------------
1085// Setup and loop
1086//-----------------------------------------------------------------------------
1087void setup() {
1088 Serial.begin(115200);
1091 Serial.println("Actuator Hotfire state machine starting...");
1092
1096
1101
1102 ESP_ERROR_CHECK(esp_read_mac(mac, ESP_MAC_ETH));
1105 SPI.begin(Actuator_Board.ETH_SCLK, Actuator_Board.ETH_MISO,
1106 Actuator_Board.ETH_MOSI, Actuator_Board.ETH_CS);
1108 Ethernet.init(Actuator_Board.ETH_CS);
1110#ifdef SENSOR_ETH_USE_DHCP
1111 // The ground station decides our address, and our ID with it.
1114#else
1115 board_id = (uint8_t)BOARD_ID;
1116 Ethernet.begin(mac, IPAddress(192, 168, 2, board_id), dns, gateway, subnet);
1117#endif
1118 Serial.print("Board ID: ");
1119 Serial.println(static_cast<unsigned>(board_id));
1121
1122 // W5500 / Ethernet status (same prints as the sense boards so the shared
1123 // test-GUI serial parser understands both)
1124 Serial.println("[ETH] Ethernet initialized (SPI WIZnet)");
1125 Serial.print("Address source: ");
1126 Serial.println(BoardNet::sourceName(net.source));
1127 Serial.print("Stack IP (Ethernet.localIP): ");
1128 Serial.println(Ethernet.localIP());
1129 Serial.print("Hardware: ");
1130 switch (Ethernet.hardwareStatus()) {
1131 case EthernetNoHardware:
1132 Serial.println("no chip detected — check CS / SPI wiring");
1133 break;
1134 case EthernetW5100:
1135 Serial.println("W5100");
1136 break;
1137 case EthernetW5200:
1138 Serial.println("W5200");
1139 break;
1140 case EthernetW5500:
1141 Serial.println("W5500");
1142 break;
1143 default:
1144 Serial.println("unknown");
1145 break;
1146 }
1147 Serial.print("Link status: ");
1148 if (Ethernet.linkStatus() == LinkON) {
1149 Serial.println("Connected");
1150 } else if (Ethernet.linkStatus() == LinkOFF) {
1151 Serial.println("Disconnected");
1152 } else {
1153 Serial.println("Unknown");
1154 }
1155 if (Ethernet.localIP() == IPAddress(0, 0, 0, 0)) {
1156 Serial.println(
1157 "[ETH] WARNING: stack IP is 0.0.0.0 — check cable / W5500");
1158 }
1159 Serial.flush();
1160
1161 udp.begin(udpListenPort);
1162 otaServer.onStart(otaFreezeAddress);
1163 otaServer.begin();
1164 Serial.print("UDP listening on port ");
1165 Serial.println(udpListenPort);
1166
1168 Serial.println("State -> WaitingForServer");
1169 Serial.flush();
1172#ifdef SENSOR_ETH_USE_DHCP
1173 Serial.println(
1174 "[NET] address assigned by the server; broadcasting until one is "
1175 "heard");
1176#endif
1177
1178 Serial.println("Setup complete. State: WaitingForServer");
1179
1180 // Ethernet is up — first log the server can receive. Announce identity + IP
1181 // and flush immediately so the server sees the board come online.
1182 HF_LOG("ONLINE board=");
1183 HF_LOG(static_cast<unsigned>(board_id));
1184 HF_LOG(" ip=");
1185 HF_LOG(Ethernet.localIP());
1186 HF_LOGLN_();
1187 flushLogs();
1188}
1189
1190void loop() {
1191#ifdef SENSOR_ETH_USE_DHCP
1192 // Renew the lease as it expires; no-op while an OTA is in flight.
1194 // On the recovery address, keep asking; a lease brings our ID with it.
1197 HF_LOG("ONLINE board=");
1198 HF_LOG(static_cast<unsigned>(board_id));
1199 HF_LOG(" ip=");
1200 HF_LOG(Ethernet.localIP());
1201 HF_LOGLN_();
1202 flushLogs();
1203 }
1204#endif
1205 // Non-blocking OTA check — blocks only if a client actually connects,
1206 // and never returns on success (the board reboots into the new image).
1207 otaServer.poll();
1208 // Bench marker: prints only on builds made with -DSTAR_OTA_TEST_MESSAGE,
1209 // so consecutive OTA uploads are visibly different on the serial monitor.
1211
1213 updatePWM();
1214
1215 int packetSize = udp.parsePacket();
1216 if (packetSize > 0) {
1217 IPAddress remoteIP = udp.remoteIP();
1219 int bytesRead = udp.read(packetBuffer, sizeof(packetBuffer));
1220 if (bytesRead > 0) {
1221 const uint8_t pktType = bytesRead >= (int)sizeof(daq::PacketHeader)
1222 ? packetBuffer[0]
1223 : 0;
1224 HF_VERBOSE("Received packet from ");
1225 HF_VERBOSE(remoteIP);
1226 HF_VERBOSE(":");
1227 HF_VERBOSE(udp.remotePort());
1228 HF_VERBOSE(" type ");
1229 HF_VERBOSE(static_cast<unsigned>(pktType));
1230 if (pktType == 4 || pktType == 10) {
1231 HF_VERBOSE(" (");
1233 HF_VERBOSE(") len=");
1234 HF_VERBOSE(bytesRead);
1235 HF_VERBOSE(" hex:");
1236 for (int i = 0; i < bytesRead && i < 32; i++) {
1237 HF_VERBOSE(" ");
1238 if (packetBuffer[i] < 16)
1239 HF_VERBOSE("0");
1241 }
1242 }
1243 HF_VERBOSELN("");
1244 Serial.flush();
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]));
1250 IncomingPacketKind kind =
1251 processIncomingPacket(packetBuffer, bytesRead, remoteIP);
1253 }
1254 }
1255
1256#ifdef SENSOR_ETH_USE_DHCP
1257 // Server gone quiet: forget it and resume discovery broadcasts so a new
1258 // (or restarted) server can find us. The address stays locked -- only the
1259 // outbound destination resets.
1261 net.serverLearned = false;
1263 Serial.println("[NET] server silent -- resuming discovery");
1264 Serial.flush();
1265 }
1266#endif
1267
1268 switch (state) {
1271 break;
1273 run_Active();
1274 break;
1277 break;
1279 run_PTAbort();
1280 break;
1282 run_NoPTAbort();
1283 break;
1286 break;
1289 break;
1292 break;
1293 }
1294
1295 unsigned long now = millis();
1297 lastHeartbeatMillis = now;
1299 HF_VERBOSELN("Setup state: sending heartbeat");
1300 Serial.flush();
1302 } else {
1304 }
1305#ifdef SENSOR_ETH_USE_DHCP
1306 // Discovery: announce to everyone on the wire until a server is
1307 // learned. Reuses the normal heartbeat packet on the same 1 s tick.
1308 if (!net.serverLearned)
1309 sendBoardHeartbeatTo(IPAddress(255, 255, 255, 255),
1311#endif
1312 }
1313
1314 // Flush buffered logs to the server ~1 Hz (or sooner if the buffer is
1315 // filling).
1316 static unsigned long s_last_log_flush_ms = 0;
1317 if (!g_logbuf.empty() &&
1318 (g_logbuf.nearFull() || now - s_last_log_flush_ms >= 1000)) {
1319 s_last_log_flush_ms = now;
1320 flushLogs();
1321 }
1322
1324}
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
unsigned long micros()
Definition Arduino.h:37
#define HEX
Definition Arduino.h:24
int analogRead(int)
Definition Arduino.h:53
#define LOW
Definition Arduino.h:20
void digitalWrite(int, int)
Definition Arduino.h:48
#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 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
#define PT_ABORT_THRESHOLD_TIMEOUT_MS
#define NO_PT_ABORT_VENT_TO_ABORT_MS
const int IPAddress
void delay(int ms)
static uint8_t chunk[64]
Definition board_uart9.c:12
The OTA listener.
PWMState pwm_states[NUM_ACTUATORS]
Definition main.cpp:41
std::vector< daq::SensorDataChunkCollection > chunks
Definition main.cpp:55
#define NUM_SENSORS
Definition main.cpp:46
static unsigned long lastHeartbeatMillis
Definition main.cpp:81
static unsigned long last_adc_read_ms
Definition main.cpp:79
static bool no_conn_abort_pt_sent
Definition main.cpp:75
static unsigned long led_cycle_start_ms
Definition main.cpp:88
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 config_valid
Definition main.cpp:66
static std::vector< daq::AbortPTLocation > abort_pt_locations
Definition main.cpp:65
const int udpListenPort
Definition main.cpp:37
IPAddress dns(192, 168, 2, 1)
IncomingPacketKind
Definition main.cpp:237
byte mac[6]
Definition main.cpp:31
IPAddress subnet(255, 255, 255, 0)
static bool is_abort_controller
Definition main.cpp:63
LedPhase
Definition main.cpp:86
@ Off
Definition main.cpp:86
@ Idle
Definition main.cpp:86
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
EthernetUDP udp
Definition main.cpp:40
const int ptBoardPort
Definition main.cpp:39
static ActuatorControllerState last_led_state
Definition main.cpp:91
IPAddress gateway(0, 0, 0, 0)
static ActuatorControllerState state
Definition main.cpp:57
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
StarOTA::Server otaServer(OTA_TCP_PORT)
char packetBuffer[UDP_TX_PACKET_MAX_SIZE]
Definition main.cpp:15
static void readCurrentSensePinsAndSend()
Definition main.cpp:768
static void run_NoPTAbort()
Definition main.cpp:944
static void run_Active()
Definition main.cpp:867
static void run_WaitingForServer()
Definition main.cpp:863
static StarOTA::Server otaServer(HOTFIRE_OTA_PORT)
static void sendAbortDoneToAllBoards()
Definition main.cpp:834
static void run_AbortFinished()
Definition main.cpp:959
static uint32_t getSelfIP()
Definition main.cpp:290
static void updatePWM()
Definition main.cpp:716
static bool pt_abort_vent_done
Definition main.cpp:114
static void applyVentToAllActuators()
Definition main.cpp:343
static void sendBoardHeartbeatTo(IPAddress dest_ip, int dest_port)
Definition main.cpp:260
static bool isIPInPTLocations(uint32_t ip)
Definition main.cpp:305
static bool no_conn_abort_vent_sent
Definition main.cpp:103
static void initializeCurrentSensePins()
Definition main.cpp:1044
static bool heartbeatTimedOut()
Definition main.cpp:797
static std::vector< bool > pt_received_during_no_conn
Definition main.cpp:106
static void run_NoConnectionAbort()
Definition main.cpp:877
void setup()
Definition main.cpp:1087
static IncomingPacketKind processIncomingPacket(const uint8_t *buffer, size_t len, IPAddress remoteIP)
Definition main.cpp:511
static bool stateAcceptsActuatorCommand(uint32_t remote_ip32)
Definition main.cpp:458
static void applyPacketTransition(IncomingPacketKind kind)
Definition main.cpp:990
static void updateLedNonBlocking()
Definition main.cpp:189
IPAddress dns(192, 168, 2, 1)
static void sendNoConnectionAbortToAllPTs()
Definition main.cpp:814
static uint8_t getStateNumber(ActuatorControllerState s)
Definition main.cpp:152
IPAddress subnet(255, 255, 255, 0)
static void applyVentLocalOnly()
Definition main.cpp:405
bool g_verbose
Definition main.cpp:65
@ Off
Definition main.cpp:86
@ Idle
Definition main.cpp:86
static void initializeActuators()
Definition main.cpp:1031
static bool readPacketHeader(const uint8_t *buffer, size_t buffer_size, daq::PacketHeader &header_out)
Definition main.cpp:434
static bool pt_abort_abort_done
Definition main.cpp:115
static size_t create_abort_done_packet(uint8_t *buffer, size_t buffer_size)
Definition main.cpp:329
static const char * actuatorPacketTypeName(uint8_t type)
Definition main.cpp:500
static void setState(ActuatorControllerState new_state)
Definition main.cpp:179
static std::vector< bool > pt_below_threshold
Definition main.cpp:113
static void otaFreezeAddress(void *, uint32_t)
Freeze our address before a single byte reaches flash.
Definition main.cpp:72
static BoardNet::State net
Definition main.cpp:57
static std::set< uint32_t > connection_loss_received_ips
Definition main.cpp:110
static void run_ConnectionLossDetected()
Definition main.cpp:963
static void processPWMActuatorCommand(const std::vector< daq::PWMActuatorCommand > &commands)
Definition main.cpp:686
static void applyAbortToAllActuators()
Definition main.cpp:374
static size_t create_no_connection_abort_packet(uint8_t *buffer, size_t buffer_size)
Definition main.cpp:315
static void run_NoConnAbortFollower()
Definition main.cpp:983
static const char * stateName(ActuatorControllerState s)
Definition main.cpp:156
static void sendBoardHeartbeat()
Definition main.cpp:283
static void run_PTAbort()
Definition main.cpp:920
static void processActuatorCommands(const std::vector< daq::ActuatorCommand > &commands)
Definition main.cpp:666
IPAddress gateway(0, 0, 0, 0)
static bool no_pt_abort_abort_done
Definition main.cpp:119
static void streamSensorDataIfDue()
Definition main.cpp:803
static IPAddress uint32ToIPAddress(uint32_t ip)
Definition main.cpp:300
static void applyAbortLocalOnly()
Definition main.cpp:418
static daq::BoardState getBoardStateForHeartbeat()
Definition main.cpp:237
static void initializePWMStates()
Definition main.cpp:1052
IPAddress serverIP(192, 168, 2, HOTFIRE_SERVER_IP_OCTET_4)
static bool no_pt_abort_vent_done
Definition main.cpp:118
void loop()
Definition main.cpp:1190
static void flushLogs()
Definition main.cpp:1069
#define HOTFIRE_SERVER_PORT
#define ENABLE_ALL_STATE_TRANSITIONS
#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_LOGLN_()
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
static BasicJsonType::size_type array_index(const string_t &s)
Definition json.hpp:14019
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 printTestMessage()
Bench-test helper: prints STAR_OTA_TEST_MESSAGE on a timer as.
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_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
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
Definition json.hpp:24474
static bool elapsed(uint32_t now, uint32_t then, uint32_t interval)
Definition self_test.c:10
uint32_t period_micros
Definition main.cpp:133
float duty_cycle
Definition main.cpp:132
bool current_pin_state
Definition main.cpp:136
uint32_t duration
Definition main.cpp:37
uint32_t on_time_micros
Definition main.cpp:134
unsigned long start_time
Definition main.cpp:36
unsigned long last_cycle_start_micros
Definition main.cpp:135
bool active
Definition main.cpp:35
Represents a single data chunk with timestamp and sensor datapoints.