Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
#include "config.h"
#include "insulinx.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/* Abbott FreeStyle InsuLinx reverse-engineered protocol.
*
* This is based on USB logs of 'auto-assist' Windows application, captured
* using USBSnoop (http://www.pcausa.com/Utilities/UsbSnoop/).
* See log files and parser.py openglucose/data/insulinx/.
*
* Buffers of 64 bytes are transferred between the host and the device. The host
* sends a request to the device and pull the reply. The first byte of the
* buffer is (probably) the type of the message, the 2nd byte is the length of
* the message, and rest is ASCII message. Bytes after lenght+2 are meaningless.
*
* There is first an init sequence, it is still a bit obscure but it goes like
* this:
*
* 1) No idea if it has particular meaning
* Request: code=0x4, msg=""
* Reply: code=0x34, msg="0xc"
*
* 2) That's the serial number followed by \0
* Request: code=0x5, msg=""
* Reply: code=0x6, msg="JAGT241-U62420x0"
*
* 3) That's device' software version followed by \0
* Request: code=0x15, msg=""
* Reply: code=0x35, msg="1.400x0"
*
* 4) No idea if it has particular meaning
* Request: code=0x1, msg=""
* Reply: code=0x71, msg="0x1"
*
* After that all kind of commands can be sent in the form:
*
* Request: code=0x60, msg="$foo?\r\n"
* Reply: code=0x60, msg="bar\r\nCKSM:0000014C\r\nCMD OK\r\n"
*
* The reply can be on multiple buffers, thus it need to pull replies until the
* "CKSM:XXXXXXXX\r\nCMD OK\r\n" is received.
*
* CKSM is the checksum of the reply, it is the simple sum of ASCII values in
* hexadecimal. In the above example it would be:
* 'b' + 'a' + 'r' + '\r' + '\n' = 0x14c
*
* Every 3 requests, the reply will start first with a special buffer starting
* with 0x22 0x01 0x03. I don't know what it means, they are ignored.
*
* To change the value of "foo":
*
* Request: code=0x60, msg="$foo,newvalue\r\n"
* Reply: code=0x60, msg="CKSM:00000000\r\nCMD OK\r\n"
*
* Here are all the commands issued by auto-assist when plugging the device:
*
* $serlnum?, $swver?, $date?, $time?, $ptname?, $ptid?, $getrmndrst,0,
* $getrmndr,0, $rmdstrorder?, $actthm?, $wktrend?, $gunits?, $clktyp?,
* $alllang?, $lang?, $inslock?, $actinscal?, $iobstatus?, $foodunits?,
* $svgsdef?, $corsetup?, $insdose?, $inslog?, $inscalsetup?, $carbratio?,
* $svgsratio?, $mlcalget,3, $cttype?, $bgdrop?, $bgtrgt?, $bgtgrng?,
* $ntsound?, $btsound?, $custthm?, $taglang?, $tagsenbl?, $tagorder?,
* $result?, $gettags,2,2, $frststrt?
*/
G_DEFINE_TYPE (OgInsulinx, og_insulinx, OG_TYPE_BASE_DEVICE)
#define BUFFER_SIZE 64
#define DEBUG g_debug
#define DEBUG_MSG debug_msg
typedef void (*ParserFunc) (OgInsulinx *self,
guint8 code,
const gchar *msg);
typedef struct
{
guint8 code;
gchar *cmd;
ParserFunc parser;
} Request;
struct _OgInsulinxPrivate
{
OgBaseDeviceStatus status;
GUsbDevice *usb_device;
GCancellable *cancellable;
/* +1 so we can always add a \0 at the end for safety */
guint8 send_buffer[BUFFER_SIZE + 1];
guint8 receive_buffer[BUFFER_SIZE + 1];
GString *received;
guint cksm;
gboolean cksm_received;
/* GQueue<owned Request> */
GQueue request_queue;
Request *req;
GTask *task;
gchar *serial_number;
gchar *sw_version;
GDateTime *device_clock;
GDateTime *system_clock;
GPtrArray *records;
gchar *first_name;
gchar *last_name;
guint year, month, day;
};
enum
{
PROP_0,
PROP_USB_DEVICE,
};
static void
ptr_array_add_null_term (GPtrArray *array,
gpointer ptr)
{
/* Remove ending NULL first */
g_ptr_array_remove_index_fast (array, array->len - 1);
g_ptr_array_add (array, ptr);
g_ptr_array_add (array, NULL);
}
static void
debug_msg (const gchar *way,
guint8 code,
const gchar *msg)
{
GString *string;
guint i;
string = g_string_sized_new (strlen (msg));
for (i = 0; msg[i] != '\0'; i++)
{
if (g_ascii_isprint (msg[i]))
g_string_append_c (string, msg[i]);
else if (msg[i] == '\r')
g_string_append (string, "\\r");
else if (msg[i] == '\n')
g_string_append (string, "\\n");
else
g_string_append_printf (string, "0x%02x", msg[i]);
}
DEBUG ("%s: code=0x%02x, msg=\"%s\"", way, code, string->str);
g_string_free (string, TRUE);
}
static void
change_status (OgInsulinx *self,
OgBaseDeviceStatus status)
{
if (self->priv->status == status)
return;
self->priv->status = status;
g_object_notify ((GObject *) self, "status");
}
static void
report_error (OgInsulinx *self,
GError *error)
{
/* Ignore CANCELLED error, it is either voluntary or consequence of an earlier
* error. */
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
DEBUG ("Error: %s", error->message);
change_status (self, OG_BASE_DEVICE_STATUS_ERROR);
g_cancellable_cancel (self->priv->cancellable);
}
if (self->priv->task != NULL)
{
g_task_return_error (self->priv->task, error);
g_clear_object (&self->priv->task);
}
else
{
g_error_free (error);
}
}
static void
control_transfer_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
OgInsulinx *self = user_data;
GError *error = NULL;
if (g_usb_device_control_transfer_finish (self->priv->usb_device, result,
&error) < 0)
{
report_error (self, error);
goto out;
}
out:
g_object_unref (self);
}
static void
request_queue_continue (OgInsulinx *self)
{
gsize len;
if (self->priv->req != NULL)
return;
self->priv->req = g_queue_pop_head (&self->priv->request_queue);
if (self->priv->req == NULL)
{
GTask *task = self->priv->task;
self->priv->task = NULL;
change_status (self, OG_BASE_DEVICE_STATUS_READY);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
change_status (self, OG_BASE_DEVICE_STATUS_BUZY);
len = strlen (self->priv->req->cmd);
g_assert (len <= BUFFER_SIZE - 2);
self->priv->send_buffer[0] = self->priv->req->code;
self->priv->send_buffer[1] = len;
g_memmove (self->priv->send_buffer + 2, self->priv->req->cmd, len);
/* Send the request */
DEBUG_MSG ("Sent", self->priv->req->code, self->priv->req->cmd);
g_usb_device_control_transfer_async (self->priv->usb_device,
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_CLASS,
G_USB_DEVICE_RECIPIENT_INTERFACE,
0x09,
0x0200,
0,
self->priv->send_buffer, BUFFER_SIZE,
0,
self->priv->cancellable,
control_transfer_cb,
g_object_ref (self));
}
static void
queue_request (OgInsulinx *self,
guint8 code,
const gchar *cmd,
ParserFunc parser)
{
Request *req;
req = g_slice_new0 (Request);
req->code = code;
req->cmd = g_strdup (cmd);
req->parser = parser;
g_queue_push_tail (&self->priv->request_queue, req);
request_queue_continue (self);
}
static void
request_free (Request *req)
{
g_free (req->cmd);
g_slice_free (Request, req);
}
static void
request_done (OgInsulinx *self)
{
g_clear_pointer (&self->priv->req, request_free);
self->priv->cksm_received = FALSE;
self->priv->cksm = 0;
request_queue_continue (self);
}
static gboolean
find_line_break (GString *string,
guint *pos)
{
gchar *p;
p = g_strstr_len (string->str, string->len, "\r\n");
if (p == NULL)
return FALSE;
*pos = p - string->str;
return TRUE;
}
static guint
checksum (const gchar *str)
{
guint ret = 0;
guint i;
for (i = 0; str[i] != '\0'; i++)
ret += str[i];
return ret;
}
static void
parser_common (OgInsulinx *self,
guint8 code,
const gchar *msg)
{
guint pos;
g_assert (self->priv->req != NULL);
g_assert (self->priv->req->parser != NULL);
/* If it is one of the initialization requests, pass it to the specialized
* parser directly. */
if (self->priv->req->code != 0x60)
{
self->priv->req->parser (self, code, msg);
return;
}
/* FIXME: Not sure what they are, ignore */
if (code == 0x22)
{
if (msg[0] != 0x3 || msg[1] != '\0')
{
report_error (self, g_error_new (OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_PARSER,
"Received 0x22 buffer with unusual msg"));
}
return;
}
if (code != 0x60)
{
report_error (self, g_error_new (OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_PARSER,
"Made a 0x60 request and received something else"));
return;
}
/* Accumulate the received msg with what's left unparsed of the previous msg.
* It can happen that a msg is split into multiple buffers. */
g_string_append (self->priv->received, msg);
/* Let's parse what we received line by line */
while (find_line_break (self->priv->received, &pos))
{
gchar *line;
guint cksm;
line = self->priv->received->str;
line[pos] = '\0';
if (sscanf (line, "CKSM:%8x", &cksm) == 1)
{
/* We received the checksum */
if (cksm != self->priv->cksm)
{
report_error (self, g_error_new (OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_PARSER,
"Checksum mismatch: expected %x, calculated %x",
cksm, self->priv->cksm));
return;
}
self->priv->cksm_received = TRUE;
}
else if (self->priv->cksm_received)
{
/* Previous line was the checksum, the only valid line afterward is
* "CMD OK", we can start the next request after that. */
if (!g_str_equal (line, "CMD OK"))
{
report_error (self, g_error_new (OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_PARSER,
"Checksum not followed by \"CMD OK\""));
return;
}
request_done (self);
}
else
{
/* Give that line to the specialized parser */
self->priv->req->parser (self, code, line);
if (self->priv->status == OG_BASE_DEVICE_STATUS_ERROR)
return;
/* Incrementaly calculate the checksum, including the line break
* that we stripped. */
self->priv->cksm += checksum (line) + checksum ("\r\n");
}
g_string_erase (self->priv->received, 0, pos + 2);
}
}
static void start_interrupt_transfer (OgInsulinx *self);
static void
interrupt_transfer_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
OgInsulinx *self = user_data;
guint8 code;
guint8 msg_len;
gchar *msg;
GError *error = NULL;
if (g_usb_device_interrupt_transfer_finish (self->priv->usb_device, result,
&error) < 0)
{
report_error (self, error);
goto out;
}
if (self->priv->req == NULL)
{
report_error (self, g_error_new (OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_UNEXPECTED,
"Received a buffer while nothing was requested"));
goto out;
}
/* 1st byte is the type of the message */
code = self->priv->receive_buffer[0];
/* 2nd byte is the length of the message */
msg_len = self->priv->receive_buffer[1];
if (msg_len > BUFFER_SIZE - 2)
{
report_error (self, g_error_new (OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_PARSER,
"Message length bigger than buffer size"));
goto out;
}
/* Extract the message and ensure it is 0-terminated */
msg = (gchar *) self->priv->receive_buffer + 2;
msg[msg_len] = '\0';
DEBUG_MSG ("Received", code, msg);
parser_common (self, code, msg);
/* continue pulling */
if (self->priv->status != OG_BASE_DEVICE_STATUS_ERROR)
start_interrupt_transfer (self);
out:
g_object_unref (self);
}
static void
start_interrupt_transfer (OgInsulinx *self)
{
g_usb_device_interrupt_transfer_async (self->priv->usb_device,
0x81,
self->priv->receive_buffer, BUFFER_SIZE,
0,
self->priv->cancellable,
interrupt_transfer_cb,
g_object_ref (self));
}
static void
og_insulinx_init (OgInsulinx *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
OG_TYPE_INSULINX, OgInsulinxPrivate);
g_queue_init (&self->priv->request_queue);
self->priv->cancellable = g_cancellable_new ();
self->priv->received = g_string_new (NULL);
self->priv->records = g_ptr_array_new_with_free_func (
(GDestroyNotify) og_record_free);
g_ptr_array_add (self->priv->records, NULL);
}
static void
get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
OgInsulinx *self = (OgInsulinx *) object;
switch (property_id)
{
case PROP_USB_DEVICE:
g_value_set_object (value, self->priv->usb_device);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
OgInsulinx *self = (OgInsulinx *) object;
switch (property_id)
{
case PROP_USB_DEVICE:
g_assert (self->priv->usb_device == NULL);
self->priv->usb_device = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
finalize (GObject *object)
{
OgInsulinx *self = (OgInsulinx *) object;
g_object_unref (self->priv->usb_device);
g_object_unref (self->priv->cancellable);
g_string_free (self->priv->received, TRUE);
g_free (self->priv->serial_number);
g_free (self->priv->sw_version);
g_clear_pointer (&self->priv->device_clock, g_date_time_unref);
g_clear_pointer (&self->priv->system_clock, g_date_time_unref);
g_clear_pointer (&self->priv->records, g_ptr_array_unref);
G_OBJECT_CLASS (og_insulinx_parent_class)->finalize (object);
}
static void
parse_init_first (OgInsulinx *self,
guint8 code,
const gchar *msg)
{
/* We could be receiving replies from a previous request that made the app
* crash and restart. Ignore them until we receive what we want. */
if (code != 0x34)
return;
/* FIXME: What's the meaning of this message? In windows logs, msg[0] == 0xc
* but here I get 0xd. Why? */
if (msg[0] != 0xd || msg[1] != '\0')
{
report_error (self, g_error_new (OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_PARSER,
"Prepare: wrong first request message"));
return;
}
request_done (self);
}
static void
parse_init_serial_number (OgInsulinx *self,
guint8 code,
const gchar *msg)
{
if (code != 0x6)
{
report_error (self, g_error_new (OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_PARSER,
"Prepare: wrong code for serial number"));
return;
}
g_assert (self->priv->serial_number == NULL);
self->priv->serial_number = g_strdup (msg);
request_done (self);
}
static void
parse_init_sw_version (OgInsulinx *self,
guint8 code,
const gchar *msg)
{
if (code != 0x35)
{
report_error (self, g_error_new (OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_PARSER,
"Prepare: wrong code for sw version"));
return;
}
g_assert (self->priv->sw_version == NULL);
self->priv->sw_version = g_strdup (msg);
request_done (self);
}
static void
parse_init_last (OgInsulinx *self,
guint8 code,
const gchar *msg)
{
/* FIXME: What's the meaning of this message? */
if (code != 0x71 || msg[0] != 0x1 || msg[1] != '\0')
{
report_error (self, g_error_new (OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_PARSER,
"Prepare: wrong last request message"));
return;
}
request_done (self);
}
static void
parse_date (OgInsulinx *self,
guint8 code,
const gchar *msg)
{
/* Temporaly store those values, we'll create the GDateTime in next request. */
if (sscanf (msg, "%u,%u,%u", &self->priv->month, &self->priv->day,
&self->priv->year) != 3)
{
report_error (self, g_error_new (OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_PARSER,
"Error parsing date"));
return;
}
/* 2 digits year, they didn't learn from the Y2K bug? Let's see what happens
* in 2100... */
self->priv->year += 2000;
}
static void
parse_time (OgInsulinx *self,
guint8 code,
const gchar *msg)
{
guint hour, minute;
if (sscanf (msg, "%u,%u", &hour, &minute) != 2)
{
report_error (self, g_error_new (OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_PARSER,
"Error parsing time"));
return;
}
self->priv->system_clock = g_date_time_new_now_local ();
/* We should have parsed the date in previous request */
self->priv->device_clock = g_date_time_new_local (
self->priv->year, self->priv->month, self->priv->day,
hour, minute, 0);
}
static void
parse_result (OgInsulinx *self,
guint8 code,
const gchar *msg)
{
guint type, month, day, year, hour, minute, glycemia;
guint ignore;
gint n_parsed;
n_parsed = sscanf (msg, "%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u",
&type,
&ignore, /* Record Number */
&month, &day, &year,
&hour, &minute,
&ignore, /* FIXME: What is that? */
&ignore, /* FIXME: What is that? */
&ignore, /* FIXME: What is that? */
&ignore, /* FIXME: What is that? */
&ignore, /* FIXME: What is that? */
&ignore, /* FIXME: What is that? */
&glycemia,
&ignore, /* FIXME: What is that? */
&ignore); /* FIXME: What is that? */
/* FIXME: Not sure what are those results */
if (type != 0)
return;
if (n_parsed != 16)
{
report_error (self, g_error_new (OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_PARSER,
"Error parsing result"));
return;
}
/* Fix 2 digits year */
year += 2000;
ptr_array_add_null_term (self->priv->records,
og_record_new (year, month, day, hour, minute, glycemia));
}
static void
parse_ptname (OgInsulinx *self,
guint8 code,
const gchar *msg)
{
gchar **names;
if (msg == NULL || *msg == '\0')
{
DEBUG ("Patient name not set");
self->priv->first_name = g_strdup ("");
self->priv->last_name = g_strdup ("");
return;
}
names = g_strsplit (msg, ",", 3);
if (names == NULL || g_strv_length (names) != 3)
{
report_error (self, g_error_new (OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_PARSER,
"Error parsing patient name"));
return;
}
g_assert (self->priv->first_name == NULL);
self->priv->first_name = g_strdup (names[0]);
g_assert (self->priv->last_name == NULL);
self->priv->last_name = g_strdup (names[1]);
/* names[2] is the middle initial, ignore */
g_strfreev (names);
}
static void
prepare_async (OgBaseDevice *base,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
OgInsulinx *self = (OgInsulinx *) base;
GError *error = NULL;
g_return_if_fail (OG_IS_INSULINX (base));
/* FIXME: We could be nicer and support queueing tasks until device is
* prepared */
if (self->priv->status != OG_BASE_DEVICE_STATUS_NONE)
{
g_task_report_new_error (self, callback, user_data, prepare_async,
OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_BUZY,
"Cannot prepare when status is not NONE");
return;
}
change_status (self, OG_BASE_DEVICE_STATUS_BUZY);
g_assert (self->priv->task == NULL);
self->priv->task = g_task_new (self, cancellable, callback, user_data);
if (!g_usb_device_open (self->priv->usb_device, &error))
{
report_error (self, error);
return;
}
if (!g_usb_device_claim_interface (self->priv->usb_device, 0,
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
&error))
{
report_error (self, error);
return;
}
if (!g_usb_device_set_configuration (self->priv->usb_device, 1, &error))
{
report_error (self, error);
return;
}
if (!g_usb_device_control_transfer (self->priv->usb_device,
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_CLASS,
G_USB_DEVICE_RECIPIENT_INTERFACE,
0x0a, /* SET_IDLE */
0,
0,
NULL, 0,
NULL,
0,
self->priv->cancellable,
&error))
{
report_error (self, error);
return;
}
/* Start pulling reply buffers, to get them as soon as one is ready */
start_interrupt_transfer (self);
/* Start our init sequence */
queue_request (self, 0x4, "", parse_init_first);
queue_request (self, 0x5, "", parse_init_serial_number);
queue_request (self, 0x15, "", parse_init_sw_version);
queue_request (self, 0x1, "", parse_init_last);
queue_request (self, 0x60, "$date?\r\n", parse_date);
queue_request (self, 0x60, "$time?\r\n", parse_time);
queue_request (self, 0x60, "$result?\r\n", parse_result);
queue_request (self, 0x60, "$ptname?\r\n", parse_ptname);
}
static gboolean
prepare_finish (OgBaseDevice *base,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (OG_IS_INSULINX (base), FALSE);
g_return_val_if_fail (g_task_is_valid (result, base), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
parse_nothing (OgInsulinx *self,
guint8 code,
const gchar *msg)
{
report_error (self, g_error_new (OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_PARSER,
"No message was expected"));
}
static void
sync_clock_async (OgBaseDevice *base,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
OgInsulinx *self = (OgInsulinx *) base;
GDateTime *now;
gchar *cmd;
g_return_if_fail (OG_IS_INSULINX (base));
/* FIXME: We could be nicer and support queueing tasks until device is
* prepared */
if (self->priv->status != OG_BASE_DEVICE_STATUS_READY)
{
g_task_report_new_error (self, callback, user_data, prepare_async,
OG_BASE_DEVICE_ERROR,
OG_BASE_DEVICE_ERROR_BUZY,
"Cannot sync clock when status is not READY");
return;
}
change_status (self, OG_BASE_DEVICE_STATUS_BUZY);
g_assert (self->priv->task == NULL);
self->priv->task = g_task_new (self, cancellable, callback, user_data);
now = g_date_time_new_now_local ();
cmd = g_strdup_printf ("$date,%u,%u,%u\r\n",
g_date_time_get_month (now),
g_date_time_get_day_of_month (now),
g_date_time_get_year (now) - 2000);
queue_request (self, 0x60, cmd, parse_nothing);
g_free (cmd);
cmd = g_strdup_printf ("$time,%u,%u\r\n",
g_date_time_get_hour (now),
g_date_time_get_minute (now));
queue_request (self, 0x60, cmd, parse_nothing);
g_free (cmd);
g_date_time_unref (now);
}
static gboolean
sync_clock_finish (OgBaseDevice *base,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (OG_IS_INSULINX (base), FALSE);
g_return_val_if_fail (g_task_is_valid (result, base), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
static const gchar *
get_name (OgBaseDevice *base)
{
g_return_val_if_fail (OG_IS_INSULINX (base), NULL);
return "Abbott FreeStyle InsuLinx";
}
static OgBaseDeviceStatus
get_status (OgBaseDevice *base)
{
OgInsulinx *self = (OgInsulinx *) base;
g_return_val_if_fail (OG_IS_INSULINX (base), OG_BASE_DEVICE_STATUS_ERROR);
return self->priv->status;
}
static const gchar *
get_serial_number (OgBaseDevice *base)
{
OgInsulinx *self = (OgInsulinx *) base;
g_return_val_if_fail (OG_IS_INSULINX (base), NULL);
return self->priv->serial_number;
}
static GDateTime *
get_clock (OgBaseDevice *base,
GDateTime **system_clock)
{
OgInsulinx *self = (OgInsulinx *) base;
g_return_val_if_fail (OG_IS_INSULINX (base), NULL);
if (system_clock != NULL)
*system_clock = self->priv->system_clock;
return self->priv->device_clock;
}
static const OgRecord * const *
get_records (OgBaseDevice *base)
{
OgInsulinx *self = (OgInsulinx *) base;
g_return_val_if_fail (OG_IS_INSULINX (base), NULL);
if (self->priv->records == NULL)
return NULL;
return (const OgRecord * const *) self->priv->records->pdata;
}
static const gchar *
get_first_name (OgBaseDevice *base)
{
OgInsulinx *self = (OgInsulinx *) base;
g_return_val_if_fail (OG_IS_INSULINX (base), NULL);
return self->priv->first_name;
}
static const gchar *
get_last_name (OgBaseDevice *base)
{
OgInsulinx *self = (OgInsulinx *) base;
g_return_val_if_fail (OG_IS_INSULINX (base), NULL);
return self->priv->last_name;
}
static void
og_insulinx_class_init (OgInsulinxClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
OgBaseDeviceClass *base_class = OG_BASE_DEVICE_CLASS (klass);
GParamSpec *param_spec;
object_class->finalize = finalize;
object_class->get_property = get_property;
object_class->set_property = set_property;
base_class->get_name = get_name;
base_class->get_status = get_status;
base_class->prepare_async = prepare_async;
base_class->prepare_finish = prepare_finish;
base_class->sync_clock_async = sync_clock_async;
base_class->sync_clock_finish = sync_clock_finish;
base_class->get_serial_number = get_serial_number;
base_class->get_clock = get_clock;
base_class->get_records = get_records;
base_class->get_first_name = get_first_name;
base_class->get_last_name = get_last_name;
g_type_class_add_private (object_class, sizeof (OgInsulinxPrivate));
param_spec = g_param_spec_object ("usb-device",
"USB Device",
"The #GUsbDevice associated with this glucometer",
G_USB_TYPE_DEVICE,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_property (object_class, PROP_USB_DEVICE, param_spec);
}
OgBaseDevice *
og_insulinx_new (GUsbDevice *usb_device)
{
return g_object_new (OG_TYPE_INSULINX,
"usb-device", usb_device,
NULL);
}