+/*-------------------------------------------------------------------------
+ * trinket.c
+ *
+ * Copyright (c) 2016, Teodor Sigaev <teodor@sigaev.ru>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *-------------------------------------------------------------------------
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <hidapi/hidapi.h>
+#include <trinket.h>
+
+#define packed_struct __attribute__((packed))
+
+typedef enum {
+ CMD_PING = 0,
+ CMD_VCC_VALUE = 1,
+ CMD_VCC_ALARM = 2,
+ CMD_CLICK_DELAY = 3,
+ CMD_ALARM_LEVELS = 4,
+ CMD_RTC = 5,
+ CMD_DAY_ARCH = 6,
+ CMD_YEAR_ARCH = 7,
+ CMD_CUM_DOSE = 8,
+ CMD_INVOKE_BSL = 9
+} TrinketCommand;
+
+#define BEGIN_COMMAND (0x1B)
+#define END_COMMAND (0x17)
+#define ACK_ANSWER_OK (0x06)
+#define ACK_ANSWER_ERR (0x15)
+#define END_ANSWER (0x17)
+
+/* http://uglyduck.ath.cx/PDF/TexasInstruments/MSP430/usblib430/Programmers_Guide_MSP430_USB_API.pdf */
+#define REPORT_ID (0x3f)
+typedef struct packed_struct {
+ uint8_t reportId;
+ uint8_t size;
+} ProtoHeader;
+
+
+#define DEV_TIMEOUT (5000 /* milliseconds */)
+typedef struct {
+ /*
+ * Command desc
+ */
+ uint8_t cmdId;
+ uint8_t cmdLen; /* 0 means variable, > 0 fixed */
+
+ /*
+ * Answer desc
+ */
+ uint32_t respLen; /* 0 means variable, > 0 fixed */
+} TrinketProtoDesc;
+
+static const TrinketProtoDesc protoDesc[] = {
+ {CMD_PING, 4, 5 },
+ {CMD_VCC_VALUE, 4, 8 },
+ {CMD_VCC_ALARM, 8, 8 },
+ {CMD_CLICK_DELAY, 8, 8 },
+ {CMD_ALARM_LEVELS, 24, 24 },
+ {CMD_RTC, 13, 13 },
+ {CMD_DAY_ARCH, 4, 4 + 1440 * 2 },
+ {CMD_YEAR_ARCH, 4, 4 + 5840 * 2 },
+ {CMD_CUM_DOSE, 4, 12 },
+ {CMD_INVOKE_BSL, 4, 0 }
+};
+
+static hid_device *trinketDev = NULL;
+
+#if 0
+static void
+dumpBuf(char *msg, uint8_t *buf, int len) {
+ int i;
+
+ fprintf(stderr,"%s:", msg);
+ for (i=0; i<len; i++)
+ fprintf(stderr, " %02x", buf[i]);
+ fprintf(stderr, "\n");
+}
+#endif
+
+TrinketErrorCode
+trinketOpen() {
+#ifndef NDEBUG
+ int i;
+
+ for(i = 0; i < sizeof(protoDesc) / sizeof(*protoDesc); i++)
+ assert(i == protoDesc[i].cmdId);
+#endif
+
+ trinketDev = hid_open(0x2047, 0x0301, NULL);
+
+ if (trinketDev == NULL) {
+ fprintf(stderr, "hid_open fails\n");
+ return ERR_ERROR;
+ }
+
+ return trinketPing();
+}
+
+void
+trinketClose() {
+ if (trinketDev)
+ hid_close(trinketDev);
+ trinketDev = NULL;
+}
+
+TrinketErrorCode
+trinketPing() {
+ const TrinketProtoDesc *proto = &protoDesc[CMD_PING];
+ struct packed_struct {
+ ProtoHeader header;
+ uint8_t begin;
+ uint8_t len;
+ uint8_t cmd;
+ uint8_t end;
+ } cmd = {
+ {REPORT_ID, proto->cmdLen},
+ BEGIN_COMMAND,
+ proto->cmdLen,
+ proto->cmdId,
+ END_COMMAND
+ };
+ struct packed_struct {
+ ProtoHeader header;
+ uint8_t begin;
+ uint16_t len;
+ uint8_t payload /* = 0 */;
+ uint8_t end;
+ } resp;
+ int r;
+
+ assert(proto->cmdId == CMD_PING);
+ assert(sizeof(cmd) == cmd.len + sizeof(ProtoHeader));
+ assert(sizeof(resp) == proto->respLen + sizeof(ProtoHeader));
+
+ r = hid_write(trinketDev, (unsigned char*)&cmd, sizeof(cmd));
+
+ if (r < 0 || r != sizeof(cmd)) {
+ fprintf(stderr, "hid_write returns %d instead of %u\n", r, cmd.len);
+ return ERR_ERROR;
+ }
+
+ /* must be assigned to 0x3F by the host */
+ resp.header.reportId = REPORT_ID;
+
+ r = hid_read_timeout(trinketDev, (unsigned char*)&resp, sizeof(resp), DEV_TIMEOUT);
+ if (r < 0) {
+ fprintf(stderr, "hid_read_timeout fails\n");
+ return ERR_NO_ANSWER;
+ }
+
+ if (r != sizeof(resp)) {
+ fprintf(stderr, "hid_read_timeout returns %d instead of %u\n", r, sizeof(resp));
+ return ERR_ERROR;
+ }
+
+ if (resp.header.reportId != REPORT_ID) {
+ fprintf(stderr, "hid_read_timeout returns report id %02x instead of %02x\n", resp.header.reportId, REPORT_ID);
+ return ERR_ERROR;
+ }
+
+ if (resp.header.size != proto->respLen) {
+ fprintf(stderr, "hid_read_timeout returns length %u intsead if %u\n", resp.header.size, proto->respLen);
+ return ERR_PROTO;
+ }
+
+ if (resp.len != proto->respLen) {
+ fprintf(stderr, "hid_read_timeout response length %u instead of %u\n", resp.len, proto->respLen);
+ return ERR_PROTO;
+ }
+
+ if (!(resp.begin == ACK_ANSWER_OK && resp.payload == 0 && resp.end == END_ANSWER)) {
+ fprintf(stderr, "hid_read_timeout ACK %02x with payload %u and ETB %02x\n", resp.begin, resp.payload, resp.end);
+ return ERR_PROTO;
+ }
+
+ return ERR_OK;
+}
+
+TrinketErrorCode
+trinketGetCumDose(double *value) {
+ const TrinketProtoDesc *proto = &protoDesc[CMD_CUM_DOSE];
+ struct {
+ ProtoHeader header;
+ uint8_t begin;
+ uint8_t len;
+ uint8_t cmd;
+ uint8_t end;
+ } cmd = {
+ {REPORT_ID, proto->cmdLen},
+ BEGIN_COMMAND,
+ proto->cmdLen,
+ proto->cmdId,
+ END_COMMAND
+ };
+ struct packed_struct {
+ ProtoHeader header;
+ uint8_t begin;
+ uint16_t len;
+ double payload;
+ uint8_t end;
+ } resp;
+ int r;
+
+ assert(proto->cmdId == CMD_CUM_DOSE);
+ assert(sizeof(cmd) == cmd.len + sizeof(ProtoHeader));
+ assert(sizeof(resp) == proto->respLen + sizeof(ProtoHeader));
+
+ r = hid_write(trinketDev, (unsigned char*)&cmd, sizeof(cmd));
+
+ if (r < 0 || r != sizeof(cmd)) {
+ fprintf(stderr, "hid_send_feature_report returns %d instead of %u\n", r, cmd.len);
+ return ERR_ERROR;
+ }
+
+ /* must be assigned to 0x3F by the host */
+ resp.header.reportId = REPORT_ID;
+
+ r = hid_read_timeout(trinketDev, (unsigned char*)&resp, sizeof(resp), DEV_TIMEOUT);
+ if (r < 0) {
+ fprintf(stderr, "hid_read_timeout fails\n");
+ return ERR_NO_ANSWER;
+ }
+
+ if (r != sizeof(resp)) {
+ fprintf(stderr, "hid_read_timeout returns %d instead of %u\n", r, sizeof(resp));
+ return ERR_ERROR;
+ }
+
+ if (resp.header.reportId != REPORT_ID) {
+ fprintf(stderr, "hid_read_timeout returns report id %02x instead of %02x\n", resp.header.reportId, REPORT_ID);
+ return ERR_ERROR;
+ }
+
+ if (resp.header.size != proto->respLen) {
+ fprintf(stderr, "hid_read_timeout returns length %u intsead if %u\n", resp.header.size, proto->respLen);
+ return ERR_PROTO;
+ }
+
+ if (resp.len != proto->respLen) {
+ fprintf(stderr, "hid_read_timeout response length %u instead of %u\n", resp.len, proto->respLen);
+ return ERR_PROTO;
+ }
+
+ if (!(resp.begin == ACK_ANSWER_OK && resp.end == END_ANSWER)) {
+ fprintf(stderr, "hid_read_timeout ACK %02x and ETB %02x\n", resp.begin, resp.end);
+ return ERR_PROTO;
+ }
+
+ *value = resp.payload;
+
+ return ERR_OK;
+}