visomat-data-downloader: increase the eeprom buffer size
[visomat-utils.git] / src / visomat-data-downloader.c
index 0eca9c1..b471dc9 100644 (file)
  */
 
 #include <stdio.h>
+#include <stdbool.h>
 #include <stdlib.h>
+#include <string.h>
 #include <errno.h>
 #include <unistd.h>
 #include <libusb.h>
 
-#define STX 0x02
-#define ETX 0x03
+#ifdef DEBUG
+#define debug(...) fprintf(stderr, __VA_ARGS__)
+static void debug_dump_buffer(const char *filename, uint8_t *buffer, unsigned int len)
+{
+       FILE *dump_file;
+
+       dump_file = fopen(filename, "wb");
+       if (dump_file == NULL) {
+               fprintf(stderr, "Failed to open %s: %s\n", filename, strerror(errno));
+               return;
+       }
 
-#define DEVICE_VID 0x1247
-#define DEVICE_PID 0x00f8
+       fwrite(buffer, 1, len, dump_file);
+       fclose(dump_file);
+}
+#else
+#define debug(...) do {} while(0)
+static void debug_dump_buffer(const char *filename, uint8_t *buffer, unsigned int len)
+{
+       (void)filename;
+       (void)buffer;
+       (void)len;
+}
+#endif
 
-#define EP_IN 0x82
-#define EP_OUT 0x03
+#define VISOMAT_DEVICE_VID    0x1247
+#define VISOMAT_DEVICE_PID    0x00f8
+#define VISOMAT_CONFIGURATION 1
+#define VISOMAT_INTERFACE     1
+#define VISOMAT_EP_IN         0x82
+#define VISOMAT_EP_OUT        0x03
+#define VISOMAT_PACKET_SIZE   64
+
+#define STX 0x02
+#define ETX 0x03
 
 #define BASE_YEAR 2000
 
@@ -63,12 +92,13 @@ struct datetime  {
 };
 
 struct pressure  {
+       unsigned int flag; /* XXX Maybe this means arrhythmia? */
        unsigned int systolic;
        unsigned int diastolic;
        unsigned int pulses;
 };
 
-static inline int extract_datetime(unsigned char *buffer, struct datetime *d)
+static inline int extract_datetime(uint8_t *buffer, struct datetime *d)
 {
        int ret;
 
@@ -102,16 +132,24 @@ static void print_record_csv_compat(struct datetime *d, struct pressure *p)
        pulse_pressure = p->systolic - p->diastolic;
        printf("%u;", pulse_pressure);
 
+       if (p->flag)
+               printf("x");
+
+#if 0
+       /* The original software does not seem to be doing that */
        if (p->pulses > 100)
                printf("tachycardia");
        else if (p->pulses < 60)
                printf("bradycardia");
+#endif
 
        printf("\n");
 }
 
-/* TODO separate better decoding data from printing it */
-static int decode_eeprom(unsigned char *buffer, unsigned int len)
+/* TODO: it would be better to separate decoding data from printing it */
+static int decode_eeprom(uint8_t *buffer,
+                        unsigned int len,
+                        unsigned int user_mask)
 {
        int ret;
        unsigned int n;
@@ -122,8 +160,15 @@ static int decode_eeprom(unsigned char *buffer, unsigned int len)
        struct datetime d;
        struct pressure p;
 
-       if (buffer[0] != STX || buffer[1] != 'M' || buffer[len - 1] != ETX)
+       if (buffer[0] != STX || buffer[1] != 'M') {
+               fprintf(stderr, "Usupported data.\n");
+               return -EINVAL;
+       }
+
+       if (buffer[len - 1] != ETX) {
+               fprintf(stderr, "Bad terminator in data. Buffer too small?\n");
                return -EINVAL;
+       }
 
        /* skip the initial STX */
        i = 1;
@@ -137,7 +182,6 @@ static int decode_eeprom(unsigned char *buffer, unsigned int len)
                        /* i tracks the bytes consumed */
                        i += 1;
 
-
                        ret = sscanf((char *)(buffer + i), "%1u%02u",
                                     &user_id, &num_records);
                        if (ret != 2)
@@ -146,7 +190,12 @@ static int decode_eeprom(unsigned char *buffer, unsigned int len)
                        /* user_id and num_records take 3 bytes */
                        i += 3;
 
-                       printf("# User: %d\n", user_id);
+                       /*
+                        * when there are no records, there is a dummy byte
+                        * which has to be consumed
+                        */
+                       if (num_records == 0)
+                               i += 1;
 
                        for (j = 0; j < num_records; j++) {
                                ret = extract_datetime(buffer + i, &d);
@@ -157,17 +206,23 @@ static int decode_eeprom(unsigned char *buffer, unsigned int len)
                                i += 10;
 
                                ret = sscanf((char *)(buffer + i),
-                                            "%04u%03u%03u",
+                                            "%1u%03u%03u%03u",
+                                            &p.flag,
                                             &p.systolic,
                                             &p.diastolic,
                                             &p.pulses);
-                               if (ret != 3)
+                               if (ret != 4)
                                        return -EINVAL;
 
                                /* pressure data is 10 bytes */
                                i += 10;
 
-                               print_record_csv_compat(&d, &p);
+                               /* TODO: split out the printing part */
+                               if (user_id & user_mask) {
+                                       if (j == 0)
+                                               printf("# User: %d\n", user_id);
+                                       print_record_csv_compat(&d, &p);
+                               }
                        }
                }
        }
