69f62d6c004dbbd9e6e74144005a9a27ac8ac38e
[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 <stdlib.h>
24 #include <string.h>
25 #include <errno.h>
26 #include <unistd.h>
27 #include <libusb.h>
28
29 #ifdef DEBUG
30 #define debug(...) fprintf(stderr, __VA_ARGS__)
31 static void debug_dump_buffer(const char *filename, uint8_t *buffer, unsigned int len)
32 {
33         FILE *dump_file;
34
35         dump_file = fopen(filename, "wb");
36         if (dump_file == NULL) {
37                 fprintf(stderr, "Failed to open %s: %s\n", filename, strerror(errno));
38                 return;
39         }
40
41         fwrite(buffer, 1, len, dump_file);
42         fclose(dump_file);
43 }
44 #else
45 #define debug(...) do {} while(0)
46 static void debug_dump_buffer(const char *filename, uint8_t *buffer, unsigned int len)
47 {
48         (void)filename;
49         (void)buffer;
50         (void)len;
51 }
52 #endif
53
54 #define VISOMAT_DEVICE_VID    0x1247
55 #define VISOMAT_DEVICE_PID    0x00f8
56 #define VISOMAT_CONFIGURATION 1
57 #define VISOMAT_INTERFACE     1
58 #define VISOMAT_EP_IN         0x82
59 #define VISOMAT_EP_OUT        0x03
60
61 #define STX 0x02
62 #define ETX 0x03
63
64 #define BASE_YEAR 2000
65
66 typedef enum {
67         VISOMAT_CMD_DUMP_EEPROM,
68         VISOMAT_CMD_UNKNOWN1, /* maybe the firmware revision */
69         VISOMAT_CMD_GET_DATETIME,
70         VISOMAT_CMD_START_MEASURE1,
71         VISOMAT_CMD_UNKNOWN2, /* XXX transmission hangs */
72         VISOMAT_CMD_UNKNOWN3,
73 } visomat_command;
74
75 static char command_codes[][3] = {
76         [VISOMAT_CMD_DUMP_EEPROM]    = "R00",
77         [VISOMAT_CMD_UNKNOWN1]       = "R01",
78         [VISOMAT_CMD_GET_DATETIME]   = "R02",
79         [VISOMAT_CMD_START_MEASURE1] = "R03",
80         [VISOMAT_CMD_UNKNOWN2]       = "R04",
81         [VISOMAT_CMD_UNKNOWN3]       = "R05",
82 };
83
84 struct datetime  {
85         unsigned int year;
86         unsigned int month;
87         unsigned int day;
88         unsigned int hour;
89         unsigned int minute;
90 };
91
92 struct pressure  {
93         unsigned int flag; /* XXX Maybe this means arrhythmia? */
94         unsigned int systolic;
95         unsigned int diastolic;
96         unsigned int pulses;
97 };
98
99 static inline int extract_datetime(uint8_t *buffer, struct datetime *d)
100 {
101         int ret;
102
103         ret = sscanf((char *)buffer, "%02u%02u%02u%02u%02u",
104                      &d->year,
105                      &d->month,
106                      &d->day,
107                      &d->hour,
108                      &d->minute);
109         if (ret != 5)
110                 return -EINVAL;
111
112         d->year += BASE_YEAR;
113
114         return 0;
115 }
116
117 /* 
118  * NOTE: the CSV output is meant to be compatible with one from the Windows
119  * program, which uses the %d/%m/%Y date format, so that's not my fault.
120  */
121 static void print_record_csv_compat(struct datetime *d, struct pressure *p)
122 {
123         unsigned int pulse_pressure;
124
125         printf("%02u/%02u/%04u;%02u.%02u;",
126                d->day, d->month, d->year, d->hour, d->minute);
127
128         printf("%u;%u;%u;", p->systolic, p->diastolic, p->pulses);
129
130         pulse_pressure = p->systolic - p->diastolic;
131         printf("%u;", pulse_pressure);
132
133         if (p->flag)
134                 printf("x");
135
136 #if 0
137         /* The original software does not seem to be doing that */
138         if (p->pulses > 100)
139                 printf("tachycardia");
140         else if (p->pulses < 60)
141                 printf("bradycardia");
142 #endif
143
144         printf("\n");
145 }
146
147 /* TODO: it would be better to separate decoding data from printing it */
148 static int decode_eeprom(uint8_t *buffer,
149                          unsigned int len,
150                          unsigned int user_mask)
151 {
152         int ret;
153         unsigned int n;
154         unsigned int i;
155         unsigned int j;
156         unsigned int user_id;
157         unsigned int num_records;
158         struct datetime d;
159         struct pressure p;
160
161         if (buffer[0] != STX || buffer[1] != 'M') {
162                 fprintf(stderr, "Usupported data.\n");
163                 return -EINVAL;
164         }
165
166         if (buffer[len - 1] != ETX) {
167                 fprintf(stderr, "Bad terminator in data. Buffer too small?\n");
168                 return -EINVAL;
169         }
170
171         /* skip the initial STX */
172         i = 1;
173
174         /* and the final ETX */
175         n = len - 1;
176
177         while (i < n) {
178                 /* Each user record begins with 'M', maybe for "Memory" */
179                 if (buffer[i] == 'M') {
180                         /* i tracks the bytes consumed */
181                         i += 1;
182
183                         ret = sscanf((char *)(buffer + i), "%1u%02u",
184                                      &user_id, &num_records);
185                         if (ret != 2)
186                                 return -EINVAL;
187
188                         /* user_id and num_records take 3 bytes */
189                         i += 3;
190
191                         /*
192                          * when there are no records, there is a dummy byte
193                          * which has to be consumed
194                          */
195                         if (num_records == 0)
196                                 i += 1;
197
198                         for (j = 0; j < num_records; j++) {
199                                 ret = extract_datetime(buffer + i, &d);
200                                 if (ret < 0)
201                                         return ret;
202
203                                 /* datetime takes 10 bytes */
204                                 i += 10;
205
206                                 ret = sscanf((char *)(buffer + i),
207                                              "%1u%03u%03u%03u",
208                                              &p.flag,
209                                              &p.systolic,
210                                              &p.diastolic,
211                                              &p.pulses);
212                                 if (ret != 4)
213                                         return -EINVAL;
214
215                                 /* pressure data is 10 bytes */
216                                 i += 10;
217
218                                 /* TODO: split out the printing part */
219                                 if (user_id & user_mask) {
220                                         if (j == 0)
221                                                 printf("# User: %d\n", user_id);
222                                         print_record_csv_compat(&d, &p);
223                                 }
224                         }
225                 }
226         }
227
228         return 0;
229 }
230
231 static int decode_datetime(uint8_t *buffer, unsigned int len)
232 {
233         int ret;
234         uint8_t code[4] = { 0 };
235         struct datetime d;
236         uint8_t *pbuffer = buffer;
237
238         if (len != 15)
239                 return -EINVAL;
240
241         code[0] = buffer[1];
242         code[1] = buffer[2];
243         code[2] = buffer[3];
244
245         ret = extract_datetime(pbuffer + 4, &d);
246         if (ret < 0)
247                 return ret;
248
249         printf("# (%s) Date: %04d/%02d/%02d %02d:%02d\n",
250                code, d.year, d.month, d.day, d.hour, d.minute);
251
252         return 0;
253 }
254
255 static int send_command(libusb_device_handle *dev, visomat_command cmd)
256 {
257         int ret;
258         int transferred;
259         uint8_t request[5];
260
261         request[0] = STX;
262         request[1] = command_codes[cmd][0];
263         request[2] = command_codes[cmd][1];
264         request[3] = command_codes[cmd][2];
265         request[4] = ETX;
266
267         transferred = 0;
268         ret = libusb_bulk_transfer(dev, VISOMAT_EP_OUT, request, sizeof(request), &transferred, 0);
269         if (ret != 0 || transferred != sizeof(request)) {
270                 fprintf(stderr, "Error: sending request: %d (%s)\ttransferred: %d (expected %zu)\n",
271                         ret, libusb_error_name(ret), transferred, sizeof(request));
272                 return ret;
273         }
274         return 0;
275 }
276
277 static int get_response(libusb_device_handle *dev,
278                         uint8_t *buffer,
279                         unsigned int len)
280 {
281         int ret;
282         int transferred;
283         uint8_t response[64] = { 0 };
284         unsigned int i;
285
286         i = 0;
287         do {
288                 unsigned int j;
289
290                 transferred = 0;
291                 ret = libusb_bulk_transfer(dev, VISOMAT_EP_IN, response, sizeof(response), &transferred, 5000);
292                 if (ret != 0) {
293                         fprintf(stderr, "Error getting response: %d (%s)\ttransferred: %d (expected %zu)\n",
294                                 ret, libusb_error_name(ret), transferred, sizeof(response));
295                         return ret;
296                 }
297
298                 for (j = 0; j < (unsigned int)transferred; j++) {
299                         buffer[i] = response[j];
300                         i += 1;
301                 }
302
303         } while (buffer[i - 1] != ETX && i < len);
304
305         /* Check the buffer is a valid response packet */
306         if (buffer[0] != STX || buffer[i - 1] != ETX)
307                 return -EINVAL;
308
309         return i;
310 }
311
312 /* Candidates for a future public API, if a shared library will ever be made */
313 #define visomat_device libusb_device_handle
314 static int visomat_dump_eeprom(visomat_device *dev, unsigned int user_mask)
315 {
316         /* Assuming an EEPROM of 1 KiB  */
317         uint8_t buffer[1024] = { 0 };
318         int ret;
319
320         ret = send_command(dev, VISOMAT_CMD_DUMP_EEPROM);
321         if (ret < 0)
322                 return ret;
323
324         ret = get_response(dev, buffer, sizeof(buffer));
325         if (ret < 0)
326                 return ret;
327
328         debug("buffer len: %d\n", ret);
329         debug_dump_buffer("eeprom.bin", buffer, sizeof(buffer));
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 int main(void)
359 {
360         int ret;
361         libusb_device_handle *dev;
362         int current_configuration;
363
364         ret = libusb_init(NULL);
365         if (ret < 0) {
366                 fprintf(stderr, "libusb_init failed: %s\n",
367                         libusb_error_name(ret));
368                 goto out;
369         }
370
371         libusb_set_debug(NULL, LIBUSB_LOG_LEVEL_INFO);
372
373         dev = libusb_open_device_with_vid_pid(NULL,
374                                               VISOMAT_DEVICE_VID,
375                                               VISOMAT_DEVICE_PID);
376         if (dev == NULL) {
377                 fprintf(stderr, "libusb_open failed: %s\n", strerror(errno));
378                 ret = -errno;
379                 goto out_libusb_exit;
380         }
381
382         current_configuration = -1;
383         ret = libusb_get_configuration(dev, &current_configuration);
384         if (ret < 0) {
385                 fprintf(stderr, "libusb_get_configuration failed: %s\n",
386                         libusb_error_name(ret));
387                 goto out_libusb_close;
388         }
389
390         if (current_configuration != VISOMAT_CONFIGURATION) {
391                 ret = libusb_set_configuration(dev, VISOMAT_CONFIGURATION);
392                 if (ret < 0) {
393                         fprintf(stderr, "libusb_set_configuration failed: %s\n",
394                                 libusb_error_name(ret));
395                         fprintf(stderr, "Cannot set configuration %d\n",
396                                 VISOMAT_CONFIGURATION);
397                         goto out_libusb_close;
398                 }
399         }
400
401         libusb_set_auto_detach_kernel_driver(dev, 1);
402
403         ret = libusb_claim_interface(dev, VISOMAT_INTERFACE);
404         if (ret < 0) {
405                 fprintf(stderr, "libusb_claim_interface failed: %s\n",
406                         libusb_error_name(ret));
407                 fprintf(stderr, "Cannot claim interface %d\n",
408                         VISOMAT_INTERFACE);
409                 goto out_libusb_close;
410         }
411
412         /* Checking that the configuration has not changed, as suggested in
413          * http://libusb.sourceforge.net/api-1.0/caveats.html
414          */
415         current_configuration = -1;
416         ret = libusb_get_configuration(dev, &current_configuration);
417         if (ret < 0) {
418                 fprintf(stderr, "libusb_get_configuration after claim failed: %s\n",
419                         libusb_error_name(ret));
420                 goto out_libusb_release_interface;
421         }
422
423         if (current_configuration != VISOMAT_CONFIGURATION) {
424                 fprintf(stderr, "libusb configuration changed (expected: %d, current: %d)\n",
425                         VISOMAT_CONFIGURATION, current_configuration);
426                 ret = -EINVAL;
427                 goto out_libusb_release_interface;
428         }
429
430         ret = visomat_get_datetime(dev);
431         if (ret < 0)
432                 goto out_libusb_release_interface;
433
434         ret = visomat_dump_eeprom(dev, 0x01 | 0x02);
435         if (ret < 0)
436                 goto out_libusb_release_interface;
437
438 out_libusb_release_interface:
439         libusb_release_interface(dev, VISOMAT_INTERFACE);
440 out_libusb_close:
441         libusb_close(dev);
442         dev = NULL;
443 out_libusb_exit:
444         libusb_exit(NULL);
445 out:
446         return ret;
447 }