• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mavlink / MAVSDK / 19020749455

03 Nov 2025 12:47AM UTC coverage: 48.156% (-0.02%) from 48.175%
19020749455

push

github

web-flow
Merge pull request #2673 from mavlink/fix-third-parties

dependencies: libevents should be built with the superbuild

17432 of 36199 relevant lines covered (48.16%)

473.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.28
/src/system_tests/mavlink_direct.cpp
1
#include "log.h"
2
#include "mavsdk.h"
3
#include "plugins/mavlink_direct/mavlink_direct.h"
4
#include "plugins/telemetry/telemetry.h"
5
#include "plugins/telemetry_server/telemetry_server.h"
6
#include <chrono>
7
#include <thread>
8
#include <future>
9
#include <gtest/gtest.h>
10
#include <json/json.h>
11

12
using namespace mavsdk;
13

14
TEST(SystemTest, MavlinkDirectRoundtrip)
4✔
15
{
16
    Mavsdk mavsdk_groundstation{Mavsdk::Configuration{ComponentType::GroundStation}};
1✔
17
    Mavsdk mavsdk_autopilot{Mavsdk::Configuration{ComponentType::Autopilot}};
1✔
18

19
    ASSERT_EQ(
1✔
20
        mavsdk_groundstation.add_any_connection("udpin://0.0.0.0:17000"),
21
        ConnectionResult::Success);
1✔
22
    ASSERT_EQ(
1✔
23
        mavsdk_autopilot.add_any_connection("udpout://127.0.0.1:17000"), ConnectionResult::Success);
1✔
24

25
    // Ground station discovers the autopilot system
26
    auto maybe_system = mavsdk_groundstation.first_autopilot(10.0);
1✔
27
    ASSERT_TRUE(maybe_system);
1✔
28
    auto system = maybe_system.value();
1✔
29
    ASSERT_TRUE(system->has_autopilot());
1✔
30

31
    // Wait for autopilot instance to discover the connection to the ground station
32
    LogInfo() << "Waiting for autopilot system to connect...";
1✔
33
    while (mavsdk_autopilot.systems().size() == 0) {
2✔
34
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
35
    }
36

37
    // Get the autopilot's view of the connected system
38
    auto groundstation_system = mavsdk_autopilot.systems().at(0);
1✔
39
    ASSERT_EQ(groundstation_system->component_ids()[0], 190);
2✔
40

41
    // Create separate MavlinkDirect instances for sender (autopilot) and receiver (ground station)
42
    auto receiver_mavlink_direct = MavlinkDirect{system};
1✔
43
    auto sender_mavlink_direct = MavlinkDirect{groundstation_system};
1✔
44

45
    auto prom = std::promise<MavlinkDirect::MavlinkMessage>();
1✔
46
    auto fut = prom.get_future();
1✔
47

48
    // Ground station subscribes to receive GLOBAL_POSITION_INT from autopilot
49
    auto handle = receiver_mavlink_direct.subscribe_message(
1✔
50
        "GLOBAL_POSITION_INT", [&prom](MavlinkDirect::MavlinkMessage message) {
2✔
51
            LogInfo() << "Received GLOBAL_POSITION_INT: " << message.fields_json;
1✔
52
            prom.set_value(message);
1✔
53
        });
1✔
54

55
    // Wait a bit more for subscription to be properly registered
56
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
57

58
    // Create a complex test message with multiple field types
59
    MavlinkDirect::MavlinkMessage test_message;
1✔
60
    test_message.message_name = "GLOBAL_POSITION_INT";
1✔
61
    test_message.system_id = 1;
1✔
62
    test_message.component_id = 1;
1✔
63
    test_message.target_system_id = 0;
1✔
64
    test_message.target_component_id = 0;
1✔
65
    test_message.fields_json =
66
        R"({"time_boot_ms":12345,"lat":473977418,"lon":-1223974560,"alt":100500,"relative_alt":50250,"vx":100,"vy":-50,"vz":25,"hdg":18000})";
1✔
67

68
    LogInfo() << "Sending GLOBAL_POSITION_INT message...";
1✔
69
    auto result = sender_mavlink_direct.send_message(test_message);
1✔
70
    LogInfo() << "Send result: "
1✔
71
              << (result == MavlinkDirect::Result::Success ? "Success" : "Error");
1✔
72
    EXPECT_EQ(result, MavlinkDirect::Result::Success);
1✔
73

74
    // Wait for message to be received
75
    ASSERT_EQ(fut.wait_for(std::chrono::seconds(1)), std::future_status::ready);
1✔
76

77
    auto received_message = fut.get();
2✔
78

79
    EXPECT_EQ(received_message.message_name, test_message.message_name);
1✔
80
    EXPECT_EQ(received_message.system_id, test_message.system_id);
1✔
81
    EXPECT_EQ(received_message.component_id, test_message.component_id);
1✔
82

83
    // Note: For now we only check message_name since field extraction is simplified
84
    EXPECT_TRUE(received_message.fields_json.find("GLOBAL_POSITION_INT") != std::string::npos);
1✔
85

86
    receiver_mavlink_direct.unsubscribe_message(handle);
1✔
87
}
1✔
88

89
TEST(SystemTest, MavlinkDirectExtendedFields)
4✔
90
{
91
    Mavsdk mavsdk_groundstation{Mavsdk::Configuration{ComponentType::GroundStation}};
1✔
92
    Mavsdk mavsdk_autopilot{Mavsdk::Configuration{ComponentType::Autopilot}};
1✔
93

94
    ASSERT_EQ(
1✔
95
        mavsdk_groundstation.add_any_connection("udpin://0.0.0.0:17001"),
96
        ConnectionResult::Success);
1✔
97
    ASSERT_EQ(
1✔
98
        mavsdk_autopilot.add_any_connection("udpout://127.0.0.1:17001"), ConnectionResult::Success);
1✔
99

100
    // Ground station discovers the autopilot system
101
    auto maybe_system = mavsdk_groundstation.first_autopilot(10.0);
1✔
102
    ASSERT_TRUE(maybe_system);
1✔
103
    auto system = maybe_system.value();
1✔
104
    ASSERT_TRUE(system->has_autopilot());
1✔
105

106
    // Wait for autopilot instance to discover the connection to the ground station
107
    while (mavsdk_autopilot.systems().size() == 0) {
1✔
108
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
×
109
    }
110

111
    auto groundstation_system = mavsdk_autopilot.systems().at(0);
1✔
112
    ASSERT_EQ(groundstation_system->component_ids()[0], 190);
2✔
113

114
    auto receiver_mavlink_direct = MavlinkDirect{system};
1✔
115
    auto sender_mavlink_direct = MavlinkDirect{groundstation_system};
1✔
116

117
    auto compact_prom = std::promise<MavlinkDirect::MavlinkMessage>();
1✔
118
    auto compact_fut = compact_prom.get_future();
1✔
119
    auto full_prom = std::promise<MavlinkDirect::MavlinkMessage>();
1✔
120
    auto full_fut = full_prom.get_future();
1✔
121

122
    bool compact_received = false;
1✔
123

124
    // Subscribe to SYS_STATUS messages
125
    auto handle = receiver_mavlink_direct.subscribe_message(
1✔
126
        "SYS_STATUS", [&](MavlinkDirect::MavlinkMessage message) {
2✔
127
            LogInfo() << "Received SYS_STATUS: " << message.fields_json;
2✔
128
            if (!compact_received) {
2✔
129
                compact_received = true;
1✔
130
                compact_prom.set_value(message);
1✔
131
            } else {
132
                full_prom.set_value(message);
1✔
133
            }
134
        });
2✔
135

136
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
137

138
    // Test 1: Send SYS_STATUS in compact form (no extensions)
139
    MavlinkDirect::MavlinkMessage compact_message;
1✔
140
    compact_message.message_name = "SYS_STATUS";
1✔
141
    compact_message.system_id = 1;
1✔
142
    compact_message.component_id = 1;
1✔
143
    compact_message.target_system_id = 0;
1✔
144
    compact_message.target_component_id = 0;
1✔
145
    compact_message.fields_json =
146
        R"({"onboard_control_sensors_present":1,"onboard_control_sensors_enabled":1,"onboard_control_sensors_health":1,"load":500,"voltage_battery":12000,"current_battery":1000,"battery_remaining":75,"drop_rate_comm":0,"errors_comm":0,"errors_count1":0,"errors_count2":0,"errors_count3":0,"errors_count4":0})";
1✔
147

148
    auto result1 = sender_mavlink_direct.send_message(compact_message);
1✔
149
    EXPECT_EQ(result1, MavlinkDirect::Result::Success);
1✔
150

151
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
1✔
152

153
    // Test 2: Send SYS_STATUS in full form (with extensions)
154
    MavlinkDirect::MavlinkMessage full_message;
1✔
155
    full_message.message_name = "SYS_STATUS";
1✔
156
    full_message.system_id = 1;
1✔
157
    full_message.component_id = 1;
1✔
158
    full_message.target_system_id = 0;
1✔
159
    full_message.target_component_id = 0;
1✔
160
    full_message.fields_json =
161
        R"({"onboard_control_sensors_present":1,"onboard_control_sensors_enabled":1,"onboard_control_sensors_health":1,"load":500,"voltage_battery":12000,"current_battery":1000,"battery_remaining":75,"drop_rate_comm":0,"errors_comm":0,"errors_count1":0,"errors_count2":0,"errors_count3":0,"errors_count4":0,"onboard_control_sensors_present_extended":123,"onboard_control_sensors_enabled_extended":456,"onboard_control_sensors_health_extended":789})";
1✔
162

163
    auto result2 = sender_mavlink_direct.send_message(full_message);
1✔
164
    EXPECT_EQ(result2, MavlinkDirect::Result::Success);
1✔
165

166
    // Wait for both messages to be received
167
    ASSERT_EQ(compact_fut.wait_for(std::chrono::seconds(5)), std::future_status::ready);
1✔
168
    ASSERT_EQ(full_fut.wait_for(std::chrono::seconds(5)), std::future_status::ready);
1✔
169

170
    auto received_compact = compact_fut.get();
1✔
171
    auto received_full = full_fut.get();
1✔
172

173
    // Verify both messages were received and parsing worked
174
    EXPECT_EQ(received_compact.message_name, "SYS_STATUS");
1✔
175
    EXPECT_EQ(received_full.message_name, "SYS_STATUS");
1✔
176

177
    // Parse JSON to verify field values
178
    Json::Value compact_json, full_json;
1✔
179
    Json::Reader reader;
1✔
180

181
    ASSERT_TRUE(reader.parse(received_compact.fields_json, compact_json));
1✔
182
    ASSERT_TRUE(reader.parse(received_full.fields_json, full_json));
1✔
183

184
    // Verify basic fields are present and correct in both messages
185
    EXPECT_EQ(compact_json["onboard_control_sensors_present"].asUInt(), 1u);
1✔
186
    EXPECT_EQ(compact_json["load"].asUInt(), 500u);
1✔
187
    EXPECT_EQ(compact_json["voltage_battery"].asUInt(), 12000u);
1✔
188
    EXPECT_EQ(compact_json["current_battery"].asInt(), 1000);
1✔
189
    EXPECT_EQ(compact_json["battery_remaining"].asInt(), 75);
1✔
190

191
    EXPECT_EQ(full_json["onboard_control_sensors_present"].asUInt(), 1u);
1✔
192
    EXPECT_EQ(full_json["load"].asUInt(), 500u);
1✔
193
    EXPECT_EQ(full_json["voltage_battery"].asUInt(), 12000u);
1✔
194
    EXPECT_EQ(full_json["current_battery"].asInt(), 1000);
1✔
195
    EXPECT_EQ(full_json["battery_remaining"].asInt(), 75);
1✔
196

197
    // Verify compact message HAS extended fields with zero values (MAVLink v2 zero-truncation)
198
    EXPECT_TRUE(compact_json.isMember("onboard_control_sensors_present_extended"));
1✔
199
    EXPECT_TRUE(compact_json.isMember("onboard_control_sensors_enabled_extended"));
1✔
200
    EXPECT_TRUE(compact_json.isMember("onboard_control_sensors_health_extended"));
1✔
201
    EXPECT_EQ(compact_json["onboard_control_sensors_present_extended"].asUInt(), 0u);
1✔
202
    EXPECT_EQ(compact_json["onboard_control_sensors_enabled_extended"].asUInt(), 0u);
1✔
203
    EXPECT_EQ(compact_json["onboard_control_sensors_health_extended"].asUInt(), 0u);
1✔
204

205
    // Verify full message HAS extended fields with correct values
206
    EXPECT_TRUE(full_json.isMember("onboard_control_sensors_present_extended"));
1✔
207
    EXPECT_TRUE(full_json.isMember("onboard_control_sensors_enabled_extended"));
1✔
208
    EXPECT_TRUE(full_json.isMember("onboard_control_sensors_health_extended"));
1✔
209

210
    EXPECT_EQ(full_json["onboard_control_sensors_present_extended"].asUInt(), 123u);
1✔
211
    EXPECT_EQ(full_json["onboard_control_sensors_enabled_extended"].asUInt(), 456u);
1✔
212
    EXPECT_EQ(full_json["onboard_control_sensors_health_extended"].asUInt(), 789u);
1✔
213

214
    receiver_mavlink_direct.unsubscribe_message(handle);
1✔
215
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
216
}
1✔
217

