fa6598d529812c5bf66145a3c97698572876aaa7
[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 <hidapi/hidapi.h>
32
33 #include <tlog.h>
34 #include <trinket.h>
35
36 #define  packed_struct __attribute__((packed))
37
38 typedef enum {
39         CMD_PING = 0,
40         CMD_VCC_VALUE = 1,
41         CMD_VCC_ALARM = 2,
42         CMD_CLICK_DELAY = 3,
43         CMD_ALARM_LEVELS = 4,
44         CMD_RTC = 5,
45         CMD_DAY_ARCH = 6,
46         CMD_YEAR_ARCH = 7,
47         CMD_CUM_DOSE = 8,
48         CMD_INVOKE_BSL = 9
49 } TrinketCommand;
50
51 #define BEGIN_COMMAND   (0x1B)
52 #define END_COMMAND             (0x17)
53 #define ACK_ANSWER_OK   (0x06)
54 #define ACK_ANSWER_ERR  (0x15)
55 #define END_ANSWER              (0x17)
56
57 /* http://uglyduck.ath.cx/PDF/TexasInstruments/MSP430/usblib430/Programmers_Guide_MSP430_USB_API.pdf */
58 #define REPORT_ID               (0x3f)
59 typedef struct packed_struct {
60         uint8_t reportId;
61         uint8_t size;
62 } ProtoHeader;
63
64
65 #define DEV_TIMEOUT             (5000 /* milliseconds */)
66 typedef struct {
67         /*
68          * Command desc
69          */
70         uint8_t                 cmdId;
71         uint8_t                 cmdLen; /* 0 means variable, > 0 fixed */
72
73         /*
74          * Answer desc
75          */
76         uint32_t                respLen; /* 0 means variable, > 0 fixed */
77 } TrinketProtoDesc;
78
79 /* hardcoded knowledge from  docs/geyger.pdf */
80 static const TrinketProtoDesc protoDesc[] = {
81         {CMD_PING,                      4,              5                               },
82         {CMD_VCC_VALUE,         4,              8                               },
83         {CMD_VCC_ALARM,         8,              8                               },
84         {CMD_CLICK_DELAY,       8,              8                               },
85         {CMD_ALARM_LEVELS,      24,             24                              },
86         {CMD_RTC,                       13,             13                              },
87         {CMD_DAY_ARCH,          4,              4 + 1440 * 2    },
88         {CMD_YEAR_ARCH,         4,              4 + 5840 * 2    },
89         {CMD_CUM_DOSE,          4,              12                              },
90         {CMD_INVOKE_BSL,        4,              0                               }
91 };
92
93 static hid_device *trinketDev = NULL;
94
95 #if 0
96 static void
97 dumpBuf(char *msg, uint8_t *buf, int len) {
98         int i;
99
100         fprintf(stderr,"%s:", msg);
101         for (i=0; i<len; i++)
102                 fprintf(stderr, " %02x", buf[i]);
103         fprintf(stderr, "\n");
104 }
105 #endif
106
107 TrinketErrorCode
108 trinketOpen() {
109         TrinketErrorCode        r;
110         int i;
111
112         for(i = 0; i < sizeof(protoDesc) / sizeof(*protoDesc); i++)
113                 tassert(i == protoDesc[i].cmdId);
114
115         trinketDev = hid_open(0x2047, 0x0301, NULL);
116
117         if (trinketDev == NULL) {
118                 tlog(TL_WARN, "hid_open fails");
119                 return ERR_ERROR;
120         }
121
122         if ((r = trinketPing()) != ERR_OK)
123                 trinketClose();
124         return r;
125 }
126
127 void
128 trinketClose() {
129         if (trinketDev)
130                 hid_close(trinketDev);
131         trinketDev = NULL;
132 }
133
134 TrinketErrorCode
135 trinketPing() {
136         const TrinketProtoDesc  *proto = &protoDesc[CMD_PING];
137         struct packed_struct {
138                 ProtoHeader header;
139                 uint8_t         begin;
140                 uint8_t         len;
141                 uint8_t         cmd;
142                 uint8_t         end;
143         } cmd = {
144                                         {REPORT_ID, proto->cmdLen},
145                                         BEGIN_COMMAND,
146                                         proto->cmdLen,
147                                         proto->cmdId,
148                                         END_COMMAND
149         };
150         struct packed_struct {
151                 ProtoHeader header;
152                 uint8_t         begin;
153                 uint16_t        len;
154                 uint8_t         payload /* = 0 */;
155                 uint8_t         end;
156         } resp;
157         int     r;
158
159         tassert(proto->cmdId == CMD_PING);
160         tassert(sizeof(cmd) == cmd.len + sizeof(ProtoHeader));
161         tassert(sizeof(resp) == proto->respLen + sizeof(ProtoHeader));
162
163         r = hid_write(trinketDev, (unsigned char*)&cmd, sizeof(cmd));
164
165         if (r < 0 || r != sizeof(cmd)) {
166                 tlog(TL_WARN, "hid_write  returns %d instead of %u", r, cmd.len);
167                 return ERR_ERROR;
168         }
169
170         /* must be assigned to 0x3F by the host */
171         resp.header.reportId = REPORT_ID;
172
173         r = hid_read_timeout(trinketDev, (unsigned char*)&resp, sizeof(resp), DEV_TIMEOUT);
174         if (r < 0) {
175                 tlog(TL_WARN, "hid_read_timeout fails");
176                 return  ERR_NO_ANSWER;
177         }
178
179         if (r != sizeof(resp)) {
180                 tlog(TL_WARN, "hid_read_timeout  returns %d instead of %u", r, (unsigned int)sizeof(resp));
181                 return ERR_ERROR;
182         }
183
184         if (resp.header.reportId != REPORT_ID) {
185                 tlog(TL_WARN, "hid_read_timeout  returns report id %02x instead of %02x", resp.header.reportId, REPORT_ID);
186                 return ERR_ERROR;
187         }
188
189         if (resp.header.size != proto->respLen) {
190                 tlog(TL_WARN, "hid_read_timeout returns length %u intsead if %u", resp.header.size, proto->respLen);
191                 return ERR_PROTO;
192         }
193
194         if (resp.len != proto->respLen) {
195                 tlog(TL_WARN, "hid_read_timeout response length %u instead of %u", resp.len, proto->respLen);
196                 return ERR_PROTO;
197         }
198
199         if (!(resp.begin == ACK_ANSWER_OK && resp.payload == 0 && resp.end == END_ANSWER)) {
200                 tlog(TL_WARN, "hid_read_timeout  ACK %02x with payload %u and ETB %02x", resp.begin, resp.payload, resp.end);
201                 return ERR_PROTO;
202         }
203
204         return ERR_OK;
205 }
206
207 TrinketErrorCode
208 trinketGetCumDose(double *value) {
209         const TrinketProtoDesc  *proto = &protoDesc[CMD_CUM_DOSE];
210         struct {
211                 ProtoHeader header;
212                 uint8_t         begin;
213                 uint8_t         len;
214                 uint8_t         cmd;
215                 uint8_t         end;
216         } cmd = {
217                                         {REPORT_ID, proto->cmdLen},
218                                         BEGIN_COMMAND,
219                                         proto->cmdLen,
220                                         proto->cmdId,
221                                         END_COMMAND
222         };
223         struct packed_struct {
224                 ProtoHeader header;
225                 uint8_t         begin;
226                 uint16_t        len;
227                 double          payload;
228                 uint8_t         end;
229         } resp;
230         int     r;
231
232         tassert(proto->cmdId == CMD_CUM_DOSE);
233         tassert(sizeof(cmd) == cmd.len + sizeof(ProtoHeader));
234         tassert(sizeof(resp) == proto->respLen + sizeof(ProtoHeader));
235
236         r = hid_write(trinketDev, (unsigned char*)&cmd, sizeof(cmd));
237
238         if (r < 0 || r != sizeof(cmd)) {
239                 tlog(TL_WARN, "hid_send_feature_report  returns %d instead of %u", r, cmd.len);
240                 return ERR_ERROR;
241         }
242
243         /* must be assigned to 0x3F by the host */
244         resp.header.reportId = REPORT_ID;
245
246         r = hid_read_timeout(trinketDev, (unsigned char*)&resp, sizeof(resp), DEV_TIMEOUT);
247         if (r < 0) {
248                 tlog(TL_WARN, "hid_read_timeout fails");
249                 return  ERR_NO_ANSWER;
250         }
251
252         if (r != sizeof(resp)) {
253                 tlog(TL_WARN, "hid_read_timeout  returns %d instead of %u", r, (unsigned int)sizeof(resp));
254                 return ERR_ERROR;
255         }
256
257         if (resp.header.reportId != REPORT_ID) {
258                 tlog(TL_WARN, "hid_read_timeout  returns report id %02x instead of %02x", resp.header.reportId, REPORT_ID);
259                 return ERR_ERROR;
260         }
261
262         if (resp.header.size != proto->respLen) {
263                 tlog(TL_WARN, "hid_read_timeout returns length %u intsead if %u", resp.header.size, proto->respLen);
264                 return ERR_PROTO;
265         }
266
267         if (resp.len != proto->respLen) {
268                 tlog(TL_WARN, "hid_read_timeout response length %u instead of %u", resp.len, proto->respLen);
269                 return ERR_PROTO;
270         }
271
272         if (!(resp.begin == ACK_ANSWER_OK && resp.end == END_ANSWER)) {
273                 tlog(TL_WARN, "hid_read_timeout  ACK %02x and ETB %02x", resp.begin, resp.end);
274                 return ERR_PROTO;
275         }
276
277         *value = resp.payload;
278
279         return ERR_OK;
280 }