574bb13c223a62cc818c3da3efb80bdc56d57b37
[visomat-utils.git] / src / visomat-data-downloader.c
1 /*
2  * visomat-data-downloader - download data from Visomat Double Comfort
3  *
4  * The Visomat Double Comfort is a blood pressure meter with a USB port.
5  *
6  * Copyright (C) 2013  Antonio Ospite <ospite@studenti.unina.it>
7  *
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.
12  *
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.
17  *
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/>.
20  */
21
22 #include <stdio.h>
23 #include <stdbool.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <unistd.h>
28 #include <libusb.h>
29
30 #ifdef DEBUG
31 #define debug(...) fprintf(stderr, __VA_ARGS__)
32 static void debug_dump_buffer(const char *filename, uint8_t *buffer, unsigned int len)
33 {
34         FILE *dump_file;
35
36         dump_file = fopen(filename, "wb");
37         if (dump_file == NULL) {
38                 fprintf(stderr, "Failed to open %s: %s\n", filename, strerror(errno));
39                 return;
40         }
41
42         fwrite(buffer, 1, len, dump_file);
43         fclose(dump_file);
44 }
45 #else
46 #define debug(...) do {} while(0)
47 static void debug_dump_buffer(const char *filename, uint8_t *buffer, unsigned int len)
48 {
49         (void)filename;
50         (void)buffer;
51         (void)len;
52 }
53 #endif
54
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
62 #define STX 0x02
63 #define ETX 0x03
64
65 #define BASE_YEAR 2000
66
67 typedef enum {
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 */
73         VISOMAT_CMD_UNKNOWN3,
74 } visomat_command;
75
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",
83 };
84
85 struct datetime  {
86         unsigned int year;
87         unsigned int month;
88         unsigned int day;
89         unsigned int hour;
90         unsigned int minute;
91 };
92
93 struct pressure  {
94         unsigned int flag; /* XXX Maybe this means arrhythmia? */
95         unsigned int systolic;
96         unsigned int diastolic;
97         unsigned int pulses;
98 };
99
100 static inline int extract_datetime(uint8_t *buffer, struct datetime *d)
101 {
102         int ret;
103
104         ret = sscanf((char *)buffer, "%02u%02u%02u%02u%02u",
105                      &d->year,
106                      &d->month,
107                      &d->day,
108                      &d->hour,
109                      &d->minute);
110         if (ret != 5)
111                 return -EINVAL;
112
113         d->year += BASE_YEAR;
114
115         return 0;
116 }
117
118 /* 
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.
121  */
122 static void print_record_csv_compat(struct datetime *d, struct pressure *p)
123 {
124         unsigned int pulse_pressure;
125
126         printf("%02u/%02u/%04u;%02u.%02u;",
127                d->day, d->month, d->year, d->hour, d->minute);
128
129         printf("%u;%u;%u;", p->systolic, p->diastolic, p->pulses);
130
131         pulse_pressure = p->systolic - p->diastolic;
132         printf("%u;", pulse_pressure);
133
134         if (p->flag)
135                 printf("x");
136
137 #if 0
138         /* The original software does not seem to be doing that */
139         if (p->pulses > 100)
140                 printf("tachycardia");
141         else if (p->pulses < 60)
142                 printf("bradycardia");
143 #endif
144
145         printf("\n");
146 }
147
148 /* TODO: it would be better to separate decoding data from printing it */
149 static int decode_eeprom(uint8_t *buffer,
150                          unsigned int len,
151                          unsigned int user_mask)
152 {
153         int ret;
154         unsigned int n;
155         unsigned int i;
156         unsigned int j;
157         unsigned int user_id;
158         unsigned int num_records;
159         struct datetime d;
160         struct pressure p;
161
162         if (buffer[0] != STX || buffer[1] != 'M') {
163                 fprintf(stderr, "Usupported data.\n");
164                 return -EINVAL;
165         }
166
167         if (buffer[len - 1] != ETX) {
168                 fprintf(stderr, "Bad terminator in data. Buffer too small?\n");
169                 return -EINVAL;
170         }
171
172         /* skip the initial STX */
173         i = 1;
174
175         /* and the final ETX */
176         n = len - 1;
177
178         while (i < n) {
179                 /* Each user record begins with 'M', maybe for "Memory" */
180                 if (buffer[i] == 'M') {
181                         /* i tracks the bytes consumed */
182                         i += 1;
183
184                         ret = sscanf((char *)(buffer + i), "%1u%02u",
185                                      &user_id, &num_records);
186                         if (ret != 2)
187                                 return -EINVAL;
188
189                         /* user_id and num_records take 3 bytes */
190                         i += 3;
191
192                         /*
193                          * when there are no records, there is a dummy byte
194                          * which has to be consumed
195                          */
196                         if (num_records == 0)
197                                 i += 1;
198
199                         for (j = 0; j < num_records; j++) {
200                                 ret = extract_datetime(buffer + i, &d);
201                                 if (ret < 0)
202                                         return ret;
203
204                                 /* datetime takes 10 bytes */
205                                 i += 10;
206
207                                 ret = sscanf((char *)(buffer + i),
208                                              "%1u%03u%03u%03u",
209                                              &p.flag,
210                                              &p.systolic,
211                                              &p.diastolic,
212                                              &p.pulses);
213                                 if (ret != 4)
214                                         return -EINVAL;
215
216                                 /* pressure data is 10 bytes */
217                                 i += 10;
218
219                                 /* TODO: split out the printing part */
220                                 if (user_id & user_mask) {
221                                         if (j == 0)
222                                                 printf("# User: %d\n", user_id);
223                                         print_record_csv_compat(&d, &p);
224                                 }
225                         }
226                 }
227         }
228
229         return 0;
230 }
231
232 static int decode_datetime(uint8_t *buffer, unsigned int len)
233 {
234         int ret;
235         uint8_t code[4] = { 0 };
236         struct datetime d;
237         uint8_t *pbuffer = buffer;
238
239         if (len != 15)
240                 return -EINVAL;
241
242         code[0] = buffer[1];
243         code[1] = buffer[2];
244         code[2] = buffer[3];
245
246         ret = extract_datetime(pbuffer + 4, &d);
247         if (ret < 0)
248                 return ret;
249
250         printf("# (%s) Date: %04d/%02d/%02d %02d:%02d\n",
251                code, d.year, d.month, d.day, d.hour, d.minute);
252
253         return 0;
254 }
255
256 static int send_command(libusb_device_handle *dev, visomat_command cmd)
257 {
258         int ret;
259         int transferred;
260         uint8_t request[5];
261
262         request[0] = STX;
263         request[1] = command_codes[cmd][0];
264         request[2] = command_codes[cmd][1];
265         request[3] = command_codes[cmd][2];
266         request[4] = ETX;
267
268         transferred = 0;
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));
273                 return ret;
274         }
275         return 0;
276 }
277
278 static int get_response(libusb_device_handle *dev,
279                         uint8_t *buffer,
280                         unsigned int len)
281 {
282         int ret;
283         int transferred;
284         uint8_t response[64] = { 0 };
285         unsigned int i;
286
287         i = 0;
288         do {
289                 unsigned int j;
290
291                 transferred = 0;
292                 ret = libusb_bulk_transfer(dev, VISOMAT_EP_IN, response, sizeof(response), &transferred, 5000);
293                 if (ret != 0) {
294                         fprintf(stderr, "Error getting response: %d (%s)\ttransferred: %d (expected %zu)\n",
295                                 ret, libusb_error_name(ret), transferred, sizeof(response));
296                         return ret;
297                 }
298
299                 for (j = 0; j < (unsigned int)transferred; j++) {
300                         buffer[i] = response[j];
301                         i += 1;
302                 }
303
304         } while (buffer[i - 1] != ETX && i < len);
305
306         /* Check the buffer is a valid response packet */
307         if (buffer[0] != STX || buffer[i - 1] != ETX)
308                 return -EINVAL;
309
310         return i;
311 }
312
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)
316 {
317         /* Assuming an EEPROM of 1 KiB  */
318         uint8_t buffer[1024] = { 0 };
319         int ret;
320
321         ret = send_command(dev, VISOMAT_CMD_DUMP_EEPROM);
322         if (ret < 0)
323                 return ret;
324
325         ret = get_response(dev, buffer, sizeof(buffer));
326         debug("buffer size: %d\n", ret);
327         debug_dump_buffer("eeprom.bin", buffer, sizeof(buffer));
328         if (ret < 0)
329                 return ret;
330
331         ret = decode_eeprom(buffer, ret, user_mask);
332         if (ret < 0)
333                 return ret;
334
335         return 0;
336 }
337
338 static int visomat_get_datetime(visomat_device *dev)
339 {
340         uint8_t buffer[255] = { 0 };
341         int ret;
342
343         ret = send_command(dev, VISOMAT_CMD_GET_DATETIME);
344         if (ret < 0)
345                 return ret;
346
347         ret = get_response(dev, buffer, sizeof(buffer));
348         if (ret < 0)
349                 return ret;
350
351         ret = decode_datetime(buffer, ret);
352         if (ret < 0)
353                 return ret;
354
355         return 0;
356 }
357
358 static void usage(const char *name)
359 {
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");
364 }
365
366 int main(int argc, char *argv[])
367 {
368         int ret;
369         int opt;
370         bool enable_libusb_debug = false;
371         libusb_device_handle *dev;
372         int current_configuration;
373
374         while ((opt = getopt(argc, argv, "Dh")) != -1) {
375                 switch (opt) {
376                 case 'D':
377                         enable_libusb_debug = true;
378                         break;
379                 case 'h':
380                         usage(argv[0]);
381                         ret = 0;
382                         goto out;
383                 default: /* '?' */
384                         usage(argv[0]);
385                         ret = -EINVAL;
386                         goto out;
387                 }
388         }
389
390         ret = libusb_init(NULL);
391         if (ret < 0) {
392                 fprintf(stderr, "libusb_init failed: %s\n",
393                         libusb_error_name(ret));
394                 goto out;
395         }
396
397         libusb_set_debug(NULL, enable_libusb_debug ?
398                          LIBUSB_LOG_LEVEL_DEBUG : LIBUSB_LOG_LEVEL_INFO);
399
400         dev = libusb_open_device_with_vid_pid(NULL,
401                                               VISOMAT_DEVICE_VID,
402                                               VISOMAT_DEVICE_PID);
403         if (dev == NULL) {
404                 fprintf(stderr, "libusb_open failed: %s\n", strerror(errno));
405                 ret = -errno;
406                 goto out_libusb_exit;
407         }
408
409         current_configuration = -1;
410         ret = libusb_get_configuration(dev, &current_configuration);
411         if (ret < 0) {
412                 fprintf(stderr, "libusb_get_configuration failed: %s\n",
413                         libusb_error_name(ret));
414                 goto out_libusb_close;
415         }
416
417         if (current_configuration != VISOMAT_CONFIGURATION) {
418                 ret = libusb_set_configuration(dev, VISOMAT_CONFIGURATION);
419                 if (ret < 0) {
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;
425                 }
426         }
427
428         libusb_set_auto_detach_kernel_driver(dev, 1);
429
430         ret = libusb_claim_interface(dev, VISOMAT_INTERFACE);
431         if (ret < 0) {
432                 fprintf(stderr, "libusb_claim_interface failed: %s\n",
433                         libusb_error_name(ret));
434                 fprintf(stderr, "Cannot claim interface %d\n",
435                         VISOMAT_INTERFACE);
436                 goto out_libusb_close;
437         }
438
439         /* Checking that the configuration has not changed, as suggested in
440          * http://libusb.sourceforge.net/api-1.0/caveats.html
441          */
442         current_configuration = -1;
443         ret = libusb_get_configuration(dev, &current_configuration);
444         if (ret < 0) {
445                 fprintf(stderr, "libusb_get_configuration after claim failed: %s\n",
446                         libusb_error_name(ret));
447                 goto out_libusb_release_interface;
448         }
449
450         if (current_configuration != VISOMAT_CONFIGURATION) {
451                 fprintf(stderr, "libusb configuration changed (expected: %d, current: %d)\n",
452                         VISOMAT_CONFIGURATION, current_configuration);
453                 ret = -EINVAL;
454                 goto out_libusb_release_interface;
455         }
456
457         ret = visomat_get_datetime(dev);
458         if (ret < 0)
459                 goto out_libusb_release_interface;
460
461         ret = visomat_dump_eeprom(dev, 0x01 | 0x02);
462         if (ret < 0)
463                 goto out_libusb_release_interface;
464
465 out_libusb_release_interface:
466         libusb_release_interface(dev, VISOMAT_INTERFACE);
467 out_libusb_close:
468         libusb_close(dev);
469         dev = NULL;
470 out_libusb_exit:
471         libusb_exit(NULL);
472 out:
473         return ret;
474 }