Instead of reading data packets, the code is actually sending some
random data to the dive computer! A small typo with bad consequences!
This is a critical bug because it not only causes the download to fail,
but also appears to change random settings on the dive computer. I
suspect that the garbage data that gets send to the dive computer
happens to contain some valid write settings commands.
For most I/O stream implementations the serial communication specific
functions are meaningless. Implementing them as no-ops allows the dive
computer backends the call the I/O stream functions unconditionally.
However, implementing the no-op with a dummy function returning
DC_STATUS_SUCCESS, does not only add some (small) overhead at runtime,
but also requires many such functions. This is inconvenient and the same
result can easily be obtained by using a NULL pointer instead.
The consequence is that the logic is reversed now. To obtain the
previous behaviour of returning the DC_STATUS_UNSUPPORTED error code
again, you'll need to implement a dummy function. But that's fine
because it's the less common case.
I/O functions with output parameters, should always initialize those
output parameters, even when an error is returned. This prevents the
(accidental) use of uninitialized variables, whenever the caller forgets
to check the return code.
As a nice side effect, the use of a local variable guarantees that the
underlying I/O implementation will always receive a valid pointer.
When two or more identical (or very similar) dive computers are
connected, the USB VID/PID can be ambiguous. That's because the VID/PID
identifies the type of the USB device, and not the individual device.
But each USB HID device descriptor returned by the device discovery
represents a single connected device, and thus guarantees to open the
correct USB device.
To obtain the same behaviour as before, an application can simply open
the first discovered device.
Replace the global USB library context with a reference counted session
to manage the lifetime of the USB library context. For the libusb based
implementation, this is actually a much better match for the underlying
libusb api, and allows to eliminate the global state. For the hidapi
based implementation, the global state is unavoidable because the hidapi
doesn't support multiple sessions. Therefore we use a singleton session.
The main difference with the serial communication is that the BLE
communication transmits each SLIP encoded data packet as one or more BLE
data packets. The BLE packets have an extra two byte header with the
total number of packets and the current packet number.
The main difference with the serial communication is that the BLE
communication uses data packets (with a maximum size of 20 bytes)
instead of a continuous data stream.
The main difference with the USB HID communication is that the BLE data
packets have a variable size and are no longer padded to the full 32
(Tx) or 64 (Rx) bytes.
The main difference with the USB HID communication is that the BLE data
stream is encoded using HDLC framing with a 32 bit CRC checksum. Due to
this encoding, the data packets can no longer be processed one by one
(as is done for the USB HID packets). The entire HDLC encoded stream
needs to be received before it can be processed. This requires some
additional buffering.
Setting a default transport type avoids the need to explicitely set a
transport using the the new --transport command-line option. This also
preserves backwards compatibility with previous versions where the
option didn't exist yet.
The dctool example application is updated to the latest changes:
- The I/O stream is opened and closed by the application.
- A new (mandatory) option is added to select the desired transport
type. This is nessecary because several dive computers support
multiple transport types now.
Currently the dive computer backends are responsible for opening (and
closing) the underlying I/O stream internally. The consequence is that
each backend is hardwired to a specific transport type (e.g. serial,
irda or usbhid). In order to remove this dependency and support more
than one transport type in the same backend, the opening (and closing)
of the I/O stream is moved to the application.
The dc_device_open() function is modified to accept a pointer to the I/O
stream, instead of a string with the device node (which only makes sense
for serial communication). The dive computer backends only depend on the
common I/O interface.
With the support for multiple transports per device, the
dc_descriptor_get_transport() function became obsolete because it does
support only a single transport type. Applications should use the new
dc_descriptor_get_transports() function instead.
With the support for multiple transports per device and the possibility
to use custom I/O implementations, libdivecomputer no longer knows which
devices are actually supported. Hence libdivecomputer needs to always
report all the devices it knows about, and it's up to the application to
filter out entries for which there is no suitable transport available
(either built-in or custom).
Because the list of supported built-in transports depends on the
availability of external libraries (libusb, hidapi) and the operating
system, the application needs some mechanism to retrieve this
information at runtime. Therefore, a new dc_context_get_transports()
function is added, which returns a bitmask with all the available
built-in transports.
Several dive computers support multiple transports. For example the
Suunto Eon Steel supports both USB HID and BLE. All devices using
bluetooth classic communication support both the native bluetooth
transport and the legacy serial port emulation.
To support this feature, the values of the dc_transport_t type are
changed into bitmasks, and the dc_descriptor_t struct is extended with a
bitfield with all the supported transports.
Add a function to query the underlying transport type. This allows the
dive computer backends to implement transport specific behaviour where
necessary.
For the built-in I/O implementations, the transport type is obviously
always hardcoded, but for a custom I/O implementation the application
needs to provide the correct type. Hence the transport type can't be
hardcoded in the vtable and needs to be passed as a parameter.
Trying to purge the input buffer by reading and discarding data packets,
results in an annoying and confusing error message if no data packet is
received. To avoid this error, the functionality should be integrated in
the USB HID code, either automatically during initialization or by
implementing the purge function.
But since there seems to be no evidence that this is actually necessary,
let's remove this code.
Check the fingerprint before downloading the dive. If a match is found,
this avoids some unnecessary communication and thus makes the download a
little bit faster.
The file list isn't freed when an error occurs, and the strings returned
from the lookup_enum function are dynamically allocated and thus should
be freed as well.
The descriptor strings are dynamically allocated and owned by the
struct. The const qualifiers are a bit misleading here, and result in
warnings when trying to free the pointers again.
The multiplication is evaluated using 32bit arithmetic, and then stored
in a 64bit integer. The 32bit integer overflow can be avoided by casting
to a 64bit type first.
The device descriptor is either mandatory for a certain command (with
DCTOOL_CONFIG_DESCRIPTOR) or always NULL. But for some commands it will
be useful to support an optional descriptor as well. To support this, we
always try to lookup the device descriptor whenever the corresponding
command-line options are set.
The write function is modified to always fill the buffer completely
before sending out the data. For escaped characters, the previous
implementation needed to append two bytes at once. Thus, if there was
only space left for a single byte, the buffer got flushed early with one
byte still unused. This can be avoid by appending one byte at a time.
The read function is modified to use a simple state machine with only a
single read call. This is mainly preparation to support reading and
processing larger data packets instead of just single bytes.
I received a bug report complaining that the most recent dives did not
get downloaded. It turns out that the internal dive number in the
logbook entries got reset back to zero somehow:
Logbook 0: empty
...
Logbook 209: empty
Logbook 210: 1
Logbook 211: 2
...
Logbook 235: 26
Logbook 236: 27
Logbook 237: 0
Logbook 238: 1
...
Logbook 254: 17
Logbook 255: 18
This confuses the logic to locate the most recent dive. Because that
logic assumes that the entry with the highest internal dive number is
always the most recent dive, it finds logbook entry #236 instead of the
correct entry #255. Now, when processing the logbook entries backwards,
it stops at those empty entries, and thus never reaches then newest
entries #237 to #255.
The workaround is based on the fact that the OSTC4, unlike the other
hwos based models, already re-orders the logbook entries and always
sends the most recent logbook entry last. So we can ignore the dive
number and simply use the last non-empty entry.
I'd never noticed this before, since my date had always been already set
correctly, but the timesync with the EON Steel only set the time, not
the date.
The fix is trivial, since the code already filled in the datetime data,
it just didn't do the SET_DATE command.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
The Dive Rite NiTek Q supports OC and CC samples. For reporting the dive
mode, any dive containing at least one CC sample is considered to be a
CCR dive.
The half-duplex emulation is basically a workaround for a timing problem
in the vyper2 backend. Since no other dive computer backend uses or
needs this feature, it makes no sense to support this at the I/O layer.
It can be implemented directly in the vyper2 backend instead.
Replace the gettimeofday() based implementation with the new monotonic
timers. This makes the implementation more robust against unexpected
adjustments of the clock.
The new timer module provides an cross-platform interface for a high
resolution monotonic clock. The timestamps are always relative to the
creation of the timer and their unit is one microseconds.
The timers can be used for logging, measuring elapsed time and
implementing timeouts.