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

mavlink / MAVSDK / 18393465410

10 Oct 2025 01:10AM UTC coverage: 48.242% (+0.6%) from 47.661%
18393465410

Pull #2684

github

web-flow
Merge 024505a21 into a58aaa9e1
Pull Request #2684: Re-connection fixes and system tests

229 of 233 new or added lines in 2 files covered. (98.28%)

16 existing lines in 3 files now uncovered.

17421 of 36112 relevant lines covered (48.24%)

472.42 hits per line

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

98.45
/src/system_tests/connections.cpp
1
#include "log.h"
2
#include "mavsdk.h"
3
#include "plugins/mavlink_direct/mavlink_direct.h"
4
#include <chrono>
5
#include <thread>
6
#include <atomic>
7
#include <gtest/gtest.h>
8

9
using namespace mavsdk;
10

11
TEST(SystemTest, TcpConnectionReconnectionFromServerSide)
4✔
12
{
13
    // Test that TCP client properly reconnects when server closes the connection
14

15
    const std::string tcp_port = "17400";
1✔
16
    const std::string server_url = "tcpin://0.0.0.0:" + tcp_port;
1✔
17
    const std::string client_url = "tcpout://127.0.0.1:" + tcp_port;
1✔
18

19
    LogInfo() << "=== Phase 1: Start server and client ===";
1✔
20

21
    // Start server
22
    Mavsdk server_mavsdk{Mavsdk::Configuration{ComponentType::Autopilot}};
1✔
23
    auto [server_result, server_handle] = server_mavsdk.add_any_connection_with_handle(server_url);
1✔
24
    ASSERT_EQ(server_result, ConnectionResult::Success);
1✔
25

26
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
27

28
    // Start client
29
    Mavsdk client_mavsdk{Mavsdk::Configuration{ComponentType::GroundStation}};
1✔
30
    ASSERT_EQ(client_mavsdk.add_any_connection(client_url), ConnectionResult::Success);
1✔
31

32
    // Wait for discovery
33
    LogInfo() << "Waiting for client to discover server...";
1✔
34
    auto maybe_system = client_mavsdk.first_autopilot(10.0);
1✔
35
    ASSERT_TRUE(maybe_system) << "Client failed to discover server";
1✔
36
    auto system = maybe_system.value();
1✔
37

38
    LogInfo() << "=== Phase 2: Verify initial connectivity ===";
1✔
39

40
    // Track automatic heartbeats (MAVSDK sends these every second automatically)
41
    std::atomic<int> message_count{0};
1✔
42
    MavlinkDirect client_mavlink{system};
1✔
43

44
    auto handle = client_mavlink.subscribe_message(
1✔
45
        "HEARTBEAT", [&message_count](MavlinkDirect::MavlinkMessage) {
12✔
46
            message_count++;
6✔
47
            LogInfo() << "Client received HEARTBEAT (total: " << message_count.load() << ")";
12✔
48
        });
6✔
49

50
    // Wait for automatic heartbeats to arrive (sent every 1 second by MAVSDK)
51
    LogInfo() << "Waiting for automatic heartbeats...";
1✔
52
    std::this_thread::sleep_for(std::chrono::seconds(2));
1✔
53
    int messages_before = message_count.load();
1✔
54
    EXPECT_GE(messages_before, 1) << "Client didn't receive automatic heartbeats";
1✔
55
    LogInfo() << "Received " << messages_before << " automatic heartbeat(s) from server";
1✔
56

57
    LogInfo() << "=== Phase 3: Remove server connection (simulates server closing connection) ===";
1✔
58

59
    // Remove the server connection - this closes the socket
60
    server_mavsdk.remove_connection(server_handle);
1✔
61

62
    // Give client time to detect disconnection
63
    LogInfo() << "Waiting for client to detect disconnection...";
1✔
64
    LogInfo() << "Expected log: 'TCP connection closed, trying to reconnect...'";
1✔
65
    std::this_thread::sleep_for(std::chrono::seconds(3));
1✔
66

67
    LogInfo() << "=== Phase 4: Re-add server connection (client should reconnect) ===";
1✔
68

69
    // Re-add the server connection on same port
70
    auto [server_result2, server_handle2] =
1✔
71
        server_mavsdk.add_any_connection_with_handle(server_url);
1✔
72
    ASSERT_EQ(server_result2, ConnectionResult::Success);
1✔
73

74
    // Wait for the reconnected client to be discovered
75
    LogInfo() << "Waiting for server to discover reconnected client...";
1✔
76
    auto start = std::chrono::steady_clock::now();
1✔
77
    while (server_mavsdk.systems().size() == 0) {
1✔
NEW
78
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
×
NEW
79
        auto elapsed = std::chrono::steady_clock::now() - start;
×
NEW
80
        if (elapsed > std::chrono::seconds(10)) {
×
NEW
81
            FAIL() << "Client did not reconnect within 10 seconds";
×
82
        }
83
    }
84

85
    LogInfo() << "Client reconnected!";
1✔
86

87
    LogInfo() << "=== Phase 5: Verify message flow after reconnection ===";
1✔
88

89
    int messages_before_reconnect = message_count.load();
1✔
90
    LogInfo() << "Messages before waiting: " << messages_before_reconnect;
1✔
91

92
    // Wait for automatic heartbeats to resume after reconnection
93
    LogInfo() << "Waiting for automatic heartbeats to resume...";
1✔
94
    std::this_thread::sleep_for(std::chrono::seconds(2));
1✔
95
    int messages_after_reconnect = message_count.load();
1✔
96

97
    LogInfo() << "Messages after waiting: " << messages_after_reconnect;
1✔
98

99
    EXPECT_GT(messages_after_reconnect, messages_before_reconnect)
1✔
100
        << "Client did NOT receive automatic heartbeats after reconnection!";
1✔
101

102
    LogInfo() << "=== SUCCESS: TCP client reconnection verified! ===";
1✔
103
    LogInfo() << "Total messages received: " << message_count.load();
2✔
104

105
    // Cleanup
106
    client_mavlink.unsubscribe_message(handle);
1✔
107
    server_mavsdk.remove_connection(server_handle2);
1✔
108
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
109
}
1✔
110

