]> git.tdb.fi Git - ext/subsurface.git/blob - libdivecomputer.c
libdivecomputer integration: add a progress bar
[ext/subsurface.git] / libdivecomputer.c
1 #include <stdio.h>
2 #include <gtk/gtk.h>
3
4 #include "dive.h"
5 #include "display.h"
6
7 /* libdivecomputer */
8 #include <device.h>
9 #include <suunto.h>
10 #include <reefnet.h>
11 #include <uwatec.h>
12 #include <oceanic.h>
13 #include <mares.h>
14 #include <hw.h>
15 #include <cressi.h>
16 #include <zeagle.h>
17 #include <atomics.h>
18 #include <utils.h>
19
20 /*
21  * I'd love to do a while-loop here for pending events, but
22  * that seems to screw up with the dive computer reading timing.
23  *
24  * I may need to spawn a new thread to do the computer
25  * reading stuff..
26  */
27 static int run_gtk_mainloop(void)
28 {
29         return gtk_main_iteration_do(0);
30 }
31
32 static void error(const char *fmt, ...)
33 {
34         va_list args;
35         GError *error;
36
37         va_start(args, fmt);
38         error = g_error_new_valist(
39                 g_quark_from_string("divelog"),
40                 DIVE_ERROR_PARSE, fmt, args);
41         va_end(args);
42         report_error(error);
43         g_error_free(error);
44 }
45
46 typedef struct device_data_t {
47         device_type_t type;
48         const char *name, *devname;
49         GtkWidget *progressbar;
50         device_devinfo_t devinfo;
51         device_clock_t clock;
52 } device_data_t;
53
54 static parser_status_t create_parser(device_data_t *devdata, parser_t **parser)
55 {
56         switch (devdata->type) {
57         case DEVICE_TYPE_SUUNTO_SOLUTION:
58                 return suunto_solution_parser_create(parser);
59
60         case DEVICE_TYPE_SUUNTO_EON:
61                 return suunto_eon_parser_create(parser, 0);
62
63         case DEVICE_TYPE_SUUNTO_VYPER:
64                 if (devdata->devinfo.model == 0x01)
65                         return suunto_eon_parser_create(parser, 1);
66                 return suunto_vyper_parser_create(parser);
67
68         case DEVICE_TYPE_SUUNTO_VYPER2:
69         case DEVICE_TYPE_SUUNTO_D9:
70                 return suunto_d9_parser_create(parser, devdata->devinfo.model);
71
72         case DEVICE_TYPE_UWATEC_ALADIN:
73         case DEVICE_TYPE_UWATEC_MEMOMOUSE:
74                 return uwatec_memomouse_parser_create(parser, devdata->clock.devtime, devdata->clock.systime);
75
76         case DEVICE_TYPE_UWATEC_SMART:
77                 return uwatec_smart_parser_create(parser, devdata->devinfo.model, devdata->clock.devtime, devdata->clock.systime);
78
79         case DEVICE_TYPE_REEFNET_SENSUS:
80                 return reefnet_sensus_parser_create(parser, devdata->clock.devtime, devdata->clock.systime);
81
82         case DEVICE_TYPE_REEFNET_SENSUSPRO:
83                 return reefnet_sensuspro_parser_create(parser, devdata->clock.devtime, devdata->clock.systime);
84
85         case DEVICE_TYPE_REEFNET_SENSUSULTRA:
86                 return reefnet_sensusultra_parser_create(parser, devdata->clock.devtime, devdata->clock.systime);
87
88         case DEVICE_TYPE_OCEANIC_VTPRO:
89                 return oceanic_vtpro_parser_create(parser);
90
91         case DEVICE_TYPE_OCEANIC_VEO250:
92                 return oceanic_veo250_parser_create(parser, devdata->devinfo.model);
93
94         case DEVICE_TYPE_OCEANIC_ATOM2:
95                 return oceanic_atom2_parser_create(parser, devdata->devinfo.model);
96
97         case DEVICE_TYPE_MARES_NEMO:
98         case DEVICE_TYPE_MARES_PUCK:
99                 return mares_nemo_parser_create(parser, devdata->devinfo.model);
100
101         case DEVICE_TYPE_MARES_ICONHD:
102                 return mares_iconhd_parser_create(parser);
103
104         case DEVICE_TYPE_HW_OSTC:
105                 return hw_ostc_parser_create(parser);
106
107         case DEVICE_TYPE_CRESSI_EDY:
108         case DEVICE_TYPE_ZEAGLE_N2ITION3:
109                 return cressi_edy_parser_create(parser, devdata->devinfo.model);
110
111         case DEVICE_TYPE_ATOMICS_COBALT:
112                 return atomics_cobalt_parser_create(parser);
113
114         default:
115                 return PARSER_STATUS_ERROR;
116         }
117 }
118
119 static int parse_gasmixes(parser_t *parser, int ngases)
120 {
121         int i;
122
123         for (i = 0; i < ngases; i++) {
124                 int rc;
125                 gasmix_t gasmix = {0};
126
127                 rc = parser_get_field(parser, FIELD_TYPE_GASMIX, i, &gasmix);
128                 if (rc != PARSER_STATUS_SUCCESS && rc != PARSER_STATUS_UNSUPPORTED)
129                         return rc;
130
131                 printf("<gasmix>\n"
132                         "   <he>%.1f</he>\n"
133                         "   <o2>%.1f</o2>\n"
134                         "   <n2>%.1f</n2>\n"
135                         "</gasmix>\n",
136                         gasmix.helium * 100.0,
137                         gasmix.oxygen * 100.0,
138                         gasmix.nitrogen * 100.0);
139         }
140         return PARSER_STATUS_SUCCESS;
141 }
142
143 void
144 sample_cb (parser_sample_type_t type, parser_sample_value_t value, void *userdata)
145 {
146         int i;
147         static const char *events[] = {
148                 "none", "deco", "rbt", "ascent", "ceiling", "workload", "transmitter",
149                 "violation", "bookmark", "surface", "safety stop", "gaschange",
150                 "safety stop (voluntary)", "safety stop (mandatory)", "deepstop",
151                 "ceiling (safety stop)", "unknown", "divetime", "maxdepth",
152                 "OLF", "PO2", "airtime", "rgbm", "heading", "tissue level warning"};
153
154         switch (type) {
155         case SAMPLE_TYPE_TIME:
156                 printf("<sample>\n");
157                 printf("   <time>%02u:%02u</time>\n", value.time / 60, value.time % 60);
158                 break;
159         case SAMPLE_TYPE_DEPTH:
160                 printf("   <depth>%.2f</depth>\n", value.depth);
161                 break;
162         case SAMPLE_TYPE_PRESSURE:
163                 printf("   <pressure tank=\"%u\">%.2f</pressure>\n", value.pressure.tank, value.pressure.value);
164                 break;
165         case SAMPLE_TYPE_TEMPERATURE:
166                 printf("   <temperature>%.2f</temperature>\n", value.temperature);
167                 break;
168         case SAMPLE_TYPE_EVENT:
169                 printf("   <event type=\"%u\" time=\"%u\" flags=\"%u\" value=\"%u\">%s</event>\n",
170                         value.event.type, value.event.time, value.event.flags, value.event.value, events[value.event.type]);
171                 break;
172         case SAMPLE_TYPE_RBT:
173                 printf("   <rbt>%u</rbt>\n", value.rbt);
174                 break;
175         case SAMPLE_TYPE_HEARTBEAT:
176                 printf("   <heartbeat>%u</heartbeat>\n", value.heartbeat);
177                 break;
178         case SAMPLE_TYPE_BEARING:
179                 printf("   <bearing>%u</bearing>\n", value.bearing);
180                 break;
181         case SAMPLE_TYPE_VENDOR:
182                 printf("   <vendor type=\"%u\" size=\"%u\">", value.vendor.type, value.vendor.size);
183                 for (i = 0; i < value.vendor.size; ++i)
184                         printf("%02X", ((unsigned char *) value.vendor.data)[i]);
185                 printf("</vendor>\n");
186                 break;
187         default:
188                 break;
189         }
190 }
191
192
193 static int parse_samples(parser_t *parser)
194 {
195         // Parse the sample data.
196         printf("Parsing the sample data.\n");
197         return parser_samples_foreach(parser, sample_cb, NULL);
198 }
199
200 static int dive_cb(const unsigned char *data, unsigned int size,
201         const unsigned char *fingerprint, unsigned int fsize,
202         void *userdata)
203 {
204         int rc;
205         parser_t *parser = NULL;
206         device_data_t *devdata = userdata;
207         dc_datetime_t dt = {0};
208
209         /* Christ, this is hacky */
210         run_gtk_mainloop();
211
212         rc = create_parser(devdata, &parser);
213         if (rc != PARSER_STATUS_SUCCESS) {
214                 error("Unable to create parser for %s", devdata->name);
215                 return rc;
216         }
217
218         rc = parser_set_data(parser, data, size);
219         if (rc != PARSER_STATUS_SUCCESS) {
220                 error("Error registering the data.");
221                 parser_destroy(parser);
222                 return rc;
223         }
224
225         rc = parser_get_datetime(parser, &dt);
226         if (rc != PARSER_STATUS_SUCCESS && rc != PARSER_STATUS_UNSUPPORTED) {
227                 error("Error parsing the datetime.");
228                 parser_destroy (parser);
229                 return rc;
230         }
231
232         printf("<datetime>%04i-%02i-%02i %02i:%02i:%02i</datetime>\n",
233                 dt.year, dt.month, dt.day,
234                 dt.hour, dt.minute, dt.second);
235
236         // Parse the divetime.
237         printf("Parsing the divetime.\n");
238         unsigned int divetime = 0;
239         rc = parser_get_field (parser, FIELD_TYPE_DIVETIME, 0, &divetime);
240         if (rc != PARSER_STATUS_SUCCESS && rc != PARSER_STATUS_UNSUPPORTED) {
241                 error("Error parsing the divetime.");
242                 parser_destroy(parser);
243                 return rc;
244         }
245
246         printf("<divetime>%02u:%02u</divetime>\n",
247                 divetime / 60, divetime % 60);
248
249         // Parse the maxdepth.
250         printf("Parsing the maxdepth.\n");
251         double maxdepth = 0.0;
252         rc = parser_get_field(parser, FIELD_TYPE_MAXDEPTH, 0, &maxdepth);
253         if (rc != PARSER_STATUS_SUCCESS && rc != PARSER_STATUS_UNSUPPORTED) {
254                 error("Error parsing the maxdepth.");
255                 parser_destroy(parser);
256                 return rc;
257         }
258
259         printf("<maxdepth>%.2f</maxdepth>\n", maxdepth);
260
261         // Parse the gas mixes.
262         printf("Parsing the gas mixes.\n");
263         unsigned int ngases = 0;
264         rc = parser_get_field(parser, FIELD_TYPE_GASMIX_COUNT, 0, &ngases);
265         if (rc != PARSER_STATUS_SUCCESS && rc != PARSER_STATUS_UNSUPPORTED) {
266                 error("Error parsing the gas mix count.");
267                 parser_destroy(parser);
268                 return rc;
269         }
270
271         rc = parse_gasmixes(parser, ngases);
272         if (rc != PARSER_STATUS_SUCCESS) {
273                 error("Error parsing the gas mix.");
274                 parser_destroy(parser);
275                 return rc;
276         }
277
278         // Initialize the sample data.
279         rc = parse_samples(parser);
280         if (rc != PARSER_STATUS_SUCCESS) {
281                 error("Error parsing the samples.");
282                 parser_destroy(parser);
283                 return rc;
284         }
285
286         parser_destroy(parser);
287         return 1;
288 }
289
290
291 static device_status_t import_device_data(device_t *device, device_data_t *devicedata)
292 {
293         return device_foreach(device, dive_cb, devicedata);
294 }
295
296 static device_status_t device_open(const char *devname,
297         device_type_t type,
298         device_t **device)
299 {
300         switch (type) {
301         case DEVICE_TYPE_SUUNTO_SOLUTION:
302                 return suunto_solution_device_open(device, devname);
303
304         case DEVICE_TYPE_SUUNTO_EON:
305                 return suunto_eon_device_open(device, devname);
306
307         case DEVICE_TYPE_SUUNTO_VYPER:
308                 return suunto_vyper_device_open(device, devname);
309
310         case DEVICE_TYPE_SUUNTO_VYPER2:
311                 return suunto_vyper2_device_open(device, devname);
312
313         case DEVICE_TYPE_SUUNTO_D9:
314                 return suunto_d9_device_open(device, devname);
315
316         case DEVICE_TYPE_UWATEC_ALADIN:
317                 return uwatec_aladin_device_open(device, devname);
318
319         case DEVICE_TYPE_UWATEC_MEMOMOUSE:
320                 return uwatec_memomouse_device_open(device, devname);
321
322         case DEVICE_TYPE_UWATEC_SMART:
323                 return uwatec_smart_device_open(device);
324
325         case DEVICE_TYPE_REEFNET_SENSUS:
326                 return reefnet_sensus_device_open(device, devname);
327
328         case DEVICE_TYPE_REEFNET_SENSUSPRO:
329                 return reefnet_sensuspro_device_open(device, devname);
330
331         case DEVICE_TYPE_REEFNET_SENSUSULTRA:
332                 return reefnet_sensusultra_device_open(device, devname);
333
334         case DEVICE_TYPE_OCEANIC_VTPRO:
335                 return oceanic_vtpro_device_open(device, devname);
336
337         case DEVICE_TYPE_OCEANIC_VEO250:
338                 return oceanic_veo250_device_open(device, devname);
339
340         case DEVICE_TYPE_OCEANIC_ATOM2:
341                 return oceanic_atom2_device_open(device, devname);
342
343         case DEVICE_TYPE_MARES_NEMO:
344                 return mares_nemo_device_open(device, devname);
345
346         case DEVICE_TYPE_MARES_PUCK:
347                 return mares_puck_device_open(device, devname);
348
349         case DEVICE_TYPE_MARES_ICONHD:
350                 return mares_iconhd_device_open(device, devname);
351
352         case DEVICE_TYPE_HW_OSTC:
353                 return hw_ostc_device_open(device, devname);
354
355         case DEVICE_TYPE_CRESSI_EDY:
356                 return cressi_edy_device_open(device, devname);
357
358         case DEVICE_TYPE_ZEAGLE_N2ITION3:
359                 return zeagle_n2ition3_device_open(device, devname);
360
361         case DEVICE_TYPE_ATOMICS_COBALT:
362                 return atomics_cobalt_device_open(device);
363
364         default:
365                 return DEVICE_STATUS_ERROR;
366         }
367 }
368
369 static void
370 event_cb(device_t *device, device_event_t event, const void *data, void *userdata)
371 {
372         const device_progress_t *progress = (device_progress_t *) data;
373         const device_devinfo_t *devinfo = (device_devinfo_t *) data;
374         const device_clock_t *clock = (device_clock_t *) data;
375         device_data_t *devdata = (device_data_t *) userdata;
376
377         /* Christ, this is hacky */
378         run_gtk_mainloop();
379
380         switch (event) {
381         case DEVICE_EVENT_WAITING:
382                 printf("Event: waiting for user action\n");
383                 break;
384         case DEVICE_EVENT_PROGRESS:
385                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(devdata->progressbar),
386                         (double) progress->current / (double) progress->maximum);
387                 break;
388         case DEVICE_EVENT_DEVINFO:
389                 devdata->devinfo = *devinfo;
390                 printf("Event: model=%u (0x%08x), firmware=%u (0x%08x), serial=%u (0x%08x)\n",
391                         devinfo->model, devinfo->model,
392                         devinfo->firmware, devinfo->firmware,
393                         devinfo->serial, devinfo->serial);
394                 break;
395         case DEVICE_EVENT_CLOCK:
396                 devdata->clock = *clock;
397                 printf("Event: systime=%lld, devtime=%u\n",
398                         clock->systime, clock->devtime);
399                 break;
400         default:
401                 break;
402         }
403 }
404
405 static int
406 cancel_cb(void *userdata)
407 {
408         return run_gtk_mainloop();
409 }
410
411 static void do_import(device_data_t *data)
412 {
413         /* FIXME! Needs user input! */
414         const char *devname = "/dev/ttyUSB0";
415         device_t *device = NULL;
416         device_status_t rc;
417
418         rc = device_open(devname, data->type, &device);
419         if (rc != DEVICE_STATUS_SUCCESS) {
420                 error("Unable to open %s (%s)", data->name, data->devname);
421                 return;
422         }
423
424         // Register the event handler.
425         int events = DEVICE_EVENT_WAITING | DEVICE_EVENT_PROGRESS | DEVICE_EVENT_DEVINFO | DEVICE_EVENT_CLOCK;
426         rc = device_set_events(device, events, event_cb, data);
427         if (rc != DEVICE_STATUS_SUCCESS) {
428                 error("Error registering the event handler.");
429                 device_close(device);
430                 return;
431         }
432
433         // Register the cancellation handler.
434         rc = device_set_cancel(device, cancel_cb, data);
435         if (rc != DEVICE_STATUS_SUCCESS) {
436                 error("Error registering the cancellation handler.");
437                 device_close(device);
438                 return;
439         }
440
441         rc = import_device_data(device, data);
442         if (rc != DEVICE_STATUS_SUCCESS) {
443                 error("Dive data import error");
444                 device_close(device);
445                 return;
446         }
447
448         device_close(device);
449 }
450
451 /*
452  * Taken from 'example.c' in libdivecomputer.
453  *
454  * I really wish there was some way to just have
455  * libdivecomputer tell us what devices it supports,
456  * rather than have the application have to know..
457  */
458 struct device_list {
459         const char *name;
460         device_type_t type;
461 } device_list[] = {
462         { "Suunto Solution",    DEVICE_TYPE_SUUNTO_SOLUTION },
463         { "Suunto Eon",         DEVICE_TYPE_SUUNTO_EON },
464         { "Suunto Vyper",       DEVICE_TYPE_SUUNTO_VYPER },
465         { "Suunto Vyper Air",   DEVICE_TYPE_SUUNTO_VYPER2 },
466         { "Suunto D9",          DEVICE_TYPE_SUUNTO_D9 },
467         { "Uwatec Aladin",      DEVICE_TYPE_UWATEC_ALADIN },
468         { "Uwatec Memo Mouse",  DEVICE_TYPE_UWATEC_MEMOMOUSE },
469         { "Uwatec Smart",       DEVICE_TYPE_UWATEC_SMART },
470         { "ReefNet Sensus",     DEVICE_TYPE_REEFNET_SENSUS },
471         { "ReefNet Sensus Pro", DEVICE_TYPE_REEFNET_SENSUSPRO },
472         { "ReefNet Sensus Ultra",DEVICE_TYPE_REEFNET_SENSUSULTRA },
473         { "Oceanic VT Pro",     DEVICE_TYPE_OCEANIC_VTPRO },
474         { "Oceanic Veo250",     DEVICE_TYPE_OCEANIC_VEO250 },
475         { "Oceanic Atom 2",     DEVICE_TYPE_OCEANIC_ATOM2 },
476         { "Mares Nemo",         DEVICE_TYPE_MARES_NEMO },
477         { "Mares Puck",         DEVICE_TYPE_MARES_PUCK },
478         { "Mares Icon HD",      DEVICE_TYPE_MARES_ICONHD },
479         { "OSTC",               DEVICE_TYPE_HW_OSTC },
480         { "Cressi Edy",         DEVICE_TYPE_CRESSI_EDY },
481         { "Zeagle N2iTiON 3",   DEVICE_TYPE_ZEAGLE_N2ITION3 },
482         { "Atomics Cobalt",     DEVICE_TYPE_ATOMICS_COBALT },
483         { NULL }
484 };
485
486 static void fill_computer_list(GtkListStore *store)
487 {
488         GtkTreeIter iter;
489         struct device_list *list = device_list;
490
491         for (list = device_list ; list->name ; list++) {
492                 gtk_list_store_append(store, &iter);
493                 gtk_list_store_set(store, &iter,
494                         0, list->name,
495                         1, list->type,
496                         -1);
497         }
498 }
499
500 static GtkComboBox *dive_computer_selector(GtkWidget *dialog)
501 {
502         GtkWidget *hbox, *combo_box;
503         GtkListStore *model;
504         GtkCellRenderer *renderer;
505
506         hbox = gtk_hbox_new(FALSE, 6);
507         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, FALSE, FALSE, 3);
508
509         model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
510         fill_computer_list(model);
511
512         combo_box = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
513         gtk_box_pack_start(GTK_BOX(hbox), combo_box, FALSE, TRUE, 3);
514
515         renderer = gtk_cell_renderer_text_new();
516         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo_box), renderer, TRUE);
517         gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo_box), renderer, "text", 0, NULL);
518
519         return GTK_COMBO_BOX(combo_box);
520 }
521
522 void import_dialog(GtkWidget *w, gpointer data)
523 {
524         int result;
525         GtkWidget *dialog, *hbox;
526         GtkComboBox *computer;
527         device_data_t devicedata = {
528                 .devname = "/dev/ttyUSB0",
529         };
530
531         dialog = gtk_dialog_new_with_buttons("Import from dive computer",
532                 GTK_WINDOW(main_window),
533                 GTK_DIALOG_DESTROY_WITH_PARENT,
534                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
535                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
536                 NULL);
537
538         computer = dive_computer_selector(dialog);
539
540         hbox = gtk_hbox_new(FALSE, 6);
541         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, FALSE, TRUE, 3);
542         devicedata.progressbar = gtk_progress_bar_new();
543         gtk_container_add(GTK_CONTAINER(hbox), devicedata.progressbar);
544
545         gtk_widget_show_all(dialog);
546         result = gtk_dialog_run(GTK_DIALOG(dialog));
547         switch (result) {
548                 int type;
549                 GtkTreeIter iter;
550                 GtkTreeModel *model;
551                 const char *comp;
552         case GTK_RESPONSE_ACCEPT:
553                 if (!gtk_combo_box_get_active_iter(computer, &iter))
554                         break;
555                 model = gtk_combo_box_get_model(computer);
556                 gtk_tree_model_get(model, &iter,
557                         0, &comp,
558                         1, &type,
559                         -1);
560                 devicedata.type = type;
561                 devicedata.name = comp;
562                 do_import(&devicedata);
563                 break;
564         default:
565                 break;
566         }
567         gtk_widget_destroy(dialog);
568 }