add trinketSetAlarmLevel
[trinked.git] / trinket.c
1 /*-------------------------------------------------------------------------
2  * trinket.c
3  *
4  * Copyright (c) 2016, Teodor Sigaev <teodor@sigaev.ru>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *        notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *        notice, this list of conditions and the following disclaimer in the
13  *        documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *-------------------------------------------------------------------------
27  */
28
29 #include <stdint.h>
30 #include <stdio.h>
31 #include <string.h>
32
33 #include <hidapi/hidapi.h>
34
35 #include <tlog.h>
36 #include <trinket.h>
37
38 #define  packed_struct __attribute__((packed))
39
40 typedef enum {
41         CMD_PING = 0,
42         CMD_VCC_VALUE = 1,
43         CMD_VCC_ALARM = 2,
44         CMD_CLICK_DELAY = 3,
45         CMD_ALARM_LEVELS = 4,
46         CMD_RTC = 5,
47         CMD_DAY_ARCH = 6,
48         CMD_YEAR_ARCH = 7,
49         CMD_CUM_DOSE = 8,
50         CMD_INVOKE_BSL = 9
51 } TrinketCommand;
52
53 #define BEGIN_COMMAND   (0x1B)
54 #define END_COMMAND             (0x17)
55 #define ACK_ANSWER_OK   (0x06)
56 #define ACK_ANSWER_ERR  (0x15)
57 #define END_ANSWER              (0x17)
58
59 /* http://uglyduck.ath.cx/PDF/TexasInstruments/MSP430/usblib430/Programmers_Guide_MSP430_USB_API.pdf */
60 #define REPORT_ID               (0x3f)
61 typedef struct packed_struct {
62         uint8_t reportId;
63         uint8_t size;
64 } ProtoHeader;
65
66
67 #define DEV_TIMEOUT             (5000 /* milliseconds */)
68 typedef struct {
69         /*
70          * Command desc
71          */
72         uint8_t                 cmdId;
73         uint8_t                 cmdLen; /* 0 means variable, > 0 fixed */
74
75         /*
76          * Answer desc
77          */
78         uint32_t                respLen; /* 0 means variable, > 0 fixed */
79 } TrinketProtoDesc;
80
81 /* hardcoded knowledge from  docs/geyger.pdf */
82 static const TrinketProtoDesc protoDesc[] = {
83         {CMD_PING,                      4,              5                               },
84         {CMD_VCC_VALUE,         4,              8                               },
85         {CMD_VCC_ALARM,         8,              8                               },
86         {CMD_CLICK_DELAY,       8,              8                               },
87         {CMD_ALARM_LEVELS,      24,             24                              },
88         {CMD_RTC,                       13,             13                              },
89         {CMD_DAY_ARCH,          4,              4 + 1440 * 2    },
90         {CMD_YEAR_ARCH,         4,              4 + 5840 * 2    },
91         {CMD_CUM_DOSE,          4,              12                              },
92         {CMD_INVOKE_BSL,        4,              0                               }
93 };
94
95 static hid_device *trinketDev = NULL;
96
97 #if 0
98 static void
99 dumpBuf(char *msg, uint8_t *buf, int len) {
100         int i;
101
102         fprintf(stderr,"%s:", msg);
103         for (i=0; i<len; i++)
104                 fprintf(stderr, " %02x", buf[i]);
105         fprintf(stderr, "\n");
106 }
107 #endif
108
109 TrinketErrorCode
110 trinketOpen() {
111         TrinketErrorCode        r;
112         int i;
113
114         for(i = 0; i < sizeof(protoDesc) / sizeof(*protoDesc); i++)
115                 tassert(i == protoDesc[i].cmdId);
116
117         trinketDev = hid_open(0x2047, 0x0301, NULL);
118
119         if (trinketDev == NULL) {
120                 tlog(TL_WARN, "hid_open fails");
121                 return ERR_ERROR;
122         }
123
124         if ((r = trinketPing()) != ERR_OK)
125                 trinketClose();
126         return r;
127 }
128
129 void
130 trinketClose() {
131         if (trinketDev)
132                 hid_close(trinketDev);
133         trinketDev = NULL;
134 }
135
136 TrinketErrorCode
137 trinketPing() {
138         const TrinketProtoDesc  *proto = &protoDesc[CMD_PING];
139         struct packed_struct {
140                 ProtoHeader header;
141                 uint8_t         begin;
142                 uint8_t         len;
143                 uint8_t         cmd;
144                 uint8_t         end;
145         } cmd = {
146                                         {REPORT_ID, proto->cmdLen},
147                                         BEGIN_COMMAND,
148                                         proto->cmdLen,
149                                         proto->cmdId,
150                                         END_COMMAND
151         };
152         struct packed_struct {
153                 ProtoHeader header;
154                 uint8_t         begin;
155                 uint16_t        len;
156                 uint8_t         payload /* = 0 */;
157                 uint8_t         end;
158         } resp;
159         int     r;
160
161         tassert(proto->cmdId == CMD_PING);
162         tassert(sizeof(cmd) == cmd.len + sizeof(ProtoHeader));
163         tassert(sizeof(resp) == proto->respLen + sizeof(ProtoHeader));
164
165         r = hid_write(trinketDev, (unsigned char*)&cmd, sizeof(cmd));
166
167         if (r < 0 || r != sizeof(cmd)) {
168                 tlog(TL_WARN, "hid_write  returns %d instead of %u", r, cmd.len);
169                 return ERR_ERROR;
170         }
171
172         /* must be assigned to 0x3F by the host */
173         resp.header.reportId = REPORT_ID;
174
175         r = hid_read_timeout(trinketDev, (unsigned char*)&resp, sizeof(resp), DEV_TIMEOUT);
176         if (r < 0) {
177                 tlog(TL_WARN, "hid_read_timeout fails");
178                 return  ERR_NO_ANSWER;
179         }
180
181         if (r != sizeof(resp)) {
182                 tlog(TL_WARN, "hid_read_timeout  returns %d instead of %u", r, (unsigned int)sizeof(resp));
183                 return ERR_ERROR;
184         }
185
186         if (resp.header.reportId != REPORT_ID) {
187                 tlog(TL_WARN, "hid_read_timeout  returns report id %02x instead of %02x", resp.header.reportId, REPORT_ID);
188                 return ERR_ERROR;
189         }
190
191         if (resp.header.size != proto->respLen) {
192                 tlog(TL_WARN, "hid_read_timeout returns length %u intsead if %u", resp.header.size, proto->respLen);
193                 return ERR_PROTO;
194         }
195
196         if (resp.len != proto->respLen) {
197                 tlog(TL_WARN, "hid_read_timeout response length %u instead of %u", resp.len, proto->respLen);
198                 return ERR_PROTO;
199         }
200
201         if (!(resp.begin == ACK_ANSWER_OK && resp.payload == 0 && resp.end == END_ANSWER)) {
202                 tlog(TL_WARN, "hid_read_timeout  ACK %02x with payload %u and ETB %02x", resp.begin, resp.payload, resp.end);
203                 return ERR_PROTO;
204         }
205
206         return ERR_OK;
207 }
208
209 TrinketErrorCode
210 trinketGetCumDose(double *value) {
211         const TrinketProtoDesc  *proto = &protoDesc[CMD_CUM_DOSE];
212         struct {
213                 ProtoHeader header;
214                 uint8_t         begin;
215                 uint8_t         len;
216                 uint8_t         cmd;
217                 uint8_t         end;
218         } cmd = {
219                                         {REPORT_ID, proto->cmdLen},
220                                         BEGIN_COMMAND,
221                                         proto->cmdLen,
222                                         proto->cmdId,
223                                         END_COMMAND
224         };
225         struct packed_struct {
226                 ProtoHeader header;
227                 uint8_t         begin;
228                 uint16_t        len;
229                 double          payload;
230                 uint8_t         end;
231         } resp;
232         int     r;
233
234         tassert(proto->cmdId == CMD_CUM_DOSE);
235         tassert(sizeof(cmd) == cmd.len + sizeof(ProtoHeader));
236         tassert(sizeof(resp) == proto->respLen + sizeof(ProtoHeader));
237
238         r = hid_write(trinketDev, (unsigned char*)&cmd, sizeof(cmd));
239
240         if (r < 0 || r != sizeof(cmd)) {
241                 tlog(TL_WARN, "hid_send_feature_report  returns %d instead of %u", r, cmd.len);
242                 return ERR_ERROR;
243         }
244
245         /* must be assigned to 0x3F by the host */
246         resp.header.reportId = REPORT_ID;
247
248         r = hid_read_timeout(trinketDev, (unsigned char*)&resp, sizeof(resp), DEV_TIMEOUT);
249         if (r < 0) {
250                 tlog(TL_WARN, "hid_read_timeout fails");
251                 return  ERR_NO_ANSWER;
252         }
253
254         if (r != sizeof(resp)) {
255                 tlog(TL_WARN, "hid_read_timeout  returns %d instead of %u", r, (unsigned int)sizeof(resp));
256                 return ERR_ERROR;
257         }
258
259         if (resp.header.reportId != REPORT_ID) {
260                 tlog(TL_WARN, "hid_read_timeout  returns report id %02x instead of %02x", resp.header.reportId, REPORT_ID);
261                 return ERR_ERROR;
262         }
263
264         if (resp.header.size != proto->respLen) {
265                 tlog(TL_WARN, "hid_read_timeout returns length %u intsead if %u", resp.header.size, proto->respLen);
266                 return ERR_PROTO;
267         }
268
269         if (resp.len != proto->respLen) {
270                 tlog(TL_WARN, "hid_read_timeout response length %u instead of %u", resp.len, proto->respLen);
271                 return ERR_PROTO;
272         }
273
274         if (!(resp.begin == ACK_ANSWER_OK && resp.end == END_ANSWER)) {
275                 tlog(TL_WARN, "hid_read_timeout  ACK %02x and ETB %02x", resp.begin, resp.end);
276                 return ERR_PROTO;
277         }
278
279         *value = resp.payload;
280
281         return ERR_OK;
282 }
283
284 TrinketErrorCode
285 trinketSetAlarmLevel(double value[N_ALARM_LEVELS]) {
286         const TrinketProtoDesc  *proto = &protoDesc[CMD_ALARM_LEVELS];
287         struct {
288                 ProtoHeader header;
289                 uint8_t         begin;
290                 uint8_t         len;
291                 uint8_t         cmd;
292                 float           levels[N_ALARM_LEVELS];
293                 uint8_t         end;
294         } cmd = {
295                                         {REPORT_ID, proto->cmdLen},
296                                         BEGIN_COMMAND,
297                                         proto->cmdLen,
298                                         proto->cmdId,
299                                         {0,0,0,0,0},
300                                         END_COMMAND
301         };
302         struct packed_struct {
303                 ProtoHeader header;
304                 uint8_t         begin;
305                 uint16_t        len;
306                 float           payload[N_ALARM_LEVELS];
307                 uint8_t         end;
308         } resp;
309         int     r;
310
311         tassert(proto->cmdId == CMD_ALARM_LEVELS);
312         tassert(sizeof(cmd) == cmd.len + sizeof(ProtoHeader));
313         tassert(sizeof(resp) == proto->respLen + sizeof(ProtoHeader));
314
315         memcpy(cmd.levels, value, sizeof(value[0]) * N_ALARM_LEVELS);
316
317         r = hid_write(trinketDev, (unsigned char*)&cmd, sizeof(cmd));
318
319         if (r < 0 || r != sizeof(cmd)) {
320                 tlog(TL_WARN, "hid_send_feature_report  returns %d instead of %u", r, cmd.len);
321                 return ERR_ERROR;
322         }
323
324         /* must be assigned to 0x3F by the host */
325         resp.header.reportId = REPORT_ID;
326
327         r = hid_read_timeout(trinketDev, (unsigned char*)&resp, sizeof(resp), DEV_TIMEOUT);
328         if (r < 0) {
329                 tlog(TL_WARN, "hid_read_timeout fails");
330                 return  ERR_NO_ANSWER;
331         }
332
333         if (r != sizeof(resp)) {
334                 tlog(TL_WARN, "hid_read_timeout  returns %d instead of %u", r, (unsigned int)sizeof(resp));
335                 return ERR_ERROR;
336         }
337
338         if (resp.header.reportId != REPORT_ID) {
339                 tlog(TL_WARN, "hid_read_timeout  returns report id %02x instead of %02x", resp.header.reportId, REPORT_ID);
340                 return ERR_ERROR;
341         }
342
343         if (resp.header.size != proto->respLen) {
344                 tlog(TL_WARN, "hid_read_timeout returns length %u intsead if %u", resp.header.size, proto->respLen);
345                 return ERR_PROTO;
346         }
347
348         if (resp.len != proto->respLen) {
349                 tlog(TL_WARN, "hid_read_timeout response length %u instead of %u", resp.len, proto->respLen);
350                 return ERR_PROTO;
351         }
352
353         if (!(resp.begin == ACK_ANSWER_OK && resp.end == END_ANSWER)) {
354                 tlog(TL_WARN, "hid_read_timeout  ACK %02x and ETB %02x", resp.begin, resp.end);
355                 return ERR_PROTO;
356         }
357
358         memcpy(value, resp.payload, sizeof(value[0]) * N_ALARM_LEVELS);
359
360         return ERR_OK;
361 }