111
TEST(SystemTest, TcpConnectionReconnectionFromClientSide)
4✔
112
{
113
    // Test that TCP client properly reconnects when it closes and reopens its own connection
114

115
    const std::string tcp_port = "17500";
1✔
116
    const std::string server_url = "tcpin://0.0.0.0:" + tcp_port;
1✔
117
    const std::string client_url = "tcpout://127.0.0.1:" + tcp_port;
1✔
118

119
    LogInfo() << "=== Phase 1: Start server ===";
1✔
120

121
    // Start server
122
    Mavsdk server_mavsdk{Mavsdk::Configuration{ComponentType::Autopilot}};
1✔
123
    auto [server_result, server_handle] = server_mavsdk.add_any_connection_with_handle(server_url);
1✔
124
    ASSERT_EQ(server_result, ConnectionResult::Success);
1✔
125

126
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
127

128
    LogInfo() << "=== Phase 2: Start client and verify initial connectivity ===";
1✔
129

130
    // Start client
131
    Mavsdk client_mavsdk{Mavsdk::Configuration{ComponentType::GroundStation}};
1✔
132
    auto [client_result, client_handle] = client_mavsdk.add_any_connection_with_handle(client_url);
1✔
133
    ASSERT_EQ(client_result, ConnectionResult::Success);
1✔
134

135
    // Wait for discovery
136
    LogInfo() << "Waiting for client to discover server...";
1✔
137
    auto maybe_system = client_mavsdk.first_autopilot(10.0);
1✔
138
    ASSERT_TRUE(maybe_system) << "Client failed to discover server";
1✔
139
    auto system = maybe_system.value();
1✔
140

141
    // Track automatic heartbeats (MAVSDK sends these every second automatically)
142
    std::atomic<int> message_count{0};
1✔
143
    MavlinkDirect client_mavlink{system};
1✔
144

145
    auto handle = client_mavlink.subscribe_message(
1✔
146
        "HEARTBEAT", [&message_count](MavlinkDirect::MavlinkMessage) {
6✔
147
            message_count++;
3✔
148
            LogInfo() << "Client received HEARTBEAT (total: " << message_count.load() << ")";
6✔
149
        });
3✔
150

151
    // Wait for automatic heartbeats to arrive (sent every 1 second by MAVSDK)
152
    LogInfo() << "Waiting for automatic heartbeats...";
1✔
153
    std::this_thread::sleep_for(std::chrono::seconds(2));
1✔
154
    int messages_before = message_count.load();
1✔
155
    EXPECT_GE(messages_before, 1) << "Client didn't receive automatic heartbeats";
1✔
156
    LogInfo() << "Received " << messages_before << " automatic heartbeat(s) from server";
1✔
157

158
    LogInfo() << "=== Phase 3: Remove client connection (client closes its connection) ===";
1✔
159

160
    // Unsubscribe before removing connection to avoid callback issues
161
    client_mavlink.unsubscribe_message(handle);
1✔
162

163
    // Remove the client connection - this closes the socket on the client side
164
    client_mavsdk.remove_connection(client_handle);
1✔
165

166
    // Give time for disconnection to be processed
167
    LogInfo() << "Waiting after client disconnection...";
1✔
168
    std::this_thread::sleep_for(std::chrono::seconds(2));
1✔
169

170
    LogInfo() << "=== Phase 4: Re-add client connection (client reconnects) ===";
1✔
171

172
    // Re-add the client connection
173
    auto [client_result2, client_handle2] =
1✔
174
        client_mavsdk.add_any_connection_with_handle(client_url);
1✔
175
    ASSERT_EQ(client_result2, ConnectionResult::Success);
1✔
176

177
    // Wait for the reconnected client to discover the server again
178
    LogInfo() << "Waiting for client to rediscover server...";
1✔
179
    auto maybe_system2 = client_mavsdk.first_autopilot(10.0);
1✔
180
    ASSERT_TRUE(maybe_system2) << "Client failed to rediscover server after reconnection";
1✔
181
    auto system2 = maybe_system2.value();
2✔
182

183
    // Give more time for the server to properly detect and accept the new connection
184
    LogInfo() << "Waiting for server to accept new client connection...";
1✔
185
    std::this_thread::sleep_for(std::chrono::seconds(2));
1✔
186

187
    LogInfo() << "Client reconnected!";
1✔
188

189
    LogInfo() << "=== Phase 5: Verify message flow after reconnection ===";
1✔
190

191
    // Re-create MavlinkDirect with new system and subscribe again
192
    MavlinkDirect client_mavlink2{system2};
1✔
193
    std::atomic<int> message_count2{0};
1✔
194

195
    auto handle2 = client_mavlink2.subscribe_message(
1✔
196
        "HEARTBEAT", [&message_count2](MavlinkDirect::MavlinkMessage) {
4✔
197
            message_count2++;
2✔
198
            LogInfo() << "Client received HEARTBEAT after reconnection (total: "
2✔
199
                      << message_count2.load() << ")";
4✔
200
        });
2✔
201

202
    // Wait for automatic heartbeats to arrive after reconnection
203
    LogInfo() << "Waiting for automatic heartbeats after reconnection...";
1✔
204
    std::this_thread::sleep_for(std::chrono::seconds(2));
1✔
205
    int messages_after = message_count2.load();
1✔
206

207
    LogInfo() << "Messages after reconnection: " << messages_after;
1✔
208

209
    EXPECT_GE(messages_after, 1)
1✔
210
        << "Client did NOT receive automatic heartbeats after reconnection!";
1✔
211

212
    LogInfo() << "=== SUCCESS: TCP client-side reconnection verified! ===";
1✔
213
    LogInfo() << "Messages before disconnect: " << messages_before;
1✔
214
    LogInfo() << "Messages after reconnect: " << messages_after;
1✔
215

216
    // Cleanup
217
    client_mavlink2.unsubscribe_message(handle2);
1✔
218
    client_mavsdk.remove_connection(client_handle2);
1✔
219
    server_mavsdk.remove_connection(server_handle);
1✔
220
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
221
}
1✔
222

