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