Compare commits

...

142 Commits

Author SHA1 Message Date
Linus Torvalds
9063bb9e00 shearwater: remove stale remnants of manual string interface code
Minimize unnecessary differences with Jef's upstream code.

The shearwater parser uses the generif field-cache code, and doesn't
need to have the snprintf workarounds for MSC, or include stdio.h etc.
The field-cache code takes care of this.

[ To be precise: the field-cache code _should_ have taken care of this,
  but didn't: it used 'memcpy()' in a macro, but didn't include the
  required header. Removing it from the shearwater code exposed this
  issue with the field-cache header, and this fixes that ]

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-05-07 15:02:00 -07:00
Linus Torvalds
5c64573c83 shearwater: remove unused 'shearwater_common_command()' function
Dirk added this helper in commit 9379004b2df5 ("Shearwater: add helper
to send bytes to the dive computer"), and it was used for a short while
by commit 437cc3e0cc9f ("Shearwater: try to gracefully shut down the
Bluetooth connection").

But then the use was reverted in commit 8120b11258d8, and this helper
function is now stale and unused, and an unnecessary difference to Jef's
upstream code base.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-05-07 14:50:19 -07:00
Linus Torvalds
b3b4eb91a2 oceanic: avoid unnecessary difference with Jef's upstream version
We ended up merging Janic McLaughlin's fix for dive truncation issues
into the subsurface branch, and then Jef ended up taking it too, but
making some changes while at it.

See commit 17ff3e066769 ("Oceanic: fix up dive truncation issues, update
memory layout") vs commit 04c367fd0645 ("Oceanic: fix up dive truncation
issues").

The two versions end up doing the same thing, this takes Jef's version
to minimize differences to upstream.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-05-07 14:29:17 -07:00
Linus Torvalds
bec9ca9e3c OSTC parser whitespace cleanups
This is pure whitespace cleanup, where the hw_ostc_parser.c file had a
number of lines with eight spaces instead of tabs.  They were right next
to lines with tabs, so it really made no sense.

I only noticed because some of it was arbitrary difference to Jef's
upstream code, and it stood out like a sore thumb when trying to
re-generate our local changes wrt upstream.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-05-07 14:08:02 -07:00
Linus Torvalds
c889348583 Remove stale workarounds for MVC from Suunto EON Steel parser
They were for the string field handling, but we now use the generic code
that has those workarounds there. No need for them in the Suunto downloader.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-05-07 13:23:19 -07:00
Linus Torvalds
761089c8f2 Remove stale declaration for dc_iostream_get_name()
This function no longer exists, it was subsumed by the new
DC_IOCTL_BLE_GET_NAME ioctl instead.  The declaration for it had just
not been removed when the definition went away.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-05-07 13:12:21 -07:00
Linus Torvalds
8b53c70950 Merge branch 'master' of git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Pull updatream updates from Jef Driesen:

 - work around Pelagic BLE oddity (Oceanic Pro Plus X and Aqualung i770R)

 - OSTC3 firmware update improvements

* 'master' of git://github.com/libdivecomputer/libdivecomputer:
  Use a more robust command to write flash memory
  Read and cache the firmware version information
  Add an extra delay after writing to the flash memory
  Add an extra delay after erasing a flash memory page
  Send the service init command one byte at a time
  Fix some typos in the comments
  Ignore excess bytes in the BLE version packet
2020-05-07 11:34:33 -07:00
Linus Torvalds
7882ba423c iostream: protect write side against bad custom IO interfaces too
This is the same as the previous commit, just for the write side error
handling.

If we have a bad custom IO low-level driver that returns
DC_STATUS_SUCCESS with zero bytes written, and we have a write() user
that isn't ready for partial writes, we just consider that an IO error.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-03-13 11:48:35 -07:00
Linus Torvalds
b77e7b6b21 iostream: protect against bad custom interfaces
Jef Driesen points out that the subsurface custom iostream read function
for legacy bluetooth can return a "success" with zero bytes actually
read.  That makes the loop we have to fill up the whole buffer very unhappy.

That's definitely a bug on the subsurface side, but let's also be
defensive about it in the iostream layer.

Note that this happens only when the reader is not able to handle
partial packets (doesn't pass in an "actual" pointer), and we haven't
gotten a full packet yet.

So we'll turn it into a DC_STATUS_TIMEOUT, which is debatable, but the
real fix is for subsurface to not do this in its rfcomm iostream
implementation, and instead return the proper error code.

Reported-by: linuxcrash <albin@mrty.ch>
Debugged-by: Jef Driesen <jef@libdivecomputer.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-03-13 09:34:00 -07:00
Linus Torvalds
0714e327b7 Merge branch 'master' of git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Pull upstream libdivecomputer updates from Jef Driesen:

 - fix lack of "end of deco" on DiveSystem iDive computers by reporting
   long NDL values

 - clean up handling of Oceanic empty logbugger

 - fix BLE download for Oceanic Pro Plus X that doesn't like the serial
   number handshake.

* 'master' of git://github.com/libdivecomputer/libdivecomputer:
  Pass infinite NDL values to the application
  Clear the buffer if no dives are present
  Report an error for invalid ringbuffer pointers
  Improve the empty logbook ringbuffer detection
  Skip the BLE handshake for the Pro Plus X
2020-02-27 16:25:31 -08:00
Linus Torvalds
4e809aefd8 Cressi: mark Cartesio and Goa as supporting BLE
They don't actually have any native BLE capabilities, but there's a "HAL
9000" docking station with bluetooth capability (and a USB cable for
wired connectivity).

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-02-16 08:50:38 -08:00
Linus Torvalds
ebbb911427 Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Merge upstream changes from Jef Driesen:

 - fix Oceanic VT Pro date parsing

 - add Sherwood Wisdom 4 and Scubapro Aladin A1 IDs from Janice McLaughlin

 - fix Cressi gasmix index and depth parsing (the gasmix index bit had
   been misparsed as very deep depth)

* 'master' of git://github.com/libdivecomputer/libdivecomputer:
  Implement the gas mix sample
  Limit the depth value to 11 bits
  Add support for the Scubapro A1
  Add support for the Sherwood Wisdom 4
  Fix the vtpro datetime parsing
2020-02-13 13:03:21 -08:00
Linus Torvalds
ae36b89118 Garmin Descent Mk1: convert to generic field cache
This converts most of the cached data to the field cache, leaving some
garmin-specific fields alone (but removing them from the "cache"
structure in the process).

This means that all of the users of the string fields have been
converted, and we don't have the duplicate string interfaces any more.

Some of the other "dc_field_cache_t" fields could easily be used by
other backends (including some of the partial conversions like the
Shearwater one, but also backends that don't do any string fields at
all), but this conversion was a fairly minimal "set up the
infrastructure, and convert the easy parts".

Considering that the string field stuff still isn't upstream, I'm not
going to push any other backends to do more conversions.

On the whole, the string code de-duplication was a fairly nice cleanup:

 8 files changed, 340 insertions(+), 484 deletions(-)

and perhaps more importantly will make it easier to do new backends in
the future with smaller diffs against upstream.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-02-13 12:53:33 -08:00
Linus Torvalds
752bfaab3d Shearwater: convert predator parser to use generic field interface
This only converts the string fields and the divemode field.

Why? Those were the only ones that were in the proper libdivecomputer
type format: a lot of the other fields are kept in the Shearwater
internal format, and then converted at get_field() time.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-02-13 12:52:11 -08:00
Linus Torvalds
0638482209 Suunto EON Steel: use the generic field cache infrastructure
The only real changes here are some interface updates: several traversal
routines are changed to return 'dc_status_t' which makes more sense in
the libdivecomputer world anyway, and that matches the generic field
cache string routines.

The other changes are also a direct result of the generic code using
slightly different names for the cache fields: they use the same names
as the DC_FIELD_xyz enum to fetch them.

This is not a full conversion to the generic wrappers, and the EON Steel
parser accesses the cache structure directly in several places because
of how the code was written.  That's fine.  Not pretty, but this is all
totally internal to libdivecomputer.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-02-13 12:52:11 -08:00
Linus Torvalds
842592fb55 Extend field-cache infrastructure to support the Suunto EON Steel
This adds a few misc fields that the EON Steel wants, and changes the
string insertion interface to return a 'dc_status_t', which will be used
by that back-end.

The existing deepblu users don't care.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-02-13 12:52:11 -08:00
Linus Torvalds
f73b6836ad Begin making the string interface and "field cache" generic
This starts with the deepblu code, which is the last one I touched.

The next step is to try to make some of the other backends use this too,
and see where the interface isn't quite generic enough.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-02-13 12:51:57 -08:00
Linus Torvalds
820a797c67 Deepblu Cosmiq+: remove debug error statements
I had sprinkled "ERROR()" statements around in various callchains during
early Deepblu development, and not removed all of them.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-01-28 09:17:14 -08:00
Linus Torvalds
f2f775b9aa Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Merge Jef's upstream changes:

 - some stream IO abstraction updates: poll() support, but also a new
   ioctl() interface to query the BLE name of the stream instead of our
   own 'get_name()' function.

   This will require corresponding changes on the subsurface side.

 - Jef merged the Oceanic BLE support from me, with changes, and some
   general atom2 backend cleanups.

 - misc small fixups like the 3s Mares BLE timeout we already had.

* git://github.com/libdivecomputer/libdivecomputer:
  Install the ioctl header file
  Advertise the BLE support in the device descriptors
  Fix the BLE device detection for the i770R and Pro Plus X
  Implement the BLE handshaking
  Implement the BLE packet sending and receiving
  Read the entire data packet in a single operation
  Remove the trailing zero byte from all commands
  Fix a bug in the ACK/NAK handling
  Remove an unnecessary function
  Add an ioctl to retrieve the remote device name
  Re-implement the set_latency function as an ioctl
  Add an ioctl function to the I/O interface
  Integrate the new poll function
  Add a poll function to the I/O interface
  Add support for the Oceanic Veo 4.0
  Increase the timeout to 3 seconds
2020-01-26 11:25:54 -08:00
Linus Torvalds
4eb34b1466 Add 'examples/dctool' to git-ignored files
I'm not sure why it wasn't there originally, but it definitely should
be on that list of ignored files, to keep 'git status' happy.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-12-28 12:01:34 -08:00
Linus Torvalds
60c98fd75b Add back Mares BlueLink Pro bluetooth support tweaks
The Mares BlueLink Pro BLE dongle is the beast from hell, and introduces
lots of extra slowdowns into the Mares communication protocol.

In particular, it turns out that we really can't send the command bytes,
then wait for the ACK byte, and then send the command argument data as a
separate packet.  Because of the delays that the dongle adds, the dive
computer will have given up on the command arguments by the time it sees
them.

At the same time we don't want to always pass the command and arguments
as one single packet in all situations, because at least the Mares
Matrix really seems to want that "wait for ACK before sending
arguments".  See commit 59bfb0f3189b ("Add support for the Mares
Matrix") for details.

So introduce a new "splitcommand" flag, which is set by default, but
gets disabled for the BLE transport case.

Also, because bluetooth is slow, we don't want to ask for big packets of
data.  It seems to cause a buffer overflow on the BlueLink Pro when the
serial data from the dive computer arrives faster than the bluetooth
data can be sent to the downloading side.

So when using the BLE transport, we also limit the packet size to 128
bytes in addition to disabling the command splitting.

With this, I can download hundreds of kB of data from the Mares Quad Air
successfully over BLE.  It's *slow*, but it works.

This is a re-application of commit 5be8c17ea1 ("Mares bluetooth support
tweaks"), with small changes for how the libdivecomputer IO interfaces
have changed in the meantime.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-12-28 11:48:47 -08:00
Linus Torvalds
3c25c78bae Mares: increase timeout to 3 seconds
The Mares Bluetooth dongle is some seriously messed up stuff, and takes
forever to answer anything.  I'm not sure what we do wrong, because the
Mares mobile App seems to be able to work with it without the excessive
delays, but it is really incredibly slow when we talk to it.

I suspect the dongle has has some "wait until buffer is half full"
timeout, and it then triggers for every command and short reply in both
directions, and there's likely some way to flush it, but I didn't see
anything back when I had one for testing.

Anyway, as a result, one second is just about the latency that the
dongle itself adds regardless of anything else, so when the dive
computer itself needs a timeout to think about things, the overall
timeout needs to be noticeably more than one second.

Three seconds seems to be a somewhat reasonable compromise, and we do
have documentation for it being the right value:

 "Then shalt thou count to three, no more, no less. Three shall be the
  number thou shalt count, and the number of the counting shall be
  three. Four shalt thou not count, neither count thou two, excepting
  that thou then proceed to three. Five is right out."

because we do have strong reasons to believe that the Mares Bluetooth
dongle is a beast from hell.  Everybody who has ecver met it has
certainly gone "Arrrghhh" at some point.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-12-28 11:35:58 -08:00
Linus Torvalds
778adb37cc Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Merge with upstream Jef.

This is mainly just the (already merged as a separate patch) support for
th eOceanic Pro Plus 4, Jef committed the identical patch but without
marking it as BLE-capable (which is kind of pointless, since it doesn't
seem to work over anything _but_ BLE).

And a couple of trivial fixlets.

* git://github.com/libdivecomputer/libdivecomputer:
  Fix the Aeris Manta memory layout
  Add support for the Oceanic Pro Plus 4
  Strip the source directory from file names
2019-12-28 11:32:25 -08:00
Jef Driesen
8711371728 Add initial support for the Oceanic Pro Plus 4
This is _partial_ support for the Oceanic Pro Plus 4 from Jef, based on
a partial dump by John Clark. Quoting Jef in the email thread:

 "I'm not sure why downloading the memory dump fails. All 4 attempts
  fail around the same memory address (0x3140), but not exactly the
  same. Strange, but the good news that there is already enough
  information to figure some things out.

  Linus and Dirk: Attached is a patch with the things I figured out so
  far. Can you make a build for John to try?

  Most of the info looks reasonable, but there are probably still a few
  things missing or wrong. I don't have any ground truth data to compare
  against for things like the temperature sign"

This is that patch for testing.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-12-21 07:31:43 -08:00
Linus Torvalds
5290984316 Merge branch 'master' of git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Merge upstream libdivecomputer updates from Jef.

Misc small updates all over, the biggest thing (code wise) is probably
the Ratio firmware update support.

* 'master' of git://github.com/libdivecomputer/libdivecomputer:
  Fix the Oceanic Geo 4.0 memory layout
  Ignore all empty logbook entries
  Add a workaround for the hwOS ppO2 firmware bug
  Use macros to encode the firmware version
  Use symbolic constants for the sample types
  Remove the obsolete hwos parameter
  Limit the tank pressure workaround to hwOS devices
  Fix the OSTC tank pressure decoding
  Fix the Scubapro G2 HUD udev rule
  Add the Mares Genius to the bluetooth filter
  Add firmware upgrade support for the Ratio computers
2019-12-21 07:27:54 -08:00
Linus Torvalds
426a39fc73 Merge upstream git://github.com/libdivecomputer/libdivecomputer
Merge misc fixes from Jef's upstream.

This fixes the incorrect and partial Oceanic Geo 4.0 support from commit
e38406b353bb ("Start adding IDs for the Oceanic Geo 4.0"), where I was
assuming it looked like the I770R.

* https://github.com/libdivecomputer/libdivecomputer:
  Add support for the Oceanic Geo 4.0
  Fix a buffer overflow
2019-10-24 05:55:35 -04:00
Linus Torvalds
d3ccb88a44 Merge git://github.com/libdivecomputer/libdivecomputer.git into Subsurface-NG
Merge upstream updates from Jef Driesen:

 - clean up Shearwater tank pressure handling

 - minor fixlets

The Shearwater pressure sensor changes by Jef means that I also changed
how we handle the battery level for the pressure sensors, and integrated
it with the tank handling.

* jef/master:
  Improve the support for multiple tank transmitters
  Extract the log version immediately
  Use a struct for the gasmix data
  Use a prefix match for the Suunto bluetooth name
  Update the Shearwater Nerd bluetooth names
  Check condition before entering the loop
2019-10-13 11:19:08 -07:00
Linus Torvalds
ce6d9896a7 Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Merge upstream libdivecomputer updates from Jef:

 - Jef took my i200C support patch, so merge that up (only difference
   was that we mark it as BLE-capable)

 - support for multiple cylinders and transmitters for the Ratio iDive
   dive computers

 - Fix Mares BLE packet cache missed invalidation

* 'master' of git://github.com/libdivecomputer/libdivecomputer:
  Discard the cached BLE packet
  Add support for Aqualung i200c
  Take the tank transmitter flags into account
  Add support for multiple tank transmitters
  Use a struct for the gasmix and tank data
2019-09-07 09:40:10 -07:00
Linus Torvalds
2d40cf46b1 Deepblu Cosmiq+: flesh out some of the comments and fingerprint size
I've tried to find where the firmware version and serial number are, and
have failed miserably.  Some of the commands I have sent instead cleared
the memory of the dive computer.  Whee.

But let's document the responses to the commands anyway, and flesh out
the fingerprinting code (which is useless without a device ID, which we
do not currently have).

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-08-27 13:45:34 -07:00
Linus Torvalds
d532be187a Deepblu Cosmiq+: stop downloading once we've seen the dives already
.. and also support cancellation of dive downloading in the middle.

The Cosmiq+ doesn't remember all that many dives, but BLE is slow and
there's no point in downloading more than necessary.

I'll look at fingerprinting next, so that we can avoid downloading the
profile data if we have already seen the header.  That's a further small
optimization.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-08-27 12:19:32 -07:00
Linus Torvalds
df7aeeef01 Deepblu Cosmiq+: fix oxygen percentage reporting
When I added more parsing of the dive data of the Cosmiq+ in commit
4dff291a1a53 ("Deepblu Cosmiq+: fill in some parsing details") I got the
gasmix units completely wrong and clearly never tested it.

The DC_FIELD_GASMIX reporting uses floating point percentages, not
integer percentages, and instead of reporting 21% as 0.21, we used to
report it as 21.0.  It all looked fine in my profiles, because I'd only
tested simulated air dives, and subsurface defaults to air even if
somebody reports crazy impossible gases.

Easy enough to fix, and now actually tested by doing a simulated nitrox
dive.

Reported-by: Michael Werle <mwerle@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-08-27 11:58:08 -07:00
Linus Torvalds
aab3d7a68e Add support for Aqualung i200c
It's exactly the same as the regular i200, but has a new version number
and string.

Tested-by: Tiago Thedim Dias <tiagotsoc@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-08-24 13:39:01 -07:00
Linus Torvalds
32e6ae4efa Deepblu Cosmiq+: fix missing prototype
Apparently I never added the new deepblu.h header file to the parser,
and never noticed.

But then Travis complains on the iOS build. Good catch, Travis.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-08-19 12:39:08 -07:00
Linus Torvalds
ccef003d47 Deepblu Cosmiq+: add progress reporting for downloading
Note that the fingerprinting code hasn't been connected yet (I'm n ot
entirely sure what I should compare - the whole 36-byte dive header?),
and it currently always downloads all dives on the Cosmiq+.

Not that that much matters, since there aren't all that many dives that
fit on it, but it's worth pointing out the current weaknesses of the
code.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-08-18 14:29:02 -07:00
Linus Torvalds
4dff291a1a Deepblu Cosmiq+: fill in some parsing details
This adds support for the different modes (scuba/gauge/freedive) and
teaches the parser to set the right sample interval and get the dive
time right.

In freedive mode, the sample interval is 1s, and the dive time is in
seconds too.  In the other modes, the sample interval is 20s, and the
divetime is in minutes.

Also set the right gas for scuba.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-08-18 13:50:50 -07:00
Linus Torvalds
eacfe4011a Deepblu Cosmiq+: add basic dive information parsing
This at least approximates downloading a dive from the Deepblu Cosmiq+,
and gives reasonable profiles for my test-dives (in a dive computer
chamber, not real dives).

I'm sure there's a lot to be improved here, and this literally only gets
depth and water temperature, but that seems to be what the Cosmiq+ gives
us.

Lots of credit to Michael Werle for bluetooth packet captures, and some basic analysis of the protocol.

Packet-logging-by: Michael Werle <mwerle@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-08-17 15:06:16 -07:00
Linus Torvalds
2efa83eeee Deepblu Cosmiq+: start downloading dive data
This actually seems to download all the data.  It just doesn't parse any
of it yet, so you get just empty dives.  But the basic packet and data
transfer seems to work.

Now I need to fill in the details on the actual dive parsing side, and I
should see a profile.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-08-17 11:53:25 -07:00
Linus Torvalds
4ce0ae9e4f Deepblu: add basic send/receive functionality
This was tested with a command to set the time and date, but only the
actual IO parts are here.

The packet format is fairly simple, even if it's not exactly clear why
everything is HEX-encoded.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-08-16 14:09:28 -07:00
Linus Torvalds
9812bf0828 Deepblu Cosmiq+: Add skeleton code
This does nothing at all, but it adds all the core skeleton
infrastructure for the Deepblu Cosmiq+ dive computer.

Let's see if I can make sense of things and make it download anything.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-08-15 14:46:38 -07:00
Linus Torvalds
ffbb472975 Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Merge upstream libdivecomputer updates from Jef:

 - Add support for the Aqualung i550C

 - Update Ratio iX3M GPS naming and note that they support rfcomm

 - misc cleanups

* 'master' of git://github.com/libdivecomputer/libdivecomputer:
  Add support for the Aqualung i550C
  Enable bluetooth support for the Ratio iX3M GPS
  Update the naming of the Ratio iX3M GPS range
  Mark the string tables as constant
  Refactor the filter functions
2019-07-14 17:34:38 -07:00
Linus Torvalds
1bbd386959 Merge git://github.com/libdivecomputer/libdivecomputer
Merge upstream libdivecomputer updates from Jef:

 - Support the new versions of the Mares Genius with more memory

 - misc small fixes (Pelagic/atom2 tank pressure fix, proper error
   codes, license info for AES code)

* git://github.com/libdivecomputer/libdivecomputer:
  Fix the tank pressure reporting
  Return the correct error code
  Add support for the Mares Genius
  Simplify the detection of air integrated models
  Refactor the gas mix and tank parsing
  Refactor the date/time parsing
  Replace the header offset with the header size
  Use symbolic constants for the commands
  Add license information to the AES code
  Update the udev rules
2019-07-04 10:50:17 +09:00
Linus Torvalds
b9d72607a8 Garmin Descent Mk1: add more informational debug output for file accesses
It turns out that it's fairly common that people pass in the wrong
directory for the Garmin downloader, and we then just silently fail to
parse any dives because we don't find anything.  The resulting logs
don't make it obvious what went wrong.

So add some informational messages about what directory we actually
tried to open, and what files we've found (and why we possibly ignored
them).

We already had this for some cases (like "This doesn't look lik ea dive
file"), but not for the more fundamental issues of not finding files to
begin with.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-05-31 13:17:48 -07:00
Linus Torvalds
e38406b353 Start adding IDs for the Oceanic Geo 4.0
There's a new dive computer in town: the Oceanic Geo 4.0.  It looks like
it should support BLE, and is probably fairly similar to the Pro Plus X.
Or so this initial support trial just assumes.

I don't have the version string for this device yet, so that hasn't been
added at all.  A full dump is required, the initial report only (almost
accidentally) gave the model numbers thanks to the BLE scan data.

Reported-by: George Rocks <jrroques2004@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-05-31 10:03:06 -07:00
Linus Torvalds
8fb2b75c25 Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Merge Jef's upstream updates.

Trivial conflicts just because of whitespace differences and a comment
difference in our Suunto D5 support changes.

* git://github.com/libdivecomputer/libdivecomputer:
  Add support for the Suunto D5
  Add support for the Tusa Talis
  Add the G2 HUD bluetooth device name
  Detect Mares Quad with more flash memory
  Fix the limit for an invalid sample temperature
  Fix a buffer overflow
2019-05-31 09:49:17 -07:00
Linus Torvalds
1194585595 Add support for Suunto D5 dive computer
It looks just like a small-form-factor EON Steel/Core, just with new
device IDs.

I haven't been able to pair over BLE yet, but that may be the usual
"Suunto wants to do a private bonded pairing" thing that makes it a pain
on the desktop.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-05-16 14:42:22 -07:00
Linus Torvalds
92cf2f8fa1 Suunto EON Steel/Core: make sure to properly sort the dive list
I incorrectly thought it was already sorted in the directory listing,
but once the dive list start overflowing, it looks like the ordering
goes away.

So sort the dives explicitly by date, rather than depend on any existing
ordering when reading the list of dives.

Reported-by: PM <boesch76@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-05-05 14:19:43 -07:00
Linus Torvalds
e650acc052 Garmin: be more permissive about the activity type
We used to require that we have one of the documented dive types in the
'sub_sport' type field.  But apparently Garmin added a new type number
for CCR diving, so CCR dives weren't recognized at all.

Add the new CCR case, but also say that if we have seen a DIVE_SUMMARY
record with average depth information, we'll just assume it's a dive
even for unrecognized sub_sport numbers.

Reported-by: Thomas Jacob <opiffe@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-05-05 10:00:27 -07:00
Linus Torvalds
2971cc20ff Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Pull upstream updates from Jef Driesen.

This adds ID entries for the Scubapro G2 HUD (but no GPS parsing support
yet) and the Aladin H Matric.

Also fix ndl/deco parsing for for Aqualung i450T.

* git://github.com/libdivecomputer/libdivecomputer:
  Fix the ndl/deco sample for the Aqualung i450T
  Add support for the Scubapro G2 HUD
  Add support for the Scubapro Aladin H Matrix
2019-04-24 09:04:42 -07:00
Linus Torvalds
c7112237b3 Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Merge upstream updates from Jef:

 - better Mares Bluelink Pro downloading

 - Suunto DX CCR and gas mix fixes

* 'master' of git://github.com/libdivecomputer/libdivecomputer:
  Add support for the Mares Bluelink Pro interface
  Check the correct vtable pointer
  Report the setpoint data
  Take the CCR diluents into account
  Implement the initial gas mix
  Get the gas mix index directly from the event data
2019-04-15 08:53:33 -07:00
Dirk Hohndel
e4698c4844 Shearwater Teric: add support for the TAG INFO_EVENT
This logs every INFO_EVENT record and provides a SAMPLE_EVENT_BOOKMARK event for
the only INFO_EVENT we can parse so far, the TAG event. Three bits in the flag
value in that event structure are now used to hold the tag type, and if a
non-zero type has been set, then the value is the heading in degress.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2019-03-16 12:23:28 -07:00
Linus Torvalds
d0ec5cf760 Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Merge with upstream from Jef:

 - ignore empty pressure samples from OSTC

 - skip empty Oceanic logbook entries

 - update Ratio parsing

* 'master' of git://github.com/libdivecomputer/libdivecomputer:
  Ignore zero tank pressure values
  Add filters for BLE communication
  Skip empty logbook entries
  Add clock synchronization support
  Use symbolic constants for the commands
  Don't pass a NULL pointer to memcpy
  Update the list of the Ratio dive computers
2019-03-12 17:00:19 -07:00
Linus Torvalds
1953d7a5a1 Shearwater: fix (again) per-cell ppO2 reporting
This re-applied commit 902dbf4d6d24 ("shearwater: Fallback to
average/voted ppo2") which got lost in the last merge with upstream when
I synced with Jef's rewrite of the PNF parser.

Reported-by: Martin Long <martin@longhome.co.uk>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-03-12 16:55:25 -07:00
Linus Torvalds
072bef1666 shearwater: properly initialize the string caches
The merge with Jef's upstream ended up taking a lot of Jef's changes to
how the Shearwater PNF parsing was done, and in the process inadvertdly
lost some of the code from our side of the branch that Jef hadn't taken.

In particular, the initialization of the string cache, and the
logversion string.

Put them back.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-01-23 13:28:39 +13:00
Dirk Hohndel
391c4095f2 Shearwater parser: add new harware model nr for Teric
As of firmware 11 at least my Teric identifies as 0x1F0A.
Also, just like libdivecomputer upstream, don't assume that an unknown
model is a Petrel - that was a stupid thing to do and caused downloads
with the Teric to break.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-01-23 09:08:10 +13:00
Linus Torvalds
1d09635a58 Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Update with Jef's upstream:

 - add support for Cressi Goa and Cartesio

 - update the Shearwater PNF parser to Jef's version

 - misc minor fixes

* git://github.com/libdivecomputer/libdivecomputer:
  Use the timezone setting of the dive computer
  Add support for the Cressi Goa and Cartesio
  Add an extra parameter for the initial CRC value
  Add support for the Ratio iDive Color series
  Shearwater Petrel Native Format parsing
  Shearwater: detect which logbook format is support
  Shearwater: add Teric to list of supported dive computers
  Shearwater: skip deleted dives
  Fix a potential buffer overflow
2019-01-22 12:15:37 +13:00
Linus Torvalds
e4c96e93ca Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Merge with upstream from Jef.

Small trivial fixlets.

* 'master' of git://github.com/libdivecomputer/libdivecomputer:
  Fix the Cobalt 2 memory size
  Use the travis homebrew plugin to install packages
  Increase the internal log buffer
  Fix undefined behaviour in left shifts
2018-12-17 09:32:47 -08:00
Linus Torvalds
33253b2f8c Fix OCEANIC_COMMON_MATCH pattern matching for BLE versions
It seems that the BLE communication protocol is somewhat different from
the serial one in the version string: while the serial version tends to
show the memory size, the BLE version string has some other numeric
pattern.

We don't have enough information to guess what it is, although normally
the BLE pattern is just "0001" instead of some memory size string.  But
I've seen the i770R once report 0090 instead.  Some status code?

Regardless, make the Pro Plus X and the I300C pattern simply ignore the
last four digits, since they clearly vary, and those two computers
support BLE.

The i770R pattern already did that, since I saw it myself.  The Pro Plus
X I have a communication trace from Brett Woods, and the i300C I just
assume follows the same pattern.

Reported-by: Brett Woods <brett@jeepswag.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-12-17 09:24:24 -08:00
Linus Torvalds
b082a96ebb Enable BLE support for the Oceanic Pro Plus X
It really looks (very superficially) like the Oceanic Pro Plus X might
act exactly the same as the Aqualung i770R over bluetooth: it has the
exact same bluetooth name pattern ("ER001299", where "ER" is the ASCII
represetnation of the model number (0x4552) and the 001299 looks like
the serial number that we then use for "authenticating" with the device.

I haven't actually tested this at all, but Brett Woods sent the
bluetooth scan information, and it looks promising.  So let's just test
it.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-12-14 13:45:17 -08:00
Linus Torvalds
e34e211c3e Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Sync up with Jef's upstream changes.

About half of it was stuff we'd already done: i770R support (together
with the Oceanic dive truncation) and the change to the Scubapro G2 BLE
protocol.

And once again, Jef has taken the work of others (me and Janice) and
made pointless changes to it just to make merging harder.

And once again, I tried to match up with upstream as much as possible to
make future merges easier.  But it annoys me.

This *may* also get the Aqualung i300C working over bluetooth.  I have
no real way to test, but the basic parsing support is there thanks to
Jef, and Janice implied that the BLE handshaking is the same as the
i770R.

* 'master' of git://github.com/libdivecomputer/libdivecomputer:
  Add support for the Aqualung i300C
  Add support for the Aqualung i770R
  Fix the Pro Plus X gas mixes
  Oceanic: fix up dive truncation issues
  Scubapro G2: update BLE downloading for new 1.4 firmware version
  Add a workaround for invalid ringbuffer begin pointers
2018-12-02 11:52:06 -08:00
Linus Torvalds
abde311d3a Merge https://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Pull upstream updates from Jef Driesen.

This is mainly the Pelagic (Oceanic, Sherwood, Aeris, Aqualung etc)
download interface reset sequence.

* https://github.com/libdivecomputer/libdivecomputer:
  Fix the RTS signal handling for Pelagic interface
  Fix a memory leak in the error handling
2018-11-14 10:55:31 -06:00
Linus Torvalds
f0fe141373 Scubapro G2: update BLE downloading for new 1.4 firmware version
The packetization format for the BLE communication used to be that the
first byte of the BLE GATT payload was the size of the payload (1-19
bytes).

That seems to no longer be true as of fw version 1.4.  It is still the
size of the payload for the simple small reply packets, but for the long
data stream it ends up being an odd sequence of 13 different values that
are almost - but not quite - 19 apart.

Whatever.  Modify our strict "length byte must make sense" rule to be
more of a guidline than a hard rule.  This makes the download succeed
again.

Very weird.

Reported-by: Adric Norris <landstander668@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-10-08 12:09:50 -07:00
Linus Torvalds
e61b7c64f9 Aqualung i770R (and i300C): do proper BLE handshake packet
The "I have NO IDEA" 0xE5 command seems to be a handshake packet with a
passphrase based on the serial number of the device.

Sadly, we can't actually read the serial number from the device until
after this handshake as successfully completed, which makes it a bit of
a chicken-and-egg problem from a communication standpoint.

However, the serial number is also exposed in the bluetooth name that
the device advertizes, which is the reason for the newly added
"dc_iostream_get_name()" function - so that whoever set up the IO for us
can give us the name.

Annoying, yes, but less annoying than "I have NO IDEA".

Thanks to Janice McLaughlin for pointing out the logic of this magic
handshake, which is apparently also the case for the Aqualung i300C.

This also simplifies the command sequence handling: if you use
oceanic_atom2_ble_transfer() and ask for an answer (even if the answer
size is zero, and only the ACK is returned), the BLE transfer code will
handle the sequence number on its own.

Only if you send a packet without expecting an answer at all do you need
to manage the sequence number manually.  This is mainly used for
oceanic_atom2_device_write(), although I suspect that code gets an ACK
and could use a zero answer size too.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-10-06 14:00:35 -07:00
Linus Torvalds
54ba49b1b3 iostream: add 'get_name()' function
Some of the transport types have meaningful names.

The BLE transport has a device name that was exposed during device
discovery, for example, and some back-ends (ok, right now only the
Aqualung i300C and i770R) need to know what that device name was in
order to handshake with the device properly.

Other transports, like the USBSTORAGE one, could usefully use this to
get the basename of the path to the storage, although right now that
transport makes do with simply doing a "dc_iostream_read()" on the
transport instead.

For now, this is just the infrastructure, ready to get hooked into.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-10-05 15:04:37 -07:00
Linus Torvalds
65ef99981f Aqualung i770R: _really_ fix the gas mix parsing
Commit fac3c8ff1487 ("Aqualung i770R: fix up gas mix parsing") was
supposed to fix gasmix parsing for the i770R, but because Janice's patch
didn't apply as-is, I had just redone it by hand, and in the process
copied the wrong o2_offset for the i770R, giving (not surprisingly) the
wrong results.

This just fixes my copy-paste error to what Janice actually had
originally.

Mea culpa.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-10-05 15:04:37 -07:00
Linus Torvalds
419b0e0498 Garmin Descent: make sure to open FIT file with O_BINARY if it exists
Primoz seems to have problems downloading dives from the Garmin Descent,
even when the same files work fine for me.  I suspect it's some Windows
issue, and the primary suspect would be the usual CR/LF text conversion.

Using O_BINARY would seem to be the obviously correct thing to do, and
won't hurt.

Reported-by: Primoz P <primozicp@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-10-05 11:52:03 -07:00
Linus Torvalds
5456475fd5 Garmin Descent: add heartrate parsing
.. the parsing was actually already there, but we never generated the
event to report it. I hadn't had any files with HR data.

Reported-by: Primoz P <primozicp@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-10-05 11:52:03 -07:00
Janice McLaughlin
fac3c8ff14 Aqualung i770R: fix up gas mix parsing
It appears that the Aqualung i770R looks almost the same as the ProPlus
X, but has an additional pO2 field for each gas by the O2 field, which
impacts the offset calculations.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-10-04 10:06:54 -07:00
Janice McLaughlin
17ff3e0667 Oceanic: fix up dive truncation issues, update memory layout
This fixes the dive truncation that happened with long dives due to the
removal of extra padding (commit a2100843b9cf: "Remove extra padding
from the end of the profile").  It turns out the rest of the profile was
in bits 13-15 (with bit 12 being something else).

Also update the memory layout and the baudrate for the i770R.

Signed-off-by: Janice McLaughlin <janice@moremobilesoftware.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-10-02 16:09:56 -07:00
Linus Torvalds
2518231577 Aqualung i770R: add packet send retry on reply failure
It seems to make things more robust.  Without this, the first packet in
particular seems to easily get lost, and the retry gets things going
again.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-10-01 16:21:42 -07:00
Linus Torvalds
c6c29f6e9a Merge branch 'master' of git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Pull fix from Jef's upstream.

This fixes the end of dive garbage for newer Aqualung dive computers,
including the i770R.

* 'master' of git://github.com/libdivecomputer/libdivecomputer:
  Remove extra padding from the end of the profile
2018-10-01 15:19:52 -07:00
Linus Torvalds
07a33e88c3 First attempt at supporting the Aqualung i770R
This works over BLE, although the end result of a dive download is still
a bit wonky.  There remains some parsing problem that Jef says are
likely be common with the Pro Plus too.

The serial download doesn't work at all, for unknown reasons.  That
*should* be the easy part that "just works" and acts the way previous
similar dive computers have acted.  It's a standard FTDI chip (FT231X)
that exposes a serial connection, but there may be some setup required
to choose between USB and BLE that we do not know about right now.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-30 10:08:06 -07:00
Linus Torvalds
5be8c17ea1 Mares bluetooth support tweaks
So it turns out that we really shouldn't send the command and arguments
as one single packet in all situations, because at least the Mares
Matrix really seems to want that "wait for ACK before sending
arguments".  See commit 59bfb0f3189b ("Add support for the Mares
Matrix") for details.

Also, because bluetooth is slow (and it looks like it's particularly
slow for us with the Qt bluetooth code for some reason), we don't want
to ask for big packets of data, because it seems to cause a buffer
overflow on the BlueLink Pro when the serial data from the dive computer
arrives faster than the bluetooth data can be sent to the downloading
side.

So when using the BLE transport, limit the packet size to 128 bytes and
disable the command splitting.

With this, I can download hundreds of kB of data from the Mares Quad Air
successfully over BLE. It's *slow*, but it works.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-27 10:48:15 -07:00
Linus Torvalds
f57c53470b Mares Icon HD family: send the command as one single write buffer
The Mares backend used to send the commands by splitting them up into
the two-byte command and the "rest", while waiting for the ACK byte
(0xAA) to come in between the two parts.

That seems to work fine for the serial side, but the the BLE packets, it
seems to cause too much of a delay between the command bytes and the
argument bytes, and the end result is that the Mares doesn't actually
act on the argument bytes at all, and just sends an EOF reply (0xEA).

The Mares app itself does seem to send them as two packets, but
apparently without actually waiting in between, avoiding the issue.

Let's just send the command as a single write, which makes at least my
loaner Mares Quad Air with the BlueLink Pro dongle happy.

We may need to revisit the details of this based on feedback.  But it
should speed up downloading too, by avoiding the roundtrip wait for the
ACK byte.

This affects all the computers in the Mares Icon HD family: Matrix,
Smart, Smart Apnea, Icon HD, Icon HD Net Ready, Puck Pro, Nemo Wide 2,
Puck 2, Quad Air, Smart Air and Quad.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-26 17:45:42 -07:00
Linus Torvalds
e97886a994 Fix dc_iostream_{read,write} debugging implementation
The dc_iostream_{read,write}() implementation had multiple issues:

 (a) it would return DC_STATUS_SUCCESS even if no iostream
     implementation existed.

     Yes, it would also return a zero "actual" bytes, but most backends
     don't even pass an "actual" pointer, so returning success was still
     completely insane.

     This one probably didn't matter, because all iostreams should have
     read and write members, but the return value was completely wrong
     if that ever were to happen.

 (b) The write side actually tested not whether a write function
     existed, but whether a read one existed.

     Again, this one probably didn't matter in practice, since an
     iostream without a read and write member doesn't make much sense,
     but the test was completely wrong regardless.

 (c) If the user passed in a NULL 'actual' pointer, the wrapper would
     ignore that, and pass in its own pointer instead, in order to know
     how many bytes to print for the debug message.

     But that means that the low-level read/write functions cannot know
     whether the user actually is able to handle a partial read or not.

     This one _definitely_ matters, because some protocols need to have
     a buffer for the whole incoming packet, but packerts may not always
     be full-size. The low-level protocol needs to know whether to wait
     for further packets (in order to fill the buffer) or to just return
     the partial data.

This fixes all of these issues.  If the user passes in a NULL actual
pointer (indicating that it needs all-or-nothing and is not ready to
handle a partial success), just loop over the IO until the buffer is
fully exhausted.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-26 11:22:23 -07:00
Dirk Hohndel
8120b11258 Revert "Shearwater: try to gracefully shut down the Bluetooth connection"
This reverts commit 437cc3e0cc9f88eedf27657c485cd55f23a4f2df.

Jef convinced me that this is fundamentally the wrong thing to try here.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-09-25 16:44:56 -07:00
Dirk Hohndel
32d62d6269 Shearwater: fix incorrect parsing of base address
I misunderstood the way the helper function worked. Thanks Jef for
pointing it out. The way I wrote the code we ended up doing undefined
shifts which explains why the data I got back seemed so random.

With this we should be able to simply act on the four known values for
the base address, with 0xDD000000 never an option by the time you got
here - but in the old code (prior to the PNF addition) we would have
fallen back to 0xC0000000, so let's do the same here.

Any other value is actually an unknown format and should be treated as
such.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-09-25 16:44:56 -07:00
Dirk Hohndel
91766d1ed0 Shearwater: better macro name
As Jef correctly points out, RDBI actually stands for Read Data by
Identifier, and the correct identifier here is Log Upload.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-09-25 16:44:56 -07:00
Linus Torvalds
f705ddefa8 Merge branch 'master' of git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Pull Aqualung i100 support from Jef's upstream libdivecomputer.

Very similar to the I200, but with the header in a different location.

* 'master' of git://github.com/libdivecomputer/libdivecomputer:
  Add support for the Aqualung i100
2018-09-24 12:25:11 -07:00
Linus Torvalds
0d1e8f1803 Merge git://github.com/libdivecomputer/libdivecomputer into Subsurface-NG
Merge upstream libdivecomputer changes from Jef Driesen.

A harmless build cleanup, and a small fix for the Mares Smart Apnea
temperature parsing.

* git://github.com/libdivecomputer/libdivecomputer:
  Fix the Mares Smart Apnea min/max temperature
  Fix the libusb and hidapi includes
2018-09-21 14:33:18 -07:00
Linus Torvalds
6f377182f5 garmin: relax FIT filename rules for Descent Mk1
When on the actual watch, the filename format for the FIT files ends up
being something like

    2018-09-21-10-23-36.fit

but then if you download the activity from the Garmin Connect activity
website, it might show up as a ZIP file that contains a file named
something like

    3030954326.fit

instead.

In order to make it easy to import these fit files that have been
downloaded from the Garmin cloud, relax the filename rules a bit.

NOTE! You still need to have the proper directory structure, and put
your FIT files in a subdirectory like

  <some path>/Garmin/Activity/

to match the way the FIT files show up when you mount the Garmin Descent
locally.  You can then point subsurface to <some path> when you do a
"download" from the Garmin Descent, regardless of whether it's an actual
case of the dive computer being mounted, or if you've downloaded the FIT
files to your local filesystem.

Reported-by: Andrew Trevor-Jones <atj777atj777@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-21 14:31:20 -07:00
Dirk Hohndel
fb70928c83 Shearwater parser: fix typo in Teric support
The surface pressure was read from the wrong location for PNF logs.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2018-09-18 15:09:14 -07:00
Linus Torvalds
8f2ac8f61e Merge branch 'teric' of git://github.com/Subsurface-divelog/libdc into Subsurface-NG
Merge Dirk's Shearwater Teric branch.

The Teric only supports Shearwater's new download format (PFN - Petrel
Native Format), and the old legacy format no longer works.

I'm sure there are issues, and Dirk just ended up ignoring an
unexplained format difference on Android, but without this you get
nothing.

[ Fixed up a format warning in the merge commit  - Linus ]

* 'teric' of git://github.com/Subsurface-divelog/libdc:
  Shearwater PNF support: fall back to default logbook style
  Shearwater: report error when parsing freedive
  Shearwater: add Teric to list of supported dive computers
  Shearwater: try to gracefully shut down the Bluetooth connection
  Shearwater: add helper to send bytes to the dive computer
  Shearwater Petrel Native Format parsing
  Shearwater: use the correct address to download dives
  Shearwater: detect which logbook format is support
  Detect Sherwater Teric
  Shearwater: skip deleted dives
2018-09-12 09:12:10 -10:00
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
Linus Torvalds
5ad97bd6bb Suunto EON Steel: report TTS now that libdivecomputer has the interface
The data was always there, and we used to report it as the length of the
next deco stop, which didn't make much sense, but it was basically the
only interface that libdivecomputer had.

Now that we've added DC_SAMPLE_TTS, report TTS properly, and just report
the deco stop time (which the EON Steel doesn't actually give us) as one
minute whenever we have a ceiling.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-09-09 11:54:15 -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
40 changed files with 3668 additions and 338 deletions

3
.gitignore vendored
View File

@ -41,6 +41,7 @@ Makefile.in
/examples/atom2 /examples/atom2
/examples/d9 /examples/d9
/examples/darwin /examples/darwin
/examples/dctool
/examples/edy /examples/edy
/examples/eon /examples/eon
/examples/frog /examples/frog
@ -82,3 +83,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 \
-fmacro-prefix-map='$(top_srcdir)/'= \ -fmacro-prefix-map='$(top_srcdir)/'= \
]) ])

View File

@ -90,6 +90,8 @@ 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},
{"cosmiq", DC_FAMILY_DEEPBLU, 0},
}; };
static const transport_table_t g_transports[] = { static const transport_table_t g_transports[] = {
@ -99,6 +101,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 *
@ -537,6 +540,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 */
@ -104,6 +108,10 @@ 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),
/* Deepblu */
DC_FAMILY_DEEPBLU = (17 << 16),
} dc_family_t; } dc_family_t;
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -310,6 +310,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,41 @@ 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)
/* these are used for the types of TAGs in Shearwater PNF info events */
#define SAMPLE_FLAGS_TYPE_SHIFT 5
#define SAMPLE_FLAGS_TYPE_MASK (7 << SAMPLE_FLAGS_TYPE_SHIFT)
#define SAMPLE_FLAGS_TYPE_NONE (0 << SAMPLE_FLAGS_TYPE_SHIFT)
#define SAMPLE_FLAGS_TYPE_INTEREST (1 << SAMPLE_FLAGS_TYPE_SHIFT)
#define SAMPLE_FLAGS_TYPE_NAVPOINT (2 << SAMPLE_FLAGS_TYPE_SHIFT)
#define SAMPLE_FLAGS_TYPE_DANGER (3 << SAMPLE_FLAGS_TYPE_SHIFT)
#define SAMPLE_FLAGS_TYPE_ANIMAL (4 << SAMPLE_FLAGS_TYPE_SHIFT)
#define SAMPLE_FLAGS_TYPE_ISSUE (5 << SAMPLE_FLAGS_TYPE_SHIFT)
#define SAMPLE_FLAGS_TYPE_INJURY (6 << SAMPLE_FLAGS_TYPE_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 +180,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 +216,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 +241,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