223
TEST(SystemTest, UdpConnectionReconnectionFromClientSide)
4✔
224
{
225
    // Test that UDP client (udpout) can be removed and re-added properly
226
    // UDP is connectionless, so this tests the connection management APIs
227

228
    const std::string udp_port = "17600";
1✔
229
    const std::string server_url = "udpin://0.0.0.0:" + udp_port;
1✔
230
    const std::string client_url = "udpout://127.0.0.1:" + udp_port;
1✔
231

232
    LogInfo() << "=== Phase 1: Start server and client ===";
1✔
233

234
    // Start server
235
    Mavsdk server_mavsdk{Mavsdk::Configuration{ComponentType::Autopilot}};
1✔
236
    auto [server_result, server_handle] = server_mavsdk.add_any_connection_with_handle(server_url);
1✔
237
    ASSERT_EQ(server_result, ConnectionResult::Success);
1✔
238

239
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
240

241
    // Start client
242
    Mavsdk client_mavsdk{Mavsdk::Configuration{ComponentType::GroundStation}};
1✔
243
    auto [client_result, client_handle] = client_mavsdk.add_any_connection_with_handle(client_url);
1✔
244
    ASSERT_EQ(client_result, ConnectionResult::Success);
1✔
245

246
    // Wait for discovery
247
    LogInfo() << "Waiting for client to discover server...";
1✔
248
    auto maybe_system = client_mavsdk.first_autopilot(10.0);
1✔
249
    ASSERT_TRUE(maybe_system) << "Client failed to discover server";
1✔
250
    auto system = maybe_system.value();
1✔
251

252
    LogInfo() << "=== Phase 2: Verify initial connectivity ===";
1✔
253

254
    // Track automatic heartbeats
255
    std::atomic<int> message_count{0};
1✔
256
    MavlinkDirect client_mavlink{system};
1✔
257

258
    auto handle = client_mavlink.subscribe_message(
1✔
259
        "HEARTBEAT", [&message_count](MavlinkDirect::MavlinkMessage) {
2✔
260
            message_count++;
1✔
261
            LogInfo() << "Client received HEARTBEAT (total: " << message_count.load() << ")";
2✔
262
        });
1✔
263

264
    // Wait for automatic heartbeats to arrive
265
    LogInfo() << "Waiting for automatic heartbeats...";
1✔
266
    std::this_thread::sleep_for(std::chrono::seconds(2));
1✔
267
    int messages_before = message_count.load();
1✔
268
    EXPECT_GE(messages_before, 1) << "Client didn't receive automatic heartbeats";
1✔
269
    LogInfo() << "Received " << messages_before << " automatic heartbeat(s) from server";
1✔
270

271
    LogInfo() << "=== Phase 3: Remove and re-add client connection ===";
1✔
272

273
    // Unsubscribe before removing connection
274
    client_mavlink.unsubscribe_message(handle);
1✔
275

276
    // Remove the client connection
277
    client_mavsdk.remove_connection(client_handle);
1✔
278

279
    // Give time for disconnection
280
    LogInfo() << "Waiting after client disconnection...";
1✔
281
    std::this_thread::sleep_for(std::chrono::seconds(2));
1✔
282

283
    // Re-add the client connection
284
    auto [client_result2, client_handle2] =
1✔
285
        client_mavsdk.add_any_connection_with_handle(client_url);
1✔
286
    ASSERT_EQ(client_result2, ConnectionResult::Success);
1✔
287

288
    // Wait for the reconnected client to discover the server again
289
    LogInfo() << "Waiting for client to rediscover server...";
1✔
290
    auto maybe_system2 = client_mavsdk.first_autopilot(10.0);
1✔
291
    ASSERT_TRUE(maybe_system2) << "Client failed to rediscover server after reconnection";
1✔
292
    auto system2 = maybe_system2.value();
2✔
293

294
    LogInfo() << "Client reconnected!";
1✔
295

296
    LogInfo() << "=== Phase 4: Verify message flow after reconnection ===";
1✔
297

298
    // Re-create MavlinkDirect with new system and subscribe again
299
    MavlinkDirect client_mavlink2{system2};
1✔
300
    std::atomic<int> message_count2{0};
1✔
301

302
    auto handle2 = client_mavlink2.subscribe_message(
1✔
303
        "HEARTBEAT", [&message_count2](MavlinkDirect::MavlinkMessage) {
4✔
304
            message_count2++;
2✔
305
            LogInfo() << "Client received HEARTBEAT after reconnection (total: "
2✔
306
                      << message_count2.load() << ")";
4✔
307
        });
2✔
308

309
    // Wait for automatic heartbeats to arrive after reconnection
310
    LogInfo() << "Waiting for automatic heartbeats after reconnection...";
1✔
311
    std::this_thread::sleep_for(std::chrono::seconds(2));
1✔
312
    int messages_after = message_count2.load();
1✔
313

314
    LogInfo() << "Messages after reconnection: " << messages_after;
1✔
315

316
    EXPECT_GE(messages_after, 1)
1✔
317
        << "Client did NOT receive automatic heartbeats after reconnection!";
1✔
318

319
    LogInfo() << "=== SUCCESS: UDP client-side reconnection verified! ===";
1✔
320
    LogInfo() << "Messages before disconnect: " << messages_before;
1✔
321
    LogInfo() << "Messages after reconnect: " << messages_after;
1✔
322

323
    // Cleanup
324
    client_mavlink2.unsubscribe_message(handle2);
1✔
325
    client_mavsdk.remove_connection(client_handle2);
1✔
326
    server_mavsdk.remove_connection(server_handle);
1✔
327
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
328
}
1✔
329

