10#include <netinet/in.h>
11#include <sys/socket.h>
30namespace fs = std::filesystem;
37static void check(
bool ok,
const std::string& what) {
38 std::cout << (
ok ?
" ok " :
" FAIL ") << what << std::endl;
47 fd_ = socket(AF_INET, SOCK_STREAM, 0);
49 setsockopt(
fd_, SOL_SOCKET, SO_REUSEADDR, &opt,
sizeof(opt));
51 addr.sin_family = AF_INET;
52 addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
54 if (bind(
fd_,
reinterpret_cast<sockaddr*
>(&addr),
sizeof(addr)) < 0)
56 socklen_t len =
sizeof(addr);
57 getsockname(
fd_,
reinterpret_cast<sockaddr*
>(&addr), &len);
58 port_ = ntohs(addr.sin_port);
60 thread_ = std::thread([
this]() {
61 int c = accept(
fd_,
nullptr,
nullptr);
65 setsockopt(c, SOL_SOCKET, SO_RCVTIMEO, &tv,
sizeof(tv));
68 ssize_t
n =
read(c, buf,
sizeof(buf));
72 std::lock_guard<std::mutex>
lk(
m_);
91 std::vector<uint8_t>
commanded(uint8_t hi, uint8_t lo) {
92 std::lock_guard<std::mutex>
lk(
m_);
95 std::vector<uint8_t> out;
96 for (
size_t i = 0; i + 18 <=
bytes_.size(); i++) {
98 std::memcpy(&len, &
bytes_[i], 4);
101 out.push_back(
bytes_[i + 17]);
117 std::ofstream act(dir /
"state_machine_actuators.csv");
118 act <<
",Idle,Armed\n";
119 act <<
"Ball Valve,CLOSE,OPEN\n";
120 act <<
"Act Valve,CLOSE,OPEN\n";
123 std::ofstream del(dir /
"state_machine_actuator_delays.csv");
124 del <<
",Idle,Armed\n";
125 del <<
"Ball Valve,0,0\n";
126 del <<
"Act Valve,0,0\n";
130 return "[network]\nactuator_cmd_port = " + std::to_string(
port) +
131 "\n\n[actuator_service]\nbind_address = \"0.0.0.0\"\n\n"
132 "[boards.ball_valve_board]\ntype = \"BALL_VALVE\"\nip = \"127.0.0.1\"\nboard_id = 71\n"
133 "enabled = true\nactive_connectors = [1]\n\n"
134 "[boards.act_11]\ntype = \"ACTUATOR\"\nip = \"127.0.0.1\"\nboard_id = 11\n"
135 "enabled = true\nnum_actuators = 10\n\n"
136 "[actuator_roles]\n\"Ball Valve\" = [\"NO\", 1, 71]\n"
137 "\"Act Valve\" = [\"NC\", 2, 11]\n";
140static bool sawCommand(
const std::vector<daqtest::Burst>& bursts, uint8_t
ch, uint8_t hw) {
141 for (
const auto&
b : bursts) {
142 auto it =
b.state.find(
ch);
143 if (it !=
b.state.end() && it->second == hw)
150 std::cout <<
"=== Ball valve through the actuator commander ===" << std::endl;
152 const uint16_t
port = 45931;
153 fs::path dir = fs::temp_directory_path() /
"daq_ball_valve_commander_test";
155 fs::create_directories(dir);
157 const std::string csv = (dir /
"state_machine_actuators.csv").
string();
160 if (!elodin.
start()) {
161 std::cerr <<
"could not start the fake Elodin" << std::endl;
165 if (!client.connect(
"127.0.0.1", elodin.
port())) {
166 std::cerr <<
"could not connect to the fake Elodin" << std::endl;
171 if (!listener.
start()) {
172 std::cerr <<
"could not bind UDP " <<
port << std::endl;
176 if (!ac.
load(cfg, csv)) {
177 std::cerr <<
"load failed" << std::endl;
184 std::this_thread::sleep_for(std::chrono::milliseconds(300));
186 std::this_thread::sleep_for(std::chrono::milliseconds(300));
189 const auto bursts = listener.
bursts();
190 check(
sawCommand(bursts, 1, 1),
"Armed column: ball valve sent 1 (open), not inverted by NO");
192 check(
sawCommand(bursts, 2, 1),
"Armed column: the actuator on board 11 still sent 1");
195 std::this_thread::sleep_for(std::chrono::milliseconds(100));
196 const auto bv = elodin.
commanded(0x35, 0x01);
197 check(bv.size() >= 2 && bv.front() == 1 && bv.back() == 0,
198 "BV_CMD [0x35, 0x01] recorded open then closed (" + std::to_string(bv.size()) +
201 "nothing written to ACT_CMD [0x32, 0x01], actuator board 11's channel 1");
202 const auto act = elodin.
commanded(0x32, 0x02);
203 check(!act.empty() && act.back() == 1,
"the actuator's own state is still on [0x32, 0x02]");
210 std::cout <<
g_failures <<
" check(s) failed" << std::endl;
213 std::cout <<
"Ball valve commander passed" << std::endl;
Stands in for Elodin: accepts one connection and keeps every byte written to it.
std::vector< uint8_t > bytes_
std::vector< uint8_t > commanded(uint8_t hi, uint8_t lo)
Every commanded state written to [hi, lo], in order: the actuator_state byte.
Collects UDP actuator packets, folding the 3x retransmit of one batch into a single burst.
std::vector< Burst > bursts() const
Standalone Elodin database client.
Sends UDP actuator commands based on state transitions.
void applyForState(State state, bool is_transition=false)
Send actuator commands for the given state (one shot).
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 setElodinClient(fsw::elodin::ElodinClient *client)
bool sendSingleActuator(const std::string &name, int pos)
Send one UDP packet for a single role (debug manual command).
static const emmc_port_t port
*brief create JSON pointer *sa reference_tokens const string_t const string_t & b
*brief user defined string literal for JSON values *sa std::size_t n
State
System state IDs — must match the TypeScript SystemState enum in web-gui/shared/types....
static std::string writeFixture(const fs::path &dir, uint16_t port)
static void check(bool ok, const std::string &what)
static bool sawCommand(const std::vector< daqtest::Burst > &bursts, uint8_t ch, uint8_t hw)
static double read(int32_t code)
static std::string ch(int ain, const std::string &kind, const std::string &extra="", const std::string &board="lj")