Compare commits

...

59 Commits

Author SHA1 Message Date
Dirk Hohndel
348387c6f6 Shearwater PNF support: fall back to default logbook style
On Android we appear to mis-interpret the response to the RDBI command.
Instead of failing, fall back to the default value (PNF for Teric,
Predator-like for everything else).

With this I can successfully download dive data from my Teric on
Android.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-09-12 10:54:39 -07:00
Dirk Hohndel
ee7c14ecc3 Shearwater: report error when parsing freedive
Support for the 8 byte freedive samples has yet to be added. For now bail out.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-09-08 18:44:00 -07:00
Dirk Hohndel
d0a3336c82 Shearwater: add Teric to list of supported dive computers
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-09-08 18:37:50 -07:00
Dirk Hohndel
437cc3e0cc Shearwater: try to gracefully shut down the Bluetooth connection
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-09-08 18:37:50 -07:00
Dirk Hohndel
9379004b2d Shearwater: add helper to send bytes to the dive computer
This should allow us to gracefully shut down the communication with BLE devices.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-09-08 18:37:50 -07:00
Dirk Hohndel
34785f55ff Shearwater Petrel Native Format parsing
This will allow parsing dives from the Shearwater Teric, but depending on the
firmware could also be used on older models.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-09-08 18:37:50 -07:00
Dirk Hohndel
0cbcc0518c Shearwater: use the correct address to download dives
Replace the hardcoded address which the one we determined based on the logbook
type available.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-09-08 17:34:42 -07:00
Dirk Hohndel
f80024ed59 Shearwater: detect which logbook format is support
RDBI response tells us which format the dive computer supports.

Shearwater recommends to use the 'Petrel Native Format' for all dive computers
which support it, even those pre-Teric models which (depending on firmware)
might support both PNF and the older 'Predator-Like Format'.

They also recommend to ignore the 0x90...... format which is very similar to
PNF but without the final record and to use the older Predator Like Format in
that case.

Which format we use is determined by the base address used to download the
logbook entries.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-09-08 17:34:34 -07:00
Dirk Hohndel
12b90c693a Detect Sherwater Teric
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-09-08 17:26:21 -07:00
Dirk Hohndel
ec0029c4ce Shearwater: skip deleted dives
Without this change a deleted dive on device is treated like the end of the
dive list.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-09-07 17:08:26 -07:00
Linus Torvalds
a9c582e26f garmin: parse dive setting information fields
Based on information from Ryan January, who got it from his Garmin
contacts and got the go-ahead to share the data.

This is mainly the water density and deco model.  It has a few other
fields troo, but nothing necessarily worth reporting.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-05 18:22:07 -07:00
Linus Torvalds
c1c0303e04 garmin: update event descriptions
Based on information from Ryan January, who got it from his Garmin
contacts and got the go-ahead to share the data.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-05 17:49:51 -07:00
Dirk Hohndel
7779bdf581 Garmin: don't assume that the first device index is 0
While it seems like a safe assumption, the cost of being careful and assembling
the full record and taking the one for device_index 0 seems worth it to me.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-05 11:41:55 -07:00
Dirk Hohndel
416bf35977 garmin: adjust the model to reflect the FIT product code
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-04 17:54:36 -07:00
Dirk Hohndel
e02037bf84 garmin: extract the devinfo from the first FIT file we parse
A typical FIT file contains several DEVICE_INFO messages.  We need to
identify the one(s) for the creator (i.e.  the actual device, not a
sub-component).

Note, Garmin identifies the Descent Mk1 as product 2859.  I think we
should use this as the model number (instead of currently using 0.

Also, the vendor event is not to send the vendor name of the device, but
in order to send vendor specific events :-)

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-04 17:54:33 -07:00
Dirk Hohndel
ac25976258 garmin: ignore FIT files that aren't dives
Dives are identified by a sub_sport range of 53-57 in the SPORT message.

This means that we need to parse the files before we actually offer them to the
application, which means we parse them three times all together, but I don't
see a way around that. Thankfully parsing a memory buffer is reasonably fast.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-04 17:53:36 -07:00
Linus Torvalds
41303bbc70 garmin: improve on debug log output for unknown fields
Yes, the hexdump was simple, but it was really hard to read, and we can
do so much better, including taking empty data into account, and
formatting it by the actual size of the individual fields.

So this improves on the debug log, so that when we decide to try to
parse new field information, the data will be in a somewhat more legible
format that makes more sense.  And getting rid of the empty fields makes
it much clearer what data might be even remotely interesting.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-04 17:18:37 -07:00
Linus Torvalds
8f7e29e1f9 garmin: start decoding notifications and gas change events
The magic numbers in the decoding are all based on Wojciech Więckowski's
fit2subs python script.

The event decoding is incomplete, but it should be easy enough to add
new events as people figure them out or as the FIT SDK documentation
improves, whichever comes first.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-02 20:17:30 -07:00
Linus Torvalds
740222d216 garmin: don't suppress the time sample at zero time
The logic to suppress multiple redundant time samples in the garmin
parser also always suppressed the time sample at 0:00, which was not
intentional.

Fix it by simply making the "suppress before" logic be "suppress until"
instead.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-02 14:09:52 -07:00
Linus Torvalds
4a43392c78 Garmin: add DC_SAMPLE_CNS reporting
The code to parse it was already there, we just didn't report it.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-02 13:31:51 -07:00
Linus Torvalds
86540206db Add support for DC_SAMPLE_TTS - time to surface in seconds
Several dive computers support this, so why not have the proper
interface for it in libdivecomputer?

Triggered by the fact that the Python scripts to generate XML files from
the Garmin FIT files can import this information, but the native
libdivecomputer model couldn't.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-02 13:16:02 -07:00
Linus Torvalds
f6ea5f514a Merge branch 'garmin-descent' into Subsurface-NG
Merge the initial Garmin Descent Mk1 support.

This actually works well enough to be useful, even though there are a
few ugly details yet to be sorted out.

The download itself is fairly complete, but all event handling is
currently missing (warnings, gas changes, things like that).

Also, because of how libdivecomputer works, the "downloading" of dives
is entirely separate from the "parsing" of dives in the libdivecomputer
world.  And that is actually problematic for the Garmin Descent
downloader, because you actually need to parse the data to even figure
out whether it's actually a dive at all!

The Garmin Descent is also a fitness and general excercise tracker, so
people can (and do) use it for other sports than just diving, and so the
activities we download may end up not being dives at all, but other
events.

But before we parse them, we don't know, and we aren't really supposed
to parse them until after we've passed the data to the application and
it passes it back for parsing.  Nasty chicken-and-egg problem there..

So right now non-diving activities will just show up as very short
and/or shallow dives.

This is fixable by just parsing things an extra time, but I really wish
libdivecomputer would just stop thinking that downloading and parsing
are separate events.

* garmin-descent:
  Add dc_usb_storage_open to the symbols list
  garmin: only record gasmixes for cylinders that aren't enabled
  garmin: don't emit fake device info and vendor event
  garmin: add support for downloading gas mixes
  garmin: add GPS coordinate data and improve parser_get_field() reports
  garmin: actually start using the parsed data
  garmin: turn all the remaining unrecognized fields into DEBUG messages
  garmin: add a lot of new field definitions
  garmin: teach the parser to show undefined values for unknown fields too
  garmin: fix file length header parsing
  garmin: teach the parser about invalid values and more dates
  garmin: some fields are defined in all message types
  Garmin: start parsing definition records
  Garmin Descent Mk1: flesh out the actual downloading part
  Add Garmin Descent Mk1 skeleton
  Add 'USB storage' transport enumeration
2018-08-31 13:24:03 -07:00
Linus Torvalds
fe2a43e798 Add dc_usb_storage_open to the symbols list
Apparetly the Windows build needs this for the library building.

Reported-by: Wojciech Więckowski <xplwowi@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-08-31 13:21:56 -07:00
Linus Torvalds
63cd80c560 Merge https://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Merge upstream libdivecomputer changes from Jef Driesen.

* https://github.com/libdivecomputer/libdivecomputer:
  Add Travis CI integration
  Fix the transport command-line parameter
  Document dc_descriptor_get_model
  Include stddef.h in iostream.h
  Add support for the Mares Smart Air
  Fix the average depth for older OSTC dives
  Add support for the Oceanic Pro Plus X
2018-08-31 12:52:50 -07:00
Linus Torvalds
22a96bf395 garmin: only record gasmixes for cylinders that aren't enabled
This actually takes the gas status information into account, and doesn't
show gas mixes that are disabled.

All the Garmin Descent data now looks reasonable, but we're not
generating any events (so no warnings, but also no gas change events
etc).

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-08-31 12:25:43 -07:00
Linus Torvalds
6a6e60c9bb garmin: don't emit fake device info and vendor event
The libdivecomputer model is just broken - we don't know this
information before parsing the dive.  But let's not emit a fake event
that generates bogus serial number data.  I thought I'd be able to fill
it in, but this really isn't reasonable, so disable it entirely for now.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-08-31 09:54:08 -07:00
Linus Torvalds
994efff75a garmin: add support for downloading gas mixes
This clarifies and generalizes the "pending sample data" a bit to also
work for gas mixes, since it's one of those things where you get
multiple fields in random order, and it needs to be batched up into one
"this gas for this cylinder" thing.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-08-31 09:44:37 -07:00
Linus Torvalds
6b7c269c9c garmin: add GPS coordinate data and improve parser_get_field() reports
This adds all the GPS information I found, although for dives the
primary ones do seem to be the "session" entry and exit ones.

But I'm exporting all of them as strings, so that we can try to figure
out what they mean.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-08-29 17:52:07 -07:00
Linus Torvalds
c8e52081cd garmin: actually start using the parsed data
This gets me real profiles, with depth and temperature information.

Sadly, the temperature data seems to be in whole degrees C, which is not
good for diving.  But certainly not unheard of.

