--- /dev/null
+/*
+ * 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 <libusb-1.0/libusb.h>
+
+typedef unsigned char byte;
+static libusb_context *usb_ctxt;
+static libusb_device_handle *devh;
+
+static int debug_packets = 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);
+}
+
+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 void raw_point(int t, int id, int raw)
+{
+ /*
+ * 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.
+ */
+
+ 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 ID 0x%04x\n", id);
+ return;
+ }
+
+ if (z < lo || z > hi) {
+ printf("### Sensor %d: value %f out of range\n", id, z);
+ return;
+ }
+
+ printf("\t-> %f %s\n", z, unit);
+}
+
+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 packet length %02x\n", len);
+ break;
+ }
+ if (pos + len > 64) {
+ printf("### Packet truncated\n");
+ break;
+ }
+ int id = get_le16(p+1);
+ int raw = get_be16(p+3);
+ int t = get_le32(p+5);
+ printf("... %02x: id=%d raw=%d t=%d", len, id, raw, t);
+ if (len > 9)
+ printf(" q=%d", p[9]);
+ printf("\n");
+ raw_point(t, id, raw);
+ 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
+ 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;
+}