INSTINCT Code Coverage Report


Directory: src/
File: Nodes/Experimental/DataProvider/IMU/NetworkStream/SkydelNetworkStream.cpp
Date: 2025-11-25 23:34:18
Exec Total Coverage
Lines: 0 160 0.0%
Functions: 0 13 0.0%
Branches: 0 188 0.0%

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 "SkydelNetworkStream.hpp"
10
11 #include <thread>
12 #include <string>
13 #include <vector>
14 #include <chrono>
15 #include <cmath>
16 #include <sstream>
17
18 #include "util/Logger.hpp"
19 #include "internal/FlowManager.hpp"
20 #include "NodeData/IMU/ImuObs.hpp"
21 #include "util/Time/TimeBase.hpp"
22 #include "Navigation/Transformations/CoordinateFrames.hpp"
23 #include "NodeData/State/PosVelAtt.hpp"
24 #include "internal/gui/widgets/HelpMarker.hpp"
25
26 using boost::asio::ip::udp;
27
28 namespace NAV::experimental
29 {
30
31 SkydelNetworkStream::SkydelNetworkStream()
32 : Imu(typeStatic()), _senderEndpoint(udp::v4(), 4444), _socket(_ioservice, _senderEndpoint)
33 {
34 _onlyRealTime = true;
35 _hasConfig = true;
36 _guiConfigDefaultWindowSize = { 305, 70 };
37
38 CreateOutputPin("ImuObs", Pin::Type::Flow, { NAV::ImuObs::type() });
39 CreateOutputPin("PosVelAtt", Pin::Type::Flow, { NAV::PosVelAtt::type() });
40 }
41
42 SkydelNetworkStream::~SkydelNetworkStream()
43 {
44 LOG_TRACE("{}: called", nameId());
45 }
46
47 std::string SkydelNetworkStream::typeStatic()
48 {
49 return "SkydelNetworkStream";
50 }
51
52 std::string SkydelNetworkStream::type() const
53 {
54 return typeStatic();
55 }
56
57 std::string SkydelNetworkStream::category()
58 {
59 return "Data Provider";
60 }
61
62 void SkydelNetworkStream::guiConfig()
63 {
64 std::string str;
65
66 if (_startCounter < _startNow)
67 {
68 str = "(loading)";
69 }
70 else
71 {
72 std::ostringstream strs;
73 strs << _dataRate;
74 str = strs.str();
75 }
76
77 ImGui::LabelText(str.c_str(), "data rate [Hz]");
78 ImGui::SameLine();
79 gui::widgets::HelpMarker("The data rate can be adjusted in Skydel: Settings/Plug-ins/<Plug-in-name>/Plug-in UI. Make sure to enable either WiFi or a LAN connection. Enabling both can lead to loss of data, because Skydel only knows one ip address.");
80 }
81
82 bool SkydelNetworkStream::resetNode()
83 {
84 return true;
85 }
86
87 void SkydelNetworkStream::do_receive()
88 {
89 _socket.async_receive_from(
90 boost::asio::buffer(_data, _maxLength), _senderEndpoint,
91 [this](boost::system::error_code errorRcvd, std::size_t bytesRcvd) {
92 if ((!errorRcvd) && (bytesRcvd > 0))
93 {
94 // Splitting the incoming string analogous to 'ImuFile.cpp'
95 std::stringstream lineStream(std::string(_data.begin(), _data.end()));
96 std::string cell;
97 auto obsG = std::make_shared<PosVelAtt>();
98 auto obs = std::make_shared<ImuObs>(this->_imuPos);
99
100 // Inits for simulated measurement variables
101 uint64_t timeSinceStartup = 0;
102
103 double posX = 0.0;
104 double posY = 0.0;
105 double posZ = 0.0;
106 double attRoll = 0.0;
107 double attPitch = 0.0;
108 double attYaw = 0.0;
109
110 double accelX = 0.0;
111 double accelY = 0.0;
112 double accelZ = 0.0;
113 double gyroX = 0.0;
114 double gyroY = 0.0;
115 double gyroZ = 0.0;
116
117 for (size_t i = 0; i < 13; i++)
118 {
119 // Reading string from csv
120 if (std::getline(lineStream, cell, ','))
121 {
122 switch (i)
123 {
124 case 0:
125 timeSinceStartup = static_cast<uint64_t>(std::stod(cell) * 1e6); // [ns] = [ms] * 1e6
126 break;
127 case 1:
128 posX = std::stod(cell);
129 break;
130 case 2:
131 posY = std::stod(cell);
132 break;
133 case 3:
134 posZ = std::stod(cell);
135 break;
136 case 4:
137 attRoll = std::stod(cell);
138 break;
139 case 5:
140 attPitch = std::stod(cell);
141 break;
142 case 6:
143 attYaw = std::stod(cell);
144 break;
145 case 7:
146 accelX = std::stod(cell);
147 break;
148 case 8:
149 accelY = std::stod(cell);
150 break;
151 case 9:
152 accelZ = std::stod(cell);
153 break;
154 case 10:
155 gyroX = std::stod(cell);
156 break;
157 case 11:
158 gyroY = std::stod(cell);
159 break;
160 case 12:
161 gyroZ = std::stod(cell);
162 break;
163
164 default:
165 LOG_ERROR("Error in network stream: Cell index is out of bounds");
166 break;
167 }
168 }
169 else
170 {
171 LOG_ERROR("Error in IMU stream: Reading a string from csv failed");
172 return;
173 }
174 }
175
176 // Set GNSS values
177 Eigen::Vector3d e_position{ posX, posY, posZ };
178 Eigen::Vector3d lla_position = trafo::ecef2lla_WGS84(e_position);
179 Eigen::Quaterniond e_Quat_b;
180 e_Quat_b = trafo::e_Quat_n(lla_position(0), lla_position(1)) * trafo::n_Quat_b(attRoll, attPitch, attYaw);
181
182 obsG->setPosition_e(e_position);
183 Eigen::Vector3d velDummy{ 0, 0, 0 }; // TODO: Add velocity output in Skydel API and NetStream
184 obsG->setVelocity_e(velDummy);
185 obsG->setAttitude_e_Quat_b(e_Quat_b); // Attitude MUST BE set after Position, because the n- to e-sys trafo depends on lla_position
186
187 // Set IMU values
188 obs->p_acceleration = { accelX, accelY, accelZ };
189 obs->p_angularRate = { gyroX, gyroY, gyroZ };
190 // TODO: Add magnetometer model to Skydel API 'InstinctDataStream'
191
192 InsTime currentTime = util::time::GetCurrentInsTime();
193 if (!currentTime.empty())
194 {
195 obs->insTime = currentTime;
196 obsG->insTime = currentTime;
197 }
198
199 if (_lastMessageTime)
200 {
201 // FIXME: This seems like a bug in clang-tidy. Check if it is working in future versions of clang-tidy
202 // NOLINTNEXTLINE(hicpp-use-nullptr, modernize-use-nullptr)
203 if (timeSinceStartup - _lastMessageTime >= static_cast<uint64_t>(1.5 / _dataRate * 1e9))
204 {
205 LOG_WARN("{}: Potentially lost a message. Previous message was at {} and current message at {} which is a time difference of {} seconds.", nameId(),
206 _lastMessageTime, timeSinceStartup, static_cast<double>(timeSinceStartup - _lastMessageTime) * 1e-9);
207 }
208 }
209 _lastMessageTime = timeSinceStartup;
210
211 this->invokeCallbacks(OUTPUT_PORT_INDEX_GNSS_OBS, obsG);
212 this->invokeCallbacks(OUTPUT_PORT_INDEX_IMU_OBS, obs);
213
214 // Data rate (for visualization in GUI)
215 _packageCount++;
216
217 if (_startCounter < _startNow)
218 {
219 _packageCount = 0;
220 _startCounter++;
221 }
222
223 if (_packageCount == 1)
224 {
225 _startPoint = std::chrono::steady_clock::now();
226 }
227 else if (_packageCount == _packagesNumber)
228 {
229 std::chrono::duration<double> elapsed_seconds = std::chrono::steady_clock::now() - _startPoint;
230 _dataRate = static_cast<double>(_packagesNumber - 1) / elapsed_seconds.count();
231
232 // Dynamic adaptation of data rate to a human-readable display update rate in GUI (~ 1 Hz)
233 if ((_dataRate > 2) && (_dataRate < 1001)) // restriction on 'reasonable' sensor data rates (Skydel max. is 1000 Hz)
234 {
235 _packagesNumber = static_cast<int>(_dataRate);
236 }
237 else if (_dataRate >= 1001)
238 {
239 _packagesNumber = 1000;
240 }
241
242 _packageCount = 0;
243
244 LOG_DATA("Elapsed Seconds = {}", elapsed_seconds.count());
245 LOG_DATA("SkydelNetworkStream: dataRate = {}", _dataRate);
246 }
247 }
248 else
249 {
250 LOG_ERROR("Error receiving the network stream from Skydel");
251 }
252
253 if (!_stop)
254 {
255 do_receive();
256 }
257 });
258 }
259
260 bool SkydelNetworkStream::initialize()
261 {
262 LOG_TRACE("{}: called", nameId());
263
264 _stop = false;
265 _packageCount = 0;
266 _startCounter = 0;
267 _packagesNumber = 2;
268
269 _lastMessageTime = 0;
270
271 do_receive();
272
273 if (_isStartup)
274 {
275 _testThread = std::thread([this]() {
276 _ioservice.run();
277 });
278 }
279 else
280 {
281 _testThread = std::thread([this]() {
282 _ioservice.restart();
283 _ioservice.run();
284 });
285 }
286
287 _isStartup = false;
288
289 return true;
290 }
291
292 void SkydelNetworkStream::deinitialize()
293 {
294 LOG_TRACE("{}: called", nameId());
295
296 _stop = true;
297 _ioservice.stop();
298 _testThread.join();
299 }
300
301 } // namespace NAV::experimental
302