From: Teodor Sigaev Date: Sun, 3 Apr 2016 15:46:07 +0000 (+0300) Subject: first X-Git-Url: http://sigaev.ru/git/gitweb.cgi?p=trinked.git;a=commitdiff_plain;h=d4790deef78ab124caa9a32ab12743941c765fdf first --- d4790deef78ab124caa9a32ab12743941c765fdf diff --git a/Makefile b/Makefile new file mode 100644 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 index 0000000..a858d35 Binary files /dev/null and b/geyger.pdf differ diff --git a/main.c b/main.c new file mode 100644 index 0000000..4b2aab5 --- /dev/null +++ b/main.c @@ -0,0 +1,222 @@ +/*------------------------------------------------------------------------- + * main.c + * + * Copyright (c) 2016, Teodor Sigaev + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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 "); + 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 index 0000000..6ff5e4d --- /dev/null +++ b/trinket.c @@ -0,0 +1,277 @@ +/*------------------------------------------------------------------------- + * trinket.c + * + * Copyright (c) 2016, Teodor Sigaev + * + * 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 +#include +#include +#include +#include + +#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; icmdLen}, + 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 index 0000000..b5bb5ca --- /dev/null +++ b/trinket.h @@ -0,0 +1,43 @@ +/*------------------------------------------------------------------------- + * trinket.h + * + * Copyright (c) 2016, Teodor Sigaev + * + * 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