first
authorTeodor Sigaev <teodor@sigaev.ru>
Sun, 3 Apr 2016 15:46:07 +0000 (18:46 +0300)
committerTeodor Sigaev <teodor@sigaev.ru>
Sun, 3 Apr 2016 15:46:07 +0000 (18:46 +0300)
Makefile [new file with mode: 0644]
geyger.pdf [new file with mode: 0644]
main.c [new file with mode: 0644]
trinket.c [new file with mode: 0644]
trinket.h [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..b56a75c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,30 @@
+CC=cc
+INCLUDE=-I/usr/local/include -I.
+LIB=-L/usr/local/lib -lhidapi
+ifndef OS
+OS=$(shell uname)
+endif
+
+CFLAGS=-Wall -pedantic -std=c99 -O2 -g -DASSERT_CORE
+ifeq ($(OS), FreeBSD)
+endif
+
+ifeq ($(OS), Linux)
+CFLAGS+= -D_GNU_SOURCE -D_LARGE_FILES -D_FILE_OFFSET_BITS=64
+endif
+
+OBJ=trinket.o main.o
+
+.SUFFIXES: .o.c
+
+.c.o:
+       $(CC) $(CFLAGS) $(INCLUDE) -c $<
+
+all: trinketd
+
+trinketd: $(OBJ)
+        $(CC) -o $@  $(OBJ) $(LIB)
+
+clean:
+       rm -rf *core *.o trinketd
+
diff --git a/geyger.pdf b/geyger.pdf
new file mode 100644 (file)
index 0000000..a858d35
Binary files /dev/null and b/geyger.pdf differ
diff --git a/main.c b/main.c
new file mode 100644 (file)
index 0000000..4b2aab5
--- /dev/null
+++ b/main.c
@@ -0,0 +1,222 @@
+/*-------------------------------------------------------------------------
+ * 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;
+}
diff --git a/trinket.c b/trinket.c
new file mode 100644 (file)
index 0000000..6ff5e4d
--- /dev/null
+++ b/trinket.c
@@ -0,0 +1,277 @@
+/*-------------------------------------------------------------------------
+ * 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;
+}
diff --git a/trinket.h b/trinket.h
new file mode 100644 (file)
index 0000000..b5bb5ca
--- /dev/null
+++ b/trinket.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ * 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