218
TEST(SystemTest, MavlinkDirectToTelemetry)
4✔
219
{
220
    Mavsdk mavsdk_groundstation{Mavsdk::Configuration{ComponentType::GroundStation}};
1✔
221
    Mavsdk mavsdk_autopilot{Mavsdk::Configuration{ComponentType::Autopilot}};
1✔
222

223
    ASSERT_EQ(
1✔
224
        mavsdk_groundstation.add_any_connection("udpin://0.0.0.0:17002"),
225
        ConnectionResult::Success);
1✔
226
    ASSERT_EQ(
1✔
227
        mavsdk_autopilot.add_any_connection("udpout://127.0.0.1:17002"), ConnectionResult::Success);
1✔
228

229
    // Ground station discovers the autopilot system
230
    auto maybe_system = mavsdk_groundstation.first_autopilot(10.0);
1✔
231
    ASSERT_TRUE(maybe_system);
1✔
232
    auto system = maybe_system.value();
1✔
233
    ASSERT_TRUE(system->has_autopilot());
1✔
234

235
    // Wait for autopilot instance to discover the connection to the ground station
236
    while (mavsdk_autopilot.systems().size() == 0) {
2✔
237
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
238
    }
239

240
    auto groundstation_system = mavsdk_autopilot.systems().at(0);
1✔
241
    ASSERT_EQ(groundstation_system->component_ids()[0], 190);
2✔
242

243
    // Autopilot uses MavlinkDirect to send, Ground station uses Telemetry to receive
244
    auto sender_mavlink_direct = MavlinkDirect{groundstation_system};
1✔
245
    auto receiver_telemetry = Telemetry{system};
1✔
246

247
    auto prom = std::promise<Telemetry::Position>();
1✔
248
    auto fut = prom.get_future();
1✔
249

250
    // Subscribe to position updates via Telemetry
251
    auto handle = receiver_telemetry.subscribe_position([&prom](Telemetry::Position position) {
3✔
252
        LogInfo() << "Received position via Telemetry: lat=" << position.latitude_deg
2✔
253
                  << " lon=" << position.longitude_deg;
1✔
254
        prom.set_value(position);
1✔
255
    });
1✔
256

257
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
258

259
    // Send GLOBAL_POSITION_INT via MavlinkDirect
260
    MavlinkDirect::MavlinkMessage test_message;
1✔
261
    test_message.message_name = "GLOBAL_POSITION_INT";
1✔
262
    test_message.system_id = 1;
1✔
263
    test_message.component_id = 1;
1✔
264
    test_message.target_system_id = 0;
1✔
265
    test_message.target_component_id = 0;
1✔
266
    test_message.fields_json =
267
        R"({"time_boot_ms":12345,"lat":473977418,"lon":-1223974560,"alt":100500,"relative_alt":50250,"vx":100,"vy":-50,"vz":25,"hdg":18000})";
1✔
268

269
    auto result = sender_mavlink_direct.send_message(test_message);
1✔
270
    EXPECT_EQ(result, MavlinkDirect::Result::Success);
1✔
271

272
    // Wait for position to be received
273
    ASSERT_EQ(fut.wait_for(std::chrono::seconds(5)), std::future_status::ready);
1✔
274

275
    auto received_position = fut.get();
1✔
276

277
    // Verify the position data (lat/lon are in degrees * 1e7 in MAVLink)
278
    EXPECT_NEAR(received_position.latitude_deg, 47.3977418, 0.0001);
1✔
279
    EXPECT_NEAR(received_position.longitude_deg, -122.3974560, 0.0001);
1✔
280

281
    receiver_telemetry.unsubscribe_position(handle);
1✔
282
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
283
}
1✔
284

285
TEST(SystemTest, TelemetryServerToMavlinkDirect)
4✔
286
{
287
    Mavsdk mavsdk_groundstation{Mavsdk::Configuration{ComponentType::GroundStation}};
1✔
288
    Mavsdk mavsdk_autopilot{Mavsdk::Configuration{ComponentType::Autopilot}};
1✔
289

290
    ASSERT_EQ(
1✔
291
        mavsdk_groundstation.add_any_connection("udpin://0.0.0.0:17003"),
292
        ConnectionResult::Success);
1✔
293
    ASSERT_EQ(
1✔
294
        mavsdk_autopilot.add_any_connection("udpout://127.0.0.1:17003"), ConnectionResult::Success);
1✔
295

296
    // Ground station discovers the autopilot system
297
    auto maybe_system = mavsdk_groundstation.first_autopilot(10.0);
1✔
298
    ASSERT_TRUE(maybe_system);
1✔
299
    auto system = maybe_system.value();
1✔
300
    ASSERT_TRUE(system->has_autopilot());
1✔
301

302
    // Wait for autopilot instance to discover the connection to the ground station
303
    while (mavsdk_autopilot.systems().size() == 0) {
2✔
304
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
305
    }
306

307
    auto groundstation_system = mavsdk_autopilot.systems().at(0);
1✔
308
    ASSERT_EQ(groundstation_system->component_ids()[0], 190);
2✔
309

310
    // Autopilot uses TelemetryServer to send, Ground station uses MavlinkDirect to receive
311
    auto autopilot_server_component = mavsdk_autopilot.server_component();
1✔
312
    auto sender_telemetry_server = TelemetryServer{autopilot_server_component};
1✔
313
    auto receiver_mavlink_direct = MavlinkDirect{system};
1✔
314

315
    auto prom = std::promise<MavlinkDirect::MavlinkMessage>();
1✔
316
    auto fut = prom.get_future();
1✔
317

318
    // Subscribe to GLOBAL_POSITION_INT via MavlinkDirect
319
    auto handle = receiver_mavlink_direct.subscribe_message(
1✔
320
        "GLOBAL_POSITION_INT", [&prom](MavlinkDirect::MavlinkMessage message) {
2✔
321
            LogInfo() << "Received GLOBAL_POSITION_INT via MavlinkDirect: " << message.fields_json;
1✔
322
            prom.set_value(message);
1✔
323
        });
1✔
324

325
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
326

327
    // Send position via TelemetryServer
328
    TelemetryServer::Position position;
1✔
329
    position.latitude_deg = 47.3977418;
1✔
330
    position.longitude_deg = -122.3974560;
1✔
331
    position.absolute_altitude_m = 100.5;
1✔
332
    position.relative_altitude_m = 50.25;
1✔
333

334
    TelemetryServer::VelocityNed velocity_ned;
1✔
335
    velocity_ned.north_m_s = 1.0f;
1✔
336
    velocity_ned.east_m_s = -0.5f;
1✔
337
    velocity_ned.down_m_s = 0.25f;
1✔
338

339
    TelemetryServer::Heading heading;
1✔
340
    heading.heading_deg = 180.0f;
1✔
341

342
    auto result = sender_telemetry_server.publish_position(position, velocity_ned, heading);
1✔
343
    EXPECT_EQ(result, TelemetryServer::Result::Success);
1✔
344

345
    // Wait for message to be received
346
    ASSERT_EQ(fut.wait_for(std::chrono::seconds(5)), std::future_status::ready);
1✔
347

348
    auto received_message = fut.get();
1✔
349

350
    EXPECT_EQ(received_message.message_name, "GLOBAL_POSITION_INT");
1✔
351
    EXPECT_EQ(received_message.system_id, 1);
1✔
352
    EXPECT_EQ(received_message.component_id, 1);
1✔
353

354
    // Verify the JSON contains the message info (since we're using simplified parsing)
355
    EXPECT_TRUE(received_message.fields_json.find("GLOBAL_POSITION_INT") != std::string::npos);
1✔
356

357
    receiver_mavlink_direct.unsubscribe_message(handle);
1✔
358
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
359
}
1✔
360

