mirror of
https://github.com/subsurface/subsurface.git
synced 2025-01-31 23:53:23 +00:00
Support for importing Poseidon MK6 logs
This patch adds support for importing the logs from a Poseidon MK6 rebreather. This DC produces logs that contain of a .txt file that has all the meta data and a .csv file that contains the sample readings. The CSV file is different from the others in that it has a line per each sample reading at given time. Thus we have to merge all the lines from one point in time into one sample reading of ours. [Dirk Hohndel: addressed some compiler warnings] Signed-off-by: Miika Turkia <miika.turkia@gmail.com> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
parent
bb020ae918
commit
8fcc074b49
5 changed files with 239 additions and 3 deletions
1
dive.h
1
dive.h
|
@ -603,6 +603,7 @@ extern int parse_shearwater_buffer(sqlite3 *handle, const char *url, const char
|
||||||
|
|
||||||
extern int parse_file(const char *filename);
|
extern int parse_file(const char *filename);
|
||||||
extern int parse_csv_file(const char *filename, int time, int depth, int temp, int po2f, int cnsf, int ndlf, int ttsf, int stopdepthf, int pressuref, int sepidx, const char *csvtemplate, int units);
|
extern int parse_csv_file(const char *filename, int time, int depth, int temp, int po2f, int cnsf, int ndlf, int ttsf, int stopdepthf, int pressuref, int sepidx, const char *csvtemplate, int units);
|
||||||
|
extern int parse_txt_file(const char *filename, const char *csv);
|
||||||
extern int parse_manual_file(const char *filename, int separator_index, int units, int number, int date, int time, int duration, int location, int gps, int maxdepth, int meandepth, int buddy, int notes, int weight, int tags);
|
extern int parse_manual_file(const char *filename, int separator_index, int units, int number, int date, int time, int duration, int location, int gps, int maxdepth, int meandepth, int buddy, int notes, int weight, int tags);
|
||||||
|
|
||||||
extern int save_dives(const char *filename);
|
extern int save_dives(const char *filename);
|
||||||
|
|
206
file.c
206
file.c
|
@ -226,7 +226,14 @@ timestamp_t parse_date(const char *date)
|
||||||
enum csv_format {
|
enum csv_format {
|
||||||
CSV_DEPTH,
|
CSV_DEPTH,
|
||||||
CSV_TEMP,
|
CSV_TEMP,
|
||||||
CSV_PRESSURE
|
CSV_PRESSURE,
|
||||||
|
POSEIDON_DEPTH,
|
||||||
|
POSEIDON_TEMP,
|
||||||
|
POSEIDON_SETPOINT,
|
||||||
|
POSEIDON_SENSOR1,
|
||||||
|
POSEIDON_SENSOR2,
|
||||||
|
POSEIDON_PRESSURE,
|
||||||
|
POSEIDON_DILUENT
|
||||||
};
|
};
|
||||||
|
|
||||||
static void add_sample_data(struct sample *sample, enum csv_format type, double val)
|
static void add_sample_data(struct sample *sample, enum csv_format type, double val)
|
||||||
|
@ -241,6 +248,27 @@ static void add_sample_data(struct sample *sample, enum csv_format type, double
|
||||||
case CSV_PRESSURE:
|
case CSV_PRESSURE:
|
||||||
sample->cylinderpressure.mbar = psi_to_mbar(val * 4);
|
sample->cylinderpressure.mbar = psi_to_mbar(val * 4);
|
||||||
break;
|
break;
|
||||||
|
case POSEIDON_DEPTH:
|
||||||
|
sample->depth.mm = val * 0.5 *1000;
|
||||||
|
break;
|
||||||
|
case POSEIDON_TEMP:
|
||||||
|
sample->temperature.mkelvin = C_to_mkelvin(val * 0.2);
|
||||||
|
break;
|
||||||
|
case POSEIDON_SETPOINT:
|
||||||
|
sample->setpoint.mbar = val * 10;
|
||||||
|
break;
|
||||||
|
case POSEIDON_SENSOR1:
|
||||||
|
sample->o2sensor[0].mbar = val * 10;
|
||||||
|
break;
|
||||||
|
case POSEIDON_SENSOR2:
|
||||||
|
sample->o2sensor[1].mbar = val * 10;
|
||||||
|
break;
|
||||||
|
case POSEIDON_PRESSURE:
|
||||||
|
sample->cylinderpressure.mbar = val * 1000;
|
||||||
|
break;
|
||||||
|
case POSEIDON_DILUENT:
|
||||||
|
sample->diluentpressure.mbar = val * 1000;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,6 +413,182 @@ int parse_file(const char *filename)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define MATCH(buffer, pattern) \
|
||||||
|
memcmp(buffer, pattern, strlen(pattern))
|
||||||
|
|
||||||
|
char *parse_mkvi_value(const char *haystack, const char *needle)
|
||||||
|
{
|
||||||
|
char *lineptr, *valueptr, *endptr, *ret = NULL;
|
||||||
|
|
||||||
|
if ((lineptr = strstr(haystack, needle)) != NULL) {
|
||||||
|
if ((valueptr = strstr(lineptr, ": ")) != NULL) {
|
||||||
|
valueptr += 2;
|
||||||
|
}
|
||||||
|
if ((endptr = strstr(lineptr, "\n")) != NULL) {
|
||||||
|
*endptr = 0;
|
||||||
|
ret = strdup(valueptr);
|
||||||
|
*endptr = '\n';
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cur_cylinder_index;
|
||||||
|
int parse_txt_file(const char *filename, const char *csv)
|
||||||
|
{
|
||||||
|
struct memblock memtxt, memcsv;
|
||||||
|
|
||||||
|
if (readfile(filename, &memtxt) < 0) {
|
||||||
|
return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MkVI stores some information in .txt file but the whole profile and events are stored in .csv file. First
|
||||||
|
* make sure the input .txt looks like proper MkVI file, then start parsing the .csv.
|
||||||
|
*/
|
||||||
|
if (MATCH(memtxt.buffer, "MkVI_Config") == 0) {
|
||||||
|
int d, m, y;
|
||||||
|
int hh = 0, mm = 0, ss = 0;
|
||||||
|
int prev_depth = 0, cur_sampletime = 0;
|
||||||
|
bool has_depth = false;
|
||||||
|
char *lineptr;
|
||||||
|
|
||||||
|
struct dive *dive;
|
||||||
|
struct divecomputer *dc;
|
||||||
|
struct tm cur_tm;
|
||||||
|
timestamp_t date;
|
||||||
|
|
||||||
|
if (sscanf(parse_mkvi_value(memtxt.buffer, "Dive started at"), "%d-%d-%d %d:%d:%d",
|
||||||
|
&y, &m, &d, &hh, &mm, &ss) != 6) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_tm.tm_year = y;
|
||||||
|
cur_tm.tm_mon = m - 1;
|
||||||
|
cur_tm.tm_mday = d;
|
||||||
|
cur_tm.tm_hour = hh;
|
||||||
|
cur_tm.tm_min = mm;
|
||||||
|
cur_tm.tm_sec = ss;
|
||||||
|
|
||||||
|
dive = alloc_dive();
|
||||||
|
dive->when = utc_mktime(&cur_tm);;
|
||||||
|
dive->dc.model = strdup("Poseidon MkVI Discovery");
|
||||||
|
dive->dc.deviceid = atoi(parse_mkvi_value(memtxt.buffer, "Rig Serial number"));
|
||||||
|
dive->dc.dctype = CCR;
|
||||||
|
|
||||||
|
dive->cylinder[cur_cylinder_index].type.size.mliter = 3000;
|
||||||
|
dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = 200000;
|
||||||
|
dive->cylinder[cur_cylinder_index].type.description = strdup("3l Mk6");
|
||||||
|
cur_cylinder_index++;
|
||||||
|
|
||||||
|
dive->cylinder[cur_cylinder_index].type.size.mliter = 3000;
|
||||||
|
dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = 200000;
|
||||||
|
dive->cylinder[cur_cylinder_index].type.description = strdup("3l Mk6");
|
||||||
|
cur_cylinder_index++;
|
||||||
|
|
||||||
|
dc = &dive->dc;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read samples from the CSV file. A sample contains all the lines with same timestamp. The CSV file has
|
||||||
|
* the following format:
|
||||||
|
*
|
||||||
|
* timestamp, type, value
|
||||||
|
*
|
||||||
|
* And following fields are of interest to us:
|
||||||
|
*
|
||||||
|
* 6 sensor1
|
||||||
|
* 7 sensor2
|
||||||
|
* 8 depth
|
||||||
|
* 13 o2 tank pressure
|
||||||
|
* 14 diluent tank pressure
|
||||||
|
* 20 o2 setpoint
|
||||||
|
* 39 water temp
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (readfile(csv, &memcsv) < 0) {
|
||||||
|
return report_error(translate("gettextFromC", "Poseidon import failed: unable to read '%s'"), csv);
|
||||||
|
}
|
||||||
|
lineptr = memcsv.buffer;
|
||||||
|
for (;;) {
|
||||||
|
char *end;
|
||||||
|
double val;
|
||||||
|
struct sample *sample;
|
||||||
|
int type;
|
||||||
|
int value;
|
||||||
|
int sampletime;
|
||||||
|
|
||||||
|
/* Collect all the information for one sample */
|
||||||
|
sscanf(lineptr, "%d,%d,%d", &cur_sampletime, &type, &value);
|
||||||
|
|
||||||
|
has_depth = false;
|
||||||
|
sample = prepare_sample(dc);
|
||||||
|
sample->time.seconds = cur_sampletime;
|
||||||
|
|
||||||
|
do {
|
||||||
|
int i = sscanf(lineptr, "%d,%d,%d", &sampletime, &type, &value);
|
||||||
|
switch (i) {
|
||||||
|
case 3:
|
||||||
|
switch (type) {
|
||||||
|
case 6:
|
||||||
|
add_sample_data(sample, POSEIDON_SENSOR1, value);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
add_sample_data(sample, POSEIDON_SENSOR2, value);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
has_depth = true;
|
||||||
|
prev_depth = value;
|
||||||
|
add_sample_data(sample, POSEIDON_DEPTH, value);
|
||||||
|
break;
|
||||||
|
case 13:
|
||||||
|
add_sample_data(sample, POSEIDON_PRESSURE, value);
|
||||||
|
break;
|
||||||
|
case 14:
|
||||||
|
add_sample_data(sample, POSEIDON_DILUENT, value);
|
||||||
|
break;
|
||||||
|
case 20:
|
||||||
|
add_sample_data(sample, POSEIDON_SETPOINT, value);
|
||||||
|
break;
|
||||||
|
case 39:
|
||||||
|
add_sample_data(sample, POSEIDON_TEMP, value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
} /* sample types */
|
||||||
|
break;
|
||||||
|
case EOF:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printf("Unable to parse input: %s\n", lineptr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
lineptr = strchr(lineptr, '\n');
|
||||||
|
if (!lineptr || !*lineptr)
|
||||||
|
break;
|
||||||
|
lineptr++;
|
||||||
|
|
||||||
|
/* Grabbing next sample time */
|
||||||
|
sscanf(lineptr, "%d,%d,%d", &cur_sampletime, &type, &value);
|
||||||
|
} while (sampletime == cur_sampletime);
|
||||||
|
|
||||||
|
if (!has_depth)
|
||||||
|
add_sample_data(sample, POSEIDON_DEPTH, prev_depth);
|
||||||
|
finish_sample(dc);
|
||||||
|
|
||||||
|
if (!lineptr || !*lineptr)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
record_dive(dive);
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return report_error(translate("gettextFromC", "No matching DC found for file '%s'"), csv);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#define MAXCOLDIGITS 3
|
#define MAXCOLDIGITS 3
|
||||||
#define MAXCOLS 100
|
#define MAXCOLS 100
|
||||||
int parse_csv_file(const char *filename, int timef, int depthf, int tempf, int po2f, int cnsf, int ndlf, int ttsf, int stopdepthf, int pressuref, int sepidx, const char *csvtemplate, int unitidx)
|
int parse_csv_file(const char *filename, int timef, int depthf, int tempf, int po2f, int cnsf, int ndlf, int ttsf, int stopdepthf, int pressuref, int sepidx, const char *csvtemplate, int unitidx)
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
#include "dive.h"
|
#include "dive.h"
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
|
#include "membuffer.h"
|
||||||
|
|
||||||
int verbose, quit;
|
int verbose, quit;
|
||||||
int metric = 1;
|
int metric = 1;
|
||||||
|
@ -1738,6 +1739,13 @@ void parse_xml_buffer(const char *url, const char *buffer, int size,
|
||||||
xmlFreeDoc(doc);
|
xmlFreeDoc(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void parse_mkvi_buffer(struct membuffer *txt, struct membuffer *csv, const char *starttime)
|
||||||
|
{
|
||||||
|
dive_start();
|
||||||
|
divedate(starttime, &cur_dive->when);
|
||||||
|
dive_end();
|
||||||
|
}
|
||||||
|
|
||||||
extern int dm4_events(void *handle, int columns, char **data, char **column)
|
extern int dm4_events(void *handle, int columns, char **data, char **column)
|
||||||
{
|
{
|
||||||
event_start();
|
event_start();
|
||||||
|
|
|
@ -1174,6 +1174,23 @@ void MainWindow::importFiles(const QStringList fileNames)
|
||||||
refreshDisplay();
|
refreshDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::importTxtFiles(const QStringList fileNames)
|
||||||
|
{
|
||||||
|
if (fileNames.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QByteArray fileNamePtr, csv;
|
||||||
|
|
||||||
|
for (int i = 0; i < fileNames.size(); ++i) {
|
||||||
|
fileNamePtr = QFile::encodeName(fileNames.at(i));
|
||||||
|
csv = fileNamePtr.data();
|
||||||
|
csv.replace(strlen(csv.data()) - 3, 3, "csv");
|
||||||
|
parse_txt_file(fileNamePtr.data(), csv);
|
||||||
|
}
|
||||||
|
process_dives(true, false);
|
||||||
|
refreshDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::loadFiles(const QStringList fileNames)
|
void MainWindow::loadFiles(const QStringList fileNames)
|
||||||
{
|
{
|
||||||
if (fileNames.isEmpty())
|
if (fileNames.isEmpty())
|
||||||
|
@ -1208,14 +1225,15 @@ void MainWindow::on_actionImportDiveLog_triggered()
|
||||||
QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open dive log file"), lastUsedDir(),
|
QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open dive log file"), lastUsedDir(),
|
||||||
tr("Dive log files (*.xml *.uddf *.udcf *.csv *.jlb *.dld *.sde *.db);;"
|
tr("Dive log files (*.xml *.uddf *.udcf *.csv *.jlb *.dld *.sde *.db);;"
|
||||||
"XML files (*.xml);;UDDF/UDCF files(*.uddf *.udcf);;JDiveLog files(*.jlb);;"
|
"XML files (*.xml);;UDDF/UDCF files(*.uddf *.udcf);;JDiveLog files(*.jlb);;"
|
||||||
"Suunto Files(*.sde *.db);;CSV Files(*.csv);;All Files(*)"));
|
"Suunto Files(*.sde *.db);;CSV Files(*.csv);;MkVI Files(*.txt);;All Files(*)"));
|
||||||
|
|
||||||
if (fileNames.isEmpty())
|
if (fileNames.isEmpty())
|
||||||
return;
|
return;
|
||||||
updateLastUsedDir(QFileInfo(fileNames[0]).dir().path());
|
updateLastUsedDir(QFileInfo(fileNames[0]).dir().path());
|
||||||
|
|
||||||
QStringList logFiles = fileNames.filter(QRegExp("^.*\\.(?!csv)", Qt::CaseInsensitive));
|
QStringList logFiles = fileNames.filter(QRegExp("^.*\\.(?!csv|?!txt)", Qt::CaseInsensitive));
|
||||||
QStringList csvFiles = fileNames.filter(".csv", Qt::CaseInsensitive);
|
QStringList csvFiles = fileNames.filter(".csv", Qt::CaseInsensitive);
|
||||||
|
QStringList txtFiles = fileNames.filter(".txt", Qt::CaseInsensitive);
|
||||||
if (logFiles.size()) {
|
if (logFiles.size()) {
|
||||||
importFiles(logFiles);
|
importFiles(logFiles);
|
||||||
}
|
}
|
||||||
|
@ -1226,6 +1244,10 @@ void MainWindow::on_actionImportDiveLog_triggered()
|
||||||
process_dives(true, false);
|
process_dives(true, false);
|
||||||
refreshDisplay();
|
refreshDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (txtFiles.size()) {
|
||||||
|
importTxtFiles(txtFiles);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::editCurrentDive()
|
void MainWindow::editCurrentDive()
|
||||||
|
|
|
@ -73,6 +73,7 @@ public:
|
||||||
void enableDcShortcuts();
|
void enableDcShortcuts();
|
||||||
void loadFiles(const QStringList files);
|
void loadFiles(const QStringList files);
|
||||||
void importFiles(const QStringList importFiles);
|
void importFiles(const QStringList importFiles);
|
||||||
|
void importTxtFiles(const QStringList fileNames);
|
||||||
void cleanUpEmpty();
|
void cleanUpEmpty();
|
||||||
void setToolButtonsEnabled(bool enabled);
|
void setToolButtonsEnabled(bool enabled);
|
||||||
ProfileWidget2 *graphics() const;
|
ProfileWidget2 *graphics() const;
|
||||||
|
|
Loading…
Add table
Reference in a new issue