@ -498,6 +498,26 @@
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
RelativePath="..\src\deepblu.c"
>
</File>
<File
RelativePath="..\src\deepblu_parser.c"
>
</File>
<File
RelativePath="..\src\field-cache.c"
>
</File>
<File <File
RelativePath="..\src\timer.c" RelativePath="..\src\timer.c"
> >
@ -840,6 +860,14 @@
RelativePath="..\src\tecdiving_divecomputereu.h" RelativePath="..\src\tecdiving_divecomputereu.h"
> >
</File> </File>
<File
RelativePath="..\src\garmin.h"
>
</File>
<File
RelativePath="..\src\deepblu.h"
>
</File>
<File <File
RelativePath="..\src\timer.h" RelativePath="..\src\timer.h"
> >

View File

@ -69,13 +69,17 @@ libdivecomputer_la_SOURCES = \
rbstream.h rbstream.c \ rbstream.h rbstream.c \
checksum.h checksum.c \ checksum.h checksum.c \
array.h array.c \ array.h array.c \
field-cache.h field-cache.c \
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 \
deepblu.h deepblu.c deepblu_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

500
src/deepblu.c Normal file
View File

@ -0,0 +1,500 @@
/*
* Deepblu Cosmiq+ downloading
*
* Copyright (C) 2019 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 <string.h>
#include <stdlib.h>
#include "deepblu.h"
#include "context-private.h"
#include "device-private.h"
#include "array.h"
// "Write state"
#define CMD_SETTIME 0x20 // Send 6 byte date-time, get single-byte 00x00 ack
#define CMD_23 0x23 // Send 00/01 byte, get ack back? Some metric/imperial setting?
// "Read dives"?
#define CMD_GETDIVENR 0x40 // Send empty byte, get single-byte number of dives back
#define CMD_GETDIVE 0x41 // Send dive number (1-nr) byte, get dive stat length byte back
#define RSP_DIVESTAT 0x42 // .. followed by packets of dive stat for that dive of that length
#define CMD_GETPROFILE 0x43 // Send dive number (1-nr) byte, get dive profile length BE word back
#define RSP_DIVEPROF 0x44 // .. followed by packets of dive profile of that length
// "Read state"
#define CMD_GETTIME 0x50 // Send empty byte, get six-byte bcd date-time back
#define CMD_51 0x51 // Send empty byte, get four bytes back (03 dc 00 e3)
#define CMD_52 0x52 // Send empty byte, get two bytes back (bf 8d)
#define CMD_53 0x53 // Send empty byte, get six bytes back (0e 81 00 03 00 00)
#define CMD_54 0x54 // Send empty byte, get byte back (00)
#define CMD_55 0x55 // Send empty byte, get byte back (00)
#define CMD_56 0x56 // Send empty byte, get byte back (00)
#define CMD_57 0x57 // Send empty byte, get byte back (00)
#define CMD_58 0x58 // Send empty byte, get byte back (52)
#define CMD_59 0x59 // Send empty byte, get six bytes back (00 00 07 00 00 00)
// (00 00 00 00 00 00)
#define CMD_5a 0x5a // Send empty byte, get six bytes back (23 1b 09 d8 37 c0)
#define CMD_5b 0x5b // Send empty byte, get six bytes back (00 21 00 14 00 01)
// (00 00 00 14 00 01)
#define CMD_5c 0x5c // Send empty byte, get six bytes back (13 88 00 46 20 00)
// (13 88 00 3c 15 00)
#define CMD_5d 0x5d // Send empty byte, get six bytes back (19 00 23 0C 02 0E)
// (14 14 14 0c 01 0e)
#define CMD_5f 0x5f // Send empty byte, get six bytes back (00 00 07 00 00 00)
#define COSMIQ_HDR_SIZE 36
typedef struct deepblu_device_t {
dc_device_t base;
dc_iostream_t *iostream;
unsigned char fingerprint[COSMIQ_HDR_SIZE];
} deepblu_device_t;
static dc_status_t deepblu_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
static dc_status_t deepblu_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
static dc_status_t deepblu_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime);
static dc_status_t deepblu_device_close (dc_device_t *abstract);
static const dc_device_vtable_t deepblu_device_vtable = {
sizeof(deepblu_device_t),
DC_FAMILY_DEEPBLU,
deepblu_device_set_fingerprint, /* set_fingerprint */
NULL, /* read */
NULL, /* write */
NULL, /* dump */
deepblu_device_foreach, /* foreach */
deepblu_device_timesync, /* timesync */
deepblu_device_close, /* close */
};
// Maximum data in a packet. It's actually much
// less than this, since BLE packets are small and
// with the 7 bytes of headers and final newline
// and the HEX encoding, the actual maximum is
// just something like 6 bytes.
//
// But in theory the data could be done over
// multiple packets. That doesn't seem to be
// the case in anything I've seen so far.
//
// Pick something small and easy to use for
// stack buffers.
#define MAX_DATA 20
static char *
write_hex_byte(unsigned char data, char *p)
{
static const char hex[16] = "0123456789ABCDEF";
*p++ = hex[data >> 4];
*p++ = hex[data & 0xf];
return p;
}
//
// Send a cmd packet.
//
// The format of the cmd on the "wire" is:
// - byte '#'
// - HEX char of cmd
// - HEX char two's complement modular sum of packet data (including cmd/size)
// - HEX char size of data as encoded in HEX
// - n * HEX char data
// - byte '\n'
// so you end up having 8 bytes of header/trailer overhead, and two bytes
// for every byte of data sent due to the HEX encoding.
//
static dc_status_t
deepblu_send_cmd(deepblu_device_t *device, const unsigned char cmd, const unsigned char data[], size_t size)
{
char buffer[8+2*MAX_DATA], *p;
unsigned char csum;
int i;
if (size > MAX_DATA)
return DC_STATUS_INVALIDARGS;
// Calculate packet csum
csum = cmd + 2*size;
for (i = 0; i < size; i++)
csum += data[i];
csum = -csum;
// Fill the data buffer
p = buffer;
*p++ = '#';
p = write_hex_byte(cmd, p);
p = write_hex_byte(csum, p);
p = write_hex_byte(size*2, p);
for (i = 0; i < size; i++)
p = write_hex_byte(data[i], p);
*p++ = '\n';
// .. and send it out
return dc_iostream_write(device->iostream, buffer, p-buffer, NULL);
}
//
// Receive one 'line' of data
//
// The deepblu BLE protocol is ASCII line based and packetized.
// Normally one packet is one line, but it looks like the Nordic
// Semi BLE chip will sometimes send packets early (some internal
// serial buffer timeout?) with incompete data.
//
// So read packets until you get newline.
static dc_status_t
deepblu_recv_line(deepblu_device_t *device, unsigned char *buf, size_t size)
{
while (1) {
unsigned char buffer[20];
size_t transferred = 0;
dc_status_t status;
status = dc_iostream_read(device->iostream, buffer, sizeof(buffer), &transferred);
if (status != DC_STATUS_SUCCESS) {
ERROR(device->base.context, "Failed to receive Deepblu reply packet.");
return status;
}
if (transferred > size) {
ERROR(device->base.context, "Deepblu reply packet with too much data (got %zu, expected %zu)", transferred, size);
return DC_STATUS_IO;
}
if (!transferred) {
ERROR(device->base.context, "Empty Deepblu reply packet");
return DC_STATUS_IO;
}
memcpy(buf, buffer, transferred);
buf += transferred;
size -= transferred;
if (buf[-1] == '\n')
break;
}
buf[-1] = 0;
return DC_STATUS_SUCCESS;
}
static int
hex_nibble(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int
read_hex_byte(char *p)
{
// This is negative if either of the nibbles is invalid
return (hex_nibble(p[0]) << 4) | hex_nibble(p[1]);
}
//
// Receive a reply packet
//
// The reply packet has the same format as the cmd packet we
// send, except the first byte is '$' instead of '#'.
static dc_status_t
deepblu_recv_data(deepblu_device_t *device, const unsigned char expected, unsigned char *buf, size_t size, size_t *received)
{
int len, i;
dc_status_t status;
char buffer[8+2*MAX_DATA];
int cmd, csum, ndata;
status = deepblu_recv_line(device, buffer, sizeof(buffer));
if (status != DC_STATUS_SUCCESS)
return status;
// deepblu_recv_line() always zero-terminates the result
// if it returned success, and has removed the final newline.
len = strlen(buffer);
HEXDUMP(device->base.context, DC_LOGLEVEL_DEBUG, "rcv", buffer, len);
// A valid reply should always be at least 7 characters: the
// initial '$' and the three header HEX bytes.
if (len < 8 || buffer[0] != '$') {
ERROR(device->base.context, "Invalid Deepblu reply packet");
return DC_STATUS_IO;
}
cmd = read_hex_byte(buffer+1);
csum = read_hex_byte(buffer+3);
ndata = read_hex_byte(buffer+5);
if ((cmd | csum | ndata) < 0) {
ERROR(device->base.context, "non-hex Deepblu reply packet header");
return DC_STATUS_IO;
}
// Verify the data length: it's the size of the HEX data,
// and should also match the line length we got (the 7
// is for the header data we already decoded above).
if ((ndata & 1) || ndata != len - 7) {
ERROR(device->base.context, "Deepblu reply packet data length does not match (claimed %d, got %d)", ndata, len-7);
return DC_STATUS_IO;
}
if (ndata >> 1 > size) {
ERROR(device->base.context, "Deepblu reply packet too big for buffer (ndata=%d, size=%zu)", ndata, size);
return DC_STATUS_IO;
}
csum += cmd + ndata;
for (i = 7; i < len; i += 2) {
int byte = read_hex_byte(buffer + i);
if (byte < 0) {
ERROR(device->base.context, "Deepblu reply packet data not valid hex");
return DC_STATUS_IO;
}
*buf++ = byte;
csum += byte;
}
if (csum & 255) {
ERROR(device->base.context, "Deepblu reply packet csum not valid (%x)", csum);
return DC_STATUS_IO;
}
*received = ndata >> 1;
return DC_STATUS_SUCCESS;
}
// Common communication pattern: send a command, expect data back with the same
// command byte.
static dc_status_t
deepblu_send_recv(deepblu_device_t *device, const unsigned char cmd,
const unsigned char *data, size_t data_size,
unsigned char *result, size_t result_size)
{
dc_status_t status;
size_t got;
status = deepblu_send_cmd(device, cmd, data, data_size);
if (status != DC_STATUS_SUCCESS)
return status;
status = deepblu_recv_data(device, cmd, result, result_size, &got);
if (status != DC_STATUS_SUCCESS)
return status;
if (got != result_size) {
ERROR(device->base.context, "Deepblu result size didn't match expected (expected %zu, got %zu)",
result_size, got);
return DC_STATUS_IO;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
deepblu_recv_bulk(deepblu_device_t *device, const unsigned char cmd, unsigned char *buf, size_t len)
{
while (len) {
dc_status_t status;
size_t got;
status = deepblu_recv_data(device, cmd, buf, len, &got);
if (status != DC_STATUS_SUCCESS)
return status;
if (got > len) {
ERROR(device->base.context, "Deepblu bulk receive overflow");
return DC_STATUS_IO;
}
buf += got;
len -= got;
}
return DC_STATUS_SUCCESS;
}
dc_status_t
deepblu_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream)
{
deepblu_device_t *device;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
// Allocate memory.
device = (deepblu_device_t *) dc_device_allocate (context, &deepblu_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
deepblu_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size)
{
deepblu_device_t *device = (deepblu_device_t *)abstract;
HEXDUMP(device->base.context, DC_LOGLEVEL_DEBUG, "set_fingerprint", data, size);
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 unsigned char bcd(int val)
{
if (val >= 0 && val < 100) {
int high = val / 10;
int low = val % 10;
return (high << 4) | low;
}
return 0;
}
static dc_status_t
deepblu_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime)
{
deepblu_device_t *device = (deepblu_device_t *)abstract;
unsigned char result[1], data[6];
dc_status_t status;
size_t len;
data[0] = bcd(datetime->year - 2000);
data[1] = bcd(datetime->month);
data[2] = bcd(datetime->day);
data[3] = bcd(datetime->hour);
data[4] = bcd(datetime->minute);
data[5] = bcd(datetime->second);
// Maybe also check that we received one zero byte (ack?)
return deepblu_send_recv(device, CMD_SETTIME,
data, sizeof(data),
result, sizeof(result));
}
static dc_status_t
deepblu_device_close (dc_device_t *abstract)
{
deepblu_device_t *device = (deepblu_device_t *) abstract;
return DC_STATUS_SUCCESS;
}
static const char zero[MAX_DATA];
static dc_status_t
deepblu_download_dive(deepblu_device_t *device, unsigned char nr, dc_dive_callback_t callback, void *userdata)
{
unsigned char header_len;
unsigned char profilebytes[2];
unsigned int profile_len;
dc_status_t status;
char header[256];
unsigned char *profile;
status = deepblu_send_recv(device, CMD_GETDIVE, &nr, 1, &header_len, 1);
if (status != DC_STATUS_SUCCESS)
return status;
status = deepblu_recv_bulk(device, RSP_DIVESTAT, header, header_len);
if (status != DC_STATUS_SUCCESS)
return status;
memset(header + header_len, 0, 256 - header_len);
/* The header is the fingerprint. If we've already seen this header, we're done */
if (memcmp(header, device->fingerprint, sizeof (device->fingerprint)) == 0)
return DC_STATUS_DONE;
status = deepblu_send_recv(device, CMD_GETPROFILE, &nr, 1, profilebytes, sizeof(profilebytes));
if (status != DC_STATUS_SUCCESS)
return status;
profile_len = (profilebytes[0] << 8) | profilebytes[1];
profile = malloc(256 + profile_len);
if (!profile) {
ERROR (device->base.context, "Insufficient buffer space available.");
return DC_STATUS_NOMEMORY;
}
// We make the dive data be 256 bytes of header, followed by the profile data
memcpy(profile, header, 256);
status = deepblu_recv_bulk(device, RSP_DIVEPROF, profile+256, profile_len);
if (status != DC_STATUS_SUCCESS)
return status;
if (callback) {
if (!callback(profile, profile_len+256, header, header_len, userdata))
return DC_STATUS_DONE;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
deepblu_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
{
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
deepblu_device_t *device = (deepblu_device_t *) abstract;
unsigned char nrdives, val;
dc_status_t status;
int i;
val = 0;
status = deepblu_send_recv(device, CMD_GETDIVENR, &val, 1, &nrdives, 1);
if (status != DC_STATUS_SUCCESS)
return status;
if (!nrdives)
return DC_STATUS_SUCCESS;
progress.maximum = nrdives;
progress.current = 0;
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
for (i = 1; i <= nrdives; i++) {
if (device_is_cancelled(abstract)) {
dc_status_set_error(&status, DC_STATUS_CANCELLED);
break;
}
status = deepblu_download_dive(device, i, callback, userdata);
switch (status) {
case DC_STATUS_DONE:
i = nrdives;
break;
case DC_STATUS_SUCCESS:
break;
default:
return status;
}
progress.current = i;
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
}
return DC_STATUS_SUCCESS;
}

43
src/deepblu.h Normal file
View File

@ -0,0 +1,43 @@
/*
* Deepblu Cosmiq+ downloading/parsing
*
* 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 DEEPBLU_H
#define DEEPBLU_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
deepblu_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
dc_status_t
deepblu_parser_create (dc_parser_t **parser, dc_context_t *context);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* DEEPBLU_H */

278
src/deepblu_parser.c Normal file
View File

@ -0,0 +1,278 @@
/*
* Deeplu Cosmiq+ parsing
*
* Copyright (C) 2019 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 <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "deepblu.h"
#include "context-private.h"
#include "parser-private.h"
#include "array.h"
#include "field-cache.h"
#define C_ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
#define MAXFIELDS 128
struct msg_desc;
typedef struct deepblu_parser_t {
dc_parser_t base;
dc_sample_callback_t callback;
void *userdata;
// 20 sec for scuba, 1 sec for freedives
int sample_interval;
// Common fields
struct dc_field_cache cache;
} deepblu_parser_t;
static dc_status_t deepblu_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size);
static dc_status_t deepblu_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime);
static dc_status_t deepblu_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value);
static dc_status_t deepblu_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata);
static const dc_parser_vtable_t deepblu_parser_vtable = {
sizeof(deepblu_parser_t),
DC_FAMILY_DEEPBLU,
deepblu_parser_set_data, /* set_data */
deepblu_parser_get_datetime, /* datetime */
deepblu_parser_get_field, /* fields */
deepblu_parser_samples_foreach, /* samples_foreach */
NULL /* destroy */
};
dc_status_t
deepblu_parser_create (dc_parser_t **out, dc_context_t *context)
{
deepblu_parser_t *parser = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
// Allocate memory.
parser = (deepblu_parser_t *) dc_parser_allocate (context, &deepblu_parser_vtable);
if (parser == NULL) {
ERROR (context, "Failed to allocate memory.");
return DC_STATUS_NOMEMORY;
}
*out = (dc_parser_t *) parser;
return DC_STATUS_SUCCESS;
}
static double
pressure_to_depth(unsigned int mbar)
{
// Specific weight of seawater (millibar to cm)
const double specific_weight = 1.024 * 0.980665;
// Absolute pressure, subtract surface pressure
if (mbar < 1013)
return 0.0;
mbar -= 1013;
return mbar / specific_weight / 100.0;
}
static dc_status_t
deepblu_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size)
{
deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract;
const unsigned char *hdr = data;
const unsigned char *profile = data + 256;
unsigned int divetime, maxpressure;
dc_gasmix_t gasmix = {0, };
if (size < 256)
return DC_STATUS_IO;
deepblu->callback = NULL;
deepblu->userdata = NULL;
memset(&deepblu->cache, 0, sizeof(deepblu->cache));
// LE16 at 0 is 'dive number'
// LE16 at 12 is the dive time
// It's in seconds for freedives, minutes for scuba/gauge
divetime = hdr[12] + 256*hdr[13];
// Byte at 2 is 'activity type' (2 = scuba, 3 = gauge, 4 = freedive)
// Byte at 3 is O2 percentage
switch (data[2]) {
case 2:
// SCUBA - divetime in minutes
divetime *= 60;
gasmix.oxygen = data[3] / 100.0;
DC_ASSIGN_IDX(deepblu->cache, GASMIX, 0, gasmix);
DC_ASSIGN_FIELD(deepblu->cache, GASMIX_COUNT, 1);
DC_ASSIGN_FIELD(deepblu->cache, DIVEMODE, DC_DIVEMODE_OC);
break;
case 3:
// GAUGE - divetime in minutes
divetime *= 60;
DC_ASSIGN_FIELD(deepblu->cache, DIVEMODE, DC_DIVEMODE_GAUGE);
break;
case 4:
// FREEDIVE - divetime in seconds
DC_ASSIGN_FIELD(deepblu->cache, DIVEMODE, DC_DIVEMODE_FREEDIVE);
deepblu->sample_interval = 1;
break;
default:
ERROR (abstract->context, "Deepblu: unknown activity type '%02x'", data[2]);
break;
}
// Seems to be fixed at 20s for scuba, 1s for freedive
deepblu->sample_interval = hdr[26];
maxpressure = hdr[22] + 256*hdr[23]; // Maxpressure in millibar
DC_ASSIGN_FIELD(deepblu->cache, DIVETIME, divetime);
DC_ASSIGN_FIELD(deepblu->cache, MAXDEPTH, pressure_to_depth(maxpressure));
return DC_STATUS_SUCCESS;
}
// The layout of the header in the 'data' is
// 0: LE16 dive number
// 2: dive type byte?
// 3: O2 percentage byte
// 4: unknown
// 5: unknown
// 6: LE16 year
// 8: day of month
// 9: month
// 10: minute
// 11: hour
// 12: LE16 dive time
// 14: LE16 ??
// 16: LE16 surface pressure?
// 18: LE16 ??
// 20: LE16 ??
// 22: LE16 max depth pressure
// 24: LE16 water temp
// 26: LE16 ??
// 28: LE16 ??
// 30: LE16 ??
// 32: LE16 ??
// 34: LE16 ??
static dc_status_t
deepblu_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
{
deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract;
const unsigned char *data = deepblu->base.data;
int len = deepblu->base.size;
if (len < 256)
return DC_STATUS_IO;
datetime->year = data[6] + (data[7] << 8);
datetime->day = data[8];
datetime->month = data[9];
datetime->minute = data[10];
datetime->hour = data[11];
datetime->second = 0;
datetime->timezone = DC_TIMEZONE_NONE;
return DC_STATUS_SUCCESS;
}
static dc_status_t
deepblu_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
{
deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract;
if (!value)
return DC_STATUS_INVALIDARGS;
/* This whole sequence should be standardized */
if (!(deepblu->cache.initialized & (1 << type)))
return DC_STATUS_UNSUPPORTED;
switch (type) {
case DC_FIELD_DIVETIME:
return DC_FIELD_VALUE(deepblu->cache, value, DIVETIME);
case DC_FIELD_MAXDEPTH:
return DC_FIELD_VALUE(deepblu->cache, value, MAXDEPTH);
case DC_FIELD_AVGDEPTH:
return DC_FIELD_VALUE(deepblu->cache, value, AVGDEPTH);
case DC_FIELD_GASMIX_COUNT:
case DC_FIELD_TANK_COUNT:
return DC_FIELD_VALUE(deepblu->cache, value, GASMIX_COUNT);
case DC_FIELD_GASMIX:
if (flags >= MAXGASES)
return DC_STATUS_UNSUPPORTED;
return DC_FIELD_INDEX(deepblu->cache, value, GASMIX, flags);
case DC_FIELD_SALINITY:
return DC_FIELD_VALUE(deepblu->cache, value, SALINITY);
case DC_FIELD_ATMOSPHERIC:
return DC_FIELD_VALUE(deepblu->cache, value, ATMOSPHERIC);
case DC_FIELD_DIVEMODE:
return DC_FIELD_VALUE(deepblu->cache, value, DIVEMODE);
case DC_FIELD_TANK:
return DC_STATUS_UNSUPPORTED;
case DC_FIELD_STRING:
return dc_field_get_string(&deepblu->cache, flags, (dc_field_string_t *)value);
default:
return DC_STATUS_UNSUPPORTED;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
deepblu_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{
deepblu_parser_t *deepblu = (deepblu_parser_t *) abstract;
const unsigned char *data = deepblu->base.data;
int len = deepblu->base.size, i;
deepblu->callback = callback;
deepblu->userdata = userdata;
// Skip the header information
if (len < 256)
return DC_STATUS_IO;
data += 256;
len -= 256;
// The rest should be samples every 20s with temperature and depth
for (i = 0; i < len/4; i++) {
dc_sample_value_t sample = {0};
unsigned int temp = data[0]+256*data[1];
unsigned int pressure = data[2]+256*data[3];
data += 4;
sample.time = (i+1)*deepblu->sample_interval;
if (callback) callback (DC_SAMPLE_TIME, sample, userdata);
sample.depth = pressure_to_depth(pressure);
if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata);
sample.temperature = temp / 10.0;
if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata);
}
return DC_STATUS_SUCCESS;
}

View File

@ -45,9 +45,11 @@ 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 int dc_filter_mares (dc_transport_t transport, const void *userdata); static int dc_filter_mares (dc_transport_t transport, const void *userdata);
static int dc_filter_divesystem (dc_transport_t transport, const void *userdata); static int dc_filter_divesystem (dc_transport_t transport, const void *userdata);
static int dc_filter_oceanic (dc_transport_t transport, const void *userdata); static int dc_filter_oceanic (dc_transport_t transport, const void *userdata);
static int dc_filter_deepblu (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);
@ -234,7 +236,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},
@ -304,8 +306,8 @@ static const dc_descriptor_t g_descriptors[] = {
{"Cressi", "Newton", DC_FAMILY_CRESSI_LEONARDO, 5, DC_TRANSPORT_SERIAL, NULL}, {"Cressi", "Newton", DC_FAMILY_CRESSI_LEONARDO, 5, DC_TRANSPORT_SERIAL, NULL},
{"Cressi", "Drake", DC_FAMILY_CRESSI_LEONARDO, 6, DC_TRANSPORT_SERIAL, NULL}, {"Cressi", "Drake", DC_FAMILY_CRESSI_LEONARDO, 6, DC_TRANSPORT_SERIAL, NULL},
/* Cressi Goa */ /* Cressi Goa */
{"Cressi", "Cartesio", DC_FAMILY_CRESSI_GOA, 1, DC_TRANSPORT_SERIAL, NULL}, {"Cressi", "Cartesio", DC_FAMILY_CRESSI_GOA, 1, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
{"Cressi", "Goa", DC_FAMILY_CRESSI_GOA, 2, DC_TRANSPORT_SERIAL, NULL}, {"Cressi", "Goa", DC_FAMILY_CRESSI_GOA, 2, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
/* Zeagle N2iTiON3 */ /* Zeagle N2iTiON3 */
{"Zeagle", "N2iTiON3", DC_FAMILY_ZEAGLE_N2ITION3, 0, DC_TRANSPORT_SERIAL, NULL}, {"Zeagle", "N2iTiON3", DC_FAMILY_ZEAGLE_N2ITION3, 0, DC_TRANSPORT_SERIAL, NULL},
{"Apeks", "Quantum X", DC_FAMILY_ZEAGLE_N2ITION3, 0, DC_TRANSPORT_SERIAL, NULL}, {"Apeks", "Quantum X", DC_FAMILY_ZEAGLE_N2ITION3, 0, DC_TRANSPORT_SERIAL, NULL},
@ -376,6 +378,10 @@ 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},
/* Deepblu */
{"Deepblu", "Cosmiq+", DC_FAMILY_DEEPBLU, 0, DC_TRANSPORT_BLE, dc_filter_deepblu},
}; };
static int static int
@ -581,6 +587,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 (userdata, usbhid, 0, dc_match_usb);
}
return 1;
}
static int dc_filter_mares (dc_transport_t transport, const void *userdata) static int dc_filter_mares (dc_transport_t transport, const void *userdata)
{ {
static const char * const bluetooth[] = { static const char * const bluetooth[] = {
@ -629,6 +648,19 @@ static int dc_filter_oceanic (dc_transport_t transport, const void *userdata)
return 1; return 1;
} }
static int dc_filter_deepblu (dc_transport_t transport, const void *userdata)
{
static const char * const bluetooth[] = {
"COSMIQ",
};
if (transport == DC_TRANSPORT_BLE) {
return DC_FILTER_INTERNAL (userdata, bluetooth, 0, dc_match_name);
}
return 1;
}
dc_status_t dc_status_t
dc_descriptor_iterator (dc_iterator_t **out) dc_descriptor_iterator (dc_iterator_t **out)
{ {

View File

@ -57,6 +57,8 @@
#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 "deepblu.h"
#include "device-private.h" #include "device-private.h"
#include "context-private.h" #include "context-private.h"
@ -211,6 +213,12 @@ 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;
case DC_FAMILY_DEEPBLU:
rc = deepblu_device_open (&device, context, iostream);
break;
default: default:
return DC_STATUS_INVALIDARGS; return DC_STATUS_INVALIDARGS;
} }

64
src/field-cache.c Normal file
View File

@ -0,0 +1,64 @@
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "parser-private.h"
#include "field-cache.h"
/*
* The field cache 'string' interface has some simple rules:
* the "descriptor" part is assumed to be a static allocation,
* while the "value" is something that this interface will
* alway sallocate with 'strdup()', so you can generate it
* dynamically on the stack or whatever without having to
* worry about it.
*/
dc_status_t dc_field_add_string(dc_field_cache_t *cache, const char *desc, const char *value)
{
int i;
cache->initialized |= 1 << DC_FIELD_STRING;
for (i = 0; i < MAXSTRINGS; i++) {
dc_field_string_t *str = cache->strings+i;
if (str->desc)
continue;
str->value = strdup(value);
if (!str->value)
return DC_STATUS_NOMEMORY;
str->desc = desc;
return DC_STATUS_SUCCESS;
}
return DC_STATUS_INVALIDARGS;
}
dc_status_t dc_field_add_string_fmt(dc_field_cache_t *cache, 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 dc_field_add_string(cache, desc, buffer);
}
dc_status_t dc_field_get_string(dc_field_cache_t *cache, unsigned idx, dc_field_string_t *value)
{
if (idx < MAXSTRINGS) {
dc_field_string_t *res = cache->strings+idx;
if (res->desc && res->value) {
*value = *res;
return DC_STATUS_SUCCESS;
}
}
return DC_STATUS_UNSUPPORTED;
}

63
src/field-cache.h Normal file
View File

@ -0,0 +1,63 @@
#include <string.h>
#define MAXGASES 16
#define MAXSTRINGS 32
// dc_get_field() data
typedef struct dc_field_cache {
unsigned int initialized;
// DC_GET_FIELD_xyz
unsigned int DIVETIME;
double MAXDEPTH;
double AVGDEPTH;
double ATMOSPHERIC;
dc_divemode_t DIVEMODE;
unsigned int GASMIX_COUNT;
dc_salinity_t SALINITY;
dc_gasmix_t GASMIX[MAXGASES];
// misc - clean me up!
double lowsetpoint;
double highsetpoint;
double customsetpoint;
// This (slong with GASMIX) should be something like
// dc_tank_t TANK[MAXGASES]
// but that's for later
dc_tankinfo_t tankinfo[MAXGASES];
double tanksize[MAXGASES];
double tankworkingpressure[MAXGASES];
// DC_GET_FIELD_STRING
dc_field_string_t strings[MAXSTRINGS];
} dc_field_cache_t;
dc_status_t dc_field_add_string(dc_field_cache_t *, const char *desc, const char *data);
dc_status_t dc_field_add_string_fmt(dc_field_cache_t *, const char *desc, const char *fmt, ...);
dc_status_t dc_field_get_string(dc_field_cache_t *, unsigned idx, dc_field_string_t *value);
/*
* Macro to make it easy to set DC_FIELD_xyz values.
*
* This explains why dc_field_cache member names are
* those odd all-capitalized names: they match the
* names of the DC_FIELD_xyz enums.
*/
#define DC_ASSIGN_FIELD(cache, name, value) do { \
(cache).initialized |= 1u << DC_FIELD_##name; \
(cache).name = (value); \
} while (0)
#define DC_ASSIGN_IDX(cache, name, idx, value) do { \
(cache).initialized |= 1u << DC_FIELD_##name; \
(cache).name[idx] = (value); \
} while (0)
// Ugly define thing makes the code much easier to read
// I'd love to use __typeof__, but that's a gcc'ism
#define DC_FIELD_VALUE(cache, p, NAME) \
(memcpy((p), &(cache).NAME, sizeof((cache).NAME)), DC_STATUS_SUCCESS)
#define DC_FIELD_INDEX(cache, p, NAME, idx) \
(memcpy((p), (cache).NAME+idx, sizeof((cache).NAME[0])), DC_STATUS_SUCCESS)

