Blackbox device type 'file' (SITL) considered working when file handler is available
[inav.git] / src / main / blackbox / blackbox_io.c
blobb344b0dfff6f1a2096efae296f37e1a3607f6bdc
1 /*
2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
18 #include <stdlib.h>
19 #include <stdarg.h>
20 #include <string.h>
22 #if defined(SITL_BUILD)
23 #include <stdio.h>
24 #include <time.h>
25 #endif
27 #include "platform.h"
29 #ifdef USE_BLACKBOX
31 #include "blackbox.h"
32 #include "blackbox_io.h"
34 #include "common/axis.h"
35 #include "common/encoding.h"
36 #include "common/maths.h"
37 #include "common/printf.h"
38 #include "common/typeconversion.h"
40 #include "config/parameter_group.h"
41 #include "config/parameter_group_ids.h"
43 #ifdef USE_SDCARD
44 #include "drivers/sdcard/sdcard.h"
45 #endif
47 #include "io/asyncfatfs/asyncfatfs.h"
48 #include "io/flashfs.h"
49 #include "io/serial.h"
51 #include "msp/msp_serial.h"
53 #include "sensors/gyro.h"
54 #include "fc/config.h"
56 #define BLACKBOX_SERIAL_PORT_MODE MODE_TX
58 // How many bytes can we transmit per loop iteration when writing headers?
59 static uint8_t blackboxMaxHeaderBytesPerIteration;
61 // How many bytes can we write *this* iteration without overflowing transmit buffers or overstressing the OpenLog?
62 int32_t blackboxHeaderBudget;
64 STATIC_UNIT_TESTED serialPort_t *blackboxPort = NULL;
65 #ifndef UNIT_TEST
66 static portSharing_e blackboxPortSharing;
67 #endif // UNIT_TEST
69 #ifdef USE_SDCARD
71 static struct {
72 afatfsFilePtr_t logFile;
73 afatfsFilePtr_t logDirectory;
74 afatfsFinder_t logDirectoryFinder;
75 uint32_t largestLogFileNumber;
77 enum {
78 BLACKBOX_SDCARD_INITIAL,
79 BLACKBOX_SDCARD_WAITING,
80 BLACKBOX_SDCARD_ENUMERATE_FILES,
81 BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY,
82 BLACKBOX_SDCARD_READY_TO_CREATE_LOG,
83 BLACKBOX_SDCARD_READY_TO_LOG
84 } state;
85 } blackboxSDCard;
87 #endif
89 #if defined(SITL_BUILD)
90 static struct {
91 FILE *file_handler;
92 } blackboxFile;
93 #endif
95 #ifndef UNIT_TEST
96 void blackboxOpen(void)
98 serialPort_t *sharedBlackboxAndMspPort = findSharedSerialPort(FUNCTION_BLACKBOX, FUNCTION_MSP);
99 if (sharedBlackboxAndMspPort) {
100 mspSerialReleasePortIfAllocated(sharedBlackboxAndMspPort);
103 #endif // UNIT_TEST
105 void blackboxWrite(uint8_t value)
107 switch (blackboxConfig()->device) {
108 #ifdef USE_FLASHFS
109 case BLACKBOX_DEVICE_FLASH:
110 flashfsWriteByte(value); // Write byte asynchronously
111 break;
112 #endif
113 #ifdef USE_SDCARD
114 case BLACKBOX_DEVICE_SDCARD:
115 afatfs_fputc(blackboxSDCard.logFile, value);
116 break;
117 #endif
118 #if defined(SITL_BUILD)
119 case BLACKBOX_DEVICE_FILE:
120 fputc(value, blackboxFile.file_handler);
121 break;
122 #endif
123 case BLACKBOX_DEVICE_SERIAL:
124 default:
125 serialWrite(blackboxPort, value);
126 break;
130 // Print the null-terminated string 's' to the blackbox device and return the number of bytes written
131 int blackboxPrint(const char *s)
133 int length;
134 const uint8_t *pos;
136 switch (blackboxConfig()->device) {
138 #ifdef USE_FLASHFS
139 case BLACKBOX_DEVICE_FLASH:
140 length = strlen(s);
141 flashfsWrite((const uint8_t*) s, length, false); // Write asynchronously
142 break;
143 #endif
145 #ifdef USE_SDCARD
146 case BLACKBOX_DEVICE_SDCARD:
147 length = strlen(s);
148 afatfs_fwrite(blackboxSDCard.logFile, (const uint8_t*) s, length); // Ignore failures due to buffers filling up
149 break;
150 #endif
152 #if defined(SITL_BUILD)
153 case BLACKBOX_DEVICE_FILE:
154 length = strlen(s);
155 fputs(s, blackboxFile.file_handler);
156 break;
157 #endif
159 case BLACKBOX_DEVICE_SERIAL:
160 default:
161 pos = (uint8_t*) s;
162 while (*pos) {
163 serialWrite(blackboxPort, *pos);
164 pos++;
167 length = pos - (uint8_t*) s;
168 break;
171 return length;
175 * If there is data waiting to be written to the blackbox device, attempt to write (a portion of) that now.
177 * Intended to be called regularly for the blackbox device to perform housekeeping.
179 void blackboxDeviceFlush(void)
181 switch (blackboxConfig()->device) {
182 #ifdef USE_FLASHFS
184 * This is our only output device which requires us to call flush() in order for it to write anything. The other
185 * devices will progressively write in the background without Blackbox calling anything.
187 case BLACKBOX_DEVICE_FLASH:
188 flashfsFlushAsync();
189 break;
190 #endif
192 default:
198 * If there is data waiting to be written to the blackbox device, attempt to write (a portion of) that now.
200 * Returns true if all data has been written to the device.
202 bool blackboxDeviceFlushForce(void)
204 switch (blackboxConfig()->device) {
205 case BLACKBOX_DEVICE_SERIAL:
206 // Nothing to speed up flushing on serial, as serial is continuously being drained out of its buffer
207 return isSerialTransmitBufferEmpty(blackboxPort);
209 #ifdef USE_FLASHFS
210 case BLACKBOX_DEVICE_FLASH:
211 return flashfsFlushAsync();
212 #endif
214 #ifdef USE_SDCARD
215 case BLACKBOX_DEVICE_SDCARD:
216 /* SD card will flush itself without us calling it, but we need to call flush manually in order to check
217 * if it's done yet or not!
219 return afatfs_flush();
220 #endif
222 #if defined(SITL_BUILD)
223 case BLACKBOX_DEVICE_FILE:
224 fflush(blackboxFile.file_handler);
225 return true;
226 #endif
228 default:
229 return false;
234 * Attempt to open the logging device. Returns true if successful.
236 #ifndef UNIT_TEST
237 bool blackboxDeviceOpen(void)
239 switch (blackboxConfig()->device) {
240 case BLACKBOX_DEVICE_SERIAL:
242 serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_BLACKBOX);
243 baudRate_e baudRateIndex;
244 portOptions_t portOptions = SERIAL_PARITY_NO | SERIAL_NOT_INVERTED;
246 if (!portConfig) {
247 return false;
250 blackboxPortSharing = determinePortSharing(portConfig, FUNCTION_BLACKBOX);
251 baudRateIndex = portConfig->peripheral_baudrateIndex;
253 if (baudRates[baudRateIndex] == 230400) {
255 * OpenLog's 230400 baud rate is very inaccurate, so it requires a larger inter-character gap in
256 * order to maintain synchronization.
258 portOptions |= SERIAL_STOPBITS_2;
259 } else {
260 portOptions |= SERIAL_STOPBITS_1;
263 blackboxPort = openSerialPort(portConfig->identifier, FUNCTION_BLACKBOX, NULL, NULL, baudRates[baudRateIndex],
264 BLACKBOX_SERIAL_PORT_MODE, portOptions);
267 * The slowest MicroSD cards have a write latency approaching 150ms. The OpenLog's buffer is about 900
268 * bytes. In order for its buffer to be able to absorb this latency we must write slower than 6000 B/s.
270 * So:
271 * Bytes per loop iteration = floor((looptime_ns / 1000000.0) * 6000)
272 * = floor((looptime_ns * 6000) / 1000000.0)
273 * = floor((looptime_ns * 3) / 500.0)
274 * = (looptime_ns * 3) / 500
276 blackboxMaxHeaderBytesPerIteration = constrain((getLooptime() * 3) / 500, 1, BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION);
278 return blackboxPort != NULL;
280 break;
281 #ifdef USE_FLASHFS
282 case BLACKBOX_DEVICE_FLASH:
283 if (flashfsGetSize() == 0 || isBlackboxDeviceFull()) {
284 return false;
287 blackboxMaxHeaderBytesPerIteration = BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION;
289 return true;
290 break;
291 #endif
292 #ifdef USE_SDCARD
293 case BLACKBOX_DEVICE_SDCARD:
294 if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_FATAL || afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_UNKNOWN || afatfs_isFull()) {
295 return false;
298 blackboxMaxHeaderBytesPerIteration = BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION;
300 return true;
301 break;
302 #endif
303 #if defined(SITL_BUILD)
304 case BLACKBOX_DEVICE_FILE:
306 const time_t now = time(NULL);
307 const struct tm *t = localtime(&now);
308 char filename[32];
309 strftime(filename, sizeof(filename), "%Y_%m_%d_%H%M%S.TXT", t);
311 blackboxFile.file_handler = fopen(filename, "wb");
312 if (blackboxFile.file_handler == NULL) {
313 fprintf(stderr, "[BlackBox] Failed to create log file\n");
314 return false;
316 fprintf(stderr, "[BlackBox] Created %s\n", filename);
319 blackboxMaxHeaderBytesPerIteration = BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION;
320 return true;
321 break;
322 #endif
323 default:
324 return false;
327 #endif // UNIT_TEST
330 * Close the Blackbox logging device immediately without attempting to flush any remaining data.
332 #ifndef UNIT_TEST
333 void blackboxDeviceClose(void)
335 switch (blackboxConfig()->device) {
336 case BLACKBOX_DEVICE_SERIAL:
337 // Since the serial port could be shared with other processes, we have to give it back here
338 closeSerialPort(blackboxPort);
339 blackboxPort = NULL;
342 * Normally this would be handled by mw.c, but since we take an unknown amount
343 * of time to shut down asynchronously, we're the only ones that know when to call it.
345 if (blackboxPortSharing == PORTSHARING_SHARED) {
346 mspSerialAllocatePorts();
348 break;
349 #ifdef USE_FLASHFS
350 case BLACKBOX_DEVICE_FLASH:
351 // Some flash device, e.g., NAND devices, require explicit close to flush internally buffered data.
352 flashfsClose();
353 break;
354 #endif
355 #if defined(SITL_BUILD)
356 case BLACKBOX_DEVICE_FILE:
357 fclose(blackboxFile.file_handler);
358 blackboxFile.file_handler = NULL;
359 break;
360 #endif
361 default:
365 #endif // UNIT_TEST
367 #ifdef USE_SDCARD
369 static void blackboxLogDirCreated(afatfsFilePtr_t directory)
371 if (directory) {
372 blackboxSDCard.logDirectory = directory;
374 afatfs_findFirst(blackboxSDCard.logDirectory, &blackboxSDCard.logDirectoryFinder);
376 blackboxSDCard.state = BLACKBOX_SDCARD_ENUMERATE_FILES;
377 } else {
378 // Retry
379 blackboxSDCard.state = BLACKBOX_SDCARD_INITIAL;
383 static void blackboxLogFileCreated(afatfsFilePtr_t file)
385 if (file) {
386 blackboxSDCard.logFile = file;
388 blackboxSDCard.largestLogFileNumber++;
390 blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_LOG;
391 } else {
392 // Retry
393 blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG;
397 static void blackboxCreateLogFile(void)
399 uint32_t remainder = blackboxSDCard.largestLogFileNumber + 1;
401 char filename[13];
403 filename[0] = 'L';
404 filename[1] = 'O';
405 filename[2] = 'G';
407 for (int i = 7; i >= 3; i--) {
408 filename[i] = (remainder % 10) + '0';
409 remainder /= 10;
412 filename[8] = '.';
413 filename[9] = 'T';
414 filename[10] = 'X';
415 filename[11] = 'T';
416 filename[12] = 0;
418 blackboxSDCard.state = BLACKBOX_SDCARD_WAITING;
420 afatfs_fopen(filename, "as", blackboxLogFileCreated);
424 * Begin a new log on the SDCard.
426 * Keep calling until the function returns true (open is complete).
428 static bool blackboxSDCardBeginLog(void)
430 fatDirectoryEntry_t *directoryEntry;
432 doMore:
433 switch (blackboxSDCard.state) {
434 case BLACKBOX_SDCARD_INITIAL:
435 if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY) {
436 blackboxSDCard.state = BLACKBOX_SDCARD_WAITING;
438 afatfs_mkdir("logs", blackboxLogDirCreated);
440 break;
442 case BLACKBOX_SDCARD_WAITING:
443 // Waiting for directory entry to be created
444 break;
446 case BLACKBOX_SDCARD_ENUMERATE_FILES:
447 while (afatfs_findNext(blackboxSDCard.logDirectory, &blackboxSDCard.logDirectoryFinder, &directoryEntry) == AFATFS_OPERATION_SUCCESS) {
448 if (directoryEntry && !fat_isDirectoryEntryTerminator(directoryEntry)) {
449 // If this is a log file, parse the log number from the filename
450 if (
451 directoryEntry->filename[0] == 'L' && directoryEntry->filename[1] == 'O' && directoryEntry->filename[2] == 'G'
452 && directoryEntry->filename[8] == 'T' && directoryEntry->filename[9] == 'X' && directoryEntry->filename[10] == 'T'
454 char logSequenceNumberString[6];
456 memcpy(logSequenceNumberString, directoryEntry->filename + 3, 5);
457 logSequenceNumberString[5] = '\0';
459 blackboxSDCard.largestLogFileNumber = MAX((uint32_t) fastA2I(logSequenceNumberString), blackboxSDCard.largestLogFileNumber);
461 } else {
462 // We're done checking all the files on the card, now we can create a new log file
463 afatfs_findLast(blackboxSDCard.logDirectory);
465 blackboxSDCard.state = BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY;
466 goto doMore;
469 break;
471 case BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY:
472 // Change into the log directory:
473 if (afatfs_chdir(blackboxSDCard.logDirectory)) {
474 // We no longer need our open handle on the log directory
475 afatfs_fclose(blackboxSDCard.logDirectory, NULL);
476 blackboxSDCard.logDirectory = NULL;
478 blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG;
479 goto doMore;
481 break;
483 case BLACKBOX_SDCARD_READY_TO_CREATE_LOG:
484 blackboxCreateLogFile();
485 break;
487 case BLACKBOX_SDCARD_READY_TO_LOG:
488 return true; // Log has been created!
491 // Not finished init yet
492 return false;
495 #endif
498 * Begin a new log (for devices which support separations between the logs of multiple flights).
500 * Keep calling until the function returns true (open is complete).
502 bool blackboxDeviceBeginLog(void)
504 switch (blackboxConfig()->device) {
505 #ifdef USE_SDCARD
506 case BLACKBOX_DEVICE_SDCARD:
507 return blackboxSDCardBeginLog();
508 #endif
509 default:
510 return true;
516 * Terminate the current log (for devices which support separations between the logs of multiple flights).
518 * retainLog - Pass true if the log should be kept, or false if the log should be discarded (if supported).
520 * Keep calling until this returns true
522 bool blackboxDeviceEndLog(bool retainLog)
524 #ifndef USE_SDCARD
525 (void) retainLog;
526 #endif
528 switch (blackboxConfig()->device) {
529 #ifdef USE_SDCARD
530 case BLACKBOX_DEVICE_SDCARD:
531 // Keep retrying until the close operation queues
532 if (
533 (retainLog && afatfs_fclose(blackboxSDCard.logFile, NULL))
534 || (!retainLog && afatfs_funlink(blackboxSDCard.logFile, NULL))
536 // Don't bother waiting the for the close to complete, it's queued now and will complete eventually
537 blackboxSDCard.logFile = NULL;
538 blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG;
539 return true;
541 return false;
542 #endif
543 default:
544 return true;
548 bool isBlackboxDeviceFull(void)
550 switch (blackboxConfig()->device) {
551 case BLACKBOX_DEVICE_SERIAL:
552 return false;
554 #ifdef USE_FLASHFS
555 case BLACKBOX_DEVICE_FLASH:
556 return flashfsIsEOF();
557 #endif
559 #ifdef USE_SDCARD
560 case BLACKBOX_DEVICE_SDCARD:
561 return afatfs_isFull();
562 #endif
564 #if defined (SITL_BUILD)
565 case BLACKBOX_DEVICE_FILE:
566 return false;
567 #endif
569 default:
570 return false;
574 bool isBlackboxDeviceWorking(void)
576 switch (blackboxConfig()->device) {
577 case BLACKBOX_DEVICE_SERIAL:
578 return blackboxPort != NULL;
579 #ifdef USE_SDCARD
580 case BLACKBOX_DEVICE_SDCARD:
581 return sdcard_isInserted() && sdcard_isFunctional() && (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY);
582 #endif
583 #ifdef USE_FLASHFS
584 case BLACKBOX_DEVICE_FLASH:
585 return flashfsIsReady();
586 #endif
587 #if defined(SITL_BUILD)
588 case BLACKBOX_DEVICE_FILE:
589 return blackboxFile.file_handler != NULL;
590 #endif
591 default:
592 return false;
596 int32_t blackboxGetLogNumber(void)
598 switch (blackboxConfig()->device) {
599 #ifdef USE_SDCARD
600 case BLACKBOX_DEVICE_SDCARD:
601 return blackboxSDCard.largestLogFileNumber;
602 #endif
603 default:
604 return -1;
609 * Call once every loop iteration in order to maintain the global blackboxHeaderBudget with the number of bytes we can
610 * transmit this iteration.
612 void blackboxReplenishHeaderBudget(void)
614 int32_t freeSpace;
616 switch (blackboxConfig()->device) {
617 case BLACKBOX_DEVICE_SERIAL:
618 freeSpace = serialTxBytesFree(blackboxPort);
619 break;
620 #ifdef USE_FLASHFS
621 case BLACKBOX_DEVICE_FLASH:
622 freeSpace = flashfsGetWriteBufferFreeSpace();
623 break;
624 #endif
625 #ifdef USE_SDCARD
626 case BLACKBOX_DEVICE_SDCARD:
627 freeSpace = afatfs_getFreeBufferSpace();
628 break;
629 #endif
630 #if defined(SITL_BUILD)
631 case BLACKBOX_DEVICE_FILE:
632 freeSpace = BLACKBOX_MAX_ACCUMULATED_HEADER_BUDGET;
633 break;
634 #endif
635 default:
636 freeSpace = 0;
639 blackboxHeaderBudget = MIN(MIN(freeSpace, blackboxHeaderBudget + blackboxMaxHeaderBytesPerIteration), BLACKBOX_MAX_ACCUMULATED_HEADER_BUDGET);
643 * You must call this function before attempting to write Blackbox header bytes to ensure that the write will not
644 * cause buffers to overflow. The number of bytes you can write is capped by the blackboxHeaderBudget. Calling this
645 * reservation function doesn't decrease blackboxHeaderBudget, so you must manually decrement that variable by the
646 * number of bytes you actually wrote.
648 * When the Blackbox device is FlashFS, a successful return code guarantees that no data will be lost if you write that
649 * many bytes to the device (i.e. FlashFS's buffers won't overflow).
651 * When the device is a serial port, a successful return code guarantees that Cleanflight's serial Tx buffer will not
652 * overflow, and the outgoing bandwidth is likely to be small enough to give the OpenLog time to absorb MicroSD card
653 * latency. However the OpenLog could still end up silently dropping data.
655 * Returns:
656 * BLACKBOX_RESERVE_SUCCESS - Upon success
657 * BLACKBOX_RESERVE_TEMPORARY_FAILURE - The buffer is currently too full to service the request, try again later
658 * BLACKBOX_RESERVE_PERMANENT_FAILURE - The buffer is too small to ever service this request
660 blackboxBufferReserveStatus_e blackboxDeviceReserveBufferSpace(int32_t bytes)
662 if (bytes <= blackboxHeaderBudget) {
663 return BLACKBOX_RESERVE_SUCCESS;
666 // Handle failure:
667 switch (blackboxConfig()->device) {
668 case BLACKBOX_DEVICE_SERIAL:
670 * One byte of the tx buffer isn't available for user data (due to its circular list implementation),
671 * hence the -1. Note that the USB VCP implementation doesn't use a buffer and has txBufferSize set to zero.
673 if (blackboxPort->txBufferSize && bytes > (int32_t) blackboxPort->txBufferSize - 1) {
674 return BLACKBOX_RESERVE_PERMANENT_FAILURE;
677 return BLACKBOX_RESERVE_TEMPORARY_FAILURE;
679 #ifdef USE_FLASHFS
680 case BLACKBOX_DEVICE_FLASH:
681 if (bytes > (int32_t) flashfsGetWriteBufferSize()) {
682 return BLACKBOX_RESERVE_PERMANENT_FAILURE;
685 if (bytes > (int32_t) flashfsGetWriteBufferFreeSpace()) {
687 * The write doesn't currently fit in the buffer, so try to make room for it. Our flushing here means
688 * that the Blackbox header writing code doesn't have to guess about the best time to ask flashfs to
689 * flush, and doesn't stall waiting for a flush that would otherwise not automatically be called.
691 flashfsFlushAsync();
694 return BLACKBOX_RESERVE_TEMPORARY_FAILURE;
695 #endif
697 #ifdef USE_SDCARD
698 case BLACKBOX_DEVICE_SDCARD:
699 // Assume that all writes will fit in the SDCard's buffers
700 return BLACKBOX_RESERVE_TEMPORARY_FAILURE;
701 #endif
703 #if defined(SITL_BUILD)
704 case BLACKBOX_DEVICE_FILE:
705 // Assume that all writes will fit in the file's buffers
706 return BLACKBOX_RESERVE_TEMPORARY_FAILURE;
707 #endif
709 default:
710 return BLACKBOX_RESERVE_PERMANENT_FAILURE;
714 #endif