STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
AbortBroadcaster.cpp
Go to the documentation of this file.
2
3#include <arpa/inet.h>
4#include <netinet/in.h>
5#include <sys/socket.h>
6#include <unistd.h>
7
8#include <cerrno>
9#include <chrono>
10#include <cstring>
11#include <iostream>
12#include <mutex>
13#include <thread>
14
15// daqv2comms — PacketHeader + PacketType
16#include "DiabloEnums.h"
17#include "DiabloPackets.h"
18#include "net/DaqInterface.hpp"
19
20namespace sequencer {
21
22namespace {
24 return static_cast<uint32_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
25 std::chrono::steady_clock::now().time_since_epoch())
26 .count() &
27 0xFFFFFFFF);
28}
29} // namespace
30
31AbortBroadcaster::AbortBroadcaster(uint16_t port, uint32_t abort_done_delay_ms)
32 : port_(port), abort_done_delay_ms_(abort_done_delay_ms) {
33 dest_addr_.s_addr = INADDR_BROADCAST;
34}
35
36bool AbortBroadcaster::configure(const std::string& broadcast_ip, uint16_t port,
37 uint32_t done_delay_ms, const std::string& bind_address) {
38 struct in_addr parsed{};
39 if (inet_pton(AF_INET, broadcast_ip.c_str(), &parsed) != 1) {
40 std::cerr << "[AbortBroadcaster] invalid broadcast address '" << broadcast_ip << "'"
41 << std::endl;
42 return false;
43 }
44 dest_addr_ = parsed;
45 dest_ip_ = broadcast_ip;
46 port_ = port;
47 abort_done_delay_ms_ = done_delay_ms;
48 bind_address_ = bind_address;
49 // The limited broadcast is the one destination that does not resolve to a single route, so a
50 // config that omits the key keeps the defect this class was fixed for. Do not silently
51 // upgrade it to a subnet-directed guess — [server_heartbeat].broadcast_ip is also what
52 // daq_bridge sends SERVER_HEARTBEAT to, and inventing a subnet here would move that too.
53 if (dest_addr_.s_addr == INADDR_BROADCAST)
54 std::cerr << "[AbortBroadcaster] WARNING: aborting to the limited broadcast "
55 "255.255.255.255 — set [server_heartbeat].broadcast_ip to the board subnet "
56 "(e.g. 192.168.2.255) or the kernel picks the egress interface"
57 << std::endl;
58
59 std::cout << "[AbortBroadcaster] ABORT/ABORT_DONE/CLEAR_ABORT -> " << dest_ip_ << ":" << port_
60 << " (from "
61 << (bind_address_ == "0.0.0.0" ? std::string("any interface") : bind_address_)
62 << ", ABORT_DONE after " << abort_done_delay_ms_ << " ms)" << std::endl;
63 return true;
64}
65
67 std::lock_guard<std::mutex> lk(done_thread_mutex_);
69 if (done_thread_.joinable())
70 done_thread_.join();
71}
72
73// ─────────────────────────────────────────────────────────────────────────────
74void AbortBroadcaster::sendPacket(uint8_t packet_type_byte) {
75 daq::PacketHeader hdr{};
76 hdr.packet_type = static_cast<daq::PacketType>(packet_type_byte);
77 hdr.version = 0; // DIABLO_COMMS_VERSION
78 hdr.timestamp = host_timestamp_ms();
79
80 int sock = socket(AF_INET, SOCK_DGRAM, 0);
81 if (sock < 0) {
82 std::cerr << "[AbortBroadcaster] socket() failed" << std::endl;
83 return;
84 }
85
86 int broadcast = 1;
87 setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
88
89 // Pin the egress NIC. A failure here is not fatal — an abort that leaves on an unknown
90 // interface still beats no abort at all — but it must be visible.
91 if (!fsw::net::bindToDaqInterface(sock, bind_address_, "AbortBroadcaster"))
92 std::cerr << "[AbortBroadcaster] continuing unpinned — the broadcast may leave on the "
93 "wrong interface"
94 << std::endl;
95
96 // A blocked sendto here would stall the abort itself. Bound it: a broadcast that cannot be
97 // queued within 100 ms is a lost repeat, not a reason to stop sending the others.
98 struct timeval tv{.tv_sec = 0, .tv_usec = 100000};
99 setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
100
101 struct sockaddr_in dest{};
102 dest.sin_family = AF_INET;
103 dest.sin_port = htons(port_);
104 dest.sin_addr = dest_addr_;
105
106 const char* type_name = (packet_type_byte == 7) ? "ABORT"
107 : (packet_type_byte == 8) ? "ABORT_DONE"
108 : (packet_type_byte == 9) ? "CLEAR_ABORT"
109 : "UNKNOWN";
110
111 // Repeat on one socket rather than sending once. Each repeat is independent: a failure is
112 // logged with errno and the remaining repeats still go out.
113 int delivered = 0;
114 for (int i = 0; i < kBroadcastRepeats; ++i) {
115 ssize_t sent = sendto(sock, &hdr, sizeof(hdr), 0, reinterpret_cast<struct sockaddr*>(&dest),
116 sizeof(dest));
117 if (sent == static_cast<ssize_t>(sizeof(hdr)))
118 ++delivered;
119 else
120 std::cerr << "[AbortBroadcaster] sendto(" << type_name << ") repeat " << (i + 1)
121 << " failed: " << strerror(errno) << std::endl;
122 if (i + 1 < kBroadcastRepeats)
123 usleep(kBroadcastGapUs);
124 }
125 close(sock);
126
127 if (delivered > 0)
128 std::cout << "[AbortBroadcaster] Sent " << type_name << " broadcast (" << dest_ip_ << ":"
129 << port_ << ", " << delivered << "/" << kBroadcastRepeats << " repeats)"
130 << std::endl;
131 else
132 std::cerr << "[AbortBroadcaster] " << type_name << " broadcast FAILED — no repeat left the "
133 << "host" << std::endl;
134}
135
136// ─────────────────────────────────────────────────────────────────────────────
138 // Immediate ABORT — before taking any lock. This is the boards' last line of defence and it
139 // must not queue behind a previous abort's bookkeeping.
140 sendPacket(static_cast<uint8_t>(daq::PacketType::ABORT));
141
142 // Cancel any previously scheduled ABORT_DONE thread, then schedule a fresh one. Both halves
143 // must be under the same lock: a second abort landing between the join and the assignment
144 // would move onto a joinable thread and terminate the process.
145 std::lock_guard<std::mutex> lk(done_thread_mutex_);
146
147 done_thread_running_ = false;
148 if (done_thread_.joinable()) {
149 if (done_thread_.get_id() == std::this_thread::get_id())
150 done_thread_.detach(); // cannot join self; it is returning anyway
151 else
152 done_thread_.join();
153 }
154
155 // Schedule ABORT_DONE
157 done_thread_ = std::thread([this]() {
159 });
160}
161
163 const auto delay = std::chrono::milliseconds(abort_done_delay_ms_);
164 const auto step = std::chrono::milliseconds(50);
165 auto elapsed = std::chrono::milliseconds(0);
166
167 while (done_thread_running_ && elapsed < delay) {
168 std::this_thread::sleep_for(step);
169 elapsed += step;
170 }
172 sendPacket(static_cast<uint8_t>(daq::PacketType::ABORT_DONE));
173}
174
178
179} // namespace sequencer
void delay(int ms)
void sendPacket(uint8_t packet_type)
void triggerAbort()
Send ABORT broadcast and schedule ABORT_DONE after abort_done_delay_ms.
std::atomic< bool > done_thread_running_
static constexpr int kBroadcastGapUs
static constexpr int kBroadcastRepeats
How many times each broadcast is repeated, and the gap between repeats.
bool configure(const std::string &broadcast_ip, uint16_t port, uint32_t done_delay_ms, const std::string &bind_address)
Adopt the destination and source NIC from config.
AbortBroadcaster(uint16_t port=5005, uint32_t abort_done_delay_ms=3000)
void sendClearAbort()
Send CLEAR_ABORT broadcast (called when leaving abort states).
static const emmc_port_t port
Definition emmc_stm32.c:210
PacketType
Defines the type of data contained in a packet.
Definition DiabloEnums.h:10
bool bindToDaqInterface(int sock, const std::string &address, const char *who)
bind() an already-created socket to address on an ephemeral port.
static bool elapsed(uint32_t now, uint32_t then, uint32_t interval)
Definition self_test.c:10