8cb99e23b219a531159b916fbdcaa03cfd43b268
[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 <errno.h>
25 #include <unistd.h>
26 #include <libusb.h>
27
28 #define VISOMAT_DEVICE_VID    0x1247
29 #define VISOMAT_DEVICE_PID    0x00f8
30 #define VISOMAT_CONFIGURATION 1
31 #define VISOMAT_INTERFACE     1
32 #define VISOMAT_EP_IN         0x82
33 #define VISOMAT_EP_OUT        0x03
34
35 #define STX 0x02
36 #define ETX 0x03
37
38 #define BASE_YEAR 2000
39
40 typedef enum {
41         VISOMAT_CMD_DUMP_EEPROM,
42         VISOMAT_CMD_UNKNOWN1, /* maybe the firmware revision */
43         VISOMAT_CMD_GET_DATETIME,
44         VISOMAT_CMD_START_MEASURE1,
45         VISOMAT_CMD_UNKNOWN2, /* XXX transmission hangs */
46         VISOMAT_CMD_UNKNOWN3,
47 } visomat_command;
48
49 static char command_codes[][3] = {
50         [VISOMAT_CMD_DUMP_EEPROM]    = "R00",
51         [VISOMAT_CMD_UNKNOWN1]       = "R01",
52         [VISOMAT_CMD_GET_DATETIME]   = "R02",
53         [VISOMAT_CMD_START_MEASURE1] = "R03",
54         [VISOMAT_CMD_UNKNOWN2]       = "R04",
55         [VISOMAT_CMD_UNKNOWN3]       = "R05",
56 };
57
58 struct datetime  {
59         unsigned int year;
60         unsigned int month;
61         unsigned int day;
62         unsigned int hour;
63         unsigned int minute;
64 };
65
66 struct pressure  {
67         unsigned int flag; /* XXX Maybe this means arrhythmia? */
68         unsigned int systolic;
69         unsigned int diastolic;
70         unsigned int pulses;
71 };
72
73 static inline int extract_datetime(unsigned char *buffer, struct datetime *d)
74 {
75         int ret;
76
77         ret = sscanf((char *)buffer, "%02u%02u%02u%02u%02u",
78                      &d->year,
79                      &d->month,
80                      &d->day,
81                      &d->hour,
82                      &d->minute);
83         if (ret != 5)
84                 return -EINVAL;
85
86         d->year += BASE_YEAR;
87
88         return 0;
89 }
90
91 /* 
92  * NOTE: the CSV output is meant to be compatible with one from the Windows
93  * program, which uses the %d/%m/%Y date format, so that's not my fault.
94  */
95 static void print_record_csv_compat(struct datetime *d, struct pressure *p)
96 {
97         unsigned int pulse_pressure;
98
99         printf("%02u/%02u/%04u;%02u.%02u;",
100                d->day, d->month, d->year, d->hour, d->minute);
101
102         printf("%u;%u;%u;", p->systolic, p->diastolic, p->pulses);
103
104         pulse_pressure = p->systolic - p->diastolic;
105         printf("%u;", pulse_pressure);
106
107         if (p->flag)
108                 printf("x");
109
110 #if 0
111         /* The original software does not seem to be doing that */
112         if (p->pulses > 100)
113                 printf("tachycardia");
114         else if (p->pulses < 60)
115                 printf("bradycardia");
116 #endif
117
118         printf("\n");
119 }
120
121 /* TODO separate better decoding data from printing it */
122 static int decode_eeprom(unsigned char *buffer,
123                          unsigned int len,
124                          unsigned int user_mask)
125 {
126         int ret;
127         unsigned int n;
128         unsigned int i;
129         unsigned int j;
130         unsigned int user_id;
131         unsigned int num_records;
132         struct datetime d;
133         struct pressure p;
134
135         if (buffer[0] != STX || buffer[1] != 'M' || buffer[len - 1] != ETX)
136                 return -EINVAL;
137
138         /* skip the initial STX */
139         i = 1;
140
141         /* and the final ETX */
142         n = len - 1;
143
144         while (i < n) {
145                 /* Each user record begins with 'M', maybe for "Memory" */
146                 if (buffer[i] == 'M') {
147                         /* i tracks the bytes consumed */
148                         i += 1;
149
150                         ret = sscanf((char *)(buffer + i), "%1u%02u",
151                                      &user_id, &num_records);
152                         if (ret != 2)
153                                 return -EINVAL;
154
155                         /* user_id and num_records take 3 bytes */
156                         i += 3;
157
158                         for (j = 0; j < num_records; j++) {
159                                 ret = extract_datetime(buffer + i, &d);
160                                 if (ret < 0)
161                                         return ret;
162
163                                 /* datetime takes 10 bytes */
164                                 i += 10;
165
166                                 ret = sscanf((char *)(buffer + i),
167                                              "%1u%03u%03u%03u",
168                                              &p.flag,
169                                              &p.systolic,
170                                              &p.diastolic,
171                                              &p.pulses);
172                                 if (ret != 4)
173                                         return -EINVAL;
174
175                                 /* pressure data is 10 bytes */
176                                 i += 10;
177
178                                 /* TODO: split out the printing part */
179                                 if (user_id & user_mask) {
180                                         if (j == 0)
181                                                 printf("# User: %d\n", user_id);
182                                         print_record_csv_compat(&d, &p);
183                                 }
184                         }
185                 }
186         }
187
188         return 0;
189 }
190
191 static int decode_datetime(unsigned char *buffer, unsigned int len)
192 {
193         int ret;
194         unsigned char code[4] = { 0 };
195         struct datetime d;
196         unsigned char *pbuffer = buffer;
197
198         if (len != 15)
199                 return -EINVAL;
200
201         code[0] = buffer[1];
202         code[1] = buffer[2];
203         code[2] = buffer[3];
204
205         ret = extract_datetime(pbuffer + 4, &d);
206         if (ret < 0)
207                 return ret;
208
209         printf("# (%s) Date: %04d/%02d/%02d %02d:%02d\n",
210                code, d.year, d.month, d.day, d.hour, d.minute);
211
212         return 0;
213 }
214
215 static int send_command(libusb_device_handle *dev, visomat_command cmd)
216 {
217         int ret;
218         int transferred;
219         unsigned char request[5];
220
221         request[0] = STX;
222         request[1] = command_codes[cmd][0];
223         request[2] = command_codes[cmd][1];
224         request[3] = command_codes[cmd][2];
225         request[4] = ETX;
226
227         transferred = 0;
228         ret = libusb_bulk_transfer(dev, VISOMAT_EP_OUT, request, sizeof(request), &transferred, 0);
229         if (ret != 0 || transferred != sizeof(request)) {
230                 fprintf(stderr, "Error: sending request: %d (%s)\ttransferred: %d (expected %zu)\n",
231                         ret, libusb_error_name(ret), transferred, sizeof(request));
232                 return ret;
233         }
234         return 0;
235 }
236
237 static int get_response(libusb_device_handle *dev,
238                         unsigned char *buffer,
239                         unsigned int len)
240 {
241         int ret;
242         int transferred;
243         unsigned char response[64] = { 0 };
244         unsigned int i;
245
246         i = 0;
247         do {
248                 unsigned int j;
249
250                 transferred = 0;
251                 ret = libusb_bulk_transfer(dev, VISOMAT_EP_IN, response, sizeof(response), &transferred, 5000);
252                 if (ret != 0) {
253                         fprintf(stderr, "Error getting response: %d (%s)\ttransferred: %d (expected %zu)\n",
254                                 ret, libusb_error_name(ret), transferred, sizeof(response));
255                         return ret;
256                 }
257
258                 for (j = 0; j < (unsigned int)transferred; j++) {
259                         buffer[i] = response[j];
260                         i += 1;
261                 }
262
263         } while (buffer[i - 1] != ETX && i < len);
264
265         /* Check the buffer is a valid response packet */
266         if (buffer[0] != STX || buffer[i - 1] != ETX)
267                 return -EINVAL;
268
269         return i;
270 }
271
272 /* Candidates for a future public API, if a shared library will ever be made */
273 #define visomat_device libusb_device_handle
274 static int visomat_dump_eeprom(visomat_device *dev, unsigned int user_mask)
275 {
276         /* Assuming an EEPROM of 1 KiB  */
277         unsigned char buffer[1024] = { 0 };
278         int ret;
279
280         ret = send_command(dev, VISOMAT_CMD_DUMP_EEPROM);
281         if (ret < 0)
282                 return ret;
283
284         ret = get_response(dev, buffer, sizeof(buffer));
285         if (ret < 0)
286                 return ret;
287
288         ret = decode_eeprom(buffer, ret, user_mask);
289         if (ret < 0)
290                 return ret;
291
292         return 0;
293 }
294
295 static int visomat_get_datetime(visomat_device *dev)
296 {
297         unsigned char buffer[255] = { 0 };
298         int ret;
299
300         ret = send_command(dev, VISOMAT_CMD_GET_DATETIME);
301         if (ret < 0)
302                 return ret;
303
304         ret = get_response(dev, buffer, sizeof(buffer));
305         if (ret < 0)
306                 return ret;
307
308         ret = decode_datetime(buffer, ret);
309         if (ret < 0)
310                 return ret;
311
312         return 0;
313 }
314
315 int main(void)
316 {
317         int ret;
318         libusb_device_handle *dev;
319
320         ret = libusb_init(NULL);
321         if (ret < 0) {
322                 fprintf(stderr, "error (%d): cannot initialize libusb.\n", ret);
323                 goto out;
324         }
325
326         libusb_set_debug(NULL, LIBUSB_LOG_LEVEL_INFO);
327
328         dev = libusb_open_device_with_vid_pid(NULL,
329                                               VISOMAT_DEVICE_VID,
330                                               VISOMAT_DEVICE_PID);
331         if (dev == NULL) {
332                 fprintf(stderr, "error (%d): cannot open device.\n", errno);
333                 ret = -errno;
334                 goto out_libusb_exit;
335         }
336
337         ret = libusb_set_configuration(dev, VISOMAT_CONFIGURATION);
338         if (ret < 0) {
339                 fprintf(stderr, "error (%d): cannot set configuration %d.\n",
340                         ret, VISOMAT_CONFIGURATION);
341                 goto out_libusb_close;
342         }
343
344         libusb_set_auto_detach_kernel_driver(dev, 1);
345
346         ret = libusb_claim_interface(dev, VISOMAT_INTERFACE);
347         if (ret < 0) {
348                 fprintf(stderr, "error (%d): cannot claim interface %d.\n",
349                         ret, VISOMAT_INTERFACE);
350                 goto out_libusb_close;
351         }
352
353         ret = visomat_get_datetime(dev);
354         if (ret < 0)
355                 goto out_libusb_release_interface;
356
357         ret = visomat_dump_eeprom(dev, 0x01 | 0x02);
358         if (ret < 0)
359                 goto out_libusb_release_interface;
360
361 out_libusb_release_interface:
362         libusb_release_interface(dev, VISOMAT_INTERFACE);
363 out_libusb_close:
364         libusb_close(dev);
365         dev = NULL;
366 out_libusb_exit:
367         libusb_exit(NULL);
368 out:
369         return ret;
370 }