17using namespace vtable;
18using namespace vtable::builder;
43 std::shared_ptr<config::BoardDiscovery> board_discovery,
45 if (!elodin_client || !elodin_client->is_connected()) {
46 std::cerr <<
"[PressureStateMachine] ERROR: Elodin client not connected" << std::endl;
72 std::cerr <<
"[PressureStateMachine] WARNING: Failed to create UDP socket for "
75 std::cerr <<
"[PressureStateMachine] Commands will fail until socket is valid" << std::endl;
78 std::cout <<
"[PressureStateMachine] ✅ Initialized" << std::endl;
81 std::cout <<
"[PressureStateMachine] GN2 Low Target: " <<
thresholds_.gn2_low_target_psi
82 <<
" PSI" << std::endl;
83 std::cout <<
"[PressureStateMachine] GN2 High Target: " <<
thresholds_.gn2_high_target_psi
84 <<
" PSI" << std::endl;
85 std::cout <<
"[PressureStateMachine] Fuel Target: " <<
thresholds_.fuel_target_psi <<
" PSI"
87 std::cout <<
"[PressureStateMachine] OX Target: " <<
thresholds_.ox_target_psi <<
" PSI"
99 std::cerr <<
"[PressureStateMachine] WARNING: Already running" << std::endl;
105 std::cout <<
"[PressureStateMachine] ✅ Started state machine" << std::endl;
117 std::cout <<
"[PressureStateMachine] ✅ Stopped state machine" << std::endl;
127 return "DEBUG_STATE";
137 return "GN2_LOW_PRESS";
149 return "GN2_HIGH_PRESS";
151 return "GN2_HIGH_VENT";
199 std::cerr <<
"[PressureStateMachine] ❌ Invalid transition: " <<
getCurrentStateName() <<
" -> "
200 <<
static_cast<int>(target_state) << std::endl;
210 std::cout <<
"[PressureStateMachine] 🚨 ABORT REQUESTED" << std::endl;
219 auto last_pressure_read = std::chrono::steady_clock::now();
222 auto now = std::chrono::steady_clock::now();
225 if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_pressure_read)
228 last_pressure_read = now;
254 if (packet_len <= 0) {
259 uint32_t len = *
reinterpret_cast<uint32_t*
>(packet_buffer);
260 uint8_t type = packet_buffer[4];
261 uint16_t packet_id = (
static_cast<uint16_t
>(packet_buffer[5]) << 8) | packet_buffer[6];
269 const uint8_t*
payload = packet_buffer + 12;
270 size_t payload_len = len - 12;
278 constexpr size_t CAL_PT_MIN_LEN = 16;
281 if (payload_len >= CAL_PT_MIN_LEN) {
283 std::memcpy(&psi_f,
payload + 12,
sizeof(
float));
289 if (payload_len >= CAL_PT_MIN_LEN) {
291 std::memcpy(&psi_f,
payload + 12,
sizeof(
float));
297 if (payload_len >= CAL_PT_MIN_LEN) {
299 std::memcpy(&psi_f,
payload + 12,
sizeof(
float));
318 if (!pressures.
valid) {
459 std::cout <<
"[PressureStateMachine] Entering state: " <<
getCurrentStateName() << std::endl;
464 std::get<0>(st_msg.
fields) = std::chrono::duration_cast<std::chrono::nanoseconds>(
465 std::chrono::system_clock::now().time_since_epoch())
467 std::get<1>(st_msg.
fields) =
static_cast<uint8_t
>(from_state);
468 std::get<2>(st_msg.
fields) =
static_cast<uint8_t
>(
state);
578 std::cout <<
"[PressureStateMachine] Pressure regulator started" << std::endl;
597 std::cout <<
"[PressureStateMachine] 🚨 ABORT: All valves opened for emergency venting"
617 if (message_id != 0) {
619 std::get<0>(cmd_msg.
fields) = std::chrono::duration_cast<std::chrono::nanoseconds>(
620 std::chrono::steady_clock::now().time_since_epoch())
622 std::get<1>(cmd_msg.
fields) =
static_cast<uint8_t
>(actuator);
623 std::get<2>(cmd_msg.
fields) =
static_cast<uint8_t
>(command);
625 std::get<4>(cmd_msg.
fields) = 1;
639 std::cerr <<
"[PressureStateMachine] ERROR: UDP socket not valid for actuator "
640 <<
static_cast<int>(actuator) << std::endl;
646 uint8_t actuator_state = 0;
652 actuator_state = (
value > 0.5f) ? 1 : 0;
658 uint8_t actuator_channel =
659 static_cast<uint8_t
>(actuator) + 1;
662 daq::ActuatorCommand cmd;
663 cmd.actuator_id = actuator_channel;
664 cmd.actuator_state = actuator_state;
666 std::vector<daq::ActuatorCommand>
commands;
670 uint32_t ts_ms =
static_cast<uint32_t
>(std::chrono::duration_cast<std::chrono::milliseconds>(
671 std::chrono::steady_clock::now().time_since_epoch())
675 std::vector<uint8_t> packet(buf, buf + len);
676 if (packet.empty()) {
677 std::cerr <<
"[PressureStateMachine] ERROR: Failed to construct actuator command packet"
685 if (sent ==
static_cast<ssize_t
>(packet.size())) {
686 std::cout <<
"[PressureStateMachine] ✅ Sent UDP command to " <<
actuator_board_ip_ <<
":"
688 <<
" -> " << (actuator_state ?
"ON" :
"OFF") << std::endl;
690 std::cerr <<
"[PressureStateMachine] ❌ Failed to send UDP command: sent " << sent <<
"/"
691 << packet.size() <<
" bytes" <<
" error: " <<
command_socket_->last_error()
700 constexpr uint16_t ACTUATOR_BOARD_PORT = 5005;
705 for (
const auto&
board : boards) {
710 return {
board.current_ip,
board.port > 0 ?
board.port : ACTUATOR_BOARD_PORT};
718 return {
"192.168.2.206", ACTUATOR_BOARD_PORT};
734 auto send_vtable_msg = [
this](VTableMsg msg) {
735 auto buf = Msg(msg).encode_vec();
741 auto make_act_vt = []() {
742 return builder::vtable({
743 raw_field(0, 8, schema(PrimType::U64(), {}, component(
"PSM.actuator.timestamp_ns"))),
744 raw_field(8, 1, schema(PrimType::U8(), {}, component(
"PSM.actuator.actuator_id"))),
745 raw_field(9, 1, schema(PrimType::U8(), {}, component(
"PSM.actuator.command_type"))),
746 raw_field(10, 4, schema(PrimType::F32(), {}, component(
"PSM.actuator.value"))),
747 raw_field(14, 1, schema(PrimType::U8(), {}, component(
"PSM.actuator.status"))),
751 for (uint8_t lo = 0x60; lo <= 0x66; ++lo) {
753 VTableMsg{.id = std::make_tuple(uint8_t(0x50), lo), .vtable = make_act_vt()});
757 auto state_trans_vt = builder::vtable({
758 raw_field(0, 8, schema(PrimType::U64(), {}, component(
"CONTROLLER.state.timestamp_ns"))),
759 raw_field(8, 1, schema(PrimType::U8(), {}, component(
"CONTROLLER.state.from_state"))),
760 raw_field(9, 1, schema(PrimType::U8(), {}, component(
"CONTROLLER.state.to_state"))),
761 raw_field(10, 1, schema(PrimType::U8(), {}, component(
"CONTROLLER.state.reason"))),
764 VTableMsg{.id = std::make_tuple(uint8_t(0x43), uint8_t(0x00)), .vtable = state_trans_vt});
766 std::cout <<
"[PressureStateMachine] ✅ Registered PSM VTables (0x5060-0x5066, 0x43)"
void readPressureData()
Read pressure data from Elodin.
void registerPSMVTables()
Register Elodin VTables for PSM actuator commands and state transitions.
void stop()
Stop the state machine.
std::chrono::milliseconds pressure_read_period_
bool requestTransition(SystemState target_state)
Request transition to a specific state (manual override).
uint16_t actuatorToMessageID(ActuatorID actuator) const
Convert actuator ID to message ID from config.
uint16_t actuator_board_port_
void stateMachineLoop()
Main state machine loop.
uint16_t pt_oup_message_id_
SystemState
System states matching the state machine diagram.
PressureThresholds thresholds_
void sendActuatorCommand(ActuatorID actuator, CommandType command, float value=0.0f)
Send actuator command.
std::atomic< bool > running_
uint16_t pt_hp_message_id_
uint16_t pt_fdp_message_id_
uint16_t pt_lp_message_id_
PressureReadings latest_pressures_
bool initialize(std::shared_ptr< elodin::ElodinClient > elodin_client, std::shared_ptr< config::BoardDiscovery > board_discovery, const PressureThresholds &thresholds)
Initialize the state machine.
std::string getCurrentStateName() const
Get current state name as string.
std::chrono::steady_clock::time_point state_entry_time_
std::atomic< SystemState > current_state_
CommandType
Command types for actuators.
void executeStateActions(SystemState state)
Execute state actions (while in state).
std::thread state_machine_thread_
uint16_t pt_fup_message_id_
void executeStateEntryActions(SystemState state)
Execute state entry actions.
SystemState getCurrentState() const
Get current state.
std::map< ActuatorID, uint16_t > actuator_message_ids_
PressureReadings getLatestPressures() const
Get latest pressure readings.
std::chrono::milliseconds state_machine_period_
uint16_t pt_odp_message_id_
std::string actuator_board_ip_
std::pair< std::string, uint16_t > getActuatorBoardAddress(ActuatorID actuator) const
Get board IP and port for actuator.
std::atomic< bool > abort_requested_
void start()
Start the state machine.
void requestAbort()
Request abort.
void sendActuatorCommandUDP(ActuatorID actuator, CommandType command, float value=0.0f)
Send actuator command via UDP to board.
ActuatorID
Actuator identifiers matching config.
std::shared_ptr< elodin::ElodinClient > elodin_client_
std::unique_ptr< daq_comms::transport::UDPSocket > command_socket_
std::shared_ptr< config::BoardDiscovery > board_discovery_
std::mutex pressure_mutex_
void checkTransitions()
Check transition conditions based on current state and pressures.
static ActuatorControllerState state
static osMessageQueueId_t commands
size_type count(const key_type &key) const
CommsMessage< uint64_t, uint8_t, uint8_t, float, uint8_t > ActuatorCommandMessage
Actuator command message.
CommsMessage< uint64_t, uint8_t, uint8_t, uint8_t > ControllerStateTransitionMessage
PSM state transition event message [0x43, 0x00].
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.
constexpr uint8_t kActuator
Motorized ball valve position: raw ADC counts -> % open.
std::tuple< Fields... > fields
Pressure sensor readings.
Configuration for pressure thresholds.
std::string board(const std::string &name, const std::string &id, bool enabled=true)