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