330
TEST(SystemTest, UdpConnectionReconnectionFromServerSide)
4✔
331
{
332
    // Test that UDP server (udpin) can be removed and re-added properly
333
    // UDP is connectionless, so this tests the connection management APIs
334

335
    const std::string udp_port = "17700";
1✔
336
    const std::string server_url = "udpin://0.0.0.0:" + udp_port;
1✔
337
    const std::string client_url = "udpout://127.0.0.1:" + udp_port;
1✔
338

339
    LogInfo() << "=== Phase 1: Start server and client ===";
1✔
340

341
    // Start server
342
    Mavsdk server_mavsdk{Mavsdk::Configuration{ComponentType::Autopilot}};
1✔
343
    auto [server_result, server_handle] = server_mavsdk.add_any_connection_with_handle(server_url);
1✔
344
    ASSERT_EQ(server_result, ConnectionResult::Success);
1✔
345

346
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
347

348
    // Start client
349
    Mavsdk client_mavsdk{Mavsdk::Configuration{ComponentType::GroundStation}};
1✔
350
    auto [client_result, client_handle] = client_mavsdk.add_any_connection_with_handle(client_url);
1✔
351
    ASSERT_EQ(client_result, ConnectionResult::Success);
1✔
352

353
    // Wait for discovery
354
    LogInfo() << "Waiting for client to discover server...";
1✔
355
    auto maybe_system = client_mavsdk.first_autopilot(10.0);
1✔
356
    ASSERT_TRUE(maybe_system) << "Client failed to discover server";
1✔
357
    auto system = maybe_system.value();
1✔
358

359
    LogInfo() << "=== Phase 2: Verify initial connectivity ===";
1✔
360

361
    // Track automatic heartbeats
362
    std::atomic<int> message_count{0};
1✔
363
    MavlinkDirect client_mavlink{system};
1✔
364

365
    auto handle = client_mavlink.subscribe_message(
1✔
366
        "HEARTBEAT", [&message_count](MavlinkDirect::MavlinkMessage) {
4✔
367
            message_count++;
2✔
368
            LogInfo() << "Client received HEARTBEAT (total: " << message_count.load() << ")";
4✔
369
        });
2✔
370

371
    // Wait for automatic heartbeats to arrive
372
    LogInfo() << "Waiting for automatic heartbeats...";
1✔
373
    std::this_thread::sleep_for(std::chrono::seconds(2));
1✔
374
    int messages_before = message_count.load();
1✔
375
    EXPECT_GE(messages_before, 1) << "Client didn't receive automatic heartbeats";
1✔
376
    LogInfo() << "Received " << messages_before << " automatic heartbeat(s) from server";
1✔
377

378
    LogInfo() << "=== Phase 3: Remove and re-add server connection ===";
1✔
379

380
    // Unsubscribe before removing connection
381
    client_mavlink.unsubscribe_message(handle);
1✔
382

383
    // Remove the server connection
384
    server_mavsdk.remove_connection(server_handle);
1✔
385

386
    // Give time for disconnection
387
    LogInfo() << "Waiting after server disconnection...";
1✔
388
    std::this_thread::sleep_for(std::chrono::seconds(2));
1✔
389

390
    // Re-add the server connection
391
    auto [server_result2, server_handle2] =
1✔
392
        server_mavsdk.add_any_connection_with_handle(server_url);
1✔
393
    ASSERT_EQ(server_result2, ConnectionResult::Success);
1✔
394

395
    // Wait for the reconnected client to discover the server again
396
    LogInfo() << "Waiting for client to rediscover server...";
1✔
397
    auto maybe_system2 = client_mavsdk.first_autopilot(10.0);
1✔
398
    ASSERT_TRUE(maybe_system2) << "Client failed to rediscover server after reconnection";
1✔
399
    auto system2 = maybe_system2.value();
2✔
400

401
    LogInfo() << "Server reconnected!";
1✔
402

403
    LogInfo() << "=== Phase 4: Verify message flow after reconnection ===";
1✔
404

405
    // Re-create MavlinkDirect with new system and subscribe again
406
    MavlinkDirect client_mavlink2{system2};
1✔
407
    std::atomic<int> message_count2{0};
1✔
408

409
    auto handle2 = client_mavlink2.subscribe_message(
1✔
410
        "HEARTBEAT", [&message_count2](MavlinkDirect::MavlinkMessage) {
2✔
411
            message_count2++;
1✔
412
            LogInfo() << "Client received HEARTBEAT after reconnection (total: "
1✔
413
                      << message_count2.load() << ")";
2✔
414
        });
1✔
415

416
    // Wait for automatic heartbeats to arrive after reconnection
417
    LogInfo() << "Waiting for automatic heartbeats after reconnection...";
1✔
418
    std::this_thread::sleep_for(std::chrono::seconds(2));
1✔
419
    int messages_after = message_count2.load();
1✔
420

421
    LogInfo() << "Messages after reconnection: " << messages_after;
1✔
422

423
    EXPECT_GE(messages_after, 1)
1✔
424
        << "Client did NOT receive automatic heartbeats after reconnection!";
1✔
425

426
    LogInfo() << "=== SUCCESS: UDP server-side reconnection verified! ===";
1✔
427
    LogInfo() << "Messages before disconnect: " << messages_before;
1✔
428
    LogInfo() << "Messages after reconnect: " << messages_after;
1✔
429

430
    // Cleanup
431
    client_mavlink2.unsubscribe_message(handle2);
1✔
432
    client_mavsdk.remove_connection(client_handle);
1✔
433
    server_mavsdk.remove_connection(server_handle2);
1✔
434
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
1✔
435
}
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