361
TEST(SystemTest, MavlinkDirectArrayFields)
4✔
362
{
363
    Mavsdk mavsdk_groundstation{Mavsdk::Configuration{ComponentType::GroundStation}};
1✔
364
    Mavsdk mavsdk_autopilot{Mavsdk::Configuration{ComponentType::Autopilot}};
1✔
365

366
    ASSERT_EQ(
1✔
367
        mavsdk_groundstation.add_any_connection("udpin://0.0.0.0:17004"),
368
        ConnectionResult::Success);
1✔
369
    ASSERT_EQ(
1✔
370
        mavsdk_autopilot.add_any_connection("udpout://127.0.0.1:17004"), ConnectionResult::Success);
1✔
371

372
    // Ground station discovers the autopilot system
373
    auto maybe_system = mavsdk_groundstation.first_autopilot(10.0);
1✔
374
    ASSERT_TRUE(maybe_system);
1✔
375
    auto system = maybe_system.value();
1✔
376
    ASSERT_TRUE(system->has_autopilot());
1✔
377

378
    // Wait for autopilot instance to discover the connection to the ground station
379
    while (mavsdk_autopilot.systems().size() == 0) {
2✔
380
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
381
    }
382

383
    auto groundstation_system = mavsdk_autopilot.systems().at(0);
1✔
384
    ASSERT_EQ(groundstation_system->component_ids()[0], 190);
2✔
385

386
    auto receiver_mavlink_direct = MavlinkDirect{system};
1✔
387
    auto sender_mavlink_direct = MavlinkDirect{groundstation_system};
1✔
388

389
    auto partial_prom = std::promise<MavlinkDirect::MavlinkMessage>();
1✔
390
    auto partial_fut = partial_prom.get_future();
1✔
391
    auto full_prom = std::promise<MavlinkDirect::MavlinkMessage>();
1✔
392
    auto full_fut = full_prom.get_future();
1✔
393

394
    bool partial_received = false;
1✔
395

396
    // Subscribe to GPS_STATUS messages
397
    auto handle = receiver_mavlink_direct.subscribe_message(
1✔
398
        "GPS_STATUS", [&](MavlinkDirect::MavlinkMessage message) {
2✔
399
            LogInfo() << "Received GPS_STATUS: " << message.fields_json;
2✔
400
            if (!partial_received) {
2✔
401
                partial_received = true;
1✔
402
                partial_prom.set_value(message);
1✔
403
            } else {
404
                full_prom.set_value(message);
1✔
405
            }
406
        });
2✔
407

408
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
409

410
    // Test 1: Send GPS_STATUS with partial array data (3 satellites)
411
    MavlinkDirect::MavlinkMessage partial_message;
1✔
412
    partial_message.message_name = "GPS_STATUS";
1✔
413
    partial_message.system_id = 1;
1✔
414
    partial_message.component_id = 1;
1✔
415
    partial_message.target_system_id = 0;
1✔
416
    partial_message.target_component_id = 0;
1✔
417
    partial_message.fields_json =
418
        R"({"satellites_visible":3,"satellite_prn":[1,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"satellite_used":[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"satellite_elevation":[45,60,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"satellite_azimuth":[90,180,270,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"satellite_snr":[25,30,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]})";
1✔
419

420
    auto result1 = sender_mavlink_direct.send_message(partial_message);
1✔
421
    EXPECT_EQ(result1, MavlinkDirect::Result::Success);
1✔
422

423
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
1✔
424

425
    // Test 2: Send GPS_STATUS with full array data (20 satellites)
426
    MavlinkDirect::MavlinkMessage full_message;
1✔
427
    full_message.message_name = "GPS_STATUS";
1✔
428
    full_message.system_id = 1;
1✔
429
    full_message.component_id = 1;
1✔
430
    full_message.target_system_id = 0;
1✔
431
    full_message.target_component_id = 0;
1✔
432
    full_message.fields_json =
433
        R"({"satellites_visible":20,"satellite_prn":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],"satellite_used":[1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0],"satellite_elevation":[45,60,30,75,20,85,50,40,65,35,25,55,70,15,80,10,90,5,45,60],"satellite_azimuth":[0,36,72,108,144,180,216,252,28,64,100,136,172,208,244,20,56,92,128,164],"satellite_snr":[25,30,15,35,20,40,28,22,32,18,26,31,16,38,24,19,33,21,29,27]})";
1✔
434

435
    auto result2 = sender_mavlink_direct.send_message(full_message);
1✔
436
    EXPECT_EQ(result2, MavlinkDirect::Result::Success);
1✔
437

438
    // Wait for both messages to be received
439
    ASSERT_EQ(partial_fut.wait_for(std::chrono::seconds(5)), std::future_status::ready);
1✔
440
    ASSERT_EQ(full_fut.wait_for(std::chrono::seconds(5)), std::future_status::ready);
1✔
441

442
    auto received_partial = partial_fut.get();
1✔
443
    auto received_full = full_fut.get();
1✔
444

445
    // Verify both messages were received and parsing worked
446
    EXPECT_EQ(received_partial.message_name, "GPS_STATUS");
1✔
447
    EXPECT_EQ(received_full.message_name, "GPS_STATUS");
1✔
448

449
    // Parse JSON to verify field values
450
    Json::Value partial_json, full_json;
1✔
451
    Json::Reader reader;
1✔
452

453
    ASSERT_TRUE(reader.parse(received_partial.fields_json, partial_json));
1✔
454
    ASSERT_TRUE(reader.parse(received_full.fields_json, full_json));
1✔
455

456
    // Verify scalar field
457
    EXPECT_EQ(partial_json["satellites_visible"].asUInt(), 3u);
1✔
458
    EXPECT_EQ(full_json["satellites_visible"].asUInt(), 20u);
1✔
459

460
    // Verify array fields are present and have correct type (arrays)
461
    EXPECT_TRUE(partial_json["satellite_prn"].isArray());
1✔
462
    EXPECT_TRUE(partial_json["satellite_used"].isArray());
1✔
463
    EXPECT_TRUE(partial_json["satellite_elevation"].isArray());
1✔
464
    EXPECT_TRUE(partial_json["satellite_azimuth"].isArray());
1✔
465
    EXPECT_TRUE(partial_json["satellite_snr"].isArray());
1✔
466

467
    EXPECT_TRUE(full_json["satellite_prn"].isArray());
1✔
468
    EXPECT_TRUE(full_json["satellite_used"].isArray());
1✔
469
    EXPECT_TRUE(full_json["satellite_elevation"].isArray());
1✔
470
    EXPECT_TRUE(full_json["satellite_azimuth"].isArray());
1✔
471
    EXPECT_TRUE(full_json["satellite_snr"].isArray());
1✔
472

473
    // Verify array lengths (should be 20 elements each)
474
    EXPECT_EQ(partial_json["satellite_prn"].size(), 20u);
1✔
475
    EXPECT_EQ(partial_json["satellite_used"].size(), 20u);
1✔
476
    EXPECT_EQ(partial_json["satellite_elevation"].size(), 20u);
1✔
477
    EXPECT_EQ(partial_json["satellite_azimuth"].size(), 20u);
1✔
478
    EXPECT_EQ(partial_json["satellite_snr"].size(), 20u);
1✔
479

480
    EXPECT_EQ(full_json["satellite_prn"].size(), 20u);
1✔
481
    EXPECT_EQ(full_json["satellite_used"].size(), 20u);
1✔
482
    EXPECT_EQ(full_json["satellite_elevation"].size(), 20u);
1✔
483
    EXPECT_EQ(full_json["satellite_azimuth"].size(), 20u);
1✔
484
    EXPECT_EQ(full_json["satellite_snr"].size(), 20u);
1✔
485

486
    // Verify specific array element values for partial message
487
    EXPECT_EQ(partial_json["satellite_prn"][0].asUInt(), 1u);
1✔
488
    EXPECT_EQ(partial_json["satellite_prn"][1].asUInt(), 2u);
1✔
489
    EXPECT_EQ(partial_json["satellite_prn"][2].asUInt(), 3u);
1✔
490
    EXPECT_EQ(partial_json["satellite_prn"][3].asUInt(), 0u); // Should be zero
1✔
491
    EXPECT_EQ(partial_json["satellite_prn"][19].asUInt(), 0u); // Last element should be zero
1✔
492

493
    EXPECT_EQ(partial_json["satellite_used"][0].asUInt(), 1u);
1✔
494
    EXPECT_EQ(partial_json["satellite_used"][1].asUInt(), 1u);
1✔
495
    EXPECT_EQ(partial_json["satellite_used"][2].asUInt(), 0u);
1✔
496
    EXPECT_EQ(partial_json["satellite_used"][3].asUInt(), 0u);
1✔
497

498
    EXPECT_EQ(partial_json["satellite_elevation"][0].asUInt(), 45u);
1✔
499
    EXPECT_EQ(partial_json["satellite_elevation"][1].asUInt(), 60u);
1✔
500
    EXPECT_EQ(partial_json["satellite_elevation"][2].asUInt(), 30u);
1✔
501
    EXPECT_EQ(partial_json["satellite_elevation"][3].asUInt(), 0u);
1✔
502

503
    // Verify specific array element values for full message
504
    EXPECT_EQ(full_json["satellite_prn"][0].asUInt(), 1u);
1✔
505
    EXPECT_EQ(full_json["satellite_prn"][9].asUInt(), 10u);
1✔
506
    EXPECT_EQ(full_json["satellite_prn"][19].asUInt(), 20u); // Last element
1✔
507

508
    EXPECT_EQ(full_json["satellite_used"][0].asUInt(), 1u);
1✔
509
    EXPECT_EQ(full_json["satellite_used"][5].asUInt(), 0u); // Some unused satellites
1✔
510
    EXPECT_EQ(full_json["satellite_used"][10].asUInt(), 1u);
1✔
511

512
    EXPECT_EQ(full_json["satellite_snr"][0].asUInt(), 25u);
1✔
513
    EXPECT_EQ(full_json["satellite_snr"][19].asUInt(), 27u); // Last element
1✔
514

515
    receiver_mavlink_direct.unsubscribe_message(handle);
1✔
516
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
517
}
1✔
518

