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

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

#include <ActuatorCommander.hpp>

Collaboration diagram for 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.

◆ clearAllManualOverrides()

Clear all manual overrides (e.g.

leaving debug mode or transitioning state).

Definition at line 789 of file ActuatorCommander.cpp.

◆ clearScriptPositions()

Drop every script position.

Called on any transition, beside clearAllManualOverrides.

Definition at line 784 of file ActuatorCommander.cpp.

◆ 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.

◆ 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.

◆ isLoaded()

Definition at line 178 of file ActuatorCommander.hpp.

◆ 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.

◆ 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.

◆ 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.

◆ publishCommandedState()

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

◆ 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.

◆ 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.

◆ 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.

◆ 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.

◆ 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.

◆ setElodinClient()

◆ setFireState()

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.

◆ 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.

◆ 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.

◆ 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.

◆ stopContinuousLoop()

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

Definition at line 662 of file ActuatorCommander.cpp.

Member Data Documentation

◆ actuator_port_

Definition at line 217 of file ActuatorCommander.hpp.

◆ bind_addr_

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

Definition at line 215 of file ActuatorCommander.hpp.

◆ bind_addr_explicit_

Definition at line 216 of file ActuatorCommander.hpp.

◆ elodin_

◆ fire_state_

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.

◆ loaded_

Definition at line 218 of file ActuatorCommander.hpp.

◆ loop_running_

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

Definition at line 212 of file ActuatorCommander.hpp.

◆ loop_state_

Definition at line 213 of file ActuatorCommander.hpp.

◆ loop_thread_

Definition at line 211 of file ActuatorCommander.hpp.

◆ manual_overrides_

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

Definition at line 199 of file ActuatorCommander.hpp.

◆ overrides_mutex_

Definition at line 200 of file ActuatorCommander.hpp.

◆ 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.

◆ pending_roles_mutex_

Definition at line 195 of file ActuatorCommander.hpp.

◆ roles_

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

Definition at line 183 of file ActuatorCommander.hpp.

◆ schedule_gen_

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

Definition at line 189 of file ActuatorCommander.hpp.

◆ 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.

◆ script_positions_mutex_

◆ 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.

◆ state_actuators_

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

Definition at line 197 of file ActuatorCommander.hpp.


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