Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/Tools/Replay/Replay.cpp
9448 views
1
/*
2
This program is free software: you can redistribute it and/or modify
3
it under the terms of the GNU General Public License as published by
4
the Free Software Foundation, either version 3 of the License, or
5
(at your option) any later version.
6
7
This program is distributed in the hope that it will be useful,
8
but WITHOUT ANY WARRANTY; without even the implied warranty of
9
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
GNU General Public License for more details.
11
12
You should have received a copy of the GNU General Public License
13
along with this program. If not, see <http://www.gnu.org/licenses/>.
14
*/
15
16
#include "Replay.h"
17
18
#include "LogReader.h"
19
20
#include <stdio.h>
21
#include <AP_HAL/utility/getopt_cpp.h>
22
23
#include <AP_Vehicle/AP_Vehicle.h>
24
25
#include <GCS_MAVLink/GCS_Dummy.h>
26
#include <AP_Filesystem/AP_Filesystem.h>
27
#include <AP_Filesystem/posix_compat.h>
28
#include <AP_AdvancedFailsafe/AP_AdvancedFailsafe.h>
29
30
#if CONFIG_HAL_BOARD == HAL_BOARD_LINUX
31
#include <AP_HAL_Linux/Scheduler.h>
32
#endif
33
34
#define streq(x, y) (!strcmp(x, y))
35
36
static ReplayVehicle replayvehicle;
37
38
// list of user parameters
39
user_parameter *user_parameters;
40
bool replay_force_ekf2;
41
bool replay_force_ekf3;
42
bool show_progress;
43
44
const AP_Param::Info ReplayVehicle::var_info[] = {
45
GSCALAR(dummy, "_DUMMY", 0),
46
47
// @Group: BARO
48
// @Path: ../libraries/AP_Baro/AP_Baro.cpp
49
GOBJECT(barometer, "BARO", AP_Baro),
50
51
// @Group: INS
52
// @Path: ../libraries/AP_InertialSensor/AP_InertialSensor.cpp
53
GOBJECT(ins, "INS", AP_InertialSensor),
54
55
// @Group: AHRS_
56
// @Path: ../libraries/AP_AHRS/AP_AHRS.cpp
57
GOBJECT(ahrs, "AHRS_", AP_AHRS),
58
59
#if AP_AIRSPEED_ENABLED
60
// @Group: ARSPD_
61
// @Path: ../libraries/AP_Airspeed/AP_Airspeed.cpp
62
GOBJECT(airspeed, "ARSP_", AP_Airspeed),
63
#endif
64
65
// @Group: EK2_
66
// @Path: ../libraries/AP_NavEKF2/AP_NavEKF2.cpp
67
GOBJECTN(ekf2, NavEKF2, "EK2_", NavEKF2),
68
69
// @Group: COMPASS_
70
// @Path: ../libraries/AP_Compass/AP_Compass.cpp
71
GOBJECT(compass, "COMPASS_", Compass),
72
73
// @Group: EK3_
74
// @Path: ../libraries/AP_NavEKF3/AP_NavEKF3.cpp
75
GOBJECTN(ekf3, NavEKF3, "EK3_", NavEKF3),
76
77
// @Group: GPS
78
// @Path: ../libraries/AP_GPS/AP_GPS.cpp
79
GOBJECT(gps, "GPS", AP_GPS),
80
81
AP_VAREND
82
};
83
84
void ReplayVehicle::load_parameters(void)
85
{
86
AP_Param::check_var_info();
87
88
StorageManager::erase();
89
AP_Param::erase_all();
90
// Load all auto-loaded EEPROM variables - also registers thread
91
// which saves parameters, which Compass now does in its init() routine
92
AP_Param::load_all();
93
}
94
95
GCS_Dummy _gcs;
96
97
#if AP_ADVANCEDFAILSAFE_ENABLED
98
AP_AdvancedFailsafe *AP::advancedfailsafe() { return nullptr; }
99
bool AP_AdvancedFailsafe::gcs_terminate(bool should_terminate, const char *reason) { return false; }
100
#endif
101
102
#if AP_LTM_TELEM_ENABLED
103
// avoid building/linking LTM:
104
void AP_LTM_Telem::init() {};
105
#endif
106
#if AP_DEVO_TELEM_ENABLED
107
// avoid building/linking Devo:
108
void AP_DEVO_Telem::init() {};
109
#endif
110
111
void ReplayVehicle::init_ardupilot(void)
112
{
113
// we pass an empty log structure, filling the structure in with
114
// either the format present in the log (if we do not emit the
115
// message as a product of Replay), or the format understood in
116
// the current code (if we do emit the message in the normal
117
// places in the EKF, for example)
118
logger.set_force_log_disarmed(true);
119
}
120
121
void Replay::usage(void)
122
{
123
::printf("Options:\n");
124
::printf("\t--parm NAME=VALUE set parameter NAME to VALUE\n");
125
::printf("\t--param-file FILENAME load parameters from a file\n");
126
::printf("\t--force-ekf2 force enable EKF2\n");
127
::printf("\t--force-ekf3 force enable EKF3\n");
128
::printf("\t--progress show a progress bar during replay\n");
129
}
130
131
enum param_key : uint8_t {
132
FORCE_EKF2 = 1,
133
FORCE_EKF3,
134
};
135
136
void Replay::_parse_command_line(uint8_t argc, char * const argv[])
137
{
138
const struct GetOptLong::option options[] = {
139
// name has_arg flag val
140
{"parm", true, 0, 'p'},
141
{"param", true, 0, 'p'},
142
{"param-file", true, 0, 'F'},
143
{"force-ekf2", false, 0, param_key::FORCE_EKF2},
144
{"force-ekf3", false, 0, param_key::FORCE_EKF3},
145
{"progress", false, 0, 'P'},
146
{"help", false, 0, 'h'},
147
{0, false, 0, 0}
148
};
149
150
GetOptLong gopt(argc, argv, "p:F:Ph", options);
151
152
int opt;
153
while ((opt = gopt.getoption()) != -1) {
154
switch (opt) {
155
case 'p': {
156
const char *eq = strchr(gopt.optarg, '=');
157
if (eq == NULL) {
158
::printf("Usage: -p NAME=VALUE\n");
159
exit(1);
160
}
161
struct user_parameter *u = NEW_NOTHROW user_parameter;
162
strncpy(u->name, gopt.optarg, eq-gopt.optarg);
163
u->value = strtof(eq+1, nullptr);
164
u->next = user_parameters;
165
user_parameters = u;
166
break;
167
}
168
169
case 'F':
170
load_param_file(gopt.optarg);
171
break;
172
173
case param_key::FORCE_EKF2:
174
replay_force_ekf2 = true;
175
break;
176
177
case param_key::FORCE_EKF3:
178
replay_force_ekf3 = true;
179
break;
180
181
case 'P':
182
show_progress = true;
183
break;
184
185
case 'h':
186
default:
187
usage();
188
exit(0);
189
}
190
}
191
192
argv += gopt.optind;
193
argc -= gopt.optind;
194
195
if (argc > 0) {
196
filename = argv[0];
197
}
198
}
199
200
static const LogStructure EKF2_log_structures[] = {
201
{ LOG_FORMAT_UNITS_MSG, sizeof(log_Format_Units), \
202
"FMTU", "QBNN", "TimeUS,FmtType,UnitIds,MultIds","s---", "F---" }, \
203
LOG_STRUCTURE_FROM_NAVEKF2 \
204
};
205
206
/*
207
write format and units structure format to the log for a LogStructure
208
*/
209
void Replay::Write_Format(const struct LogStructure &s)
210
{
211
struct log_Format pkt {};
212
213
pkt.head1 = HEAD_BYTE1;
214
pkt.head2 = HEAD_BYTE2;
215
pkt.msgid = LOG_FORMAT_MSG;
216
pkt.type = s.msg_type;
217
pkt.length = s.msg_len;
218
strncpy_noterm(pkt.name, s.name, sizeof(pkt.name));
219
strncpy_noterm(pkt.format, s.format, sizeof(pkt.format));
220
strncpy_noterm(pkt.labels, s.labels, sizeof(pkt.labels));
221
222
AP::logger().WriteCriticalBlock(&pkt, sizeof(pkt));
223
224
struct log_Format_Units pkt2 {};
225
pkt2.head1 = HEAD_BYTE1;
226
pkt2.head2 = HEAD_BYTE2;
227
pkt2.msgid = LOG_FORMAT_UNITS_MSG;
228
pkt2.time_us = AP_HAL::micros64();
229
pkt2.format_type = s.msg_type;
230
strncpy_noterm(pkt2.units, s.units, sizeof(pkt2.units));
231
strncpy_noterm(pkt2.multipliers, s.multipliers, sizeof(pkt2.multipliers));
232
233
AP::logger().WriteCriticalBlock(&pkt2, sizeof(pkt2));
234
}
235
236
/*
237
write all EKF2 formats out at the start. This allows --force-ekf2 to
238
work even when EKF2 was not compiled into the original replay log
239
firmware
240
*/
241
void Replay::write_EKF_formats(void)
242
{
243
for (const auto &f : EKF2_log_structures) {
244
Write_Format(f);
245
}
246
}
247
248
void Replay::setup()
249
{
250
::printf("Starting\n");
251
252
uint8_t argc;
253
char * const *argv;
254
255
hal.util->commandline_arguments(argc, argv);
256
257
if (argc > 0) {
258
_parse_command_line(argc, argv);
259
}
260
261
_vehicle.setup();
262
263
set_user_parameters();
264
265
if (replay_force_ekf2) {
266
reader.set_parameter("EK2_ENABLE", 1, true);
267
}
268
if (replay_force_ekf3) {
269
reader.set_parameter("EK3_ENABLE", 1, true);
270
}
271
272
if (replay_force_ekf2 && replay_force_ekf3) {
273
::printf("Cannot force both EKF types\n");
274
exit(1);
275
}
276
277
if (filename == nullptr) {
278
#if CONFIG_HAL_BOARD == HAL_BOARD_CHIBIOS
279
// allow replay on stm32
280
filename = "APM/replayin.bin";
281
#else
282
::printf("You must supply a log filename\n");
283
exit(1);
284
#endif
285
}
286
// LogReader reader = LogReader(log_structure);
287
if (!reader.open_log(filename)) {
288
::printf("open(%s): %m\n", filename);
289
exit(1);
290
}
291
292
if (replay_force_ekf2) {
293
write_EKF_formats();
294
}
295
}
296
297
void Replay::loop()
298
{
299
if (!reader.update()) {
300
#if CONFIG_HAL_BOARD == HAL_BOARD_LINUX
301
// If we don't tear down the threads then they continue to access
302
// global state during object destruction.
303
((Linux::Scheduler*)hal.scheduler)->teardown();
304
#endif
305
exit(0);
306
}
307
308
// Display progress bar if enabled
309
if (show_progress) {
310
uint32_t now = AP_HAL::millis();
311
if (now - last_progress_update > 100) { // Update every 100ms
312
last_progress_update = now;
313
float percent = reader.get_percent_read();
314
315
// Create a 50-character progress bar
316
const uint8_t bar_width = 50;
317
uint8_t completed_chars = (uint8_t)(percent * bar_width / 100.0f);
318
319
// Print the progress bar
320
printf("\rProgress: [");
321
for (uint8_t i = 0; i < bar_width; i++) {
322
if (i < completed_chars) {
323
printf("=");
324
} else if (i == completed_chars) {
325
printf(">");
326
} else {
327
printf(" ");
328
}
329
}
330
printf("] %.1f%%", percent);
331
if (percent >= 100.0f) {
332
printf("\n");
333
}
334
}
335
}
336
}
337
338
/*
339
setup user -p parameters
340
*/
341
void Replay::set_user_parameters(void)
342
{
343
for (struct user_parameter *u=user_parameters; u; u=u->next) {
344
if (!reader.set_parameter(u->name, u->value, true)) {
345
::printf("Failed to set parameter %s to %f\n", u->name, u->value);
346
exit(1);
347
}
348
}
349
}
350
351
/*
352
parse a parameter file line
353
*/
354
bool Replay::parse_param_line(char *line, char **vname, float &value)
355
{
356
if (line[0] == '#') {
357
return false;
358
}
359
char *saveptr = NULL;
360
char *pname = strtok_r(line, ", =\t", &saveptr);
361
if (pname == NULL) {
362
return false;
363
}
364
if (strlen(pname) > AP_MAX_NAME_SIZE) {
365
return false;
366
}
367
const char *value_s = strtok_r(NULL, ", =\t", &saveptr);
368
if (value_s == NULL) {
369
return false;
370
}
371
value = strtof(value_s, nullptr);
372
*vname = pname;
373
return true;
374
}
375
376
377
/*
378
load a default set of parameters from a file
379
*/
380
void Replay::load_param_file(const char *pfilename)
381
{
382
auto &fs = AP::FS();
383
int fd = fs.open(pfilename, O_RDONLY, true);
384
if (fd == -1) {
385
printf("Failed to open parameter file: %s\n", pfilename);
386
exit(1);
387
}
388
char line[100];
389
390
while (fs.fgets(line, sizeof(line)-1, fd)) {
391
char *pname;
392
float value;
393
if (!parse_param_line(line, &pname, value)) {
394
continue;
395
}
396
struct user_parameter *u = NEW_NOTHROW user_parameter;
397
strncpy_noterm(u->name, pname, sizeof(u->name));
398
u->value = value;
399
u->next = user_parameters;
400
user_parameters = u;
401
}
402
fs.close(fd);
403
}
404
405
Replay replay(replayvehicle);
406
AP_Vehicle& vehicle = replayvehicle;
407
408
const AP_HAL::HAL& hal = AP_HAL::get_HAL();
409
410
AP_HAL_MAIN_CALLBACKS(&replay);
411
412