Also, while this does actually transfer a lot of other information too,
there are certainly things missing.  No gas information is gathered
(although we do parse it, we just don't save it), and none of the events
are parsed at all.

And the GPS information that we have isn't passed on yet, because there
are no libdivecomputer interfaces to do that.  I'll have to come up with
something.

But it's actually almost useful.  All the basics seem to be there.  How
*buggy* it is, I do not know, but the profiles don't look obviously
broken.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-08-29 15:55:48 -07:00
Linus Torvalds
00a90e2822 garmin: turn all the remaining unrecognized fields into DEBUG messages
There aren't that many relevant ones left, and I have reached the point
where I think the remaining missing fields just aren't that important
any more.  You can always get them by saving the libdivecomputer
log-file and see the debug messages that way.

Now I'll need to turn the parsing skeleton into actually generating the
actual libdivecomputer data.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-08-29 12:49:56 -07:00
Linus Torvalds
6d470d8430 garmin: add a lot of new field definitions
This actually seems to cover almost all of the relevant dive fields.
There's a lot of different GPS coordinates, I have no idea what they all
are, but they are at least marked in the definitions.

NOTE! None of this actually fills in any information yet.  It's all
about just parsing things and getting the types etc right.

On that note, this also adds a bit of dynamic type checking, which
caught a mistake or two.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-08-29 12:22:42 -07:00
Linus Torvalds
3dbe5353f5 garmin: teach the parser to show undefined values for unknown fields too
Make it easier to see which of the unknown fields don't contain anything
interesting.

Soon this will be at the stage where the parser skeleton itself doesn't
need much work, and I should look at the actual data and turn it into
samples instead.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-08-28 17:29:38 -07:00
Linus Torvalds
6d53e31cba garmin: fix file length header parsing
Oops.  I used array_uint16_le() to get the data size.  Too much
copy-and-paste from the profile version (which is indeed 16 bits).

The data size is a 32-bit entity, and this would truncate the data we
read.

Also, verify that there is space for the final CRC in the file, even if
we don't actually check it.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-08-28 17:26:48 -07:00
Linus Torvalds
bc2ba57302 garmin: teach the parser about invalid values and more dates
The invalid values skip the parser callback function entirely.  Of
course, since it's not really doing anything right now, that's mostly
costmetic.

Extend the FIT type declarations to also have the invalid values.

Also, add a few timestamp entries, and print them out to show the
timestamps in a human-legible format.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-08-28 16:18:06 -07:00
Linus Torvalds
b65b2318f1 garmin: some fields are defined in all message types
It turns out that the timestamp field can exist across all message
types, as can a few other special fields.

Split those out as "ANY" type fields, so that we get the field
descriptor without having to fill in every message descriptor.

This also makes the message descriptors smaller, since we no longer need
to worry about the high-numbered (253) timestamp field in the arrays.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-08-28 15:00:54 -07:00
Linus Torvalds
2c7479ad1c Garmin: start parsing definition records
This is _very_ incomplete.  The FIT file is really fairly generic, but
this has the basics for parsing, with tables to look up the low-level
parsers by the FIT "message ID" and "field nr".

It doesn't actually parse anything yet, so consider this a FIT decoder
skeleton.

Right now it basically prints out the different record values, and names
then for the (few) cases where I've found or guessed the numbers.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-08-28 12:40:16 -07:00
Linus Torvalds
a726a38cbb Garmin Descent Mk1: flesh out the actual downloading part
It's really just reading files from storage, but with the proper sorting
and fingerprint handling.

The Garmin back-end does no actual parsing yet, so the end result is
garbage, but now the data has technically been downloaded.  Without the
parser, I haven't actually verified that any of it is remotely correct,
but it all looks good.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-08-27 15:10:33 -07:00
Linus Torvalds
8f790b52e4 Add Garmin Descent Mk1 skeleton
This does absolutely nothing, but it adds the basic skeleton for a new
dive computer support.

Not only don't I have any real code for any of this yet, but I actually
think it might be useful to have a "this is how to add a new dive
computer" example commit.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-08-27 13:19:29 -07:00
Linus Torvalds
8735156e89 Add 'USB storage' transport enumeration
We now have at least two dive computers that enumerate as USB storage
devices: the Uemis Zurich and the Garmin Descent Mk1.

The Uemis is handled purely inside of subsurface, with no
libdivecomputer support.  That was likely a mistake, but it was not
practical to do a libdivecomputer backend for it at the time.

The Garmin Descent Mk1 support would be much nicer to have natively in
libdivecomputer, and looks much more relevant and practical than the
Uemis situation was.

So start off with defining a new transport type.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-08-27 13:17:05 -07:00
Dirk Hohndel
bb985eedbb Mark Aqualung i750TC as Bluetooth capable
There is also an i300C that is Bluetooth capable, but I don't know if
that's the same model as the i300 or a different variation.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-08-05 12:04:30 -07:00
Linus Torvalds
8f4945dc1e Merge branch 'master' of git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Merge with misc fixes upstream.

This fixes a couple of small error cases.

* 'master' of git://github.com/libdivecomputer/libdivecomputer:
  Don't pass a NULL pointer to memcpy
  Fix an uninitialized variable
2018-06-28 09:28:27 -07:00
Linus Torvalds
02560a7e7f Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Merge with upstream libdivecomputer from Jef.

This fixes some sleeping functions, and also implements support for the
Tecdiving DiveComputer.eu dive computers.

There's also various minor cleanups.  Most notable is perhaps the
unification of the Uwatec dive computer backends.

* git://github.com/libdivecomputer/libdivecomputer:
  Initialize the socket library for the bluetooth discovery
  Fix the length of the Suunto D6i gas change event
  Add support for the Tecdiving DiveComputer.eu
  Fix the Mac OS X timer implementation
  Add the average depth to the xml output
  Skip the handshake for BLE communication
  Unify the Uwatec Smart, Meridian and G2 backends
  Re-organize the packet send/receive code
  Use symbolic constants for the commands
  Implement an rfcomm filter function
  Remove the filter for HW OSTC's without bluetooth
  Implement the sleep function for IrDA and bluetooth
2018-06-27 08:05:00 -07:00
Linus Torvalds
5255ba5448 Merge https://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Merge upstream updates from Jef Driesen.

This fixes the parsing of the Uwatec Aladin Tec (and Tec 2G) by adding a
missing event descriptor.

* 'master' of https://github.com/libdivecomputer/libdivecomputer:
  Add a missing event descriptor
2018-06-21 07:56:13 +09:00
Anton Lundin
503d934c19 shearwater: Emit a string saying the source of ppo2 values
Signed-off-by: Anton Lundin <glance@acc.umu.se>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-06-21 07:55:12 +09:00
Anton Lundin
902dbf4d6d shearwater: Fallback to average/voted ppo2
If we can't find any calibration values for the individual sensors,
fallback to emitting the average/voted ppo2 instead, so users always get
a ppo2 value.

Signed-off-by: Anton Lundin <glance@acc.umu.se>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-06-21 07:55:12 +09:00
Dirk Hohndel
e0761561e9 Mares: add BLE for dive computers that support bluelink pro
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-05-12 21:12:35 -07:00
Linus Torvalds
d264349676 Re-instate the lack of handshaking for the Scubapro Aladin Sport Matrix
I thought this wasn't needed any more (incorrectly thinking that Jef had
knowledge we didn't - he had merged the other changes), and had just
taken Jef's version of the code.

Berthold Stöger tells me otherwise.  The Aladin Sport Matrix returns 0
instead of 1 to the initial handshake, and makes libdivecomputer
unhappy.  This just skips the handshake entirely for the Sport Matrix,
since apparently LogTrak doesn't do any either.

See also commit 8a84ece7d0ef ("Support for the Scubapro Aladin Sport
Matrix") in our old Subsurface branch.

Reported-by: Berthold Stöger <berthold.stoeger@tuwien.ac.at>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-04-25 12:53:48 -07:00
Linus Torvalds
e97a47cca5 Shearwater: add extended information parsing
This adds the string field interface to the Shearwater family of dive
computers.

That includes proper serial number formatting, but it also has a lot of
new fields for battery information (both the dive computer itself and
the transmitter) but also deco model information.

Much of the deco model cases come from Anton Lundin in the original
subsurface branch, and Dirk Hohndel added the battery type and serial
number and firmware version data.  And I ended up massaging it even in
that original branch, so it blamed me for all these lines even back
there.

The sign-offs from Dirk and Anton are from the original commits.

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
Linus Torvalds
14490a462a Suunto EON Steel/Core: add extended information parsing
This adds the string field interface to the Suunto EON Steel and EON
Core.

This is actually a big deal, because it gets rid of all the ad-hoc
string parsing, and actually just uses the strings that the EON Steel
events and warnings natively use.

It also reports the severity of the notification/warning/alarm, so that
Subsurface can then use the proper icon.  An event isn't just an event,
there's a big difference between a warning and just a notification.

It also fills in the tank information data for closed-circuit cylinder
use.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-04-24 17:32:21 -07:00
Dirk Hohndel
97c8bb908e Suunto D9 family: add extended information parsing
This adds the string field interface to the Suunto D9 family.

It's really just the proper serial number handling.  From Dirk's
original commit:

 "We have the correct firmware in the devinfo, but that's the firmware
  the dive computer is on NOW, not necessarily the firmware it was using
  when recording the dive"

so thus just serial number.

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
Dirk Hohndel
f248a95d64 Oceanic Atom2: add extended string information parsing
This adds the string field interface to the Oceanic Atom2 family,
including the proper serial number handling.

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
Dirk Hohndel
df1e97c471 Heinrichs Weikamp OSTC: add extended information parsing
This adds the string field interface to the HW OSTC family, including
the proper serial number handling.

The deco model information was done by Anton Lundin in the original
subsurface branch, and the salinity, serial number, battery voltage and
desat information was added by Dirk Hohndel.  Jan Mulder added the
battery percentage.

[ The sign-offs have been taken from the original commits in that old
  subsurface branch, and I'm marking Dirk as the main author because on
  the whole most of the lines come from him  - Linus ]

Signed-off-by: Anton Lundin <glance@acc.umu.se>
Signed-off-by: Jan Mulder <jlmulder@xs4all.nl>
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
Linus Torvalds
a38d640df4 Make dc_parser_new() pass in the serial number to +dc_parser_new_internal
The libdivecomputer serial number handling is very very messy.

There are multiple issues that make it messy:

 - it's not actually figured out at parse-time, it's figured out at
   download time and passed up through the DC_EVENT_DEVINFO as part of
   the devinfo structure.

 - it's passed around as an "unsigned in" in the devinfo structure,
   which is entirely useless to anybody but libdivecomputer, since a
   serial number isn't actually a number, but a string, and the format
   of the string depends on the dive computer.

 - it is *not* passed to the parser, so the parser can't do a better job
   at it later.

But it turns out that the sane "create new parser" helper function does
actually get it, as part of the "devinfo" that is passed to it.  So as
long as you use that sane interface, we can now pass it in to the actual
parser creation, and then the dive computer parsers that want to do a
reasonable job of actually generating a real serial number string can
now save it off and do so.

This just adds the infrastructure to make this possible.  I'll do the
dive computers one by one.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-04-24 17:32:21 -07:00
Linus Torvalds
6c51ace384 Shearwater Petrel: make the hardware ID decoding a bit easier to read
Dirk seems to have some documentation about the different ID's, plus it
just makes sense to order the switch statement by number.

This is partly based on Dirk's original commit to do the different model
numbers, with various changes over time due to merge conflict
resolution.  Dirk's sign-off comes from Dirks commit in the original
subsurface branch.

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
Dirk Hohndel
db08a534bf Atomics Cobalt: use the new DC string fields
Not a lot of fields, but give the serial number in the proper format,
and other version information (Software version and bootloader version).
And the Nofly time that the dive computer reports.

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
Linus Torvalds
167848aa59 Add subsurface-specific cylinder descriptor extension
This extends the libdivecomputer notion of "dc_tankvolume_t" to not just
have the tank volume type (imperial or metric), but be a "dc_tankinfo_t"
that shows other information about the cylinder.

The imperial-vs-metric data remains the same two values:

 1 - metric
 2 - imperial

but instead of being an enumeration of volume types, it is extended to a
bitmap of tank information, and the other bits currently are

 4 - CC diluent cylinder
 8 - CC O2 cylinder

with possible future extensions (bailout gas, perhaps).

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-04-24 17:32:21 -07:00
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
Linus Torvalds
49f89d2205 Add subsurface-specific event extension: event strings and severity
We _really_ find the standard libdivecomputer event enumeration much too
inflexible and not giving us enough useful information.  This is
particularly noticeable with the Suunto EON Steel/Core, where there are
no fixed event enumerations, but instead the dive computer literally
gives you event strings.

Do the same thing in the libdivecomputer interface: allow an event of
type SAMPLE_EVENT_STRING which instead of the useless "value" gives an
actual string describing the event.

Also, extend the "flags" field to have not just a NONE/BEGIN/END marker,
but a severity level. The severity level is 3 bits, so 0-7, with the meaning being

 0 - 'no severity info'
 1 - state change (so 'surface' event or similar - don't even show it by default)
 2 - notification (informational, eg "safety stop", "tank change")
 3 - warning ("ascent speed")
 4 - alarm (some actual dive violation).
 5-7: future expansion?

Think of 0 as "legacy - missing information", 1 as "internal DC thing",
and 2-4 as (green-yellow-red).

This makes it possible for the dive computer back-end to give the user
actual useful information for events.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-04-24 17:32:21 -07:00
Linus Torvalds
063041ddca Add subsurface-specific build setup
This changes the dc_version_suffix to show that this is the subsurface
'next generation' branch, and does minor tweaks to the build system: we
add the 'build' subdirectory to the .gitignore branch because that's
where we typically do our builds, and we tweak the default compiler
warning flags to not be as annoying.

No real semantic changes.

This is partially based off patches in the original Subsurface-branch by
Dirk Hohndel (configure.ac) and Jan Mulder (gitignore).  The sign-offs
for those come from those patches:

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
Signed-off-by: Jan Mulder <jlmulder@xs4all.nl>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-04-24 17:32:21 -07:00
33 changed files with 2787 additions and 197 deletions

2
.gitignore vendored
View File

@ -82,3 +82,5 @@ Makefile.in
/src/libdivecomputer.la /src/libdivecomputer.la
/src/libdivecomputer.rc /src/libdivecomputer.rc
/src/revision.h /src/revision.h
/build

View File

@ -2,7 +2,7 @@
m4_define([dc_version_major],[0]) m4_define([dc_version_major],[0])
m4_define([dc_version_minor],[7]) m4_define([dc_version_minor],[7])
m4_define([dc_version_micro],[0]) m4_define([dc_version_micro],[0])
m4_define([dc_version_suffix],[devel]) m4_define([dc_version_suffix],[devel-Subsurface-NG])
m4_define([dc_version],dc_version_major.dc_version_minor.dc_version_micro[]m4_ifset([dc_version_suffix],-[dc_version_suffix])) m4_define([dc_version],dc_version_major.dc_version_minor.dc_version_micro[]m4_ifset([dc_version_suffix],-[dc_version_suffix]))
# Libtool versioning. # Libtool versioning.
@ -175,19 +175,21 @@ AC_CHECK_FUNCS([getopt_long])
# Checks for supported compiler options. # Checks for supported compiler options.
AX_APPEND_COMPILE_FLAGS([ \ AX_APPEND_COMPILE_FLAGS([ \
-pedantic \
-Wall \ -Wall \
-Wextra \
-Wshadow \ -Wshadow \
-Wrestrict \ -Wrestrict \
-Wformat=2 \ -Wformat=2 \
-Wwrite-strings \ -Wwrite-strings \
-Wcast-qual \
-Wpointer-arith \ -Wpointer-arith \
-Wstrict-prototypes \ -Wstrict-prototypes \
-Wmissing-prototypes \ -Wmissing-prototypes \
-Wmissing-declarations \ -Wmissing-declarations \
-Wno-unused-parameter \ -Wno-unused-parameter \
-Wno-unused-function \
-Wno-unused-variable \
-Wno-unused-but-set-variable \
-Wno-pointer-sign \
-Wno-shadow \
]) ])
# Windows specific compiler options. # Windows specific compiler options.

View File

@ -89,6 +89,7 @@ static const backend_table_t g_backends[] = {
{"idive", DC_FAMILY_DIVESYSTEM_IDIVE, 0x03}, {"idive", DC_FAMILY_DIVESYSTEM_IDIVE, 0x03},
{"cochran", DC_FAMILY_COCHRAN_COMMANDER, 0}, {"cochran", DC_FAMILY_COCHRAN_COMMANDER, 0},
{"divecomputereu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0}, {"divecomputereu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0},
{"descentmk1", DC_FAMILY_GARMIN, 0},
}; };
static const transport_table_t g_transports[] = { static const transport_table_t g_transports[] = {
@ -98,6 +99,7 @@ static const transport_table_t g_transports[] = {
{"irda", DC_TRANSPORT_IRDA}, {"irda", DC_TRANSPORT_IRDA},
{"bluetooth", DC_TRANSPORT_BLUETOOTH}, {"bluetooth", DC_TRANSPORT_BLUETOOTH},
{"ble", DC_TRANSPORT_BLE}, {"ble", DC_TRANSPORT_BLE},
{"usbstorage",DC_TRANSPORT_USBSTORAGE},
}; };
const char * const char *
@ -536,6 +538,8 @@ dctool_iostream_open (dc_iostream_t **iostream, dc_context_t *context, dc_descri
return dctool_irda_open (iostream, context, descriptor, devname); return dctool_irda_open (iostream, context, descriptor, devname);
case DC_TRANSPORT_BLUETOOTH: case DC_TRANSPORT_BLUETOOTH:
return dctool_bluetooth_open (iostream, context, descriptor, devname); return dctool_bluetooth_open (iostream, context, descriptor, devname);
case DC_TRANSPORT_USBSTORAGE:
return dc_usb_storage_open (iostream, context, devname);
default: default:
return DC_STATUS_UNSUPPORTED; return DC_STATUS_UNSUPPORTED;
} }

View File

@ -414,6 +414,24 @@ dctool_xml_output_write (dctool_output_t *abstract, dc_parser_t *parser, const u
convert_pressure(atmospheric, output->units)); 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. // Parse the sample data.
message ("Parsing the sample data.\n"); message ("Parsing the sample data.\n");
status = dc_parser_samples_foreach (parser, sample_cb, &sampledata); status = dc_parser_samples_foreach (parser, sample_cb, &sampledata);

View File

@ -48,9 +48,13 @@ typedef enum dc_transport_t {
DC_TRANSPORT_USBHID = (1 << 2), DC_TRANSPORT_USBHID = (1 << 2),
DC_TRANSPORT_IRDA = (1 << 3), DC_TRANSPORT_IRDA = (1 << 3),
DC_TRANSPORT_BLUETOOTH = (1 << 4), DC_TRANSPORT_BLUETOOTH = (1 << 4),
DC_TRANSPORT_BLE = (1 << 5) DC_TRANSPORT_BLE = (1 << 5),
DC_TRANSPORT_USBSTORAGE= (1 << 6),
} dc_transport_t; } dc_transport_t;
// Idiotic enums can't be queried
#define DC_TRANSPORT_USBSTORAGE DC_TRANSPORT_USBSTORAGE
typedef enum dc_family_t { typedef enum dc_family_t {
DC_FAMILY_NULL = 0, DC_FAMILY_NULL = 0,
/* Suunto */ /* Suunto */
@ -103,6 +107,8 @@ typedef enum dc_family_t {
DC_FAMILY_COCHRAN_COMMANDER = (14 << 16), DC_FAMILY_COCHRAN_COMMANDER = (14 << 16),
/* Tecdiving */ /* Tecdiving */
DC_FAMILY_TECDIVING_DIVECOMPUTEREU = (15 << 16), DC_FAMILY_TECDIVING_DIVECOMPUTEREU = (15 << 16),
/* Garmin */
DC_FAMILY_GARMIN = (16 << 16),
} dc_family_t; } dc_family_t;
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -283,6 +283,9 @@ dc_iostream_sleep (dc_iostream_t *iostream, unsigned int milliseconds);
dc_status_t dc_status_t
dc_iostream_close (dc_iostream_t *iostream); dc_iostream_close (dc_iostream_t *iostream);
dc_status_t
dc_usb_storage_open (dc_iostream_t **out, dc_context_t *context, const char *name);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif /* __cplusplus */ #endif /* __cplusplus */

View File

@ -46,9 +46,13 @@ typedef enum dc_sample_type_t {
DC_SAMPLE_PPO2, DC_SAMPLE_PPO2,
DC_SAMPLE_CNS, DC_SAMPLE_CNS,
DC_SAMPLE_DECO, DC_SAMPLE_DECO,
DC_SAMPLE_GASMIX DC_SAMPLE_GASMIX,
DC_SAMPLE_TTS, // time to surface in seconds
} dc_sample_type_t; } dc_sample_type_t;
// Make it easy to test support compile-time with "#ifdef DC_SAMPLE_TTS"
#define DC_SAMPLE_TTS DC_SAMPLE_TTS
typedef enum dc_field_type_t { typedef enum dc_field_type_t {
DC_FIELD_DIVETIME, DC_FIELD_DIVETIME,
DC_FIELD_MAXDEPTH, DC_FIELD_MAXDEPTH,
@ -62,9 +66,13 @@ typedef enum dc_field_type_t {
DC_FIELD_TEMPERATURE_MAXIMUM, DC_FIELD_TEMPERATURE_MAXIMUM,
DC_FIELD_TANK_COUNT, DC_FIELD_TANK_COUNT,
DC_FIELD_TANK, DC_FIELD_TANK,
DC_FIELD_DIVEMODE DC_FIELD_DIVEMODE,
DC_FIELD_STRING,
} dc_field_type_t; } dc_field_type_t;
// Make it easy to test support compile-time with "#ifdef DC_FIELD_STRING"
#define DC_FIELD_STRING DC_FIELD_STRING
typedef enum parser_sample_event_t { typedef enum parser_sample_event_t {
SAMPLE_EVENT_NONE, SAMPLE_EVENT_NONE,
SAMPLE_EVENT_DECOSTOP, SAMPLE_EVENT_DECOSTOP,
@ -92,17 +100,30 @@ typedef enum parser_sample_event_t {
SAMPLE_EVENT_HEADING, SAMPLE_EVENT_HEADING,
SAMPLE_EVENT_TISSUELEVEL, SAMPLE_EVENT_TISSUELEVEL,
SAMPLE_EVENT_GASCHANGE2, /* Deprecated: replaced by DC_SAMPLE_GASMIX. */ SAMPLE_EVENT_GASCHANGE2, /* Deprecated: replaced by DC_SAMPLE_GASMIX. */
SAMPLE_EVENT_STRING,
} parser_sample_event_t; } parser_sample_event_t;
/* To let the compile know we have this */
#define SAMPLE_EVENT_STRING SAMPLE_EVENT_STRING
/* For backwards compatibility */ /* For backwards compatibility */
#define SAMPLE_EVENT_UNKNOWN SAMPLE_EVENT_FLOOR #define SAMPLE_EVENT_UNKNOWN SAMPLE_EVENT_FLOOR
typedef enum parser_sample_flags_t { typedef enum parser_sample_flags_t {
SAMPLE_FLAGS_NONE = 0, SAMPLE_FLAGS_NONE = 0,
SAMPLE_FLAGS_BEGIN = (1 << 0), SAMPLE_FLAGS_BEGIN = (1 << 0),
SAMPLE_FLAGS_END = (1 << 1) SAMPLE_FLAGS_END = (1 << 1),
SAMPLE_FLAGS_SEVERITY_MASK = (7 << 2),
} parser_sample_flags_t; } parser_sample_flags_t;
#define SAMPLE_FLAGS_SEVERITY_SHIFT 2
#define SAMPLE_FLAGS_SEVERITY_MISSING (0 << SAMPLE_FLAGS_SEVERITY_SHIFT)
#define SAMPLE_FLAGS_SEVERITY_STATE (1 << SAMPLE_FLAGS_SEVERITY_SHIFT)
#define SAMPLE_FLAGS_SEVERITY_INFO (2 << SAMPLE_FLAGS_SEVERITY_SHIFT)
#define SAMPLE_FLAGS_SEVERITY_WARN (3 << SAMPLE_FLAGS_SEVERITY_SHIFT)
#define SAMPLE_FLAGS_SEVERITY_ALARM (4 << SAMPLE_FLAGS_SEVERITY_SHIFT)
typedef enum parser_sample_vendor_t { typedef enum parser_sample_vendor_t {
SAMPLE_VENDOR_NONE, SAMPLE_VENDOR_NONE,
SAMPLE_VENDOR_UWATEC_ALADIN, SAMPLE_VENDOR_UWATEC_ALADIN,
@ -148,11 +169,16 @@ typedef struct dc_gasmix_t {
#define DC_GASMIX_UNKNOWN 0xFFFFFFFF #define DC_GASMIX_UNKNOWN 0xFFFFFFFF
typedef enum dc_tankvolume_t { typedef unsigned int dc_tankinfo_t;
DC_TANKVOLUME_NONE, #define DC_TANKINFO_METRIC 1
DC_TANKVOLUME_METRIC, #define DC_TANKINFO_IMPERIAL 2
DC_TANKVOLUME_IMPERIAL, #define DC_TANKINFO_CC_DILUENT 4
} dc_tankvolume_t; #define DC_TANKINFO_CC_O2 8
// For backwards compatibility
#define DC_TANKVOLUME_NONE 0
#define DC_TANKVOLUME_METRIC DC_TANKINFO_METRIC
#define DC_TANKVOLUME_IMPERIAL DC_TANKINFO_IMPERIAL
/* /*
* Tank volume * Tank volume
@ -179,13 +205,18 @@ typedef enum dc_tankvolume_t {
typedef struct dc_tank_t { typedef struct dc_tank_t {
unsigned int gasmix; /* Gas mix index, or DC_GASMIX_UNKNOWN */ unsigned int gasmix; /* Gas mix index, or DC_GASMIX_UNKNOWN */
dc_tankvolume_t type; /* Tank type */ dc_tankinfo_t type; /* Tank type - metric/imperial and oc/cc */
double volume; /* Volume (liter) */ double volume; /* Volume (liter) */
double workpressure; /* Work pressure (bar) */ double workpressure; /* Work pressure (bar) */
double beginpressure; /* Begin pressure (bar) */ double beginpressure; /* Begin pressure (bar) */
double endpressure; /* End pressure (bar) */ double endpressure; /* End pressure (bar) */
} dc_tank_t; } dc_tank_t;
typedef struct dc_field_string_t {
const char *desc;
const char *value;
} dc_field_string_t;
typedef union dc_sample_value_t { typedef union dc_sample_value_t {
unsigned int time; unsigned int time;
double depth; double depth;
@ -199,6 +230,7 @@ typedef union dc_sample_value_t {
unsigned int time; unsigned int time;
unsigned int flags; unsigned int flags;
unsigned int value; unsigned int value;
const char *name;
} event; } event;
unsigned int rbt; unsigned int rbt;
unsigned int heartbeat; unsigned int heartbeat;

View File

@ -490,6 +490,14 @@
RelativePath="..\src\tecdiving_divecomputereu_parser.c" RelativePath="..\src\tecdiving_divecomputereu_parser.c"
> >
</File> </File>
<File
RelativePath="..\src\garmin.c"
>
</File>
<File
RelativePath="..\src\garmin_parser.c"
>
</File>
<File <File
RelativePath="..\src\timer.c" RelativePath="..\src\timer.c"
> >
@ -828,6 +836,10 @@
RelativePath="..\src\tecdiving_divecomputereu.h" RelativePath="..\src\tecdiving_divecomputereu.h"
> >
</File> </File>
<File
RelativePath="..\src\garmin.h"
>
</File>
<File <File
RelativePath="..\src\timer.h" RelativePath="..\src\timer.h"
> >

View File

@ -71,10 +71,12 @@ libdivecomputer_la_SOURCES = \
buffer.c \ buffer.c \
cochran_commander.h cochran_commander.c cochran_commander_parser.c \ cochran_commander.h cochran_commander.c cochran_commander_parser.c \
tecdiving_divecomputereu.h tecdiving_divecomputereu.c tecdiving_divecomputereu_parser.c \ tecdiving_divecomputereu.h tecdiving_divecomputereu.c tecdiving_divecomputereu_parser.c \
garmin.h garmin.c garmin_parser.c \
socket.h socket.c \ socket.h socket.c \
irda.c \ irda.c \
usbhid.c \ usbhid.c \
bluetooth.c \ bluetooth.c \
usb_storage.c \
custom.c custom.c
if OS_WIN32 if OS_WIN32

View File

@ -20,6 +20,12 @@
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef _MSC_VER
#define snprintf _snprintf
#endif
#include <libdivecomputer/units.h> #include <libdivecomputer/units.h>
@ -129,6 +135,9 @@ atomics_cobalt_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dateti
} }
#define BUFLEN 16
static dc_status_t static dc_status_t
atomics_cobalt_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) atomics_cobalt_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
{ {
@ -143,6 +152,9 @@ atomics_cobalt_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, un
dc_tank_t *tank = (dc_tank_t *) value; dc_tank_t *tank = (dc_tank_t *) value;
double atmospheric = 0.0; double atmospheric = 0.0;
char buf[BUFLEN];
dc_field_string_t *string = (dc_field_string_t *) value;
if (parser->atmospheric) if (parser->atmospheric)
atmospheric = parser->atmospheric; atmospheric = parser->atmospheric;
else else
@ -208,6 +220,29 @@ atomics_cobalt_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, un
return DC_STATUS_DATAFORMAT; return DC_STATUS_DATAFORMAT;
} }
break; break;
case DC_FIELD_STRING:
switch(flags) {
case 0: // Serialnr
string->desc = "Serial";
snprintf(buf, BUFLEN, "%c%c%c%c-%c%c%c%c", p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11]);
break;
case 1: // Program Version
string->desc = "Program Version";
snprintf(buf, BUFLEN, "%.2f", array_uint16_le(p + 30) / 100.0);
break;
case 2: // Boot Version
string->desc = "Boot Version";
snprintf(buf, BUFLEN, "%.2f", array_uint16_le(p + 32) / 100.0);
break;
case 3: // Nofly
string->desc = "NoFly Time";
snprintf(buf, BUFLEN, "%0u:%02u", p[0x52], p[0x53]);
break;
default:
return DC_STATUS_UNSUPPORTED;
}
string->value = strdup(buf);
break;
default: default:
return DC_STATUS_UNSUPPORTED; return DC_STATUS_UNSUPPORTED;
} }

