]> mj.ucw.cz Git - arexx.git/commitdiff
Daemon mode
authorMartin Mares <mj@ucw.cz>
Tue, 27 Dec 2011 13:45:19 +0000 (14:45 +0100)
committerMartin Mares <mj@ucw.cz>
Tue, 27 Dec 2011 13:45:19 +0000 (14:45 +0100)
Makefile
TODO
arexx.c [deleted file]
arexxd.c [new file with mode: 0644]

index 04cfded65c779edee889e0d53f196c8c798e9ada..6d5a256247018557e4418e02d29516bb410e5a4d 100644 (file)
--- 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 1eef6b0c7fb03c15539f5a54a4197ba17aa89721..c5b8d9f1c5f897513740bf5305a4abf74437c12d 100644 (file)
--- 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 (file)
index 796d13a..0000000
--- a/arexx.c
+++ /dev/null
@@ -1,384 +0,0 @@
-/*
- *     Linux Interfece for Arexx Data Loggers
- *
- *     (c) 2011 Martin Mares <mj@ucw.cz>
- */
-
-#include <stdio.h>
-#include <stdarg.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <math.h>
-#include <time.h>
-#include <sys/stat.h>
-#include <libusb-1.0/libusb.h>
-#include <rrd.h>
-
-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 "<deviceinfo>". 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<devn; i++) {
-               struct libusb_device_descriptor desc;
-               libusb_device *dev = devlist[i];
-               if (!libusb_get_device_descriptor(dev, &desc)) {
-                       if (desc.idVendor == 0x0451 && desc.idProduct == 0x3211) {
-                               printf("Arexx USB receiver found at usb%d.%d\n", libusb_get_bus_number(dev), libusb_get_device_address(dev));
-                               int err;
-                               if (err = libusb_open(dev, &devh))
-                                       die("libusb_open() failed: error %d\n", err);
-                               if (err = libusb_claim_interface(devh, 0))
-                                       die("libusb_claim_interface() failed: error %d\n", err);
-                               libusb_free_device_list(devlist, 1);
-                               return;
-                       }
-               }
-       }
-
-       die("No Arexx USB receiver found");
-}
-
-static void dump_packet(byte *pkt)
-{
-       for (int i=0; i<64; i++) {
-               if (!(i % 16))
-                       printf("\t%02x:", i);
-               printf(" %02x", pkt[i]);
-               if (i % 16 == 15)
-                       printf("\n");
-       }
-}
-
-static int send_and_receive(byte *req, byte *reply)
-{
-       int err, transferred;
-       if (err = libusb_bulk_transfer(devh, 0x01, req, 64, &transferred, 200)) {
-               fprintf(stderr, "Transmit error: %d\n", err);
-               return 0;
-       }
-       if (debug_packets) {
-               printf(">> 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 (file)
index 0000000..6e301a0
--- /dev/null
+++ b/arexxd.c
@@ -0,0 +1,528 @@
+/*
+ *     Linux Interfece for Arexx Data Loggers
+ *
+ *     (c) 2011 Martin Mares <mj@ucw.cz>
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <math.h>
+#include <time.h>
+#include <getopt.h>
+#include <syslog.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/stat.h>
+#include <libusb-1.0/libusb.h>
+#include <rrd.h>
+
+#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 "<deviceinfo>". 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<devn; i++) {
+               struct libusb_device_descriptor desc;
+               libusb_device *dev = devlist[i];
+               if (!libusb_get_device_descriptor(dev, &desc)) {
+                       if (desc.idVendor == 0x0451 && desc.idProduct == 0x3211) {
+                               log_info("Arexx USB receiver found at usb%d.%d", libusb_get_bus_number(dev), libusb_get_device_address(dev));
+                               int err;
+                               if (err = libusb_open(dev, &devh)) {
+                                       log_error("libusb_open() failed: error %d", err);
+                                       goto failed;
+                               }
+                               if (err = libusb_claim_interface(devh, 0)) {
+                                       log_error("libusb_claim_interface() failed: error %d", err);
+                                       libusb_close(devh);
+                                       goto failed;
+                               }
+                               libusb_free_device_list(devlist, 1);
+                               return 1;
+                       }
+               }
+       }
+
+failed:
+       libusb_free_device_list(devlist, 1);
+       return 0;
+}
+
+static void release_device(void)
+{
+       libusb_close(devh);
+       devh = NULL;
+}
+
+static void dump_packet(byte *pkt)
+{
+       for (int i=0; i<64; i++) {
+               if (!(i % 16))
+                       log_pkt("\t%02x:", i);
+               log_pkt(" %02x", pkt[i]);
+               if (i % 16 == 15)
+                       log_pkt("\n");
+       }
+}
+
+static int send_and_receive(byte *req, byte *reply)
+{
+       int err, transferred;
+       if (err = libusb_bulk_transfer(devh, 0x01, req, 64, &transferred, 200)) {
+               if (err == LIBUSB_ERROR_TIMEOUT) {
+                       log_pkt(">> 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;
+}