STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
sequencer::ActuatorCommander Class Reference

Sends UDP actuator commands based on state transitions. More...

#include <ActuatorCommander.hpp>

Collaboration diagram for sequencer::ActuatorCommander:
[legend]

Public Member Functions

bool load (const std::string &config_content, const std::string &csv_path, const std::string &delay_csv_path="")
 Load actuator roles from config content and state→actuator map from CSV.
void setDefaultBindAddress (const std::string &address)
 Pin outgoing actuator commands to a NIC, unless [actuator_service].bind_address already named one explicitly.
void applyForState (State state, bool is_transition=false)
 Send actuator commands for the given state (one shot).
void startContinuousLoop (State state, bool allow_delays=true, bool defer_first_pass=false)
 Start the 1 Hz continuous re-send loop for the given state.
void stopContinuousLoop ()
 Stop the continuous re-send loop (blocks until the thread exits).
bool sendSingleActuator (const std::string &name, int pos)
 Send one UDP packet for a single role (debug manual command).
std::map< std::string, int > positionsForState (const std::string &state_name) const
 The logical positions the CSV declares for state_name (role -> 0 CLOSED / 1 OPEN).
double delayForRole (const std::string &state_name, const std::string &role) const
 The staged delay, in seconds, that role waits before moving on entry to state_name.
void setManualOverride (const std::string &name, int pos)
 Debug: hold this logical position for the role until cleared or state transition.
