diff options
author | Miika Turkia <miika.turkia@gmail.com> | 2013-03-05 07:10:39 +0200 |
---|---|---|
committer | Dirk Hohndel <dirk@hohndel.org> | 2013-03-04 22:08:03 -0800 |
commit | a94f7807f8eb99b5387d7f9cd973571d9f44f904 (patch) | |
tree | 851b2643648b0267b4b1d4ef8e9daf530815ec75 | |
parent | a6a487306e5606cb49351c23e3eb2527cd0a79d3 (diff) | |
download | subsurface-a94f7807f8eb99b5387d7f9cd973571d9f44f904.tar.gz |
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>
-rw-r--r-- | Documentation/user-manual.txt | 36 | ||||
-rw-r--r-- | Makefile | 9 | ||||
-rw-r--r-- | dive.h | 2 | ||||
-rw-r--r-- | file.c | 20 | ||||
-rw-r--r-- | gtk-gui.c | 5 | ||||
-rw-r--r-- | parse-xml.c | 216 |
6 files changed, 286 insertions, 2 deletions
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 322107647..8284d63a7 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -785,6 +785,41 @@ The file Divelogs.SDE can now be opened (or imported) in Subsurface. Different from earlier versions of Subsurface, no manual 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]] Importing Dives from MacDive ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -861,6 +896,7 @@ button). Then do the following: - Open the 'File - Export - XML' menu - Select the dives that you want to export - 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). @@ -120,6 +120,11 @@ ifneq ($(strip $(LIBZIP)),) ZIP = -DLIBZIP $(shell $(PKGCONFIG) --cflags libzip) endif +LIBSQLITE3 = $(shell $(PKGCONFIG) --libs sqlite3 2> /dev/null) +ifneq ($(strip $(LIBSQLITE3)),) + SQLITE3 = -DSQLITE3 $(shell $(PKGCONFIG) --cflags sqlite3) +endif + ifeq ($(UNAME), linux) LIBGCONF2 = $(shell $(PKGCONFIG) --libs gconf-2.0) GCONF2CFLAGS = $(shell $(PKGCONFIG) --cflags gconf-2.0) @@ -152,7 +157,7 @@ ifneq ($(strip $(LIBXSLT)),) XSLT=-DXSLT='"$(XSLTDIR)"' 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)) MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.mo)) @@ -266,7 +271,7 @@ update-po-files: tx pull -af EXTRA_FLAGS = $(GTKCFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) \ - $(XSLT) $(ZIP) $(LIBDIVECOMPUTERCFLAGS) \ + $(XSLT) $(ZIP) $(SQLITE3) $(LIBDIVECOMPUTERCFLAGS) \ $(LIBSOUPCFLAGS) $(OSMGPSMAPFLAGS) $(GCONF2CFLAGS) %.o: %.c @@ -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 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 show_dive_info(struct dive *); @@ -100,6 +100,13 @@ static int try_to_open_zip(const char *filename, struct memblock *mem, GError ** 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) { 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) { struct memblock mem; +#ifdef SQLITE3 + char *fmt; +#endif if (readfile(filename, &mem) < 0) { /* 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) 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); free(mem.buffer); } @@ -138,6 +138,11 @@ static GtkFileFilter *setup_filter(void) gtk_file_filter_add_pattern(filter, "*.dld"); gtk_file_filter_add_pattern(filter, "*.DLD"); #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_set_name(filter, _("XML file")); diff --git a/parse-xml.c b/parse-xml.c index 23f0ddaf4..80a12442e 100644 --- a/parse-xml.c +++ b/parse-xml.c @@ -14,6 +14,10 @@ #endif #include <glib/gi18n.h> +#ifdef SQLITE3 +#include<sqlite3.h> +#endif + #include "dive.h" #include "device.h" @@ -1532,6 +1536,218 @@ void parse_xml_buffer(const char *url, const char *buffer, int size, 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) { LIBXML_TEST_VERSION |