519
TEST(SystemTest, MavlinkDirectLoadCustomXml)
4✔
520
{
521
    Mavsdk mavsdk_groundstation{Mavsdk::Configuration{ComponentType::GroundStation}};
1✔
522
    Mavsdk mavsdk_autopilot{Mavsdk::Configuration{ComponentType::Autopilot}};
1✔
523

524
    ASSERT_EQ(
1✔
525
        mavsdk_groundstation.add_any_connection("udpin://0.0.0.0:17005"),
526
        ConnectionResult::Success);
1✔
527
    ASSERT_EQ(
1✔
528
        mavsdk_autopilot.add_any_connection("udpout://127.0.0.1:17005"), ConnectionResult::Success);
1✔
529

530
    // Ground station discovers the autopilot system
531
    auto maybe_system = mavsdk_groundstation.first_autopilot(10.0);
1✔
532
    ASSERT_TRUE(maybe_system);
1✔
533
    auto system = maybe_system.value();
1✔
534
    ASSERT_TRUE(system->has_autopilot());
1✔
535

536
    // Wait for autopilot instance to discover the connection to the ground station
537
    while (mavsdk_autopilot.systems().size() == 0) {
2✔
538
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
539
    }
540

541
    auto groundstation_system = mavsdk_autopilot.systems().at(0);
1✔
542
    ASSERT_EQ(groundstation_system->component_ids()[0], 190);
2✔
543

544
    auto sender_mavlink_direct = MavlinkDirect{groundstation_system};
1✔
545
    auto receiver_mavlink_direct = MavlinkDirect{system};
1✔
546

547
    // Define custom XML with a test message
548
    std::string custom_xml = R"(<?xml version="1.0"?>
1✔
549
<mavlink>
550
    <version>3</version>
551
    <dialect>0</dialect>
552
    <messages>
553
        <message id="420" name="CUSTOM_TEST_MESSAGE">
554
            <description>A test custom message for LoadCustomXml</description>
555
            <field type="uint32_t" name="test_value">Test value field</field>
556
            <field type="uint16_t" name="counter">Counter field</field>
557
            <field type="uint8_t" name="status">Status byte</field>
558
        </message>
559
    </messages>
560
</mavlink>)";
561

562
    // Load custom XML on both sender and receiver
563
    auto result1 = sender_mavlink_direct.load_custom_xml(custom_xml);
1✔
564
    EXPECT_EQ(result1, MavlinkDirect::Result::Success);
1✔
565

566
    auto result2 = receiver_mavlink_direct.load_custom_xml(custom_xml);
1✔
567
    EXPECT_EQ(result2, MavlinkDirect::Result::Success);
1✔
568

569
    auto prom = std::promise<MavlinkDirect::MavlinkMessage>();
1✔
570
    auto fut = prom.get_future();
1✔
571

572
    // Subscribe to the custom message
573
    auto handle = receiver_mavlink_direct.subscribe_message(
1✔
574
        "CUSTOM_TEST_MESSAGE", [&prom](MavlinkDirect::MavlinkMessage message) {
2✔
575
            LogInfo() << "Received CUSTOM_TEST_MESSAGE: " << message.fields_json;
1✔
576
            prom.set_value(message);
1✔
577
        });
1✔
578

579
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
580

581
    // Send the custom message
582
    MavlinkDirect::MavlinkMessage custom_message;
1✔
583
    custom_message.message_name = "CUSTOM_TEST_MESSAGE";
1✔
584
    custom_message.system_id = 1;
1✔
585
    custom_message.component_id = 1;
1✔
586
    custom_message.target_system_id = 0;
1✔
587
    custom_message.target_component_id = 0;
1✔
588
    custom_message.fields_json = R"({"test_value":42,"counter":1337,"status":5})";
1✔
589

590
    auto send_result = sender_mavlink_direct.send_message(custom_message);
1✔
591
    EXPECT_EQ(send_result, MavlinkDirect::Result::Success);
1✔
592

593
    // Wait for message to be received
594
    ASSERT_EQ(fut.wait_for(std::chrono::seconds(5)), std::future_status::ready);
1✔
595

596
    auto received_message = fut.get();
1✔
597

598
    EXPECT_EQ(received_message.message_name, "CUSTOM_TEST_MESSAGE");
1✔
599

600
    // Parse JSON to verify field values
601
    Json::Value json;
1✔
602
    Json::Reader reader;
1✔
603
    ASSERT_TRUE(reader.parse(received_message.fields_json, json));
1✔
604

605
    // Verify custom message fields
606
    EXPECT_EQ(json["test_value"].asUInt(), 42u);
1✔
607
    EXPECT_EQ(json["counter"].asUInt(), 1337u);
1✔
608
    EXPECT_EQ(json["status"].asUInt(), 5u);
1✔
609

610
    receiver_mavlink_direct.unsubscribe_message(handle);
1✔
611
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
612
}
1✔
613

614
TEST(SystemTest, MavlinkDirectArdupilotmegaMessage)
4✔
615
{
616
    Mavsdk mavsdk_groundstation{Mavsdk::Configuration{ComponentType::GroundStation}};
1✔
617
    Mavsdk mavsdk_autopilot{Mavsdk::Configuration{ComponentType::Autopilot}};
1✔
618

619
    ASSERT_EQ(
1✔
620
        mavsdk_groundstation.add_any_connection("udpin://0.0.0.0:17000"),
621
        ConnectionResult::Success);
1✔
622
    ASSERT_EQ(
1✔
623
        mavsdk_autopilot.add_any_connection("udpout://127.0.0.1:17000"), ConnectionResult::Success);
1✔
624

625
    // Ground station discovers the autopilot system
626
    auto maybe_system = mavsdk_groundstation.first_autopilot(10.0);
1✔
627
    ASSERT_TRUE(maybe_system);
1✔
628
    auto system = maybe_system.value();
1✔
629
    ASSERT_TRUE(system->has_autopilot());
1✔
630

631
    // Wait for autopilot instance to discover the connection to the ground station
632
    LogInfo() << "Waiting for autopilot system to connect...";
1✔
633
    while (mavsdk_autopilot.systems().size() == 0) {
2✔
634
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
635
    }
636

637
    // Get the autopilot's view of the connected system
638
    auto groundstation_system = mavsdk_autopilot.systems().at(0);
1✔
639
    ASSERT_EQ(groundstation_system->component_ids()[0], 190);
2✔
640

641
    // Create separate MavlinkDirect instances for sender (autopilot) and receiver (ground station)
642
    auto receiver_mavlink_direct = MavlinkDirect{system};
1✔
643
    auto sender_mavlink_direct = MavlinkDirect{groundstation_system};
1✔
644

645
    auto prom = std::promise<MavlinkDirect::MavlinkMessage>();
1✔
646
    auto fut = prom.get_future();
1✔
647

648
    // Ground station subscribes to receive MEMINFO from autopilot (ArduPilot-specific message)
649
    auto handle = receiver_mavlink_direct.subscribe_message(
1✔
650
        "MEMINFO", [&prom](MavlinkDirect::MavlinkMessage message) {
2✔
651
            LogInfo() << "Received MEMINFO: " << message.fields_json;
1✔
652
            prom.set_value(message);
1✔
653
        });
1✔
654

655
    // Autopilot sends MEMINFO message (ID 152 from ardupilotmega.xml)
656
    MavlinkDirect::MavlinkMessage meminfo_msg;
1✔
657
    meminfo_msg.message_name = "MEMINFO";
1✔
658
    meminfo_msg.fields_json = R"({"brkval": 32768, "freemem": 8192})";
1✔
659

660
    auto result = sender_mavlink_direct.send_message(meminfo_msg);
1✔
661
    ASSERT_EQ(result, MavlinkDirect::Result::Success);
1✔
662

663
    // Wait for message to be received
664
    ASSERT_EQ(fut.wait_for(std::chrono::seconds(5)), std::future_status::ready);
1✔
665

666
    auto received_message = fut.get();
1✔
667

668
    // Verify the message was received
669
    EXPECT_EQ(received_message.message_name, "MEMINFO");
1✔
670

671
    // Parse JSON to verify field values
672
    Json::Value json;
1✔
673
    Json::Reader reader;
1✔
674
    ASSERT_TRUE(reader.parse(received_message.fields_json, json));
1✔
675

676
    // Verify MEMINFO message fields
677
    EXPECT_EQ(json["brkval"].asUInt(), 32768u); // Heap top
1✔
678
    EXPECT_EQ(json["freemem"].asUInt(), 8192u); // Free memory
1✔
679

680
    LogInfo() << "Successfully tested ArduPilot-specific MEMINFO message from ardupilotmega.xml";
1✔
681
    receiver_mavlink_direct.unsubscribe_message(handle);
1✔
682
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
683
}
1✔
684