@@ -175,12 +230,12 @@ static int decode_eeprom(unsigned char *buffer, unsigned int len)
        return 0;
 }
 
-static int decode_datetime(unsigned char *buffer, unsigned int len)
+static int decode_datetime(uint8_t *buffer, unsigned int len)
 {
        int ret;
-       unsigned char code[4] = { 0 };
+       uint8_t code[4] = { 0 };
        struct datetime d;
-       unsigned char *pbuffer = buffer;
+       uint8_t *pbuffer = buffer;
 
        if (len != 15)
                return -EINVAL;
@@ -203,7 +258,7 @@ static int send_command(libusb_device_handle *dev, visomat_command cmd)
 {
        int ret;
        int transferred;
-       unsigned char request[5];
+       uint8_t request[5];
 
        request[0] = STX;
        request[1] = command_codes[cmd][0];
@@ -212,7 +267,7 @@ static int send_command(libusb_device_handle *dev, visomat_command cmd)
        request[4] = ETX;
 
        transferred = 0;
-       ret = libusb_bulk_transfer(dev, EP_OUT, request, sizeof(request), &transferred, 0);
+       ret = libusb_bulk_transfer(dev, VISOMAT_EP_OUT, request, sizeof(request), &transferred, 0);
        if (ret != 0 || transferred != sizeof(request)) {
                fprintf(stderr, "Error: sending request: %d (%s)\ttransferred: %d (expected %zu)\n",
                        ret, libusb_error_name(ret), transferred, sizeof(request));
@@ -222,12 +277,12 @@ static int send_command(libusb_device_handle *dev, visomat_command cmd)
 }
 
 static int get_response(libusb_device_handle *dev,
-                       unsigned char *buffer,
+                       uint8_t *buffer,
                        unsigned int len)
 {
        int ret;
        int transferred;
-       unsigned char response[64] = { 0 };
+       uint8_t response[VISOMAT_PACKET_SIZE] = { 0 };
        unsigned int i;
 
        i = 0;
@@ -235,7 +290,7 @@ static int get_response(libusb_device_handle *dev,
                unsigned int j;
 
                transferred = 0;
-               ret = libusb_bulk_transfer(dev, EP_IN, response, sizeof(response), &transferred, 5000);
+               ret = libusb_bulk_transfer(dev, VISOMAT_EP_IN, response, sizeof(response), &transferred, 5000);
                if (ret != 0) {
                        fprintf(stderr, "Error getting response: %d (%s)\ttransferred: %d (expected %zu)\n",
                                ret, libusb_error_name(ret), transferred, sizeof(response));
@@ -258,10 +313,10 @@ static int get_response(libusb_device_handle *dev,
 
 /* Candidates for a future public API, if a shared library will ever be made */
 #define visomat_device libusb_device_handle
-static int visomat_dump_eeprom(visomat_device *dev)
+static int visomat_dump_eeprom(visomat_device *dev, unsigned int user_mask)
 {
-       /* Assuming an EEPROM of 1 KiB  */
-       unsigned char buffer[1024] = { 0 };
+       /* Assuming an EEPROM of 4 KiB  */
+       uint8_t buffer[4096] = { 0 };
        int ret;
 
        ret = send_command(dev, VISOMAT_CMD_DUMP_EEPROM);
@@ -269,10 +324,12 @@ static int visomat_dump_eeprom(visomat_device *dev)
                return ret;
 
        ret = get_response(dev, buffer, sizeof(buffer));
+       debug("buffer size: %d\n", ret);
+       debug_dump_buffer("eeprom.bin", buffer, sizeof(buffer));
        if (ret < 0)
                return ret;
 
-       ret = decode_eeprom(buffer, ret);
+       ret = decode_eeprom(buffer, ret, user_mask);
        if (ret < 0)
                return ret;
 
@@ -281,7 +338,7 @@ static int visomat_dump_eeprom(visomat_device *dev)
 
 static int visomat_get_datetime(visomat_device *dev)
 {
-       unsigned char buffer[255] = { 0 };
+       uint8_t buffer[255] = { 0 };
        int ret;
 
        ret = send_command(dev, VISOMAT_CMD_GET_DATETIME);
@@ -299,34 +356,120 @@ static int visomat_get_datetime(visomat_device *dev)
        return 0;
 }
 
-int main(void)
+static void usage(const char *name)
+{
+       printf("usage: %s [OPTIONS]\n\n", name);
+       printf("OPTIONS:\n");
+       printf("\t-D\t\tenable libusb debug output\n");
+       printf("\t-h\t\tthis help message\n");
+}
+
+int main(int argc, char *argv[])
 {
        int ret;
+       int opt;
+       bool enable_libusb_debug = false;
        libusb_device_handle *dev;
+       int current_configuration;
+
+       while ((opt = getopt(argc, argv, "Dh")) != -1) {
+               switch (opt) {
+               case 'D':
+                       enable_libusb_debug = true;
+                       break;
+               case 'h':
+                       usage(argv[0]);
+                       ret = 0;
+                       goto out;
+               default: /* '?' */
+                       usage(argv[0]);
+                       ret = -EINVAL;
+                       goto out;
+               }
+       }
+
+       ret = libusb_init(NULL);
+       if (ret < 0) {
+               fprintf(stderr, "libusb_init failed: %s\n",
+                       libusb_error_name(ret));
+               goto out;
+       }
 
-       libusb_init(NULL);
-       libusb_set_debug(NULL, LIBUSB_LOG_LEVEL_INFO);
+       libusb_set_debug(NULL, enable_libusb_debug ?
+                        LIBUSB_LOG_LEVEL_DEBUG : LIBUSB_LOG_LEVEL_INFO);
 
-       dev = libusb_open_device_with_vid_pid(NULL, DEVICE_VID, DEVICE_PID);
+       dev = libusb_open_device_with_vid_pid(NULL,
+                                             VISOMAT_DEVICE_VID,
+                                             VISOMAT_DEVICE_PID);
        if (dev == NULL) {
-               fprintf(stderr, "Couldn't open device.\n");
-               ret = -ENODEV;
+               fprintf(stderr, "libusb_open failed: %s\n", strerror(errno));
+               ret = -errno;
                goto out_libusb_exit;
        }
 
-       libusb_set_configuration(dev, 1);
-       libusb_claim_interface(dev, 1);
-       libusb_detach_kernel_driver(dev, 1);
+       current_configuration = -1;
+       ret = libusb_get_configuration(dev, &current_configuration);
+       if (ret < 0) {
+               fprintf(stderr, "libusb_get_configuration failed: %s\n",
+                       libusb_error_name(ret));
+               goto out_libusb_close;
+       }
+
+       if (current_configuration != VISOMAT_CONFIGURATION) {
+               ret = libusb_set_configuration(dev, VISOMAT_CONFIGURATION);
+               if (ret < 0) {
+                       fprintf(stderr, "libusb_set_configuration failed: %s\n",
+                               libusb_error_name(ret));
+                       fprintf(stderr, "Cannot set configuration %d\n",
+                               VISOMAT_CONFIGURATION);
+                       goto out_libusb_close;
+               }
+       }
+
+       libusb_set_auto_detach_kernel_driver(dev, 1);
+
+       ret = libusb_claim_interface(dev, VISOMAT_INTERFACE);
+       if (ret < 0) {
+               fprintf(stderr, "libusb_claim_interface failed: %s\n",
+                       libusb_error_name(ret));
+               fprintf(stderr, "Cannot claim interface %d\n",
+                       VISOMAT_INTERFACE);
+               goto out_libusb_close;
+       }
+
+       /* Checking that the configuration has not changed, as suggested in
+        * http://libusb.sourceforge.net/api-1.0/caveats.html
+        */
+       current_configuration = -1;
+       ret = libusb_get_configuration(dev, &current_configuration);
+       if (ret < 0) {
+               fprintf(stderr, "libusb_get_configuration after claim failed: %s\n",
+                       libusb_error_name(ret));
+               goto out_libusb_release_interface;
+       }
+
+       if (current_configuration != VISOMAT_CONFIGURATION) {
+               fprintf(stderr, "libusb configuration changed (expected: %d, current: %d)\n",
+                       VISOMAT_CONFIGURATION, current_configuration);
+               ret = -EINVAL;
+               goto out_libusb_release_interface;
+       }
 
        ret = visomat_get_datetime(dev);
        if (ret < 0)
-               goto out;
+               goto out_libusb_release_interface;
 
-       ret = visomat_dump_eeprom(dev);
+       ret = visomat_dump_eeprom(dev, 0x01 | 0x02);
+       if (ret < 0)
+               goto out_libusb_release_interface;
 
-out:
+out_libusb_release_interface:
+       libusb_release_interface(dev, VISOMAT_INTERFACE);
+out_libusb_close:
        libusb_close(dev);
+       dev = NULL;
 out_libusb_exit:
        libusb_exit(NULL);
+out:
        return ret;
 }