View File

@ -334,6 +334,7 @@ dc_context_get_transports (dc_context_t *context)
#elif defined(HAVE_LIBUSB) && !defined(__APPLE__) #elif defined(HAVE_LIBUSB) && !defined(__APPLE__)
| DC_TRANSPORT_USBHID | DC_TRANSPORT_USBHID
#endif #endif
| DC_TRANSPORT_USBSTORAGE
#ifdef _WIN32 #ifdef _WIN32
#ifdef HAVE_AF_IRDA_H #ifdef HAVE_AF_IRDA_H
| DC_TRANSPORT_IRDA | DC_TRANSPORT_IRDA

View File

@ -34,6 +34,7 @@ static int dc_filter_suunto (dc_transport_t transport, const void *userdata);
static int dc_filter_shearwater (dc_transport_t transport, const void *userdata); static int dc_filter_shearwater (dc_transport_t transport, const void *userdata);
static int dc_filter_hw (dc_transport_t transport, const void *userdata); static int dc_filter_hw (dc_transport_t transport, const void *userdata);
static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata); static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata);
static int dc_filter_garmin (dc_transport_t transport, const void *userdata);
static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item); static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item);
@ -215,7 +216,7 @@ static const dc_descriptor_t g_descriptors[] = {
{"Sherwood", "Vision", DC_FAMILY_OCEANIC_ATOM2, 0x4556, DC_TRANSPORT_SERIAL, NULL}, {"Sherwood", "Vision", DC_FAMILY_OCEANIC_ATOM2, 0x4556, DC_TRANSPORT_SERIAL, NULL},
{"Oceanic", "VTX", DC_FAMILY_OCEANIC_ATOM2, 0x4557, DC_TRANSPORT_SERIAL, NULL}, {"Oceanic", "VTX", DC_FAMILY_OCEANIC_ATOM2, 0x4557, DC_TRANSPORT_SERIAL, NULL},
{"Aqualung", "i300", DC_FAMILY_OCEANIC_ATOM2, 0x4559, DC_TRANSPORT_SERIAL, NULL}, {"Aqualung", "i300", DC_FAMILY_OCEANIC_ATOM2, 0x4559, DC_TRANSPORT_SERIAL, NULL},
{"Aqualung", "i750TC", DC_FAMILY_OCEANIC_ATOM2, 0x455A, DC_TRANSPORT_SERIAL, NULL}, {"Aqualung", "i750TC", DC_FAMILY_OCEANIC_ATOM2, 0x455A, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, NULL},
{"Aqualung", "i450T", DC_FAMILY_OCEANIC_ATOM2, 0x4641, DC_TRANSPORT_SERIAL, NULL}, {"Aqualung", "i450T", DC_FAMILY_OCEANIC_ATOM2, 0x4641, DC_TRANSPORT_SERIAL, NULL},
{"Aqualung", "i550", DC_FAMILY_OCEANIC_ATOM2, 0x4642, DC_TRANSPORT_SERIAL, NULL}, {"Aqualung", "i550", DC_FAMILY_OCEANIC_ATOM2, 0x4642, DC_TRANSPORT_SERIAL, NULL},
{"Aqualung", "i200", DC_FAMILY_OCEANIC_ATOM2, 0x4646, DC_TRANSPORT_SERIAL, NULL}, {"Aqualung", "i200", DC_FAMILY_OCEANIC_ATOM2, 0x4646, DC_TRANSPORT_SERIAL, NULL},
@ -238,16 +239,16 @@ static const dc_descriptor_t g_descriptors[] = {
{"Mares", "Airlab", DC_FAMILY_MARES_DARWIN , 1, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Airlab", DC_FAMILY_MARES_DARWIN , 1, DC_TRANSPORT_SERIAL, NULL},
/* Mares Icon HD */ /* Mares Icon HD */
{"Mares", "Matrix", DC_FAMILY_MARES_ICONHD , 0x0F, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Matrix", DC_FAMILY_MARES_ICONHD , 0x0F, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Smart", DC_FAMILY_MARES_ICONHD , 0x000010, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Smart", DC_FAMILY_MARES_ICONHD , 0x000010, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
{"Mares", "Smart Apnea", DC_FAMILY_MARES_ICONHD , 0x010010, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Smart Apnea", DC_FAMILY_MARES_ICONHD , 0x010010, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Icon HD", DC_FAMILY_MARES_ICONHD , 0x14, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Icon HD", DC_FAMILY_MARES_ICONHD , 0x14, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Icon HD Net Ready", DC_FAMILY_MARES_ICONHD , 0x15, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Icon HD Net Ready", DC_FAMILY_MARES_ICONHD , 0x15, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Puck Pro", DC_FAMILY_MARES_ICONHD , 0x18, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Puck Pro", DC_FAMILY_MARES_ICONHD , 0x18, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
{"Mares", "Nemo Wide 2", DC_FAMILY_MARES_ICONHD , 0x19, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Nemo Wide 2", DC_FAMILY_MARES_ICONHD , 0x19, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Puck 2", DC_FAMILY_MARES_ICONHD , 0x1F, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Puck 2", DC_FAMILY_MARES_ICONHD , 0x1F, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Quad Air", DC_FAMILY_MARES_ICONHD , 0x23, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Quad Air", DC_FAMILY_MARES_ICONHD , 0x23, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
{"Mares", "Smart Air", DC_FAMILY_MARES_ICONHD , 0x24, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Smart Air", DC_FAMILY_MARES_ICONHD , 0x24, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
{"Mares", "Quad", DC_FAMILY_MARES_ICONHD , 0x29, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Quad", DC_FAMILY_MARES_ICONHD , 0x29, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
/* Heinrichs Weikamp */ /* Heinrichs Weikamp */
{"Heinrichs Weikamp", "OSTC", DC_FAMILY_HW_OSTC, 0, DC_TRANSPORT_SERIAL, NULL}, {"Heinrichs Weikamp", "OSTC", DC_FAMILY_HW_OSTC, 0, DC_TRANSPORT_SERIAL, NULL},
{"Heinrichs Weikamp", "OSTC Mk2", DC_FAMILY_HW_OSTC, 1, DC_TRANSPORT_SERIAL, NULL}, {"Heinrichs Weikamp", "OSTC Mk2", DC_FAMILY_HW_OSTC, 1, DC_TRANSPORT_SERIAL, NULL},
@ -291,6 +292,7 @@ static const dc_descriptor_t g_descriptors[] = {
{"Shearwater", "Perdix", DC_FAMILY_SHEARWATER_PETREL, 5, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_shearwater}, {"Shearwater", "Perdix", DC_FAMILY_SHEARWATER_PETREL, 5, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_shearwater},
{"Shearwater", "Perdix AI", DC_FAMILY_SHEARWATER_PETREL, 6, DC_TRANSPORT_BLE, dc_filter_shearwater}, {"Shearwater", "Perdix AI", DC_FAMILY_SHEARWATER_PETREL, 6, DC_TRANSPORT_BLE, dc_filter_shearwater},
{"Shearwater", "Nerd 2", DC_FAMILY_SHEARWATER_PETREL, 7, DC_TRANSPORT_BLE, dc_filter_shearwater}, {"Shearwater", "Nerd 2", DC_FAMILY_SHEARWATER_PETREL, 7, DC_TRANSPORT_BLE, dc_filter_shearwater},
{"Shearwater", "Teric", DC_FAMILY_SHEARWATER_PETREL, 8, DC_TRANSPORT_BLE, dc_filter_shearwater},
/* Dive Rite NiTek Q */ /* Dive Rite NiTek Q */
{"Dive Rite", "NiTek Q", DC_FAMILY_DIVERITE_NITEKQ, 0, DC_TRANSPORT_SERIAL, NULL}, {"Dive Rite", "NiTek Q", DC_FAMILY_DIVERITE_NITEKQ, 0, DC_TRANSPORT_SERIAL, NULL},
/* Citizen Hyper Aqualand */ /* Citizen Hyper Aqualand */
@ -328,6 +330,8 @@ static const dc_descriptor_t g_descriptors[] = {
{"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_COMMANDER, 5, DC_TRANSPORT_SERIAL, NULL}, {"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_COMMANDER, 5, DC_TRANSPORT_SERIAL, NULL},
/* Tecdiving DiveComputer.eu */ /* Tecdiving DiveComputer.eu */
{"Tecdiving", "DiveComputer.eu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_tecdiving}, {"Tecdiving", "DiveComputer.eu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_tecdiving},
/* Garmin */
{"Garmin", "Descent Mk1", DC_FAMILY_GARMIN, 2859, DC_TRANSPORT_USBSTORAGE, dc_filter_garmin},
}; };
static int static int
@ -467,6 +471,19 @@ static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata)
return 1; return 1;
} }
static int dc_filter_garmin (dc_transport_t transport, const void *userdata)
{
static const dc_usb_desc_t usbhid[] = {
{0x091e, 0x2b2b}, // Garmin Descent Mk1
};
if (transport == DC_TRANSPORT_USBSTORAGE) {
return dc_filter_internal_usb ((const dc_usb_desc_t *) userdata, usbhid, C_ARRAY_SIZE(usbhid));
}
return 1;
}
dc_status_t dc_status_t
dc_descriptor_iterator (dc_iterator_t **out) dc_descriptor_iterator (dc_iterator_t **out)
{ {

View File

@ -56,6 +56,7 @@
#include "divesystem_idive.h" #include "divesystem_idive.h"
#include "cochran_commander.h" #include "cochran_commander.h"
#include "tecdiving_divecomputereu.h" #include "tecdiving_divecomputereu.h"
#include "garmin.h"
#include "device-private.h" #include "device-private.h"
#include "context-private.h" #include "context-private.h"
@ -207,6 +208,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr
case DC_FAMILY_TECDIVING_DIVECOMPUTEREU: case DC_FAMILY_TECDIVING_DIVECOMPUTEREU:
rc = tecdiving_divecomputereu_device_open (&device, context, iostream); rc = tecdiving_divecomputereu_device_open (&device, context, iostream);
break; break;
case DC_FAMILY_GARMIN:
rc = garmin_device_open (&device, context, iostream);
break;
default: default:
return DC_STATUS_INVALIDARGS; return DC_STATUS_INVALIDARGS;
} }

309
src/garmin.c Normal file
View File

@ -0,0 +1,309 @@
/*
* Garmin Descent Mk1 USB storage downloading
*
* Copyright (C) 2018 Linus Torvalds
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "garmin.h"
#include "context-private.h"
#include "device-private.h"
#include "array.h"
typedef struct garmin_device_t {
dc_device_t base;
dc_iostream_t *iostream;
unsigned char fingerprint[FIT_NAME_SIZE];
} garmin_device_t;
static dc_status_t garmin_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
static dc_status_t garmin_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
static dc_status_t garmin_device_close (dc_device_t *abstract);
static const dc_device_vtable_t garmin_device_vtable = {
sizeof(garmin_device_t),
DC_FAMILY_GARMIN,
garmin_device_set_fingerprint, /* set_fingerprint */
NULL, /* read */
NULL, /* write */
NULL, /* dump */
garmin_device_foreach, /* foreach */
NULL, /* timesync */
garmin_device_close, /* close */
};
dc_status_t
garmin_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream)
{
dc_status_t status = DC_STATUS_SUCCESS;
garmin_device_t *device = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
// Allocate memory.
device = (garmin_device_t *) dc_device_allocate (context, &garmin_device_vtable);
if (device == NULL) {
ERROR (context, "Failed to allocate memory.");
return DC_STATUS_NOMEMORY;
}
// Set the default values.
device->iostream = iostream;
memset(device->fingerprint, 0, sizeof(device->fingerprint));
*out = (dc_device_t *) device;
return DC_STATUS_SUCCESS;
}
static dc_status_t
garmin_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size)
{
garmin_device_t *device = (garmin_device_t *)abstract;
if (size && size != sizeof (device->fingerprint))
return DC_STATUS_INVALIDARGS;
if (size)
memcpy (device->fingerprint, data, sizeof (device->fingerprint));
else
memset (device->fingerprint, 0, sizeof (device->fingerprint));
return DC_STATUS_SUCCESS;
}
static dc_status_t
garmin_device_close (dc_device_t *abstract)
{
dc_status_t status = DC_STATUS_SUCCESS;
garmin_device_t *device = (garmin_device_t *) abstract;
return DC_STATUS_SUCCESS;
}
struct file_list {
int nr, allocated;
struct fit_name *array;
};
static int name_cmp(const void *a, const void *b)
{
// Sort reverse string ordering (newest first), so use 'b,a'
return strcmp(b,a);
}
/*
* Get the FIT file list and sort it.
*
* Return number of files found.
*/
static int get_file_list(DIR *dir, struct file_list *files)
{
struct dirent *de;
while ((de = readdir(dir)) != NULL) {
int len = strlen(de->d_name);
if (len != FIT_NAME_SIZE-1)
continue;
if (strncasecmp(de->d_name + len - 4, ".FIT", 4))
continue;
if (files->nr == files->allocated) {
struct fit_name *array;
int n = 3*(files->allocated + 8)/2;
size_t new_size;
new_size = n * sizeof(array[0]);
array = realloc(files->array, new_size);
if (!array)
return DC_STATUS_NOMEMORY;
files->array = array;
files->allocated = n;
}
memcpy(files->array + files->nr++, de->d_name, FIT_NAME_SIZE);
}
qsort(files->array, files->nr, sizeof(struct fit_name), name_cmp);
return DC_STATUS_SUCCESS;
}
static dc_status_t
read_file(char *pathname, int pathlen, const char *name, dc_buffer_t *file)
{
int fd, rc;
pathname[pathlen] = '/';
memcpy(pathname+pathlen+1, name, FIT_NAME_SIZE);
fd = open(pathname, O_RDONLY);
if (fd < 0)
return DC_STATUS_IO;
rc = DC_STATUS_SUCCESS;
for (;;) {
char buffer[4096];
int n;
n = read(fd, buffer, sizeof(buffer));
if (!n)
break;
if (n > 0) {
dc_buffer_append(file, buffer, n);
continue;
}
rc = DC_STATUS_IO;
break;
}
close(fd);
return rc;
}
static dc_status_t
garmin_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
{
dc_status_t status = DC_STATUS_SUCCESS;
garmin_device_t *device = (garmin_device_t *) abstract;
dc_parser_t *parser;
char pathname[PATH_MAX];
size_t pathlen;
struct file_list files = { 0, 0, NULL };
dc_buffer_t *file;
DIR *dir;
int rc;
// Read the directory name from the iostream
rc = dc_iostream_read(device->iostream, &pathname, sizeof(pathname), &pathlen);
if (rc != DC_STATUS_SUCCESS)
return rc;
// The actual dives are under the "Garmin/Activity/" directory
// as FIT files, with names like "2018-08-20-10-23-30.fit".
// Make sure our buffer is big enough.
if (pathlen + strlen("/Garmin/Activity/") + FIT_NAME_SIZE + 2 > PATH_MAX)
return DC_STATUS_IO;
if (pathlen && pathname[pathlen-1] != '/')
pathname[pathlen++] = '/';
strcpy(pathname + pathlen, "Garmin/Activity");
pathlen += strlen("Garmin/Activity");
dir = opendir(pathname);
if (!dir)
return DC_STATUS_IO;
// Get the list of FIT files
rc = get_file_list(dir, &files);
closedir(dir);
if (rc != DC_STATUS_SUCCESS || !files.nr) {
free(files.array);
return rc;
}
// Can we find the fingerprint entry?
for (int i = 0; i < files.nr; i++) {
const char *name = files.array[i].name;
if (memcmp(name, device->fingerprint, sizeof (device->fingerprint)))
continue;
// Found fingerprint, just cut the array short here
files.nr = i;
break;
}
// Enable progress notifications.
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
progress.maximum = files.nr;
progress.current = 0;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
file = dc_buffer_new (16384);
if (file == NULL) {
ERROR (abstract->context, "Insufficient buffer space available.");
free(files.array);
return DC_STATUS_NOMEMORY;
}
if ((rc = garmin_parser_create(&parser, abstract->context) != DC_STATUS_SUCCESS)) {
ERROR (abstract->context, "Failed to create parser for dive verification.");
free(files.array);
return rc;
}
dc_event_devinfo_t devinfo;
dc_event_devinfo_t *devinfo_p = &devinfo;
for (int i = 0; i < files.nr; i++) {
const char *name = files.array[i].name;
const unsigned char *data;
unsigned int size;
short is_dive = 0;
if (device_is_cancelled(abstract)) {
status = DC_STATUS_CANCELLED;
break;
}
// Reset the membuffer, read the data
dc_buffer_clear(file);
dc_buffer_append(file, name, FIT_NAME_SIZE);
status = read_file(pathname, pathlen, name, file);
if (status != DC_STATUS_SUCCESS)
break;
data = dc_buffer_get_data(file);
size = dc_buffer_get_size(file);
is_dive = garmin_parser_is_dive(parser, data, size, devinfo_p);
if (devinfo_p) {
// first time we came through here, let's emit the
// devinfo and vendor events
device_event_emit (abstract, DC_EVENT_DEVINFO, devinfo_p);
devinfo_p = NULL;
}
if (!is_dive) {
DEBUG (abstract->context, "decided %s isn't a dive.", name);
continue;
}
if (callback && !callback(data, size, name, FIT_NAME_SIZE, userdata))
break;
progress.current++;
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
}
free(files.array);
dc_parser_destroy(parser);
return status;
}

59
src/garmin.h Normal file
View File

@ -0,0 +1,59 @@
/*
* Garmin Descent Mk1
*
* Copyright (C) 2018 Linus Torvalds
*
* 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
*/
#ifndef GARMIN_H
#define GARMIN_H
#include <libdivecomputer/context.h>
#include <libdivecomputer/iostream.h>
#include <libdivecomputer/device.h>
#include <libdivecomputer/parser.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
dc_status_t
garmin_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
dc_status_t
garmin_parser_create (dc_parser_t **parser, dc_context_t *context);
// we need to be able to call into the parser to check if the
// files that we find are actual dives
int
garmin_parser_is_dive (dc_parser_t *abstract, const unsigned char *data, unsigned int size, dc_event_devinfo_t *devinfo_p);
// The dive names are of the form "2018-08-20-10-23-30.fit"
// With the terminating zero, that's 24 bytes.
//
// We use this as the fingerprint, but it ends up being a
// special fixed header in the parser data too.
#define FIT_NAME_SIZE 24
struct fit_name {
char name[FIT_NAME_SIZE];
};
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* GARMIN_H */

1363
src/garmin_parser.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -36,7 +36,7 @@ dc_status_t
hw_ostc_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); hw_ostc_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
dc_status_t dc_status_t
hw_ostc_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int hwos); hw_ostc_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int serial, unsigned int hwos);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -36,7 +36,7 @@ dc_status_t
hw_ostc3_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); hw_ostc3_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
dc_status_t dc_status_t
hw_ostc3_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model); hw_ostc3_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int serial, unsigned int model);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -20,6 +20,12 @@
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef _MSC_VER
#define snprintf _snprintf
#endif
#include "libdivecomputer/units.h" #include "libdivecomputer/units.h"
@ -61,8 +67,14 @@
#define OSTC3_APNEA 3 #define OSTC3_APNEA 3
#define OSTC3_PSCR 4 #define OSTC3_PSCR 4
#define OSTC3_ZHL16 0
#define OSTC3_ZHL16_GF 1
#define OSTC4_VPM 2
#define OSTC4 0x3B #define OSTC4 0x3B
#define UNSUPPORTED 0xFFFFFFFF
typedef struct hw_ostc_sample_info_t { typedef struct hw_ostc_sample_info_t {
unsigned int type; unsigned int type;
unsigned int divisor; unsigned int divisor;
@ -78,7 +90,13 @@ typedef struct hw_ostc_layout_t {
unsigned int salinity; unsigned int salinity;
unsigned int duration; unsigned int duration;
unsigned int temperature; unsigned int temperature;
unsigned int battery;
unsigned int desat;
unsigned int firmware; unsigned int firmware;
unsigned int deco_info1;
unsigned int deco_info2;
unsigned int decomode;
unsigned int battery_percentage;
} hw_ostc_layout_t; } hw_ostc_layout_t;
typedef struct hw_ostc_gasmix_t { typedef struct hw_ostc_gasmix_t {
@ -90,6 +108,7 @@ typedef struct hw_ostc_parser_t {
dc_parser_t base; dc_parser_t base;
unsigned int hwos; unsigned int hwos;
unsigned int model; unsigned int model;
unsigned int serial;
// Cached fields. // Cached fields.
unsigned int cached; unsigned int cached;
unsigned int version; unsigned int version;
@ -127,7 +146,13 @@ static const hw_ostc_layout_t hw_ostc_layout_ostc = {
43, /* salinity */ 43, /* salinity */
47, /* duration */ 47, /* duration */
13, /* temperature */ 13, /* temperature */
34, /* battery volt after dive */
17, /* desat */
32, /* firmware */ 32, /* firmware */
49, /* deco_info1 */
50, /* deco_info1 */
51, /* decomode */
0, /* battery percentage TBD */
}; };
static const hw_ostc_layout_t hw_ostc_layout_frog = { static const hw_ostc_layout_t hw_ostc_layout_frog = {
@ -139,7 +164,13 @@ static const hw_ostc_layout_t hw_ostc_layout_frog = {
43, /* salinity */ 43, /* salinity */
47, /* duration */ 47, /* duration */
19, /* temperature */ 19, /* temperature */
34, /* battery volt after dive */
23, /* desat */
32, /* firmware */ 32, /* firmware */
49, /* deco_info1 */
50, /* deco_info2 */
51, /* decomode */
0, /* battery percentage TBD */
}; };
static const hw_ostc_layout_t hw_ostc_layout_ostc3 = { static const hw_ostc_layout_t hw_ostc_layout_ostc3 = {
@ -151,7 +182,13 @@ static const hw_ostc_layout_t hw_ostc_layout_ostc3 = {
70, /* salinity */ 70, /* salinity */
75, /* duration */ 75, /* duration */
22, /* temperature */ 22, /* temperature */
50, /* battery volt after dive */
26, /* desat */
48, /* firmware */ 48, /* firmware */
77, /* deco_info1 */
78, /* deco_info2 */
79, /* decomode */
59, /* battery percentage */
}; };
static unsigned int static unsigned int
@ -294,7 +331,7 @@ hw_ostc_parser_cache (hw_ostc_parser_t *parser)
} }
static dc_status_t static dc_status_t
hw_ostc_parser_create_internal (dc_parser_t **out, dc_context_t *context, unsigned int hwos, unsigned int model) hw_ostc_parser_create_internal (dc_parser_t **out, dc_context_t *context, unsigned int serial, unsigned int hwos, unsigned int model)
{ {
hw_ostc_parser_t *parser = NULL; hw_ostc_parser_t *parser = NULL;
@ -324,6 +361,7 @@ hw_ostc_parser_create_internal (dc_parser_t **out, dc_context_t *context, unsign
parser->gasmix[i].oxygen = 0; parser->gasmix[i].oxygen = 0;
parser->gasmix[i].helium = 0; parser->gasmix[i].helium = 0;
} }
parser->serial = serial;
*out = (dc_parser_t *) parser; *out = (dc_parser_t *) parser;
@ -332,15 +370,15 @@ hw_ostc_parser_create_internal (dc_parser_t **out, dc_context_t *context, unsign
dc_status_t dc_status_t
hw_ostc_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int hwos) hw_ostc_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int serial, unsigned int hwos)
{ {
return hw_ostc_parser_create_internal (out, context, hwos, 0); return hw_ostc_parser_create_internal (out, context, serial, hwos, 0);
} }
dc_status_t dc_status_t
hw_ostc3_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model) hw_ostc3_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int serial, unsigned int model)
{ {
return hw_ostc_parser_create_internal (out, context, 1, model); return hw_ostc_parser_create_internal (out, context, serial, 1, model);
} }
static dc_status_t static dc_status_t
@ -428,6 +466,8 @@ hw_ostc_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
#define BUFLEN 32
static dc_status_t static dc_status_t
hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
{ {
@ -452,9 +492,12 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned
dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
dc_salinity_t *water = (dc_salinity_t *) value; dc_salinity_t *water = (dc_salinity_t *) value;
dc_field_string_t *string = (dc_field_string_t *) value;
unsigned int salinity = data[layout->salinity]; unsigned int salinity = data[layout->salinity];
if (version == 0x23 || version == 0x24) if (version == 0x23 || version == 0x24)
salinity += 100; salinity += 100;
char buf[BUFLEN];
if (value) { if (value) {
switch (type) { switch (type) {
@ -552,6 +595,79 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned
return DC_STATUS_UNSUPPORTED; return DC_STATUS_UNSUPPORTED;
} }
break; break;
case DC_FIELD_STRING:
switch(flags) {
case 0: /* serial */
string->desc = "Serial";
snprintf(buf, BUFLEN, "%u", parser->serial);
break;
case 1: /* battery */
string->desc = "Battery at end";
unsigned int percentage = (unsigned int) data[layout->battery_percentage];
if (percentage != 0xFF && (version == 0x23 || version == 0x24)) {
percentage = percentage>100? 100: percentage;
snprintf(buf, BUFLEN, "%.2fV, %u%% remaining",
array_uint16_le (data + layout->battery) / 1000.0,
percentage);
} else {
snprintf(buf, BUFLEN, "%.2fV", array_uint16_le (data + layout->battery) / 1000.0);
}
break;
case 2: /* desat */
string->desc = "Desat time";
snprintf(buf, BUFLEN, "%0u:%02u", array_uint16_le (data + layout->desat) / 60,
array_uint16_le (data + layout->desat) % 60);
break;
case 3: /* firmware */
string->desc = "FW Version";
/* OSTC4 stores firmware as XXXX XYYY YYZZ ZZZB, -> X.Y.Z beta? */
if (parser->model == OSTC4) {
int firmwareOnDevice = array_uint16_le (data + layout->firmware);
unsigned char X = 0, Y = 0, Z = 0, beta = 0;
X = (firmwareOnDevice & 0xF800) >> 11;
Y = (firmwareOnDevice & 0x07C0) >> 6;
Z = (firmwareOnDevice & 0x003E) >> 1;
beta = firmwareOnDevice & 0x0001;
snprintf(buf, BUFLEN, "%u.%u.%u%s\n", X, Y, Z, beta? "beta": "");
} else {
snprintf(buf, BUFLEN, "%0u.%02u", data[layout->firmware], data[layout->firmware + 1]);
}
break;
case 4: /* Deco model */
string->desc = "Deco model";
if (((version == 0x23 || version == 0x24) && data[layout->decomode] == OSTC3_ZHL16) ||
(version == 0x22 && data[layout->decomode] == FROG_ZHL16) ||
(version == 0x21 && (data[layout->decomode] == OSTC_ZHL16_OC || data[layout->decomode] == OSTC_ZHL16_CC)))
strncpy(buf, "ZH-L16", BUFLEN);
else if (((version == 0x23 || version == 0x24) && data[layout->decomode] == OSTC3_ZHL16_GF) ||
(version == 0x22 && data[layout->decomode] == FROG_ZHL16_GF) ||
(version == 0x21 && (data[layout->decomode] == OSTC_ZHL16_OC_GF || data[layout->decomode] == OSTC_ZHL16_CC_GF)))
strncpy(buf, "ZH-L16-GF", BUFLEN);
else if (((version == 0x24) && data[layout->decomode] == OSTC4_VPM))
strncpy(buf, "VPM", BUFLEN);
else
return DC_STATUS_DATAFORMAT;
break;
case 5: /* Deco model info */
string->desc = "Deco model info";
if (((version == 0x23 || version == 0x24) && data[layout->decomode] == OSTC3_ZHL16) ||
(version == 0x22 && data[layout->decomode] == FROG_ZHL16) ||
(version == 0x21 && (data[layout->decomode] == OSTC_ZHL16_OC || data[layout->decomode] == OSTC_ZHL16_CC)))
snprintf(buf, BUFLEN, "Saturation %u, Desaturation %u", layout->deco_info1, layout->deco_info2);
else if (((version == 0x23 || version == 0x24) && data[layout->decomode] == OSTC3_ZHL16_GF) ||
(version == 0x22 && data[layout->decomode] == FROG_ZHL16_GF) ||
(version == 0x21 && (data[layout->decomode] == OSTC_ZHL16_OC_GF || data[layout->decomode] == OSTC_ZHL16_CC_GF)))
snprintf(buf, BUFLEN, "GF %u/%u", data[layout->deco_info1], data[layout->deco_info2]);
else
return DC_STATUS_DATAFORMAT;
break;
default:
return DC_STATUS_UNSUPPORTED;
}
string->value = strdup(buf);
break;
default: default:
return DC_STATUS_UNSUPPORTED; return DC_STATUS_UNSUPPORTED;
} }

View File

@ -75,6 +75,8 @@ dc_usbhid_device_free
dc_usbhid_iterator_new dc_usbhid_iterator_new
dc_usbhid_open dc_usbhid_open
dc_usb_storage_open
dc_custom_open dc_custom_open
dc_parser_new dc_parser_new

View File

@ -37,7 +37,7 @@ dc_status_t
oceanic_atom2_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream, unsigned int model); oceanic_atom2_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream, unsigned int model);
dc_status_t dc_status_t
oceanic_atom2_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model); oceanic_atom2_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model, unsigned int serial);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -20,6 +20,8 @@
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <libdivecomputer/units.h> #include <libdivecomputer/units.h>
@ -103,6 +105,7 @@ struct oceanic_atom2_parser_t {
unsigned int model; unsigned int model;
unsigned int headersize; unsigned int headersize;
unsigned int footersize; unsigned int footersize;
unsigned int serial;
// Cached fields. // Cached fields.
unsigned int cached; unsigned int cached;
unsigned int header; unsigned int header;
@ -132,7 +135,7 @@ static const dc_parser_vtable_t oceanic_atom2_parser_vtable = {
dc_status_t dc_status_t
oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model) oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model, unsigned int serial)
{ {
oceanic_atom2_parser_t *parser = NULL; oceanic_atom2_parser_t *parser = NULL;
@ -179,6 +182,7 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, unsigned
parser->headersize = 3 * PAGESIZE; parser->headersize = 3 * PAGESIZE;
} }
parser->serial = serial;
parser->cached = 0; parser->cached = 0;
parser->header = 0; parser->header = 0;
parser->footer = 0; parser->footer = 0;
@ -370,6 +374,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
#define BUF_LEN 16
static dc_status_t static dc_status_t
oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser) oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser)
@ -506,6 +511,9 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns
dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
dc_salinity_t *water = (dc_salinity_t *) value; dc_salinity_t *water = (dc_salinity_t *) value;
dc_field_string_t *string = (dc_field_string_t *) value;
char buf[BUF_LEN];
if (value) { if (value) {
switch (type) { switch (type) {
@ -561,6 +569,17 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns
return DC_STATUS_DATAFORMAT; return DC_STATUS_DATAFORMAT;
} }
break; break;
case DC_FIELD_STRING:
switch(flags) {
case 0: /* Serial */
string->desc = "Serial";
snprintf(buf, BUF_LEN, "%06u", parser->serial);
break;
default:
return DC_STATUS_UNSUPPORTED;
}
string->value = strdup(buf);
break;
default: default:
return DC_STATUS_UNSUPPORTED; return DC_STATUS_UNSUPPORTED;
} }