685
TEST(SystemTest, MavlinkDirectNanInfinityJsonHandling)
4✔
686
{
687
    Mavsdk mavsdk_groundstation{Mavsdk::Configuration{ComponentType::GroundStation}};
1✔
688
    Mavsdk mavsdk_autopilot{Mavsdk::Configuration{ComponentType::Autopilot}};
1✔
689

690
    ASSERT_EQ(
1✔
691
        mavsdk_groundstation.add_any_connection("udpin://0.0.0.0:17006"),
692
        ConnectionResult::Success);
1✔
693
    ASSERT_EQ(
1✔
694
        mavsdk_autopilot.add_any_connection("udpout://127.0.0.1:17006"), ConnectionResult::Success);
1✔
695

696
    // Ground station discovers the autopilot system
697
    auto maybe_system = mavsdk_groundstation.first_autopilot(10.0);
1✔
698
    ASSERT_TRUE(maybe_system);
1✔
699
    auto system = maybe_system.value();
1✔
700
    ASSERT_TRUE(system->has_autopilot());
1✔
701

702
    // Wait for autopilot instance to discover the connection to the ground station
703
    while (mavsdk_autopilot.systems().size() == 0) {
2✔
704
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
705
    }
706

707
    auto groundstation_system = mavsdk_autopilot.systems().at(0);
1✔
708
    ASSERT_EQ(groundstation_system->component_ids()[0], 190);
2✔
709

710
    auto receiver_mavlink_direct = MavlinkDirect{system};
1✔
711
    auto sender_mavlink_direct = MavlinkDirect{groundstation_system};
1✔
712

713
    // Define custom XML with a test message containing float fields
714
    std::string custom_xml = R"(<?xml version="1.0"?>
1✔
715
<mavlink>
716
    <version>3</version>
717
    <dialect>0</dialect>
718
    <messages>
719
        <message id="421" name="FLOAT_TEST_MESSAGE">
720
            <description>A test message for NaN/infinity handling in JSON</description>
721
            <field type="float" name="normal_float">Normal float field</field>
722
            <field type="float" name="nan_float">NaN float field</field>
723
            <field type="float" name="pos_inf_float">Positive infinity float field</field>
724
            <field type="float" name="neg_inf_float">Negative infinity float field</field>
725
            <field type="double" name="normal_double">Normal double field</field>
726
            <field type="double" name="nan_double">NaN double field</field>
727
            <field type="float[4]" name="float_array">Array with some NaN/inf values</field>
728
        </message>
729
    </messages>
730
</mavlink>)";
731

732
    // Load custom XML on both sender and receiver
733
    auto result1 = sender_mavlink_direct.load_custom_xml(custom_xml);
1✔
734
    EXPECT_EQ(result1, MavlinkDirect::Result::Success);
1✔
735

736
    auto result2 = receiver_mavlink_direct.load_custom_xml(custom_xml);
1✔
737
    EXPECT_EQ(result2, MavlinkDirect::Result::Success);
1✔
738

739
    auto prom = std::promise<MavlinkDirect::MavlinkMessage>();
1✔
740
    auto fut = prom.get_future();
1✔
741

742
    // Subscribe to the custom message
743
    auto handle = receiver_mavlink_direct.subscribe_message(
1✔
744
        "FLOAT_TEST_MESSAGE", [&prom](MavlinkDirect::MavlinkMessage message) {
2✔
745
            LogInfo() << "Received FLOAT_TEST_MESSAGE: " << message.fields_json;
1✔
746
            prom.set_value(message);
1✔
747
        });
1✔
748

749
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
750

751
    // Create test message with NaN and infinity values
752
    // Note: We can't send these directly in JSON, so we'll simulate the scenario
753
    // where they would be received from a MAVLink message containing such values
754

755
    // For testing, we'll send a normal message and verify that the libmav receiver
756
    // handles NaN/inf correctly. The actual test will be done by checking that
757
    // valid JSON is produced even when NaN/inf values are present in libmav parsing.
758

759
    MavlinkDirect::MavlinkMessage test_message;
1✔
760
    test_message.message_name = "FLOAT_TEST_MESSAGE";
1✔
761
    test_message.system_id = 1;
1✔
762
    test_message.component_id = 1;
1✔
763
    test_message.target_system_id = 0;
1✔
764
    test_message.target_component_id = 0;
1✔
765
    test_message.fields_json =
766
        R"({"normal_float":3.14,"nan_float":null,"pos_inf_float":null,"neg_inf_float":null,"normal_double":2.718,"nan_double":null,"float_array":[1.0,null,null,4.0]})";
1✔
767

768
    auto send_result = sender_mavlink_direct.send_message(test_message);
1✔
769
    EXPECT_EQ(send_result, MavlinkDirect::Result::Success);
1✔
770

771
    // Wait for message to be received
772
    ASSERT_EQ(fut.wait_for(std::chrono::seconds(5)), std::future_status::ready);
1✔
773

774
    auto received_message = fut.get();
1✔
775

776
    EXPECT_EQ(received_message.message_name, "FLOAT_TEST_MESSAGE");
1✔
777

778
    // Parse JSON to verify it's valid JSON (most important test)
779
    Json::Value json;
1✔
780
    Json::Reader reader;
1✔
781
    ASSERT_TRUE(reader.parse(received_message.fields_json, json))
1✔
782
        << "JSON parsing failed, indicating invalid JSON was generated. "
×
783
        << "JSON content: " << received_message.fields_json;
1✔
784

785
    // Verify all expected fields are present
786
    EXPECT_TRUE(json.isMember("normal_float"));
1✔
787
    EXPECT_TRUE(json.isMember("nan_float"));
1✔
788
    EXPECT_TRUE(json.isMember("pos_inf_float"));
1✔
789
    EXPECT_TRUE(json.isMember("neg_inf_float"));
1✔
790
    EXPECT_TRUE(json.isMember("normal_double"));
1✔
791
    EXPECT_TRUE(json.isMember("nan_double"));
1✔
792
    EXPECT_TRUE(json.isMember("float_array"));
1✔
793

794
    // Verify that normal values are preserved correctly
795
    EXPECT_TRUE(json["normal_float"].isNumeric());
1✔
796
    EXPECT_NEAR(json["normal_float"].asFloat(), 3.14f, 0.001f);
1✔
797
    EXPECT_TRUE(json["normal_double"].isNumeric());
1✔
798
    EXPECT_NEAR(json["normal_double"].asDouble(), 2.718, 0.001);
1✔
799

800
    // The key test: verify that null values in input JSON were converted to NaN
801
    // in the MAVLink message, then back to null in the output JSON
802
    EXPECT_TRUE(json["nan_float"].isNull())
1✔
803
        << "nan_float should be null, got: " << json["nan_float"];
1✔
804
    EXPECT_TRUE(json["pos_inf_float"].isNull())
1✔
805
        << "pos_inf_float should be null, got: " << json["pos_inf_float"];
1✔
806
    EXPECT_TRUE(json["neg_inf_float"].isNull())
1✔
807
        << "neg_inf_float should be null, got: " << json["neg_inf_float"];
1✔
808
    EXPECT_TRUE(json["nan_double"].isNull())
1✔
809
        << "nan_double should be null, got: " << json["nan_double"];
1✔
810

811
    // Verify array handling: normal values preserved, null values round-trip as null (via NaN)
812
    EXPECT_TRUE(json["float_array"].isArray());
1✔
813
    EXPECT_EQ(json["float_array"].size(), 4u);
1✔
814
    EXPECT_TRUE(json["float_array"][0].isNumeric());
1✔
815
    EXPECT_NEAR(json["float_array"][0].asFloat(), 1.0f, 0.001f);
1✔
816
    EXPECT_TRUE(json["float_array"][1].isNull())
1✔
817
        << "float_array[1] should be null (converted from NaN), got: " << json["float_array"][1];
1✔
818
    EXPECT_TRUE(json["float_array"][2].isNull())
1✔
819
        << "float_array[2] should be null (converted from NaN), got: " << json["float_array"][2];
1✔
820
    EXPECT_TRUE(json["float_array"][3].isNumeric());
1✔
821
    EXPECT_NEAR(json["float_array"][3].asFloat(), 4.0f, 0.001f);
1✔
822

823
    LogInfo() << "Successfully verified that float/double JSON handling produces valid JSON";
1✔
824
    receiver_mavlink_direct.unsubscribe_message(handle);
1✔
825
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
826
}
1✔
827