338
src/garmin.c Normal file
View File

@ -0,0 +1,338 @@
/*
* 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(dc_device_t *abstract, DIR *dir, struct file_list *files)
{
struct dirent *de;
DEBUG (abstract->context, "Iterating over Garmin files");
while ((de = readdir(dir)) != NULL) {
int len = strlen(de->d_name);
struct fit_name *entry;
const char *explain = NULL;
DEBUG (abstract->context, " %s", de->d_name);
if (len < 5)
explain = "name too short";
if (len >= FIT_NAME_SIZE)
explain = "name too long";
if (strncasecmp(de->d_name + len - 4, ".FIT", 4))
explain = "name lacks FIT suffix";
DEBUG (abstract->context, " %s - %s", de->d_name, explain ? explain : "adding to list");
if (explain)
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;
}
/*
* NOTE! This depends on the zero-padding that strncpy does.
*
* strncpy() doesn't just limit the size of the copy, it
* will zero-pad the end of the result buffer.
*/
entry = files->array + files->nr++;
strncpy(entry->name, de->d_name, FIT_NAME_SIZE);
}
DEBUG (abstract->context, "Found %d files", files->nr);
qsort(files->array, files->nr, sizeof(struct fit_name), name_cmp);
return DC_STATUS_SUCCESS;
}
#ifndef O_BINARY
#define O_BINARY 0
#endif
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 | O_BINARY);
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)-1, &pathlen);
if (rc != DC_STATUS_SUCCESS)
return rc;
pathname[pathlen] = 0;
// 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) {
ERROR (abstract->context, "Invalid Garmin base directory '%s'", pathname);
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) {
ERROR (abstract->context, "Failed to open directory '%s'.", pathname);
return DC_STATUS_IO;
}
// Get the list of FIT files
rc = get_file_list(abstract, 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;
DEBUG (abstract->context, "Ignoring '%s' and older", name);
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 */

1318
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); hw_ostc_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int serial);
#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"
@ -69,8 +75,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
#define OSTC3FW(major,minor) ( \ #define OSTC3FW(major,minor) ( \
(((major) & 0xFF) << 8) | \ (((major) & 0xFF) << 8) | \
((minor) & 0xFF)) ((minor) & 0xFF))
@ -96,7 +108,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 {
@ -108,6 +126,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;
@ -145,7 +164,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 = {
@ -157,7 +182,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 = {
@ -169,7 +200,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
@ -312,7 +349,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;
@ -342,6 +379,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;
@ -350,15 +388,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) hw_ostc_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int serial)
{ {
return hw_ostc_parser_create_internal (out, context, 0, 0); return hw_ostc_parser_create_internal (out, context, serial, 0, 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
@ -446,6 +484,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)
{ {
@ -470,9 +510,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) {
@ -570,6 +613,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

@ -188,43 +188,86 @@ dc_iostream_poll (dc_iostream_t *iostream, int timeout)
dc_status_t dc_status_t
dc_iostream_read (dc_iostream_t *iostream, void *data, size_t size, size_t *actual) dc_iostream_read (dc_iostream_t *iostream, void *data, size_t size, size_t *actual)
{ {
dc_status_t status = DC_STATUS_SUCCESS; if (actual)
size_t nbytes = 0; *actual = 0;
if (iostream == NULL || iostream->vtable->read == NULL) { if (iostream == NULL || iostream->vtable->read == NULL)
goto out; return DC_STATUS_IO;
while (size) {
dc_status_t status;
size_t nbytes = 0;
status = iostream->vtable->read (iostream, data, size, &nbytes);
HEXDUMP (iostream->context, DC_LOGLEVEL_INFO, "Read", (unsigned char *) data, nbytes);
/*
* If the reader is able to handle partial results,
* return them as such. NOTE! No need to add up a
* total, we will go through this loop only once
* in this case.
*/
if (actual) {
*actual = nbytes;
return status;
}
if (status != DC_STATUS_SUCCESS)
return status;
/*
* Defensive check: if the read() function returned
* zero bytes, don't loop forever - give up with a
* timeout. Jef pointed out that the subsurface
* qt_serial_read() function can cause this badness..
*/
if (!nbytes)
return DC_STATUS_TIMEOUT;
/*
* Continue reading to fill up the whole buffer,
* since the reader is not able to handle a
* partial result.
*/
data = (void *)(nbytes + (char *)data);
size -= nbytes;
} }
status = iostream->vtable->read (iostream, data, size, &nbytes); return DC_STATUS_SUCCESS;
HEXDUMP (iostream->context, DC_LOGLEVEL_INFO, "Read", (unsigned char *) data, nbytes);
out:
if (actual)
*actual = nbytes;
return status;
} }
dc_status_t dc_status_t
dc_iostream_write (dc_iostream_t *iostream, const void *data, size_t size, size_t *actual) dc_iostream_write (dc_iostream_t *iostream, const void *data, size_t size, size_t *actual)
{ {
dc_status_t status = DC_STATUS_SUCCESS; if (actual)
size_t nbytes = 0; *actual = 0;
if (iostream == NULL || iostream->vtable->write == NULL) { if (iostream == NULL || iostream->vtable->write == NULL)
goto out; return DC_STATUS_IO;
while (size) {
dc_status_t status;
size_t nbytes = 0;
status = iostream->vtable->write (iostream, data, size, &nbytes);
HEXDUMP (iostream->context, DC_LOGLEVEL_INFO, "Write", (const unsigned char *) data, nbytes);
if (actual) {
*actual = nbytes;
return status;
}
if (status != DC_STATUS_SUCCESS)
return status;
if (!nbytes)
return DC_STATUS_IO;
data = (void *)(nbytes + (char *)data);
size -= nbytes;
} }
status = iostream->vtable->write (iostream, data, size, &nbytes); return DC_STATUS_SUCCESS;
HEXDUMP (iostream->context, DC_LOGLEVEL_INFO, "Write", (const unsigned char *) data, nbytes);
out:
if (actual)
*actual = nbytes;
return status;
} }
dc_status_t dc_status_t

View File

@ -76,6 +76,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

@ -97,6 +97,7 @@ typedef struct mares_iconhd_device_t {
unsigned char cache[20]; unsigned char cache[20];
unsigned int available; unsigned int available;
unsigned int offset; unsigned int offset;
unsigned int splitcommand;
} mares_iconhd_device_t; } mares_iconhd_device_t;
static dc_status_t mares_iconhd_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t mares_iconhd_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
@ -250,14 +251,17 @@ mares_iconhd_packet (mares_iconhd_device_t *device,
{ {
dc_status_t status = DC_STATUS_SUCCESS; dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t *abstract = (dc_device_t *) device; dc_device_t *abstract = (dc_device_t *) device;
unsigned int split_csize;
assert (csize >= 2); assert (csize >= 2);
if (device_is_cancelled (abstract)) if (device_is_cancelled (abstract))
return DC_STATUS_CANCELLED; return DC_STATUS_CANCELLED;
split_csize = device->splitcommand ? 2 : csize;
// Send the command header to the dive computer. // Send the command header to the dive computer.
status = mares_iconhd_write (device, command, 2); status = mares_iconhd_write (device, command, split_csize);
if (status != DC_STATUS_SUCCESS) { if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command."); ERROR (abstract->context, "Failed to send the command.");
return status; return status;
@ -277,9 +281,9 @@ mares_iconhd_packet (mares_iconhd_device_t *device,
return DC_STATUS_PROTOCOL; return DC_STATUS_PROTOCOL;
} }
// Send the command payload to the dive computer. // Send any remaining command payload to the dive computer.
if (csize > 2) { if (csize > split_csize) {
status = mares_iconhd_write (device, command + 2, csize - 2); status = mares_iconhd_write (device, command + split_csize, csize - split_csize);
if (status != DC_STATUS_SUCCESS) { if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command."); ERROR (abstract->context, "Failed to send the command.");
return status; return status;
@ -476,6 +480,15 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_
device->available = 0; device->available = 0;
device->offset = 0; device->offset = 0;
/*
* At least the Mares Matrix needs the command to be split into
* base and argument, with a wait for the ACK byte in between.
*
* See commit 59bfb0f3189b ("Add support for the Mares Matrix")
* for details.
*/
device->splitcommand = 1;
// Set the serial communication protocol (115200 8E1). // Set the serial communication protocol (115200 8E1).
status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_EVEN, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_EVEN, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE);
if (status != DC_STATUS_SUCCESS) { if (status != DC_STATUS_SUCCESS) {
@ -575,6 +588,26 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_
break; break;
} }
if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE) {
/*
* Don't ask for larger amounts of data with the BLE
* transport - it will fail. I suspect there is a buffer
* overflow in the BlueLink Pro dongle when bluetooth is
* slower than the serial protocol that the dongle talks to
* the dive computer.
*/
if (device->packetsize > 128)
device->packetsize = 128;
/*
* With BLE, don't wait for ACK before sending the arguments
* to a command.
*
* There is some timing issue that makes that take too long
* and causes the command to be aborted.
*/
device->splitcommand = 0;
}
*out = (dc_device_t *) device; *out = (dc_device_t *) device;
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;

View File

@ -1011,7 +1011,7 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream
} else if (OCEANIC_COMMON_MATCH (device->base.version, oceanic_default_version)) { } else if (OCEANIC_COMMON_MATCH (device->base.version, oceanic_default_version)) {
device->base.layout = &oceanic_default_layout; device->base.layout = &oceanic_default_layout;
} else { } else {
WARNING (context, "Unsupported device detected!"); WARNING (context, "Unsupported device detected (%s)!", device->base.version);
device->base.layout = &oceanic_default_layout; device->base.layout = &oceanic_default_layout;
if (memcmp(device->base.version + 12, "256K", 4) == 0) { if (memcmp(device->base.version + 12, "256K", 4) == 0) {
device->base.layout = &oceanic_atom1_layout; device->base.layout = &oceanic_atom1_layout;

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>
@ -113,6 +115,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;
@ -142,7 +145,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;
@ -195,6 +198,7 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, unsigned
parser->headersize = 5 * PAGESIZE / 2; parser->headersize = 5 * PAGESIZE / 2;
} }
parser->serial = serial;
parser->cached = 0; parser->cached = 0;
parser->header = 0; parser->header = 0;
parser->footer = 0; parser->footer = 0;
@ -396,6 +400,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)
@ -543,6 +548,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) {
@ -572,7 +580,7 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns
break; break;
case DC_FIELD_SALINITY: case DC_FIELD_SALINITY:
if (parser->model == A300CS || parser->model == VTX || if (parser->model == A300CS || parser->model == VTX ||
parser->model == I750TC) { parser->model == I750TC || parser->model == I770R) {
if (data[0x18] & 0x80) { if (data[0x18] & 0x80) {
water->type = DC_WATER_FRESH; water->type = DC_WATER_FRESH;
} else { } else {
@ -598,6 +606,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;
} }
@ -752,7 +771,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
if (have_pressure) { if (have_pressure) {
unsigned int idx = 2; unsigned int idx = 2;
if (parser->model == A300CS || parser->model == VTX || if (parser->model == A300CS || parser->model == VTX ||
parser->model == I750TC) parser->model == I750TC || parser->model == I770R)
idx = 16; idx = 16;
pressure = array_uint16_le(data + parser->header + idx); pressure = array_uint16_le(data + parser->header + idx);
if (pressure == 10000) if (pressure == 10000)
@ -805,7 +824,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
tank = 0; tank = 0;
pressure = (((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF); pressure = (((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF);
} else if (parser->model == A300CS || parser->model == VTX || } else if (parser->model == A300CS || parser->model == VTX ||
parser->model == I750TC) { parser->model == I750TC || parser->model == I770R) {
// Tank pressure (1 psi) and number (one based index) // Tank pressure (1 psi) and number (one based index)
tank = (data[offset + 1] & 0x03) - 1; tank = (data[offset + 1] & 0x03) - 1;
pressure = ((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF; pressure = ((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF;

View File

@ -57,6 +57,8 @@
#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 "deepblu.h"
#include "context-private.h" #include "context-private.h"
#include "parser-private.h" #include "parser-private.h"
@ -65,7 +67,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;
@ -88,7 +90,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);
@ -119,7 +121,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:
@ -132,11 +134,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); rc = hw_ostc_parser_create (&parser, context, serial);
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:
@ -152,10 +154,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);
@ -172,6 +174,12 @@ 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;
case DC_FAMILY_DEEPBLU:
rc = deepblu_parser_create (&parser, context);
break;
default: default:
return DC_STATUS_INVALIDARGS; return DC_STATUS_INVALIDARGS;
} }
@ -188,7 +196,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);
} }
@ -196,7 +206,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

@ -212,28 +212,41 @@ 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: case 0x0F0F:
case 0x1F0A:
model = TERIC; model = TERIC;
break; break;
default: default:
WARNING (abstract->context, "Unknown hardware type %04x.", hardware); // return a model of 0 which is unknown
WARNING (abstract->context, "Unknown hardware type %04x. Assuming Petrel.", hardware);
} }
// Emit a device info event. // Emit a device info event.

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

@ -28,6 +28,7 @@
#include "context-private.h" #include "context-private.h"
#include "parser-private.h" #include "parser-private.h"
#include "array.h" #include "array.h"
#include "field-cache.h"
#define ISINSTANCE(parser) ( \ #define ISINSTANCE(parser) ( \
dc_parser_isinstance((parser), &shearwater_predator_parser_vtable) || \ dc_parser_isinstance((parser), &shearwater_predator_parser_vtable) || \
@ -51,6 +52,7 @@
#define LOG_RECORD_CLOSING_5 0x25 #define LOG_RECORD_CLOSING_5 0x25
#define LOG_RECORD_CLOSING_6 0x26 #define LOG_RECORD_CLOSING_6 0x26
#define LOG_RECORD_CLOSING_7 0x27 #define LOG_RECORD_CLOSING_7 0x27
#define LOG_RECORD_INFO_EVENT 0x30
#define LOG_RECORD_FINAL 0xFF #define LOG_RECORD_FINAL 0xFF
#define SZ_BLOCK 0x80 #define SZ_BLOCK 0x80
@ -68,12 +70,15 @@
#define IMPERIAL 1 #define IMPERIAL 1
#define NGASMIXES 10 #define NGASMIXES 10
#define MAXSTRINGS 32
#define NTANKS 2 #define NTANKS 2
#define NRECORDS 8 #define NRECORDS 8
#define PREDATOR 2 #define PREDATOR 2
#define PETREL 3 #define PETREL 3
#define INFO_EVENT_TAG_LOG 38
#define UNDEFINED 0xFFFFFFFF #define UNDEFINED 0xFFFFFFFF
typedef struct shearwater_predator_parser_t shearwater_predator_parser_t; typedef struct shearwater_predator_parser_t shearwater_predator_parser_t;
@ -87,6 +92,7 @@ typedef struct shearwater_predator_tank_t {
unsigned int enabled; unsigned int enabled;
unsigned int beginpressure; unsigned int beginpressure;
unsigned int endpressure; unsigned int endpressure;
unsigned int battery;
} shearwater_predator_tank_t; } shearwater_predator_tank_t;
struct shearwater_predator_parser_t { struct shearwater_predator_parser_t {
@ -110,10 +116,13 @@ struct shearwater_predator_parser_t {
unsigned int tankidx[NTANKS]; unsigned int tankidx[NTANKS];
unsigned int calibrated; unsigned int calibrated;
double calibration[3]; double calibration[3];
dc_divemode_t mode; unsigned int serial;
unsigned int units; unsigned int units;
unsigned int atmospheric; unsigned int atmospheric;
unsigned int density; unsigned int density;
/* Generic field cache */
struct dc_field_cache cache;
}; };
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);
@ -159,7 +168,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;
@ -187,6 +196,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->pnf = 0; parser->pnf = 0;
parser->logversion = 0; parser->logversion = 0;
@ -207,17 +219,19 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig
parser->tank[i].enabled = 0; parser->tank[i].enabled = 0;
parser->tank[i].beginpressure = 0; parser->tank[i].beginpressure = 0;
parser->tank[i].endpressure = 0; parser->tank[i].endpressure = 0;
parser->tank[i].battery = 0;
parser->tankidx[i] = i; parser->tankidx[i] = i;
} }
parser->calibrated = 0; parser->calibrated = 0;
for (unsigned int i = 0; i < 3; ++i) { for (unsigned int i = 0; i < 3; ++i) {
parser->calibration[i] = 0.0; parser->calibration[i] = 0.0;
} }
parser->mode = DC_DIVEMODE_OC;
parser->units = METRIC; parser->units = METRIC;
parser->density = 1025; parser->density = 1025;
parser->atmospheric = ATM / (BAR / 1000); parser->atmospheric = ATM / (BAR / 1000);
DC_ASSIGN_FIELD(parser->cache, DIVEMODE, DC_DIVEMODE_OC);
*out = (dc_parser_t *) parser; *out = (dc_parser_t *) parser;
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
@ -225,16 +239,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);
} }
@ -270,11 +284,12 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char
for (unsigned int i = 0; i < 3; ++i) { for (unsigned int i = 0; i < 3; ++i) {
parser->calibration[i] = 0.0; parser->calibration[i] = 0.0;
} }
parser->mode = DC_DIVEMODE_OC;
parser->units = METRIC; parser->units = METRIC;
parser->density = 1025; parser->density = 1025;
parser->atmospheric = ATM / (BAR / 1000); parser->atmospheric = ATM / (BAR / 1000);
DC_ASSIGN_FIELD(parser->cache, DIVEMODE, DC_DIVEMODE_OC);
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
@ -300,6 +315,84 @@ shearwater_predator_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *d
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
// 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)
{
// We don't know what other state bits than 0-2 mean
state &= 7;
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
};
dc_field_add_string(&parser->cache, 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->opening[2] + 18 : 67;
unsigned int idx_gfs = parser->pnf ? parser->opening[3] + 5 : 85;
switch (data[idx_deco_model]) {
case 0:
dc_field_add_string_fmt(&parser->cache, "Deco model", "GF %u/%u", data[4], data[5]);
break;
case 1:
dc_field_add_string_fmt(&parser->cache, "Deco model", "VPM-B +%u", data[idx_deco_model + 1]);
break;
case 2:
dc_field_add_string_fmt(&parser->cache, "Deco model", "VPM-B/GFS +%u %u%%", data[idx_deco_model + 1], data[idx_gfs]);
break;
default:
dc_field_add_string_fmt(&parser->cache, "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->opening[4] + 9 : 120;
switch (data[idx_battery_type]) {
case 1:
dc_field_add_string(&parser->cache, "Battery type", "1.5V Alkaline");
break;
case 2:
dc_field_add_string(&parser->cache, "Battery type", "1.5V Lithium");
break;
case 3:
dc_field_add_string(&parser->cache, "Battery type", "1.2V NiMH");
break;
case 4:
dc_field_add_string(&parser->cache, "Battery type", "3.6V Saft");
break;
case 5:
dc_field_add_string(&parser->cache, "Battery type", "3.7V Li-Ion");
break;
default:
dc_field_add_string_fmt(&parser->cache, "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)
@ -311,6 +404,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
if (parser->cached) { if (parser->cached) {
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
memset(&parser->cache, 0, sizeof(parser->cache));
// 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
@ -432,13 +526,16 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
// 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 + pnf + idx[i]); unsigned int pressure = array_uint16_be (data + offset + pnf + idx[i]);
if (pressure < 0xFFF0) { if (pressure < 0xFFF0) {
unsigned int battery = 1u << (pressure >> 12);
pressure &= 0x0FFF; pressure &= 0x0FFF;
if (!tank[i].enabled) { if (!tank[i].enabled) {
tank[i].enabled = 1; tank[i].enabled = 1;
tank[i].beginpressure = pressure; tank[i].beginpressure = pressure;
tank[i].endpressure = pressure; tank[i].endpressure = pressure;
tank[i].battery = 0;
} }
tank[i].endpressure = pressure; tank[i].endpressure = pressure;
tank[i].battery |= battery;
} }
} }
} }
@ -472,6 +569,8 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
} }
} }
dc_field_add_string_fmt(&parser->cache, "Logversion", "%d%s", logversion, pnf ? "(PNF)" : "");
// Cache sensor calibration for later use // Cache sensor calibration for later use
unsigned int nsensors = 0, ndefaults = 0; unsigned int nsensors = 0, ndefaults = 0;
unsigned int base = parser->opening[3] + (pnf ? 6 : 86); unsigned int base = parser->opening[3] + (pnf ? 6 : 86);
@ -500,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)
dc_field_add_string(&parser->cache, "PPO2 source", "voted/averaged");
} else { } else {
parser->calibrated = data[base]; parser->calibrated = data[base];
if (mode != DC_DIVEMODE_OC)
dc_field_add_string(&parser->cache, "PPO2 source", "cells");
} }
// Cache the data for later use. // Cache the data for later use.
@ -523,12 +626,22 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
parser->tankidx[i] = UNDEFINED; parser->tankidx[i] = UNDEFINED;
} }
} }
parser->mode = mode;
parser->units = data[parser->opening[0] + 8]; parser->units = data[parser->opening[0] + 8];
parser->atmospheric = array_uint16_be (data + parser->opening[1] + (parser->pnf ? 16 : 47)); parser->atmospheric = array_uint16_be (data + parser->opening[1] + (parser->pnf ? 16 : 47));
parser->density = array_uint16_be (data + parser->opening[3] + (parser->pnf ? 3 : 83)); parser->density = array_uint16_be (data + parser->opening[3] + (parser->pnf ? 3 : 83));
parser->cached = 1; parser->cached = 1;
DC_ASSIGN_FIELD(parser->cache, DIVEMODE, mode);
dc_field_add_string_fmt(&parser->cache, "Serial", "%08x", parser->serial);
// bytes 1-31 are identical in all formats
dc_field_add_string_fmt(&parser->cache, "FW Version", "%2x", data[19]);
add_deco_model(parser, data);
add_battery_type(parser, data);
dc_field_add_string_fmt(&parser->cache, "Battery at end", "%.1f V", data[9] / 10.0);
add_battery_info(parser, "T1 battery", tank[0].battery);
add_battery_info(parser, "T2 battery", tank[1].battery);
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
@ -547,6 +660,7 @@ 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_tank_t *tank = (dc_tank_t *) value; dc_tank_t *tank = (dc_tank_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;
if (value) { if (value) {
switch (type) { switch (type) {
@ -594,8 +708,9 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ
*((double *) value) = parser->atmospheric / 1000.0; *((double *) value) = parser->atmospheric / 1000.0;
break; break;
case DC_FIELD_DIVEMODE: case DC_FIELD_DIVEMODE:
*((dc_divemode_t *) value) = parser->mode; return DC_FIELD_VALUE(parser->cache, value, DIVEMODE);
break; case DC_FIELD_STRING:
return dc_field_get_string(&parser->cache, flags, string);
default: default:
return DC_STATUS_UNSUPPORTED; return DC_STATUS_UNSUPPORTED;
} }
@ -639,6 +754,41 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
while (offset + parser->samplesize <= length) { while (offset + parser->samplesize <= 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;
if (pnf && data[offset] == LOG_RECORD_INFO_EVENT) {
// additional events defined in PNF
INFO(abstract->context, "PNF INFO_EVENT ID %u time %u W1 %u W2 %u", data[offset + 1],
array_uint32_be (data + offset + 4),
array_uint32_be (data + offset + 8),
array_uint32_be (data + offset + 12));
if (data[offset + 1] == INFO_EVENT_TAG_LOG) {
// this is a TAG
// its time is a unix timestamp, so we need to subtract the dive start time
unsigned int tag_time = array_uint32_be (data + offset + 4) - array_uint32_be (data + parser->opening[0] + 12);
unsigned int tag_heading = array_uint32_be (data + offset + 8);
unsigned int tag_type = array_uint32_be (data + offset + 12);
// heading is only valid if 0..360
// type is only valid if 0..5
if (tag_heading <= 360 && tag_type <= 5) {
// encode this as a bookmark event, using the flags to capture the type and value for heading
sample.event.type = SAMPLE_EVENT_BOOKMARK;
sample.event.time = tag_time;
sample.event.flags = (tag_type + 1) << SAMPLE_FLAGS_TYPE_SHIFT; // 0 means it isn't a tag
sample.event.value = tag_heading;
if (callback) callback (DC_SAMPLE_EVENT, sample, userdata);
}
}
offset += parser->samplesize;
continue;
}
// 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;
@ -683,19 +833,19 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
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 + pnf + 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 + pnf + 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 + pnf + 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 + pnf + 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
@ -815,6 +965,5 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
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;
@ -244,7 +246,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;
@ -260,6 +262,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;
@ -343,6 +346,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)
@ -358,6 +362,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) {
@ -405,6 +412,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

@ -641,12 +641,35 @@ read_file(suunto_eonsteel_device_t *eon, const char *filename, dc_buffer_t *buf)
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
/*
* Insert a directory entry in the sorted list, most recent entry
* first.
*
* The directory entry names are the timestamps as hex, so ordering
* in alphabetical order ends up also ordering in date order!
*/
static struct directory_entry *insert_dirent(struct directory_entry *entry, struct directory_entry *list)
{
struct directory_entry **pos = &list, *next;
while ((next = *pos) != NULL) {
/* Is this bigger (more recent) than the next entry? We're good! */
if (strcmp(entry->name, next->name) > 0)
break;
pos = &next->next;
}
entry->next = next;
*pos = entry;
return list;
}
/* /*
* NOTE! This will create the list of dirent's in reverse order, * NOTE! This will create the list of dirent's in reverse order,
* with the last dirent first. That's intentional: for dives, * with the last dirent first. That's intentional: for dives,
* we will want to look up the last dive first. * we will want to look up the last dive first.
*/ */
static struct directory_entry *parse_dirent(suunto_eonsteel_device_t *eon, int nr, const unsigned char *p, unsigned int len, struct directory_entry *old) static struct directory_entry *parse_dirent(suunto_eonsteel_device_t *eon, int nr, const unsigned char *p, unsigned int len, struct directory_entry *list)
{ {
while (len > 8) { while (len > 8) {
unsigned int type = array_uint32_le(p); unsigned int type = array_uint32_le(p);
@ -667,10 +690,9 @@ static struct directory_entry *parse_dirent(suunto_eonsteel_device_t *eon, int n
ERROR(eon->base.context, "out of memory"); ERROR(eon->base.context, "out of memory");
break; break;
} }
entry->next = old; list = insert_dirent(entry, list);
old = entry;
} }
return old; return list;
} }
static dc_status_t static dc_status_t
@ -731,6 +753,19 @@ get_file_list(suunto_eonsteel_device_t *eon, struct directory_entry **res)
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
static int
count_file_list(struct directory_entry *list)
{
int count = 0;
while (list) {
count++;
list = list->next;
}
return count;
}
dc_status_t dc_status_t
suunto_eonsteel_device_open(dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream, unsigned int model) suunto_eonsteel_device_open(dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream, unsigned int model)
{ {
@ -802,7 +837,6 @@ suunto_eonsteel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callbac
dc_buffer_t *file; dc_buffer_t *file;
char pathname[64]; char pathname[64];
unsigned int time; unsigned int time;
unsigned int count = 0;
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
// Emit a device info event. // Emit a device info event.
@ -820,46 +854,17 @@ suunto_eonsteel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callbac
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
// Locate the most recent dive.
// The filename represent the time of the dive, encoded as a hexadecimal
// number. Thus the most recent dive can be found by simply sorting the
// filenames alphabetically.
struct directory_entry *head = de, *tail = de, *latest = de;
while (de) {
if (strcmp (de->name, latest->name) > 0) {
latest = de;
}
tail = de;
count++;
de = de->next;
}
// Make the most recent dive the head of the list.
// The linked list is made circular, by attaching the head to the tail and
// then cut open again just before the most recent dive.
de = head;
while (de) {
if (de->next == latest) {
de->next = NULL;
tail->next = head;
break;
}
de = de->next;
}
file = dc_buffer_new (16384); file = dc_buffer_new (16384);
if (file == NULL) { if (file == NULL) {
ERROR (abstract->context, "Insufficient buffer space available."); ERROR (abstract->context, "Insufficient buffer space available.");
file_list_free (latest); file_list_free(de);
return DC_STATUS_NOMEMORY; return DC_STATUS_NOMEMORY;
} }
progress.maximum = count; progress.maximum = count_file_list(de);
progress.current = 0; progress.current = 0;
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
de = latest;
while (de) { while (de) {
int len; int len;
struct directory_entry *next = de->next; struct directory_entry *next = de->next;

View File

@ -29,6 +29,7 @@
#include "parser-private.h" #include "parser-private.h"
#include "array.h" #include "array.h"
#include "platform.h" #include "platform.h"
#include "field-cache.h"
#define C_ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) #define C_ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
@ -70,38 +71,15 @@ struct type_desc {
}; };
#define MAXTYPE 512 #define MAXTYPE 512
#define MAXGASES 16
typedef struct suunto_eonsteel_parser_t { typedef struct suunto_eonsteel_parser_t {
dc_parser_t base; dc_parser_t base;
struct type_desc type_desc[MAXTYPE]; struct type_desc type_desc[MAXTYPE];
// field cache struct dc_field_cache cache;
struct {
unsigned int initialized;
unsigned int divetime;
double maxdepth;
double avgdepth;
unsigned int ngases;
dc_gasmix_t gasmix[MAXGASES];
dc_salinity_t salinity;
double surface_pressure;
dc_divemode_t divemode;
double lowsetpoint;
double highsetpoint;
double customsetpoint;
dc_tankvolume_t tankinfo[MAXGASES];
double tanksize[MAXGASES];
double tankworkingpressure[MAXGASES];
} cache;
} suunto_eonsteel_parser_t; } 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 +145,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;
@ -468,8 +436,6 @@ struct sample_data {
/* We gather up deco and cylinder pressure information */ /* We gather up deco and cylinder pressure information */
int gasnr; int gasnr;
int tts, ndl;
double ceiling;
}; };
static void sample_time(struct sample_data *info, unsigned short time_delta) static void sample_time(struct sample_data *info, unsigned short time_delta)
@ -507,7 +473,6 @@ static void sample_ndl(struct sample_data *info, short ndl)
{ {
dc_sample_value_t sample = {0}; dc_sample_value_t sample = {0};
info->ndl = ndl;
if (ndl < 0) if (ndl < 0)
return; return;
@ -518,14 +483,27 @@ static void sample_ndl(struct sample_data *info, short ndl)
static void sample_tts(struct sample_data *info, unsigned short tts) static void sample_tts(struct sample_data *info, unsigned short tts)
{ {
if (tts != 0xffff) if (tts != 0xffff) {
info->tts = tts; dc_sample_value_t sample = {0};
sample.time = tts;
if (info->callback) info->callback(DC_SAMPLE_TTS, sample, info->userdata);
}
} }
static void sample_ceiling(struct sample_data *info, unsigned short ceiling) static void sample_ceiling(struct sample_data *info, unsigned short ceiling)
{ {
if (ceiling != 0xffff) if (ceiling != 0xffff) {
info->ceiling = ceiling / 100.0; dc_sample_value_t sample = {0};
// We don't actually have a time for the
// deco stop, we just have a ceiling.
//
// We'll just say it's one minute.
sample.deco.type = DC_DECO_DECOSTOP;
sample.deco.time = ceiling ? 60 : 0;
sample.deco.depth = ceiling / 100.0;
if (info->callback) info->callback(DC_SAMPLE_DECO, sample, info->userdata);
}
} }
static void sample_heading(struct sample_data *info, unsigned short heading) static void sample_heading(struct sample_data *info, unsigned short heading)
@ -597,7 +575,7 @@ static void sample_gas_switch_event(struct sample_data *info, unsigned short idx
suunto_eonsteel_parser_t *eon = info->eon; suunto_eonsteel_parser_t *eon = info->eon;
dc_sample_value_t sample = {0}; dc_sample_value_t sample = {0};
if (idx < 1 || idx > eon->cache.ngases) if (idx < 1 || idx > eon->cache.GASMIX_COUNT)
return; return;
sample.gasmix = idx - 1; sample.gasmix = idx - 1;
@ -681,26 +659,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 +681,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 +688,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 +705,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 +712,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 +729,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);
} }
@ -976,10 +901,6 @@ static int traverse_samples(unsigned short type, const struct type_desc *desc, c
if (desc->size > len) if (desc->size > len)
ERROR(eon->base.context, "Got %d bytes of data for '%s' that wants %d bytes", len, desc->desc, desc->size); ERROR(eon->base.context, "Got %d bytes of data for '%s' that wants %d bytes", len, desc->desc, desc->size);
info->ndl = -1;
info->tts = 0;
info->ceiling = 0.0;
for (i = 0; i < EON_MAX_GROUP; i++) { for (i = 0; i < EON_MAX_GROUP; i++) {
enum eon_sample type = desc->type[i]; enum eon_sample type = desc->type[i];
int bytes = handle_sample_type(desc, info, type, data); int bytes = handle_sample_type(desc, info, type, data);
@ -995,15 +916,6 @@ static int traverse_samples(unsigned short type, const struct type_desc *desc, c
used += bytes; used += bytes;
} }
if (info->ndl < 0 && (info->tts || info->ceiling)) {
dc_sample_value_t sample = {0};
sample.deco.type = DC_DECO_DECOSTOP;
sample.deco.time = info->tts;
sample.deco.depth = info->ceiling;
if (info->callback) info->callback(DC_SAMPLE_DECO, sample, info->userdata);
}
// Warn if there are left-over bytes for something we did use part of // Warn if there are left-over bytes for something we did use part of
if (used && len) if (used && len)
ERROR(eon->base.context, "Entry for '%s' had %d bytes, only used %d", desc->desc, len+used, used); ERROR(eon->base.context, "Entry for '%s' had %d bytes, only used %d", desc->desc, len+used, used);
@ -1026,11 +938,6 @@ suunto_eonsteel_parser_samples_foreach(dc_parser_t *abstract, dc_sample_callback
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
// Ugly define thing makes the code much easier to read
// I'd love to use __typeof__, but that's a gcc'ism
#define field_value(p, set) \
memcpy((p), &(set), sizeof(set))
static dc_status_t static dc_status_t
suunto_eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsigned int flags, void *value) suunto_eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsigned int flags, void *value)
{ {
@ -1043,33 +950,27 @@ suunto_eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsi
switch (type) { switch (type) {
case DC_FIELD_DIVETIME: case DC_FIELD_DIVETIME:
field_value(value, eon->cache.divetime); return DC_FIELD_VALUE(eon->cache, value, DIVETIME);
break;
case DC_FIELD_MAXDEPTH: case DC_FIELD_MAXDEPTH:
field_value(value, eon->cache.maxdepth); return DC_FIELD_VALUE(eon->cache, value, MAXDEPTH);
break;
case DC_FIELD_AVGDEPTH: case DC_FIELD_AVGDEPTH:
field_value(value, eon->cache.avgdepth); return DC_FIELD_VALUE(eon->cache, value, AVGDEPTH);
break;
case DC_FIELD_GASMIX_COUNT: case DC_FIELD_GASMIX_COUNT:
case DC_FIELD_TANK_COUNT: case DC_FIELD_TANK_COUNT:
field_value(value, eon->cache.ngases); return DC_FIELD_VALUE(eon->cache, value, GASMIX_COUNT);
break;
case DC_FIELD_GASMIX: case DC_FIELD_GASMIX:
if (flags >= MAXGASES) if (flags >= MAXGASES)
return DC_STATUS_UNSUPPORTED; return DC_STATUS_UNSUPPORTED;
field_value(value, eon->cache.gasmix[flags]); return DC_FIELD_INDEX(eon->cache, value, GASMIX, flags);
break;
case DC_FIELD_SALINITY: case DC_FIELD_SALINITY:
field_value(value, eon->cache.salinity); return DC_FIELD_VALUE(eon->cache, value, SALINITY);
break;
case DC_FIELD_ATMOSPHERIC: case DC_FIELD_ATMOSPHERIC:
field_value(value, eon->cache.surface_pressure); return DC_FIELD_VALUE(eon->cache, value, ATMOSPHERIC);
break;
case DC_FIELD_DIVEMODE: case DC_FIELD_DIVEMODE:
field_value(value, eon->cache.divemode); return DC_FIELD_VALUE(eon->cache, value, DIVEMODE);
break;
case DC_FIELD_TANK: case DC_FIELD_TANK:
if (flags >= MAXGASES)
return DC_STATUS_UNSUPPORTED;
/* /*
* Sadly it seems that the EON Steel doesn't tell us whether * Sadly it seems that the EON Steel doesn't tell us whether
* we get imperial or metric data - the only indication is * we get imperial or metric data - the only indication is
@ -1094,11 +995,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 dc_field_get_string(&eon->cache, flags, (dc_field_string_t *)value);
default: default:
return DC_STATUS_UNSUPPORTED; return DC_STATUS_UNSUPPORTED;
} }
@ -1127,7 +1030,7 @@ suunto_eonsteel_parser_get_datetime(dc_parser_t *parser, dc_datetime_t *datetime
// time in ms // time in ms
static void add_time_field(suunto_eonsteel_parser_t *eon, unsigned short time_delta_ms) static void add_time_field(suunto_eonsteel_parser_t *eon, unsigned short time_delta_ms)
{ {
eon->cache.divetime += time_delta_ms; eon->cache.DIVETIME += time_delta_ms;
} }
// depth in cm // depth in cm
@ -1135,8 +1038,8 @@ static void set_depth_field(suunto_eonsteel_parser_t *eon, unsigned short d)
{ {
if (d != 0xffff) { if (d != 0xffff) {
double depth = d / 100.0; double depth = d / 100.0;
if (depth > eon->cache.maxdepth) if (depth > eon->cache.MAXDEPTH)
eon->cache.maxdepth = depth; eon->cache.MAXDEPTH = depth;
eon->cache.initialized |= 1 << DC_FIELD_MAXDEPTH; eon->cache.initialized |= 1 << DC_FIELD_MAXDEPTH;
} }
} }
@ -1149,28 +1052,28 @@ 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
// get a working pressure and non-integral size // get a working pressure and non-integral size
static int add_gas_type(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, unsigned char type) static dc_status_t 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.GASMIX_COUNT;
dc_tankvolume_t tankinfo = DC_TANKVOLUME_METRIC; dc_tankinfo_t tankinfo = DC_TANKINFO_METRIC;
char *name; char *name;
if (idx >= MAXGASES) if (idx >= MAXGASES)
return 0; return DC_STATUS_SUCCESS;
eon->cache.ngases = idx+1; eon->cache.GASMIX_COUNT = idx+1;
name = lookup_enum(desc, type); name = lookup_enum(desc, type);
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"))
@ -1181,46 +1084,46 @@ static int add_gas_type(suunto_eonsteel_parser_t *eon, const struct type_desc *d
eon->cache.initialized |= 1 << DC_FIELD_GASMIX_COUNT; eon->cache.initialized |= 1 << DC_FIELD_GASMIX_COUNT;
eon->cache.initialized |= 1 << DC_FIELD_TANK_COUNT; eon->cache.initialized |= 1 << DC_FIELD_TANK_COUNT;
free(name); free(name);
return 0; return DC_STATUS_SUCCESS;
} }
// "sml.DeviceLog.Header.Diving.Gases.Gas.Oxygen" // "sml.DeviceLog.Header.Diving.Gases.Gas.Oxygen"
// O2 percentage as a byte // O2 percentage as a byte
static int add_gas_o2(suunto_eonsteel_parser_t *eon, unsigned char o2) static dc_status_t add_gas_o2(suunto_eonsteel_parser_t *eon, unsigned char o2)
{ {
int idx = eon->cache.ngases-1; int idx = eon->cache.GASMIX_COUNT-1;
if (idx >= 0) if (idx >= 0)
eon->cache.gasmix[idx].oxygen = o2 / 100.0; eon->cache.GASMIX[idx].oxygen = o2 / 100.0;
eon->cache.initialized |= 1 << DC_FIELD_GASMIX; eon->cache.initialized |= 1 << DC_FIELD_GASMIX;
return 0; return DC_STATUS_SUCCESS;
} }
// "sml.DeviceLog.Header.Diving.Gases.Gas.Helium" // "sml.DeviceLog.Header.Diving.Gases.Gas.Helium"
// He percentage as a byte // He percentage as a byte
static int add_gas_he(suunto_eonsteel_parser_t *eon, unsigned char he) static dc_status_t add_gas_he(suunto_eonsteel_parser_t *eon, unsigned char he)
{ {
int idx = eon->cache.ngases-1; int idx = eon->cache.GASMIX_COUNT-1;
if (idx >= 0) if (idx >= 0)
eon->cache.gasmix[idx].helium = he / 100.0; eon->cache.GASMIX[idx].helium = he / 100.0;
eon->cache.initialized |= 1 << DC_FIELD_GASMIX; eon->cache.initialized |= 1 << DC_FIELD_GASMIX;
return 0; return DC_STATUS_SUCCESS;
} }
static int add_gas_size(suunto_eonsteel_parser_t *eon, float l) static dc_status_t add_gas_size(suunto_eonsteel_parser_t *eon, float l)
{ {
int idx = eon->cache.ngases-1; int idx = eon->cache.GASMIX_COUNT-1;
if (idx >= 0) if (idx >= 0)
eon->cache.tanksize[idx] = l; eon->cache.tanksize[idx] = l;
eon->cache.initialized |= 1 << DC_FIELD_TANK; eon->cache.initialized |= 1 << DC_FIELD_TANK;
return 0; return DC_STATUS_SUCCESS;
} }
static int add_gas_workpressure(suunto_eonsteel_parser_t *eon, float wp) static dc_status_t add_gas_workpressure(suunto_eonsteel_parser_t *eon, float wp)
{ {
int idx = eon->cache.ngases-1; int idx = eon->cache.GASMIX_COUNT-1;
if (idx >= 0) if (idx >= 0)
eon->cache.tankworkingpressure[idx] = wp; eon->cache.tankworkingpressure[idx] = wp;
return 0; return DC_STATUS_SUCCESS;
} }
static float get_le32_float(const unsigned char *src) static float get_le32_float(const unsigned char *src)
@ -1242,12 +1145,22 @@ static float get_le32_float(const unsigned char *src)
// Info.SW // Info.SW
// Name // Name
// SerialNumber // SerialNumber
static int traverse_device_fields(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, static dc_status_t traverse_device_fields(suunto_eonsteel_parser_t *eon,
const unsigned char *data, int len) const struct type_desc *desc,
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 0; return dc_field_add_string(&eon->cache, "Serial", data);
if (!strcmp(name, "Info.HW"))
return dc_field_add_string(&eon->cache, "HW Version", data);
if (!strcmp(name, "Info.SW"))
return dc_field_add_string(&eon->cache, "FW Version", data);
if (!strcmp(name, "Info.BatteryAtStart"))
return dc_field_add_string(&eon->cache, "Battery at start", data);
if (!strcmp(name, "Info.BatteryAtEnd"))
return dc_field_add_string(&eon->cache, "Battery at end", data);
return DC_STATUS_SUCCESS;
} }
// "sml.DeviceLog.Header.Diving.Gases" // "sml.DeviceLog.Header.Diving.Gases"
@ -1263,8 +1176,9 @@ static int traverse_device_fields(suunto_eonsteel_parser_t *eon, const struct ty
// .Gas.EndPressure (float32,precision=0) // .Gas.EndPressure (float32,precision=0)
// .Gas.TransmitterStartBatteryCharge (int8,precision=2) // .Gas.TransmitterStartBatteryCharge (int8,precision=2)
// .Gas.TransmitterEndBatteryCharge (int8,precision=2) // .Gas.TransmitterEndBatteryCharge (int8,precision=2)
static int traverse_gas_fields(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, static dc_status_t traverse_gas_fields(suunto_eonsteel_parser_t *eon,
const unsigned char *data, int len) const struct type_desc *desc,
const unsigned char *data, int len)
{ {
const char *name = desc->desc + strlen("sml.DeviceLog.Header.Diving.Gases"); const char *name = desc->desc + strlen("sml.DeviceLog.Header.Diving.Gases");
@ -1277,13 +1191,31 @@ 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 dc_field_add_string(&eon->cache, "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));
return 0; // 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 dc_field_add_string_fmt(&eon->cache, "Transmitter Battery at start", "%d %%", data[0]);
if (!strcmp(name, ".Gas.TransmitterEndBatteryCharge"))
return dc_field_add_string_fmt(&eon->cache, "Transmitter Battery at end", "%d %%", data[0]);
return DC_STATUS_SUCCESS;
} }
@ -1324,8 +1256,9 @@ static int traverse_gas_fields(suunto_eonsteel_parser_t *eon, const struct type_
// EndTissue.Helium+Pressure (uint32) // EndTissue.Helium+Pressure (uint32)
// EndTissue.RgbmNitrogen (float32,precision=3) // EndTissue.RgbmNitrogen (float32,precision=3)
// EndTissue.RgbmHelium (float32,precision=3) // EndTissue.RgbmHelium (float32,precision=3)
static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, static dc_status_t traverse_diving_fields(suunto_eonsteel_parser_t *eon,
const unsigned char *data, int len) const struct type_desc *desc,
const unsigned char *data, int len)
{ {
const char *name = desc->desc + strlen("sml.DeviceLog.Header.Diving."); const char *name = desc->desc + strlen("sml.DeviceLog.Header.Diving.");
@ -1334,17 +1267,25 @@ static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct ty
if (!strcmp(name, "SurfacePressure")) { if (!strcmp(name, "SurfacePressure")) {
unsigned int pressure = array_uint32_le(data); // in SI units - Pascal unsigned int pressure = array_uint32_le(data); // in SI units - Pascal
eon->cache.surface_pressure = pressure / 100000.0; // bar DC_ASSIGN_FIELD(eon->cache, ATMOSPHERIC, pressure / 100000.0); // bar
eon->cache.initialized |= 1 << DC_FIELD_ATMOSPHERIC; return DC_STATUS_SUCCESS;
return 0;
} }
if (!strcmp(name, "Algorithm"))
return dc_field_add_string(&eon->cache, "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; DC_ASSIGN_FIELD(eon->cache, DIVEMODE, DC_DIVEMODE_CCR);
eon->cache.initialized |= 1 << DC_FIELD_DIVEMODE;
} }
return 0; return dc_field_add_string(&eon->cache, "Dive Mode", data);
}
/* Signed byte of conservatism (-2 .. +2) */
if (!strcmp(name, "Conservatism")) {
int val = *(signed char *)data;
return dc_field_add_string_fmt(&eon->cache, "Personal Adjustment", "P%d", val);
} }
if (!strcmp(name, "LowSetPoint")) { if (!strcmp(name, "LowSetPoint")) {
@ -1359,7 +1300,19 @@ static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct ty
return 0; 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 dc_field_add_string_fmt(&eon->cache, "Desaturation Time", "%d:%02d", time / 60, time % 60);
}
if (!strcmp(name, "SurfaceTime")) {
unsigned int time = array_uint32_le(data) / 60;
return dc_field_add_string_fmt(&eon->cache, "Surface Time", "%d:%02d", time / 60, time % 60);
}
return DC_STATUS_SUCCESS;
} }
// "Header" fields are: // "Header" fields are:
@ -1371,8 +1324,9 @@ static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct ty
// Duration (uint32) // Duration (uint32)
// PauseDuration (uint32) // PauseDuration (uint32)
// SampleInterval (uint8) // SampleInterval (uint8)
static int traverse_header_fields(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, static dc_status_t traverse_header_fields(suunto_eonsteel_parser_t *eon,
const unsigned char *data, int len) const struct type_desc *desc,
const unsigned char *data, int len)
{ {
const char *name = desc->desc + strlen("sml.DeviceLog.Header."); const char *name = desc->desc + strlen("sml.DeviceLog.Header.");
@ -1381,15 +1335,17 @@ static int traverse_header_fields(suunto_eonsteel_parser_t *eon, const struct ty
if (!strcmp(name, "Depth.Max")) { if (!strcmp(name, "Depth.Max")) {
double d = get_le32_float(data); double d = get_le32_float(data);
if (d > eon->cache.maxdepth) if (d > eon->cache.MAXDEPTH)
eon->cache.maxdepth = d; DC_ASSIGN_FIELD(eon->cache, MAXDEPTH, d);
return 0; return DC_STATUS_SUCCESS;
} }
if (!strcmp(name, "DateTime"))
return dc_field_add_string(&eon->cache, "Dive ID", data);
return 0; return DC_STATUS_SUCCESS;
} }
static int traverse_dynamic_fields(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, const unsigned char *data, int len) static dc_status_t traverse_dynamic_fields(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, const unsigned char *data, int len)
{ {
const char *name = desc->desc; const char *name = desc->desc;
@ -1404,7 +1360,7 @@ static int traverse_dynamic_fields(suunto_eonsteel_parser_t *eon, const struct t
} }
} }
} }
return 0; return DC_STATUS_SUCCESS;
} }
/* /*
@ -1428,6 +1384,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;
} }
@ -1457,7 +1415,7 @@ static void initialize_field_caches(suunto_eonsteel_parser_t *eon)
// The internal time fields are in ms and have to be added up // The internal time fields are in ms and have to be added up
// like that. At the end, we translate it back to seconds. // like that. At the end, we translate it back to seconds.
eon->cache.divetime /= 1000; eon->cache.DIVETIME /= 1000;
} }
static void show_descriptor(suunto_eonsteel_parser_t *eon, int nr, struct type_desc *desc) static void show_descriptor(suunto_eonsteel_parser_t *eon, int nr, struct type_desc *desc)

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;
}