View File

@ -56,6 +56,7 @@
#include "divesystem_idive.h" #include "divesystem_idive.h"
#include "cochran_commander.h" #include "cochran_commander.h"
#include "tecdiving_divecomputereu.h" #include "tecdiving_divecomputereu.h"
#include "garmin.h"
#include "context-private.h" #include "context-private.h"
#include "parser-private.h" #include "parser-private.h"
@ -64,7 +65,7 @@
#define REACTPROWHITE 0x4354 #define REACTPROWHITE 0x4354
static dc_status_t static dc_status_t
dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t family, unsigned int model, unsigned int devtime, dc_ticks_t systime) dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t family, unsigned int model, unsigned int serial, unsigned int devtime, dc_ticks_t systime)
{ {
dc_status_t rc = DC_STATUS_SUCCESS; dc_status_t rc = DC_STATUS_SUCCESS;
dc_parser_t *parser = NULL; dc_parser_t *parser = NULL;
@ -87,7 +88,7 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
break; break;
case DC_FAMILY_SUUNTO_VYPER2: case DC_FAMILY_SUUNTO_VYPER2:
case DC_FAMILY_SUUNTO_D9: case DC_FAMILY_SUUNTO_D9:
rc = suunto_d9_parser_create (&parser, context, model); rc = suunto_d9_parser_create (&parser, context, model, serial);
break; break;
case DC_FAMILY_SUUNTO_EONSTEEL: case DC_FAMILY_SUUNTO_EONSTEEL:
rc = suunto_eonsteel_parser_create(&parser, context, model); rc = suunto_eonsteel_parser_create(&parser, context, model);
@ -118,7 +119,7 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
if (model == REACTPROWHITE) if (model == REACTPROWHITE)
rc = oceanic_veo250_parser_create (&parser, context, model); rc = oceanic_veo250_parser_create (&parser, context, model);
else else
rc = oceanic_atom2_parser_create (&parser, context, model); rc = oceanic_atom2_parser_create (&parser, context, model, serial);
break; break;
case DC_FAMILY_MARES_NEMO: case DC_FAMILY_MARES_NEMO:
case DC_FAMILY_MARES_PUCK: case DC_FAMILY_MARES_PUCK:
@ -131,11 +132,11 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
rc = mares_iconhd_parser_create (&parser, context, model); rc = mares_iconhd_parser_create (&parser, context, model);
break; break;
case DC_FAMILY_HW_OSTC: case DC_FAMILY_HW_OSTC:
rc = hw_ostc_parser_create (&parser, context, 0); rc = hw_ostc_parser_create (&parser, context, serial, 0);
break; break;
case DC_FAMILY_HW_FROG: case DC_FAMILY_HW_FROG:
case DC_FAMILY_HW_OSTC3: case DC_FAMILY_HW_OSTC3:
rc = hw_ostc3_parser_create (&parser, context, model); rc = hw_ostc3_parser_create (&parser, context, serial, model);
break; break;
case DC_FAMILY_CRESSI_EDY: case DC_FAMILY_CRESSI_EDY:
case DC_FAMILY_ZEAGLE_N2ITION3: case DC_FAMILY_ZEAGLE_N2ITION3:
@ -148,10 +149,10 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
rc = atomics_cobalt_parser_create (&parser, context); rc = atomics_cobalt_parser_create (&parser, context);
break; break;
case DC_FAMILY_SHEARWATER_PREDATOR: case DC_FAMILY_SHEARWATER_PREDATOR:
rc = shearwater_predator_parser_create (&parser, context, model); rc = shearwater_predator_parser_create (&parser, context, model, serial);
break; break;
case DC_FAMILY_SHEARWATER_PETREL: case DC_FAMILY_SHEARWATER_PETREL:
rc = shearwater_petrel_parser_create (&parser, context, model); rc = shearwater_petrel_parser_create (&parser, context, model, serial);
break; break;
case DC_FAMILY_DIVERITE_NITEKQ: case DC_FAMILY_DIVERITE_NITEKQ:
rc = diverite_nitekq_parser_create (&parser, context); rc = diverite_nitekq_parser_create (&parser, context);
@ -168,6 +169,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
case DC_FAMILY_TECDIVING_DIVECOMPUTEREU: case DC_FAMILY_TECDIVING_DIVECOMPUTEREU:
rc = tecdiving_divecomputereu_parser_create (&parser, context); rc = tecdiving_divecomputereu_parser_create (&parser, context);
break; break;
case DC_FAMILY_GARMIN:
rc = garmin_parser_create (&parser, context);
break;
default: default:
return DC_STATUS_INVALIDARGS; return DC_STATUS_INVALIDARGS;
} }
@ -184,7 +188,9 @@ dc_parser_new (dc_parser_t **out, dc_device_t *device)
return DC_STATUS_INVALIDARGS; return DC_STATUS_INVALIDARGS;
return dc_parser_new_internal (out, device->context, return dc_parser_new_internal (out, device->context,
dc_device_get_type (device), device->devinfo.model, dc_device_get_type (device),
device->devinfo.model,
device->devinfo.serial,
device->clock.devtime, device->clock.systime); device->clock.devtime, device->clock.systime);
} }
@ -192,7 +198,9 @@ dc_status_t
dc_parser_new2 (dc_parser_t **out, dc_context_t *context, dc_descriptor_t *descriptor, unsigned int devtime, dc_ticks_t systime) dc_parser_new2 (dc_parser_t **out, dc_context_t *context, dc_descriptor_t *descriptor, unsigned int devtime, dc_ticks_t systime)
{ {
return dc_parser_new_internal (out, context, return dc_parser_new_internal (out, context,
dc_descriptor_get_type (descriptor), dc_descriptor_get_model (descriptor), dc_descriptor_get_type (descriptor),
dc_descriptor_get_model (descriptor),
0,
devtime, systime); devtime, systime);
} }

