STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
BoardListener.hpp
Go to the documentation of this file.
1/**
2 * A fake actuator board: binds the actuator command UDP port on loopback and records what was
3 * actually sent, and when.
4 *
5 * This is the only thing that can answer "did the valve move when we said it would". Every other
6 * way of testing the delay feature measures a clock somewhere in the sequencer, which is how a
7 * staggered send could be shadowed by an unstaged one for as long as it was: the arithmetic was
8 * right and the commands still went out together. Assert on the wire.
9 *
10 * Shared by test_actuator_delays (the mechanism) and test_hold_rules (the caller).
11 */
12#pragma once
13
14#include <arpa/inet.h>
15#include <netinet/in.h>
16#include <sys/socket.h>
17#include <unistd.h>
18
19#include <atomic>
20#include <chrono>
21#include <map>
22#include <mutex>
23#include <set>
24#include <thread>
25#include <vector>
26
27#include "DiabloPacketUtils.h"
28
29namespace daqtest {
30
31using Clock = std::chrono::steady_clock;
32
33/** One received command burst: when it arrived (ms since the listener started) and what it said. */
34struct Burst {
35 long long at_ms;
36 /** Channels the batch carried. */
37 std::set<uint8_t> channels;
38 /** channel -> commanded hardware state (1 = energised; for an NC valve that is OPEN). */
39 std::map<uint8_t, uint8_t> state;
40};
41
42/** Collects UDP actuator packets, folding the 3x retransmit of one batch into a single burst. */
44public:
45 explicit BoardListener(uint16_t port) : port_(port) {
46 }
48 stop();
49 }
50
51 bool start() {
52 sock_ = socket(AF_INET, SOCK_DGRAM, 0);
53 if (sock_ < 0)
54 return false;
55 int reuse = 1;
56 setsockopt(sock_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
57 struct sockaddr_in addr{};
58 addr.sin_family = AF_INET;
59 addr.sin_port = htons(port_);
60 inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
61 if (bind(sock_, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) < 0) {
62 close(sock_);
63 sock_ = -1;
64 return false;
65 }
66 struct timeval tv{.tv_sec = 0, .tv_usec = 20000};
67 setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
68 running_ = true;
69 t0_ = Clock::now();
70 thread_ = std::thread([this]() {
71 run();
72 });
73 return true;
74 }
75
76 void stop() {
77 if (!running_.exchange(false))
78 return;
79 if (thread_.joinable())
80 thread_.join();
81 if (sock_ >= 0) {
82 close(sock_);
83 sock_ = -1;
84 }
85 }
86
87 std::vector<Burst> bursts() const {
88 std::lock_guard<std::mutex> lk(mutex_);
89 return bursts_;
90 }
91
92private:
93 void run() {
94 uint8_t buf[1024];
95 while (running_) {
96 ssize_t n = recv(sock_, buf, sizeof(buf), 0);
97 if (n <= 0)
98 continue;
99 daq::PacketHeader header;
100 std::vector<daq::ActuatorCommand> cmds;
101 if (!daq::parse_actuator_command_packet(buf, static_cast<size_t>(n), header, cmds))
102 continue;
103 Burst b{};
104 for (const auto& c : cmds) {
105 b.channels.insert(c.actuator_id);
106 b.state[c.actuator_id] = c.actuator_state;
107 }
108 b.at_ms =
109 std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - t0_).count();
110
111 std::lock_guard<std::mutex> lk(mutex_);
112 // The same batch is retransmitted 3x ~1 ms apart; fold identical content arriving
113 // within 50 ms into one burst so assertions talk about batches, not packets. Compared
114 // on the full channel->state map, not just the channel set: two bursts carrying the
115 // same channels with opposite commands are two events, not a retransmit.
116 if (!bursts_.empty() && bursts_.back().state == b.state &&
117 b.at_ms - bursts_.back().at_ms < 50)
118 continue;
119 bursts_.push_back(std::move(b));
120 }
121 }
122
123 uint16_t port_;
124 int sock_ = -1;
125 std::atomic<bool> running_{false};
126 std::thread thread_;
127 mutable std::mutex mutex_;
128 std::vector<Burst> bursts_;
129 Clock::time_point t0_;
130};
131
132/** When `ch` was first commanded to `hw` at or after `after_ms`. -1 if it never was. */
133inline long long firstCommand(const std::vector<Burst>& bursts, uint8_t ch, uint8_t hw,
134 long long after_ms = -1) {
135 for (const auto& b : bursts) {
136 if (b.at_ms < after_ms)
137 continue;
138 auto it = b.state.find(ch);
139 if (it != b.state.end() && it->second == hw)
140 return b.at_ms;
141 }
142 return -1;
143}
144
145} // namespace daqtest
BoardListener(uint16_t port)
std::vector< Burst > bursts_
Clock::time_point t0_
std::atomic< bool > running_
BoardListener(uint16_t port)
std::vector< Burst > bursts() const
static const emmc_port_t port
Definition emmc_stm32.c:210
*brief create JSON pointer *sa reference_tokens const string_t const string_t & b
Definition json.hpp:13878
bool parse_actuator_command_packet(const uint8_t *buffer, size_t buffer_size, PacketHeader &header_out, std::vector< ActuatorCommand > &commands_out)
Parses an Actuator Command packet from buffer.
A fake actuator board: binds the actuator command UDP port on loopback and records what was actually ...
std::chrono::steady_clock Clock
long long firstCommand(const std::vector< Burst > &bursts, uint8_t ch, uint8_t hw, long long after_ms=-1)
When ch was first commanded to hw at or after after_ms.
*brief user defined string literal for JSON values *sa std::size_t n
Definition json.hpp:24474
One received command burst: when it arrived (ms since the listener started) and what it said.
std::map< uint8_t, uint8_t > state
channel -> commanded hardware state (1 = energised; for an NC valve that is OPEN).
std::set< uint8_t > channels
Channels the batch carried.
static std::string ch(int ain, const std::string &kind, const std::string &extra="", const std::string &board="lj")
Definition test_t7.cpp:98