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

mavlink / MAVSDK / 21724056693

05 Feb 2026 06:42PM UTC coverage: 49.043% (+0.08%) from 48.959%
21724056693

push

github

web-flow
core: fix MAVLink message sequence (#2762)

* core: fix MAVLink message sequence

The MAVLink headers use an inline function to get the mavlink channel,
so we end up having separate sequence numbers in different translation
units. By defining this function ourselves and linking it later, we make
sure to use the same sequence everywhere.

This should fix seq numbering that's invalid (per component).

We also add a system test to actually test this.

* system_tests: fixup thread sanitizer issues

* system_tests: use TCP for seq to avoid drops

51 of 56 new or added lines in 6 files covered. (91.07%)

6 existing lines in 5 files now uncovered.

18367 of 37451 relevant lines covered (49.04%)

672.8 hits per line

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

98.52
/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) {
2✔
108
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
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) {
1✔
UNCOV
634
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
×
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) {
2✔
1093
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
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) {
7✔
1116
            all_messages_total++;
6✔
1117
            if (message.message_name == "HEARTBEAT") {
6✔
1118
                all_messages_heartbeat++;
2✔
1119
                LogInfo() << "ALL subscription received HEARTBEAT";
2✔
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
        });
6✔
1130

1131
    // Subscription 2: HEARTBEAT only
1132
    auto handle_heartbeat = receiver_mavlink_direct.subscribe_message(
1✔
1133
        "HEARTBEAT", [&](MavlinkDirect::MavlinkMessage message) {
2✔
1134
            if (message.message_name == "HEARTBEAT") {
2✔
1135
                heartbeat_only_heartbeat++;
2✔
1136
                LogInfo() << "HEARTBEAT-only subscription received HEARTBEAT";
2✔
1137
            } else {
1138
                heartbeat_only_other++;
×
1139
                LogErr() << "BUG: HEARTBEAT-only subscription received: " << message.message_name;
×
1140
            }
1141
        });
2✔
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) {
2✔
1243
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
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

© 2026 Coveralls, Inc