22#include <netinet/in.h>
23#include <sys/socket.h>
42static void check(
bool ok,
const std::string& what) {
43 std::cout << (
ok ?
" ok " :
" FAIL ") << what << std::endl;
49static std::vector<uint8_t>
ptFrame(uint8_t lo, uint8_t channel,
float psi, uint8_t cal_status,
52 1, channel, std::array<uint8_t, 3>{0, 0, 0}, psi,
58 const uint32_t len =
static_cast<uint32_t
>(
payload.size() + 4);
59 std::vector<uint8_t> f(8 +
payload.size());
60 std::memcpy(f.data(), &len, 4);
75 std::atomic<bool>
run{
true};
76 std::atomic<uint16_t>
port{0};
78 void start(std::vector<uint8_t> blob) {
79 th = std::thread([
this, blob = std::move(blob)]() {
80 int srv = socket(AF_INET, SOCK_STREAM, 0);
82 setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &opt,
sizeof(opt));
83 struct sockaddr_in
a{};
84 a.sin_family = AF_INET;
85 a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
87 if (bind(srv,
reinterpret_cast<struct sockaddr*
>(&
a),
sizeof(
a)) < 0) {
91 socklen_t sl =
sizeof(
a);
92 getsockname(srv,
reinterpret_cast<struct sockaddr*
>(&
a), &sl);
93 port = ntohs(
a.sin_port);
96 int c = accept(srv,
nullptr,
nullptr);
102 std::this_thread::sleep_for(std::chrono::milliseconds(50));
103 ssize_t unused = write(c, blob.data(), blob.size());
106 std::this_thread::sleep_for(std::chrono::milliseconds(10));
110 for (
int i = 0; i < 500 &&
port.load() == 0; i++)
111 std::this_thread::sleep_for(std::chrono::milliseconds(2));
131 std::atomic<bool>
run{
true};
135 void start(std::vector<uint8_t> blob,
int publishAfterSubscribes) {
136 th = std::thread([
this, blob = std::move(blob), publishAfterSubscribes]() {
137 int srv = socket(AF_INET, SOCK_STREAM, 0);
139 setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &opt,
sizeof(opt));
140 struct sockaddr_in
a{};
141 a.sin_family = AF_INET;
142 a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
144 if (bind(srv,
reinterpret_cast<struct sockaddr*
>(&
a),
sizeof(
a)) < 0) {
148 socklen_t sl =
sizeof(
a);
149 getsockname(srv,
reinterpret_cast<struct sockaddr*
>(&
a), &sl);
150 port = ntohs(
a.sin_port);
153 int c = accept(srv,
nullptr,
nullptr);
160 bool published =
false;
163 const ssize_t
n = recv(c, buf,
sizeof(buf), MSG_DONTWAIT);
166 if (!published &&
subscribes.load() >= publishAfterSubscribes) {
167 ssize_t unused = write(c, blob.data(), blob.size());
171 std::this_thread::sleep_for(std::chrono::milliseconds(5));
176 for (
int i = 0; i < 500 &&
port.load() == 0; i++)
177 std::this_thread::sleep_for(std::chrono::milliseconds(2));
201[sensor_roles_pt_board]
204[sensor_roles_pt_board_2]
209 std::cout <<
"=== PressureFeed ===" << std::endl;
217 std::vector<uint8_t> blob;
218 const auto a =
ptFrame(0x16, 6, 250.0f, 1);
219 const auto b =
ptFrame(0x34, 4, 4321.0f, 1);
220 blob.insert(blob.end(),
a.begin(),
a.end());
221 blob.insert(blob.end(),
b.begin(),
b.end());
225 check(db.
port.load() != 0,
"fake elodin-db is listening");
228 feed.start(cfg, {
"GN2 Regulated",
"GN2 High"},
"127.0.0.1", db.
port.load());
229 std::this_thread::sleep_for(std::chrono::milliseconds(400));
232 check(
feed.read(
"GN2 Regulated", psi) == PressureFeed::Status::Ok && psi == 250.0,
233 "a board-1 reading lands under its own role");
236 check(
feed.read(
"GN2 High", psi) == PressureFeed::Status::Ok && psi == 4321.0,
237 "a BOARD-2 reading lands too — table 0x34, which a board-1 filter would drop");
239 check(
feed.read(
"Nonexistent", psi) == PressureFeed::Status::NotSubscribed,
240 "a role nothing subscribed to is NotSubscribed, not a stale zero");
257[[boards.labjack_1.channels]]
265 std::vector<uint8_t> blob;
266 const auto esp =
ptFrame(0x16, 6, 250.0f, 1, 0x20);
267 const auto t7 =
ptFrame(0x16, 6, 812.5f, 1, 0x26);
268 blob.insert(blob.end(), esp.begin(), esp.end());
269 blob.insert(blob.end(),
t7.begin(),
t7.end());
274 feed.start(c, {
"GN2 Regulated",
"T7 Tank"},
"127.0.0.1", db.
port.load());
275 std::this_thread::sleep_for(std::chrono::milliseconds(400));
278 check(
feed.read(
"T7 Tank", psi) == PressureFeed::Status::Ok && psi == 812.5,
279 "a T7 PT reading lands under its role, from table {0x26, 0x16}");
281 check(
feed.read(
"GN2 Regulated", psi) == PressureFeed::Status::Ok && psi == 250.0,
282 "and the ESP32 PT on {0x20, 0x16} keeps its own value");
293 feed.start(cfg, {
"GN2 Regulated"},
"127.0.0.1", db.
port.load());
294 std::this_thread::sleep_for(std::chrono::milliseconds(400));
297 const auto s =
feed.read(
"GN2 Regulated", psi);
298 check(s == PressureFeed::Status::Uncalibrated,
299 "calibration_status = 0 is refused even though a number arrived");
300 check(psi == -1.0,
"and no value is handed back to be acted on");
302 "the refusal says why, in words an operator can act on");
314 feed.start(cfg, {
"GN2 Regulated"},
"127.0.0.1", db.
port.load());
315 std::this_thread::sleep_for(std::chrono::milliseconds(300));
318 check(
feed.read(
"GN2 Regulated", psi) == PressureFeed::Status::Ok,
"fresh at first");
323 check(
feed.read(
"GN2 Regulated", psi) == PressureFeed::Status::Stale,
324 "and stale once it stops arriving — a press loop must not cycle against a frozen "
339 std::vector<uint8_t> blob;
340 const auto a =
ptFrame(0x16, 6, 250.0f, 1);
341 blob.insert(blob.end(),
a.begin(),
a.end());
347 check(db.
port.load() != 0,
"refusing fake db is listening");
350 feed.start(cfg, {
"GN2 Regulated",
"GN2 High"},
"127.0.0.1", db.
port.load());
354 for (
int i = 0; i < 400; i++) {
355 if (
feed.read(
"GN2 Regulated", psi) == PressureFeed::Status::Ok) {
359 std::this_thread::sleep_for(std::chrono::milliseconds(10));
361 check(got,
"a table refused on first subscribe is re-subscribed and starts delivering");
362 check(got && psi > 249.0 && psi < 251.0,
"and the value that arrives is the right one");
363 check(db.
subscribes.load() >= 3,
"the feed really did send a second subscribe");
372 feed.start(cfg, {},
"127.0.0.1", 1);
373 std::this_thread::sleep_for(std::chrono::milliseconds(100));
374 check(
feed.subscribedRoles().empty(),
"no roles, no subscription, no thread");
376 check(
feed.read(
"GN2 High", psi) == PressureFeed::Status::NotSubscribed,
377 "and every read is NotSubscribed");
384 feed.start(cfg, {
"Not On Any Board"},
"127.0.0.1", 1);
385 std::this_thread::sleep_for(std::chrono::milliseconds(100));
386 check(
feed.subscribedRoles().empty(),
"an unresolvable role subscribes to nothing");
390 std::cout << (
g_failures == 0 ?
"\nAll pressure-feed checks passed.\n"
391 :
"\nFAILURES: " + std::to_string(
g_failures) +
"\n");
static route_t * find(uint16_t pin)
static std::string explain(const std::string &role, Status s)
Human phrasing for a refusal, e.g.
static constexpr int kMaxAgeMs
How old a reading may be and still be acted on.
Live calibrated pressures, by ROLE NAME, for the sequencer.
*brief create JSON pointer *sa reference_tokens const string_t const string_t & b
*brief create JSON pointer *sa reference_tokens const string_t & a
CommsMessage< uint64_t, uint8_t, std::array< uint8_t, 3 >, float, uint32_t, uint8_t > CalibratedPTMessage
Calibrated Pressure Transducer message for Elodin.
Config load_from_string(const std::string &text)
Parse config from an in-memory TOML string (SequencerService / ActuatorCommander hold it so).
*brief user defined string literal for JSON values *sa std::size_t n
Fake elodin-db that REFUSES the first subscription, the way the real one does when the calibrated VTa...
void start(std::vector< uint8_t > blob, int publishAfterSubscribes)
std::atomic< uint16_t > port
std::atomic< int > subscribes
Fake elodin-db: accept one connection, write blob, then hold the socket open.
void start(std::vector< uint8_t > blob, bool close_after_write)
void start(std::vector< uint8_t > blob)
std::atomic< uint16_t > port
static constexpr std::size_t nbytes()
The whole parsed config.toml, restricted to what C++ consumes.
std::vector< std::string > t7_errors
static ArmAction feed(const Frame &f, uint32_t t)
static std::vector< uint8_t > ptFrame(uint8_t lo, uint8_t channel, float psi, uint8_t cal_status, uint8_t hi=0x20)
One calibrated-PT frame for table [hi, lo] (0x20 an ESP32 PT, 0x26 a LabJack T7 input).
static void check(bool ok, const std::string &what)
static std::string t7(const std::string &channels, const std::string &extra="", const std::string &name="lj", int board_id=81)
static const uint8_t kConfig[]