/*
* Linux Interfece for Arexx Data Loggers
*
- * (c) 2011 Martin Mares <mj@ucw.cz>
+ * (c) 2011-2012 Martin Mares <mj@ucw.cz>
*/
#include <stdio.h>
#include <time.h>
#include <getopt.h>
#include <syslog.h>
-#include <pwd.h>
-#include <grp.h>
+#include <signal.h>
#include <sys/stat.h>
#include <libusb-1.0/libusb.h>
#include <rrd.h>
-#define LOG_PATH "/var/log/arexxd"
+#define DEFAULT_LOG_DIR "/var/log/arexxd"
typedef unsigned char byte;
static libusb_context *usb_ctxt;
static int debug_mode;
static int debug_packets;
static int debug_raw_data;
+static char *log_dir = DEFAULT_LOG_DIR;
static void die(char *fmt, ...)
{
/*** RRD interface ***/
-#define SLOT_SIZE 10 // 10 seconds per averaging slot
#define MAX_ARGS 20
#define MAX_ARG_SIZE 1024
arg_pos += len;
}
-static void rrd_point(time_t t, int id, double val)
+static void rrd_point(time_t t, const char *name, double val, char *unit)
{
char rr_name[256];
- snprintf(rr_name, sizeof(rr_name), "sensor-%d.rrd", id);
+ snprintf(rr_name, sizeof(rr_name), "sensor-%s.rrd", name);
struct stat st;
if (stat(rr_name, &st) < 0 || !st.st_size) {
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
+ if (!strcmp(unit, "%RH"))
+ arg_push("DS:rh:GAUGE:300:0:100");
+ else if (!strcmp(unit, "ppm"))
+ arg_push("DS:ppm:GAUGE:300:0:1000000");
+ else
+ arg_push("DS:temp:GAUGE:300:-200:200");
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
+ arg_push("RRA:MIN:0.25:60:88800"); // including minima and maxima
+ arg_push("RRA:MAX:0.25:60:88800");
rrd_create(arg_cnt, arg_ptr);
if (rrd_test_error()) {
log_error("rrd_create on %s failed: %s", rr_name, rrd_get_error());
#define TIME_OFFSET 946681200 // Timestamp of 2000-01-01 00:00:00
+static int data_point_counter; // Since last log message
+
+static double correct_point(int id, double val, const char **name)
+{
+ /*
+ * Manually calculated corrections and renames for my sensors.
+ * Replace with your formulae.
+ */
+ switch (id) {
+ case 10415:
+ *name = "ursarium";
+ return val - 0.93;
+ case 10707:
+ *name = "balcony";
+ return val - 0.71;
+ case 19246:
+ *name = "catarium";
+ return val + 0.49;
+ case 19247:
+ *name = "catarium-rh";
+ return val;
+ case 12133:
+ *name = "outside";
+ return val + 0.44;
+ default:
+ return val;
+ }
+}
+
static void cooked_point(time_t t, int id, double val, char *unit, int q)
{
- struct tm tm;
- localtime_r(&t, &tm);
+ char namebuf[16];
+ snprintf(namebuf, sizeof(namebuf), "%d", id);
+ const char *name = namebuf;
- char tbuf[64];
- strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", &tm);
+ double val2 = correct_point(id, val, &name);
- if (debug_raw_data)
- printf("== %s id=%d val=%.3f unit=%s q=%d\n", tbuf, id, val, unit, q);
+ if (debug_raw_data) {
+ 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 name=%s val=%.3f val2=%.3f unit=%s q=%d\n", tbuf, id, name, val, val2, unit, q);
+ }
- rrd_point(t, id, val);
+ data_point_counter++;
+ rrd_point(t, name, val2, unit);
}
static void raw_point(int t, int id, int raw, int q)
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));
+ log_info("Arexx data logger 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);
/*** Main ***/
+static sigset_t term_sigs;
+static volatile sig_atomic_t want_shutdown;
+
+static void sigterm_handler(int sig __attribute__((unused)))
+{
+ want_shutdown = 1;
+}
+
+static void interruptible_msleep(int ms)
+{
+ sigprocmask(SIG_UNBLOCK, &term_sigs, NULL);
+ struct timespec ts = { .tv_sec = ms/1000, .tv_nsec = (ms%1000) * 1000000 };
+ nanosleep(&ts, NULL);
+ sigprocmask(SIG_BLOCK, &term_sigs, NULL);
+}
+
static const struct option long_options[] = {
{ "debug", 0, NULL, 'd' },
- { "log-packets", 0, NULL, 'p' },
+ { "log-dir", 1, NULL, 'l' },
+ { "debug-packets", 0, NULL, 'p' },
+ { "debug-raw", 0, NULL, 'r' },
{ NULL, 0, NULL, 0 },
};
static void usage(void)
{
- fprintf(stderr, "Usage: arexxd [--debug] [--log-packets]\n");
+ fprintf(stderr, "\n\
+Usage: arexxd <options>\n\
+\n\
+Options:\n\
+-d, --debug Debug mode (no chdir, no fork, no syslog)\n\
+-l, --log-dir=<dir> Directory where all received data should be stored\n\
+-p, --debug-packets Log all packets sent and received\n\
+-r, --debug-raw Log conversion from raw values\n\
+");
exit(1);
}
int main(int argc, char **argv)
{
int opt;
- while ((opt = getopt_long(argc, argv, "dp", long_options, NULL)) >= 0)
+ while ((opt = getopt_long(argc, argv, "dl:pr", long_options, NULL)) >= 0)
switch (opt) {
case 'd':
debug_mode++;
break;
+ case 'l':
+ log_dir = optarg;
+ break;
case 'p':
debug_packets++;
+ break;
+ case 'r':
debug_raw_data++;
break;
default:
// libusb_set_debug(usb_ctxt, 3);
if (!debug_mode) {
- if (chdir(LOG_PATH) < 0)
- die("Cannot change directory to %s: %m", LOG_PATH);
+ if (chdir(log_dir) < 0)
+ die("Cannot change directory to %s: %m", log_dir);
if (debug_packets || debug_raw_data) {
close(1);
if (open("debug", O_WRONLY | O_CREAT | O_APPEND, 0666) < 0)
use_syslog = 1;
}
+ struct sigaction sa = { .sa_handler = sigterm_handler };
+ sigaction(SIGTERM, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+
+ sigemptyset(&term_sigs);
+ sigaddset(&term_sigs, SIGTERM);
+ sigaddset(&term_sigs, SIGINT);
+ sigprocmask(SIG_BLOCK, &term_sigs, NULL);
+
int inited = 0;
- for (;;) {
+ while (!want_shutdown) {
if (!find_device()) {
if (!inited) {
inited = 1;
- log_error("Receiver not connected, waiting until it appears");
+ log_error("Data logger not connected, waiting until it appears");
}
- sleep(30);
+ interruptible_msleep(30000);
continue;
}
- inited = 1;
-
- int sync_in = 0;
- for (;;) {
- if (!sync_in) {
- log_info("Synchronizing receiver time");
+ log_info("Listening");
+
+ time_t last_sync = 0;
+ time_t last_show = 0;
+ int want_stats = 0;
+ int want_sleep = 0;
+ data_point_counter = 0;
+ while (!want_shutdown) {
+ time_t now = time(NULL);
+ if (now > last_sync + 900) {
+ log_info("Synchronizing data logger time");
set_clock();
- sync_in = 200;
+ last_sync = now;
+ }
+ if (want_stats && now > last_show + 300) {
+ log_info("Stats: received %d data points", data_point_counter);
+ data_point_counter = 0;
+ last_show = now;
}
+
byte req[64], reply[64];
memset(req, 0, sizeof(req));
req[0] = 3;
err = send_and_receive(req, reply);
if (err < 0)
break;
+ want_sleep = 1;
if (err > 0 && parse_packet(reply))
- continue;
- sleep(4);
- sync_in--;
+ want_sleep = 0;
+ if (want_sleep) {
+ interruptible_msleep(4000);
+ want_stats = 1;
+ } else
+ interruptible_msleep(5);
}
- log_info("Disconnecting receiver");
+ log_info("Disconnecting data logger");
release_device();
+ inited = 0;
+ interruptible_msleep(10000);
}
+ log_info("Terminated");
return 0;
}