Replace the hardcoded libusb based code with the new USB I/O transport.
This enables the use of a custom I/O on platforms where libusb is not
available.
The USB communication is now also implemented as an I/O stream
transport. Unlike most I/O devices, USB communication supports multiple
interfaces and endpoints. This requires some some special care:
In the general case, autodetection isn't really possible without
additional knowledge. Hence the need for the filter parameters to pass
this kind of information.
The implementation assumes two bulk endpoints for the standard
read/write interface. Communication with the control endpoint is
supported through the new DC_IOCTL_USB_CONTROL_{READ,WRITE} ioctl's.
Replace the small helper function to retrieve the function pointer and
then call the function, with another helper function to call the filter
function directly. This way the function pointer doesn't need to be
exposed at all.
When the fingerprint feature isn't used (or with a timestamp of zero),
the response to the SIZE (0xC6) and DATA (0xC4) commands is received
almost instantly:
[0.302704] W: C60000000010270000
[0.366727] R: DCF90F00
[0.367829] W: C40000000010270000
[0.394812] R: E0F90F00
But when the fingerprint feature is used (with a non-zero timestamp),
there is a noticable delay:
[0.341218] W: C64CEB204D10270000
[1.927905] R: FE0B0000
[1.931610] W: C44CEB204D10270000
[5.092081] R: 020C0000
In this particular case, the total amount of dive data was close to 1M
bytes, which pushed the delay over the 3 second timeout, and caused the
download to fail. Increasing the timeout to 5 seconds fixed the problem.
The most likely explanation is that the dive computer needs to scan its
internal logbook to determine which dives and how many bytes to send.
Because that involves reading relative slow flash memory, this can take
up to a few seconds, especially if there are many dives present.
The duration of the delay also depends on the value of the fingerprint
timestamp: the less dives we try to download, the longer the delay! I
suspect that's because the most recent dives are located near the end of
the logbook. Hence, the less dives we request, the more dives the dive
computer needs to skip.
Below are some timings for downloading espectively 792410, 531856 and
270850 bytes from the same dive computer, but with a different
fingerprint value:
[0.216305] W: C6F8D5C84510270000
[0.373511] R: 56170C00
[0.378929] W: C4F8D5C84510270000
[0.661388] R: 5A170C00
[0.236246] W: C620D80F4810270000
[0.559608] R: 8C1D0800
[0.563755] W: C420D80F4810270000
[1.171631] R: 901D0800
[0.246193] W: C654E6434A10270000
[0.826365] R: FE210400
[0.831760] W: C454E6434A10270000
[1.974351] R: 02220400
- Added missing man pages for the new functions.
- Updated the main libdivecomputer man page to reflect the new flow.
- Fixed minor typos in the dc_parser_get_field and
dc_parser_samples_foreach functions.
Instead of assuming that the dive list is presented in a sorted circular
list, sort it properly alphabetically (which also ends up being a
numerical sort for the HEX ascii dive names).
The "search for most recent dive, then splice the list around" case
doesn't work in the general case. It happens to work if you don't
delete any dives, and dives only disappear as they are being overwritten
by new dives when the storage overflows.
But if you delete dives and then create new ones, the dive list will not
be sorted at all, and we should sort it properly when downloading.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
In recent hwos firmware versions, the depth is no longer stored as
pressure (in millibar), but directly as depth (in meters) with the
salinity and gravity factor already applied.
The S_BLOCK_WRITE (0x30) command sends a stream of bytes to the dive
computer. Because the payload has no fixed length and there is no length
field included, the hwOS firmware detects the end of the stream by means
of a 400ms timeout. The main disadvantage of this approach is that a
short hiccup in the communication will be incorrectly detected as the
end of the stream. Hence only a part of the data will get written to
the flash memory, and the remainder of the data will get interpreted as
the next commands.
To avoid this problem, the hwOS firmware v3.09 and later supports a new
S_BLOCK_WRITE2 (0x31) command, which uses a fixed size payload of 256
bytes.
Reported-by: Ralph Lembcke <mail@ralph-lembcke.de>
By reading the firmware version information immediately after entering
download or service mode, we can identify the specific firmware version
and adapt to minor differences in the communication protocol.
The S_BLOCK_WRITE (0x30) command sends a stream of bytes to the dive
computer. Because the payload has no fixed length and there is no length
field included, the hwOS firmware detects the end of the stream by means
of a 400ms timeout. Therefore the ready byte is always delayed by this
400ms timeout.
The same remark applies to the DISPLAY (0x6E) and CUSTOMTEXT (0x63)
commands. But because libdivecomputer always pad the text with zeros and
sends the maximum payload size, we won't hit the timeout.
Reported-by: Ralph Lembcke <mail@ralph-lembcke.de>
Erasing a flash memory page is a relative slow operation and takes a
significant amount of time. Therefore, the ready byte is delayed, and
the standard timeout is no longer sufficient. Estimate the required
delay and wait.
Reported-by: Ralph Lembcke <mail@ralph-lembcke.de>
The hwOS firmware reads the service init command one byte at a time, and
sends the echo immediately after each byte.
Reported-by: Ralph Lembcke <mail@ralph-lembcke.de>
For the Oceanic Pro Plus X and the Aqualung i770R, downloading over BLE
often fails because the version packet contains one or more unexpected
bytes.
For a successful download, the correct structure for the version packet
is as follows:
5A 4F4345414E4F43582031432030303032 C6
That's the start byte, the payload "OCEANOCX 1C 0002" and the checksum.
For all the failed packets, there are one or more bytes extra present
between the payload and the checksum:
5A 4F4345414E4F43582031432030303032 9F02 67
5A 4F4345414E4F43582031432030303032 3603 FF
5A 4F4345414E4F43582031432030303032 64 2A
5A 4F4345414E4F43582031432030303032 9202 5A
5A 4F4345414E4F43582031432030303032 08 CE
5A 4F4345414E4F43582031432030303032 2C01 F3
The amount of extra bytes, and their content appears to be pretty
random. The strangest part is that the checksum of the packet is
actually correct and includes those extra bytes!
As workaround, accept extra bytes in the BLE packet, verify the checksum
as usual, and finally strip the excess bytes and only pass the actual
content to the next layer. To avoid false positives, the workaround is
limited to packets with a payload and checksum, and only enabled for the
two affected models.
When the last deco stop is cleared, the dive computer switches to NDL
mode with an infinite time (0x7FFF for APOS4 and 0xFFFF for APOS3). But
because libdivecomputer does not report those infinite values to the
application, detecting the end of the deco phase is not very intuitive.
This issue is fixed by passing those infinite NDL values as-is to the
application, despite the relative large values (respectively 9.1 and
18.2 hours). For reference, the finite NDL values reported by the ratio
dive computers can be large as well, with values up to 0x4000 (4.55
hours).
Due to how the Oceanic ringbuffer is implemented, the ringbuffer always
contains at least one entry. If there are no dives recorded yet, the
content of that entry will be empty. Such entries are already ignored
during processing, but instead of returning this empty entry to the
caller, simply clear the logbook buffer, and return no entries at all.
Previously, invalid ringbuffer pointers were always handled during the
second pass. But that changed after the previous commit. If the invalid
pointer is located in the first logbook entry, this is now handled as
"no dives present". Fixed by returning the correct error code instead.
If all the entries in the logbook ringbuffer happen to be empty, the
ringbuffer end pointer will not have a valid value. Creating the
ringbuffer stream will fail, and an error will be returned to the
caller. Fixed by adding an extra check, and exit if there are no dives.
The depth value is encoded with only 11 bits instead of 12 bits. The
extra bit contains the gas mix index. This resulted in wrong depths,
with values larger than 204.8m.
For the BCD encoded day field (range 1-31), two bits are sufficient to
represent the upper digit (range 0-3). The purpose of the highest bit is
unknown, but it's certainly not part of the day field, and needs to be
masked off.
The bluetooth device filtering is based on the fact that the format of
the bluetooth device name is something like 'FQ001124', where the two
first letters are the ASCII representation of the model number (e.g.
'FQ' or 0x4651 for the i770R), and the six digits are the serial number.
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.
Linus Torvalds reports the BLE pattern for the i770R is normally just
"0001", allthough he once also observed "0090" with the same dive
computer. A communication trace from a Pro Plus X also showed "0001".
We don't have enough information to guess the meaning of the number.
Regardless, for those two dive computers supporting BLE, make the
pattern simply ignore the last four digits, since they clearly vary.
Based-on-code-by: Linus Torvalds <torvalds@linux-foundation.org>
The BLE communication sends a handshake packet containing 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 has
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 device name the device advertizes, which
is the reason for the newly added DC_IOCTL_BLE_GET_NAME ioctl.
Thanks to Janice McLaughlin for pointing out the logic of this magic
handshake.
Based-on-code-by: Linus Torvalds <torvalds@linux-foundation.org>
Refactor the packet receiving code to read the ack byte, the payload
data and the checksum all at once, with just a single read operation.
This is not only a bit more efficient, but will also simplify the BLE
support.
The trailing zero byte is present for historic reasons only. At the time
the Oceanic protocol was implemented, the Oceanic application send this
extra zero byte too, and we simply copied this behaviour. But more
recent versions no longer send it. Probably a small (harmless) bug that
was fixed.
The write command is send as two separate packets. The first packet
contains the B2 command and the page number, and the second packet
contains the payload and checksum. Because the payload can contain
arbitrary data, the first byte of a packet is not necessary a command
byte. But the code to select the correct ack byte is based on this
assumption. Fixed by passing the expected ack byte.
The set_latency function is the perfect example of a feature that should
be implemented as an ioctl: it's only implemented by a single driver,
and the functionality is also highly platform specific.
This new ioctl function allows to perform I/O stream specific requests
through a generic interface. This provides an easy way to extend the I/O
interface with some driver specific features, without having to modify
the public api.