Permalink
Cannot retrieve contributors at this time
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?
OpenGlucose/src/insulinx.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1017 lines (858 sloc)
25.5 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |