INSTINCT Code Coverage Report


Directory: src/
File: Nodes/DataLink/udpRecv.cpp
Date: 2025-11-25 23:34:18
Exec Total Coverage
Lines: 13 177 7.3%
Functions: 4 16 25.0%
Branches: 10 320 3.1%

Line Branch Exec Source
1 // This file is part of INSTINCT, the INS Toolkit for Integrated
2 // Navigation Concepts and Training by the Institute of Navigation of
3 // the University of Stuttgart, Germany.
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9 #include "udpRecv.hpp"
10
11 #include "Nodes/DataLink/UdpUtil.hpp"
12 #include "NodeData/GNSS/GnssObs.hpp"
13 #include "NodeData/State/PosVelAtt.hpp"
14 #include "Navigation/GNSS/Core/SatelliteIdentifier.hpp"
15 #include "Navigation/Time/InsTime.hpp"
16
17 #include "util/Assert.h"
18 #include "internal/FlowManager.hpp"
19
20 #include "util/Logger.hpp"
21 #include "internal/gui/widgets/imgui_ex.hpp"
22 #include "internal/gui/widgets/EnumCombo.hpp"
23 #include "internal/gui/NodeEditorApplication.hpp"
24
25 #include <cstdint>
26 #include <cstring>
27 #include <memory>
28 #include <boost/system/detail/error_code.hpp>
29
30 114 NAV::UdpRecv::UdpRecv()
31
4/8
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 114 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 114 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 114 times.
✗ Branch 12 not taken.
114 : Node(typeStatic()), _socket(_io_context)
32 {
33 LOG_TRACE("{}: called", name);
34
35 114 _onlyRealTime = true;
36 114 _hasConfig = true;
37 114 _guiConfigDefaultWindowSize = { 261, 95 };
38
39
4/8
✓ Branch 2 taken 114 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 114 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 114 times.
✓ Branch 10 taken 114 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
456 CreateOutputPin("Data", Pin::Type::Flow, { NAV::PosVelAtt::type() });
40 228 }
41
42 228 NAV::UdpRecv::~UdpRecv()
43 {
44 LOG_TRACE("{}: called", nameId());
45 228 }
46
47 228 std::string NAV::UdpRecv::typeStatic()
48 {
49
1/2
✓ Branch 1 taken 228 times.
✗ Branch 2 not taken.
456 return "UdpRecv";
50 }
51
52 ✗ std::string NAV::UdpRecv::type() const
53 {
54 ✗ return typeStatic();
55 }
56
57 114 std::string NAV::UdpRecv::category()
58 {
59
1/2
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
228 return "Data Link";
60 }
61
62 ✗ void NAV::UdpRecv::guiConfig()
63 {
64 ✗ ImGui::SetNextItemWidth(150 * gui::NodeEditorApplication::windowFontRatio());
65 ✗ if (ImGui::InputIntL(fmt::format("Port##{}", size_t(id)).c_str(), &_port, UdpUtil::PORT_LIMITS[0], UdpUtil::PORT_LIMITS[1]))
66 {
67 ✗ flow::ApplyChanges();
68 }
69 ✗ ImGui::SetNextItemWidth(150 * gui::NodeEditorApplication::windowFontRatio());
70 ✗ if (gui::widgets::EnumCombo(fmt::format("Output Type##{}", size_t(id)).c_str(), _outputType))
71 {
72 ✗ LOG_DEBUG("{}: Output Type changed to {}", nameId(), to_string(_outputType));
73 ✗ if (_outputType == UdpUtil::MessageType::PosVelAtt)
74 {
75 ✗ outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).dataIdentifier = { NAV::PosVelAtt::type() };
76 ✗ outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).name = NAV::PosVelAtt::type();
77 }
78 ✗ else if (_outputType == UdpUtil::MessageType::PosVel)
79 {
80 ✗ outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).dataIdentifier = { NAV::PosVel::type() };
81 ✗ outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).name = NAV::PosVel::type();
82 }
83 ✗ else if (_outputType == UdpUtil::MessageType::Pos)
84 {
85 ✗ outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).dataIdentifier = { NAV::Pos::type() };
86 ✗ outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).name = NAV::Pos::type();
87 }
88 ✗ else if (_outputType == UdpUtil::MessageType::GnssObs)
89 {
90 ✗ outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).dataIdentifier = { NAV::GnssObs::type() };
91 ✗ outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).name = NAV::GnssObs::type();
92 }
93
94 ✗ for (auto& link : outputPins.front().links)
95 {
96 ✗ if (auto* connectedPin = link.getConnectedPin())
97 {
98 ✗ outputPins.front().recreateLink(*connectedPin);
99 }
100 }
101
102 ✗ flow::ApplyChanges();
103 }
104 ✗ }
105
106 ✗ bool NAV::UdpRecv::resetNode()
107 {
108 ✗ return true;
109 }
110
111 ✗ json NAV::UdpRecv::save() const
112 {
113 LOG_TRACE("{}: called", nameId());
114
115 ✗ json j;
116 ✗ j["port"] = _port;
117 ✗ j["outputType"] = _outputType;
118
119 ✗ return j;
120 ✗ }
121
122 ✗ void NAV::UdpRecv::restore(json const& j)
123 {
124 LOG_TRACE("{}: called", nameId());
125 ✗ if (j.contains("port"))
126 {
127 ✗ j.at("port").get_to(_port);
128 }
129 ✗ if (j.contains("outputType"))
130 {
131 ✗ j.at("outputType").get_to(_outputType);
132
133 ✗ if (!outputPins.empty())
134 {
135 ✗ if (_outputType == UdpUtil::MessageType::PosVelAtt)
136 {
137 ✗ outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).dataIdentifier = { NAV::PosVelAtt::type() };
138 ✗ outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).name = NAV::PosVelAtt::type();
139 }
140 ✗ else if (_outputType == UdpUtil::MessageType::PosVel)
141 {
142 ✗ outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).dataIdentifier = { NAV::PosVel::type() };
143 ✗ outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).name = NAV::PosVel::type();
144 }
145 ✗ else if (_outputType == UdpUtil::MessageType::Pos)
146 {
147 ✗ outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).dataIdentifier = { NAV::Pos::type() };
148 ✗ outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).name = NAV::Pos::type();
149 }
150 ✗ else if (_outputType == UdpUtil::MessageType::GnssObs)
151 {
152 ✗ outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).dataIdentifier = { NAV::GnssObs::type() };
153 ✗ outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).name = NAV::GnssObs::type();
154 }
155 }
156 }
157 ✗ }
158
159 ✗ bool NAV::UdpRecv::initialize()
160 {
161 LOG_TRACE("{}: called", nameId());
162
163 try
164 {
165 ✗ _socket = boost::asio::ip::udp::socket(_io_context, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), static_cast<uint16_t>(_port)));
166 }
167 ✗ catch (const std::exception& /* e */)
168 {
169 ✗ LOG_ERROR("{}: Port {} is already in use. Choose a different port for this instance.", nameId(), _port);
170 ✗ return false;
171 ✗ }
172
173 ✗ _running = true;
174
175 ✗ asyncReceive();
176
177 ✗ if (_isStartup)
178 {
179 ✗ _recvThread = std::thread([this]() {
180 ✗ _io_context.run();
181 ✗ });
182 }
183 else
184 {
185 ✗ _recvThread = std::thread([this]() {
186 ✗ _io_context.restart();
187 ✗ _io_context.run();
188 ✗ });
189 }
190
191 ✗ _isStartup = false;
192
193 ✗ return true;
194 }
195
196 ✗ void NAV::UdpRecv::deinitialize()
197 {
198 ✗ _running = false;
199 ✗ _io_context.stop();
200 ✗ _recvThread.join();
201 ✗ _socket.close();
202
203 LOG_TRACE("{}: called", nameId());
204 ✗ }
205
206 ✗ void NAV::UdpRecv::asyncReceive()
207 {
208 ✗ _socket.async_receive_from(
209 ✗ boost::asio::buffer(_charArray, UdpUtil::MAXIMUM_BYTES), _sender_endpoint,
210 ✗ [this](boost::system::error_code errorRcvd, std::size_t bytesRcvd) {
211 ✗ if ((!errorRcvd) && (bytesRcvd > 0))
212 {
213 ✗ UdpUtil::MessageType msgType{};
214 ✗ std::memcpy(&msgType, _charArray.data(), UdpUtil::Size::MSGTYPE);
215 LOG_DATA("{}: Received {} bytes (message type {})", nameId(), bytesRcvd, fmt::underlying(msgType));
216
217 ✗ int32_t gpsCycle{};
218 ✗ int32_t gpsWeek{};
219 ✗ double gpsTow{};
220
221 ✗ std::memcpy(&gpsCycle, _charArray.data() + UdpUtil::Offset::GPSCYCLE, UdpUtil::Size::GPSCYCLE);
222 ✗ std::memcpy(&gpsWeek, _charArray.data() + UdpUtil::Offset::GPSWEEK, UdpUtil::Size::GPSWEEK);
223 ✗ std::memcpy(&gpsTow, _charArray.data() + UdpUtil::Offset::GPSTOW, UdpUtil::Size::GPSTOW);
224
225 ✗ if (msgType == UdpUtil::MessageType::PosVelAtt)
226 {
227 ✗ if (outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).name != NAV::PosVelAtt::type())
228 {
229 ✗ LOG_ERROR("{}: Change output type to 'PosVelAtt'!", nameId());
230 ✗ return;
231 }
232 ✗ auto obs = std::make_shared<PosVelAtt>();
233
234 ✗ obs->insTime = InsTime(gpsCycle, gpsWeek, gpsTow);
235
236 // Position in LLA coordinates
237 ✗ Eigen::Vector3d posLLA{};
238 ✗ std::memcpy(posLLA.data(), _charArray.data() + UdpUtil::Offset::POS, UdpUtil::Size::POS);
239
240 // Velocity in local frame
241 ✗ Eigen::Vector3d vel_n{};
242 ✗ std::memcpy(vel_n.data(), _charArray.data() + UdpUtil::Offset::VEL, UdpUtil::Size::VEL);
243
244 // Attitude
245 ✗ Eigen::Quaterniond n_Quat_b{};
246 ✗ std::memcpy(&n_Quat_b.x(), _charArray.data() + UdpUtil::Offset::QUAT, UdpUtil::Size::QUAT);
247 ✗ std::memcpy(&n_Quat_b.y(), _charArray.data() + UdpUtil::Offset::QUAT + UdpUtil::Size::QUAT, UdpUtil::Size::QUAT);
248 ✗ std::memcpy(&n_Quat_b.z(), _charArray.data() + UdpUtil::Offset::QUAT + 2 * UdpUtil::Size::QUAT, UdpUtil::Size::QUAT);
249 ✗ std::memcpy(&n_Quat_b.w(), _charArray.data() + UdpUtil::Offset::QUAT + 3 * UdpUtil::Size::QUAT, UdpUtil::Size::QUAT);
250
251 ✗ obs->setPosVelAtt_n(posLLA, vel_n, n_Quat_b);
252
253 ✗ this->invokeCallbacks(OUTPUT_PORT_INDEX_NODE_DATA, obs);
254 ✗ }
255 ✗ else if (msgType == UdpUtil::MessageType::PosVel)
256 {
257 ✗ if (outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).name != NAV::PosVel::type())
258 {
259 ✗ LOG_ERROR("{}: Change output type to 'PosVel'!", nameId());
260 ✗ return;
261 }
262 ✗ auto obs = std::make_shared<PosVel>();
263
264 ✗ obs->insTime = InsTime(gpsCycle, gpsWeek, gpsTow);
265
266 // Position in LLA coordinates
267 ✗ Eigen::Vector3d posLLA{};
268 ✗ std::memcpy(posLLA.data(), _charArray.data() + UdpUtil::Offset::POS, UdpUtil::Size::POS);
269
270 // Velocity in local frame
271 ✗ Eigen::Vector3d vel_n{};
272 ✗ std::memcpy(vel_n.data(), _charArray.data() + UdpUtil::Offset::VEL, UdpUtil::Size::VEL);
273
274 ✗ obs->setPosVel_n(posLLA, vel_n);
275
276 ✗ this->invokeCallbacks(OUTPUT_PORT_INDEX_NODE_DATA, obs);
277 ✗ }
278 ✗ else if (msgType == UdpUtil::MessageType::Pos)
279 {
280 ✗ if (outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).name != NAV::Pos::type())
281 {
282 ✗ LOG_ERROR("{}: Change output type to 'Pos'!", nameId());
283 ✗ return;
284 }
285 ✗ auto obs = std::make_shared<Pos>();
286
287 ✗ obs->insTime = InsTime(gpsCycle, gpsWeek, gpsTow);
288
289 // Position in LLA coordinates
290 ✗ Eigen::Vector3d posLLA{};
291 ✗ std::memcpy(posLLA.data(), _charArray.data() + UdpUtil::Offset::POS, UdpUtil::Size::POS);
292
293 ✗ obs->setPosition_lla(posLLA);
294
295 ✗ this->invokeCallbacks(OUTPUT_PORT_INDEX_NODE_DATA, obs);
296 ✗ }
297 ✗ else if (msgType == UdpUtil::MessageType::GnssObs)
298 {
299 ✗ if (outputPins.at(OUTPUT_PORT_INDEX_NODE_DATA).name != NAV::GnssObs::type())
300 {
301 ✗ LOG_ERROR("{}: Change output type to 'GnssObs'!", nameId());
302 ✗ return;
303 }
304 ✗ auto gnssObs = std::make_shared<GnssObs>();
305 ✗ gnssObs->insTime = InsTime(gpsCycle, gpsWeek, gpsTow);
306
307 ✗ size_t byteSizeGnssData{};
308 ✗ std::memcpy(&byteSizeGnssData, _charArray.data() + UdpUtil::Offset::SIZE, UdpUtil::Size::SIZE);
309 ✗ size_t sizeGnssData = byteSizeGnssData / UdpUtil::Size::SINGLE_OBSERVATION_DATA;
310 ✗ INS_ASSERT_USER_ERROR(byteSizeGnssData % UdpUtil::Size::SINGLE_OBSERVATION_DATA == 0,
311 "The UdpRecv node received a not dividable amount of bytes for the GnssObs data.");
312 LOG_DATA("{}: {} GNSS signals ({} bytes)", nameId(), sizeGnssData, byteSizeGnssData);
313 ✗ gnssObs->data.resize(sizeGnssData, GnssObs::ObservationData(SatSigId()));
314 ✗ std::memcpy(gnssObs->data.data(), _charArray.data() + UdpUtil::Offset::GNSSDATA, byteSizeGnssData);
315
316 ✗ this->invokeCallbacks(OUTPUT_PORT_INDEX_NODE_DATA, gnssObs);
317 ✗ }
318 else
319 {
320 ✗ LOG_ERROR("{}: Data type not receivable, yet.", nameId());
321 }
322 }
323 else
324 {
325 ✗ LOG_ERROR("{}: Error receiving the UDP network stream: {}, Received bytes: {}", nameId(), errorRcvd.what(), bytesRcvd);
326 }
327
328 ✗ if (_running)
329 {
330 ✗ asyncReceive();
331 }
332 });
333 ✗ }
334
335 ✗ const char* NAV::to_string(NAV::UdpUtil::MessageType value)
336 {
337 ✗ switch (value)
338 {
339 ✗ case NAV::UdpUtil::MessageType::PosVelAtt:
340 ✗ return "PosVelAtt";
341 ✗ case NAV::UdpUtil::MessageType::PosVel:
342 ✗ return "PosVel";
343 ✗ case NAV::UdpUtil::MessageType::Pos:
344 ✗ return "Pos";
345 ✗ case NAV::UdpUtil::MessageType::GnssObs:
346 ✗ return "GnssObs";
347 ✗ case NAV::UdpUtil::MessageType::COUNT:
348 ✗ return "";
349 }
350 ✗ return "";
351 }
352