Compare commits

...

93 Commits

Author SHA1 Message Date
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 3087 additions and 233 deletions

2
.gitignore vendored
View File

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

View File

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

View File

@ -90,6 +90,7 @@ static const backend_table_t g_backends[] = {
{"idive", DC_FAMILY_DIVESYSTEM_IDIVE, 0x03},
{"cochran", DC_FAMILY_COCHRAN_COMMANDER, 0},
{"divecomputereu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0},
{"descentmk1", DC_FAMILY_GARMIN, 0},
};
static const transport_table_t g_transports[] = {
@ -99,6 +100,7 @@ static const transport_table_t g_transports[] = {
{"irda", DC_TRANSPORT_IRDA},
{"bluetooth", DC_TRANSPORT_BLUETOOTH},
{"ble", DC_TRANSPORT_BLE},
{"usbstorage",DC_TRANSPORT_USBSTORAGE},
};
const char *
@ -537,6 +539,8 @@ dctool_iostream_open (dc_iostream_t **iostream, dc_context_t *context, dc_descri
return dctool_irda_open (iostream, context, descriptor, devname);
case DC_TRANSPORT_BLUETOOTH:
return dctool_bluetooth_open (iostream, context, descriptor, devname);
case DC_TRANSPORT_USBSTORAGE:
return dc_usb_storage_open (iostream, context, devname);
default:
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));
}
message ("Parsing strings.\n");
int idx;
for (idx = 0; idx < 100; idx++) {
dc_field_string_t str = { NULL };
status = dc_parser_get_field(parser, DC_FIELD_STRING, idx, &str);
if (status != DC_STATUS_SUCCESS && status != DC_STATUS_UNSUPPORTED) {
ERROR ("Error parsing strings");
goto cleanup;
}
if (status == DC_STATUS_UNSUPPORTED)
break;
if (!str.desc || !str.value)
break;
fprintf (output->ostream, "<extradata key='%s' value='%s' />\n",
str.desc, str.value);
}
// Parse the sample data.
message ("Parsing the sample data.\n");
status = dc_parser_samples_foreach (parser, sample_cb, &sampledata);

View File

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

View File

@ -45,6 +45,7 @@ typedef struct dc_custom_cbs_t {
dc_status_t (*purge) (void *userdata, dc_direction_t direction);
dc_status_t (*sleep) (void *userdata, unsigned int milliseconds);
dc_status_t (*close) (void *userdata);
const char *(*get_name) (void *userdata);
} dc_custom_cbs_t;
/**

View File

@ -283,6 +283,22 @@ dc_iostream_sleep (dc_iostream_t *iostream, unsigned int milliseconds);
dc_status_t
dc_iostream_close (dc_iostream_t *iostream);
/**
* Get the name of the device
*
* @param[in] iostream A valid I/O stream.
* @returns a NUL-terminated constant C string that has a lifetime
* until the close of the iostream, or NUL if no name exists.
*
* The name depends on the iostream transport. For a BLE device, it
* is the name of the device during device discovery.
*/
const char *
dc_iostream_get_name (dc_iostream_t *iostream);
dc_status_t
dc_usb_storage_open (dc_iostream_t **out, dc_context_t *context, const char *name);
#ifdef __cplusplus
}
#endif /* __cplusplus */

View File

