STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_ball_valve_commander.cpp
Go to the documentation of this file.
1/**
2 * A ball valve through the sequencer's actuator path, asserted on the wire.
3 *
4 * 1. It is commanded like any actuator: its state's column, and a manual/script command, reach
5 * the board as actuator_id 1 — pass-through, even when the role was written "NO" (a ball valve
6 * has no polarity; 1 opens it).
7 * 2. Its commanded state is recorded on [0x35] BV_CMD, never on [0x32] ACT_CMD: board 71 is
8 * slot 1, the same slot as actuator board 11, and [0x32] carries no board type.
9 */
10#include <netinet/in.h>
11#include <sys/socket.h>
12#include <unistd.h>
13
14#include <atomic>
15#include <chrono>
16#include <cstring>
17#include <filesystem>
18#include <fstream>
19#include <iostream>
20#include <mutex>
21#include <string>
22#include <thread>
23#include <vector>
24
25#include "BoardListener.hpp"
29
30namespace fs = std::filesystem;
34
35static int g_failures = 0;
36
37static void check(bool ok, const std::string& what) {
38 std::cout << (ok ? " ok " : " FAIL ") << what << std::endl;
39 if (!ok)
40 g_failures++;
41}
42
43/** Stands in for Elodin: accepts one connection and keeps every byte written to it. */
45public:
46 bool start() {
47 fd_ = socket(AF_INET, SOCK_STREAM, 0);
48 int opt = 1;
49 setsockopt(fd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
50 sockaddr_in addr{};
51 addr.sin_family = AF_INET;
52 addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
53 addr.sin_port = 0;
54 if (bind(fd_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0)
55 return false;
56 socklen_t len = sizeof(addr);
57 getsockname(fd_, reinterpret_cast<sockaddr*>(&addr), &len);
58 port_ = ntohs(addr.sin_port);
59 listen(fd_, 1);
60 thread_ = std::thread([this]() {
61 int c = accept(fd_, nullptr, nullptr);
62 if (c < 0)
63 return;
64 timeval tv{0, 50000};
65 setsockopt(c, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
66 uint8_t buf[4096];
67 while (run_) {
68 ssize_t n = read(c, buf, sizeof(buf));
69 if (n == 0)
70 break;
71 if (n > 0) {
72 std::lock_guard<std::mutex> lk(m_);
73 bytes_.insert(bytes_.end(), buf, buf + n);
74 }
75 }
76 close(c);
77 });
78 return true;
79 }
80 void stop() {
81 run_ = false;
82 if (thread_.joinable())
83 thread_.join();
84 close(fd_);
85 }
86 uint16_t port() const {
87 return port_;
88 }
89
90 /** Every commanded state written to [hi, lo], in order: the actuator_state byte. */
91 std::vector<uint8_t> commanded(uint8_t hi, uint8_t lo) {
92 std::lock_guard<std::mutex> lk(m_);
93 // TABLE packet: u32 len (= 10 + 4) | u8 type (1) | u8 id[2] | u8 req | u64 ts | u8 ch | u8
94 // st
95 std::vector<uint8_t> out;
96 for (size_t i = 0; i + 18 <= bytes_.size(); i++) {
97 uint32_t len;
98 std::memcpy(&len, &bytes_[i], 4);
99 if (len == 14 && bytes_[i + 4] == 1 && bytes_[i + 5] == hi && bytes_[i + 6] == lo &&
100 bytes_[i + 16] == lo)
101 out.push_back(bytes_[i + 17]);
102 }
103 return out;
104 }
105
106private:
107 int fd_ = -1;
108 uint16_t port_ = 0;
109 std::atomic<bool> run_{true};
110 std::thread thread_;
111 std::mutex m_;
112 std::vector<uint8_t> bytes_;
113};
114
115static std::string writeFixture(const fs::path& dir, uint16_t port) {
116 {
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";
121 }
122 {
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";
127 }
128 // Both boards on loopback; the ball valve on channel 1, the actuator on channel 2, so the
129 // listener can tell them apart. "NO" on the ball valve is deliberate: it must not invert.
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";
138}
139
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)
144 return true;
145 }
146 return false;
147}
148
149int main() {
150 std::cout << "=== Ball valve through the actuator commander ===" << std::endl;
151
152 const uint16_t port = 45931;
153 fs::path dir = fs::temp_directory_path() / "daq_ball_valve_commander_test";
154 fs::remove_all(dir);
155 fs::create_directories(dir);
156 const std::string cfg = writeFixture(dir, port);
157 const std::string csv = (dir / "state_machine_actuators.csv").string();
158
159 FakeElodin elodin;
160 if (!elodin.start()) {
161 std::cerr << "could not start the fake Elodin" << std::endl;
162 return 1;
163 }
165 if (!client.connect("127.0.0.1", elodin.port())) {
166 std::cerr << "could not connect to the fake Elodin" << std::endl;
167 return 1;
168 }
169
170 BoardListener listener(port);
171 if (!listener.start()) {
172 std::cerr << "could not bind UDP " << port << std::endl;
173 return 1;
174 }
176 if (!ac.load(cfg, csv)) {
177 std::cerr << "load failed" << std::endl;
178 return 1;
179 }
180 ac.setElodinClient(&client);
181
182 // Entering Armed opens both (the column), then a manual command closes the ball valve.
183 ac.applyForState(State::ARMED, /*is_transition=*/true);
184 std::this_thread::sleep_for(std::chrono::milliseconds(300));
185 check(ac.sendSingleActuator("Ball Valve", 0), "manual close of the ball valve accepted");
186 std::this_thread::sleep_for(std::chrono::milliseconds(300));
187 listener.stop();
188
189 const auto bursts = listener.bursts();
190 check(sawCommand(bursts, 1, 1), "Armed column: ball valve sent 1 (open), not inverted by NO");
191 check(sawCommand(bursts, 1, 0), "manual close: ball valve sent 0");
192 check(sawCommand(bursts, 2, 1), "Armed column: the actuator on board 11 still sent 1");
193
194 // Board 71 slot 1 ch 1 -> [0x35, 0x01]; board 11 slot 1 ch 2 -> [0x32, 0x02].
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()) +
199 " writes)");
200 check(elodin.commanded(0x32, 0x01).empty(),
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]");
204
205 client.disconnect();
206 elodin.stop();
207 fs::remove_all(dir);
208
209 if (g_failures) {
210 std::cout << g_failures << " check(s) failed" << std::endl;
211 return 1;
212 }
213 std::cout << "Ball valve commander passed" << std::endl;
214 return 0;
215}
Stands in for Elodin: accepts one connection and keeps every byte written to it.
std::vector< uint8_t > bytes_
std::atomic< bool > run_
uint16_t port() const
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
Definition emmc_stm32.c:210
static bool ok
Definition flight_e2e.c:435
*brief create JSON pointer *sa reference_tokens const string_t const string_t & b
Definition json.hpp:13878
*brief user defined string literal for JSON values *sa std::size_t n
Definition json.hpp:24474
State
System state IDs — must match the TypeScript SystemState enum in web-gui/shared/types....
static int g_failures
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")
Definition test_t7.cpp:98