828
TEST(SystemTest, MavlinkDirectMessageFiltering)
4✔
829
{
830
    // Test that message filtering works correctly: when subscribed to one message type,
831
    // only that message type is received, not others
832

833
    Mavsdk mavsdk_groundstation{Mavsdk::Configuration{ComponentType::GroundStation}};
1✔
834
    Mavsdk mavsdk_autopilot{Mavsdk::Configuration{ComponentType::Autopilot}};
1✔
835

836
    ASSERT_EQ(
1✔
837
        mavsdk_groundstation.add_any_connection("udpin://0.0.0.0:17008"),
838
        ConnectionResult::Success);
1✔
839
    ASSERT_EQ(
1✔
840
        mavsdk_autopilot.add_any_connection("udpout://127.0.0.1:17008"), ConnectionResult::Success);
1✔
841

842
    // Ground station discovers the autopilot system
843
    auto maybe_system = mavsdk_groundstation.first_autopilot(10.0);
1✔
844
    ASSERT_TRUE(maybe_system);
1✔
845
    auto system = maybe_system.value();
1✔
846
    ASSERT_TRUE(system->has_autopilot());
1✔
847

848
    // Wait for autopilot instance to discover the connection to the ground station
849
    LogInfo() << "Waiting for autopilot system to connect...";
1✔
850
    while (mavsdk_autopilot.systems().size() == 0) {
2✔
851
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
852
    }
853

854
    auto groundstation_system = mavsdk_autopilot.systems().at(0);
1✔
855
    ASSERT_EQ(groundstation_system->component_ids()[0], 190);
2✔
856

857
    // Create separate MavlinkDirect instances
858
    auto receiver_mavlink_direct = MavlinkDirect{system};
1✔
859
    auto sender_mavlink_direct = MavlinkDirect{groundstation_system};
1✔
860

861
    // Set up counters to track what messages are received
862
    std::atomic<int> heartbeat_count{0};
1✔
863
    std::atomic<int> global_position_count{0};
1✔
864
    std::atomic<int> sys_status_count{0};
1✔
865
    std::atomic<int> other_message_count{0};
1✔
866

867
    // Subscribe ONLY to HEARTBEAT messages
868
    auto handle = receiver_mavlink_direct.subscribe_message(
1✔
869
        "HEARTBEAT", [&](MavlinkDirect::MavlinkMessage message) {
2✔
870
            if (message.message_name == "HEARTBEAT") {
2✔
871
                heartbeat_count++;
2✔
872
                LogInfo() << "Received expected HEARTBEAT message";
2✔
873
            } else if (message.message_name == "GLOBAL_POSITION_INT") {
×
874
                global_position_count++;
×
875
                LogErr() << "BUG: Received GLOBAL_POSITION_INT when subscribed to HEARTBEAT!";
×
876
            } else if (message.message_name == "SYS_STATUS") {
×
877
                sys_status_count++;
×
878
                LogErr() << "BUG: Received SYS_STATUS when subscribed to HEARTBEAT!";
×
879
            } else {
880
                other_message_count++;
×
881
                LogErr() << "BUG: Received unexpected message: " << message.message_name;
×
882
            }
883
        });
2✔
884

885
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
886

887
    // Send different message types - the subscription should only receive HEARTBEAT
888

889
    // 1. Send HEARTBEAT (should be received)
890
    MavlinkDirect::MavlinkMessage heartbeat_message;
2✔
891
    heartbeat_message.message_name = "HEARTBEAT";
1✔
892
    heartbeat_message.system_id = 1;
1✔
893
    heartbeat_message.component_id = 1;
1✔
894
    heartbeat_message.fields_json =
895
        R"({"type":2,"autopilot":3,"base_mode":81,"custom_mode":0,"system_status":4,"mavlink_version":3})";
1✔
896

897
    LogInfo() << "Sending HEARTBEAT message (should be received)";
1✔
898
    EXPECT_EQ(
1✔
899
        sender_mavlink_direct.send_message(heartbeat_message), MavlinkDirect::Result::Success);
1✔
900
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
1✔
901

902
    // 2. Send GLOBAL_POSITION_INT (should NOT be received)
903
    MavlinkDirect::MavlinkMessage gps_message;
2✔
904
    gps_message.message_name = "GLOBAL_POSITION_INT";
1✔
905
    gps_message.system_id = 1;
1✔
906
    gps_message.component_id = 1;
1✔
907
    gps_message.fields_json =
908
        R"({"time_boot_ms":12345,"lat":473977418,"lon":-1223974560,"alt":100500,"relative_alt":50250,"vx":100,"vy":-50,"vz":25,"hdg":18000})";
1✔
909

910
    LogInfo() << "Sending GLOBAL_POSITION_INT message (should NOT be received)";
1✔
911
    EXPECT_EQ(sender_mavlink_direct.send_message(gps_message), MavlinkDirect::Result::Success);
1✔
912
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
1✔
913

914
    // 3. Send SYS_STATUS (should NOT be received)
915
    MavlinkDirect::MavlinkMessage sys_status_message;
1✔
916
    sys_status_message.message_name = "SYS_STATUS";
1✔
917
    sys_status_message.system_id = 1;
1✔
918
    sys_status_message.component_id = 1;
1✔
919
    sys_status_message.fields_json =
920
        R"({"onboard_control_sensors_present":1,"onboard_control_sensors_enabled":1,"onboard_control_sensors_health":1,"load":500,"voltage_battery":12000,"current_battery":1000,"battery_remaining":75,"drop_rate_comm":0,"errors_comm":0,"errors_count1":0,"errors_count2":0,"errors_count3":0,"errors_count4":0})";
1✔
921

922
    LogInfo() << "Sending SYS_STATUS message (should NOT be received)";
1✔
923
    EXPECT_EQ(
1✔
924
        sender_mavlink_direct.send_message(sys_status_message), MavlinkDirect::Result::Success);
1✔
925
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
1✔
926

927
    // 4. Send another HEARTBEAT (should be received)
928
    LogInfo() << "Sending second HEARTBEAT message (should be received)";
1✔
929
    EXPECT_EQ(
1✔
930
        sender_mavlink_direct.send_message(heartbeat_message), MavlinkDirect::Result::Success);
1✔
931
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
1✔
932

933
    // Verify filtering worked correctly
934
    EXPECT_GE(heartbeat_count.load(), 2) << "Should have received at least 2 HEARTBEAT messages";
2✔
935
    EXPECT_EQ(global_position_count.load(), 0)
2✔
936
        << "Should NOT have received any GLOBAL_POSITION_INT messages";
1✔
937
    EXPECT_EQ(sys_status_count.load(), 0) << "Should NOT have received any SYS_STATUS messages";
2✔
938
    EXPECT_EQ(other_message_count.load(), 0) << "Should NOT have received any other message types";
2✔
939

940
    LogInfo() << "Message filtering test results:";
1✔
941
    LogInfo() << "  HEARTBEAT received: " << heartbeat_count.load() << " (expected: >= 2)";
2✔
942
    LogInfo() << "  GLOBAL_POSITION_INT received: " << global_position_count.load()
3✔
943
              << " (expected: 0)";
1✔
944
    LogInfo() << "  SYS_STATUS received: " << sys_status_count.load() << " (expected: 0)";
2✔
945
    LogInfo() << "  Other messages received: " << other_message_count.load() << " (expected: 0)";
2✔
946

947
    receiver_mavlink_direct.unsubscribe_message(handle);
1✔
948
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
949
}
1✔
950

