libdc/examples/output_xml.c
Linus Torvalds 362fe3f936 Add subsurface-specific DC field extension: descriptor/value strings
The default libdivecomputer fields are good for structured data that has
a well-defined format, like the cylinder information, or the temperature
data.

But it is entirely useless for miscellaneous divecomputer-specific
information, where there is no standard way of representing the data
across different kinds of dive computers.

Examples of this include simple things like deco calculation algorithm
(what kind of Buehlmann, gradient factor information or is it some
vendor-specific mode?) and even something as trivial as a serial number.

No, serial numbers aren't numbers. They are strings. Really.

But this also includes much more complex data that is really specific to
a particular dive computer or family: what the battery status is for the
dive computer or the wireless transmitters it is connected to (sometimes
it's a voltage, sometimes it's a percentage, sometimes it's just "good"
or "marginal").

It also includes random incidental information like firmware version
numbers (again, these are strings, not numbers, despite the name) or
dive mode and personal adjustment information.

So allow the dive computer to just give "extra information" in the form
of an array of { key, value } string pairs.  For my Perdix AI the
information could be

  { "Serial", "370d1f24" }
  { "FW Version", "44" }
  { "Deco model", "GF 40/85" }
  { "Battery type", "3.6V Saft" }
  { "Battery at end", "3.4 V" }

and for my EON Steel with three wireless transmitters connected it can
look like this:

  { "Serial", "1742104730" }
  { "FW Version", "1.6.5" }
  { "HW Version", "70.3.0" }
  { "Battery at start", "Charge: 83%, Voltage: 4.012V" }
  { "Deco algorithm", "Suunto Fused RGBM" }
  { "Personal Adjustment", "P-2" }
  { "Battery at end", "Charge: 79%, Voltage: 3.977V" }
  { "Dive Mode", "Trimix" }
  { "Desaturation Time", "7:53" }
  { "Transmitter ID", "1519107801" }
  { "Transmitter Battery at start", "87 %" }
  { "Transmitter Battery at end", "87 %" }
  { "Transmitter ID", "1550110028" }
  { "Transmitter Battery at start", "100 %" }
  { "Transmitter Battery at end", "100 %" }
  { "Transmitter ID", "1719102387" }
  { "Transmitter Battery at start", "100 %" }
  { "Transmitter Battery at end", "100 %" }

so this data is inherently unstructured and dependent on the dive
computer, but quite relevant to the diver.  Subsurface shows this in the
"Extra Info" panel for each dive computer.

Also teach the example output-xml code about the new string field
extension.  That example output-xml code was written by Anton Lundin in
the old Subsurface branch, and signed-off-by Dirk.  The sign-offs here
are taken from that original work.

Signed-off-by: Anton Lundin <glance@acc.umu.se>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-04-24 17:32:21 -07:00

449 lines
13 KiB
C

