mirror of https://github.com/ArduPilot/ardupilot
1192 lines
31 KiB
C++
1192 lines
31 KiB
C++
/*
|
|
DataFlash logging - file oriented variant
|
|
|
|
This uses SD library to create log files called logs/NN.bin in the
|
|
given directory
|
|
|
|
SD Card Rates
|
|
- deletion rate seems to be ~50 files/second.
|
|
- stat seems to be ~150/second
|
|
- readdir loop of 511 entry directory ~62,000 microseconds
|
|
*/
|
|
|
|
|
|
#include <AP_HAL/AP_HAL.h>
|
|
|
|
#if CONFIG_HAL_BOARD == HAL_BOARD_F4LIGHT && (defined(BOARD_SDCARD_NAME) || defined(BOARD_DATAFLASH_FATFS))
|
|
|
|
#include "DataFlash_File_sd.h"
|
|
|
|
#include <AP_Common/AP_Common.h>
|
|
#include <assert.h>
|
|
#include <AP_Math/AP_Math.h>
|
|
#include <time.h>
|
|
#include <stdio.h>
|
|
#include <GCS_MAVLink/GCS.h>
|
|
#include <AP_Param_Helper/AP_Param_Helper.h>
|
|
|
|
extern const AP_HAL::HAL& hal;
|
|
|
|
#if defined(BOARD_DATAFLASH_FATFS)
|
|
#define MAX_LOG_FILES 9U
|
|
#else
|
|
#define MAX_LOG_FILES 50U
|
|
#endif
|
|
|
|
#define DATAFLASH_PAGE_SIZE 1024UL
|
|
|
|
#define MAX_FILE_SIZE 2048 * 1024L // not more 2MB
|
|
|
|
/*
|
|
constructor
|
|
*/
|
|
DataFlash_File::DataFlash_File(DataFlash_Class &front,
|
|
DFMessageWriter_DFLogStart *writer,
|
|
const char *log_directory) :
|
|
DataFlash_Backend(front, writer),
|
|
_write_fd(File()),
|
|
_read_fd(File()),
|
|
_read_fd_log_num(0),
|
|
_read_offset(0),
|
|
_write_offset(0),
|
|
_open_error(false),
|
|
_log_directory(log_directory),
|
|
_cached_oldest_log(0),
|
|
_last_oldest_log(0),
|
|
_writebuf(0),
|
|
_writebuf_chunk(4096),
|
|
_last_write_time(0),
|
|
has_data(false),
|
|
_busy(false)
|
|
{}
|
|
|
|
|
|
void DataFlash_File::Init()
|
|
{
|
|
DataFlash_Backend::Init();
|
|
|
|
if(HAL_F4Light::state.sd_busy) return; // SD mounted via USB
|
|
|
|
semaphore = hal.util->new_semaphore();
|
|
if (semaphore == nullptr) {
|
|
AP_HAL::panic("Failed to create DataFlash_File semaphore");
|
|
return;
|
|
}
|
|
|
|
|
|
// create the log directory if need be
|
|
const char* custom_dir = hal.util->get_custom_log_directory();
|
|
if (custom_dir != nullptr){
|
|
_log_directory = custom_dir;
|
|
}
|
|
|
|
if (! SD.exists(_log_directory) ) {
|
|
char buf[80];
|
|
const char *cp, *rp;
|
|
char *wp;
|
|
for(cp=_log_directory + strlen(_log_directory);cp>_log_directory && *cp != '/';cp--){ }
|
|
for(wp=buf, rp=_log_directory; rp<cp;){
|
|
*wp++ = *rp++;
|
|
}
|
|
*wp++ = 0;
|
|
SD.mkdir(buf);
|
|
|
|
if (!SD.mkdir(_log_directory)) {
|
|
|
|
printf("Failed to create log directory %s: %s\n", _log_directory, SD.strError(SD.lastError));
|
|
gcs().send_text(MAV_SEVERITY_WARNING,"Failed to create log directory %s: %s\n", _log_directory, SD.strError(SD.lastError));
|
|
_log_directory="0:";
|
|
}
|
|
}
|
|
|
|
// determine and limit file backend buffersize
|
|
uint32_t bufsize = _front._params.file_bufsize;
|
|
if (bufsize > 64) {
|
|
bufsize = 64; // DMA limitations.
|
|
}
|
|
bufsize *= 1024;
|
|
|
|
// If we can't allocate the full size, try to reduce it until we can allocate it
|
|
while (!_writebuf.set_size(bufsize) && bufsize >= _writebuf_chunk) {
|
|
printf("DataFlash_File: Couldn't set buffer size to=%u\n", (unsigned)bufsize);
|
|
bufsize /= 2;
|
|
}
|
|
|
|
if (!_writebuf.get_size()) {
|
|
printf("Out of memory for logging\n");
|
|
return;
|
|
}
|
|
|
|
printf("DataFlash_File: buffer size=%u\n", (unsigned)bufsize);
|
|
|
|
_initialised = true;
|
|
hal.scheduler->register_io_process(FUNCTOR_BIND_MEMBER(&DataFlash_File::_io_timer, void));
|
|
}
|
|
|
|
bool DataFlash_File::file_exists(const char *filename) const
|
|
{
|
|
return SD.exists(filename);
|
|
}
|
|
|
|
bool DataFlash_File::log_exists(const uint16_t lognum) const
|
|
{
|
|
char *filename = _log_file_name(lognum);
|
|
if (filename == nullptr) {
|
|
// internal_error();
|
|
return false; // ?!
|
|
}
|
|
bool ret = file_exists(filename);
|
|
free(filename);
|
|
return ret;
|
|
}
|
|
|
|
void DataFlash_File::periodic_1Hz(const uint32_t now)
|
|
{
|
|
if (!(_write_fd) || !_initialised || _open_error || _busy) return; // too early
|
|
|
|
if (!io_thread_alive()) {
|
|
gcs().send_text(MAV_SEVERITY_WARNING, "No IO Thread Heartbeat");
|
|
// If you try to close the file here then it will almost
|
|
// certainly block. Since this is the main thread, this is
|
|
// likely to cause a crash.
|
|
// _write_fd.close();
|
|
_write_fd.sync();
|
|
printf("\nLoging stopped\n");
|
|
_initialised = false;
|
|
}
|
|
}
|
|
|
|
void DataFlash_File::periodic_fullrate(const uint32_t now)
|
|
{
|
|
DataFlash_Backend::push_log_blocks();
|
|
}
|
|
|
|
uint32_t DataFlash_File::bufferspace_available()
|
|
{
|
|
const uint32_t space = _writebuf.space();
|
|
const uint32_t crit = critical_message_reserved_space();
|
|
|
|
return (space > crit) ? space - crit : 0;
|
|
}
|
|
|
|
// return true for CardInserted() if we successfully initialized
|
|
bool DataFlash_File::CardInserted(void) const
|
|
{
|
|
return _initialised && !_open_error && !HAL_F4Light::state.sd_busy;
|
|
}
|
|
|
|
// returns the available space in _log_directory as a percentage
|
|
// returns -1.0f on error
|
|
float DataFlash_File::avail_space_percent(uint32_t *free)
|
|
{
|
|
|
|
uint32_t space;
|
|
int32_t avail = SD.getfree(_log_directory, &space);
|
|
if(free) *free = avail;
|
|
if (avail == -1) {
|
|
return -1.0f;
|
|
}
|
|
|
|
return (avail/(float)space) * 100;
|
|
}
|
|
|
|
#if 0 // why such hard?
|
|
|
|
// find_oldest_log - find oldest log in _log_directory
|
|
// returns 0 if no log was found
|
|
uint16_t DataFlash_File::find_oldest_log()
|
|
{
|
|
if (_cached_oldest_log != 0) {
|
|
return _cached_oldest_log;
|
|
}
|
|
|
|
uint16_t last_log_num = find_last_log();
|
|
if (last_log_num == 0) {
|
|
return 0;
|
|
}
|
|
|
|
uint16_t current_oldest_log = 0; // 0 is invalid
|
|
|
|
// We could count up to find_last_log(), but if people start
|
|
// relying on the min_avail_space_percent feature we could end up
|
|
// doing a *lot* of asprintf()s and stat()s
|
|
File dir = SD.open(_log_directory);
|
|
if (!dir) {
|
|
// internal_error();
|
|
printf("error opening logs dir: %s", SD.strError(SD.lastError));
|
|
return 0;
|
|
}
|
|
|
|
// we only count files which look like xxx.BIN
|
|
while(1){
|
|
File de=dir.openNextFile();
|
|
if(!de) {
|
|
if(SD.lastError){
|
|
printf("error scanning logs: %s", SD.strError(SD.lastError));
|
|
}
|
|
break;
|
|
}
|
|
|
|
char *nm = de.name();
|
|
de.close();
|
|
|
|
uint8_t length = strlen(nm);
|
|
if (length < 5) {
|
|
// not long enough for \d+[.]BIN
|
|
continue;
|
|
}
|
|
if (strncmp(&nm[length-4], ".BIN", 4)) {
|
|
// doesn't end in .BIN
|
|
continue;
|
|
}
|
|
|
|
uint16_t thisnum = strtoul(nm, nullptr, 10);
|
|
if (current_oldest_log == 0) {
|
|
current_oldest_log = thisnum;
|
|
} else {
|
|
if (current_oldest_log <= last_log_num) {
|
|
if (thisnum > last_log_num) {
|
|
current_oldest_log = thisnum;
|
|
} else if (thisnum < current_oldest_log) {
|
|
current_oldest_log = thisnum;
|
|
}
|
|
} else { // current_oldest_log > last_log_num
|
|
if (thisnum > last_log_num) {
|
|
if (thisnum < current_oldest_log) {
|
|
current_oldest_log = thisnum;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dir.close();
|
|
_cached_oldest_log = current_oldest_log;
|
|
|
|
return current_oldest_log;
|
|
}
|
|
|
|
#else
|
|
|
|
// result cached later
|
|
uint16_t DataFlash_File::find_oldest_log()
|
|
{
|
|
|
|
if (_cached_oldest_log != 0) {
|
|
return _cached_oldest_log;
|
|
}
|
|
|
|
uint16_t i;
|
|
if(_last_oldest_log >=MAX_LOG_FILES-1) _last_oldest_log=1;
|
|
for (i=_last_oldest_log; i<MAX_LOG_FILES; i++) {
|
|
if ( log_exists(i)) {
|
|
_cached_oldest_log=i;
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
|
|
void DataFlash_File::Prep_MinSpace()
|
|
{
|
|
const uint16_t first_log_to_remove = find_oldest_log();
|
|
if (first_log_to_remove != 0) {
|
|
|
|
_last_oldest_log = _cached_oldest_log + 1;
|
|
_cached_oldest_log = 0;
|
|
|
|
uint16_t log_to_remove = first_log_to_remove;
|
|
|
|
uint16_t count = 0;
|
|
do {
|
|
uint32_t free_sp;
|
|
float avail = avail_space_percent(&free_sp);
|
|
|
|
if (avail < 0) { // internal_error()
|
|
#if defined(BOARD_DATAFLASH_FATFS)
|
|
printf("error getting free space, formatting!\n");
|
|
SD.format(_log_directory);
|
|
return;
|
|
#elif defined(BOARD_SDCARD_CS_PIN)
|
|
if(hal_param_helper->_sd_format){
|
|
printf("error getting free space, formatting!\n");
|
|
gcs().send_text(MAV_SEVERITY_WARNING,"error getting free space, formatting!\n");
|
|
SD.format(_log_directory);
|
|
return;
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
if (avail >= min_avail_space_percent && free_sp*512 >= MAX_FILE_SIZE) { // not less 2MB - space for one file
|
|
break;
|
|
}
|
|
if (count++ > MAX_LOG_FILES+10) {
|
|
// *way* too many deletions going on here. Possible internal error.
|
|
break;
|
|
}
|
|
char *filename_to_remove = _log_file_name(log_to_remove);
|
|
if (filename_to_remove == nullptr) {
|
|
break;
|
|
}
|
|
if (SD.exists(filename_to_remove)) {
|
|
printf("Removing (%s) for minimum-space requirements (%.2f%% < %.0f%%) %.1fMb\n",
|
|
filename_to_remove, (double)avail, (double)min_avail_space_percent, free_sp/(1024.*2));
|
|
if (!SD.remove(filename_to_remove)) {
|
|
printf("Failed to remove %s: %s\n", filename_to_remove, SD.strError(SD.lastError));
|
|
}
|
|
free(filename_to_remove);
|
|
}
|
|
log_to_remove++;
|
|
if (log_to_remove > MAX_LOG_FILES) {
|
|
log_to_remove = 1;
|
|
}
|
|
} while (log_to_remove != first_log_to_remove);
|
|
|
|
}
|
|
// check the result
|
|
#if defined(BOARD_DATAFLASH_FATFS)
|
|
float avail = avail_space_percent();
|
|
if (avail <= 0) { // erase don't helps
|
|
printf("erase don't get free space, formatting!\n");
|
|
SD.format(_log_directory);
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
void DataFlash_File::Prep() {
|
|
if (hal.util->get_soft_armed()) {
|
|
// do not want to do any filesystem operations while we are e.g. flying
|
|
return;
|
|
}
|
|
Prep_MinSpace();
|
|
}
|
|
|
|
bool DataFlash_File::NeedPrep()
|
|
{
|
|
if (!CardInserted()) {
|
|
// should not have been called?!
|
|
return false;
|
|
}
|
|
|
|
if (avail_space_percent() < min_avail_space_percent) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
construct a log file name given a log number.
|
|
Note: Caller must free.
|
|
*/
|
|
char *DataFlash_File::_log_file_name(const uint16_t log_num) const
|
|
{
|
|
char *buf = (char *)malloc(256);
|
|
if(!buf) return nullptr;
|
|
|
|
sprintf(buf, "%s/%u.BIN", _log_directory, (unsigned)log_num);
|
|
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
return path name of the lastlog.txt marker file
|
|
Note: Caller must free.
|
|
*/
|
|
char *DataFlash_File::_lastlog_file_name(void) const
|
|
{
|
|
char *buf = (char *)malloc(256);
|
|
if(!buf) return nullptr;
|
|
|
|
sprintf(buf, "%s/LASTLOG.TXT", _log_directory);
|
|
return buf;
|
|
}
|
|
|
|
|
|
// remove all log files
|
|
void DataFlash_File::EraseAll()
|
|
{
|
|
uint16_t log_num;
|
|
const bool was_logging = (_write_fd != -1);
|
|
stop_logging();
|
|
|
|
for (log_num=1; log_num<=MAX_LOG_FILES; log_num++) {
|
|
char *fname = _log_file_name(log_num);
|
|
if (fname == nullptr) {
|
|
break;
|
|
}
|
|
SD.remove(fname);
|
|
free(fname);
|
|
}
|
|
char *fname = _lastlog_file_name();
|
|
if (fname != nullptr) {
|
|
SD.remove(fname);
|
|
free(fname);
|
|
}
|
|
|
|
_cached_oldest_log = 0;
|
|
|
|
if (was_logging) {
|
|
start_new_log();
|
|
}
|
|
}
|
|
|
|
|
|
bool DataFlash_File::WritesOK() const
|
|
{
|
|
if (!_write_fd) {
|
|
return false;
|
|
}
|
|
if (_open_error) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool DataFlash_File::StartNewLogOK() const
|
|
{
|
|
if (_open_error) {
|
|
return false;
|
|
}
|
|
return DataFlash_Backend::StartNewLogOK();
|
|
}
|
|
|
|
|
|
/* Write a block of data at current offset */
|
|
bool DataFlash_File::_WritePrioritisedBlock(const void *pBuffer, uint16_t size, bool is_critical)
|
|
{
|
|
if (! WriteBlockCheckStartupMessages()) {
|
|
_dropped++;
|
|
return false;
|
|
}
|
|
|
|
if (!semaphore->take(1)) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t space = _writebuf.space();
|
|
|
|
if (_writing_startup_messages &&
|
|
_startup_messagewriter->fmt_done()) {
|
|
// the state machine has called us, and it has finished
|
|
// writing format messages out. It can always get back to us
|
|
// with more messages later, so let's leave room for other
|
|
// things:
|
|
if (space < non_messagewriter_message_reserved_space()) {
|
|
// this message isn't dropped, it will be sent again...
|
|
semaphore->give();
|
|
return false;
|
|
}
|
|
} else {
|
|
// we reserve some amount of space for critical messages:
|
|
if (!is_critical && space < critical_message_reserved_space()) {
|
|
// printf("dropping NC block! size=%d\n", size);
|
|
_dropped++;
|
|
semaphore->give();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// if no room for entire message - drop it:
|
|
if (space < size) {
|
|
// hal.util->perf_count(_perf_overruns);
|
|
printf("dropping block! size=%d\n", size);
|
|
|
|
_dropped++;
|
|
semaphore->give();
|
|
return false;
|
|
}
|
|
|
|
_writebuf.write((uint8_t*)pBuffer, size);
|
|
has_data=true;
|
|
|
|
semaphore->give();
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
read a packet. The header bytes have already been read.
|
|
*/
|
|
bool DataFlash_File::ReadBlock(void *pkt, uint16_t size)
|
|
{
|
|
if (!(_read_fd) || !_initialised || _open_error) {
|
|
return false;
|
|
}
|
|
|
|
memset(pkt, 0, size);
|
|
if (_read_fd.read(pkt, size) != size) {
|
|
return false;
|
|
}
|
|
_read_offset += size;
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
find the highest log number
|
|
*/
|
|
uint16_t DataFlash_File::find_last_log()
|
|
{
|
|
unsigned ret = 0;
|
|
char *fname = _lastlog_file_name();
|
|
if (fname == nullptr) {
|
|
return ret;
|
|
}
|
|
File fd = SD.open(fname, FILE_READ);
|
|
free(fname);
|
|
if (fd) {
|
|
char buf[10];
|
|
memset(buf, 0, sizeof(buf));
|
|
if (fd.read(buf, sizeof(buf)-1) > 0) {
|
|
ret = strtoul(buf, nullptr, 10); // зачем тащить толстую функцию зря
|
|
// sscanf(buf, "%u", &ret);
|
|
}
|
|
fd.close();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
uint32_t DataFlash_File::_get_log_size(const uint16_t log_num) const
|
|
{
|
|
char *fname = _log_file_name(log_num);
|
|
if (fname == nullptr) {
|
|
return 0;
|
|
}
|
|
|
|
File fd = SD.open(fname, FILE_READ);
|
|
free(fname);
|
|
|
|
if(!fd) return 0;
|
|
|
|
uint32_t sz= fd.size();
|
|
fd.close();
|
|
|
|
return sz;
|
|
}
|
|
|
|
uint32_t DataFlash_File::_get_log_time(const uint16_t log_num) const
|
|
{
|
|
char *fname = _log_file_name(log_num);
|
|
if (fname == nullptr) {
|
|
return 0;
|
|
}
|
|
|
|
FILINFO fno;
|
|
|
|
int8_t ret = SD.stat(fname, &fno);
|
|
free(fname);
|
|
|
|
if(ret<0) return 0;
|
|
|
|
uint16_t date=fno.fdate,
|
|
time=fno.ftime;
|
|
|
|
|
|
struct tm t;
|
|
|
|
t.tm_sec = FAT_SECOND(time); // seconds after the minute 0-61*
|
|
t.tm_min = FAT_MINUTE(time); // minutes after the hour 0-59
|
|
t.tm_hour = FAT_HOUR(time); // hours since midnight 0-23
|
|
t.tm_mday = FAT_DAY(date); // day of the month 1-31
|
|
t.tm_mon = FAT_MONTH(date); // months since January 0-11
|
|
t.tm_year = FAT_YEAR(date); // years since 1900
|
|
// t.tm_yday int days since January 1 0-365
|
|
t.tm_isdst =false;
|
|
|
|
|
|
return to_timestamp(&t);
|
|
}
|
|
|
|
/*
|
|
convert a list entry number back into a log number (which can then
|
|
be converted into a filename). A "list entry number" is a sequence
|
|
where the oldest log has a number of 1, the second-from-oldest 2,
|
|
and so on. Thus the highest list entry number is equal to the
|
|
number of logs.
|
|
*/
|
|
uint16_t DataFlash_File::_log_num_from_list_entry(const uint16_t list_entry)
|
|
{
|
|
uint16_t oldest_log = find_oldest_log();
|
|
if (oldest_log == 0) {
|
|
// We don't have any logs...
|
|
return 0;
|
|
}
|
|
|
|
uint32_t log_num = oldest_log + list_entry - 1;
|
|
|
|
if (log_num > MAX_LOG_FILES) {
|
|
log_num -= MAX_LOG_FILES;
|
|
}
|
|
while(!log_exists(log_num)){ // skip gaps
|
|
log_num++;
|
|
if(log_num>MAX_LOG_FILES) {
|
|
log_num=MAX_LOG_FILES;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (uint16_t)log_num;
|
|
}
|
|
|
|
/*
|
|
find the number of pages in a log
|
|
*/
|
|
void DataFlash_File::get_log_boundaries(const uint16_t list_entry, uint16_t & start_page, uint16_t & end_page)
|
|
{
|
|
const uint16_t log_num = _log_num_from_list_entry(list_entry);
|
|
//if (log_num == 0) {
|
|
if (! log_exists(log_num)) {
|
|
// that failed - probably no logs
|
|
start_page = 0;
|
|
end_page = 0;
|
|
return;
|
|
}
|
|
|
|
start_page = 0;
|
|
end_page = _get_log_size(log_num) / DATAFLASH_PAGE_SIZE;
|
|
}
|
|
|
|
/*
|
|
retrieve data from a log file
|
|
*/
|
|
int16_t DataFlash_File::get_log_data(const uint16_t list_entry, const uint16_t page, const uint32_t offset, const uint16_t len, uint8_t *data)
|
|
{
|
|
if (!_initialised || _open_error) {
|
|
return -1;
|
|
}
|
|
|
|
const uint16_t log_num = _log_num_from_list_entry(list_entry);
|
|
if (log_num == 0) {
|
|
// that failed - probably no logs
|
|
return -1;
|
|
}
|
|
|
|
if (_read_fd && log_num != _read_fd_log_num) {
|
|
_read_fd.close();
|
|
}
|
|
if (!(_read_fd)) {
|
|
char *fname = _log_file_name(log_num);
|
|
if (fname == nullptr) {
|
|
return -1;
|
|
}
|
|
stop_logging();
|
|
_read_fd = SD.open(fname, O_RDONLY);
|
|
if (!(_read_fd)) {
|
|
_open_error = true;
|
|
|
|
printf("Log read open fail for %s: %s\n", fname, SD.strError(SD.lastError));
|
|
free(fname);
|
|
return -1;
|
|
}
|
|
free(fname);
|
|
_read_offset = 0;
|
|
_read_fd_log_num = log_num;
|
|
}
|
|
|
|
int16_t ret = (int16_t)_read_fd.read(data, len);
|
|
if (ret > 0) {
|
|
_read_offset += ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
find size and date of a log
|
|
*/
|
|
void DataFlash_File::get_log_info(const uint16_t list_entry, uint32_t &size, uint32_t &time_utc)
|
|
{
|
|
uint16_t log_num = _log_num_from_list_entry(list_entry);
|
|
// if (log_num == 0) {
|
|
if (! log_exists(log_num)) {
|
|
|
|
// that failed - probably no logs
|
|
size = 0;
|
|
time_utc = 0;
|
|
return;
|
|
}
|
|
|
|
size = _get_log_size(log_num);
|
|
time_utc = _get_log_time(log_num);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
get the number of logs [no more - note that the log numbers must be consecutive ]
|
|
*/
|
|
uint16_t DataFlash_File::get_num_logs()
|
|
{
|
|
uint16_t ret = 0;
|
|
uint16_t high = find_last_log();
|
|
uint16_t i;
|
|
for (i=high; i>0; i--) {
|
|
if (log_exists(i)) {
|
|
ret++;
|
|
if(_cached_oldest_log == 0 || i<_cached_oldest_log)
|
|
_cached_oldest_log=i;
|
|
}
|
|
}
|
|
|
|
if (i == 0) {
|
|
for (i=MAX_LOG_FILES; i>high; i--) {
|
|
if ( log_exists(i)) {
|
|
ret++;
|
|
if(_cached_oldest_log == 0 || i<_cached_oldest_log)
|
|
_cached_oldest_log=i;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
stop logging
|
|
*/
|
|
void DataFlash_File::stop_logging(void)
|
|
{
|
|
if (_write_fd) {
|
|
_write_fd.close();
|
|
}
|
|
}
|
|
|
|
|
|
void DataFlash_File::PrepForArming()
|
|
{
|
|
if (logging_started()) {
|
|
return;
|
|
}
|
|
start_new_log();
|
|
}
|
|
|
|
/*
|
|
start writing to a new log file
|
|
*/
|
|
uint16_t DataFlash_File::start_new_log(void)
|
|
{
|
|
stop_logging();
|
|
|
|
start_new_log_reset_variables();
|
|
|
|
if (_open_error) {
|
|
// we have previously failed to open a file - don't try again
|
|
// to prevent us trying to open files while in flight
|
|
return 0xFFFF;
|
|
}
|
|
|
|
if (_read_fd) {
|
|
_read_fd.close();
|
|
}
|
|
|
|
uint16_t log_num = find_last_log();
|
|
// re-use empty logs if possible
|
|
if (_get_log_size(log_num) > 0 || log_num == 0) {
|
|
log_num++;
|
|
}
|
|
if (log_num > MAX_LOG_FILES) {
|
|
log_num = 1;
|
|
}
|
|
_cached_oldest_log = 0;
|
|
|
|
bool was_ovf=false;
|
|
|
|
char *fname;
|
|
while(1) { // try to create log file
|
|
|
|
fname = _log_file_name(log_num);
|
|
if (fname == nullptr) {
|
|
_open_error = true;
|
|
return 0xFFFF; // no memory
|
|
}
|
|
|
|
_write_fd = SD.open(fname, O_WRITE|O_CREAT|O_TRUNC);
|
|
|
|
if (_write_fd) { // file opened
|
|
free(fname);
|
|
break;
|
|
}
|
|
|
|
// opening failed
|
|
printf("Log open fail for %s: %s\n",fname, SD.strError(SD.lastError));
|
|
free(fname);
|
|
|
|
log_num++; // if not at end - try to open next log
|
|
|
|
if (log_num >= MAX_LOG_FILES) {
|
|
log_num = 1;
|
|
if(was_ovf) {
|
|
_initialised = false; // no space
|
|
printf("\nLoging stopped\n");
|
|
return 0xFFFF;
|
|
}
|
|
was_ovf=true;
|
|
}
|
|
}
|
|
_write_offset = 0;
|
|
_writebuf.clear();
|
|
has_data = false;
|
|
|
|
// now update lastlog.txt with the new log number
|
|
fname = _lastlog_file_name();
|
|
|
|
File fd = SD.open(fname, O_WRITE|O_CREAT);
|
|
free(fname);
|
|
if (!fd) {
|
|
return 0xFFFF;
|
|
}
|
|
|
|
char buf[30];
|
|
snprintf(buf, sizeof(buf), "%u\r\n", (unsigned)log_num);
|
|
const ssize_t to_write = strlen(buf);
|
|
const ssize_t written = fd.write((uint8_t *)buf, to_write);
|
|
fd.close();
|
|
|
|
if (written < to_write) {
|
|
return 0xFFFF;
|
|
}
|
|
|
|
return log_num;
|
|
}
|
|
|
|
/*
|
|
Read the log and print it on port
|
|
*/
|
|
void DataFlash_File::LogReadProcess(const uint16_t list_entry,
|
|
uint16_t start_page, uint16_t end_page,
|
|
print_mode_fn print_mode,
|
|
AP_HAL::BetterStream *port)
|
|
{
|
|
uint8_t log_step = 0;
|
|
if (!_initialised || _open_error) {
|
|
return;
|
|
}
|
|
|
|
const uint16_t log_num = _log_num_from_list_entry(list_entry);
|
|
if (log_num == 0) {
|
|
return;
|
|
}
|
|
|
|
if (_read_fd) {
|
|
_read_fd.close();
|
|
}
|
|
char *fname = _log_file_name(log_num);
|
|
if (fname == nullptr) {
|
|
return;
|
|
}
|
|
_read_fd = SD.open(fname, O_RDONLY);
|
|
free(fname);
|
|
if (!(_read_fd)) {
|
|
return;
|
|
}
|
|
_read_fd_log_num = log_num;
|
|
_read_offset = 0;
|
|
if (start_page != 0) {
|
|
if (!_read_fd.seek(start_page * DATAFLASH_PAGE_SIZE)) {
|
|
_read_fd.close();
|
|
return;
|
|
}
|
|
_read_offset = start_page * DATAFLASH_PAGE_SIZE;
|
|
}
|
|
|
|
uint8_t log_counter = 0;
|
|
|
|
while (true) {
|
|
uint8_t data;
|
|
if (_read_fd.read(&data, 1) != 1) {
|
|
// reached end of file
|
|
break;
|
|
}
|
|
_read_offset++;
|
|
|
|
// This is a state machine to read the packets
|
|
switch(log_step) {
|
|
case 0:
|
|
if (data == HEAD_BYTE1) {
|
|
log_step++;
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
if (data == HEAD_BYTE2) {
|
|
log_step++;
|
|
} else {
|
|
log_step = 0;
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
log_step = 0;
|
|
_print_log_entry(data, print_mode, port);
|
|
log_counter++;
|
|
break;
|
|
}
|
|
if (_read_offset >= (end_page+1) * DATAFLASH_PAGE_SIZE) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
_read_fd.close();
|
|
}
|
|
|
|
/*
|
|
this is a lot less verbose than the block interface. Dumping 2Gbyte
|
|
of logs a page at a time isn't so useful. Just pull the SD card out or mount FC as massStorage
|
|
and look at it on your PC
|
|
*/
|
|
void DataFlash_File::DumpPageInfo(AP_HAL::BetterStream *port)
|
|
{
|
|
port->printf("DataFlash: num_logs=%u\n", (unsigned)get_num_logs());
|
|
}
|
|
|
|
void DataFlash_File::ShowDeviceInfo(AP_HAL::BetterStream *port)
|
|
{
|
|
port->printf("DataFlash logs stored in %s\n", _log_directory);
|
|
}
|
|
|
|
|
|
/*
|
|
list available log numbers
|
|
*/
|
|
void DataFlash_File::ListAvailableLogs(AP_HAL::BetterStream *port)
|
|
{
|
|
uint16_t num_logs = get_num_logs();
|
|
|
|
if (num_logs == 0) {
|
|
port->printf("\nNo logs\n\n");
|
|
return;
|
|
}
|
|
port->printf("\n%u logs\n", (unsigned)num_logs);
|
|
|
|
for (uint16_t i=1; i<=num_logs; i++) {
|
|
uint16_t log_num = _log_num_from_list_entry(i);
|
|
char *filename = _log_file_name(log_num);
|
|
|
|
|
|
if (filename != nullptr) {
|
|
if(file_exists(filename)) {
|
|
uint32_t time=_get_log_time(log_num);
|
|
struct tm *tm = gmtime((const time_t*)&time);
|
|
port->printf("Log %u in %s of size %u %u/%u/%u %u:%u\n",
|
|
(unsigned)i,
|
|
filename,
|
|
(unsigned)_get_log_size(log_num),
|
|
(unsigned)tm->tm_year+1900,
|
|
(unsigned)tm->tm_mon+1,
|
|
(unsigned)tm->tm_mday,
|
|
(unsigned)tm->tm_hour,
|
|
(unsigned)tm->tm_min);
|
|
|
|
}
|
|
free(filename);
|
|
}
|
|
}
|
|
port->println();
|
|
}
|
|
|
|
|
|
void DataFlash_File::_io_timer(void)
|
|
{
|
|
uint32_t tnow = AP_HAL::millis();
|
|
_io_timer_heartbeat = tnow;
|
|
|
|
if (!(_write_fd) || !_initialised || _open_error || !has_data) {
|
|
return;
|
|
}
|
|
|
|
uint32_t nbytes = _writebuf.available();
|
|
if (nbytes == 0) {
|
|
return;
|
|
}
|
|
if (nbytes < _writebuf_chunk &&
|
|
tnow - _last_write_time < 2000UL) {
|
|
// write in _writebuf_chunk-sized chunks, but always write at
|
|
// least once per 2 seconds if data is available
|
|
has_data=false;
|
|
return;
|
|
}
|
|
|
|
_last_write_time = tnow;
|
|
if (nbytes > _writebuf_chunk) {
|
|
// be kind to the FAT filesystem
|
|
nbytes = _writebuf_chunk;
|
|
}
|
|
|
|
uint32_t size;
|
|
const uint8_t *head = _writebuf.readptr(size);
|
|
nbytes = MIN(nbytes, size);
|
|
|
|
// try to align writes on a 512 byte boundary to avoid filesystem reads
|
|
if ((nbytes + _write_offset) % 512 != 0) {
|
|
uint32_t ofs = (nbytes + _write_offset) % 512;
|
|
if (ofs < nbytes) {
|
|
nbytes -= ofs;
|
|
}
|
|
}
|
|
|
|
if(nbytes==0) return;
|
|
|
|
ssize_t nwritten = _write_fd.write(head, nbytes);
|
|
if (nwritten <= 0) {
|
|
FRESULT err=SD.lastError;
|
|
printf("\nLog write %ld bytes fails: %s\n",nbytes, SD.strError(err));
|
|
gcs().send_text(MAV_SEVERITY_WARNING,"Log write %ld bytes fails: %s\n",nbytes, SD.strError(err));
|
|
// stop_logging();
|
|
_write_fd.close();
|
|
#if defined(BOARD_DATAFLASH_FATFS)
|
|
if(FR_INT_ERR == err || FR_NO_FILESYSTEM == err) { // internal error - bad filesystem
|
|
gcs().send_text(MAV_SEVERITY_INFO, "Formatting DataFlash, please wait");
|
|
uint32_t t=AP_HAL::millis();
|
|
_busy = true; // format requires a long time and 1s task will kill process
|
|
SD.format(_log_directory);
|
|
_busy = false;
|
|
gcs().send_text(MAV_SEVERITY_INFO, "Formatting complete in %ldms", AP_HAL::millis() - t);
|
|
start_new_log(); // re-open logging
|
|
if(_write_fd) { // success?
|
|
nwritten = _write_fd.write(head, nbytes); // ok, try to write again
|
|
if(nwritten>0) { // if ok
|
|
_write_offset += nwritten; // then mark data as written
|
|
_writebuf.advance(nwritten);
|
|
_write_fd.sync(); // and fix it on SD
|
|
return;
|
|
}
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
_busy = true; // Prep_MinSpace requires a long time and 1s task will kill process
|
|
Prep_MinSpace();
|
|
_busy = false;
|
|
start_new_log(); // re-open logging
|
|
if(_write_fd) { // success?
|
|
nwritten = _write_fd.write(head, nbytes); // ok, try to write again
|
|
if(nwritten>0) { // if ok
|
|
_write_offset += nwritten; // then mark data as written
|
|
_writebuf.advance(nwritten);
|
|
_write_fd.sync(); // and fix it on SD
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
_initialised = false;
|
|
} else {
|
|
_write_offset += nwritten;
|
|
_writebuf.advance(nwritten);
|
|
/*
|
|
the best strategy for minimizing corruption on microSD cards
|
|
seems to be to write in 4k chunks and fsync the file on each
|
|
chunk, ensuring the directory entry is updated after each
|
|
write.
|
|
*/
|
|
_write_fd.sync();
|
|
|
|
#if defined(BOARD_DATAFLASH_FATFS) // limit file size in some MBytes and reopen new log file
|
|
|
|
if(_write_fd.size() >= MAX_FILE_SIZE) { // size > 2M
|
|
stop_logging();
|
|
uint32_t t = AP_HAL::millis();
|
|
_busy = true;
|
|
Prep_MinSpace();
|
|
_busy = false;
|
|
printf("\nlog file reopened in %ldms\n", AP_HAL::millis() - t);
|
|
start_new_log(); // re-start logging
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// this sensor is enabled if we should be logging at the moment
|
|
bool DataFlash_File::logging_enabled() const
|
|
{
|
|
if (hal.util->get_soft_armed() ||
|
|
_front.log_while_disarmed()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool DataFlash_File::io_thread_alive() const
|
|
{
|
|
uint32_t tnow = AP_HAL::millis();
|
|
// if the io thread hasn't had a heartbeat in a 5 second then it is dead
|
|
if(_io_timer_heartbeat + 5000 > tnow) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool DataFlash_File::logging_failed() const
|
|
{
|
|
bool op=false;
|
|
|
|
if(_write_fd) op=true;
|
|
|
|
if (!op &&
|
|
(hal.util->get_soft_armed() ||
|
|
_front.log_while_disarmed())) {
|
|
return true;
|
|
}
|
|
if (_open_error) {
|
|
return true;
|
|
}
|
|
if (!io_thread_alive()) {
|
|
// No heartbeat in a second. IO thread is dead?! Very Not Good.
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void DataFlash_File::vehicle_was_disarmed()
|
|
{
|
|
if (_front._params.file_disarm_rot) {
|
|
// rotate our log. Closing the current one and letting the
|
|
// logging restart naturally based on log_disarmed should do
|
|
// the trick:
|
|
stop_logging();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// функция конвертации между UNIX-временем и обычным представлением в виде даты и времени суток
|
|
#define _TBIAS_DAYS ((70 * (uint32_t)365) + 17)
|
|
#define _TBIAS_SECS (_TBIAS_DAYS * (xtime_t)86400)
|
|
#define _TBIAS_YEAR 1900
|
|
#define MONTAB(year) ((((year) & 03) || ((year) == 0)) ? mos : lmos)
|
|
|
|
const uint16_t lmos[] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335};
|
|
const uint16_t mos[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
|
|
|
|
#define Daysto32(year, mon) (((year - 1) / 4) + MONTAB(year)[mon])
|
|
|
|
uint32_t DataFlash_File::to_timestamp(const struct tm *t)
|
|
{ /* convert time structure to scalar time */
|
|
int32_t days;
|
|
uint32_t secs;
|
|
int32_t mon, year;
|
|
|
|
/* Calculate number of days. */
|
|
mon = t->tm_mon - 1;
|
|
year = t->tm_year - _TBIAS_YEAR;
|
|
days = Daysto32(year, mon) - 1;
|
|
days += 365 * year;
|
|
days += t->tm_mday;
|
|
days -= _TBIAS_DAYS;
|
|
|
|
/* Calculate number of seconds. */
|
|
secs = 3600 * t->tm_hour;
|
|
secs += 60 * t->tm_min;
|
|
secs += t->tm_sec;
|
|
|
|
secs += (days * (uint32_t)86400);
|
|
|
|
return (secs);
|
|
}
|
|
#endif
|