--- /dev/null
+/*-------------------------------------------------------------------------
+ * main.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 <fcntl.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <hidapi/hidapi.h>
+#include <sys/param.h>
+
+#include <trinket.h>
+
+static char* dbdir = NULL;
+static int period = (150);
+static char* pidfile = NULL;
+static char* logfile = NULL;
+static bool daemonize = false;
+static char tfile[MAXPATHLEN],
+ ofile[MAXPATHLEN];
+
+static const double coefficient = 1.0;
+
+static bool stopRequest = false;
+
+static void
+sig_int(int sig) {
+ stopRequest = true;
+}
+
+static void
+usage(const char *errmsg) {
+ puts("trinketd - collecting info from AtomPro");
+ puts("Copyright (c) 2016, Teodor Sigaev <teodor@sigaev.ru>");
+ puts("trinketd [-d] [-l logfile] [-p pidfile] [-P period] [-D datadir]");
+
+ if (errmsg) {
+ puts("");
+ puts(errmsg);
+ }
+
+ exit(1);
+}
+
+static void
+main_loop() {
+ double prevDose,
+ curDose;
+
+restart:
+ prevDose = -1;
+
+ if (trinketOpen() != ERR_OK) {
+ fprintf(stderr, "trinketOpen fails\n");
+ sleep(1);
+ goto restart;
+ }
+
+ while(42 && stopRequest == false) {
+ if (trinketGetCumDose(&curDose) != ERR_OK) {
+ fprintf(stderr, "trinketGetCumDose fails\n");
+ trinketClose();
+ sleep(1);
+ goto restart;
+ }
+
+ if (prevDose >= 0.0) {
+ double counts = curDose - prevDose;
+ double radlevel = coefficient * counts / (double)period;
+ FILE *fh = stdout;
+ bool successOpen = true;
+
+ if (daemonize) {
+ if ((fh = fopen(tfile, "w")) == NULL) {
+ fprintf(stderr, "fopen fails\n");
+ successOpen = false;
+ }
+ }
+
+ if (successOpen) {
+ fprintf(fh, "counts: %lld\n", (long long)counts);
+ fprintf(fh, "radlevel: %e\n", radlevel);
+ }
+
+ if (fh != stdout) {
+ fflush(fh);
+ fclose(fh);
+ rename(tfile, ofile);
+ }
+ }
+
+ prevDose = curDose;
+ sleep(period);
+ }
+
+ trinketClose();
+}
+
+
+extern char *optarg;
+extern int opterr, optind;
+
+int
+main(int argn, char* argv[]) {
+ int i;
+ int pidfd,
+ logfd;
+ struct sigaction sa;
+
+ opterr = 0;
+ while((i=getopt(argn,argv,"dD:p:P:h")) != EOF) {
+ switch(i) {
+ case 'd':
+ daemonize = true;
+ break;
+ case 'D':
+ dbdir = strdup(optarg);
+ break;
+ case 'p':
+ pidfile = strdup(optarg);
+ break;
+ case 'P':
+ period = atoi(optarg);
+ break;
+ case 'h':
+ default:
+ usage(NULL);
+ }
+ }
+
+ if (opterr || optind != argn)
+ usage("argument err");
+
+ if (period <= 0)
+ usage("Collecting period could not be zero nor negative");
+
+ if (daemonize && !dbdir)
+ usage("trinketd: it is useless to use -d without -D");
+
+ if (dbdir && strlen(dbdir) > MAXPATHLEN - 32)
+ usage("datadir is too long");
+
+ if (pidfile) {
+ pidfd = open(pidfile, O_CREAT | O_WRONLY | O_TRUNC, 0666);
+ if (pidfd < 0)
+ usage("could not write pidfile");
+ }
+
+ if (logfile) {
+ logfd = open(logfile, O_CREAT | O_WRONLY | O_APPEND, 0666);
+ if (logfd < 0)
+ usage("could not write logfile");
+ }
+
+ if (daemonize) {
+ if (daemon(0, 0) == -1)
+ usage("could not daemonize");
+ }
+
+ if (pidfile) {
+ char pid[64];
+
+ snprintf(pid, sizeof(pid), "%lld", (long long)getpid());
+ if (write(pidfd, pid, strlen(pid)) != strlen(pid))
+ usage("could not write pid");
+ close(pidfd);
+ }
+
+ if (logfile) {
+ dup2(logfd, fileno(stderr));
+ close(logfd);
+ }
+
+ if (hid_init() < 0) {
+ fprintf(stderr, "hid_init fails\n");
+ exit(1);
+ }
+
+ if (dbdir) {
+ snprintf(tfile, MAXPATHLEN, "%s/trinket.tmp", dbdir);
+ snprintf(ofile, MAXPATHLEN, "%s/trinket.dat", dbdir);
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = sig_int;
+ sigaction(SIGINT, &sa, 0);
+ sigaction(SIGTERM, &sa, 0);
+ sigaction(SIGHUP, &sa, 0);
+
+ main_loop();
+
+ hid_exit();
+ return 0;
+}
--- /dev/null
+/*-------------------------------------------------------------------------
+ * 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;
+}
--- /dev/null
+/*-------------------------------------------------------------------------
+ * trinket.h
+ *
+ * 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.
+ *-------------------------------------------------------------------------
+ */
+#ifndef TRINKET_H
+#define TRINKET_H
+
+typedef enum {
+ ERR_OK = 0,
+ ERR_NO_ANSWER = 1,
+ ERR_ERROR = 2,
+ ERR_PROTO = 3
+} TrinketErrorCode;
+
+TrinketErrorCode trinketOpen();
+void trinketClose();
+TrinketErrorCode trinketPing();
+TrinketErrorCode trinketGetCumDose(double *value);
+
+#endif