void clearAllManualOverrides ()
 Clear all manual overrides (e.g.
void setScriptPosition (const std::string &name, int pos)
 A running script's position for one valve — the script layering on top of its state's CSV column.
void clearScriptPositions ()
 Drop every script position.
void setFireState (State s)
 Set the Elodin client for publishing commanded state [0x32, ch] to the DB.
void setElodinClient (fsw::elodin::ElodinClient *client)
void publishInitialState ()
 Publish initial de-energized state for all actuators to Elodin DB [0x32].
bool isLoaded () const

Private Member Functions

std::map< std::string, std::map< std::string, int > >::const_iterator findStateActuators (const std::string &state_name) const
bool sendBatch (const std::map< std::string, std::vector< std::pair< uint8_t, uint8_t > > > &by_board)
 Send to every board with the retransmits interleaved, so boards stay in step.
bool sendUDP (const std::string &board_ip, const std::vector< std::pair< uint8_t, uint8_t > > &id_state_pairs)
void publishCommandedState (uint8_t table_hi, uint8_t global_channel, uint8_t logical_pos)

Static Private Member Functions

static void logOverrideShadowingScript (const std::string &role)
 Warn once per role that a manual override is fighting a running script for a valve.

Private Attributes

std::map< std::string, ActuatorRole > roles_
std::map< std::string, std::map< std::string, double > > state_actuator_delays_
 state -> actuator -> delay in seconds, from state_machine_actuator_delays.csv.
State fire_state_ {State::FIRE}
 Bumped on every transition so an in-flight delayed stage from the previous state aborts instead of landing after we have already moved on.
std::atomic< uint64_t > schedule_gen_ {0}
std::set< std::string > pending_roles_
 Roles whose delayed stage has not fired yet.
std::mutex pending_roles_mutex_
std::map< std::string, std::map< std::string, int > > state_actuators_
std::map< std::string, int > manual_overrides_
std::mutex overrides_mutex_
std::map< std::string, int > script_positions_
 Valves a running script has taken over, role -> logical position.
std::mutex script_positions_mutex_
std::thread loop_thread_
std::atomic< bool > loop_running_ {false}
State loop_state_ {State::IDLE}
std::string bind_addr_ {"0.0.0.0"}
bool bind_addr_explicit_ {false}
uint16_t actuator_port_ {5005}
bool loaded_ {false}
fsw::elodin::ElodinClient * elodin_ {nullptr}

Detailed Description

Sends UDP actuator commands based on state transitions.

Manages the continuous re-send loop (~10 Hz). Debug mode: manual overrides apply until cleared (Sequencer clears them on every state transition so the CSV wins when changing state).

Thread-safe: applyForState, setManualOverride, clearAllManualOverrides, stopContinuousLoop.

Definition at line 44 of file ActuatorCommander.hpp.

Member Function Documentation

◆ applyForState()

void sequencer::ActuatorCommander::applyForState ( State state,
bool is_transition = false )

Send actuator commands for the given state (one shot).

Groups commands by board IP, sends one UDP packet per board. PWM actuators are skipped in FIRE state. Command every actuator for state.

is_transition selects between the two things this does. On an actual state change it runs the CSV's per-actuator delays as a staged schedule; on the 1 Hz republish it sends the settled (final) positions with no delay. Without that split the republish would re-arm every pending delay once a second and the schedule would never finish.

Definition at line 395 of file ActuatorCommander.cpp.

References sequencer::actuator_elodin_low_byte(), sequencer::ActuatorRole::board_id, sequencer::ActuatorRole::board_ip, ch(), sequencer::ActuatorRole::channel, sequencer::ActuatorRole::cmd_table_hi, emit(), findStateActuators(), fire_state_, sequencer::ActuatorRole::is_no, sequencer::ActuatorRole::is_pwm, lk, lock, logOverrideShadowingScript(), manual_overrides_, sequencer::StateMachine::name(), overrides_mutex_, pending_roles_, pending_roles_mutex_, publishCommandedState(), r, roles_, schedule_gen_, script_positions_, script_positions_mutex_, sendBatch(), state, state_actuator_delays_, and state_actuators_.

Referenced by main(), and startContinuousLoop().

◆ clearAllManualOverrides()

void sequencer::ActuatorCommander::clearAllManualOverrides ( )

Clear all manual overrides (e.g.

leaving debug mode or transitioning state).

Definition at line 789 of file ActuatorCommander.cpp.

References lock, manual_overrides_, and overrides_mutex_.

◆ clearScriptPositions()

void sequencer::ActuatorCommander::clearScriptPositions ( )

Drop every script position.

Called on any transition, beside clearAllManualOverrides.

Definition at line 784 of file ActuatorCommander.cpp.

References lk, script_positions_, and script_positions_mutex_.

Referenced by main().

◆ delayForRole()

double sequencer::ActuatorCommander::delayForRole ( const std::string & state_name,
const std::string & role ) const

The staged delay, in seconds, that role waits before moving on entry to state_name.

0 when the state, the role, or the delays file has no entry.

Exposed so a timed hold can be measured from when the valve it cares about actually opens rather than from the transition: a staggered state opens its valves at different times, so "the state was held for N" and "that valve was open for N" are different claims.

Definition at line 671 of file ActuatorCommander.cpp.

References state_actuator_delays_.

◆ findStateActuators()

std::map< std::string, std::map< std::string, int > >::const_iterator sequencer::ActuatorCommander::findStateActuators ( const std::string & state_name) const
private

Definition at line 263 of file ActuatorCommander.cpp.

References state_actuators_, and sequencer::toLower().

Referenced by applyForState(), and positionsForState().

◆ isLoaded()

bool sequencer::ActuatorCommander::isLoaded ( ) const
inline

Definition at line 178 of file ActuatorCommander.hpp.

References loaded_.

◆ load()

bool sequencer::ActuatorCommander::load ( const std::string & config_content,
const std::string & csv_path,
const std::string & delay_csv_path = "" )

Load actuator roles from config content and state→actuator map from CSV.

Parameters
config_contentRaw config.toml file contents (already read).
csv_pathPath to state_machine_actuators.csv (with fallbacks).
Returns
true on success.
Parameters
delay_csv_pathWhere the per-actuator staged delays live, from [state_machine].actuator_delay_csv. Empty falls back to deriving it from csv_path.

The derivation used to be the ONLY way this was found: the delays filename was built by swapping the "state_machine_actuators.csv" suffix on the positions path. That key was parsed into the config and then read by nothing, so pointing it anywhere had no effect — and if the positions file was named anything else the swap did not fire, the POSITIONS file was opened as the delays file, every cell parsed as non-numeric, and the stand silently ran with no staggering at all. Losing a fire-column stagger with no error is not an acceptable failure mode for a missing filename.

Definition at line 66 of file ActuatorCommander.cpp.

References actuator_port_, b, bind_addr_, bind_addr_explicit_, sequencer::ActuatorRole::board_id, sequencer::ActuatorRole::board_ip, sequencer::ActuatorRole::channel, sequencer::ActuatorRole::cmd_table_hi, sequencer::ActuatorRole::is_no, sequencer::ActuatorRole::is_pwm, fsw::elodin::kBallValveCmdHi, fsw::config::load_from_string(), loaded_, r, roles_, state_actuator_delays_, state_actuators_, and sequencer::trimVal().

Referenced by engine.layerx.measurements.MeasurementStore::get(), and main().

◆ logOverrideShadowingScript()

void sequencer::ActuatorCommander::logOverrideShadowingScript ( const std::string & role)
staticprivate

Warn once per role that a manual override is fighting a running script for a valve.

Definition at line 738 of file ActuatorCommander.cpp.

References lk.

Referenced by applyForState().

◆ positionsForState()

std::map< std::string, int > sequencer::ActuatorCommander::positionsForState ( const std::string & state_name) const

The logical positions the CSV declares for state_name (role -> 0 CLOSED / 1 OPEN).

Empty when the state has no column.

Exposed so a timed hold can be checked against the state it expires into: the hold closes its valves only because the return state's column says CLOSE, and if it does not, the timer expires, the state changes, and the valve stays open.

Definition at line 681 of file ActuatorCommander.cpp.

References findStateActuators(), and state_actuators_.

◆ publishCommandedState()

void sequencer::ActuatorCommander::publishCommandedState ( uint8_t table_hi,
uint8_t global_channel,
uint8_t logical_pos )
private

Definition at line 797 of file ActuatorCommander.cpp.

References elodin_.

Referenced by applyForState().

◆ publishInitialState()

void sequencer::ActuatorCommander::publishInitialState ( )

Publish initial de-energized state for all actuators to Elodin DB [0x32].

Does NOT send UDP commands — only populates the DB so the frontend has data on startup.

Definition at line 814 of file ActuatorCommander.cpp.

References sequencer::actuator_elodin_low_byte(), elodin_, ActuatorCommander::publishCommandedState(), and roles_.

◆ sendBatch()

bool sequencer::ActuatorCommander::sendBatch ( const std::map< std::string, std::vector< std::pair< uint8_t, uint8_t > > > & by_board)
private

Send to every board with the retransmits interleaved, so boards stay in step.

Send one command batch to every board, interleaving the retransmits.

Each packet still goes out 3x ~1 ms apart so it lands in the board's UDP receive window whatever its loop() rate. What changed is the ordering: round 1 to ALL boards, then round 2, then round 3, instead of finishing all three rounds for board A before starting board B. The old order meant every extra board added ~2 ms of skew to a state change – with two actuator boards, board 2's first packet left ~2 ms after board 1's, on every transition. Now the boards are within microseconds of each other and the batch takes ~2 ms total instead of ~2 ms per board.

One socket for the whole batch rather than one per board, since we now revisit each board 3x.

Definition at line 296 of file ActuatorCommander.cpp.

References actuator_port_, bind_addr_, daq::create_actuator_command_packet(), and o.

Referenced by applyForState(), and sendUDP().

◆ sendSingleActuator()

bool sequencer::ActuatorCommander::sendSingleActuator ( const std::string & name,
int pos )

Send one UDP packet for a single role (debug manual command).

Definition at line 688 of file ActuatorCommander.cpp.

References sequencer::ActuatorRole::board_ip, sequencer::ActuatorRole::is_no, ok, roles_, sendUDP(), and sequencer::toLower().

Referenced by main().

◆ sendUDP()

bool sequencer::ActuatorCommander::sendUDP ( const std::string & board_ip,
const std::vector< std::pair< uint8_t, uint8_t > > & id_state_pairs )
private

Definition at line 279 of file ActuatorCommander.cpp.

References sendBatch().

Referenced by sendSingleActuator().

◆ setDefaultBindAddress()

void sequencer::ActuatorCommander::setDefaultBindAddress ( const std::string & address)

Pin outgoing actuator commands to a NIC, unless [actuator_service].bind_address already named one explicitly.

Call after load().

bind_address defaulted to 0.0.0.0 in every shipped profile, so the bind() at the bottom of sendBatch() constrained nothing and the kernel chose the egress interface. That was harmless while the DAQ owned its machine and is not now that it shares the apps box.

Definition at line 60 of file ActuatorCommander.cpp.

References bind_addr_, and bind_addr_explicit_.

◆ setElodinClient()

void sequencer::ActuatorCommander::setElodinClient ( fsw::elodin::ElodinClient * client)
inline

Definition at line 170 of file ActuatorCommander.hpp.

References elodin_.

Referenced by main().

◆ setFireState()

void sequencer::ActuatorCommander::setFireState ( State s)
inline

Set the Elodin client for publishing commanded state [0x32, ch] to the DB.

Which state hands PWM actuators to controller_service. During it this commander stops commanding PWM roles entirely, so exactly one thing drives them during a burn. Was the State::FIRE enumerator; now follows [fire] state like every other fire decision.

Definition at line 166 of file ActuatorCommander.hpp.

References fire_state_.

Referenced by main().

◆ setManualOverride()

void sequencer::ActuatorCommander::setManualOverride ( const std::string & name,
int pos )

Debug: hold this logical position for the role until cleared or state transition.

Definition at line 721 of file ActuatorCommander.cpp.

References lock, manual_overrides_, overrides_mutex_, roles_, and sequencer::toLower().

Referenced by main().

◆ setScriptPosition()

void sequencer::ActuatorCommander::setScriptPosition ( const std::string & name,
int pos )

A running script's position for one valve — the script layering on top of its state's CSV column.

Deliberately NOT manual_overrides_. That map is cleared on every transition AND whenever debug mode is switched off (SequencerService::doSetDebugMode), so a script writing there would have its valve intent silently wiped by an operator leaving debug mode while the script kept running.

Setting a position also takes OWNERSHIP of the valve: any staged command still in flight for it is cancelled, and it stops being skipped by the republish's pending-delay filter. Without that, the state's own delays column could move the valve seconds after the script already commanded it, landing last and silently winning.

Like setManualOverride, this is not debug-gated here — the gate belongs to the caller.

Definition at line 750 of file ActuatorCommander.cpp.

References a, b, lk, pending_roles_, pending_roles_mutex_, r, roles_, script_positions_, and script_positions_mutex_.

Referenced by main().

◆ startContinuousLoop()

void sequencer::ActuatorCommander::startContinuousLoop ( State state,
bool allow_delays = true,
bool defer_first_pass = false )

Start the 1 Hz continuous re-send loop for the given state.

Stops any previously running loop first. allow_delays false makes the entry apply immediately — used for abort states, which must not sit behind a delay.

Parameters
defer_first_passskip the immediate pass and start republishing one period later. For a dynamic state: the caller has already applied the column, so that pass is redundant, and running it races the script that is about to start commanding valves — a race the script loses, because the pass resolves positions and only then spends a couple of milliseconds on retransmits, landing a stale command on top of the script's.

Definition at line 616 of file ActuatorCommander.cpp.

References applyForState(), loop_running_, loop_state_, loop_thread_, sequencer::StateMachine::name(), state, and stopContinuousLoop().

Referenced by main().

◆ stopContinuousLoop()

void sequencer::ActuatorCommander::stopContinuousLoop ( )

Stop the continuous re-send loop (blocks until the thread exits).

Definition at line 662 of file ActuatorCommander.cpp.

References loop_running_, and loop_thread_.

Referenced by main(), and startContinuousLoop().

Member Data Documentation

◆ actuator_port_

uint16_t sequencer::ActuatorCommander::actuator_port_ {5005}
private

Definition at line 217 of file ActuatorCommander.hpp.

Referenced by load(), and sendBatch().

◆ bind_addr_

std::string sequencer::ActuatorCommander::bind_addr_ {"0.0.0.0"}
private

Definition at line 215 of file ActuatorCommander.hpp.

Referenced by load(), sendBatch(), and setDefaultBindAddress().

◆ bind_addr_explicit_

bool sequencer::ActuatorCommander::bind_addr_explicit_ {false}
private

Definition at line 216 of file ActuatorCommander.hpp.

Referenced by load(), and setDefaultBindAddress().

◆ elodin_

fsw::elodin::ElodinClient* sequencer::ActuatorCommander::elodin_ {nullptr}
private

◆ fire_state_

State sequencer::ActuatorCommander::fire_state_ {State::FIRE}
private

Bumped on every transition so an in-flight delayed stage from the previous state aborts instead of landing after we have already moved on.

Definition at line 188 of file ActuatorCommander.hpp.

Referenced by applyForState(), and setFireState().

◆ loaded_

bool sequencer::ActuatorCommander::loaded_ {false}
private

Definition at line 218 of file ActuatorCommander.hpp.

Referenced by isLoaded(), and load().

◆ loop_running_

std::atomic<bool> sequencer::ActuatorCommander::loop_running_ {false}
private

Definition at line 212 of file ActuatorCommander.hpp.

Referenced by startContinuousLoop(), and stopContinuousLoop().

◆ loop_state_

State sequencer::ActuatorCommander::loop_state_ {State::IDLE}
private

Definition at line 213 of file ActuatorCommander.hpp.

Referenced by startContinuousLoop().

◆ loop_thread_

std::thread sequencer::ActuatorCommander::loop_thread_
private

Definition at line 211 of file ActuatorCommander.hpp.

Referenced by startContinuousLoop(), and stopContinuousLoop().

◆ manual_overrides_

std::map<std::string, int> sequencer::ActuatorCommander::manual_overrides_
private

◆ overrides_mutex_

std::mutex sequencer::ActuatorCommander::overrides_mutex_
private

◆ pending_roles_

std::set<std::string> sequencer::ActuatorCommander::pending_roles_
private

Roles whose delayed stage has not fired yet.

The 1 Hz republish skips these: the board holds its last commanded position, which is exactly the pre-transition value we want it to keep until the delay elapses. Without this the republish would send the settled position first and any delay longer than the republish period could never be observed.

Definition at line 194 of file ActuatorCommander.hpp.

Referenced by applyForState(), and setScriptPosition().

◆ pending_roles_mutex_

std::mutex sequencer::ActuatorCommander::pending_roles_mutex_
private

Definition at line 195 of file ActuatorCommander.hpp.

Referenced by applyForState(), and setScriptPosition().

◆ roles_

std::map<std::string, ActuatorRole> sequencer::ActuatorCommander::roles_
private

◆ schedule_gen_

std::atomic<uint64_t> sequencer::ActuatorCommander::schedule_gen_ {0}
private

Definition at line 189 of file ActuatorCommander.hpp.

Referenced by applyForState().

◆ script_positions_

std::map<std::string, int> sequencer::ActuatorCommander::script_positions_
private

Valves a running script has taken over, role -> logical position.

Its own mutex, and never held at the same time as overrides_mutex_ — applyForState snapshots this first and then takes the other, so no call path holds both and there is no lock order to get wrong.

Definition at line 205 of file ActuatorCommander.hpp.

Referenced by applyForState(), clearScriptPositions(), and setScriptPosition().

◆ script_positions_mutex_

std::mutex sequencer::ActuatorCommander::script_positions_mutex_
private

Definition at line 206 of file ActuatorCommander.hpp.

Referenced by applyForState(), clearScriptPositions(), and setScriptPosition().

◆ state_actuator_delays_

std::map<std::string, std::map<std::string, double> > sequencer::ActuatorCommander::state_actuator_delays_
private

state -> actuator -> delay in seconds, from state_machine_actuator_delays.csv.

Definition at line 185 of file ActuatorCommander.hpp.

Referenced by applyForState(), delayForRole(), and load().

◆ state_actuators_

std::map<std::string, std::map<std::string, int> > sequencer::ActuatorCommander::state_actuators_
private

Definition at line 197 of file ActuatorCommander.hpp.

Referenced by applyForState(), findStateActuators(), load(), and positionsForState().


The documentation for this class was generated from the following files: