Limited support for Suunto DM4 import

Basic functionality is implemented but at least support for multiple
cylinders is missing. Event/alarm support is only partial.

Signed-off-by: Miika Turkia <miika.turkia@gmail.com>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
Miika Turkia 2013-03-05 07:10:39 +02:00 committed by Dirk Hohndel
parent a6a487306e
commit a94f7807f8
6 changed files with 286 additions and 2 deletions

View file

@ -785,6 +785,41 @@ The file Divelogs.SDE can now be opened (or imported) in
Subsurface. Different from earlier versions of Subsurface, no manual Subsurface. Different from earlier versions of Subsurface, no manual
unpacking of the .SDE file is needed anymore. unpacking of the .SDE file is needed anymore.
[[S_ImportingDivesSuuntoDM4]]
Importing dives from Suunto DM4
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To import divelog from Suunto DM4, you need to locate the DM4 database
where the dives are stored. You can either look for the original
database or take a backup of the dives. We recommend the backup method
as the directory path to the main database is partly random.
Backing up Suunto DM4
^^^^^^^^^^^^^^^^^^^^^
- Start Suunto DM4
- Select 'File - Create backup'
- From the file menu select the location and name for the backup, we'll
use DM4 in here with the default suffix .bak
- Click 'Save'
- Your dives are now exported to the file DM4.bak
Reading Suunto DM4 backup in Subsurface
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Importing the logs from DM4 does not require any special steps. You just
do the following:
- Open the following menu 'File - Import XML Files(s)'
- Browse your directories to the location where your DM4 backup is
stored
- Select the backup file you want to import and click 'Open'
It is also possible to do the importing from command line just like with
JDiveLog (see <<S_ImportingDivesJDiveLog, chapter Importing Dives from JDiveLog>>):
subsurface MyDives.xml --import DM4.bak
[[S_ImportingMacDive]] [[S_ImportingMacDive]]
Importing Dives from MacDive Importing Dives from MacDive
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -861,6 +896,7 @@ button). Then do the following:
- Open the 'File - Export - XML' menu - Open the 'File - Export - XML' menu
- Select the dives that you want to export - Select the dives that you want to export
- Click on the export button and select the filename - Click on the export button and select the filename
- Rename the backup file to extension .db
This file can now be opened in Subsurface (as described in the previous sections). This file can now be opened in Subsurface (as described in the previous sections).

View file

@ -120,6 +120,11 @@ ifneq ($(strip $(LIBZIP)),)
ZIP = -DLIBZIP $(shell $(PKGCONFIG) --cflags libzip) ZIP = -DLIBZIP $(shell $(PKGCONFIG) --cflags libzip)
endif endif
LIBSQLITE3 = $(shell $(PKGCONFIG) --libs sqlite3 2> /dev/null)
ifneq ($(strip $(LIBSQLITE3)),)
SQLITE3 = -DSQLITE3 $(shell $(PKGCONFIG) --cflags sqlite3)
endif
ifeq ($(UNAME), linux) ifeq ($(UNAME), linux)
LIBGCONF2 = $(shell $(PKGCONFIG) --libs gconf-2.0) LIBGCONF2 = $(shell $(PKGCONFIG) --libs gconf-2.0)
GCONF2CFLAGS = $(shell $(PKGCONFIG) --cflags gconf-2.0) GCONF2CFLAGS = $(shell $(PKGCONFIG) --cflags gconf-2.0)
@ -152,7 +157,7 @@ ifneq ($(strip $(LIBXSLT)),)
XSLT=-DXSLT='"$(XSLTDIR)"' XSLT=-DXSLT='"$(XSLTDIR)"'
endif endif
LIBS = $(LIBXML2) $(LIBXSLT) $(LIBGTK) $(LIBGCONF2) $(LIBDIVECOMPUTER) $(EXTRALIBS) $(LIBZIP) -lpthread -lm $(LIBOSMGPSMAP) $(LIBSOUP) $(LIBWINSOCK) LIBS = $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGTK) $(LIBGCONF2) $(LIBDIVECOMPUTER) $(EXTRALIBS) $(LIBZIP) -lpthread -lm $(LIBOSMGPSMAP) $(LIBSOUP) $(LIBWINSOCK)
MSGLANGS=$(notdir $(wildcard po/*po)) MSGLANGS=$(notdir $(wildcard po/*po))
MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.mo)) MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.mo))
@ -266,7 +271,7 @@ update-po-files:
tx pull -af tx pull -af
EXTRA_FLAGS = $(GTKCFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) \ EXTRA_FLAGS = $(GTKCFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) \
$(XSLT) $(ZIP) $(LIBDIVECOMPUTERCFLAGS) \ $(XSLT) $(ZIP) $(SQLITE3) $(LIBDIVECOMPUTERCFLAGS) \
$(LIBSOUPCFLAGS) $(OSMGPSMAPFLAGS) $(GCONF2CFLAGS) $(LIBSOUPCFLAGS) $(OSMGPSMAPFLAGS) $(GCONF2CFLAGS)
%.o: %.c %.o: %.c

2
dive.h
View file

@ -525,6 +525,8 @@ extern void parse_xml_buffer(const char *url, const char *buf, int size, struct
extern void parse_xml_exit(void); extern void parse_xml_exit(void);
extern void set_filename(const char *filename, gboolean force); extern void set_filename(const char *filename, gboolean force);
extern int parse_dm4_buffer(const char *url, const char *buf, int size, struct dive_table *table, GError **error);
extern void parse_file(const char *filename, GError **error, gboolean possible_default_filename); extern void parse_file(const char *filename, GError **error, gboolean possible_default_filename);
extern void show_dive_info(struct dive *); extern void show_dive_info(struct dive *);

20
file.c
View file

@ -100,6 +100,13 @@ static int try_to_open_zip(const char *filename, struct memblock *mem, GError **
return success; return success;
} }
#ifdef SQLITE3
static int try_to_open_db(const char *filename, struct memblock *mem, GError **error)
{
return parse_dm4_buffer(filename, mem->buffer, mem->size, &dive_table, error);
}
#endif
static timestamp_t parse_date(const char *date) static timestamp_t parse_date(const char *date)
{ {
int hour, min, sec; int hour, min, sec;
@ -258,6 +265,9 @@ static void parse_file_buffer(const char *filename, struct memblock *mem, GError
void parse_file(const char *filename, GError **error, gboolean possible_default_filename) void parse_file(const char *filename, GError **error, gboolean possible_default_filename)
{ {
struct memblock mem; struct memblock mem;
#ifdef SQLITE3
char *fmt;
#endif
if (readfile(filename, &mem) < 0) { if (readfile(filename, &mem) < 0) {
/* we don't want to display an error if this was the default file */ /* we don't want to display an error if this was the default file */
@ -285,6 +295,16 @@ void parse_file(const char *filename, GError **error, gboolean possible_default_
if (possible_default_filename) if (possible_default_filename)
set_filename(filename, TRUE); set_filename(filename, TRUE);
#ifdef SQLITE3
fmt = strrchr(filename, '.');
if (fmt && !strcasecmp(fmt + 1, "DB")) {
if (!try_to_open_db(filename, &mem, error)) {
free(mem.buffer);
return;
}
}
#endif
parse_file_buffer(filename, &mem, error); parse_file_buffer(filename, &mem, error);
free(mem.buffer); free(mem.buffer);
} }

View file

@ -138,6 +138,11 @@ static GtkFileFilter *setup_filter(void)
gtk_file_filter_add_pattern(filter, "*.dld"); gtk_file_filter_add_pattern(filter, "*.dld");
gtk_file_filter_add_pattern(filter, "*.DLD"); gtk_file_filter_add_pattern(filter, "*.DLD");
#endif #endif
#ifdef SQLITE3
gtk_file_filter_add_pattern(filter, "*.DB");
gtk_file_filter_add_pattern(filter, "*.db");
#endif
gtk_file_filter_add_mime_type(filter, "text/xml"); gtk_file_filter_add_mime_type(filter, "text/xml");
gtk_file_filter_set_name(filter, _("XML file")); gtk_file_filter_set_name(filter, _("XML file"));

View file

@ -14,6 +14,10 @@
#endif #endif
#include <glib/gi18n.h> #include <glib/gi18n.h>
#ifdef SQLITE3
#include<sqlite3.h>
#endif
#include "dive.h" #include "dive.h"
#include "device.h" #include "device.h"
@ -1532,6 +1536,218 @@ void parse_xml_buffer(const char *url, const char *buffer, int size,
xmlFreeDoc(doc); xmlFreeDoc(doc);
} }
#ifdef SQLITE3
extern int dm4_events(void *handle, int columns, char **data, char **column)
{
event_start();
if(data[1])
cur_event.time.seconds = atoi(data[1]);
if(data[2]) {
switch (atoi(data[2])) {
case 1:
/* 1 Mandatory Safety Stop */
cur_event.name = strdup("safety stop (mandatory)");
break;
case 3:
/* 3 Deco */
/* What is Subsurface's term for going to
* deco? */
cur_event.name = strdup("deco");
break;
case 4:
/* 4 Ascent warning */
cur_event.name = strdup("ascent");
break;
case 5:
/* 5 Ceiling broken */
cur_event.name = strdup("violation");
break;
case 10:
/* 10 OLF 80% */
case 11:
/* 11 OLF 100% */
cur_event.name = strdup("OLF");
break;
case 12:
/* 12 High ppO2 */
cur_event.name = strdup("PO2");
break;
case 18:
/* 18 Ceiling error */
cur_event.name = strdup("ceiling");
break;
case 19:
/* 19 Surfaced */
cur_event.name = strdup("surface");
break;
case 258:
/* 258 Bookmark */
cur_event.name = strdup("bookmark");
break;
default:
cur_event.name = strdup("unknown");
break;
}
}
event_end();
return 0;
}
extern int dm4_dive(void *param, int columns, char **data, char **column)
{
int i, interval, retval = 0;
sqlite3 *handle = (sqlite3 *)param;
float *profileBlob;
unsigned char *tempBlob;
int *pressureBlob;
time_t when;
struct tm *tm;
char *err = NULL;
char get_events_template[] = "select * from Mark where DiveId = %d";
char get_events[64];
dive_start();
cur_dive->number = atoi(data[0]);
/* Suunto saves time in 100 nano seconds, we'll need the time in
* seconds.
*/
when = (time_t)(atol(data[1]) / 10000000);
tm = localtime(&when);
/* Suunto starts counting time in year 1, we need epoch */
tm->tm_year -= 1969;
cur_dive->when = mktime(tm);
if (data[2])
utf8_string(data[2], &cur_dive->notes);
/*
* DM4 stores Duration and DiveTime. It looks like DiveTime is
* 10 to 60 seconds shorter than Duration. However, I have no
* idea what is the difference and which one should be used.
* Duration = data[3]
* DiveTime = data[15]
*/
if (data[15])
cur_dive->duration.seconds = atoi(data[15]);
/*
* TODO: the deviceid hash should be calculated here.
*/
settings_start();
dc_settings_start();
if (data[4])
utf8_string(data[4], &cur_settings.dc.serial_nr);
if (data[5])
utf8_string(data[5], &cur_settings.dc.model);
cur_settings.dc.deviceid = 0xffffffff;
dc_settings_end();
settings_end();
if (data[6])
cur_dive->maxdepth.mm = atof(data[6]) * 1000;
if (data[8])
cur_dive->airtemp.mkelvin = (atoi(data[8]) + 273.15) * 1000;
if (data[9])
cur_dive->watertemp.mkelvin = (atoi(data[9]) + 273.15) * 1000;
/*
* TODO: handle multiple cylinders
*/
cylinder_start();
if (data[10])
cur_dive->cylinder[cur_cylinder_index].start.mbar = (atoi(data[10]));
if (data[11])
cur_dive->cylinder[cur_cylinder_index].end.mbar = (atoi(data[11]));
if (data[12])
cur_dive->cylinder[cur_cylinder_index].type.size.mliter = (atof(data[12])) * 1000;
if (data[13])
cur_dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = (atoi(data[13]));
if (data[20])
cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = atoi(data[20]) * 10;
if (data[21])
cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = atoi(data[21]) * 10;
cylinder_end();
if (data[14])
cur_dive->surface_pressure.mbar = (atoi(data[14]) * 1000);
interval = data[16] ? atoi(data[16]) : 0;
profileBlob = (float *)data[17];
tempBlob = (unsigned char *)data[18];
pressureBlob = (int *)data[19];
for (i=0; interval && i * interval < cur_dive->duration.seconds; i++) {
sample_start();
cur_sample->time.seconds = i * interval;
if (profileBlob)
cur_sample->depth.mm = profileBlob[i] * 1000;
else
cur_sample->depth.mm = cur_dive->maxdepth.mm;
if (tempBlob && tempBlob[i])
cur_sample->temperature.mkelvin = (tempBlob[i] + 273.15) * 1000;
if (pressureBlob)
cur_sample->cylinderpressure.mbar = pressureBlob[i] ;
sample_end();
}
snprintf(get_events, sizeof(get_events) - 1, get_events_template, cur_dive->number);
retval = sqlite3_exec(handle, get_events, &dm4_events, 0, &err);
if (retval != SQLITE_OK) {
fprintf(stderr, _("Database query get_events failed.\n"));
return 1;
}
dive_end();
/*
for (i=0; i<columns;++i) {
fprintf(stderr, "%s\t", column[i]);
}
fprintf(stderr, "\n");
for (i=0; i<columns;++i) {
fprintf(stderr, "%s\t", data[i]);
}
fprintf(stderr, "\n");
//exit(0);
*/
return SQLITE_OK;
}
#endif
int parse_dm4_buffer(const char *url, const char *buffer, int size,
struct dive_table *table, GError **error)
{
#ifdef SQLITE3
int retval;
char *err = NULL;
sqlite3 *handle;
target_table = table;
char get_dives[] = "select D.DiveId,StartTime,Note,Duration,SourceSerialNumber,Source,MaxDepth,SampleInterval,StartTemperature,BottomTemperature,D.StartPressure,D.EndPressure,CylinderVolume,CylinderWorkPressure,SurfacePressure,DiveTime,SampleInterval,ProfileBlob,TemperatureBlob,PressureBlob,Oxygen,Helium FROM Dive AS D JOIN DiveMixture AS MIX ON D.DiveId=MIX.DiveId";
retval = sqlite3_open(url,&handle);
if(retval) {
fprintf(stderr, _("Database connection failed '%s'.\n"), url);
return 1;
}
retval = sqlite3_exec(handle, get_dives, &dm4_dive, handle, &err);
if (retval != SQLITE_OK) {
fprintf(stderr, _("Database query failed '%s'.\n"), url);
return 1;
}
sqlite3_close(handle);
#endif
return 0;
}
void parse_xml_init(void) void parse_xml_init(void)
{ {
LIBXML_TEST_VERSION LIBXML_TEST_VERSION