From: Martin Mares Date: Tue, 27 Dec 2011 13:45:19 +0000 (+0100) Subject: Daemon mode X-Git-Tag: v1.0~8 X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=0911a39b28f734a789cb87506b1307312ae31f66;p=arexx.git Daemon mode --- diff --git a/Makefile b/Makefile index 04cfded..6d5a256 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,8 @@ LD=gcc CFLAGS=-O2 -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Wundef -Wredundant-decls -std=gnu99 LDLIBS=-lusb-1.0 -lm -lrrd -all: arexx +all: arexxd clean: rm -f `find . -name "*~" -or -name "*.[oa]" -or -name "\#*\#" -or -name TAGS -or -name core -or -name .depend -or -name .#*` - rm -f arexx + rm -f arexxd diff --git a/TODO b/TODO index 1eef6b0..c5b8d9f 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,2 @@ -- Print of messages -- use syslog? -- Handle disconnect and reconnect of the receiver -- Periodical synchronization of time - Multiple receivers? +- Logging of min/max temperatures diff --git a/arexx.c b/arexx.c deleted file mode 100644 index 796d13a..0000000 --- a/arexx.c +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Linux Interfece for Arexx Data Loggers - * - * (c) 2011 Martin Mares - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -typedef unsigned char byte; -static libusb_context *usb_ctxt; -static libusb_device_handle *devh; - -static int debug_packets = 0; -static int debug_raw_data = 0; - -#define TIME_OFFSET 946681200 // Timestamp of 2000-01-01 00:00:00 - -static void die(char *msg, ...) -{ - va_list args; - va_start(args, msg); - vfprintf(stderr, msg, args); - fputc('\n', stderr); - va_end(args); - exit(1); -} - -/*** RRD interface ***/ - -#define RRD_NAME "/tmp/power.rrd" -#define SLOT_SIZE 10 // 10 seconds per averaging slot -#define MAX_ARGS 20 -#define MAX_ARG_SIZE 1024 - -static int arg_cnt; -static char *arg_ptr[MAX_ARGS+1]; -static char arg_buf[MAX_ARG_SIZE]; -static int arg_pos; - -static void arg_new(void) -{ - arg_cnt = 1; - arg_pos = 0; - arg_ptr[0] = "rrdtool"; -} - -static void arg_push(const char *fmt, ...) -{ - if (arg_cnt >= MAX_ARGS) - die("MAX_ARGS exceeded"); - va_list va; - va_start(va, fmt); - int len = 1 + vsnprintf(arg_buf + arg_pos, MAX_ARG_SIZE - arg_pos, fmt, va); - if (arg_pos + len > MAX_ARG_SIZE) - die("MAX_ARG_SIZE exceeded"); - arg_ptr[arg_cnt++] = arg_buf + arg_pos; - arg_ptr[arg_cnt] = NULL; - arg_pos += len; -} - -static void rrd_point(time_t t, int id, double val) -{ - char rr_name[256]; - snprintf(rr_name, sizeof(rr_name), "sensor-%d.rrd", id); - - struct stat st; - if (stat(rr_name, &st) < 0 || !st.st_size) { - // We have to create the RRD - printf("### Creating %s\n", rr_name); - arg_new(); - arg_push(rr_name); - arg_push("--start"); - arg_push("%d", (int) time(NULL) - 28*86400); - arg_push("--step"); - arg_push("60"); - arg_push("DS:temp:GAUGE:300:0:10000"); // Anything over 10 kW is considered a ghost - arg_push("RRA:AVERAGE:0.25:1:20160"); // Last 14 days with full resolution - arg_push("RRA:AVERAGE:0.25:60:88800"); // Last 10 years with 1h resolution - rrd_create(arg_cnt, arg_ptr); - if (rrd_test_error()) - die("rrd_create failed: %s", rrd_get_error()); - } - - arg_new(); - arg_push(rr_name); - arg_push("%d:%f", t, val); - rrd_update(arg_cnt, arg_ptr); - if (rrd_test_error()) - printf("### rrd_update failed: %s\n", rrd_get_error()); -} - -/*** Transforms ***/ - -static void cooked_point(time_t t, int id, double val, char *unit, int q) -{ - struct tm tm; - localtime_r(&t, &tm); - - char tbuf[64]; - strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", &tm); - - printf("== %s id=%d val=%.3f unit=%s q=%d\n", tbuf, id, val, unit, q); - - rrd_point(t, id, val); -} - -static void raw_point(int t, int id, int raw, int q) -{ - /* - * The binary blob provided by Arexx contains an embedded XML fragment - * with descriptions of all known sensor types. If you want to see it, - * grep the blob for "". The meanings of the parameters are - * as follows: - * - * m1, m2 Device type matches if (raw_sensor_id & m1) == m2 - * type Unit measured by the sensor (1=Celsius, 2=RH%, 3=CO2 ppm) - * dm User-visible sensor ID = raw_sensor_id & dm - * i 1 if the raw value is signed - * p[] Coefficients of transformation polynomial (x^0 first) - * vLo, vUp Upper and lower bound on the final value - * scale Scaling function: - * 0 = identity (default) - * 1 = 10^x - * 2 = exp(x) - * 3 = (x < 0) ? 0 : log10(x) - * 4 = (x < 0) ? 0 : log(x) - * - * The raw values are transformed this way: - * - sign-extend if signed - * - apply the transformation polynomial - * - apply the scaling function - * - drop if outside the interval [vLo,vUp] - * - * This function applies the necessary transform for sensors we've - * seen in the wild. We deliberately ignore the "dm" parameter as we want - * to report different channels of a single sensor as multiple sensors. - */ - - double z = raw; - double hi, lo; - char *unit; - int idhi = id & 0xf000; - - if (idhi == 0x1000) { - z = 0.02*z - 273.15; - lo = -200; - hi = 600; - unit = "C"; - } else if (idhi == 0x2000) { - if (raw >= 0x8000) - z -= 0x10000; - z /= 128; - lo = -60; - hi = 125; - unit = "C"; - } else if (idhi == 0x4000) { - if (!(id & 1)) { - z = z/100 - 39.6; - lo = -60; - hi = 125; - unit = "C"; - } else { - z = -2.8e-6*z*z + 0.0405*z - 4; - lo = 0; - hi = 100.1; - unit = "%RH"; - } - } else if (idhi == 0x6000) { - if (!(id & 1)) { - if (raw >= 0x8000) - z -= 0x10000; - z /= 128; - lo = -60; - hi = 125; - unit = "C"; - } else { - z = -3.8123e-11*z; - z = (z + 1.9184e-7) * z; - z = (z - 1.0998e-3) * z; - z += 6.56; - z = pow(10, z); - lo = 0; - hi = 1e6; - unit = "ppm"; - } - } else { - printf("### Unknown sensor type 0x%04x\n", id); - return; - } - - if (z < lo || z > hi) { - printf("### Sensor %d: value %f out of range\n", id, z); - return; - } - - cooked_point(t + TIME_OFFSET, id, z, unit, q); -} - -/*** USB interface ***/ - -static void find_device(void) -{ - libusb_device **devlist; - ssize_t devn = libusb_get_device_list(usb_ctxt, &devlist); - if (devn < 0) - die("Cannot enumerate USB devices: error %d\n", (int) devn); - - for (ssize_t i=0; i> xmit %d bytes\n", transferred); - dump_packet(req); - } - if (err = libusb_bulk_transfer(devh, 0x81, reply, 64, &transferred, 200)) { - fprintf(stderr, "Receive error: %d\n", err); - return 0; - } - if (debug_packets) - printf("<< recv %d bytes\n", transferred); - while (transferred < 64) - reply[transferred++] = 0xff; - if (debug_packets) - dump_packet(reply); - return 1; -} - -static unsigned int get_be16(byte *p) -{ - return p[1] | (p[0] << 8); -} - -static unsigned int get_le16(byte *p) -{ - return p[0] | (p[1] << 8); -} - -static unsigned int get_le32(byte *p) -{ - return get_le16(p) | (get_le16(p+2) << 16); -} - -static void put_le16(byte *p, unsigned int x) -{ - p[0] = x; - p[1] = x >> 8; -} - -static void put_le32(byte *p, unsigned int x) -{ - put_le16(p, x); - put_le16(p+2, x>>16); -} - -static int parse_packet(byte *reply) -{ - if (reply[0]) { - printf("### Unknown packet type %02x\n", reply[0]); - return 0; - } - - int pos = 1; - int points = 0; - while (pos < 64) { - byte *p = reply + pos; - int len = p[0]; - if (!len || len == 0xff) - break; - if (len < 9 || len > 10) { - printf("### Unknown tuple length %02x\n", len); - break; - } - if (pos + len > 64) { - printf("### Tuple truncated\n"); - break; - } - int id = get_le16(p+1); - int raw = get_be16(p+3); - int t = get_le32(p+5); - int q = (len > 9) ? p[9] : -1; - if (debug_raw_data) { - printf("... %02x: id=%d raw=%d t=%d", len, id, raw, t); - if (len > 9) - printf(" q=%d", q); - printf("\n"); - } - raw_point(t, id, raw, q); - pos += len; - points++; - } - - return points; -} - -static void set_clock(void) -{ - puts("### Syncing time"); - - byte req[64], reply[64]; - memset(req, 0, 64); - req[0] = 4; - time_t t = time(NULL); - put_le32(req+1, t-TIME_OFFSET); - send_and_receive(req, reply); - -#if 0 - /* - * Original software also sends a packet with type 3 and the timestamp, - * but it does not make any sense, especially as they ignore the sensor - * readings in the answer. - */ - req[0] = 3; - send_and_receive(req, reply); - parse_packet(reply); -#endif -} - -int main(void) -{ - int err; - if (err = libusb_init(&usb_ctxt)) - die("Cannot initialize libusb: error %d", err); - libusb_set_debug(usb_ctxt, 3); - - find_device(); - set_clock(); - - for (;;) { - byte req[64], reply[64]; - memset(req, 0, sizeof(req)); - req[0] = 3; - if (send_and_receive(req, reply)) { - if (parse_packet(reply)) - continue; - } - sleep(4); - } - - return 0; -} diff --git a/arexxd.c b/arexxd.c new file mode 100644 index 0000000..6e301a0 --- /dev/null +++ b/arexxd.c @@ -0,0 +1,528 @@ +/* + * Linux Interfece for Arexx Data Loggers + * + * (c) 2011 Martin Mares + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_PATH "/var/log/arexxd" + +typedef unsigned char byte; +static libusb_context *usb_ctxt; +static libusb_device_handle *devh; + +static int use_syslog; +static int debug_mode; +static int debug_packets; +static int debug_raw_data; + +static void die(char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + if (use_syslog) + vsyslog(LOG_CRIT, fmt, args); + else { + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + } + va_end(args); + exit(1); +} + +static void log_error(char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + if (use_syslog) + vsyslog(LOG_ERR, fmt, args); + else { + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + } + va_end(args); +} + +static void log_info(char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + if (use_syslog) + vsyslog(LOG_INFO, fmt, args); + else { + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + } + va_end(args); +} + +static void log_pkt(char *fmt, ...) +{ + if (!debug_packets) + return; + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); +} + +/*** RRD interface ***/ + +#define SLOT_SIZE 10 // 10 seconds per averaging slot +#define MAX_ARGS 20 +#define MAX_ARG_SIZE 1024 + +static int arg_cnt; +static char *arg_ptr[MAX_ARGS+1]; +static char arg_buf[MAX_ARG_SIZE]; +static int arg_pos; + +static void arg_new(void) +{ + arg_cnt = 1; + arg_pos = 0; + arg_ptr[0] = "rrdtool"; +} + +static void arg_push(const char *fmt, ...) +{ + if (arg_cnt >= MAX_ARGS) + die("MAX_ARGS exceeded"); + va_list va; + va_start(va, fmt); + int len = 1 + vsnprintf(arg_buf + arg_pos, MAX_ARG_SIZE - arg_pos, fmt, va); + if (arg_pos + len > MAX_ARG_SIZE) + die("MAX_ARG_SIZE exceeded"); + arg_ptr[arg_cnt++] = arg_buf + arg_pos; + arg_ptr[arg_cnt] = NULL; + arg_pos += len; +} + +static void rrd_point(time_t t, int id, double val) +{ + char rr_name[256]; + snprintf(rr_name, sizeof(rr_name), "sensor-%d.rrd", id); + + struct stat st; + if (stat(rr_name, &st) < 0 || !st.st_size) { + // We have to create the RRD + log_info("Creating %s", rr_name); + arg_new(); + arg_push(rr_name); + arg_push("--start"); + arg_push("%d", (int) time(NULL) - 28*86400); + arg_push("--step"); + arg_push("60"); + arg_push("DS:temp:GAUGE:300:0:10000"); // Anything over 10 kW is considered a ghost + arg_push("RRA:AVERAGE:0.25:1:20160"); // Last 14 days with full resolution + arg_push("RRA:AVERAGE:0.25:60:88800"); // Last 10 years with 1h resolution + rrd_create(arg_cnt, arg_ptr); + if (rrd_test_error()) { + log_error("rrd_create on %s failed: %s", rr_name, rrd_get_error()); + return; + } + } + + arg_new(); + arg_push(rr_name); + arg_push("%d:%f", t, val); + rrd_update(arg_cnt, arg_ptr); + if (rrd_test_error()) + log_error("rrd_update on %s failed: %s", rr_name, rrd_get_error()); +} + +/*** Transforms ***/ + +#define TIME_OFFSET 946681200 // Timestamp of 2000-01-01 00:00:00 + +static void cooked_point(time_t t, int id, double val, char *unit, int q) +{ + struct tm tm; + localtime_r(&t, &tm); + + char tbuf[64]; + strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", &tm); + + if (debug_raw_data) + printf("== %s id=%d val=%.3f unit=%s q=%d\n", tbuf, id, val, unit, q); + + rrd_point(t, id, val); +} + +static void raw_point(int t, int id, int raw, int q) +{ + /* + * The binary blob provided by Arexx contains an embedded XML fragment + * with descriptions of all known sensor types. If you want to see it, + * grep the blob for "". The meanings of the parameters are + * as follows: + * + * m1, m2 Device type matches if (raw_sensor_id & m1) == m2 + * type Unit measured by the sensor (1=Celsius, 2=RH%, 3=CO2 ppm) + * dm User-visible sensor ID = raw_sensor_id & dm + * i 1 if the raw value is signed + * p[] Coefficients of transformation polynomial (x^0 first) + * vLo, vUp Upper and lower bound on the final value + * scale Scaling function: + * 0 = identity (default) + * 1 = 10^x + * 2 = exp(x) + * 3 = (x < 0) ? 0 : log10(x) + * 4 = (x < 0) ? 0 : log(x) + * + * The raw values are transformed this way: + * - sign-extend if signed + * - apply the transformation polynomial + * - apply the scaling function + * - drop if outside the interval [vLo,vUp] + * + * This function applies the necessary transform for sensors we've + * seen in the wild. We deliberately ignore the "dm" parameter as we want + * to report different channels of a single sensor as multiple sensors. + */ + + double z = raw; + double hi, lo; + char *unit; + int idhi = id & 0xf000; + + if (idhi == 0x1000) { + z = 0.02*z - 273.15; + lo = -200; + hi = 600; + unit = "C"; + } else if (idhi == 0x2000) { + if (raw >= 0x8000) + z -= 0x10000; + z /= 128; + lo = -60; + hi = 125; + unit = "C"; + } else if (idhi == 0x4000) { + if (!(id & 1)) { + z = z/100 - 39.6; + lo = -60; + hi = 125; + unit = "C"; + } else { + z = -2.8e-6*z*z + 0.0405*z - 4; + lo = 0; + hi = 100.1; + unit = "%RH"; + } + } else if (idhi == 0x6000) { + if (!(id & 1)) { + if (raw >= 0x8000) + z -= 0x10000; + z /= 128; + lo = -60; + hi = 125; + unit = "C"; + } else { + z = -3.8123e-11*z; + z = (z + 1.9184e-7) * z; + z = (z - 1.0998e-3) * z; + z += 6.56; + z = pow(10, z); + lo = 0; + hi = 1e6; + unit = "ppm"; + } + } else { + log_error("Unknown sensor type 0x%04x", id); + return; + } + + if (z < lo || z > hi) { + log_error("Sensor %d: value %f out of range", id, z); + return; + } + + cooked_point(t + TIME_OFFSET, id, z, unit, q); +} + +/*** USB interface ***/ + +static int find_device(void) +{ + libusb_device **devlist; + ssize_t devn = libusb_get_device_list(usb_ctxt, &devlist); + if (devn < 0) { + log_error("Cannot enumerate USB devices: error %d", (int) devn); + return 0; + } + + for (ssize_t i=0; i> xmit timed out\n"); + return 0; + } + log_error("Transmit error: %d", err); + return err; + } + if (debug_packets) { + log_pkt(">> xmit %d bytes\n", transferred); + dump_packet(req); + } + if (err = libusb_bulk_transfer(devh, 0x81, reply, 64, &transferred, 200)) { + if (err == LIBUSB_ERROR_TIMEOUT) { + log_pkt(">> recv timed out\n"); + return 0; + } + log_error("Receive error: %d", err); + return err; + } + if (debug_packets) + log_pkt("<< recv %d bytes\n", transferred); + while (transferred < 64) + reply[transferred++] = 0xff; + if (debug_packets) + dump_packet(reply); + return 1; +} + +static unsigned int get_be16(byte *p) +{ + return p[1] | (p[0] << 8); +} + +static unsigned int get_le16(byte *p) +{ + return p[0] | (p[1] << 8); +} + +static unsigned int get_le32(byte *p) +{ + return get_le16(p) | (get_le16(p+2) << 16); +} + +static void put_le16(byte *p, unsigned int x) +{ + p[0] = x; + p[1] = x >> 8; +} + +static void put_le32(byte *p, unsigned int x) +{ + put_le16(p, x); + put_le16(p+2, x>>16); +} + +static int parse_packet(byte *reply) +{ + if (reply[0]) { + log_error("Unknown packet type %02x", reply[0]); + return 0; + } + + int pos = 1; + int points = 0; + while (pos < 64) { + byte *p = reply + pos; + int len = p[0]; + if (!len || len == 0xff) + break; + if (len < 9 || len > 10) { + log_error("Unknown tuple length %02x", len); + break; + } + if (pos + len > 64) { + log_error("Tuple truncated"); + break; + } + int id = get_le16(p+1); + int raw = get_be16(p+3); + int t = get_le32(p+5); + int q = (len > 9) ? p[9] : -1; + if (debug_raw_data) { + printf("... %02x: id=%d raw=%d t=%d", len, id, raw, t); + if (len > 9) + printf(" q=%d", q); + printf("\n"); + } + raw_point(t, id, raw, q); + pos += len; + points++; + } + + return points; +} + +static void set_clock(void) +{ + log_info("Synchronizing receiver time"); + + byte req[64], reply[64]; + memset(req, 0, 64); + req[0] = 4; + time_t t = time(NULL); + put_le32(req+1, t-TIME_OFFSET); + send_and_receive(req, reply); + +#if 0 + /* + * Original software also sends a packet with type 3 and the timestamp, + * but it does not make any sense, especially as they ignore the sensor + * readings in the answer. + */ + req[0] = 3; + send_and_receive(req, reply); + parse_packet(reply); +#endif +} + +/*** Main ***/ + +static const struct option long_options[] = { + { "debug", 0, NULL, 'd' }, + { "log-packets", 0, NULL, 'p' }, + { NULL, 0, NULL, 0 }, +}; + +static void usage(void) +{ + fprintf(stderr, "Usage: arexxd [--debug] [--log-packets]\n"); + exit(1); +} + +int main(int argc, char **argv) +{ + int opt; + while ((opt = getopt_long(argc, argv, "dp", long_options, NULL)) >= 0) + switch (opt) { + case 'd': + debug_mode++; + break; + case 'p': + debug_packets++; + debug_raw_data++; + break; + default: + usage(); + } + if (optind < argc) + usage(); + + int err; + if (err = libusb_init(&usb_ctxt)) + die("Cannot initialize libusb: error %d", err); + // libusb_set_debug(usb_ctxt, 3); + + if (!debug_mode) { + if (chdir(LOG_PATH) < 0) + die("Cannot change directory to %s: %m", LOG_PATH); + if (debug_packets || debug_raw_data) { + close(1); + if (open("debug", O_WRONLY | O_CREAT | O_APPEND, 0666) < 0) + die("Cannot open debug log: %m"); + setlinebuf(stdout); + } + openlog("arexxd", LOG_NDELAY, LOG_DAEMON); + pid_t pid = fork(); + if (pid < 0) + die("fork() failed: %m"); + if (pid) + return 0; + setsid(); + use_syslog = 1; + } + + int inited = 0; + for (;;) { + if (!find_device()) { + if (!inited) { + inited = 1; + log_error("Receiver not connected, waiting until it appears"); + } + sleep(30); + continue; + } + + inited = 1; + + int sync_in = 0; + for (;;) { + if (!sync_in) { + set_clock(); + sync_in = 100; + } + byte req[64], reply[64]; + memset(req, 0, sizeof(req)); + req[0] = 3; + err = send_and_receive(req, reply); + if (err < 0) + break; + if (err > 0 && parse_packet(reply)) + continue; + sleep(4); + sync_in--; + } + + log_info("Disconnecting receiver"); + release_device(); + } + + return 0; +}