INSTINCT Code Coverage Report


Directory: src/
File: Nodes/DataProvider/WiFi/Sensors/ArubaSensor.cpp
Date: 2025-11-25 23:34:18
Exec Total Coverage
Lines: 13 187 7.0%
Functions: 4 12 33.3%
Branches: 12 294 4.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 "ArubaSensor.hpp"
10
11 #include "util/Time/TimeBase.hpp"
12 #include "util/Logger.hpp"
13 #include <libssh/libssh.h>
14 #include <regex>
15 #include <sstream>
16
17 #include "internal/FlowManager.hpp"
18
19 #include "NodeData/WiFi/WiFiObs.hpp"
20
21 114 NAV::ArubaSensor::ArubaSensor()
22
6/12
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 114 times.
✗ Branch 5 not taken.
✓ Branch 9 taken 114 times.
✗ Branch 10 not taken.
✓ Branch 14 taken 114 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 114 times.
✗ Branch 18 not taken.
✓ Branch 20 taken 114 times.
✗ Branch 21 not taken.
912 : Node(typeStatic())
23 {
24 LOG_TRACE("{}: called", name);
25
26 114 _onlyRealTime = true;
27 114 _hasConfig = true;
28 114 _guiConfigDefaultWindowSize = { 710, 220 };
29
30
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("WiFiObs", Pin::Type::Flow, { NAV::WiFiObs::type() });
31 228 }
32
33 228 NAV::ArubaSensor::~ArubaSensor()
34 {
35 LOG_TRACE("{}: called", nameId());
36 228 }
37
38 228 std::string NAV::ArubaSensor::typeStatic()
39 {
40
1/2
✓ Branch 1 taken 228 times.
✗ Branch 2 not taken.
456 return "ArubaSensor";
41 }
42
43 ✗ std::string NAV::ArubaSensor::type() const
44 {
45 ✗ return typeStatic();
46 }
47
48 114 std::string NAV::ArubaSensor::category()
49 {
50
1/2
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
228 return "Data Provider";
51 }
52
53 ✗ void NAV::ArubaSensor::guiConfig()
54 {
55 ✗ if (ImGui::InputTextWithHint("SSH Host", "192.168.0.0", &_sshHost))
56 {
57 ✗ LOG_DEBUG("{}: ssh host changed to {}", nameId(), _sshHost);
58 ✗ flow::ApplyChanges();
59 ✗ doDeinitialize();
60 }
61 ✗ if (ImGui::InputTextWithHint("SSH User", "admin", &_sshUser))
62 {
63 ✗ LOG_DEBUG("{}: ssh admin changed to {}", nameId(), _sshUser);
64 ✗ flow::ApplyChanges();
65 ✗ doDeinitialize();
66 }
67 ✗ if (ImGui::InputText("SSH Password", &_sshPassword))
68 {
69 ✗ LOG_DEBUG("{}: ssh password changed to {}", nameId(), _sshPassword);
70 ✗ flow::ApplyChanges();
71 ✗ doDeinitialize();
72 }
73 ✗ if (ImGui::InputText("SSH Host Keys", &_sshHostkeys))
74 {
75 ✗ LOG_DEBUG("{}: ssh host keys changed to {}", nameId(), _sshHostkeys);
76 ✗ flow::ApplyChanges();
77 ✗ doDeinitialize();
78 }
79 ✗ if (ImGui::InputText("SSH Key Exchange", &_sshKeyExchange))
80 {
81 ✗ LOG_DEBUG("{}: ssh key exchange changed to {}", nameId(), _sshKeyExchange);
82 ✗ flow::ApplyChanges();
83 ✗ doDeinitialize();
84 }
85 ✗ if (ImGui::InputText("SSH Publickey Accepted Types", &_sshPublickeyAcceptedTypes))
86 {
87 ✗ LOG_DEBUG("{}: ssh publickey accepted types changed to {}", nameId(), _sshPublickeyAcceptedTypes);
88 ✗ flow::ApplyChanges();
89 ✗ doDeinitialize();
90 }
91 ✗ ImGui::Spacing(); // Add spacing here
92 ✗ ImGui::Separator(); // Add a horizontal line
93 ✗ if (ImGui::InputInt("Output interval [ms]", &_outputInterval))
94 {
95 ✗ LOG_DEBUG("{}: output interval changed to {}", nameId(), _outputInterval);
96 ✗ flow::ApplyChanges();
97 }
98 ✗ }
99
100 ✗ [[nodiscard]] json NAV::ArubaSensor::save() const
101 {
102 LOG_TRACE("{}: called", nameId());
103
104 ✗ json j;
105
106 ✗ j["sshHost"] = _sshHost;
107 ✗ j["sshUser"] = _sshUser;
108 ✗ j["sshPassword"] = _sshPassword;
109 ✗ j["sshHostkeys"] = _sshHostkeys;
110 ✗ j["sshKeyExchange"] = _sshKeyExchange;
111 ✗ j["sshPublickeyAcceptedTypes"] = _sshPublickeyAcceptedTypes;
112 ✗ j["outputInterval"] = _outputInterval;
113
114 ✗ return j;
115 ✗ }
116
117 ✗ void NAV::ArubaSensor::restore(json const& j)
118 {
119 LOG_TRACE("{}: called", nameId());
120
121 ✗ if (j.contains("sshHost"))
122 {
123 ✗ j.at("sshHost").get_to(_sshHost);
124 }
125 ✗ if (j.contains("sshUser"))
126 {
127 ✗ j.at("sshUser").get_to(_sshUser);
128 }
129 ✗ if (j.contains("sshPassword"))
130 {
131 ✗ j.at("sshPassword").get_to(_sshPassword);
132 }
133 ✗ if (j.contains("sshHostkeys"))
134 {
135 ✗ j.at("sshHostkeys").get_to(_sshHostkeys);
136 }
137 ✗ if (j.contains("sshKeyExchange"))
138 {
139 ✗ j.at("sshKeyExchange").get_to(_sshKeyExchange);
140 }
141 ✗ if (j.contains("sshPublickeyAcceptedTypes"))
142 {
143 ✗ j.at("sshPublickeyAcceptedTypes").get_to(_sshPublickeyAcceptedTypes);
144 }
145 ✗ if (j.contains("outputInterval"))
146 {
147 ✗ j.at("outputInterval").get_to(_outputInterval);
148 }
149 ✗ }
150
151 ✗ bool NAV::ArubaSensor::resetNode()
152 {
153 ✗ return true;
154 }
155
156 ✗ bool NAV::ArubaSensor::initialize()
157 {
158 LOG_TRACE("{}: called", nameId());
159
160 // SSH session
161 ✗ _session = ssh_new();
162 ✗ if (_session == nullptr)
163 {
164 ✗ LOG_INFO("{}: Failed to create SSH _session", nameId());
165 ✗ return false;
166 }
167 ✗ LOG_DEBUG("{}: Successfully created SSH _session", nameId());
168
169 ✗ ssh_options_set(_session, SSH_OPTIONS_HOST, "192.168.178.45");
170 ✗ ssh_options_set(_session, SSH_OPTIONS_USER, "admin");
171 ✗ ssh_options_set(_session, SSH_OPTIONS_HOSTKEYS, "ssh-rsa");
172 ✗ ssh_options_set(_session, SSH_OPTIONS_KEY_EXCHANGE, "ecdh-sha2-nistp256");
173 ✗ ssh_options_set(_session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, "ssh-rsa");
174
175 // connect
176 ✗ if (ssh_connect(_session) != SSH_OK)
177 {
178 ✗ LOG_INFO("{}: Failed to connect to the router", nameId());
179 ✗ ssh_free(_session);
180 ✗ return false;
181 }
182 ✗ LOG_DEBUG("{}: Successfully connected to the router", nameId());
183
184 // authenticate
185 ✗ if (ssh_userauth_password(_session, nullptr, _sshPassword.c_str()) != SSH_AUTH_SUCCESS)
186 {
187 ✗ LOG_INFO("{}: Authentication failed", nameId());
188 ✗ ssh_disconnect(_session);
189 ✗ ssh_free(_session);
190 ✗ return false;
191 }
192 ✗ LOG_DEBUG("{}: Authentication succeeded", nameId());
193
194 // channel
195 ✗ _channel = ssh_channel_new(_session);
196 ✗ if (_channel == nullptr)
197 {
198 ✗ LOG_INFO("{}: Failed to create SSH channel", nameId());
199 ✗ ssh_disconnect(_session);
200 ✗ ssh_free(_session);
201 ✗ return false;
202 }
203 ✗ LOG_DEBUG("{}: Successfully created SSH channel", nameId());
204
205 // open _session
206 ✗ if (ssh_channel_open_session(_channel) != SSH_OK)
207 {
208 ✗ LOG_INFO("{}: Failed to open an SSH _session", nameId());
209 ✗ ssh_channel_free(_channel);
210 ✗ ssh_disconnect(_session);
211 ✗ ssh_free(_session);
212 ✗ return false;
213 }
214 ✗ LOG_DEBUG("{}: Successfully opened an SSH _session", nameId());
215
216 // pty
217 ✗ if (ssh_channel_request_pty(_channel) != SSH_OK)
218 {
219 ✗ LOG_INFO("{}: Failed to open pty", nameId());
220 ✗ ssh_channel_free(_channel);
221 ✗ ssh_disconnect(_session);
222 ✗ ssh_free(_session);
223 ✗ return false;
224 }
225 ✗ LOG_DEBUG("{}: Successfully opened pty", nameId());
226
227 // shell
228 ✗ if (ssh_channel_request_shell(_channel) != SSH_OK)
229 {
230 ✗ LOG_INFO("{}: Failed to open shell", nameId());
231 ✗ ssh_channel_free(_channel);
232 ✗ ssh_disconnect(_session);
233 ✗ ssh_free(_session);
234 ✗ return false;
235 }
236 ✗ LOG_DEBUG("{}: Successfully opened shell", nameId());
237
238 ✗ _timer.start(_outputInterval, readSensorDataThread, this);
239
240 ✗ return true;
241 }
242
243 ✗ void NAV::ArubaSensor::deinitialize()
244 {
245 LOG_TRACE("{}: called", nameId());
246
247 ✗ if (!isInitialized())
248 {
249 ✗ return;
250 }
251
252 ✗ ssh_channel_write(_channel, "exit\n", strlen("exit\n"));
253 ✗ ssh_channel_free(_channel);
254 ✗ ssh_disconnect(_session);
255 ✗ ssh_free(_session);
256
257 ✗ if (_timer.is_running())
258 {
259 ✗ _timer.stop();
260 }
261 }
262
263 ✗ void NAV::ArubaSensor::readSensorDataThread(void* userData)
264 {
265 ✗ auto* node = static_cast<ArubaSensor*>(userData);
266 ✗ auto obs = std::make_shared<WiFiObs>();
267
268 ✗ std::array<char, 1024> buffer{};
269 ✗ std::string receivedData;
270 ✗ size_t bytesRead = 0;
271 // Send command to access point
272 ✗ ssh_channel_write(node->_channel, "show ap range scanning-results\n", strlen("show ap range scanning-results\n"));
273 // Read output
274 do {
275 ✗ bytesRead = static_cast<size_t>(ssh_channel_read_timeout(node->_channel, buffer.data(), sizeof(buffer), 0, 10)); // timeout in ms
276 ✗ if (bytesRead > 0)
277 {
278 ✗ receivedData.append(buffer.data(), bytesRead);
279 }
280 ✗ } while (bytesRead > 0);
281 // Send command to clear output
282 ✗ ssh_channel_write(node->_channel, "clear range-scanning-result\n", strlen("clear range-scanning-result\n"));
283
284 // Parse the received data
285 ✗ std::istringstream iss(receivedData);
286 ✗ std::string line;
287
288 ✗ while (std::getline(iss, line) && line.find("Peer-bssid") == std::string::npos) {} // Skip lines until the header "Peer-bssid" is found
289
290 // Skip the header lines
291 ✗ std::getline(iss, line);
292 ✗ std::getline(iss, line);
293
294 // MAC address validation
295 ✗ std::regex macRegex("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$");
296
297 ✗ while (std::getline(iss, line) && !line.empty())
298 {
299 ✗ std::istringstream lineStream(line);
300 ✗ std::string value;
301 ✗ std::string macAddress;
302 ✗ lineStream >> macAddress;
303
304 ✗ int rtt = 0;
305 ✗ int rssi = 0;
306 ✗ int stdValue = 0;
307
308 ✗ lineStream >> rtt >> rssi >> stdValue;
309
310 ✗ std::regex timeRegex(R"(\d{4}-\d{2}-\d{2})"); // Time format: YYYY-MM-DD
311 ✗ std::string timeStamp1;
312 ✗ std::string timeStamp2;
313 ✗ lineStream >> timeStamp1;
314 ✗ lineStream >> timeStamp2;
315 ✗ while (lineStream >> value)
316 {
317 ✗ if (std::regex_match(value, timeRegex)) // Check if the value is a time stamp
318 {
319 ✗ timeStamp1 = value;
320 ✗ break;
321 }
322 }
323 ✗ lineStream >> value;
324 ✗ timeStamp2 = value;
325 ✗ double measuredDistance = static_cast<double>(rtt) * 1e-9 / 2 * InsConst::C_AIR;
326 ✗ double measuredDistanceStd = static_cast<double>(stdValue) * 1e-9 / 2 * InsConst::C_AIR;
327 ✗ if (std::regex_match(macAddress, macRegex)) // Check if the MAC address is valid
328 {
329 ✗ InsTime_YMDHMS yearMonthDayHMS(std::stoi(timeStamp1.substr(0, 4)), std::stoi(timeStamp1.substr(5, 2)), std::stoi(timeStamp1.substr(8, 2)), std::stoi(timeStamp2.substr(0, 2)), std::stoi(timeStamp2.substr(3, 2)), std::stoi(timeStamp2.substr(6, 2)));
330 ✗ InsTime timeOfMeasurement(yearMonthDayHMS, UTC);
331 ✗ std::ranges::transform(macAddress, macAddress.begin(), ::toupper); // Convert to uppercase
332 ✗ obs->distance = measuredDistance;
333 ✗ obs->distanceStd = measuredDistanceStd;
334 ✗ obs->macAddress = macAddress;
335 ✗ obs->insTime = timeOfMeasurement;
336 // obs->insTime = util::time::GetCurrentInsTime(); // if you want to use the instinct time instead
337 ✗ node->invokeCallbacks(OUTPUT_PORT_INDEX_WIFI_OBS, obs);
338 }
339 ✗ }
340 ✗ }
341