951
TEST(SystemTest, MavlinkDirectEmptyStringFiltering)
4✔
952
{
953
    // Test that subscribing with empty string ("") receives all message types
954

955
    Mavsdk mavsdk_groundstation{Mavsdk::Configuration{ComponentType::GroundStation}};
1✔
956
    Mavsdk mavsdk_autopilot{Mavsdk::Configuration{ComponentType::Autopilot}};
1✔
957

958
    ASSERT_EQ(
1✔
959
        mavsdk_groundstation.add_any_connection("udpin://0.0.0.0:17009"),
960
        ConnectionResult::Success);
1✔
961
    ASSERT_EQ(
1✔
962
        mavsdk_autopilot.add_any_connection("udpout://127.0.0.1:17009"), ConnectionResult::Success);
1✔
963

964
    // Ground station discovers the autopilot system
965
    auto maybe_system = mavsdk_groundstation.first_autopilot(10.0);
1✔
966
    ASSERT_TRUE(maybe_system);
1✔
967
    auto system = maybe_system.value();
1✔
968
    ASSERT_TRUE(system->has_autopilot());
1✔
969

970
    // Wait for autopilot instance to discover the connection to the ground station
971
    LogInfo() << "Waiting for autopilot system to connect...";
1✔
972
    while (mavsdk_autopilot.systems().size() == 0) {
2✔
973
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
974
    }
975

976
    auto groundstation_system = mavsdk_autopilot.systems().at(0);
1✔
977
    ASSERT_EQ(groundstation_system->component_ids()[0], 190);
2✔
978

979
    // Create separate MavlinkDirect instances
980
    auto receiver_mavlink_direct = MavlinkDirect{system};
1✔
981
    auto sender_mavlink_direct = MavlinkDirect{groundstation_system};
1✔
982

983
    // Set up counters to track what messages are received
984
    std::atomic<int> heartbeat_count{0};
1✔
985
    std::atomic<int> global_position_count{0};
1✔
986
    std::atomic<int> sys_status_count{0};
1✔
987
    std::atomic<int> total_messages{0};
1✔
988

989
    // Subscribe to ALL messages with empty string
990
    auto handle =
991
        receiver_mavlink_direct.subscribe_message("", [&](MavlinkDirect::MavlinkMessage message) {
6✔
992
            total_messages++;
5✔
993
            if (message.message_name == "HEARTBEAT") {
5✔
994
                heartbeat_count++;
1✔
995
                LogInfo() << "Received HEARTBEAT via empty string subscription";
1✔
996
            } else if (message.message_name == "GLOBAL_POSITION_INT") {
4✔
997
                global_position_count++;
1✔
998
                LogInfo() << "Received GLOBAL_POSITION_INT via empty string subscription";
1✔
999
            } else if (message.message_name == "SYS_STATUS") {
3✔
1000
                sys_status_count++;
1✔
1001
                LogInfo() << "Received SYS_STATUS via empty string subscription";
1✔
1002
            } else {
1003
                LogInfo() << "Received other message via empty string subscription: "
4✔
1004
                          << message.message_name;
2✔
1005
            }
1006
        });
5✔
1007

1008
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
1009

1010
    // Send different message types - all should be received with empty string subscription
1011

1012
    // 1. Send HEARTBEAT
1013
    MavlinkDirect::MavlinkMessage heartbeat_message;
2✔
1014
    heartbeat_message.message_name = "HEARTBEAT";
1✔
1015
    heartbeat_message.system_id = 1;
1✔
1016
    heartbeat_message.component_id = 1;
1✔
1017
    heartbeat_message.fields_json =
1018
        R"({"type":2,"autopilot":3,"base_mode":81,"custom_mode":0,"system_status":4,"mavlink_version":3})";
1✔
1019

1020
    LogInfo() << "Sending HEARTBEAT message";
1✔
1021
    EXPECT_EQ(
1✔
1022
        sender_mavlink_direct.send_message(heartbeat_message), MavlinkDirect::Result::Success);
1✔
1023
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
1✔
1024

1025
    // 2. Send GLOBAL_POSITION_INT
1026
    MavlinkDirect::MavlinkMessage gps_message;
2✔
1027
    gps_message.message_name = "GLOBAL_POSITION_INT";
1✔
1028
    gps_message.system_id = 1;
1✔
1029
    gps_message.component_id = 1;
1✔
1030
    gps_message.fields_json =
1031
        R"({"time_boot_ms":12345,"lat":473977418,"lon":-1223974560,"alt":100500,"relative_alt":50250,"vx":100,"vy":-50,"vz":25,"hdg":18000})";
1✔
1032

1033
    LogInfo() << "Sending GLOBAL_POSITION_INT message";
1✔
1034
    EXPECT_EQ(sender_mavlink_direct.send_message(gps_message), MavlinkDirect::Result::Success);
1✔
1035
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
1✔
1036

1037
    // 3. Send SYS_STATUS
1038
    MavlinkDirect::MavlinkMessage sys_status_message;
1✔
1039
    sys_status_message.message_name = "SYS_STATUS";
1✔
1040
    sys_status_message.system_id = 1;
1✔
1041
    sys_status_message.component_id = 1;
1✔
1042
    sys_status_message.fields_json =
1043
        R"({"onboard_control_sensors_present":1,"onboard_control_sensors_enabled":1,"onboard_control_sensors_health":1,"load":500,"voltage_battery":12000,"current_battery":1000,"battery_remaining":75,"drop_rate_comm":0,"errors_comm":0,"errors_count1":0,"errors_count2":0,"errors_count3":0,"errors_count4":0})";
1✔
1044

1045
    LogInfo() << "Sending SYS_STATUS message";
1✔
1046
    EXPECT_EQ(
1✔
1047
        sender_mavlink_direct.send_message(sys_status_message), MavlinkDirect::Result::Success);
1✔
1048
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
1✔
1049

1050
    // Verify empty string subscription received ALL message types
1051
    EXPECT_GE(heartbeat_count.load(), 1) << "Should have received at least 1 HEARTBEAT message";
2✔
1052
    EXPECT_GE(global_position_count.load(), 1)
2✔
1053
        << "Should have received at least 1 GLOBAL_POSITION_INT message";
1✔
1054
    EXPECT_GE(sys_status_count.load(), 1) << "Should have received at least 1 SYS_STATUS message";
2✔
1055
    EXPECT_GE(total_messages.load(), 3) << "Should have received at least 3 total messages";
2✔
1056

1057
    LogInfo() << "Empty string filtering test results:";
1✔
1058
    LogInfo() << "  HEARTBEAT received: " << heartbeat_count.load() << " (expected: >= 1)";
2✔
1059
    LogInfo() << "  GLOBAL_POSITION_INT received: " << global_position_count.load()
3✔
1060
              << " (expected: >= 1)";
1✔
1061
    LogInfo() << "  SYS_STATUS received: " << sys_status_count.load() << " (expected: >= 1)";
2✔
1062
    LogInfo() << "  Total messages received: " << total_messages.load() << " (expected: >= 3)";
2✔
1063

1064
    receiver_mavlink_direct.unsubscribe_message(handle);
1✔
1065
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
1066
}
1✔
1067

1068
TEST(SystemTest, MavlinkDirectMultipleSubscriptions)
4✔
1069
{
1070
    // Test that having multiple subscriptions works correctly:
1071
    // - One subscription for all messages ("")
1072
    // - One subscription for specific message ("HEARTBEAT")
1073
    // This should expose any issues with shared CallbackList behavior
1074

1075
    Mavsdk mavsdk_groundstation{Mavsdk::Configuration{ComponentType::GroundStation}};
1✔
1076
    Mavsdk mavsdk_autopilot{Mavsdk::Configuration{ComponentType::Autopilot}};
1✔
1077

1078
    ASSERT_EQ(
1✔
1079
        mavsdk_groundstation.add_any_connection("udpin://0.0.0.0:17010"),
1080
        ConnectionResult::Success);
1✔
1081
    ASSERT_EQ(
1✔
1082
        mavsdk_autopilot.add_any_connection("udpout://127.0.0.1:17010"), ConnectionResult::Success);
1✔
1083

1084
    // Ground station discovers the autopilot system
1085
    auto maybe_system = mavsdk_groundstation.first_autopilot(10.0);
1✔
1086
    ASSERT_TRUE(maybe_system);
1✔
1087
    auto system = maybe_system.value();
1✔
1088
    ASSERT_TRUE(system->has_autopilot());
1✔
1089

1090
    // Wait for autopilot instance to discover the connection to the ground station
1091
    LogInfo() << "Waiting for autopilot system to connect...";
1✔
1092
    while (mavsdk_autopilot.systems().size() == 0) {
1✔
1093
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
×
1094
    }
1095

1096
    auto groundstation_system = mavsdk_autopilot.systems().at(0);
1✔
1097
    ASSERT_EQ(groundstation_system->component_ids()[0], 190);
2✔
1098

1099
    // Create separate MavlinkDirect instances
1100
    auto receiver_mavlink_direct = MavlinkDirect{system};
1✔
1101
    auto sender_mavlink_direct = MavlinkDirect{groundstation_system};
1✔
1102

1103
    // Set up counters for the "all messages" subscription
1104
    std::atomic<int> all_messages_heartbeat{0};
1✔
1105
    std::atomic<int> all_messages_gps{0};
1✔
1106
    std::atomic<int> all_messages_sys_status{0};
1✔
1107
    std::atomic<int> all_messages_total{0};
1✔
1108

1109
    // Set up counters for the "HEARTBEAT only" subscription
1110
    std::atomic<int> heartbeat_only_heartbeat{0};
1✔
1111
    std::atomic<int> heartbeat_only_other{0};
1✔
1112

1113
    // Subscription 1: All messages (empty string)
1114
    auto handle_all =
1115
        receiver_mavlink_direct.subscribe_message("", [&](MavlinkDirect::MavlinkMessage message) {
9✔
1116
            all_messages_total++;
8✔
1117
            if (message.message_name == "HEARTBEAT") {
8✔
1118
                all_messages_heartbeat++;
4✔
1119
                LogInfo() << "ALL subscription received HEARTBEAT";
4✔
1120
            } else if (message.message_name == "GLOBAL_POSITION_INT") {
4✔
1121
                all_messages_gps++;
1✔
1122
                LogInfo() << "ALL subscription received GLOBAL_POSITION_INT";
1✔
1123
            } else if (message.message_name == "SYS_STATUS") {
3✔
1124
                all_messages_sys_status++;
1✔
1125
                LogInfo() << "ALL subscription received SYS_STATUS";
1✔
1126
            } else {
1127
                LogInfo() << "ALL subscription received other: " << message.message_name;
2✔
1128
            }
1129
        });
8✔
1130

1131
    // Subscription 2: HEARTBEAT only
1132
    auto handle_heartbeat = receiver_mavlink_direct.subscribe_message(
1✔
1133
        "HEARTBEAT", [&](MavlinkDirect::MavlinkMessage message) {
4✔
1134
            if (message.message_name == "HEARTBEAT") {
4✔
1135
                heartbeat_only_heartbeat++;
4✔
1136
                LogInfo() << "HEARTBEAT-only subscription received HEARTBEAT";
4✔
1137
            } else {
1138
                heartbeat_only_other++;
×
1139
                LogErr() << "BUG: HEARTBEAT-only subscription received: " << message.message_name;
×
1140
            }
1141
        });
4✔
1142

1143
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
1144

1145
    // Send different message types
1146

1147
    // 1. Send HEARTBEAT (both subscriptions should receive it)
1148
    MavlinkDirect::MavlinkMessage heartbeat_message;
2✔
1149
    heartbeat_message.message_name = "HEARTBEAT";
1✔
1150
    heartbeat_message.system_id = 1;
1✔
1151
    heartbeat_message.component_id = 1;
1✔
1152
    heartbeat_message.fields_json =
1153
        R"({"type":2,"autopilot":3,"base_mode":81,"custom_mode":0,"system_status":4,"mavlink_version":3})";
1✔
1154

1155
    LogInfo() << "Sending HEARTBEAT message";
1✔
1156
    EXPECT_EQ(
1✔
1157
        sender_mavlink_direct.send_message(heartbeat_message), MavlinkDirect::Result::Success);
1✔
1158
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
1✔
1159

1160
    // 2. Send GLOBAL_POSITION_INT (only ALL subscription should receive it)
1161
    MavlinkDirect::MavlinkMessage gps_message;
2✔
1162
    gps_message.message_name = "GLOBAL_POSITION_INT";
1✔
1163
    gps_message.system_id = 1;
1✔
1164
    gps_message.component_id = 1;
1✔
1165
    gps_message.fields_json =
1166
        R"({"time_boot_ms":12345,"lat":473977418,"lon":-1223974560,"alt":100500,"relative_alt":50250,"vx":100,"vy":-50,"vz":25,"hdg":18000})";
1✔
1167

1168
    LogInfo() << "Sending GLOBAL_POSITION_INT message";
1✔
1169
    EXPECT_EQ(sender_mavlink_direct.send_message(gps_message), MavlinkDirect::Result::Success);
1✔
1170
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
1✔
1171

1172
    // 3. Send SYS_STATUS (only ALL subscription should receive it)
1173
    MavlinkDirect::MavlinkMessage sys_status_message;
1✔
1174
    sys_status_message.message_name = "SYS_STATUS";
1✔
1175
    sys_status_message.system_id = 1;
1✔
1176
    sys_status_message.component_id = 1;
1✔
1177
    sys_status_message.fields_json =
1178
        R"({"onboard_control_sensors_present":1,"onboard_control_sensors_enabled":1,"onboard_control_sensors_health":1,"load":500,"voltage_battery":12000,"current_battery":1000,"battery_remaining":75,"drop_rate_comm":0,"errors_comm":0,"errors_count1":0,"errors_count4":0})";
1✔
1179

1180
    LogInfo() << "Sending SYS_STATUS message";
1✔
1181
    EXPECT_EQ(
1✔
1182
        sender_mavlink_direct.send_message(sys_status_message), MavlinkDirect::Result::Success);
1✔
1183
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
1✔
1184

1185
    // 4. Send another HEARTBEAT (both subscriptions should receive it)
1186
    LogInfo() << "Sending second HEARTBEAT message";
1✔
1187
    EXPECT_EQ(
1✔
1188
        sender_mavlink_direct.send_message(heartbeat_message), MavlinkDirect::Result::Success);
1✔
1189
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
1✔
1190

1191
    // Verify correct filtering behavior
1192
    LogInfo() << "Multiple subscriptions test results:";
1✔
1193
    LogInfo() << "  ALL subscription - HEARTBEAT: " << all_messages_heartbeat.load()
3✔
1194
              << " (expected: >= 2)";
1✔
1195
    LogInfo() << "  ALL subscription - GPS: " << all_messages_gps.load() << " (expected: >= 1)";
2✔
1196
    LogInfo() << "  ALL subscription - SYS_STATUS: " << all_messages_sys_status.load()
3✔
1197
              << " (expected: >= 1)";
1✔
1198
    LogInfo() << "  ALL subscription - Total: " << all_messages_total.load() << " (expected: >= 4)";
2✔
1199
    LogInfo() << "  HEARTBEAT-only - HEARTBEAT: " << heartbeat_only_heartbeat.load()
3✔
1200
              << " (expected: >= 2)";
1✔
1201
    LogInfo() << "  HEARTBEAT-only - Other: " << heartbeat_only_other.load() << " (expected: 0)";
2✔
1202

1203
    // ALL subscription should receive everything
1204
    EXPECT_GE(all_messages_heartbeat.load(), 2)
2✔
1205
        << "ALL subscription should receive HEARTBEAT messages";
1✔
1206
    EXPECT_GE(all_messages_gps.load(), 1) << "ALL subscription should receive GLOBAL_POSITION_INT";
2✔
1207
    EXPECT_GE(all_messages_sys_status.load(), 1) << "ALL subscription should receive SYS_STATUS";
2✔
1208
    EXPECT_GE(all_messages_total.load(), 4) << "ALL subscription should receive all sent messages";
2✔
1209

1210
    // HEARTBEAT-only subscription should only receive HEARTBEAT
1211
    EXPECT_GE(heartbeat_only_heartbeat.load(), 2)
2✔
1212
        << "HEARTBEAT-only should receive HEARTBEAT messages";
1✔
1213
    EXPECT_EQ(heartbeat_only_other.load(), 0)
2✔
1214
        << "HEARTBEAT-only should NOT receive any other messages";
1✔
1215

1216
    receiver_mavlink_direct.unsubscribe_message(handle_all);
1✔
1217
    receiver_mavlink_direct.unsubscribe_message(handle_heartbeat);
1✔
1218
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
1219
}
1✔
1220

