[PATCH] libdivecomputer - custom implementation for serial communication

Jef Driesen jef at libdivecomputer.org
Mon Jun 29 07:53:04 PDT 2015


On 2015-06-26 01:33, Claudiu Olteanu wrote:
> I attached some patches which can be used to make the *libdivecomputer 
> *
> project to accept callbacks to custom functions used for serial
> communication.
> 
> As I mentioned in my weekly report, I am not sure if I made the best
> decisions on the implementation.

I didn't have time yet to go through all the bluetooth related emails 
and do a detailed review of your work so far. Instead, I'll describe the 
architectural changes that I had already planned for integrating my 
bluetooth prototype. Conceptually there is really no difference with the 
changes that are needed for your custom I/O backend.

There are three main steps:

1. Abstract serial interface

The very first step will be to change the serial communication layer 
from a concrete implementation into a abstract interface. This will be 
very similar to how the device and parser layer work. So if you wonder 
how something should be done, just replace "serial" with "device" or 
"parser" and look how it's done there.

We'll need three files here:

  * include/libdivecomputer/serial.h
  * src/serial-private.h
  * src/serial.c

The first one is the public interface. This is basically the existing 
src/serial.h file, but everything gets an extra "dc_" prefix. There is 
no need for the dc_serial_open() function, because each backend will 
have to implement its own, and may need completely different parameters. 
(If you're familiar with C++, a virtual constructor is not possible.)

(Note that the serial api will need some additional rework too in order 
to become public, but let's ignore that for now.)

In the serial-private.h file, you define the base structure that is 
shared by all implementations:

struct dc_serial_t {
     const dc_serial_vtable_t *vtable;
     dc_context_t *context;
};

and the virtual function table containing one function pointer for each 
public method:

struct dc_serial_vtable_t {
     int (*close) (dc_serial_t *serial);
     ...
};

In the serial.c file, you implement each function by calling the 
corresponding function in the vtable.


Next, we need to modify the existing serial code, and turn it into an 
implementation of our new interface. To do this, you add the new 
dc_serial_t struct as the first member of the existing struct:

struct serial_t {
     dc_serial_t base;
     ...
};

And setup a vtable:

static const dc_serial_vtable_t serial_vtable = {
     serial_close, /* close */
     ...
};

Of course you'll need a bit more glue code here and there, but I think 
you get the idea. If not, just ask.


Last but not least, you replace all serial_xxx calls with dc_serial_xxx 
calls in the entire codebase.

At this point there should be no functional changes (e.g. everything is 
still working as before), but we now have the necessary infrastructure 
for implementing a custom serial I/O layer.

2. Custom serial backend

This will be very similar to the native backend. The public part should 
contain a structure with the callback functions:

typedef struct dc_custom_serial_operations_t {
     int (*close) (void *userdata);
     ...
} dc_custom_serial_operations_t;

Notice how I'm passing a void pointer here! The internals of the custom 
backend are not exposed to the application! Instead we pass a userdata 
cookie that needs to be supplied by the application in the custom open 
function:

int
dc_custom_serial_open (dc_serial_t **out, dc_context_t *context, const 
dc_serial_operations_t *ops, void *userdata);

And in the private implementation part:

struct dc_custom_serial_t {
     dc_serial_t base;
     const dc_custom_serial_operations_t *ops;
     void *userdata;
};

static const dc_serial_vtable_t custom_serial_vtable = {
     custom_serial_close, /* close */
     ...
};

static int
custom_serial_close(dc_serial_t *serial)
{
     return serial->ops->close(serial->userdata);
}

At this point, the application can already create a custom I/O backend, 
but libdivecomputer won't be able to use it yet, because it's still 
hardcoded to use the native backend.

3. Modify the dc_device_open() function

The last remaining step is to replace the last "const char *name" 
parameter of the dc_device_open() function with a "dc_serial_t *serial" 
parameter. Of course this will also need similar changes to all dive 
computer backends.

(To take into account irda and usb communication, we may actually need a 
void* parameter here, so we can also pass a dc_irda_t parameter in the 
future. Or we need to introduce a more generic I/O abstraction layer. 
For example something like a dc_stream_t object, which can abstract both 
serial and irda communication.)

This is a relative small change, but it does affect the application 
side. So we'll have to decide how we're going to deal with this: modify 
the existing function and break backwards compatibility, or add another 
dc_device_open2() function and keep the old one around for a while?

(Note that after v0.5 backwards compatibility will get broken anyway, 
because I want to cleanup some older mistakes too.)

Jef


More information about the subsurface mailing list