2  * visomat-data-downloader - download data from Visomat Double Comfort
 
   4  * The Visomat Double Comfort is a blood pressure meter with a USB port.
 
   6  * Copyright (C) 2013  Antonio Ospite <ospite@studenti.unina.it>
 
   8  * This program is free software: you can redistribute it and/or modify
 
   9  * it under the terms of the GNU General Public License as published by
 
  10  * the Free Software Foundation, either version 3 of the License, or
 
  11  * (at your option) any later version.
 
  13  * This program is distributed in the hope that it will be useful,
 
  14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  16  * GNU General Public License for more details.
 
  18  * You should have received a copy of the GNU General Public License
 
  19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
  31 #define debug(...) fprintf(stderr, __VA_ARGS__)
 
  32 static void debug_dump_buffer(const char *filename, uint8_t *buffer, unsigned int len)
 
  36         dump_file = fopen(filename, "wb");
 
  37         if (dump_file == NULL) {
 
  38                 fprintf(stderr, "Failed to open %s: %s\n", filename, strerror(errno));
 
  42         fwrite(buffer, 1, len, dump_file);
 
  46 #define debug(...) do {} while(0)
 
  47 static void debug_dump_buffer(const char *filename, uint8_t *buffer, unsigned int len)
 
  55 #define VISOMAT_DEVICE_VID    0x1247
 
  56 #define VISOMAT_DEVICE_PID    0x00f8
 
  57 #define VISOMAT_CONFIGURATION 1
 
  58 #define VISOMAT_INTERFACE     1
 
  59 #define VISOMAT_EP_IN         0x82
 
  60 #define VISOMAT_EP_OUT        0x03
 
  65 #define BASE_YEAR 2000
 
  68         VISOMAT_CMD_DUMP_EEPROM,
 
  69         VISOMAT_CMD_UNKNOWN1, /* maybe the firmware revision */
 
  70         VISOMAT_CMD_GET_DATETIME,
 
  71         VISOMAT_CMD_START_MEASURE1,
 
  72         VISOMAT_CMD_UNKNOWN2, /* XXX transmission hangs */
 
  76 static char command_codes[][3] = {
 
  77         [VISOMAT_CMD_DUMP_EEPROM]    = "R00",
 
  78         [VISOMAT_CMD_UNKNOWN1]       = "R01",
 
  79         [VISOMAT_CMD_GET_DATETIME]   = "R02",
 
  80         [VISOMAT_CMD_START_MEASURE1] = "R03",
 
  81         [VISOMAT_CMD_UNKNOWN2]       = "R04",
 
  82         [VISOMAT_CMD_UNKNOWN3]       = "R05",
 
  94         unsigned int flag; /* XXX Maybe this means arrhythmia? */
 
  95         unsigned int systolic;
 
  96         unsigned int diastolic;
 
 100 static inline int extract_datetime(uint8_t *buffer, struct datetime *d)
 
 104         ret = sscanf((char *)buffer, "%02u%02u%02u%02u%02u",
 
 113         d->year += BASE_YEAR;
 
 119  * NOTE: the CSV output is meant to be compatible with one from the Windows
 
 120  * program, which uses the %d/%m/%Y date format, so that's not my fault.
 
 122 static void print_record_csv_compat(struct datetime *d, struct pressure *p)
 
 124         unsigned int pulse_pressure;
 
 126         printf("%02u/%02u/%04u;%02u.%02u;",
 
 127                d->day, d->month, d->year, d->hour, d->minute);
 
 129         printf("%u;%u;%u;", p->systolic, p->diastolic, p->pulses);
 
 131         pulse_pressure = p->systolic - p->diastolic;
 
 132         printf("%u;", pulse_pressure);
 
 138         /* The original software does not seem to be doing that */
 
 140                 printf("tachycardia");
 
 141         else if (p->pulses < 60)
 
 142                 printf("bradycardia");
 
 148 /* TODO: it would be better to separate decoding data from printing it */
 
 149 static int decode_eeprom(uint8_t *buffer,
 
 151                          unsigned int user_mask)
 
 157         unsigned int user_id;
 
 158         unsigned int num_records;
 
 162         if (buffer[0] != STX || buffer[1] != 'M') {
 
 163                 fprintf(stderr, "Usupported data.\n");
 
 167         if (buffer[len - 1] != ETX) {
 
 168                 fprintf(stderr, "Bad terminator in data. Buffer too small?\n");
 
 172         /* skip the initial STX */
 
 175         /* and the final ETX */
 
 179                 /* Each user record begins with 'M', maybe for "Memory" */
 
 180                 if (buffer[i] == 'M') {
 
 181                         /* i tracks the bytes consumed */
 
 184                         ret = sscanf((char *)(buffer + i), "%1u%02u",
 
 185                                      &user_id, &num_records);
 
 189                         /* user_id and num_records take 3 bytes */
 
 193                          * when there are no records, there is a dummy byte
 
 194                          * which has to be consumed
 
 196                         if (num_records == 0)
 
 199                         for (j = 0; j < num_records; j++) {
 
 200                                 ret = extract_datetime(buffer + i, &d);
 
 204                                 /* datetime takes 10 bytes */
 
 207                                 ret = sscanf((char *)(buffer + i),
 
 216                                 /* pressure data is 10 bytes */
 
 219                                 /* TODO: split out the printing part */
 
 220                                 if (user_id & user_mask) {
 
 222                                                 printf("# User: %d\n", user_id);
 
 223                                         print_record_csv_compat(&d, &p);
 
 232 static int decode_datetime(uint8_t *buffer, unsigned int len)
 
 235         uint8_t code[4] = { 0 };
 
 237         uint8_t *pbuffer = buffer;
 
 246         ret = extract_datetime(pbuffer + 4, &d);
 
 250         printf("# (%s) Date: %04d/%02d/%02d %02d:%02d\n",
 
 251                code, d.year, d.month, d.day, d.hour, d.minute);
 
 256 static int send_command(libusb_device_handle *dev, visomat_command cmd)
 
 263         request[1] = command_codes[cmd][0];
 
 264         request[2] = command_codes[cmd][1];
 
 265         request[3] = command_codes[cmd][2];
 
 269         ret = libusb_bulk_transfer(dev, VISOMAT_EP_OUT, request, sizeof(request), &transferred, 0);
 
 270         if (ret != 0 || transferred != sizeof(request)) {
 
 271                 fprintf(stderr, "Error: sending request: %d (%s)\ttransferred: %d (expected %zu)\n",
 
 272                         ret, libusb_error_name(ret), transferred, sizeof(request));
 
 278 static int get_response(libusb_device_handle *dev,
 
 284         uint8_t response[64] = { 0 };
 
 292                 ret = libusb_bulk_transfer(dev, VISOMAT_EP_IN, response, sizeof(response), &transferred, 5000);
 
 294                         fprintf(stderr, "Error getting response: %d (%s)\ttransferred: %d (expected %zu)\n",
 
 295                                 ret, libusb_error_name(ret), transferred, sizeof(response));
 
 299                 for (j = 0; j < (unsigned int)transferred; j++) {
 
 300                         buffer[i] = response[j];
 
 304         } while (buffer[i - 1] != ETX && i < len);
 
 306         /* Check the buffer is a valid response packet */
 
 307         if (buffer[0] != STX || buffer[i - 1] != ETX)
 
 313 /* Candidates for a future public API, if a shared library will ever be made */
 
 314 #define visomat_device libusb_device_handle
 
 315 static int visomat_dump_eeprom(visomat_device *dev, unsigned int user_mask)
 
 317         /* Assuming an EEPROM of 1 KiB  */
 
 318         uint8_t buffer[1024] = { 0 };
 
 321         ret = send_command(dev, VISOMAT_CMD_DUMP_EEPROM);
 
 325         ret = get_response(dev, buffer, sizeof(buffer));
 
 326         debug("buffer size: %d\n", ret);
 
 327         debug_dump_buffer("eeprom.bin", buffer, sizeof(buffer));
 
 331         ret = decode_eeprom(buffer, ret, user_mask);
 
 338 static int visomat_get_datetime(visomat_device *dev)
 
 340         uint8_t buffer[255] = { 0 };
 
 343         ret = send_command(dev, VISOMAT_CMD_GET_DATETIME);
 
 347         ret = get_response(dev, buffer, sizeof(buffer));
 
 351         ret = decode_datetime(buffer, ret);
 
 358 static void usage(const char *name)
 
 360         printf("usage: %s [OPTIONS]\n\n", name);
 
 361         printf("OPTIONS:\n");
 
 362         printf("\t-D\t\tenable libusb debug output\n");
 
 363         printf("\t-h\t\tthis help message\n");
 
 366 int main(int argc, char *argv[])
 
 370         bool enable_libusb_debug = false;
 
 371         libusb_device_handle *dev;
 
 372         int current_configuration;
 
 374         while ((opt = getopt(argc, argv, "Dh")) != -1) {
 
 377                         enable_libusb_debug = true;
 
 390         ret = libusb_init(NULL);
 
 392                 fprintf(stderr, "libusb_init failed: %s\n",
 
 393                         libusb_error_name(ret));
 
 397         libusb_set_debug(NULL, enable_libusb_debug ?
 
 398                          LIBUSB_LOG_LEVEL_DEBUG : LIBUSB_LOG_LEVEL_INFO);
 
 400         dev = libusb_open_device_with_vid_pid(NULL,
 
 404                 fprintf(stderr, "libusb_open failed: %s\n", strerror(errno));
 
 406                 goto out_libusb_exit;
 
 409         current_configuration = -1;
 
 410         ret = libusb_get_configuration(dev, ¤t_configuration);
 
 412                 fprintf(stderr, "libusb_get_configuration failed: %s\n",
 
 413                         libusb_error_name(ret));
 
 414                 goto out_libusb_close;
 
 417         if (current_configuration != VISOMAT_CONFIGURATION) {
 
 418                 ret = libusb_set_configuration(dev, VISOMAT_CONFIGURATION);
 
 420                         fprintf(stderr, "libusb_set_configuration failed: %s\n",
 
 421                                 libusb_error_name(ret));
 
 422                         fprintf(stderr, "Cannot set configuration %d\n",
 
 423                                 VISOMAT_CONFIGURATION);
 
 424                         goto out_libusb_close;
 
 428         libusb_set_auto_detach_kernel_driver(dev, 1);
 
 430         ret = libusb_claim_interface(dev, VISOMAT_INTERFACE);
 
 432                 fprintf(stderr, "libusb_claim_interface failed: %s\n",
 
 433                         libusb_error_name(ret));
 
 434                 fprintf(stderr, "Cannot claim interface %d\n",
 
 436                 goto out_libusb_close;
 
 439         /* Checking that the configuration has not changed, as suggested in
 
 440          * http://libusb.sourceforge.net/api-1.0/caveats.html
 
 442         current_configuration = -1;
 
 443         ret = libusb_get_configuration(dev, ¤t_configuration);
 
 445                 fprintf(stderr, "libusb_get_configuration after claim failed: %s\n",
 
 446                         libusb_error_name(ret));
 
 447                 goto out_libusb_release_interface;
 
 450         if (current_configuration != VISOMAT_CONFIGURATION) {
 
 451                 fprintf(stderr, "libusb configuration changed (expected: %d, current: %d)\n",
 
 452                         VISOMAT_CONFIGURATION, current_configuration);
 
 454                 goto out_libusb_release_interface;
 
 457         ret = visomat_get_datetime(dev);
 
 459                 goto out_libusb_release_interface;
 
 461         ret = visomat_dump_eeprom(dev, 0x01 | 0x02);
 
 463                 goto out_libusb_release_interface;
 
 465 out_libusb_release_interface:
 
 466         libusb_release_interface(dev, VISOMAT_INTERFACE);