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