In one of the Windows system header files, an "interface" macro is
defined as:
#define interface struct
This results in some very strange build errors when also including the
descriptor-private.h header file. That's because the dc_usb_params_t
struct has a member field named "interface":
typedef struct dc_usb_params_t {
unsigned int interface;
unsigned char endpoint_in;
unsigned char endpoint_out;
} dc_usb_params_t;
As a workaround, define the WIN32_LEAN_AND_MEAN macro before including
the windows.h header file. This excludes some less common Windows API
declarations, including the above one.
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.
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.
The Linux implementation is very straighforward and just a lightweight
wrapper around the select function. But the Windows implementation is
much more complex, because the Windows event notification mechanism
behaves very different:
The WaitCommEvent function does not support a timeout and is always a
blocking call. The only way to implement a timeout is to use
asynchronous I/O (or overlapped I/O as it's called in the Windows API),
to run the operation in the background. This requires some additional
book keeping to keep track of the pending background operation.
The event mechanism is also edge triggered instead of level triggered,
and reading the event with the WaitCommEvent function clears the pending
event. Therefore, the state of the input buffer needs to be checked with
the ClearCommError function before and after the WaitCommEvent call.
The check before is necessary in case the event is already cleared by a
previous WaitCommEvent call, while there is still data present in the
input buffer. In this case, WaitCommEvent should not be called at all,
because it would wait until more data arrives.
The check afterwards is necessary in case WaitCommEvent reports a
pending event, while the data in the input buffer has already been
consumed. In this case, the current event must be ignored and
WaitCommEvent needs to be called again, to wait for the next event.
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.
The device descriptors are extended with a filter function. During the
device discovery, this filter function is used to return only devices
that match a known dive computer.
The filtering is optional, and can be disabled by passing a NULL pointer
for the device descriptor when creating the iterator with one of the
dc_xxx_iterator_new() functions.
Replacing the callback based interface with an iterator based interface,
results in a more extensible abstraction with a common interface for
each of the built-in I/O implementations (serial, usbhid, irda and
bluetooth).
Errors reported by system calls are now converted to the corresponding
libdivecomputer status code. This results in a more descriptive and
meaningfull return value.
The low level serial and IrDA functions are modified to:
- Use the libdivecomputer namespace prefix.
- Return a more detailed status code instead of the zero on success and
negative on error return value. This will allow to return more
fine-grained error codes.
- The read and write functions have an additional output parameter to
return the actual number of bytes transferred. Since these functions
are not atomic, some data might still be transferred successfully if
an error occurs.
The dive computer backends are updated to use the new api.
When the close function returns, all resources should be freed,
regardless of whether an error has occured or not. The error code is
purely informative.
However, in order to return the first error code, which is usually the
most interesting one, the current implementation is unnecessary
complicated. If an error occurs, there is no need to exit immediately.
Simply store the error code unless there is already a previous one, and
then continue.
Currently this isn't used or needed anywhere, but the research has been
done, and it would be silly to drop the knowledge. We may need it in the
future.
For the time being, the serial port enumeration code is of very limited
use. It's not used anywhere in the library, and as an internal api it's
also not available to applications. It serves mainly as a reference
implementation for future use.
Apparantly, the windows wingdi.h header file already defines the
ERROR macro. By defining the NOGDI macro before including the
windows.h header file, we can prevent the wingdi.h file from being
included and thus avoid the warning. We don't need that header for
anything anyway.
Because the libusb header file includes the windows.h file
explicitly, it needs the same fix.
The public header files are moved to a new subdirectory, to separate
the definition of the public interface from the actual implementation.
Using an identical directory layout as the final installation has the
advantage that the example code can be build outside the project tree
without any modifications to the #include statements.
When using half-duplex communication (e.g. only a single wire for both
Tx and Rx) a data packet needs to be transmitted entirely before
attempting to switch into receiving mode.
For legacy serial hardware, the tcdrain() probably works as advertised,
and waits until the data has been transmitted. However for common
usb-serial converters, the hardware doesn't provide any feedback to the
driver, and the tcdrain() function can only wait until the data has been
transmitted to the usb-serial chip. There is no guarantee that the data
has actually been transmitted by the usb-serial chip.
As a workaround, we wait at least the minimum amount of time required to
transmit the data packet over a serial line, taking into account the
current configuration.
The "\\.\" prefix allows to access the Win32 device namespace directly,
without going through the file system. This is required to support
non-standard port names, and COMx ports with a number greater than 9.