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