/*
* libdivecomputer
*
* Copyright (C) 2016 Jef Driesen
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <libdivecomputer/units.h>
#include "output-private.h"
#include "utils.h"
static dc_status_t dctool_xml_output_write (dctool_output_t *output, dc_parser_t *parser, const unsigned char data[], unsigned int size, const unsigned char fingerprint[], unsigned int fsize);
static dc_status_t dctool_xml_output_free (dctool_output_t *output);
typedef struct dctool_xml_output_t {
dctool_output_t base;
FILE *ostream;
dctool_units_t units;
} dctool_xml_output_t;
static const dctool_output_vtable_t xml_vtable = {
sizeof(dctool_xml_output_t), /* size */
dctool_xml_output_write, /* write */
dctool_xml_output_free, /* free */
};
typedef struct sample_data_t {
FILE *ostream;
dctool_units_t units;
unsigned int nsamples;
} sample_data_t;
static double
convert_depth (double value, dctool_units_t units)
{
if (units == DCTOOL_UNITS_IMPERIAL) {
return value / FEET;
} else {
return value;
}
}
static double
convert_temperature (double value, dctool_units_t units)
{
if (units == DCTOOL_UNITS_IMPERIAL) {
return value * (9.0 / 5.0) + 32.0;
} else {
return value;
}
}
static double
convert_pressure (double value, dctool_units_t units)
{
if (units == DCTOOL_UNITS_IMPERIAL) {
return value * BAR / PSI;
} else {
return value;
}
}
static double
convert_volume (double value, dctool_units_t units)
{
if (units == DCTOOL_UNITS_IMPERIAL) {
return value / 1000.0 / CUFT;
} else {
return value;
}
}
static void
sample_cb (dc_sample_type_t type, dc_sample_value_t value, void *userdata)
{
static const char *events[] = {
"none", "deco", "rbt", "ascent", "ceiling", "workload", "transmitter",
"violation", "bookmark", "surface", "safety stop", "gaschange",
"safety stop (voluntary)", "safety stop (mandatory)", "deepstop",
"ceiling (safety stop)", "floor", "divetime", "maxdepth",
"OLF", "PO2", "airtime", "rgbm", "heading", "tissue level warning",
"gaschange2"};
static const char *decostop[] = {
"ndl", "safety", "deco", "deep"};
sample_data_t *sampledata = (sample_data_t *) userdata;
switch (type) {
case DC_SAMPLE_TIME:
if (sampledata->nsamples++)
fprintf (sampledata->ostream, "</sample>\n");
fprintf (sampledata->ostream, "<sample>\n");
fprintf (sampledata->ostream, " <time>%02u:%02u</time>\n", value.time / 60, value.time % 60);
break;
case DC_SAMPLE_DEPTH:
fprintf (sampledata->ostream, " <depth>%.2f</depth>\n",
convert_depth(value.depth, sampledata->units));
break;
case DC_SAMPLE_PRESSURE:
fprintf (sampledata->ostream, " <pressure tank=\"%u\">%.2f</pressure>\n",
value.pressure.tank,
convert_pressure(value.pressure.value, sampledata->units));
break;
case DC_SAMPLE_TEMPERATURE:
fprintf (sampledata->ostream, " <temperature>%.2f</temperature>\n",
convert_temperature(value.temperature, sampledata->units));
break;
case DC_SAMPLE_EVENT:
if (value.event.type != SAMPLE_EVENT_GASCHANGE && value.event.type != SAMPLE_EVENT_GASCHANGE2) {
fprintf (sampledata->ostream, " <event type=\"%u\" time=\"%u\" flags=\"%u\" value=\"%u\">%s</event>\n",
value.event.type, value.event.time, value.event.flags, value.event.value, events[value.event.type]);
}
break;
case DC_SAMPLE_RBT:
fprintf (sampledata->ostream, " <rbt>%u</rbt>\n", value.rbt);
break;
case DC_SAMPLE_HEARTBEAT:
fprintf (sampledata->ostream, " <heartbeat>%u</heartbeat>\n", value.heartbeat);
break;
case DC_SAMPLE_BEARING:
fprintf (sampledata->ostream, " <bearing>%u</bearing>\n", value.bearing);
break;
case DC_SAMPLE_VENDOR:
fprintf (sampledata->ostream, " <vendor type=\"%u\" size=\"%u\">", value.vendor.type, value.vendor.size);
for (unsigned int i = 0; i < value.vendor.size; ++i)
fprintf (sampledata->ostream, "%02X", ((const unsigned char *) value.vendor.data)[i]);
fprintf (sampledata->ostream, "</vendor>\n");
break;
case DC_SAMPLE_SETPOINT:
fprintf (sampledata->ostream, " <setpoint>%.2f</setpoint>\n", value.setpoint);
break;
case DC_SAMPLE_PPO2:
fprintf (sampledata->ostream, " <ppo2>%.2f</ppo2>\n", value.ppo2);
break;
case DC_SAMPLE_CNS:
fprintf (sampledata->ostream, " <cns>%.1f</cns>\n", value.cns * 100.0);
break;
case DC_SAMPLE_DECO:
fprintf (sampledata->ostream, " <deco time=\"%u\" depth=\"%.2f\">%s</deco>\n",
value.deco.time,
convert_depth(value.deco.depth, sampledata->units),
decostop[value.deco.type]);
break;
case DC_SAMPLE_GASMIX:
fprintf (sampledata->ostream, " <gasmix>%u</gasmix>\n", value.gasmix);
break;
default:
break;
}
}
dctool_output_t *
dctool_xml_output_new (const char *filename, dctool_units_t units)
{
dctool_xml_output_t *output = NULL;
if (filename == NULL)
goto error_exit;
// Allocate memory.
output = (dctool_xml_output_t *) dctool_output_allocate (&xml_vtable);
if (output == NULL) {
goto error_exit;
}
// Open the output file.
output->ostream = fopen (filename, "w");
if (output->ostream == NULL) {
goto error_free;
}
output->units = units;
fprintf (output->ostream, "<device>\n");
return (dctool_output_t *) output;
error_free:
dctool_output_deallocate ((dctool_output_t *) output);
error_exit:
return NULL;
}
static dc_status_t
dctool_xml_output_write (dctool_output_t *abstract, dc_parser_t *parser, const unsigned char data[], unsigned int size, const unsigned char fingerprint[], unsigned int fsize)
{
dctool_xml_output_t *output = (dctool_xml_output_t *) abstract;
dc_status_t status = DC_STATUS_SUCCESS;
// Initialize the sample data.
sample_data_t sampledata = {0};
sampledata.nsamples = 0;
sampledata.ostream = output->ostream;
sampledata.units = output->units;
fprintf (output->ostream, "<dive>\n<number>%u</number>\n<size>%u</size>\n", abstract->number, size);
if (fingerprint) {
fprintf (output->ostream, "<fingerprint>");
for (unsigned int i = 0; i < fsize; ++i)
fprintf (output->ostream, "%02X", fingerprint[i]);
fprintf (output->ostream, "</fingerprint>\n");
}
// Parse the datetime.
message ("Parsing the datetime.\n");
dc_datetime_t dt = {0};
status = dc_parser_get_datetime (parser, &dt);
if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) {
ERROR ("Error parsing the datetime.");
goto cleanup;
}
if (dt.timezone == DC_TIMEZONE_NONE) {
fprintf (output->ostream, "<datetime>%04i-%02i-%02i %02i:%02i:%02i</datetime>\n",
dt.year, dt.month, dt.day,
dt.hour, dt.minute, dt.second);
} else {
fprintf (output->ostream, "<datetime>%04i-%02i-%02i %02i:%02i:%02i %+03i:%02i</datetime>\n",
dt.year, dt.month, dt.day,
dt.hour, dt.minute, dt.second,
dt.timezone / 3600, (dt.timezone % 3600) / 60);
}
// Parse the divetime.
message ("Parsing the divetime.\n");
unsigned int divetime = 0;
status = dc_parser_get_field (parser, DC_FIELD_DIVETIME, 0, &divetime);
if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) {
ERROR ("Error parsing the divetime.");
goto cleanup;
}
fprintf (output->ostream, "<divetime>%02u:%02u</divetime>\n",
divetime / 60, divetime % 60);
// Parse the maxdepth.
message ("Parsing the maxdepth.\n");
double maxdepth = 0.0;
status = dc_parser_get_field (parser, DC_FIELD_MAXDEPTH, 0, &maxdepth);
if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) {
ERROR ("Error parsing the maxdepth.");
goto cleanup;
}
fprintf (output->ostream, "<maxdepth>%.2f</maxdepth>\n",
convert_depth(maxdepth, output->units));
// Parse the temperature.
message ("Parsing the temperature.\n");
for (unsigned int i = 0; i < 3; ++i) {
dc_field_type_t fields[] = {DC_FIELD_TEMPERATURE_SURFACE,
DC_FIELD_TEMPERATURE_MINIMUM,
DC_FIELD_TEMPERATURE_MAXIMUM};
const char *names[] = {"surface", "minimum", "maximum"};
double temperature = 0.0;
status = dc_parser_get_field (parser, fields[i], 0, &temperature);
if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) {
ERROR ("Error parsing the temperature.");
goto cleanup;
}
if (status != DC_STATUS_UNSUPPORTED) {
fprintf (output->ostream, "<temperature type=\"%s\">%.1f</temperature>\n",
names[i],
convert_temperature(temperature, output->units));
}
}
// Parse the gas mixes.
message ("Parsing the gas mixes.\n");
unsigned int ngases = 0;
status = dc_parser_get_field (parser, DC_FIELD_GASMIX_COUNT, 0, &ngases);
if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) {
ERROR ("Error parsing the gas mix count.");
goto cleanup;
}
for (unsigned int i = 0; i < ngases; ++i) {
dc_gasmix_t gasmix = {0};
status = dc_parser_get_field (parser, DC_FIELD_GASMIX, i, &gasmix);
if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) {
ERROR ("Error parsing the gas mix.");
goto cleanup;
}
fprintf (output->ostream,
"<gasmix>\n"
" <he>%.1f</he>\n"
" <o2>%.1f</o2>\n"
" <n2>%.1f</n2>\n"
"</gasmix>\n",
gasmix.helium * 100.0,
gasmix.oxygen * 100.0,
gasmix.nitrogen * 100.0);
}
// Parse the tanks.
message ("Parsing the tanks.\n");
unsigned int ntanks = 0;
status = dc_parser_get_field (parser, DC_FIELD_TANK_COUNT, 0, &ntanks);
if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) {
ERROR ("Error parsing the tank count.");
goto cleanup;
}
for (unsigned int i = 0; i < ntanks; ++i) {
const char *names[] = {"none", "metric", "imperial"};
dc_tank_t tank = {0};
status = dc_parser_get_field (parser, DC_FIELD_TANK, i, &tank);
if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) {
ERROR ("Error parsing the tank.");
goto cleanup;
}
fprintf (output->ostream, "<tank>\n");
if (tank.gasmix != DC_GASMIX_UNKNOWN) {
fprintf (output->ostream,
" <gasmix>%u</gasmix>\n",
tank.gasmix);
}
if (tank.type != DC_TANKVOLUME_NONE) {
fprintf (output->ostream,
" <type>%s</type>\n"
" <volume>%.1f</volume>\n"
" <workpressure>%.2f</workpressure>\n",
names[tank.type],
convert_volume(tank.volume, output->units),
convert_pressure(tank.workpressure, output->units));
}
fprintf (output->ostream,
" <beginpressure>%.2f</beginpressure>\n"
" <endpressure>%.2f</endpressure>\n"
"</tank>\n",
convert_pressure(tank.beginpressure, output->units),
convert_pressure(tank.endpressure, output->units));
}
// Parse the dive mode.
message ("Parsing the dive mode.\n");
dc_divemode_t divemode = DC_DIVEMODE_OC;
status = dc_parser_get_field (parser, DC_FIELD_DIVEMODE, 0, &divemode);
if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) {
ERROR ("Error parsing the dive mode.");
goto cleanup;
}
if (status != DC_STATUS_UNSUPPORTED) {
const char *names[] = {"freedive", "gauge", "oc", "ccr", "scr"};
fprintf (output->ostream, "<divemode>%s</divemode>\n",
names[divemode]);
}
// Parse the salinity.
message ("Parsing the salinity.\n");
dc_salinity_t salinity = {DC_WATER_FRESH, 0.0};
status = dc_parser_get_field (parser, DC_FIELD_SALINITY, 0, &salinity);
if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) {
ERROR ("Error parsing the salinity.");
goto cleanup;
}
if (status != DC_STATUS_UNSUPPORTED) {
fprintf (output->ostream, "<salinity type=\"%u\">%.1f</salinity>\n",
salinity.type, salinity.density);
}
// Parse the atmospheric pressure.
message ("Parsing the atmospheric pressure.\n");
double atmospheric = 0.0;
status = dc_parser_get_field (parser, DC_FIELD_ATMOSPHERIC, 0, &atmospheric);
if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) {
ERROR ("Error parsing the atmospheric pressure.");
goto cleanup;
}
if (status != DC_STATUS_UNSUPPORTED) {
fprintf (output->ostream, "<atmospheric>%.5f</atmospheric>\n",
convert_pressure(atmospheric, output->units));
}
message ("Parsing strings.\n");
int idx;
for (idx = 0; idx < 100; idx++) {
dc_field_string_t str = { NULL };
status = dc_parser_get_field(parser, DC_FIELD_STRING, idx, &str);
if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) {
ERROR ("Error parsing strings");
goto cleanup;
}
if (status == DC_STATUS_UNSUPPORTED)
break;
if (!str.desc || !str.value)
break;
fprintf (output->ostream, "<extradata key='%s' value='%s' />\n",
str.desc, str.value);
}
// Parse the sample data.
message ("Parsing the sample data.\n");
status = dc_parser_samples_foreach (parser, sample_cb, &sampledata);
if (status != DC_STATUS_SUCCESS) {
ERROR ("Error parsing the sample data.");
goto cleanup;
}
cleanup:
if (sampledata.nsamples)
fprintf (output->ostream, "</sample>\n");
fprintf (output->ostream, "</dive>\n");
return status;
}
static dc_status_t
dctool_xml_output_free (dctool_output_t *abstract)
{
dctool_xml_output_t *output = (dctool_xml_output_t *) abstract;
fprintf (output->ostream, "</device>\n");
fclose (output->ostream);
return DC_STATUS_SUCCESS;
}