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
61 #define VISOMAT_PACKET_SIZE 64
66 #define DATETIME_PACKET_SIZE 15
67 #define BASE_YEAR 2000
70 VISOMAT_CMD_DUMP_EEPROM,
71 VISOMAT_CMD_UNKNOWN1, /* maybe the firmware revision */
72 VISOMAT_CMD_GET_DATETIME,
73 VISOMAT_CMD_START_MEASURE1,
74 VISOMAT_CMD_UNKNOWN2, /* XXX transmission hangs */
78 static char command_codes[][3] = {
79 [VISOMAT_CMD_DUMP_EEPROM] = "R00",
80 [VISOMAT_CMD_UNKNOWN1] = "R01",
81 [VISOMAT_CMD_GET_DATETIME] = "R02",
82 [VISOMAT_CMD_START_MEASURE1] = "R03",
83 [VISOMAT_CMD_UNKNOWN2] = "R04",
84 [VISOMAT_CMD_UNKNOWN3] = "R05",
96 unsigned int flag; /* XXX Maybe this means arrhythmia? */
97 unsigned int systolic;
98 unsigned int diastolic;
102 static inline int extract_datetime(uint8_t *buffer, struct datetime *d)
106 ret = sscanf((char *)buffer, "%02u%02u%02u%02u%02u",
115 d->year += BASE_YEAR;
121 * NOTE: the CSV output is meant to be compatible with one from the Windows
122 * program, which uses the %d/%m/%Y date format, so that's not my fault.
124 static void print_record_csv_compat(struct datetime *d, struct pressure *p)
126 unsigned int pulse_pressure;
128 printf("%02u/%02u/%04u;%02u.%02u;",
129 d->day, d->month, d->year, d->hour, d->minute);
131 printf("%u;%u;%u;", p->systolic, p->diastolic, p->pulses);
133 pulse_pressure = p->systolic - p->diastolic;
134 printf("%u;", pulse_pressure);
140 /* The original software does not seem to be doing that */
142 printf("tachycardia");
143 else if (p->pulses < 60)
144 printf("bradycardia");
150 /* TODO: it would be better to separate decoding data from printing it */
151 static int decode_eeprom(uint8_t *buffer,
153 unsigned int user_mask)
159 unsigned int user_id;
160 unsigned int num_records;
164 if (buffer[0] != STX || buffer[1] != 'M') {
165 fprintf(stderr, "Usupported data.\n");
169 if (buffer[len - 1] != ETX) {
170 fprintf(stderr, "Bad terminator in data. Buffer too small?\n");
174 /* skip the initial STX */
177 /* and the final ETX */
181 /* Each user record begins with 'M', maybe for "Memory" */
182 if (buffer[i] == 'M') {
183 /* i tracks the bytes consumed */
186 ret = sscanf((char *)(buffer + i), "%1u%02u",
187 &user_id, &num_records);
191 /* user_id and num_records take 3 bytes */
195 * when there are no records, there is a dummy byte
196 * which has to be consumed
198 if (num_records == 0)
201 for (j = 0; j < num_records; j++) {
202 ret = extract_datetime(buffer + i, &d);
206 /* datetime takes 10 bytes */
209 ret = sscanf((char *)(buffer + i),
218 /* pressure data is 10 bytes */
221 /* TODO: split out the printing part */
222 if (user_id & user_mask) {
224 printf("# User: %d\n", user_id);
225 print_record_csv_compat(&d, &p);
234 static int decode_datetime(uint8_t *buffer, unsigned int len)
237 uint8_t code[4] = { 0 };
239 uint8_t *pbuffer = buffer;
241 if (len != DATETIME_PACKET_SIZE)
248 ret = extract_datetime(pbuffer + 4, &d);
252 printf("# (%s) Date: %04d/%02d/%02d %02d:%02d\n",
253 code, d.year, d.month, d.day, d.hour, d.minute);
258 static int send_command(libusb_device_handle *dev, visomat_command cmd)
265 request[1] = command_codes[cmd][0];
266 request[2] = command_codes[cmd][1];
267 request[3] = command_codes[cmd][2];
271 ret = libusb_bulk_transfer(dev, VISOMAT_EP_OUT, request, sizeof(request), &transferred, 0);
272 if (ret != 0 || transferred != sizeof(request)) {
273 fprintf(stderr, "Error: sending request: %d (%s)\ttransferred: %d (expected %zu)\n",
274 ret, libusb_error_name(ret), transferred, sizeof(request));
280 static int get_response(libusb_device_handle *dev,
286 uint8_t response[VISOMAT_PACKET_SIZE] = { 0 };
294 ret = libusb_bulk_transfer(dev, VISOMAT_EP_IN, response, sizeof(response), &transferred, 5000);
296 fprintf(stderr, "Error getting response: %d (%s)\ttransferred: %d (expected %zu)\n",
297 ret, libusb_error_name(ret), transferred, sizeof(response));
301 for (j = 0; j < (unsigned int)transferred; j++) {
302 buffer[i] = response[j];
306 } while (buffer[i - 1] != ETX && i < len);
308 /* Check if the buffer contains a valid response packet */
309 if (buffer[0] != STX || buffer[i - 1] != ETX)
315 /* Candidates for a future public API, if a shared library will ever be made */
316 #define visomat_device libusb_device_handle
317 static int visomat_dump_eeprom(visomat_device *dev, unsigned int user_mask)
319 /* Assuming an EEPROM of 4 KiB */
320 uint8_t buffer[4096] = { 0 };
323 ret = send_command(dev, VISOMAT_CMD_DUMP_EEPROM);
327 ret = get_response(dev, buffer, sizeof(buffer));
328 debug("buffer size: %d\n", ret);
329 debug_dump_buffer("eeprom.bin", buffer, sizeof(buffer));
333 ret = decode_eeprom(buffer, ret, user_mask);
340 static int visomat_get_datetime(visomat_device *dev)
342 uint8_t buffer[255] = { 0 };
345 ret = send_command(dev, VISOMAT_CMD_GET_DATETIME);
349 ret = get_response(dev, buffer, sizeof(buffer));
353 ret = decode_datetime(buffer, ret);
360 static void usage(const char *name)
362 printf("usage: %s [OPTIONS]\n\n", name);
363 printf("OPTIONS:\n");
364 printf("\t-D\t\tenable libusb debug output\n");
365 printf("\t-h\t\tthis help message\n");
368 int main(int argc, char *argv[])
372 bool enable_libusb_debug = false;
373 libusb_device_handle *dev;
374 int current_configuration;
376 while ((opt = getopt(argc, argv, "Dh")) != -1) {
379 enable_libusb_debug = true;
392 ret = libusb_init(NULL);
394 fprintf(stderr, "libusb_init failed: %s\n",
395 libusb_error_name(ret));
399 libusb_set_debug(NULL, enable_libusb_debug ?
400 LIBUSB_LOG_LEVEL_DEBUG : LIBUSB_LOG_LEVEL_INFO);
402 dev = libusb_open_device_with_vid_pid(NULL,
406 fprintf(stderr, "libusb_open failed: %s\n", strerror(errno));
408 goto out_libusb_exit;
411 current_configuration = -1;
412 ret = libusb_get_configuration(dev, ¤t_configuration);
414 fprintf(stderr, "libusb_get_configuration failed: %s\n",
415 libusb_error_name(ret));
416 goto out_libusb_close;
419 if (current_configuration != VISOMAT_CONFIGURATION) {
420 ret = libusb_set_configuration(dev, VISOMAT_CONFIGURATION);
422 fprintf(stderr, "libusb_set_configuration failed: %s\n",
423 libusb_error_name(ret));
424 fprintf(stderr, "Cannot set configuration %d\n",
425 VISOMAT_CONFIGURATION);
426 goto out_libusb_close;
430 libusb_set_auto_detach_kernel_driver(dev, 1);
432 ret = libusb_claim_interface(dev, VISOMAT_INTERFACE);
434 fprintf(stderr, "libusb_claim_interface failed: %s\n",
435 libusb_error_name(ret));
436 fprintf(stderr, "Cannot claim interface %d\n",
438 goto out_libusb_close;
441 /* Checking that the configuration has not changed, as suggested in
442 * http://libusb.sourceforge.net/api-1.0/caveats.html
444 current_configuration = -1;
445 ret = libusb_get_configuration(dev, ¤t_configuration);
447 fprintf(stderr, "libusb_get_configuration after claim failed: %s\n",
448 libusb_error_name(ret));
449 goto out_libusb_release_interface;
452 if (current_configuration != VISOMAT_CONFIGURATION) {
453 fprintf(stderr, "libusb configuration changed (expected: %d, current: %d)\n",
454 VISOMAT_CONFIGURATION, current_configuration);
456 goto out_libusb_release_interface;
459 ret = visomat_get_datetime(dev);
461 goto out_libusb_release_interface;
463 ret = visomat_dump_eeprom(dev, 0x01 | 0x02);
465 goto out_libusb_release_interface;
467 out_libusb_release_interface:
468 libusb_release_interface(dev, VISOMAT_INTERFACE);