View File

@ -316,6 +316,27 @@ done:
return status; return status;
} }
dc_status_t
shearwater_common_command (shearwater_common_device_t *device, const unsigned char input[], unsigned int isize)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t *abstract = (dc_device_t *) device;
if (isize > SZ_PACKET)
return DC_STATUS_INVALIDARGS;
if (device_is_cancelled (abstract))
return DC_STATUS_CANCELLED;
// Send the command packet.
status = shearwater_common_slip_write (device, input, isize);
if (status != DC_STATUS_SUCCESS)
ERROR (abstract->context, "Failed to send the command packet.");
return status;
}
dc_status_t dc_status_t
shearwater_common_transfer (shearwater_common_device_t *device, const unsigned char input[], unsigned int isize, unsigned char output[], unsigned int osize, unsigned int *actual) shearwater_common_transfer (shearwater_common_device_t *device, const unsigned char input[], unsigned int isize, unsigned char output[], unsigned int osize, unsigned int *actual)
@ -379,7 +400,6 @@ shearwater_common_transfer (shearwater_common_device_t *device, const unsigned c
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
dc_status_t dc_status_t
shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buffer, unsigned int address, unsigned int size, unsigned int compression, dc_event_progress_t *progress) shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buffer, unsigned int address, unsigned int size, unsigned int compression, dc_event_progress_t *progress)
{ {

View File

@ -32,6 +32,7 @@ extern "C" {
#define ID_SERIAL 0x8010 #define ID_SERIAL 0x8010
#define ID_FIRMWARE 0x8011 #define ID_FIRMWARE 0x8011
#define ID_RDBI 0x8021
#define ID_HARDWARE 0x8050 #define ID_HARDWARE 0x8050
#define PREDATOR 2 #define PREDATOR 2
@ -40,6 +41,7 @@ extern "C" {
#define PERDIX 5 #define PERDIX 5
#define PERDIXAI 6 #define PERDIXAI 6
#define NERD2 7 #define NERD2 7
#define TERIC 8
#define NSTEPS 10000 #define NSTEPS 10000
#define STEP(i,n) ((NSTEPS * (i) + (n) / 2) / (n)) #define STEP(i,n) ((NSTEPS * (i) + (n) / 2) / (n))
@ -52,6 +54,9 @@ typedef struct shearwater_common_device_t {
dc_status_t dc_status_t
shearwater_common_setup (shearwater_common_device_t *device, dc_context_t *context, dc_iostream_t *iostream); shearwater_common_setup (shearwater_common_device_t *device, dc_context_t *context, dc_iostream_t *iostream);
dc_status_t
shearwater_common_command (shearwater_common_device_t *device, const unsigned char input[], unsigned int isize);
dc_status_t dc_status_t
shearwater_common_transfer (shearwater_common_device_t *device, const unsigned char input[], unsigned int isize, unsigned char output[], unsigned int osize, unsigned int *actual); shearwater_common_transfer (shearwater_common_device_t *device, const unsigned char input[], unsigned int isize, unsigned char output[], unsigned int osize, unsigned int *actual);

View File

@ -33,7 +33,6 @@
#define MANIFEST_ADDR 0xE0000000 #define MANIFEST_ADDR 0xE0000000
#define MANIFEST_SIZE 0x600 #define MANIFEST_SIZE 0x600
#define DIVE_ADDR 0xC0000000
#define DIVE_SIZE 0xFFFFFF #define DIVE_SIZE 0xFFFFFF
#define RECORD_SIZE 0x20 #define RECORD_SIZE 0x20
@ -212,25 +211,40 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
unsigned int hardware = array_uint_be (dc_buffer_get_data (buffer), dc_buffer_get_size (buffer)); unsigned int hardware = array_uint_be (dc_buffer_get_data (buffer), dc_buffer_get_size (buffer));
unsigned int model = 0; unsigned int model = 0;
switch (hardware) { switch (hardware) {
case 0x0808: // Petrel 2 case 0x0101:
case 0x0909: // Petrel 1 case 0x0202:
case 0x0B0B: // Petrel 1 (newer hardware) model = PREDATOR;
model = PETREL;
break; break;
case 0x0606:
case 0x0A0A: // Nerd 1 case 0x0A0A: // Nerd 1
model = NERD; model = NERD;
break; break;
case 0x0E0D: // Nerd 2 case 0x0E0D: // Nerd 2
model = NERD2; model = NERD2;
break; break;
case 0x0707: case 0x0404:
case 0x0909: // Petrel 1
case 0x0B0B: // Petrel 1 (newer hardware)
model = PETREL;
break;
case 0x0505:
case 0x0808: // Petrel 2
model = PETREL;
break;
case 0x0707: // documentation list 0C0D for both Perdix and Perdix AI :-(
model = PERDIX; model = PERDIX;
break; break;
case 0x0C0C:
case 0x0C0D: case 0x0C0D:
case 0x0D0D:
model = PERDIXAI; model = PERDIXAI;
break; break;
case 0x0F0F:
model = TERIC;
break;
default: default:
WARNING (abstract->context, "Unknown hardware type %04x.", hardware); model = PETREL;
WARNING (abstract->context, "Unknown hardware type %04x. Assuming Petrel.", hardware);
} }
// Emit a device info event. // Emit a device info event.
@ -240,6 +254,39 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
devinfo.serial = array_uint32_be (serial); devinfo.serial = array_uint32_be (serial);
device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);
// Read the logbook type
rc = shearwater_common_identifier (&device->base, buffer, ID_RDBI);
if (rc != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the logbook type.");
dc_buffer_free (buffer);
dc_buffer_free (manifests);
return rc;
}
unsigned int base_addr = array_uint_be (dc_buffer_get_data (buffer), dc_buffer_get_size (buffer));
INFO(abstract->context, "RDBI command completed with %d bytes, evaluated as %08x", dc_buffer_get_size (buffer), base_addr);
base_addr &= 0xFF000000u;
switch (base_addr) {
case 0xDD000000: // Predator or Predator-Like Format
// on a Predator, use the old format, otherwise use the Predator-Like Format (what we called Petrel so far)
if (model != PREDATOR)
base_addr = 0xC0000000u;
break;
case 0x90000000: // some firmware versions supported an earlier version of PNF without final record
// use the Predator-Like Format instead
base_addr = 0xC0000000u;
break;
case 0x80000000: // new Petrel Native Format with final record
// that's the correct address
break;
default: // unknown format
// use the defaults for the models
if (model >= TERIC)
base_addr = 0x80000000u;
else
base_addr = 0xC0000000u;
}
// Read the manifest pages
while (1) { while (1) {
// Update the progress state. // Update the progress state.
// Assume the worst case scenario of a full manifest, and adjust the // Assume the worst case scenario of a full manifest, and adjust the
@ -263,11 +310,17 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
unsigned int size = dc_buffer_get_size (buffer); unsigned int size = dc_buffer_get_size (buffer);
// Process the records in the manifest. // Process the records in the manifest.
unsigned int count = 0; unsigned int count = 0, deleted = 0;
unsigned int offset = 0; unsigned int offset = 0;
while (offset < size) { while (offset < size) {
// Check for a valid dive header. // Check for a valid dive header.
unsigned int header = array_uint16_be (data + offset); unsigned int header = array_uint16_be (data + offset);
if (header == 0x5A23) {
// this is a deleted dive; keep looking
offset += RECORD_SIZE;
deleted++;
continue;
}
if (header != 0xA5C4) if (header != 0xA5C4)
break; break;
@ -281,7 +334,7 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
// Update the progress state. // Update the progress state.
current += 1; current += 1;
maximum -= RECORD_COUNT - count; maximum -= RECORD_COUNT - count - deleted;
// Append the manifest records to the main buffer. // Append the manifest records to the main buffer.
if (!dc_buffer_append (manifests, data, count * RECORD_SIZE)) { if (!dc_buffer_append (manifests, data, count * RECORD_SIZE)) {
@ -292,7 +345,7 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
} }
// Stop downloading manifest if there are no more records. // Stop downloading manifest if there are no more records.
if (count != RECORD_COUNT) if (count + deleted != RECORD_COUNT)
break; break;
} }
@ -307,13 +360,18 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
unsigned int offset = 0; unsigned int offset = 0;
while (offset < size) { while (offset < size) {
// skip deleted dives
if (array_uint16_be(data + offset) == 0x5A23) {
offset += RECORD_SIZE;
continue;
}
// Get the address of the dive. // Get the address of the dive.
unsigned int address = array_uint32_be (data + offset + 20); unsigned int address = array_uint32_be (data + offset + 20);
// Download the dive. // Download the dive.
progress.current = NSTEPS * current; progress.current = NSTEPS * current;
progress.maximum = NSTEPS * maximum; progress.maximum = NSTEPS * maximum;
rc = shearwater_common_download (&device->base, buffer, DIVE_ADDR + address, DIVE_SIZE, 1, &progress); rc = shearwater_common_download (&device->base, buffer, base_addr + address, DIVE_SIZE, 1, &progress);
if (rc != DC_STATUS_SUCCESS) { if (rc != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to download the dive."); ERROR (abstract->context, "Failed to download the dive.");
dc_buffer_free (buffer); dc_buffer_free (buffer);
@ -331,6 +389,11 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call
offset += RECORD_SIZE; offset += RECORD_SIZE;
} }
// send the "graceful exit" instruction
// I don't think we care about the return value of this call - this is just trying to be nice to the device
unsigned char command[4] = { 0x2e, 0x90, 0x20, 0x00 };
rc = shearwater_common_command(&device->base, command, 4);
DEBUG(abstract->context, "Sent graceful exit command, rc=%d", rc);
// Update and emit a progress event. // Update and emit a progress event.
progress.current = NSTEPS * current; progress.current = NSTEPS * current;

View File

@ -35,7 +35,7 @@ dc_status_t
shearwater_petrel_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); shearwater_petrel_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
dc_status_t dc_status_t
shearwater_petrel_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model); shearwater_petrel_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model, unsigned int serial);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -35,7 +35,7 @@ dc_status_t
shearwater_predator_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream); shearwater_predator_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
dc_status_t dc_status_t
shearwater_predator_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model); shearwater_predator_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model, unsigned int serial);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -20,6 +20,13 @@
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#ifdef _MSC_VER
#define snprintf _snprintf
#endif
#include <libdivecomputer/units.h> #include <libdivecomputer/units.h>
@ -33,6 +40,30 @@
dc_parser_isinstance((parser), &shearwater_predator_parser_vtable) || \ dc_parser_isinstance((parser), &shearwater_predator_parser_vtable) || \
dc_parser_isinstance((parser), &shearwater_petrel_parser_vtable)) dc_parser_isinstance((parser), &shearwater_petrel_parser_vtable))
// Petrel Native Format constants
#define PNF_BLOCKSIZE 0x20
#define LOG_RECORD_DIVE_SAMPLE 0x01
#define LOG_RECORD_FREEDIVE_SAMPLE 0x02
#define LOG_RECORD_OPENING_0 0x10
#define LOG_RECORD_OPENING_1 0x11
#define LOG_RECORD_OPENING_2 0x12
#define LOG_RECORD_OPENING_3 0x13
#define LOG_RECORD_OPENING_4 0x14
#define LOG_RECORD_OPENING_5 0x15
#define LOG_RECORD_OPENING_6 0x16
#define LOG_RECORD_OPENING_7 0x17
#define LOG_RECORD_CLOSING_0 0x20
#define LOG_RECORD_CLOSING_1 0x21
#define LOG_RECORD_CLOSING_2 0x22
#define LOG_RECORD_CLOSING_3 0x23
#define LOG_RECORD_CLOSING_4 0x24
#define LOG_RECORD_CLOSING_5 0x25
#define LOG_RECORD_CLOSING_6 0x26
#define LOG_RECORD_CLOSING_7 0x27
#define LOG_RECORD_FINAL 0xFF
#define NUM_BLOCK_IDS 0x28
// constant for the older Predator and Predator-like formats
#define SZ_BLOCK 0x80 #define SZ_BLOCK 0x80
#define SZ_SAMPLE_PREDATOR 0x10 #define SZ_SAMPLE_PREDATOR 0x10
#define SZ_SAMPLE_PETREL 0x20 #define SZ_SAMPLE_PETREL 0x20
@ -47,6 +78,7 @@
#define IMPERIAL 1 #define IMPERIAL 1
#define NGASMIXES 10 #define NGASMIXES 10
#define MAXSTRINGS 32
#define PREDATOR 2 #define PREDATOR 2
#define PETREL 3 #define PETREL 3
@ -57,6 +89,7 @@ struct shearwater_predator_parser_t {
dc_parser_t base; dc_parser_t base;
unsigned int model; unsigned int model;
unsigned int petrel; unsigned int petrel;
unsigned int pnf;
unsigned int samplesize; unsigned int samplesize;
// Cached fields. // Cached fields.
unsigned int cached; unsigned int cached;
@ -68,7 +101,14 @@ struct shearwater_predator_parser_t {
unsigned int helium[NGASMIXES]; unsigned int helium[NGASMIXES];
unsigned int calibrated; unsigned int calibrated;
double calibration[3]; double calibration[3];
unsigned int serial;
dc_divemode_t mode; dc_divemode_t mode;
/* Block addresses for PNF */
unsigned int block_offset[NUM_BLOCK_IDS];
/* String fields */
dc_field_string_t strings[MAXSTRINGS];
}; };
static dc_status_t shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); static dc_status_t shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size);
@ -112,7 +152,7 @@ shearwater_predator_find_gasmix (shearwater_predator_parser_t *parser, unsigned
static dc_status_t static dc_status_t
shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model, unsigned int petrel) shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model, unsigned int serial, unsigned int petrel)
{ {
shearwater_predator_parser_t *parser = NULL; shearwater_predator_parser_t *parser = NULL;
const dc_parser_vtable_t *vtable = NULL; const dc_parser_vtable_t *vtable = NULL;
@ -140,6 +180,9 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig
parser->model = model; parser->model = model;
parser->petrel = petrel; parser->petrel = petrel;
parser->samplesize = samplesize; parser->samplesize = samplesize;
parser->serial = serial;
// Set the default values.
parser->cached = 0; parser->cached = 0;
parser->logversion = 0; parser->logversion = 0;
parser->headersize = 0; parser->headersize = 0;
@ -162,16 +205,16 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig
dc_status_t dc_status_t
shearwater_predator_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model) shearwater_predator_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model, unsigned int serial)
{ {
return shearwater_common_parser_create (out, context, model, 0); return shearwater_common_parser_create (out, context, model, serial, 0);
} }
dc_status_t dc_status_t
shearwater_petrel_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model) shearwater_petrel_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model, unsigned int serial)
{ {
return shearwater_common_parser_create (out, context, model, 1); return shearwater_common_parser_create (out, context, model, serial, 1);
} }
@ -205,6 +248,7 @@ shearwater_predator_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *d
{ {
const unsigned char *data = abstract->data; const unsigned char *data = abstract->data;
unsigned int size = abstract->size; unsigned int size = abstract->size;
shearwater_predator_parser_t *parser = (shearwater_predator_parser_t *) abstract;
if (size < 2 * SZ_BLOCK) if (size < 2 * SZ_BLOCK)
return DC_STATUS_DATAFORMAT; return DC_STATUS_DATAFORMAT;
@ -219,6 +263,150 @@ shearwater_predator_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *d
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
/*
* These string cache interfaces should be some generic
* library rather than copied for all the dive computers.
*
* This is just copied from the EON Steel code.
*/
static void
add_string(shearwater_predator_parser_t *parser, const char *desc, const char *value)
{
int i;
for (i = 0; i < MAXSTRINGS; i++) {
dc_field_string_t *str = parser->strings+i;
if (str->desc)
continue;
str->desc = desc;
str->value = strdup(value);
break;
}
}
static void
add_string_fmt(shearwater_predator_parser_t *parser, const char *desc, const char *fmt, ...)
{
char buffer[256];
va_list ap;
/*
* We ignore the return value from vsnprintf, and we
* always NUL-terminate the destination buffer ourselves.
*
* That way we don't have to worry about random bad legacy
* implementations.
*/
va_start(ap, fmt);
buffer[sizeof(buffer)-1] = 0;
(void) vsnprintf(buffer, sizeof(buffer)-1, fmt, ap);
va_end(ap);
return add_string(parser, desc, buffer);
}
// The Battery state is a big-endian word:
//
// ffff = not paired / no comms for 90 s
// fffe = no comms for 30 s
//
// Otherwise:
// - top four bits are battery state (0 - normal, 1 - critical, 2 - warning)
// - bottom 12 bits are pressure in 2 psi increments (0..8k psi)
//
// This returns the state as a bitmask (so you can see all states it had
// during the dive). Note that we currently do not report pairing and
// communication lapses. Todo?
static unsigned int
battery_state(const unsigned char *data)
{
unsigned int pressure = array_uint16_be(data);
unsigned int state;
if ((pressure & 0xFFF0) == 0xFFF0)
return 0;
state = pressure >> 12;
if (state > 2)
return 0;
return 1u << state;
}
// Show the battery state
//
// NOTE! Right now it only shows the most serious bit
// but the code is set up so that we could perhaps
// indicate that the battery is on the edge (ie it
// reported both "normal" _and_ "warning" during the
// dive - maybe that would be a "starting to warn")
//
// We could also report unpaired and comm errors.
static void
add_battery_info(shearwater_predator_parser_t *parser, const char *desc, unsigned int state)
{
if (state >= 1 && state <= 7) {
static const char *states[8] = {
"", // 000 - No state bits, not used
"normal", // 001 - only normal
"critical", // 010 - only critical
"critical", // 011 - both normal and critical
"warning", // 100 - only warning
"warning", // 101 - normal and warning
"critical", // 110 - warning and critical
"critical", // 111 - normal, warning and critical
};
add_string(parser, desc, states[state]);
}
}
static void
add_deco_model(shearwater_predator_parser_t *parser, const unsigned char *data)
{
unsigned int idx_deco_model = parser->pnf ? parser->block_offset[LOG_RECORD_OPENING_2] + 18 : 67;
unsigned int idx_gfs = parser->pnf ? parser->block_offset[LOG_RECORD_OPENING_3] + 5 : 85;
switch (data[idx_deco_model]) {
case 0:
add_string_fmt(parser, "Deco model", "GF %u/%u", data[4], data[5]);
break;
case 1:
add_string_fmt(parser, "Deco model", "VPM-B +%u", data[idx_deco_model + 1]);
break;
case 2:
add_string_fmt(parser, "Deco model", "VPM-B/GFS +%u %u%%", data[idx_deco_model + 1], data[idx_gfs]);
break;
default:
add_string_fmt(parser, "Deco model", "Unknown model %d", data[idx_deco_model]);
}
}
static void
add_battery_type(shearwater_predator_parser_t *parser, const unsigned char *data)
{
if (parser->logversion < 7)
return;
unsigned int idx_battery_type = parser->pnf ? parser->block_offset[LOG_RECORD_OPENING_4] + 9 : 120;
switch (data[idx_battery_type]) {
case 1:
add_string(parser, "Battery type", "1.5V Alkaline");
break;
case 2:
add_string(parser, "Battery type", "1.5V Lithium");
break;
case 3:
add_string(parser, "Battery type", "1.2V NiMH");
break;
case 4:
add_string(parser, "Battery type", "3.6V Saft");
break;
case 5:
add_string(parser, "Battery type", "3.7V Li-Ion");
break;
default:
add_string_fmt(parser, "Battery type", "unknown type %d", data[idx_battery_type]);
break;
}
}
static dc_status_t static dc_status_t
shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
@ -231,21 +419,66 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
// the log formats are very similar - but the Petrel Native Format (PNF)
// is organized differently. There everything is in 32 byte (PNF_BLOCKSIZE) blocks
// and the offsets of various fields are different. It still seems to make sense
// to just all parse it in one place
// header and footer are concepts of the Predator and Predator-like formats
unsigned int headersize = SZ_BLOCK; unsigned int headersize = SZ_BLOCK;
unsigned int footersize = SZ_BLOCK; unsigned int footersize = SZ_BLOCK;
if (size < headersize + footersize) { if (size < headersize + footersize) {
ERROR (abstract->context, "Invalid data length."); ERROR (abstract->context, "Invalid data length.");
return DC_STATUS_DATAFORMAT; return DC_STATUS_DATAFORMAT;
} }
// remember if this is a Petrel Native Format download
// if yes, we need different ways to access the various data fields
// for samples it's simple, they are just offset by one (so we can use pnf as offset)
// for header and footer data it's more complicated because of the new block structure
unsigned int pnf = parser->pnf = data[0] == 0x10 ? 1 : 0;
// sanity check on the log format
// is this a Predator-like or Petrel-native (or Teric style) log?
if (parser->petrel == 0 && pnf) {
ERROR (abstract->context, "This is a Petrel-native log, but we claim this is a Predator");
return DC_STATUS_DATAFORMAT;
}
memset (parser->block_offset, 0, NUM_BLOCK_IDS * sizeof(unsigned int));
if (pnf) {
// find the offsets of the various header and footer blocks
int i = 0, j = 0;
while (i < size) {
for (j = LOG_RECORD_OPENING_0; j < NUM_BLOCK_IDS; j++) {
if (data[i] == j)
parser->block_offset[j] = i;
if (j == LOG_RECORD_OPENING_7)
j = LOG_RECORD_CLOSING_0 - 1;
}
i += PNF_BLOCKSIZE;
}
}
// there is a small risk we are taking here... if the log were damaged and one or
// more of the blocks were missing, we'll default to looking into block 0 and
// report bogus data. This may be worth testing for?
// Log versions before 6 weren't reliably stored in the data, but // Log versions before 6 weren't reliably stored in the data, but
// 6 is also the oldest version that we assume in our code // 6 is also the oldest version that we assume in our code
unsigned int logversion = 6; unsigned int logversion = 6;
if (data[127] > 6) if (!pnf && data[127] > 6)
logversion = data[127]; logversion = data[127];
if (pnf)
logversion = data[parser->block_offset[LOG_RECORD_OPENING_4] + 16];
INFO(abstract->context, "Shearwater log version %u\n", logversion);
memset(parser->strings, 0, sizeof(parser->strings));
add_string_fmt(parser, "Logversion", "%d%s", logversion, pnf ? "(PNF)" : "");
// Adjust the footersize for the final block. // Adjust the footersize for the final block.
if (parser->petrel || array_uint16_be (data + size - footersize) == 0xFFFD) { if (parser->petrel == 1 || array_uint16_be (data + size - footersize) == 0xFFFD) {
footersize += SZ_BLOCK; footersize += SZ_BLOCK;
if (size < headersize + footersize) { if (size < headersize + footersize) {
ERROR (abstract->context, "Invalid data length."); ERROR (abstract->context, "Invalid data length.");
@ -253,6 +486,14 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
} }
} }
// if this is logversion 9 or higher, make sure this isn't a freedive, as we can't parse that
if (logversion > 9 && pnf) {
if (data[parser->block_offset[LOG_RECORD_OPENING_5] + 25] == LOG_RECORD_FREEDIVE_SAMPLE) {
ERROR (abstract->context, "Cannot parse freedive samples");
return DC_STATUS_DATAFORMAT;
}
}
// Default dive mode. // Default dive mode.
dc_divemode_t mode = DC_DIVEMODE_OC; dc_divemode_t mode = DC_DIVEMODE_OC;
@ -262,9 +503,19 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
unsigned int helium[NGASMIXES] = {0}; unsigned int helium[NGASMIXES] = {0};
unsigned int o2_previous = 0, he_previous = 0; unsigned int o2_previous = 0, he_previous = 0;
unsigned int offset = headersize; // Transmitter battery levels
unsigned int length = size - footersize; unsigned int t1_battery = 0, t2_battery = 0;
// the indices in the sample block are offset by 1 in PNF
unsigned int offset = pnf ? 0 : headersize;
unsigned int length = pnf ? size : size - footersize;
while (offset < length) { while (offset < length) {
// Ignore blocks that aren't dive samples
if (pnf && data[offset] != LOG_RECORD_DIVE_SAMPLE) {
offset += parser->samplesize;
continue;
}
// Ignore empty samples. // Ignore empty samples.
if (array_isequal (data + offset, parser->samplesize, 0x00)) { if (array_isequal (data + offset, parser->samplesize, 0x00)) {
offset += parser->samplesize; offset += parser->samplesize;
@ -272,14 +523,14 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
} }
// Status flags. // Status flags.
unsigned int status = data[offset + 11]; unsigned int status = data[offset + 11 + pnf];
if ((status & OC) == 0) { if ((status & OC) == 0) {
mode = DC_DIVEMODE_CCR; mode = DC_DIVEMODE_CCR;
} }
// Gaschange. // Gaschange.
unsigned int o2 = data[offset + 7]; unsigned int o2 = data[offset + 7 + pnf];
unsigned int he = data[offset + 8]; unsigned int he = data[offset + 8 + pnf];
if (o2 != o2_previous || he != he_previous) { if (o2 != o2_previous || he != he_previous) {
// Find the gasmix in the list. // Find the gasmix in the list.
unsigned int idx = 0; unsigned int idx = 0;
@ -304,13 +555,27 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
he_previous = he; he_previous = he;
} }
// Transmitter battery levels
if (logversion >= 7) {
// T1 at offset 27, T2 at offset 19
t1_battery |= battery_state(data + offset + 27 + pnf);
t2_battery |= battery_state(data + offset + 19 + pnf);
}
offset += parser->samplesize; offset += parser->samplesize;
} }
// for header and footer indices we use a variable base that is set to the
// correct value based on the log type
unsigned int base = 0;
// Cache sensor calibration for later use // Cache sensor calibration for later use
unsigned int nsensors = 0, ndefaults = 0; unsigned int nsensors = 0, ndefaults = 0;
// calibration value for sensors
base = pnf ? parser->block_offset[LOG_RECORD_OPENING_3] + 7 : 87;
for (size_t i = 0; i < 3; ++i) { for (size_t i = 0; i < 3; ++i) {
unsigned int calibration = array_uint16_be(data + 87 + i * 2); unsigned int calibration = array_uint16_be(data + base + i * 2);
parser->calibration[i] = calibration / 100000.0; parser->calibration[i] = calibration / 100000.0;
if (parser->model == PREDATOR) { if (parser->model == PREDATOR) {
// The Predator expects the mV output of the cells to be // The Predator expects the mV output of the cells to be
@ -319,7 +584,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
// sensors lines up and matches the average. // sensors lines up and matches the average.
parser->calibration[i] *= 2.2; parser->calibration[i] *= 2.2;
} }
if (data[86] & (1 << i)) { if (data[base - 1] & (1 << i)) {
if (calibration == 2100) { if (calibration == 2100) {
ndefaults++; ndefaults++;
} }
@ -334,8 +599,12 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
// uncalibrated). // uncalibrated).
WARNING (abstract->context, "Disabled all O2 sensors due to a default calibration value."); WARNING (abstract->context, "Disabled all O2 sensors due to a default calibration value.");
parser->calibrated = 0; parser->calibrated = 0;
if (mode != DC_DIVEMODE_OC)
add_string(parser, "PPO2 source", "voted/averaged");
} else { } else {
parser->calibrated = data[86]; parser->calibrated = data[base - 1];
if (mode != DC_DIVEMODE_OC)
add_string(parser, "PPO2 source", "cells");
} }
// Cache the data for later use. // Cache the data for later use.
@ -348,6 +617,14 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
parser->helium[i] = helium[i]; parser->helium[i] = helium[i];
} }
parser->mode = mode; parser->mode = mode;
add_string_fmt(parser, "Serial", "%08x", parser->serial);
// bytes 1-31 are identical in all formats
add_string_fmt(parser, "FW Version", "%2x", data[19]);
add_deco_model(parser, data);
add_battery_type(parser, data);
add_string_fmt(parser, "Battery at end", "%.1f V", data[9] / 10.0);
add_battery_info(parser, "T1 battery", t1_battery);
add_battery_info(parser, "T2 battery", t2_battery);
parser->cached = 1; parser->cached = 1;
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
@ -374,18 +651,29 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ
dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
dc_salinity_t *water = (dc_salinity_t *) value; dc_salinity_t *water = (dc_salinity_t *) value;
dc_field_string_t *string = (dc_field_string_t *) value;
unsigned int density = 0; unsigned int density = 0;
// the first 32 bytes of the footer and closing block 0 are identical
unsigned int block_start = parser->pnf ? parser->block_offset[LOG_RECORD_CLOSING_0] : footer;
if (value) { if (value) {
unsigned int idx;
switch (type) { switch (type) {
case DC_FIELD_DIVETIME: case DC_FIELD_DIVETIME:
*((unsigned int *) value) = array_uint16_be (data + footer + 6) * 60; // FIXME: this is wrong based on the documentation I received
// it should be a 3 byte value in offsets 6-8 that is dive length in seconds
*((unsigned int *) value) = array_uint16_be (data + block_start + 6) * 60;
break; break;
case DC_FIELD_MAXDEPTH: case DC_FIELD_MAXDEPTH:
if (units == IMPERIAL) if (units == IMPERIAL)
*((double *) value) = array_uint16_be (data + footer + 4) * FEET; *((double *) value) = array_uint16_be (data + block_start + 4) * FEET;
else else
*((double *) value) = array_uint16_be (data + footer + 4); *((double *) value) = array_uint16_be (data + block_start + 4);
// according to the documentation this should have been in tenth of a meter
// before, but the existing code for the Predator-like format didn't have
// that adjustment, so let's just do that for PNF (where we definitely need it).
if (parser->pnf)
*((double *)value) /= 10.0;
break; break;
case DC_FIELD_GASMIX_COUNT: case DC_FIELD_GASMIX_COUNT:
*((unsigned int *) value) = parser->ngasmixes; *((unsigned int *) value) = parser->ngasmixes;
@ -396,7 +684,8 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ
gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium;
break; break;
case DC_FIELD_SALINITY: case DC_FIELD_SALINITY:
density = array_uint16_be (data + 83); idx = parser->pnf ? parser->block_offset[LOG_RECORD_OPENING_3] + 3 : 83;
density = array_uint16_be (data + idx);
if (density == 1000) if (density == 1000)
water->type = DC_WATER_FRESH; water->type = DC_WATER_FRESH;
else else
@ -404,11 +693,21 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ
water->density = density; water->density = density;
break; break;
case DC_FIELD_ATMOSPHERIC: case DC_FIELD_ATMOSPHERIC:
idx = parser->pnf ? parser->block_offset[LOG_RECORD_OPENING_1] + 16 : 47;
*((double *) value) = array_uint16_be (data + 47) / 1000.0; *((double *) value) = array_uint16_be (data + 47) / 1000.0;
break; break;
case DC_FIELD_DIVEMODE: case DC_FIELD_DIVEMODE:
*((dc_divemode_t *) value) = parser->mode; *((dc_divemode_t *) value) = parser->mode;
break; break;
case DC_FIELD_STRING:
if (flags < MAXSTRINGS) {
dc_field_string_t *p = parser->strings + flags;
if (p->desc) {
*string = *p;
break;
}
}
return DC_STATUS_UNSUPPORTED;
default: default:
return DC_STATUS_UNSUPPORTED; return DC_STATUS_UNSUPPORTED;
} }
@ -438,11 +737,27 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
unsigned int o2_previous = 0, he_previous = 0; unsigned int o2_previous = 0, he_previous = 0;
unsigned int time = 0; unsigned int time = 0;
unsigned int offset = parser->headersize; unsigned int pnf = parser->pnf;
unsigned int length = size - parser->footersize; unsigned int offset = pnf ? 0 : parser->headersize;
unsigned int length = pnf ? size : size - parser->footersize;
unsigned int time_increment = 10;
// the time increment is now given in ms. not sure how we'll deal with that since all we do is full seconds
if (pnf && parser->logversion >= 9)
time_increment = array_uint16_be (data + parser->block_offset[LOG_RECORD_OPENING_5] + 23) / 1000;
while (offset < length) { while (offset < length) {
dc_sample_value_t sample = {0}; dc_sample_value_t sample = {0};
// stop parsing if we see the end block
if (pnf && data[offset] == LOG_RECORD_FINAL && data[offset + 1] == 0xFD)
break;
// Ignore blocks that aren't dive samples
if (pnf && data[offset] != LOG_RECORD_DIVE_SAMPLE) {
offset += parser->samplesize;
continue;
}
// Ignore empty samples. // Ignore empty samples.
if (array_isequal (data + offset, parser->samplesize, 0x00)) { if (array_isequal (data + offset, parser->samplesize, 0x00)) {
offset += parser->samplesize; offset += parser->samplesize;
@ -450,12 +765,12 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
} }
// Time (seconds). // Time (seconds).
time += 10; time += time_increment;
sample.time = time; sample.time = time;
if (callback) callback (DC_SAMPLE_TIME, sample, userdata); if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
// Depth (1/10 m or ft). // Depth (1/10 m or ft).
unsigned int depth = array_uint16_be (data + offset); unsigned int depth = array_uint16_be (data + pnf + offset);
if (units == IMPERIAL) if (units == IMPERIAL)
sample.depth = depth * FEET / 10.0; sample.depth = depth * FEET / 10.0;
else else
@ -463,7 +778,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
// Temperature (°C or °F). // Temperature (°C or °F).
int temperature = (signed char) data[offset + 13]; int temperature = (signed char) data[offset + pnf + 13];
if (temperature < 0) { if (temperature < 0) {
// Fix negative temperatures. // Fix negative temperatures.
temperature += 102; temperature += 102;
@ -478,30 +793,31 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
// Status flags. // Status flags.
unsigned int status = data[offset + 11]; unsigned int status = data[offset + pnf + 11];
if ((status & OC) == 0) { if ((status & OC) == 0) {
// PPO2 // PPO2
if ((status & PPO2_EXTERNAL) == 0) { if ((status & PPO2_EXTERNAL) == 0) {
#ifdef SENSOR_AVERAGE if (!parser->calibrated) {
sample.ppo2 = data[offset + 6] / 100.0; sample.ppo2 = data[offset + pnf + 6] / 100.0;
if (callback) callback (DC_SAMPLE_PPO2, sample, userdata); if (callback) callback (DC_SAMPLE_PPO2, sample, userdata);
#else } else {
sample.ppo2 = data[offset + 12] * parser->calibration[0]; sample.ppo2 = data[offset + pnf + 12] * parser->calibration[0];
if (callback && (parser->calibrated & 0x01)) callback (DC_SAMPLE_PPO2, sample, userdata); if (callback && (parser->calibrated & 0x01)) callback (DC_SAMPLE_PPO2, sample, userdata);
sample.ppo2 = data[offset + 14] * parser->calibration[1]; sample.ppo2 = data[offset + pnf + 14] * parser->calibration[1];
if (callback && (parser->calibrated & 0x02)) callback (DC_SAMPLE_PPO2, sample, userdata); if (callback && (parser->calibrated & 0x02)) callback (DC_SAMPLE_PPO2, sample, userdata);
sample.ppo2 = data[offset + 15] * parser->calibration[2]; sample.ppo2 = data[offset + pnf + 15] * parser->calibration[2];
if (callback && (parser->calibrated & 0x04)) callback (DC_SAMPLE_PPO2, sample, userdata); if (callback && (parser->calibrated & 0x04)) callback (DC_SAMPLE_PPO2, sample, userdata);
#endif }
} }
// Setpoint // Setpoint
if (parser->petrel) { if (parser->petrel) {
sample.setpoint = data[offset + 18] / 100.0; sample.setpoint = data[offset + pnf + 18] / 100.0;
} else { } else {
// this will only ever be called for the actual Predator, so no adjustment needed for PNF
if (status & SETPOINT_HIGH) { if (status & SETPOINT_HIGH) {
sample.setpoint = data[18] / 100.0; sample.setpoint = data[18] / 100.0;
} else { } else {
@ -513,13 +829,13 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
// CNS // CNS
if (parser->petrel) { if (parser->petrel) {
sample.cns = data[offset + 22] / 100.0; sample.cns = data[offset + pnf + 22] / 100.0;
if (callback) callback (DC_SAMPLE_CNS, sample, userdata); if (callback) callback (DC_SAMPLE_CNS, sample, userdata);
} }
// Gaschange. // Gaschange.
unsigned int o2 = data[offset + 7]; unsigned int o2 = data[offset + pnf + 7];
unsigned int he = data[offset + 8]; unsigned int he = data[offset + pnf + 8];
if (o2 != o2_previous || he != he_previous) { if (o2 != o2_previous || he != he_previous) {
unsigned int idx = shearwater_predator_find_gasmix (parser, o2, he); unsigned int idx = shearwater_predator_find_gasmix (parser, o2, he);
if (idx >= parser->ngasmixes) { if (idx >= parser->ngasmixes) {
@ -534,7 +850,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
} }
// Deco stop / NDL. // Deco stop / NDL.
unsigned int decostop = array_uint16_be (data + offset + 2); unsigned int decostop = array_uint16_be (data + offset + pnf + 2);
if (decostop) { if (decostop) {
sample.deco.type = DC_DECO_DECOSTOP; sample.deco.type = DC_DECO_DECOSTOP;
if (units == IMPERIAL) if (units == IMPERIAL)
@ -545,7 +861,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
sample.deco.type = DC_DECO_NDL; sample.deco.type = DC_DECO_NDL;
sample.deco.depth = 0.0; sample.deco.depth = 0.0;
} }
sample.deco.time = data[offset + 9] * 60; sample.deco.time = data[offset + pnf + 9] * 60;
if (callback) callback (DC_SAMPLE_DECO, sample, userdata); if (callback) callback (DC_SAMPLE_DECO, sample, userdata);
// for logversion 7 and newer (introduced for Perdix AI) // for logversion 7 and newer (introduced for Perdix AI)
@ -560,14 +876,14 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
// For regular values, the top 4 bits contain the battery // For regular values, the top 4 bits contain the battery
// level (0=normal, 1=critical, 2=warning), and the lower 12 // level (0=normal, 1=critical, 2=warning), and the lower 12
// bits the tank pressure in units of 2 psi. // bits the tank pressure in units of 2 psi.
unsigned int pressure = array_uint16_be (data + offset + 27); unsigned int pressure = array_uint16_be (data + offset + pnf + 27);
if (pressure < 0xFFF0) { if (pressure < 0xFFF0) {
pressure &= 0x0FFF; pressure &= 0x0FFF;
sample.pressure.tank = 0; sample.pressure.tank = 0;
sample.pressure.value = pressure * 2 * PSI / BAR; sample.pressure.value = pressure * 2 * PSI / BAR;
if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata);
} }
pressure = array_uint16_be (data + offset + 19); pressure = array_uint16_be (data + offset + pnf + 19);
if (pressure < 0xFFF0) { if (pressure < 0xFFF0) {
pressure &= 0x0FFF; pressure &= 0x0FFF;
sample.pressure.tank = 1; sample.pressure.tank = 1;
@ -582,14 +898,13 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
// 0xFD Not available in current mode // 0xFD Not available in current mode
// 0xFC Not available because of DECO // 0xFC Not available because of DECO
// 0xFB Tank size or max pressure havent been set up // 0xFB Tank size or max pressure havent been set up
if (data[offset + 21] < 0xF0) { if (data[offset + pnf + 21] < 0xF0) {
sample.rbt = data[offset + 21]; sample.rbt = data[offset + pnf + 21];
if (callback) callback (DC_SAMPLE_RBT, sample, userdata); if (callback) callback (DC_SAMPLE_RBT, sample, userdata);
} }
} }
offset += parser->samplesize; offset += parser->samplesize;
} }
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }

View File

@ -36,7 +36,7 @@ dc_status_t
suunto_d9_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream, unsigned int model); suunto_d9_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream, unsigned int model);
dc_status_t dc_status_t
suunto_d9_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model); suunto_d9_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int model, unsigned int serial);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -20,7 +20,8 @@
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <string.h> // memcmp #include <string.h> // memcmp, strdup
#include <stdio.h> // snprintf
#include "suunto_d9.h" #include "suunto_d9.h"
#include "context-private.h" #include "context-private.h"
@ -72,6 +73,7 @@ typedef struct suunto_d9_parser_t suunto_d9_parser_t;
struct suunto_d9_parser_t { struct suunto_d9_parser_t {
dc_parser_t base; dc_parser_t base;
unsigned int model; unsigned int model;
unsigned int serial;
// Cached fields. // Cached fields.
unsigned int cached; unsigned int cached;
unsigned int id; unsigned int id;
@ -233,7 +235,7 @@ suunto_d9_parser_cache (suunto_d9_parser_t *parser)
} }
dc_status_t dc_status_t
suunto_d9_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model) suunto_d9_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int model, unsigned int serial)
{ {
suunto_d9_parser_t *parser = NULL; suunto_d9_parser_t *parser = NULL;
@ -249,6 +251,7 @@ suunto_d9_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int
// Set the default values. // Set the default values.
parser->model = model; parser->model = model;
parser->serial = serial;
parser->cached = 0; parser->cached = 0;
parser->id = 0; parser->id = 0;
parser->mode = AIR; parser->mode = AIR;
@ -330,6 +333,7 @@ suunto_d9_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
#define BUFLEN 16
static dc_status_t static dc_status_t
suunto_d9_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) suunto_d9_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
@ -345,6 +349,9 @@ suunto_d9_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigne
return rc; return rc;
dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
dc_field_string_t *string = (dc_field_string_t *) value;
char buf[BUFLEN];
if (value) { if (value) {
switch (type) { switch (type) {
@ -392,6 +399,17 @@ suunto_d9_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigne
return DC_STATUS_DATAFORMAT; return DC_STATUS_DATAFORMAT;
} }
break; break;
case DC_FIELD_STRING:
switch (flags) {
case 0: /* serial */
string->desc = "Serial";
snprintf(buf, BUFLEN, "%08u", parser->serial);
break;
default:
return DC_STATUS_UNSUPPORTED;
}
string->value = strdup(buf);
break;
default: default:
return DC_STATUS_UNSUPPORTED; return DC_STATUS_UNSUPPORTED;
} }

View File

@ -21,8 +21,16 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h>
#include <ctype.h> #include <ctype.h>
#include <math.h> #include <math.h>
#include <stdarg.h>
/* Wow. MSC is truly crap */
#ifdef _MSC_VER
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#endif
#include "suunto_eonsteel.h" #include "suunto_eonsteel.h"
#include "context-private.h" #include "context-private.h"
@ -71,6 +79,7 @@ struct type_desc {
#define MAXTYPE 512 #define MAXTYPE 512
#define MAXGASES 16 #define MAXGASES 16
#define MAXSTRINGS 32
typedef struct suunto_eonsteel_parser_t { typedef struct suunto_eonsteel_parser_t {
dc_parser_t base; dc_parser_t base;
@ -89,7 +98,8 @@ typedef struct suunto_eonsteel_parser_t {
double lowsetpoint; double lowsetpoint;
double highsetpoint; double highsetpoint;
double customsetpoint; double customsetpoint;
dc_tankvolume_t tankinfo[MAXGASES]; dc_field_string_t strings[MAXSTRINGS];
dc_tankinfo_t tankinfo[MAXGASES];
double tanksize[MAXGASES]; double tanksize[MAXGASES];
double tankworkingpressure[MAXGASES]; double tankworkingpressure[MAXGASES];
} cache; } cache;
@ -97,11 +107,6 @@ typedef struct suunto_eonsteel_parser_t {
typedef int (*eon_data_cb_t)(unsigned short type, const struct type_desc *desc, const unsigned char *data, int len, void *user); typedef int (*eon_data_cb_t)(unsigned short type, const struct type_desc *desc, const unsigned char *data, int len, void *user);
typedef struct eon_event_t {
const char *name;
parser_sample_event_t type;
} eon_event_t;
static const struct { static const struct {
const char *name; const char *name;
enum eon_sample type; enum eon_sample type;
@ -167,16 +172,6 @@ static enum eon_sample lookup_descriptor_type(suunto_eonsteel_parser_t *eon, str
return ES_none; return ES_none;
} }
static parser_sample_event_t lookup_event(const char *name, const eon_event_t events[], size_t n)
{
for (size_t i = 0; i < n; ++i) {
if (!strcasecmp(name, events[i].name))
return events[i].type;
}
return SAMPLE_EVENT_NONE;
}
static const char *desc_type_name(enum eon_sample type) static const char *desc_type_name(enum eon_sample type)
{ {
int i; int i;
@ -681,26 +676,17 @@ static void sample_event_state_type(const struct type_desc *desc, struct sample_
static void sample_event_state_value(const struct type_desc *desc, struct sample_data *info, unsigned char value) static void sample_event_state_value(const struct type_desc *desc, struct sample_data *info, unsigned char value)
{ {
dc_sample_value_t sample = {0}; dc_sample_value_t sample = {0};
static const eon_event_t states[] = {
{"Wet Outside", SAMPLE_EVENT_NONE},
{"Below Wet Activation Depth", SAMPLE_EVENT_NONE},
{"Below Surface", SAMPLE_EVENT_NONE},
{"Dive Active", SAMPLE_EVENT_NONE},
{"Surface Calculation", SAMPLE_EVENT_NONE},
{"Tank pressure available", SAMPLE_EVENT_NONE},
{"Closed Circuit Mode", SAMPLE_EVENT_NONE},
};
const char *name; const char *name;
name = info->state_type; name = info->state_type;
if (!name) if (!name)
return; return;
sample.event.type = lookup_event(name, states, C_ARRAY_SIZE(states)); sample.event.type = SAMPLE_EVENT_STRING;
if (sample.event.type == SAMPLE_EVENT_NONE) sample.event.name = name;
return;
sample.event.flags = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END; sample.event.flags = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END;
sample.event.flags |= 1 << SAMPLE_FLAGS_SEVERITY_SHIFT;
if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata); if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata);
} }
@ -712,25 +698,6 @@ static void sample_event_notify_type(const struct type_desc *desc, struct sample
static void sample_event_notify_value(const struct type_desc *desc, struct sample_data *info, unsigned char value) static void sample_event_notify_value(const struct type_desc *desc, struct sample_data *info, unsigned char value)
{ {
static const eon_event_t notifications[] = {
{"NoFly Time", SAMPLE_EVENT_NONE},
{"Depth", SAMPLE_EVENT_NONE},
{"Surface Time", SAMPLE_EVENT_NONE},
{"Tissue Level", SAMPLE_EVENT_TISSUELEVEL},
{"Deco", SAMPLE_EVENT_NONE},
{"Deco Window", SAMPLE_EVENT_NONE},
{"Safety Stop Ahead", SAMPLE_EVENT_NONE},
{"Safety Stop", SAMPLE_EVENT_SAFETYSTOP},
{"Safety Stop Broken", SAMPLE_EVENT_CEILING_SAFETYSTOP},
{"Deep Stop Ahead", SAMPLE_EVENT_NONE},
{"Deep Stop", SAMPLE_EVENT_DEEPSTOP},
{"Dive Time", SAMPLE_EVENT_DIVETIME},
{"Gas Available", SAMPLE_EVENT_NONE},
{"SetPoint Switch", SAMPLE_EVENT_NONE},
{"Diluent Hypoxia", SAMPLE_EVENT_NONE},
{"Air Time", SAMPLE_EVENT_NONE},
{"Tank Pressure", SAMPLE_EVENT_NONE},
};
dc_sample_value_t sample = {0}; dc_sample_value_t sample = {0};
const char *name; const char *name;
@ -738,11 +705,11 @@ static void sample_event_notify_value(const struct type_desc *desc, struct sampl
if (!name) if (!name)
return; return;
sample.event.type = lookup_event(name, notifications, C_ARRAY_SIZE(notifications)); sample.event.type = SAMPLE_EVENT_STRING;
if (sample.event.type == SAMPLE_EVENT_NONE) sample.event.name = name;
return;
sample.event.flags = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END; sample.event.flags = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END;
sample.event.flags |= 2 << SAMPLE_FLAGS_SEVERITY_SHIFT;
if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata); if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata);
} }
@ -755,22 +722,6 @@ static void sample_event_warning_type(const struct type_desc *desc, struct sampl
static void sample_event_warning_value(const struct type_desc *desc, struct sample_data *info, unsigned char value) static void sample_event_warning_value(const struct type_desc *desc, struct sample_data *info, unsigned char value)
{ {
static const eon_event_t warnings[] = {
{"ICD Penalty", SAMPLE_EVENT_NONE},
{"Deep Stop Penalty", SAMPLE_EVENT_VIOLATION},
{"Mandatory Safety Stop", SAMPLE_EVENT_SAFETYSTOP_MANDATORY},
{"OTU250", SAMPLE_EVENT_NONE},
{"OTU300", SAMPLE_EVENT_NONE},
{"CNS80%", SAMPLE_EVENT_NONE},
{"CNS100%", SAMPLE_EVENT_NONE},
{"Max.Depth", SAMPLE_EVENT_MAXDEPTH},
{"Air Time", SAMPLE_EVENT_AIRTIME},
{"Tank Pressure", SAMPLE_EVENT_NONE},
{"Safety Stop Broken", SAMPLE_EVENT_CEILING_SAFETYSTOP},
{"Deep Stop Broken", SAMPLE_EVENT_CEILING_SAFETYSTOP},
{"Ceiling Broken", SAMPLE_EVENT_CEILING},
{"PO2 High", SAMPLE_EVENT_PO2},
};
dc_sample_value_t sample = {0}; dc_sample_value_t sample = {0};
const char *name; const char *name;
@ -778,11 +729,11 @@ static void sample_event_warning_value(const struct type_desc *desc, struct samp
if (!name) if (!name)
return; return;
sample.event.type = lookup_event(name, warnings, C_ARRAY_SIZE(warnings)); sample.event.type = SAMPLE_EVENT_STRING;
if (sample.event.type == SAMPLE_EVENT_NONE) sample.event.name = name;
return;
sample.event.flags = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END; sample.event.flags = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END;
sample.event.flags |= 3 << SAMPLE_FLAGS_SEVERITY_SHIFT;
if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata); if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata);
} }
@ -795,27 +746,18 @@ static void sample_event_alarm_type(const struct type_desc *desc, struct sample_
static void sample_event_alarm_value(const struct type_desc *desc, struct sample_data *info, unsigned char value) static void sample_event_alarm_value(const struct type_desc *desc, struct sample_data *info, unsigned char value)
{ {
static const eon_event_t alarms[] = {
{"Mandatory Safety Stop Broken", SAMPLE_EVENT_CEILING_SAFETYSTOP},
{"Ascent Speed", SAMPLE_EVENT_ASCENT},
{"Diluent Hyperoxia", SAMPLE_EVENT_NONE},
{"Violated Deep Stop", SAMPLE_EVENT_VIOLATION},
{"Ceiling Broken", SAMPLE_EVENT_CEILING},
{"PO2 High", SAMPLE_EVENT_PO2},
{"PO2 Low", SAMPLE_EVENT_PO2},
};
dc_sample_value_t sample = {0};
const char *name; const char *name;
dc_sample_value_t sample = {0};
name = info->alarm_type; name = info->alarm_type;
if (!name) if (!name)
return; return;
sample.event.type = lookup_event(name, alarms, C_ARRAY_SIZE(alarms)); sample.event.type = SAMPLE_EVENT_STRING;
if (sample.event.type == SAMPLE_EVENT_NONE) sample.event.name = name;
return;
sample.event.flags = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END; sample.event.flags = value ? SAMPLE_FLAGS_BEGIN : SAMPLE_FLAGS_END;
sample.event.flags |= 4 << SAMPLE_FLAGS_SEVERITY_SHIFT;
if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata); if (info->callback) info->callback(DC_SAMPLE_EVENT, sample, info->userdata);
} }
@ -1026,6 +968,19 @@ suunto_eonsteel_parser_samples_foreach(dc_parser_t *abstract, dc_sample_callback
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
static dc_status_t get_string_field(suunto_eonsteel_parser_t *eon, unsigned idx, dc_field_string_t *value)
{
if (idx < MAXSTRINGS) {
dc_field_string_t *res = eon->cache.strings+idx;
if (res->desc && res->value) {
*value = *res;
return DC_STATUS_SUCCESS;
}
}
return DC_STATUS_UNSUPPORTED;
}
// Ugly define thing makes the code much easier to read // Ugly define thing makes the code much easier to read
// I'd love to use __typeof__, but that's a gcc'ism // I'd love to use __typeof__, but that's a gcc'ism
#define field_value(p, set) \ #define field_value(p, set) \
@ -1094,11 +1049,13 @@ suunto_eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsi
* We need to have workpressure and a valid tank. In that case, * We need to have workpressure and a valid tank. In that case,
* a fractional tank size implies imperial. * a fractional tank size implies imperial.
*/ */
if (tank->workpressure && (tank->type == DC_TANKVOLUME_METRIC)) { if (tank->workpressure && (tank->type & DC_TANKINFO_METRIC)) {
if (fabs(tank->volume - rint(tank->volume)) > 0.001) if (fabs(tank->volume - rint(tank->volume)) > 0.001)
tank->type = DC_TANKVOLUME_IMPERIAL; tank->type += DC_TANKINFO_IMPERIAL - DC_TANKINFO_METRIC;
} }
break; break;
case DC_FIELD_STRING:
return get_string_field(eon, flags, (dc_field_string_t *)value);
default: default:
return DC_STATUS_UNSUPPORTED; return DC_STATUS_UNSUPPORTED;
} }
@ -1149,7 +1106,7 @@ static void set_depth_field(suunto_eonsteel_parser_t *eon, unsigned short d)
// "enum:0=Off,1=Primary,2=?,3=Diluent" // "enum:0=Off,1=Primary,2=?,3=Diluent"
// "enum:0=Off,1=Primary,3=Diluent,4=Oxygen" // "enum:0=Off,1=Primary,3=Diluent,4=Oxygen"
// //
// We turn that into the DC_TANKVOLUME data here, but // We turn that into the DC_TANKINFO data here, but
// initially consider all non-off tanks to me METRIC. // initially consider all non-off tanks to me METRIC.
// //
// We may later turn the METRIC tank size into IMPERIAL if we // We may later turn the METRIC tank size into IMPERIAL if we
@ -1157,7 +1114,7 @@ static void set_depth_field(suunto_eonsteel_parser_t *eon, unsigned short d)
static int add_gas_type(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, unsigned char type) static int add_gas_type(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, unsigned char type)
{ {
int idx = eon->cache.ngases; int idx = eon->cache.ngases;
dc_tankvolume_t tankinfo = DC_TANKVOLUME_METRIC; dc_tankinfo_t tankinfo = DC_TANKINFO_METRIC;
char *name; char *name;
if (idx >= MAXGASES) if (idx >= MAXGASES)
@ -1168,9 +1125,9 @@ static int add_gas_type(suunto_eonsteel_parser_t *eon, const struct type_desc *d
if (!name) if (!name)
DEBUG(eon->base.context, "Unable to look up gas type %u in %s", type, desc->format); DEBUG(eon->base.context, "Unable to look up gas type %u in %s", type, desc->format);
else if (!strcasecmp(name, "Diluent")) else if (!strcasecmp(name, "Diluent"))
; tankinfo |= DC_TANKINFO_CC_DILUENT;
else if (!strcasecmp(name, "Oxygen")) else if (!strcasecmp(name, "Oxygen"))
; tankinfo |= DC_TANKINFO_CC_O2;
else if (!strcasecmp(name, "None")) else if (!strcasecmp(name, "None"))
tankinfo = DC_TANKVOLUME_NONE; tankinfo = DC_TANKVOLUME_NONE;
else if (strcasecmp(name, "Primary")) else if (strcasecmp(name, "Primary"))
@ -1223,6 +1180,42 @@ static int add_gas_workpressure(suunto_eonsteel_parser_t *eon, float wp)
return 0; return 0;
} }
static int add_string(suunto_eonsteel_parser_t *eon, const char *desc, const char *value)
{
int i;
eon->cache.initialized |= 1 << DC_FIELD_STRING;
for (i = 0; i < MAXSTRINGS; i++) {
dc_field_string_t *str = eon->cache.strings+i;
if (str->desc)
continue;
str->desc = desc;
str->value = strdup(value);
break;
}
return 0;
}
static int add_string_fmt(suunto_eonsteel_parser_t *eon, const char *desc, const char *fmt, ...)
{
char buffer[256];
va_list ap;
/*
* We ignore the return value from vsnprintf, and we
* always NUL-terminate the destination buffer ourselves.
*
* That way we don't have to worry about random bad legacy
* implementations.
*/
va_start(ap, fmt);
buffer[sizeof(buffer)-1] = 0;
(void) vsnprintf(buffer, sizeof(buffer)-1, fmt, ap);
va_end(ap);
return add_string(eon, desc, buffer);
}
static float get_le32_float(const unsigned char *src) static float get_le32_float(const unsigned char *src)
{ {
union { union {
@ -1246,7 +1239,16 @@ static int traverse_device_fields(suunto_eonsteel_parser_t *eon, const struct ty
const unsigned char *data, int len) const unsigned char *data, int len)
{ {
const char *name = desc->desc + strlen("sml.DeviceLog.Device."); const char *name = desc->desc + strlen("sml.DeviceLog.Device.");
if (!strcmp(name, "SerialNumber"))
return add_string(eon, "Serial", data);
if (!strcmp(name, "Info.HW"))
return add_string(eon, "HW Version", data);
if (!strcmp(name, "Info.SW"))
return add_string(eon, "FW Version", data);
if (!strcmp(name, "Info.BatteryAtStart"))
return add_string(eon, "Battery at start", data);
if (!strcmp(name, "Info.BatteryAtEnd"))
return add_string(eon, "Battery at end", data);
return 0; return 0;
} }
@ -1277,12 +1279,30 @@ static int traverse_gas_fields(suunto_eonsteel_parser_t *eon, const struct type_
if (!strcmp(name, ".Gas.Helium")) if (!strcmp(name, ".Gas.Helium"))
return add_gas_he(eon, data[0]); return add_gas_he(eon, data[0]);
if (!strcmp(name, ".Gas.TransmitterID"))
return add_string(eon, "Transmitter ID", data);
if (!strcmp(name, ".Gas.TankSize")) if (!strcmp(name, ".Gas.TankSize"))
return add_gas_size(eon, get_le32_float(data)); return add_gas_size(eon, get_le32_float(data));
if (!strcmp(name, ".Gas.TankFillPressure")) if (!strcmp(name, ".Gas.TankFillPressure"))
return add_gas_workpressure(eon, get_le32_float(data)); return add_gas_workpressure(eon, get_le32_float(data));
// There is a bug with older transmitters, where the transmitter
// battery charge returns zero. Rather than returning that bogus
// data, just don't return any battery charge information at all.
//
// Make sure to add all non-battery-charge field checks above this
// test, so that it doesn't trigger for anything else.
if (!data[0])
return 0;
if (!strcmp(name, ".Gas.TransmitterStartBatteryCharge"))
return add_string_fmt(eon, "Transmitter Battery at start", "%d %%", data[0]);
if (!strcmp(name, ".Gas.TransmitterEndBatteryCharge"))
return add_string_fmt(eon, "Transmitter Battery at end", "%d %%", data[0]);
return 0; return 0;
} }
@ -1339,12 +1359,22 @@ static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct ty
return 0; return 0;
} }
if (!strcmp(name, "Algorithm"))
return add_string(eon, "Deco algorithm", data);
if (!strcmp(name, "DiveMode")) { if (!strcmp(name, "DiveMode")) {
if (!strncmp((const char *)data, "CCR", 3)) { if (!strncmp((const char *)data, "CCR", 3)) {
eon->cache.divemode = DC_DIVEMODE_CCR; eon->cache.divemode = DC_DIVEMODE_CCR;
eon->cache.initialized |= 1 << DC_FIELD_DIVEMODE; eon->cache.initialized |= 1 << DC_FIELD_DIVEMODE;
} }
return 0; return add_string(eon, "Dive Mode", data);
}
/* Signed byte of conservatism (-2 .. +2) */
if (!strcmp(name, "Conservatism")) {
int val = *(signed char *)data;
return add_string_fmt(eon, "Personal Adjustment", "P%d", val);
} }
if (!strcmp(name, "LowSetPoint")) { if (!strcmp(name, "LowSetPoint")) {
@ -1359,6 +1389,18 @@ static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct ty
return 0; return 0;
} }
// Time recoded in seconds.
// Let's just agree to ignore seconds
if (!strcmp(name, "DesaturationTime")) {
unsigned int time = array_uint32_le(data) / 60;
return add_string_fmt(eon, "Desaturation Time", "%d:%02d", time / 60, time % 60);
}
if (!strcmp(name, "SurfaceTime")) {
unsigned int time = array_uint32_le(data) / 60;
return add_string_fmt(eon, "Surface Time", "%d:%02d", time / 60, time % 60);
}
return 0; return 0;
} }
@ -1385,6 +1427,8 @@ static int traverse_header_fields(suunto_eonsteel_parser_t *eon, const struct ty
eon->cache.maxdepth = d; eon->cache.maxdepth = d;
return 0; return 0;
} }
if (!strcmp(name, "DateTime"))
return add_string(eon, "Dive ID", data);
return 0; return 0;
} }
@ -1428,6 +1472,8 @@ static int traverse_sample_fields(suunto_eonsteel_parser_t *eon, const struct ty
set_depth_field(eon, array_uint16_le(data)); set_depth_field(eon, array_uint16_le(data));
data += 2; data += 2;
continue; continue;
default:
break;
} }
break; break;
} }

109
src/usb_storage.c Normal file
View File

@ -0,0 +1,109 @@
/*
* Dummy "stream" operations for USB storage
*
* Copyright (C) 2018 Linus Torvalds
*
* 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
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "common-private.h"
#include "context-private.h"
#include "iostream-private.h"
#include "iterator-private.h"
#include "descriptor-private.h"
#include "timer.h"
// Fake "device" that just contains the directory name that
// you can read out of the iostream. All the actual IO is
// up to you.
typedef struct dc_usbstorage_t {
dc_iostream_t base;
char pathname[PATH_MAX];
} dc_usbstorage_t;
static dc_status_t
dc_usb_storage_read (dc_iostream_t *iostream, void *data, size_t size, size_t *actual);
static const dc_iostream_vtable_t dc_usbstorage_vtable = {
sizeof(dc_usbstorage_t),
NULL, /* set_timeout */
NULL, /* set_latency */
NULL, /* set_break */
NULL, /* set_dtr */
NULL, /* set_rts */
NULL, /* get_lines */
NULL, /* get_available */
NULL, /* configure */
dc_usb_storage_read, /* read */
NULL, /* write */
NULL, /* flush */
NULL, /* purge */
NULL, /* sleep */
NULL, /* close */
};
dc_status_t
dc_usb_storage_open (dc_iostream_t **out, dc_context_t *context, const char *name)
{
dc_usbstorage_t *device = NULL;
struct stat st;
if (out == NULL || name == NULL)
return DC_STATUS_INVALIDARGS;
INFO (context, "Open: name=%s", name);
if (stat(name, &st) < 0 || !S_ISDIR(st.st_mode))
return DC_STATUS_NODEVICE;
// Allocate memory.
device = (dc_usbstorage_t *) dc_iostream_allocate (context, &dc_usbstorage_vtable, DC_TRANSPORT_USBSTORAGE);
if (device == NULL) {
SYSERROR (context, ENOMEM);
return DC_STATUS_NOMEMORY;
}
strncpy(device->pathname, name, PATH_MAX);
device->pathname[PATH_MAX-1] = 0;
*out = (dc_iostream_t *) device;
return DC_STATUS_SUCCESS;
}
static dc_status_t
dc_usb_storage_read (dc_iostream_t *iostream, void *data, size_t size, size_t *actual)
{
dc_usbstorage_t *device = (dc_usbstorage_t *) iostream;
size_t len = strlen(device->pathname);
if (size <= len)
return DC_STATUS_IO;
memcpy(data, device->pathname, len+1);
if (actual)
*actual = len;
return DC_STATUS_SUCCESS;
}