@ -46,9 +46,13 @@ typedef enum dc_sample_type_t {
DC_SAMPLE_PPO2,
DC_SAMPLE_CNS,
DC_SAMPLE_DECO,
DC_SAMPLE_GASMIX
DC_SAMPLE_GASMIX,
DC_SAMPLE_TTS, // time to surface in seconds
} 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 {
DC_FIELD_DIVETIME,
DC_FIELD_MAXDEPTH,
@ -62,9 +66,13 @@ typedef enum dc_field_type_t {
DC_FIELD_TEMPERATURE_MAXIMUM,
DC_FIELD_TANK_COUNT,
DC_FIELD_TANK,
DC_FIELD_DIVEMODE
DC_FIELD_DIVEMODE,
DC_FIELD_STRING,
} 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 {
SAMPLE_EVENT_NONE,
SAMPLE_EVENT_DECOSTOP,
@ -92,17 +100,41 @@ typedef enum parser_sample_event_t {
SAMPLE_EVENT_HEADING,
SAMPLE_EVENT_TISSUELEVEL,
SAMPLE_EVENT_GASCHANGE2, /* Deprecated: replaced by DC_SAMPLE_GASMIX. */
SAMPLE_EVENT_STRING,
} parser_sample_event_t;
/* To let the compile know we have this */
#define SAMPLE_EVENT_STRING SAMPLE_EVENT_STRING
/* For backwards compatibility */
#define SAMPLE_EVENT_UNKNOWN SAMPLE_EVENT_FLOOR
typedef enum parser_sample_flags_t {
SAMPLE_FLAGS_NONE = 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;
#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 {
SAMPLE_VENDOR_NONE,
SAMPLE_VENDOR_UWATEC_ALADIN,
@ -148,11 +180,16 @@ typedef struct dc_gasmix_t {
#define DC_GASMIX_UNKNOWN 0xFFFFFFFF
typedef enum dc_tankvolume_t {
DC_TANKVOLUME_NONE,
DC_TANKVOLUME_METRIC,
DC_TANKVOLUME_IMPERIAL,
} dc_tankvolume_t;
typedef unsigned int dc_tankinfo_t;
#define DC_TANKINFO_METRIC 1
#define DC_TANKINFO_IMPERIAL 2
#define DC_TANKINFO_CC_DILUENT 4
#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
@ -179,13 +216,18 @@ typedef enum dc_tankvolume_t {
typedef struct dc_tank_t {
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 workpressure; /* Work pressure (bar) */
double beginpressure; /* Begin pressure (bar) */
double endpressure; /* End pressure (bar) */
} 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 {
unsigned int time;
double depth;
@ -199,6 +241,7 @@ typedef union dc_sample_value_t {
unsigned int time;
unsigned int flags;
unsigned int value;
const char *name;
} event;
unsigned int rbt;
unsigned int heartbeat;

View File

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

View File

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

View File

@ -20,6 +20,12 @@
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef _MSC_VER
#define snprintf _snprintf
#endif
#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
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;
double atmospheric = 0.0;
char buf[BUFLEN];
dc_field_string_t *string = (dc_field_string_t *) value;
if (parser->atmospheric)
atmospheric = parser->atmospheric;
else
@ -208,6 +220,29 @@ atomics_cobalt_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, un
return DC_STATUS_DATAFORMAT;
}
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:
return DC_STATUS_UNSUPPORTED;
}

View File

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

View File

@ -41,6 +41,7 @@ static dc_status_t dc_custom_flush (dc_iostream_t *abstract);
static dc_status_t dc_custom_purge (dc_iostream_t *abstract, dc_direction_t direction);
static dc_status_t dc_custom_sleep (dc_iostream_t *abstract, unsigned int milliseconds);
static dc_status_t dc_custom_close (dc_iostream_t *abstract);
static const char *dc_custom_get_name (dc_iostream_t *abstract);
typedef struct dc_custom_t {
/* Base class. */
@ -66,6 +67,7 @@ static const dc_iostream_vtable_t dc_custom_vtable = {
dc_custom_purge, /* purge */
dc_custom_sleep, /* sleep */
dc_custom_close, /* close */
dc_custom_get_name, /* get_name */
};
dc_status_t
@ -246,3 +248,14 @@ dc_custom_close (dc_iostream_t *abstract)
return custom->callbacks.close (custom->userdata);
}
static const char *
dc_custom_get_name (dc_iostream_t *abstract)
{
dc_custom_t *custom = (dc_custom_t *) abstract;
if (custom->callbacks.get_name == NULL)
return NULL;
return custom->callbacks.get_name (custom->userdata);
}

View File

@ -34,6 +34,7 @@ static int dc_filter_suunto (dc_transport_t transport, const void *userdata);
static int dc_filter_shearwater (dc_transport_t transport, const void *userdata);
static int dc_filter_hw (dc_transport_t transport, const void *userdata);
static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata);
static int dc_filter_garmin (dc_transport_t transport, const void *userdata);
static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item);
@ -208,20 +209,20 @@ static const dc_descriptor_t g_descriptors[] = {
{"Oceanic", "OCi", DC_FAMILY_OCEANIC_ATOM2, 0x454B, DC_TRANSPORT_SERIAL, NULL},
{"Aeris", "A300CS", DC_FAMILY_OCEANIC_ATOM2, 0x454C, DC_TRANSPORT_SERIAL, NULL},
{"Beuchat", "Mundial 3", DC_FAMILY_OCEANIC_ATOM2, 0x4550, DC_TRANSPORT_SERIAL, NULL},
{"Oceanic", "Pro Plus X", DC_FAMILY_OCEANIC_ATOM2, 0x4552, DC_TRANSPORT_SERIAL, NULL},
{"Oceanic", "Pro Plus X", DC_FAMILY_OCEANIC_ATOM2, 0x4552, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
{"Oceanic", "F10", DC_FAMILY_OCEANIC_ATOM2, 0x4553, DC_TRANSPORT_SERIAL, NULL},
{"Oceanic", "F11", DC_FAMILY_OCEANIC_ATOM2, 0x4554, DC_TRANSPORT_SERIAL, NULL},
{"Subgear", "XP-Air", DC_FAMILY_OCEANIC_ATOM2, 0x4555, 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},
{"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", "i550", DC_FAMILY_OCEANIC_ATOM2, 0x4642, DC_TRANSPORT_SERIAL, NULL},
{"Aqualung", "i200", DC_FAMILY_OCEANIC_ATOM2, 0x4646, DC_TRANSPORT_SERIAL, NULL},
{"Aqualung", "i300C", DC_FAMILY_OCEANIC_ATOM2, 0x4648, DC_TRANSPORT_SERIAL, NULL},
{"Aqualung", "i300C", DC_FAMILY_OCEANIC_ATOM2, 0x4648, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
{"Aqualung", "i100", DC_FAMILY_OCEANIC_ATOM2, 0x464E, DC_TRANSPORT_SERIAL, NULL},
{"Aqualung", "i770R", DC_FAMILY_OCEANIC_ATOM2, 0x4651, DC_TRANSPORT_SERIAL, NULL},
{"Aqualung", "i770R", DC_FAMILY_OCEANIC_ATOM2, 0x4651, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
/* Mares Nemo */
{"Mares", "Nemo", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Nemo Steel", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL},
@ -241,16 +242,16 @@ static const dc_descriptor_t g_descriptors[] = {
{"Mares", "Airlab", DC_FAMILY_MARES_DARWIN , 1, DC_TRANSPORT_SERIAL, NULL},
/* Mares Icon HD */
{"Mares", "Matrix", DC_FAMILY_MARES_ICONHD , 0x0F, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Smart", DC_FAMILY_MARES_ICONHD , 0x000010, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Smart", DC_FAMILY_MARES_ICONHD , 0x000010, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
{"Mares", "Smart Apnea", DC_FAMILY_MARES_ICONHD , 0x010010, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Icon HD", DC_FAMILY_MARES_ICONHD , 0x14, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Icon HD Net Ready", DC_FAMILY_MARES_ICONHD , 0x15, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Puck Pro", DC_FAMILY_MARES_ICONHD , 0x18, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Puck Pro", DC_FAMILY_MARES_ICONHD , 0x18, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
{"Mares", "Nemo Wide 2", DC_FAMILY_MARES_ICONHD , 0x19, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Puck 2", DC_FAMILY_MARES_ICONHD , 0x1F, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Quad Air", DC_FAMILY_MARES_ICONHD , 0x23, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Smart Air", DC_FAMILY_MARES_ICONHD , 0x24, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Quad", DC_FAMILY_MARES_ICONHD , 0x29, DC_TRANSPORT_SERIAL, NULL},
{"Mares", "Quad Air", DC_FAMILY_MARES_ICONHD , 0x23, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
{"Mares", "Smart Air", DC_FAMILY_MARES_ICONHD , 0x24, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
{"Mares", "Quad", DC_FAMILY_MARES_ICONHD , 0x29, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, NULL},
/* Heinrichs Weikamp */
{"Heinrichs Weikamp", "OSTC", DC_FAMILY_HW_OSTC, 0, DC_TRANSPORT_SERIAL, NULL},
{"Heinrichs Weikamp", "OSTC Mk2", DC_FAMILY_HW_OSTC, 1, DC_TRANSPORT_SERIAL, NULL},
@ -350,6 +351,8 @@ static const dc_descriptor_t g_descriptors[] = {
{"Cochran", "EMC-20H", DC_FAMILY_COCHRAN_COMMANDER, 5, DC_TRANSPORT_SERIAL, NULL},
/* Tecdiving DiveComputer.eu */
{"Tecdiving", "DiveComputer.eu", DC_FAMILY_TECDIVING_DIVECOMPUTEREU, 0, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH, dc_filter_tecdiving},
/* Garmin */
{"Garmin", "Descent Mk1", DC_FAMILY_GARMIN, 2859, DC_TRANSPORT_USBSTORAGE, dc_filter_garmin},
};
static int
@ -502,6 +505,19 @@ static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata)
return 1;
}
static int dc_filter_garmin (dc_transport_t transport, const void *userdata)
{
static const dc_usb_desc_t usbhid[] = {
{0x091e, 0x2b2b}, // Garmin Descent Mk1
};
if (transport == DC_TRANSPORT_USBSTORAGE) {
return dc_filter_internal_usb ((const dc_usb_desc_t *) userdata, usbhid, C_ARRAY_SIZE(usbhid));
}
return 1;
}
dc_status_t
dc_descriptor_iterator (dc_iterator_t **out)
{

View File

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

323
src/garmin.c Normal file
View File

@ -0,0 +1,323 @@
/*
* Garmin Descent Mk1 USB storage downloading
*
* Copyright (C) 2018 Linus Torvalds
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "garmin.h"
#include "context-private.h"
#include "device-private.h"
#include "array.h"
typedef struct garmin_device_t {
dc_device_t base;
dc_iostream_t *iostream;
unsigned char fingerprint[FIT_NAME_SIZE];
} garmin_device_t;
static dc_status_t garmin_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
static dc_status_t garmin_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
static dc_status_t garmin_device_close (dc_device_t *abstract);
static const dc_device_vtable_t garmin_device_vtable = {
sizeof(garmin_device_t),
DC_FAMILY_GARMIN,
garmin_device_set_fingerprint, /* set_fingerprint */
NULL, /* read */
NULL, /* write */
NULL, /* dump */
garmin_device_foreach, /* foreach */
NULL, /* timesync */
garmin_device_close, /* close */
};
dc_status_t
garmin_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream)
{
dc_status_t status = DC_STATUS_SUCCESS;
garmin_device_t *device = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
// Allocate memory.
device = (garmin_device_t *) dc_device_allocate (context, &garmin_device_vtable);
if (device == NULL) {
ERROR (context, "Failed to allocate memory.");
return DC_STATUS_NOMEMORY;
}
// Set the default values.
device->iostream = iostream;
memset(device->fingerprint, 0, sizeof(device->fingerprint));
*out = (dc_device_t *) device;
return DC_STATUS_SUCCESS;
}
static dc_status_t
garmin_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size)
{
garmin_device_t *device = (garmin_device_t *)abstract;
if (size && size != sizeof (device->fingerprint))
return DC_STATUS_INVALIDARGS;
if (size)
memcpy (device->fingerprint, data, sizeof (device->fingerprint));
else
memset (device->fingerprint, 0, sizeof (device->fingerprint));
return DC_STATUS_SUCCESS;
}
static dc_status_t
garmin_device_close (dc_device_t *abstract)
{
dc_status_t status = DC_STATUS_SUCCESS;
garmin_device_t *device = (garmin_device_t *) abstract;
return DC_STATUS_SUCCESS;
}
struct file_list {
int nr, allocated;
struct fit_name *array;
};
static int name_cmp(const void *a, const void *b)
{
// Sort reverse string ordering (newest first), so use 'b,a'
return strcmp(b,a);
}
/*
* Get the FIT file list and sort it.
*
* Return number of files found.
*/
static int get_file_list(DIR *dir, struct file_list *files)
{
struct dirent *de;
while ((de = readdir(dir)) != NULL) {
int len = strlen(de->d_name);
struct fit_name *entry;
if (len < 5)
continue;
if (len >= FIT_NAME_SIZE)
continue;
if (strncasecmp(de->d_name + len - 4, ".FIT", 4))
continue;
if (files->nr == files->allocated) {
struct fit_name *array;
int n = 3*(files->allocated + 8)/2;
size_t new_size;
new_size = n * sizeof(array[0]);
array = realloc(files->array, new_size);
if (!array)
return DC_STATUS_NOMEMORY;
files->array = array;
files->allocated = n;
}
/*
* 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);
}
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), &pathlen);
if (rc != DC_STATUS_SUCCESS)
return rc;
// The actual dives are under the "Garmin/Activity/" directory
// as FIT files, with names like "2018-08-20-10-23-30.fit".
// Make sure our buffer is big enough.
if (pathlen + strlen("/Garmin/Activity/") + FIT_NAME_SIZE + 2 > PATH_MAX)
return DC_STATUS_IO;
if (pathlen && pathname[pathlen-1] != '/')
pathname[pathlen++] = '/';
strcpy(pathname + pathlen, "Garmin/Activity");
pathlen += strlen("Garmin/Activity");
dir = opendir(pathname);
if (!dir)
return DC_STATUS_IO;
// Get the list of FIT files
rc = get_file_list(dir, &files);
closedir(dir);
if (rc != DC_STATUS_SUCCESS || !files.nr) {
free(files.array);
return rc;
}
// Can we find the fingerprint entry?
for (int i = 0; i < files.nr; i++) {
const char *name = files.array[i].name;
if (memcmp(name, device->fingerprint, sizeof (device->fingerprint)))
continue;
// Found fingerprint, just cut the array short here
files.nr = i;
break;
}
// Enable progress notifications.
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
progress.maximum = files.nr;
progress.current = 0;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
file = dc_buffer_new (16384);
if (file == NULL) {
ERROR (abstract->context, "Insufficient buffer space available.");
free(files.array);
return DC_STATUS_NOMEMORY;
}
if ((rc = garmin_parser_create(&parser, abstract->context) != DC_STATUS_SUCCESS)) {
ERROR (abstract->context, "Failed to create parser for dive verification.");
free(files.array);
return rc;
}
dc_event_devinfo_t devinfo;
dc_event_devinfo_t *devinfo_p = &devinfo;
for (int i = 0; i < files.nr; i++) {
const char *name = files.array[i].name;
const unsigned char *data;
unsigned int size;
short is_dive = 0;
if (device_is_cancelled(abstract)) {
status = DC_STATUS_CANCELLED;
break;
}
// Reset the membuffer, read the data
dc_buffer_clear(file);
dc_buffer_append(file, name, FIT_NAME_SIZE);
status = read_file(pathname, pathlen, name, file);
if (status != DC_STATUS_SUCCESS)
break;
data = dc_buffer_get_data(file);
size = dc_buffer_get_size(file);
is_dive = garmin_parser_is_dive(parser, data, size, devinfo_p);
if (devinfo_p) {
// first time we came through here, let's emit the
// devinfo and vendor events
device_event_emit (abstract, DC_EVENT_DEVINFO, devinfo_p);
devinfo_p = NULL;
}
if (!is_dive) {
DEBUG (abstract->context, "decided %s isn't a dive.", name);
continue;
}
if (callback && !callback(data, size, name, FIT_NAME_SIZE, userdata))
break;
progress.current++;
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
}
free(files.array);
dc_parser_destroy(parser);
return status;
}

59
src/garmin.h Normal file
View File

@ -0,0 +1,59 @@
/*
* Garmin Descent Mk1
*
* Copyright (C) 2018 Linus Torvalds
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#ifndef GARMIN_H
#define GARMIN_H
#include <libdivecomputer/context.h>
#include <libdivecomputer/iostream.h>
#include <libdivecomputer/device.h>
#include <libdivecomputer/parser.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
dc_status_t
garmin_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
dc_status_t
garmin_parser_create (dc_parser_t **parser, dc_context_t *context);
// we need to be able to call into the parser to check if the
// files that we find are actual dives
int
garmin_parser_is_dive (dc_parser_t *abstract, const unsigned char *data, unsigned int size, dc_event_devinfo_t *devinfo_p);
// The dive names are of the form "2018-08-20-10-23-30.fit"
// With the terminating zero, that's 24 bytes.
//
// We use this as the fingerprint, but it ends up being a
// special fixed header in the parser data too.
#define FIT_NAME_SIZE 24
struct fit_name {
char name[FIT_NAME_SIZE];
};
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* GARMIN_H */

1370
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);
dc_status_t
hw_ostc_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int hwos);
hw_ostc_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int serial, unsigned int hwos);
#ifdef __cplusplus
}

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

View File

@ -20,6 +20,12 @@
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef _MSC_VER
#define snprintf _snprintf
#endif
#include "libdivecomputer/units.h"
@ -61,8 +67,14 @@
#define OSTC3_APNEA 3
#define OSTC3_PSCR 4
#define OSTC3_ZHL16 0
#define OSTC3_ZHL16_GF 1
#define OSTC4_VPM 2
#define OSTC4 0x3B
#define UNSUPPORTED 0xFFFFFFFF
typedef struct hw_ostc_sample_info_t {
unsigned int type;
unsigned int divisor;
@ -78,7 +90,13 @@ typedef struct hw_ostc_layout_t {
unsigned int salinity;
unsigned int duration;
unsigned int temperature;
unsigned int battery;
unsigned int desat;
unsigned int firmware;
unsigned int deco_info1;
unsigned int deco_info2;
unsigned int decomode;
unsigned int battery_percentage;
} hw_ostc_layout_t;
typedef struct hw_ostc_gasmix_t {
@ -90,6 +108,7 @@ typedef struct hw_ostc_parser_t {
dc_parser_t base;
unsigned int hwos;
unsigned int model;
unsigned int serial;
// Cached fields.
unsigned int cached;
unsigned int version;
@ -127,7 +146,13 @@ static const hw_ostc_layout_t hw_ostc_layout_ostc = {
43, /* salinity */
47, /* duration */
13, /* temperature */
34, /* battery volt after dive */
17, /* desat */
32, /* firmware */
49, /* deco_info1 */
50, /* deco_info1 */
51, /* decomode */
0, /* battery percentage TBD */
};
static const hw_ostc_layout_t hw_ostc_layout_frog = {
@ -139,7 +164,13 @@ static const hw_ostc_layout_t hw_ostc_layout_frog = {
43, /* salinity */
47, /* duration */
19, /* temperature */
34, /* battery volt after dive */
23, /* desat */
32, /* firmware */
49, /* deco_info1 */
50, /* deco_info2 */
51, /* decomode */
0, /* battery percentage TBD */
};
static const hw_ostc_layout_t hw_ostc_layout_ostc3 = {
@ -151,7 +182,13 @@ static const hw_ostc_layout_t hw_ostc_layout_ostc3 = {
70, /* salinity */
75, /* duration */
22, /* temperature */
50, /* battery volt after dive */
26, /* desat */
48, /* firmware */
77, /* deco_info1 */
78, /* deco_info2 */
79, /* decomode */
59, /* battery percentage */
};
static unsigned int
@ -294,7 +331,7 @@ hw_ostc_parser_cache (hw_ostc_parser_t *parser)
}
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;
@ -324,6 +361,7 @@ hw_ostc_parser_create_internal (dc_parser_t **out, dc_context_t *context, unsign
parser->gasmix[i].oxygen = 0;
parser->gasmix[i].helium = 0;
}
parser->serial = serial;
*out = (dc_parser_t *) parser;
@ -332,15 +370,15 @@ hw_ostc_parser_create_internal (dc_parser_t **out, dc_context_t *context, unsign
dc_status_t
hw_ostc_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int hwos)
hw_ostc_parser_create (dc_parser_t **out, dc_context_t *context, unsigned int serial, unsigned int hwos)
{
return hw_ostc_parser_create_internal (out, context, hwos, 0);
return hw_ostc_parser_create_internal (out, context, serial, hwos, 0);
}
dc_status_t
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
@ -428,6 +466,8 @@ hw_ostc_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime)
return DC_STATUS_SUCCESS;
}
#define BUFLEN 32
static dc_status_t
hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value)
{
@ -452,9 +492,12 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned
dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
dc_salinity_t *water = (dc_salinity_t *) value;
dc_field_string_t *string = (dc_field_string_t *) value;
unsigned int salinity = data[layout->salinity];
if (version == 0x23 || version == 0x24)
salinity += 100;
char buf[BUFLEN];
if (value) {
switch (type) {
@ -552,6 +595,79 @@ hw_ostc_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned
return DC_STATUS_UNSUPPORTED;
}
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:
return DC_STATUS_UNSUPPORTED;
}

View File

@ -68,6 +68,8 @@ struct dc_iostream_vtable_t {
dc_status_t (*sleep) (dc_iostream_t *iostream, unsigned int milliseconds);
dc_status_t (*close) (dc_iostream_t *iostream);
const char *(*get_name) (dc_iostream_t *iostream);
};
dc_iostream_t *

View File

@ -186,43 +186,74 @@ dc_iostream_configure (dc_iostream_t *iostream, unsigned int baudrate, unsigned
dc_status_t
dc_iostream_read (dc_iostream_t *iostream, void *data, size_t size, size_t *actual)
{
dc_status_t status = DC_STATUS_SUCCESS;
if (actual)
*actual = 0;
if (iostream == NULL || iostream->vtable->read == NULL)
return DC_STATUS_IO;
while (size) {
dc_status_t status;
size_t nbytes = 0;
if (iostream == NULL || iostream->vtable->read == NULL) {
goto out;
}
status = iostream->vtable->read (iostream, data, size, &nbytes);
HEXDUMP (iostream->context, DC_LOGLEVEL_INFO, "Read", (unsigned char *) data, nbytes);
out:
if (actual)
/*
* 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;
/*
* 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;
}
return DC_STATUS_SUCCESS;
}
dc_status_t
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)
*actual = 0;
if (iostream == NULL || iostream->vtable->write == NULL)
return DC_STATUS_IO;
while (size) {
dc_status_t status;
size_t nbytes = 0;
if (iostream == NULL || iostream->vtable->read == NULL) {
goto out;
}
status = iostream->vtable->write (iostream, data, size, &nbytes);
HEXDUMP (iostream->context, DC_LOGLEVEL_INFO, "Write", (const unsigned char *) data, nbytes);
out:
if (actual)
if (actual) {
*actual = nbytes;
return status;
}
if (status != DC_STATUS_SUCCESS)
return status;
data = (void *)(nbytes + (char *)data);
size -= nbytes;
}
return DC_STATUS_SUCCESS;
}
dc_status_t
@ -274,3 +305,15 @@ dc_iostream_close (dc_iostream_t *iostream)
return status;
}
const char *
dc_iostream_get_name (dc_iostream_t *iostream)
{
if (iostream == NULL)
return NULL;
if (iostream->vtable->get_name)
return iostream->vtable->get_name (iostream);
return NULL;
}

View File

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

View File

@ -72,6 +72,7 @@ typedef struct mares_iconhd_device_t {
unsigned char version[140];
unsigned int model;
unsigned int packetsize;
unsigned int splitcommand;
} mares_iconhd_device_t;
static dc_status_t mares_iconhd_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
@ -152,14 +153,17 @@ mares_iconhd_transfer (mares_iconhd_device_t *device,
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t *abstract = (dc_device_t *) device;
unsigned int split_csize;
assert (csize >= 2);
if (device_is_cancelled (abstract))
return DC_STATUS_CANCELLED;
split_csize = device->splitcommand ? 2 : csize;
// Send the command header to the dive computer.
status = dc_iostream_write (device->iostream, command, 2, NULL);
status = dc_iostream_write (device->iostream, command, split_csize, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command.");
return status;
@ -179,9 +183,9 @@ mares_iconhd_transfer (mares_iconhd_device_t *device,
return DC_STATUS_PROTOCOL;
}
// Send the command payload to the dive computer.
if (csize > 2) {
status = dc_iostream_write (device->iostream, command + 2, csize - 2, NULL);
// Send any remaining command payload to the dive computer.
if (csize > split_csize) {
status = dc_iostream_write (device->iostream, command + split_csize, csize - split_csize, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command.");
return status;
@ -237,6 +241,15 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_
device->model = 0;
device->packetsize = 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).
status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_EVEN, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE);
if (status != DC_STATUS_SUCCESS) {
@ -310,6 +323,26 @@ mares_iconhd_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_
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;
return DC_STATUS_SUCCESS;

View File

@ -57,6 +57,7 @@
typedef struct oceanic_atom2_device_t {
oceanic_common_device_t base;
dc_iostream_t *iostream;
unsigned int sequence;
unsigned int delay;
unsigned int bigpage;
unsigned char cache[256];
@ -121,7 +122,7 @@ static const oceanic_common_version_t oceanic_atom2b_version[] = {
{"AQUAI300 \0\0 512K"},
{"HOLLDG03 \0\0 512K"},
{"AQUAI100 \0\0 512K"},
{"AQUA300C \0\0 512K"},
{"AQUA300C \0\0 \0\0\0\0"},
};
static const oceanic_common_version_t oceanic_atom2c_version[] = {
@ -190,12 +191,13 @@ static const oceanic_common_version_t oceanic_reactpro_version[] = {
{"REACPRO2 \0\0 512K"},
};
// Like the i770R, there's some extended pattern for the last
// four digits. The serial communication apparently says "2048"
// for this, but the BLE version says "0001".
//
// The middle two digits are the FW version or something,
static const oceanic_common_version_t oceanic_proplusx_version[] = {
{"OCEANOCX \0\0 2048"},
};
static const oceanic_common_version_t aqualung_i770r_version[] = {
{"AQUA770R \0\0 2048"},
{"OCEANOCX \0\0 \0\0\0\0"},
};
static const oceanic_common_version_t aeris_a300cs_version[] = {
@ -204,6 +206,19 @@ static const oceanic_common_version_t aeris_a300cs_version[] = {
{"AQUAI750 \0\0 2048"},
};
// Not 100% sure what the pattern is.
// I've seen:
//
// "AQUA770R 1A 0001"
// "AQUA770R 1A 0090"
//
// from the same dive computer. On other ones, it's
// apparently the two middle digits that change, on
// the i770R it might be all of them.
static const oceanic_common_version_t aqualung_i770r_version[] = {
{"AQUA770R \0\0 \0\0\0\0"},
};
static const oceanic_common_version_t aqualung_i450t_version[] = {
{"AQUAI450 \0\0 2048"},
};
@ -591,7 +606,7 @@ oceanic_atom2_packet (oceanic_atom2_device_t *device, const unsigned char comman
static dc_status_t
oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size)
oceanic_atom2_serial_transfer (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size)
{
// Send the command to the device. If the device responds with an
// ACK byte, the command was received successfully and the answer
@ -621,6 +636,200 @@ oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char comm
return DC_STATUS_SUCCESS;
}
/*
* The BLE GATT packet size is up to 20 bytes and the format is:
*
* byte 0: <0xCD>
* Seems to always have this value. Don't ask what it means
* byte 1: <d 1 c s s s s s>
* d=0 means "command", d=1 means "reply from dive computer"
* 1 is always set, afaik
* c=0 means "last packet" in sequence, c=1 means "more packets coming"
* sssss is a 5-bit sequence number for packets
* byte 2: <cmd seq>
* starts at 0 for the connection, incremented for each command
* byte 3: <length of data>
* 1-16 bytes of data per packet.
* byte 4..n: <data>
*/
static dc_status_t
oceanic_atom2_ble_write(oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize)
{
unsigned char buf[20];
unsigned char cmd_seq = device->sequence;
unsigned char pkt_seq;
pkt_seq = 0;
while (csize) {
dc_status_t ret;
unsigned char status = 0x40;
unsigned int cpartial = csize;
if (cpartial > 16) {
cpartial = 16;
status |= 0x20;
}
buf[0] = 0xcd;
buf[1] = status | (pkt_seq & 31);
buf[2] = cmd_seq;
buf[3] = cpartial;
memcpy(buf+4, command, cpartial);
command += cpartial;
csize -= cpartial;
ret = dc_iostream_write(device->iostream, buf, 4+cpartial, NULL);
if (ret != DC_STATUS_SUCCESS)
return ret;
pkt_seq++;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
oceanic_atom2_ble_read(oceanic_atom2_device_t *device, unsigned char **result_p, unsigned int *size_p)
{
unsigned char *result = NULL;
unsigned int size = 0, allocated = 0;
unsigned char buf[20];
unsigned char cmd_seq = device->sequence;
unsigned char pkt_seq;
dc_status_t ret = DC_STATUS_SUCCESS;
pkt_seq = 0;
for (;;) {
unsigned char status, expect;
size_t transferred = 0;
ret = dc_iostream_read(device->iostream, buf, sizeof(buf), &transferred);
if (ret != DC_STATUS_SUCCESS)
break;
ret = DC_STATUS_IO;
if (transferred < 5 || transferred > 20) {
ERROR(device->base.base.context, "Odd BLE packet size %zd", transferred);
break;
}
if (buf[0] != 0xcd)
ERROR(device->base.base.context, "Odd first byte (got '%02x', expected 'cd'", buf[0]);
// Verify status byte
expect = 0xc0;
expect |= (pkt_seq & 31);
status = buf[1];
if ((status & ~0x20) != expect)
ERROR(device->base.base.context, "Odd status byte (got '%02x', expected '%02x'", buf[1], expect);
// Verify command sequence byte
expect = cmd_seq;
if (buf[2] != expect)
ERROR(device->base.base.context, "Odd cmd sequence byte (got '%02x', expected '%02x'", buf[2], expect);
// Verify length byte
expect = buf[3];
if (expect < 1 || expect > 16) {
ERROR(device->base.base.context, "Odd reply size byte (got %d, expected 1..16", buf[3]);
break;
}
if (transferred < 4+expect) {
ERROR(device->base.base.context, "Packet too small (got %zd bytes, expected at least %d bytes)", transferred, 4+expect);
break;
}
if (size + expect > allocated) {
unsigned int newsize = size + expect + 100;
unsigned char *newalloc = realloc(result, newsize);
if (!newalloc) {
ret = DC_STATUS_NOMEMORY;
break;
}
result = newalloc;
allocated = newsize;
}
memcpy(result + size, buf+4, expect);
size += expect;
pkt_seq++;
/* More packets? */
if (status & 0x20)
continue;
ret = DC_STATUS_SUCCESS;
break;
}
if (ret != DC_STATUS_SUCCESS) {
free(result);
size = 0;
result = NULL;
}
*result_p = result;
*size_p = size;
return ret;
}
/*
* Transfer a command and optionally read return data.
*
* NOTE! The NUL byte at the end of a command is a serial transfer thing,
* and we remove it. The correct thing to do would be to add it on the
* serial transfer side instead (or perhaps not send it at all, Jef says
* it may be historical), but right now I've tried to minimize the changes
* that the BLE transfer code made to the code, so instead this tries to
* just skip the extraneous byte.
*/
static dc_status_t
oceanic_atom2_ble_transfer (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size)
{
unsigned char buf[20];
unsigned char cmd_seq = device->sequence;
unsigned char pkt_seq;
dc_status_t ret = DC_STATUS_SUCCESS;
int retry = 3;
/*
* The serial commands have a NUL byte at the end. It's bogus.
* It should be added on the serial transfer side, not removed
* here.
*/
if (csize > 1 && csize < 8 && !command[csize-1])
csize--;
retry:
if (--retry < 0)
return ret;
ret = oceanic_atom2_ble_write(device, command, csize);
if (ret != DC_STATUS_SUCCESS)
return ret;
pkt_seq = 0;
if (answer) {
unsigned char *buf;
unsigned int size;
ret = oceanic_atom2_ble_read(device, &buf, &size);
if (ret != DC_STATUS_SUCCESS)
goto retry;
if (size > asize && buf[0] == ACK) {
memcpy(answer, buf+1, asize);
device->sequence++;
} else {
ERROR(device->base.base.context, "Result too small: got %d bytes, expected at least %d bytes", size, asize+1);
ret = DC_STATUS_IO;
goto retry;
}
free(buf);
}
return ret;
}
static dc_status_t
oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size)
{
if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE)
return oceanic_atom2_ble_transfer(device, command, csize, answer, asize, crc_size);
return oceanic_atom2_serial_transfer(device, command, csize, answer, asize, crc_size);
}
static dc_status_t
oceanic_atom2_quit (oceanic_atom2_device_t *device)
@ -657,6 +866,7 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream
// Set the default values.
device->iostream = iostream;
device->delay = 0;
device->sequence = 0;
device->bigpage = 1; // no big pages
device->cached_page = INVALID;
device->cached_highmem = INVALID;
@ -834,9 +1044,55 @@ oceanic_atom2_device_keepalive (dc_device_t *abstract)
if (rc != DC_STATUS_SUCCESS)
return rc;
/* No answer: increment sequence number manually */
device->sequence++;
return DC_STATUS_SUCCESS;
}
/*
* The BLE communication sends a handshake packet that seems
* to be a passphrase based on the BLE name of the device
* (more specifically the serial number encoded in the name).
*
* The packet format is:
* 0xe5
* < 8 bytes of passphrase >
* one-byte checksum of the passphrase.
*/
static dc_status_t
oceanic_atom2_send_ble_handshake(oceanic_atom2_device_t *device)
{
unsigned char handshake[10] = { 0xe5, }, ack[1];
const char *bt_name = dc_iostream_get_name(device->iostream);
/*
* Allow skipping the handshake if no name. But the download will
* likely fail.
*
* The format of the name is something like 'FQ001124', where the
* two first letters indicate the kind of device it is, and the
* six digits are the serial number.
*
* Jef theorizes that 'FQ' in hexadecimal is 0x4651, which is
* the model number of the i770R.
*/
if (!bt_name || strlen(bt_name) < 8)
return DC_STATUS_SUCCESS;
/* Turn ASCII numbers into just raw byte values */
for (int i = 0; i < 6; i++)
handshake[i+1] = bt_name[i+2] - '0';
/* Add simple checksum */
handshake[9] = checksum_add_uint8(handshake+1, 8, 0x00);
/*
* .. and send it off.
*
* NOTE! We don't expect any data back, but we do want the ACK.
*/
return oceanic_atom2_ble_transfer(device, handshake, sizeof(handshake), ack, 0, 0);
}
dc_status_t
oceanic_atom2_device_version (dc_device_t *abstract, unsigned char data[], unsigned int size)
@ -857,7 +1113,10 @@ oceanic_atom2_device_version (dc_device_t *abstract, unsigned char data[], unsig
memcpy (data, answer, PAGESIZE);
return DC_STATUS_SUCCESS;
if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE)
rc = oceanic_atom2_send_ble_handshake(device);
return rc;
}
@ -978,6 +1237,9 @@ oceanic_atom2_device_write (dc_device_t *abstract, unsigned int address, const u
if (rc != DC_STATUS_SUCCESS)
return rc;
/* No answer, increment sequence number manually */
device->sequence++;
nbytes += PAGESIZE;
address += PAGESIZE;
data += PAGESIZE;

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

View File

@ -20,6 +20,8 @@
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <libdivecomputer/units.h>
@ -106,6 +108,7 @@ struct oceanic_atom2_parser_t {
unsigned int model;
unsigned int headersize;
unsigned int footersize;
unsigned int serial;
// Cached fields.
unsigned int cached;
unsigned int header;
@ -135,7 +138,7 @@ static const dc_parser_vtable_t oceanic_atom2_parser_vtable = {
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;
@ -183,6 +186,7 @@ oceanic_atom2_parser_create (dc_parser_t **out, dc_context_t *context, unsigned
parser->headersize = 3 * PAGESIZE;
}
parser->serial = serial;
parser->cached = 0;
parser->header = 0;
parser->footer = 0;
@ -377,6 +381,7 @@ oceanic_atom2_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetim
return DC_STATUS_SUCCESS;
}
#define BUF_LEN 16
static dc_status_t
oceanic_atom2_parser_cache (oceanic_atom2_parser_t *parser)
@ -521,6 +526,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_salinity_t *water = (dc_salinity_t *) value;
dc_field_string_t *string = (dc_field_string_t *) value;
char buf[BUF_LEN];
if (value) {
switch (type) {
@ -550,7 +558,7 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns
break;
case DC_FIELD_SALINITY:
if (parser->model == A300CS || parser->model == VTX ||
parser->model == I750TC) {
parser->model == I750TC || parser->model == I770R) {
if (data[0x18] & 0x80) {
water->type = DC_WATER_FRESH;
} else {
@ -576,6 +584,17 @@ oceanic_atom2_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, uns
return DC_STATUS_DATAFORMAT;
}
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:
return DC_STATUS_UNSUPPORTED;
}
@ -728,7 +747,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
if (have_pressure) {
unsigned int idx = 2;
if (parser->model == A300CS || parser->model == VTX ||
parser->model == I750TC)
parser->model == I750TC || parser->model == I770R)
idx = 16;
pressure = array_uint16_le(data + parser->header + idx);
if (pressure == 10000)
@ -781,7 +800,7 @@ oceanic_atom2_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_
tank = 0;
pressure = (((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF);
} 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 = (data[offset + 1] & 0x03) - 1;
pressure = ((data[offset + 7] << 8) + data[offset + 6]) & 0x0FFF;

View File

@ -507,13 +507,14 @@ oceanic_common_device_profile (dc_device_t *abstract, dc_event_progress_t *progr
// Remove padding from the profile.
if (layout->highmem) {
// The logbook entry contains the total number of pages containing
// profile data, excluding the footer page. Limit the profile size
// to this size.
unsigned int value = array_uint16_le (profiles + offset + 12);
unsigned int value_hi = value & 0xE000;
unsigned int value_lo = value & 0x0FFF;
unsigned int npages = ((value_hi >> 1) | value_lo) + 1;
unsigned char *profile = profiles + offset;
// profile+12 and the bottom 4 bits of profile+13 and the top 3 bits of profile+13
// is the number of pages of profile data until the start of the footer
// (not including the prepended logbook entry).
unsigned int high_part = array_uint16_le (profile + 12) & 0xE000;
unsigned int low_part = array_uint16_le (profile + 12) & 0x0FFF;
unsigned int npages = ((high_part >> 1) | low_part) + 1;
// INFO (abstract->context, "profile npages: 0x%X (%d)", npages, npages);
unsigned int length = npages * PAGESIZE;
if (rb_entry_size > length) {
rb_entry_size = length;

View File

@ -57,6 +57,7 @@
#include "divesystem_idive.h"
#include "cochran_commander.h"
#include "tecdiving_divecomputereu.h"
#include "garmin.h"
#include "context-private.h"
#include "parser-private.h"
@ -65,7 +66,7 @@
#define REACTPROWHITE 0x4354
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_parser_t *parser = NULL;
@ -88,7 +89,7 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
break;
case DC_FAMILY_SUUNTO_VYPER2:
case DC_FAMILY_SUUNTO_D9:
rc = suunto_d9_parser_create (&parser, context, model);
rc = suunto_d9_parser_create (&parser, context, model, serial);
break;
case DC_FAMILY_SUUNTO_EONSTEEL:
rc = suunto_eonsteel_parser_create(&parser, context, model);
@ -119,7 +120,7 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
if (model == REACTPROWHITE)
rc = oceanic_veo250_parser_create (&parser, context, model);
else
rc = oceanic_atom2_parser_create (&parser, context, model);
rc = oceanic_atom2_parser_create (&parser, context, model, serial);
break;
case DC_FAMILY_MARES_NEMO:
case DC_FAMILY_MARES_PUCK:
@ -132,11 +133,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);
break;
case DC_FAMILY_HW_OSTC:
rc = hw_ostc_parser_create (&parser, context, 0);
rc = hw_ostc_parser_create (&parser, context, serial, 0);
break;
case DC_FAMILY_HW_FROG:
case DC_FAMILY_HW_OSTC3:
rc = hw_ostc3_parser_create (&parser, context, model);
rc = hw_ostc3_parser_create (&parser, context, serial, model);
break;
case DC_FAMILY_CRESSI_EDY:
case DC_FAMILY_ZEAGLE_N2ITION3:
@ -152,10 +153,10 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
rc = atomics_cobalt_parser_create (&parser, context);
break;
case DC_FAMILY_SHEARWATER_PREDATOR:
rc = shearwater_predator_parser_create (&parser, context, model);
rc = shearwater_predator_parser_create (&parser, context, model, serial);
break;
case DC_FAMILY_SHEARWATER_PETREL:
rc = shearwater_petrel_parser_create (&parser, context, model);
rc = shearwater_petrel_parser_create (&parser, context, model, serial);
break;
case DC_FAMILY_DIVERITE_NITEKQ:
rc = diverite_nitekq_parser_create (&parser, context);
@ -172,6 +173,9 @@ dc_parser_new_internal (dc_parser_t **out, dc_context_t *context, dc_family_t fa
case DC_FAMILY_TECDIVING_DIVECOMPUTEREU:
rc = tecdiving_divecomputereu_parser_create (&parser, context);
break;
case DC_FAMILY_GARMIN:
rc = garmin_parser_create (&parser, context);
break;
default:
return DC_STATUS_INVALIDARGS;
}
@ -188,7 +192,9 @@ dc_parser_new (dc_parser_t **out, dc_device_t *device)
return DC_STATUS_INVALIDARGS;
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);
}
@ -196,7 +202,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)
{
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);
}

View File

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

View File

@ -54,6 +54,9 @@ typedef struct shearwater_common_device_t {
dc_status_t
shearwater_common_setup (shearwater_common_device_t *device, dc_context_t *context, dc_iostream_t *iostream);
dc_status_t
shearwater_common_command (shearwater_common_device_t *device, const unsigned char input[], unsigned int isize);
dc_status_t
shearwater_common_transfer (shearwater_common_device_t *device, const unsigned char input[], unsigned int isize, unsigned char output[], unsigned int osize, unsigned int *actual);

View File

@ -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 model = 0;
switch (hardware) {
case 0x0808: // Petrel 2
case 0x0909: // Petrel 1
case 0x0B0B: // Petrel 1 (newer hardware)
model = PETREL;
case 0x0101:
case 0x0202:
model = PREDATOR;
break;
case 0x0606:
case 0x0A0A: // Nerd 1
model = NERD;
break;
case 0x0E0D: // Nerd 2
model = NERD2;
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;
break;
case 0x0C0C:
case 0x0C0D:
case 0x0D0D:
model = PERDIXAI;
break;
case 0x0F0F:
case 0x1F0A:
model = TERIC;
break;
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.

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

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

View File

@ -20,6 +20,13 @@
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#ifdef _MSC_VER
#define snprintf _snprintf
#endif
#include <libdivecomputer/units.h>
@ -51,6 +58,7 @@
#define LOG_RECORD_CLOSING_5 0x25
#define LOG_RECORD_CLOSING_6 0x26
#define LOG_RECORD_CLOSING_7 0x27
#define LOG_RECORD_INFO_EVENT 0x30
#define LOG_RECORD_FINAL 0xFF
#define SZ_BLOCK 0x80
@ -68,11 +76,14 @@
#define IMPERIAL 1
#define NGASMIXES 10
#define MAXSTRINGS 32
#define NRECORDS 7
#define PREDATOR 2
#define PETREL 3
#define INFO_EVENT_TAG_LOG 38
#define UNDEFINED 0xFFFFFFFF
typedef struct shearwater_predator_parser_t shearwater_predator_parser_t;
@ -96,10 +107,14 @@ struct shearwater_predator_parser_t {
unsigned int helium[NGASMIXES];
unsigned int calibrated;
double calibration[3];
unsigned int serial;
dc_divemode_t mode;
unsigned int units;
unsigned int atmospheric;
unsigned int density;
/* String fields */
dc_field_string_t strings[MAXSTRINGS];
};
static dc_status_t shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size);
@ -145,7 +160,7 @@ shearwater_predator_find_gasmix (shearwater_predator_parser_t *parser, unsigned
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;
const dc_parser_vtable_t *vtable = NULL;
@ -173,6 +188,9 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig
parser->model = model;
parser->petrel = petrel;
parser->samplesize = samplesize;
parser->serial = serial;
// Set the default values.
parser->cached = 0;
parser->pnf = 0;
parser->logversion = 0;
@ -204,16 +222,16 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig
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
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);
}
@ -272,6 +290,150 @@ shearwater_predator_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *d
return DC_STATUS_SUCCESS;
}
/*
* These string cache interfaces should be some generic
* library rather than copied for all the dive computers.
*
* This is just copied from the EON Steel code.
*/
static void
add_string(shearwater_predator_parser_t *parser, const char *desc, const char *value)
{
int i;
for (i = 0; i < MAXSTRINGS; i++) {
dc_field_string_t *str = parser->strings+i;
if (str->desc)
continue;
str->desc = desc;
str->value = strdup(value);
break;
}
}
static void
add_string_fmt(shearwater_predator_parser_t *parser, const char *desc, const char *fmt, ...)
{
char buffer[256];
va_list ap;
/*
* We ignore the return value from vsnprintf, and we
* always NUL-terminate the destination buffer ourselves.
*
* That way we don't have to worry about random bad legacy
* implementations.
*/
va_start(ap, fmt);
buffer[sizeof(buffer)-1] = 0;
(void) vsnprintf(buffer, sizeof(buffer)-1, fmt, ap);
va_end(ap);
return add_string(parser, desc, buffer);
}
// The Battery state is a big-endian word:
//
// ffff = not paired / no comms for 90 s
// fffe = no comms for 30 s
//
// Otherwise:
// - top four bits are battery state (0 - normal, 1 - critical, 2 - warning)
// - bottom 12 bits are pressure in 2 psi increments (0..8k psi)
//
// This returns the state as a bitmask (so you can see all states it had
// during the dive). Note that we currently do not report pairing and
// communication lapses. Todo?
static unsigned int
battery_state(const unsigned char *data)
{
unsigned int pressure = array_uint16_be(data);
unsigned int state;
if ((pressure & 0xFFF0) == 0xFFF0)
return 0;
state = pressure >> 12;
if (state > 2)
return 0;
return 1u << state;
}
// Show the battery state
//
// NOTE! Right now it only shows the most serious bit
// but the code is set up so that we could perhaps
// indicate that the battery is on the edge (ie it
// reported both "normal" _and_ "warning" during the
// dive - maybe that would be a "starting to warn")
//
// We could also report unpaired and comm errors.
static void
add_battery_info(shearwater_predator_parser_t *parser, const char *desc, unsigned int state)
{
if (state >= 1 && state <= 7) {
static const char *states[8] = {
"", // 000 - No state bits, not used
"normal", // 001 - only normal
"critical", // 010 - only critical
"critical", // 011 - both normal and critical
"warning", // 100 - only warning
"warning", // 101 - normal and warning
"critical", // 110 - warning and critical
"critical", // 111 - normal, warning and critical
};
add_string(parser, desc, states[state]);
}
}
static void
add_deco_model(shearwater_predator_parser_t *parser, const unsigned char *data)
{
unsigned int idx_deco_model = parser->pnf ? parser->opening[2] + 18 : 67;
unsigned int idx_gfs = parser->pnf ? parser->opening[3] + 5 : 85;
switch (data[idx_deco_model]) {
case 0:
add_string_fmt(parser, "Deco model", "GF %u/%u", data[4], data[5]);
break;
case 1:
add_string_fmt(parser, "Deco model", "VPM-B +%u", data[idx_deco_model + 1]);
break;
case 2:
add_string_fmt(parser, "Deco model", "VPM-B/GFS +%u %u%%", data[idx_deco_model + 1], data[idx_gfs]);
break;
default:
add_string_fmt(parser, "Deco model", "Unknown model %d", data[idx_deco_model]);
}
}
static void
add_battery_type(shearwater_predator_parser_t *parser, const unsigned char *data)
{
if (parser->logversion < 7)
return;
unsigned int idx_battery_type = parser->pnf ? parser->opening[4] + 9 : 120;
switch (data[idx_battery_type]) {
case 1:
add_string(parser, "Battery type", "1.5V Alkaline");
break;
case 2:
add_string(parser, "Battery type", "1.5V Lithium");
break;
case 3:
add_string(parser, "Battery type", "1.2V NiMH");
break;
case 4:
add_string(parser, "Battery type", "3.6V Saft");
break;
case 5:
add_string(parser, "Battery type", "3.7V Li-Ion");
break;
default:
add_string_fmt(parser, "Battery type", "unknown type %d", data[idx_battery_type]);
break;
}
}
static dc_status_t
shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
@ -283,6 +445,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
if (parser->cached) {
return DC_STATUS_SUCCESS;
}
memset(parser->strings, 0, sizeof(parser->strings));
// Verify the minimum length.
if (size < 2) {
@ -337,6 +500,9 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
unsigned int helium[NGASMIXES] = {0};
unsigned int o2_previous = 0, he_previous = 0;
// Transmitter battery levels
unsigned int t1_battery = 0, t2_battery = 0;
unsigned int offset = headersize;
unsigned int length = size - footersize;
while (offset + parser->samplesize <= length) {
@ -382,6 +548,11 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
o2_previous = o2;
he_previous = he;
}
// Transmitter battery levels
// T1 at offset 27, T2 at offset 19
t1_battery |= battery_state(data + offset + 27 + pnf);
t2_battery |= battery_state(data + offset + 19 + pnf);
} else if (type == LOG_RECORD_FREEDIVE_SAMPLE) {
// Freedive record
mode = DC_DIVEMODE_FREEDIVE;
@ -410,6 +581,13 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
// Log versions before 6 weren't reliably stored in the data, but
// 6 is also the oldest version that we assume in our code
unsigned int logversion = data[parser->opening[4] + (pnf ? 16 : 127)];
add_string_fmt(parser, "Logversion", "%d%s", logversion, pnf ? "(PNF)" : "");
// The transmitter battery levels are only valid for logversion 7+
if (logversion < 7) {
t1_battery = 0;
t2_battery = 0;
}
// Cache sensor calibration for later use
unsigned int nsensors = 0, ndefaults = 0;
@ -439,8 +617,12 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
// uncalibrated).
WARNING (abstract->context, "Disabled all O2 sensors due to a default calibration value.");
parser->calibrated = 0;
if (mode != DC_DIVEMODE_OC)
add_string(parser, "PPO2 source", "voted/averaged");
} else {
parser->calibrated = data[base];
if (mode != DC_DIVEMODE_OC)
add_string(parser, "PPO2 source", "cells");
}
// Cache the data for later use.
@ -459,6 +641,15 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser)
parser->density = array_uint16_be (data + parser->opening[3] + (parser->pnf ? 3 : 83));
parser->cached = 1;
add_string_fmt(parser, "Serial", "%08x", parser->serial);
// bytes 1-31 are identical in all formats
add_string_fmt(parser, "FW Version", "%2x", data[19]);
add_deco_model(parser, data);
add_battery_type(parser, data);
add_string_fmt(parser, "Battery at end", "%.1f V", data[9] / 10.0);
add_battery_info(parser, "T1 battery", t1_battery);
add_battery_info(parser, "T2 battery", t2_battery);
return DC_STATUS_SUCCESS;
}
@ -476,6 +667,7 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ
dc_gasmix_t *gasmix = (dc_gasmix_t *) value;
dc_salinity_t *water = (dc_salinity_t *) value;
dc_field_string_t *string = (dc_field_string_t *) value;
if (value) {
switch (type) {
@ -514,6 +706,15 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ
case DC_FIELD_DIVEMODE:
*((dc_divemode_t *) value) = parser->mode;
break;
case DC_FIELD_STRING:
if (flags < MAXSTRINGS) {
dc_field_string_t *p = parser->strings + flags;
if (p->desc) {
*string = *p;
break;
}
}
return DC_STATUS_UNSUPPORTED;
default:
return DC_STATUS_UNSUPPORTED;
}
@ -557,6 +758,41 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
while (offset + parser->samplesize <= length) {
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.
if (array_isequal (data + offset, parser->samplesize, 0x00)) {
offset += parser->samplesize;
@ -601,10 +837,10 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
if ((status & OC) == 0) {
// PPO2
if ((status & PPO2_EXTERNAL) == 0) {
#ifdef SENSOR_AVERAGE
if (!parser->calibrated) {
sample.ppo2 = data[offset + pnf + 6] / 100.0;
if (callback) callback (DC_SAMPLE_PPO2, sample, userdata);
#else
} else {
sample.ppo2 = data[offset + pnf + 12] * parser->calibration[0];
if (callback && (parser->calibrated & 0x01)) callback (DC_SAMPLE_PPO2, sample, userdata);
@ -613,7 +849,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
sample.ppo2 = data[offset + pnf + 15] * parser->calibration[2];
if (callback && (parser->calibrated & 0x04)) callback (DC_SAMPLE_PPO2, sample, userdata);
#endif
}
}
// Setpoint
@ -737,6 +973,5 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal
offset += parser->samplesize;
}
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);
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
}

View File

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

View File

@ -21,8 +21,16 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <stdarg.h>
/* Wow. MSC is truly crap */
#ifdef _MSC_VER
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#endif
#include "suunto_eonsteel.h"
#include "context-private.h"
@ -71,6 +79,7 @@ struct type_desc {
#define MAXTYPE 512
#define MAXGASES 16
#define MAXSTRINGS 32
typedef struct suunto_eonsteel_parser_t {
dc_parser_t base;
@ -89,7 +98,8 @@ typedef struct suunto_eonsteel_parser_t {
double lowsetpoint;
double highsetpoint;
double customsetpoint;
dc_tankvolume_t tankinfo[MAXGASES];
dc_field_string_t strings[MAXSTRINGS];
dc_tankinfo_t tankinfo[MAXGASES];
double tanksize[MAXGASES];
double tankworkingpressure[MAXGASES];
} cache;
@ -97,11 +107,6 @@ typedef struct suunto_eonsteel_parser_t {
typedef int (*eon_data_cb_t)(unsigned short type, const struct type_desc *desc, const unsigned char *data, int len, void *user);
typedef struct eon_event_t {
const char *name;
parser_sample_event_t type;
} eon_event_t;
static const struct {
const char *name;
enum eon_sample type;
@ -167,16 +172,6 @@ static enum eon_sample lookup_descriptor_type(suunto_eonsteel_parser_t *eon, str
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)
{
int i;
@ -468,8 +463,6 @@ struct sample_data {
/* We gather up deco and cylinder pressure information */
int gasnr;
int tts, ndl;
double ceiling;
};
static void sample_time(struct sample_data *info, unsigned short time_delta)
@ -507,7 +500,6 @@ static void sample_ndl(struct sample_data *info, short ndl)
{
dc_sample_value_t sample = {0};
info->ndl = ndl;
if (ndl < 0)
return;
@ -518,14 +510,27 @@ static void sample_ndl(struct sample_data *info, short ndl)
static void sample_tts(struct sample_data *info, unsigned short tts)
{
if (tts != 0xffff)
info->tts = tts;
if (tts != 0xffff) {
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)
{
if (ceiling != 0xffff)
info->ceiling = ceiling / 100.0;
if (ceiling != 0xffff) {
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)
@ -681,26 +686,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)
{
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;
name = info->state_type;
if (!name)
return;
sample.event.type = lookup_event(name, states, C_ARRAY_SIZE(states));
if (sample.event.type == SAMPLE_EVENT_NONE)
return;
sample.event.type = SAMPLE_EVENT_STRING;
sample.event.name = name;
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);
}
@ -712,25 +708,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 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};
const char *name;
@ -738,11 +715,11 @@ static void sample_event_notify_value(const struct type_desc *desc, struct sampl
if (!name)
return;
sample.event.type = lookup_event(name, notifications, C_ARRAY_SIZE(notifications));
if (sample.event.type == SAMPLE_EVENT_NONE)
return;
sample.event.type = SAMPLE_EVENT_STRING;
sample.event.name = name;
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);
}
@ -755,22 +732,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 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};
const char *name;
@ -778,11 +739,11 @@ static void sample_event_warning_value(const struct type_desc *desc, struct samp
if (!name)
return;
sample.event.type = lookup_event(name, warnings, C_ARRAY_SIZE(warnings));
if (sample.event.type == SAMPLE_EVENT_NONE)
return;
sample.event.type = SAMPLE_EVENT_STRING;
sample.event.name = name;
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);
}
@ -795,27 +756,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 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;
dc_sample_value_t sample = {0};
name = info->alarm_type;
if (!name)
return;
sample.event.type = lookup_event(name, alarms, C_ARRAY_SIZE(alarms));
if (sample.event.type == SAMPLE_EVENT_NONE)
return;
sample.event.type = SAMPLE_EVENT_STRING;
sample.event.name = name;
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);
}
@ -976,10 +928,6 @@ static int traverse_samples(unsigned short type, const struct type_desc *desc, c
if (desc->size > len)
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++) {
enum eon_sample type = desc->type[i];
int bytes = handle_sample_type(desc, info, type, data);
@ -995,15 +943,6 @@ static int traverse_samples(unsigned short type, const struct type_desc *desc, c
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
if (used && len)
ERROR(eon->base.context, "Entry for '%s' had %d bytes, only used %d", desc->desc, len+used, used);
@ -1026,6 +965,19 @@ suunto_eonsteel_parser_samples_foreach(dc_parser_t *abstract, dc_sample_callback
return DC_STATUS_SUCCESS;
}
static dc_status_t get_string_field(suunto_eonsteel_parser_t *eon, unsigned idx, dc_field_string_t *value)
{
if (idx < MAXSTRINGS) {
dc_field_string_t *res = eon->cache.strings+idx;
if (res->desc && res->value) {
*value = *res;
return DC_STATUS_SUCCESS;
}
}
return DC_STATUS_UNSUPPORTED;
}
// Ugly define thing makes the code much easier to read
// I'd love to use __typeof__, but that's a gcc'ism
#define field_value(p, set) \
@ -1094,11 +1046,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,
* 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)
tank->type = DC_TANKVOLUME_IMPERIAL;
tank->type += DC_TANKINFO_IMPERIAL - DC_TANKINFO_METRIC;
}
break;
case DC_FIELD_STRING:
return get_string_field(eon, flags, (dc_field_string_t *)value);
default:
return DC_STATUS_UNSUPPORTED;
}
@ -1149,7 +1103,7 @@ static void set_depth_field(suunto_eonsteel_parser_t *eon, unsigned short d)
// "enum:0=Off,1=Primary,2=?,3=Diluent"
// "enum:0=Off,1=Primary,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.
//
// We may later turn the METRIC tank size into IMPERIAL if we
@ -1157,7 +1111,7 @@ static void set_depth_field(suunto_eonsteel_parser_t *eon, unsigned short d)
static int add_gas_type(suunto_eonsteel_parser_t *eon, const struct type_desc *desc, unsigned char type)
{
int idx = eon->cache.ngases;
dc_tankvolume_t tankinfo = DC_TANKVOLUME_METRIC;
dc_tankinfo_t tankinfo = DC_TANKINFO_METRIC;
char *name;
if (idx >= MAXGASES)
@ -1168,9 +1122,9 @@ static int add_gas_type(suunto_eonsteel_parser_t *eon, const struct type_desc *d
if (!name)
DEBUG(eon->base.context, "Unable to look up gas type %u in %s", type, desc->format);
else if (!strcasecmp(name, "Diluent"))
;
tankinfo |= DC_TANKINFO_CC_DILUENT;
else if (!strcasecmp(name, "Oxygen"))
;
tankinfo |= DC_TANKINFO_CC_O2;
else if (!strcasecmp(name, "None"))
tankinfo = DC_TANKVOLUME_NONE;
else if (strcasecmp(name, "Primary"))
@ -1223,6 +1177,42 @@ static int add_gas_workpressure(suunto_eonsteel_parser_t *eon, float wp)
return 0;
}
static int add_string(suunto_eonsteel_parser_t *eon, const char *desc, const char *value)
{
int i;
eon->cache.initialized |= 1 << DC_FIELD_STRING;
for (i = 0; i < MAXSTRINGS; i++) {
dc_field_string_t *str = eon->cache.strings+i;
if (str->desc)
continue;
str->desc = desc;
str->value = strdup(value);
break;
}
return 0;
}
static int add_string_fmt(suunto_eonsteel_parser_t *eon, const char *desc, const char *fmt, ...)
{
char buffer[256];
va_list ap;
/*
* We ignore the return value from vsnprintf, and we
* always NUL-terminate the destination buffer ourselves.
*
* That way we don't have to worry about random bad legacy
* implementations.
*/
va_start(ap, fmt);
buffer[sizeof(buffer)-1] = 0;
(void) vsnprintf(buffer, sizeof(buffer)-1, fmt, ap);
va_end(ap);
return add_string(eon, desc, buffer);
}
static float get_le32_float(const unsigned char *src)
{
union {
@ -1246,7 +1236,16 @@ static int traverse_device_fields(suunto_eonsteel_parser_t *eon, const struct ty
const unsigned char *data, int len)
{
const char *name = desc->desc + strlen("sml.DeviceLog.Device.");
if (!strcmp(name, "SerialNumber"))
return add_string(eon, "Serial", data);
if (!strcmp(name, "Info.HW"))
return add_string(eon, "HW Version", data);
if (!strcmp(name, "Info.SW"))
return add_string(eon, "FW Version", data);
if (!strcmp(name, "Info.BatteryAtStart"))
return add_string(eon, "Battery at start", data);
if (!strcmp(name, "Info.BatteryAtEnd"))
return add_string(eon, "Battery at end", data);
return 0;
}
@ -1277,12 +1276,30 @@ static int traverse_gas_fields(suunto_eonsteel_parser_t *eon, const struct type_
if (!strcmp(name, ".Gas.Helium"))
return add_gas_he(eon, data[0]);
if (!strcmp(name, ".Gas.TransmitterID"))
return add_string(eon, "Transmitter ID", data);
if (!strcmp(name, ".Gas.TankSize"))
return add_gas_size(eon, get_le32_float(data));
if (!strcmp(name, ".Gas.TankFillPressure"))
return add_gas_workpressure(eon, get_le32_float(data));
// There is a bug with older transmitters, where the transmitter
// battery charge returns zero. Rather than returning that bogus
// data, just don't return any battery charge information at all.
//
// Make sure to add all non-battery-charge field checks above this
// test, so that it doesn't trigger for anything else.
if (!data[0])
return 0;
if (!strcmp(name, ".Gas.TransmitterStartBatteryCharge"))
return add_string_fmt(eon, "Transmitter Battery at start", "%d %%", data[0]);
if (!strcmp(name, ".Gas.TransmitterEndBatteryCharge"))
return add_string_fmt(eon, "Transmitter Battery at end", "%d %%", data[0]);
return 0;
}
@ -1339,12 +1356,22 @@ static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct ty
return 0;
}
if (!strcmp(name, "Algorithm"))
return add_string(eon, "Deco algorithm", data);
if (!strcmp(name, "DiveMode")) {
if (!strncmp((const char *)data, "CCR", 3)) {
eon->cache.divemode = DC_DIVEMODE_CCR;
eon->cache.initialized |= 1 << DC_FIELD_DIVEMODE;
}
return 0;
return add_string(eon, "Dive Mode", data);
}
/* Signed byte of conservatism (-2 .. +2) */
if (!strcmp(name, "Conservatism")) {
int val = *(signed char *)data;
return add_string_fmt(eon, "Personal Adjustment", "P%d", val);
}
if (!strcmp(name, "LowSetPoint")) {
@ -1359,6 +1386,18 @@ static int traverse_diving_fields(suunto_eonsteel_parser_t *eon, const struct ty
return 0;
}
// Time recoded in seconds.
// Let's just agree to ignore seconds
if (!strcmp(name, "DesaturationTime")) {
unsigned int time = array_uint32_le(data) / 60;
return add_string_fmt(eon, "Desaturation Time", "%d:%02d", time / 60, time % 60);
}
if (!strcmp(name, "SurfaceTime")) {
unsigned int time = array_uint32_le(data) / 60;
return add_string_fmt(eon, "Surface Time", "%d:%02d", time / 60, time % 60);
}
return 0;
}
@ -1385,6 +1424,8 @@ static int traverse_header_fields(suunto_eonsteel_parser_t *eon, const struct ty
eon->cache.maxdepth = d;
return 0;
}
if (!strcmp(name, "DateTime"))
return add_string(eon, "Dive ID", data);
return 0;
}
@ -1428,6 +1469,8 @@ static int traverse_sample_fields(suunto_eonsteel_parser_t *eon, const struct ty
set_depth_field(eon, array_uint16_le(data));
data += 2;
continue;
default:
break;
}
break;
}

109
src/usb_storage.c Normal file
View File

@ -0,0 +1,109 @@
/*
* Dummy "stream" operations for USB storage
*
* Copyright (C) 2018 Linus Torvalds
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "common-private.h"
#include "context-private.h"
#include "iostream-private.h"
#include "iterator-private.h"
#include "descriptor-private.h"
#include "timer.h"
// Fake "device" that just contains the directory name that
// you can read out of the iostream. All the actual IO is
// up to you.
typedef struct dc_usbstorage_t {
dc_iostream_t base;
char pathname[PATH_MAX];
} dc_usbstorage_t;
static dc_status_t
dc_usb_storage_read (dc_iostream_t *iostream, void *data, size_t size, size_t *actual);
static const dc_iostream_vtable_t dc_usbstorage_vtable = {
sizeof(dc_usbstorage_t),
NULL, /* set_timeout */
NULL, /* set_latency */
NULL, /* set_break */
NULL, /* set_dtr */
NULL, /* set_rts */
NULL, /* get_lines */
NULL, /* get_available */
NULL, /* configure */
dc_usb_storage_read, /* read */
NULL, /* write */
NULL, /* flush */
NULL, /* purge */
NULL, /* sleep */
NULL, /* close */
};
dc_status_t
dc_usb_storage_open (dc_iostream_t **out, dc_context_t *context, const char *name)
{
dc_usbstorage_t *device = NULL;
struct stat st;
if (out == NULL || name == NULL)
return DC_STATUS_INVALIDARGS;
INFO (context, "Open: name=%s", name);
if (stat(name, &st) < 0 || !S_ISDIR(st.st_mode))
return DC_STATUS_NODEVICE;
// Allocate memory.
device = (dc_usbstorage_t *) dc_iostream_allocate (context, &dc_usbstorage_vtable, DC_TRANSPORT_USBSTORAGE);
if (device == NULL) {
SYSERROR (context, ENOMEM);
return DC_STATUS_NOMEMORY;
}
strncpy(device->pathname, name, PATH_MAX);
device->pathname[PATH_MAX-1] = 0;
*out = (dc_iostream_t *) device;
return DC_STATUS_SUCCESS;
}
static dc_status_t
dc_usb_storage_read (dc_iostream_t *iostream, void *data, size_t size, size_t *actual)
{
dc_usbstorage_t *device = (dc_usbstorage_t *) iostream;
size_t len = strlen(device->pathname);
if (size <= len)
return DC_STATUS_IO;
memcpy(data, device->pathname, len+1);
if (actual)
*actual = len;
return DC_STATUS_SUCCESS;
}