1221
TEST(SystemTest, MavlinkDirectLargeUint64)
4✔
1222
{
1223
    // Test GPS_RAW_INT with time_usec field > 2^32 to verify proper uint64 handling
1224

1225
    Mavsdk mavsdk_groundstation{Mavsdk::Configuration{ComponentType::GroundStation}};
1✔
1226
    Mavsdk mavsdk_autopilot{Mavsdk::Configuration{ComponentType::Autopilot}};
1✔
1227

1228
    ASSERT_EQ(
1✔
1229
        mavsdk_groundstation.add_any_connection("udpin://0.0.0.0:17011"),
1230
        ConnectionResult::Success);
1✔
1231
    ASSERT_EQ(
1✔
1232
        mavsdk_autopilot.add_any_connection("udpout://127.0.0.1:17011"), ConnectionResult::Success);
1✔
1233

1234
    // Ground station discovers the autopilot system
1235
    auto maybe_system = mavsdk_groundstation.first_autopilot(10.0);
1✔
1236
    ASSERT_TRUE(maybe_system);
1✔
1237
    auto system = maybe_system.value();
1✔
1238
    ASSERT_TRUE(system->has_autopilot());
1✔
1239

1240
    // Wait for autopilot instance to discover the connection to the ground station
1241
    LogInfo() << "Waiting for autopilot system to connect...";
1✔
1242
    while (mavsdk_autopilot.systems().size() == 0) {
1✔
1243
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
×
1244
    }
1245

1246
    auto groundstation_system = mavsdk_autopilot.systems().at(0);
1✔
1247
    ASSERT_EQ(groundstation_system->component_ids()[0], 190);
2✔
1248

1249
    auto receiver_mavlink_direct = MavlinkDirect{system};
1✔
1250
    auto sender_mavlink_direct = MavlinkDirect{groundstation_system};
1✔
1251

1252
    auto prom = std::promise<MavlinkDirect::MavlinkMessage>();
1✔
1253
    auto fut = prom.get_future();
1✔
1254

1255
    // Subscribe to GPS_RAW_INT messages
1256
    auto handle = receiver_mavlink_direct.subscribe_message(
1✔
1257
        "GPS_RAW_INT", [&prom](MavlinkDirect::MavlinkMessage message) {
2✔
1258
            LogInfo() << "Received GPS_RAW_INT: " << message.fields_json;
1✔
1259
            prom.set_value(message);
1✔
1260
        });
1✔
1261

1262
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
1263

1264
    // Send GPS_RAW_INT with time_usec > 2^32 (5000000000 microseconds = ~83 minutes)
1265
    // This value requires full uint64 representation
1266
    MavlinkDirect::MavlinkMessage gps_raw_message;
1✔
1267
    gps_raw_message.message_name = "GPS_RAW_INT";
1✔
1268
    gps_raw_message.system_id = 1;
1✔
1269
    gps_raw_message.component_id = 1;
1✔
1270
    gps_raw_message.target_system_id = 0;
1✔
1271
    gps_raw_message.target_component_id = 0;
1✔
1272
    gps_raw_message.fields_json =
1273
        R"({"time_usec":5000000000,"fix_type":3,"lat":473977418,"lon":-1223974560,"alt":100500,"eph":100,"epv":150,"vel":500,"cog":18000,"satellites_visible":12})";
1✔
1274

1275
    LogInfo() << "Sending GPS_RAW_INT with time_usec=5000000000 (> 2^32)";
1✔
1276
    auto result = sender_mavlink_direct.send_message(gps_raw_message);
1✔
1277
    EXPECT_EQ(result, MavlinkDirect::Result::Success);
1✔
1278

1279
    // Wait for message to be received
1280
    ASSERT_EQ(fut.wait_for(std::chrono::seconds(5)), std::future_status::ready);
1✔
1281

1282
    auto received_message = fut.get();
1✔
1283

1284
    EXPECT_EQ(received_message.message_name, "GPS_RAW_INT");
1✔
1285
    EXPECT_EQ(received_message.system_id, 1);
1✔
1286
    EXPECT_EQ(received_message.component_id, 1);
1✔
1287

1288
    // Parse JSON to verify uint64 field value is preserved
1289
    Json::Value json;
1✔
1290
    Json::Reader reader;
1✔
1291
    ASSERT_TRUE(reader.parse(received_message.fields_json, json))
1✔
1292
        << "Failed to parse received JSON: " << received_message.fields_json;
1✔
1293

1294
    // Verify time_usec field is present and has the correct large value
1295
    ASSERT_TRUE(json.isMember("time_usec")) << "time_usec field missing from JSON";
1✔
1296
    EXPECT_EQ(json["time_usec"].asUInt64(), 5000000000ULL)
1✔
1297
        << "time_usec value incorrect: expected 5000000000, got " << json["time_usec"].asUInt64();
1✔
1298

1299
    // Verify other fields for completeness
1300
    EXPECT_EQ(json["fix_type"].asUInt(), 3u);
1✔
1301
    EXPECT_EQ(json["lat"].asInt(), 473977418);
1✔
1302
    EXPECT_EQ(json["lon"].asInt(), -1223974560);
1✔
1303
    EXPECT_EQ(json["alt"].asInt(), 100500);
1✔
1304
    EXPECT_EQ(json["satellites_visible"].asUInt(), 12u);
1✔
1305

1306
    LogInfo() << "Successfully verified uint64 handling for time_usec > 2^32";
1✔
1307
    receiver_mavlink_direct.unsubscribe_message(handle);
1✔
1308
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
1309
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc