author | Florian Forster <octo@collectd.org> | |
Wed, 7 Aug 2013 06:39:08 +0000 (08:39 +0200) | ||
committer | Florian Forster <octo@collectd.org> | |
Wed, 7 Aug 2013 06:39:08 +0000 (08:39 +0200) |
configure.in | patch | blob | history | |
src/Makefile.am | patch | blob | history | |
src/collectd.conf.in | patch | blob | history | |
src/collectd.conf.pod | patch | blob | history | |
src/sigrok.c | [new file with mode: 0644] | patch | blob |
src/types.db | patch | blob | history |
diff --git a/configure.in b/configure.in
index 27c6cd316b916a0047cfe389face9b5b40d0af9b..0838de8b914317c42cb35894f8bf6328b5dc41f4 100644 (file)
--- a/configure.in
+++ b/configure.in
AM_CONDITIONAL(BUILD_WITH_LM_SENSORS, test "x$with_libsensors" = "xyes")
# }}}
+# --with-libsigrok {{{
+with_libsigrok_cflags=""
+with_libsigrok_ldflags=""
+AC_ARG_WITH(libsigrok, [AS_HELP_STRING([--with-libsigrok@<:@=PREFIX@:>@], [Path to libsigrok.])],
+[
+ if test "x$withval" = "xno"
+ then
+ with_libsigrok="no"
+ else
+ with_libsigrok="yes"
+ if test "x$withval" != "xyes"
+ then
+ with_libsigrok_cflags="-I$withval/include"
+ with_libsigrok_ldflags="-L$withval/lib"
+ fi
+ fi
+],[])
+
+# libsigrok has a glib dependency
+if test "x$with_libsigrok" = "xyes"
+then
+ if test -z "m4_ifdef([AM_PATH_GLIB_2_0], [yes], [])"
+ then
+ with_libsigrok="no (glib not available)"
+ else
+ AM_PATH_GLIB_2_0([2.28.0],
+ [with_libsigrok_cflags="$with_libsigrok_cflags $GLIB_CFLAGS"; with_libsigrok_ldflags="$with_libsigrok_ldflags $GLIB_LIBS"])
+ fi
+fi
+
+# libsigrok headers
+if test "x$with_libsigrok" = "xyes"
+then
+ SAVE_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $with_libsigrok_cflags"
+
+ AC_CHECK_HEADERS(libsigrok/libsigrok.h, [], [with_libsigrok="no (libsigrok/libsigrok.h not found)"])
+
+ CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+
+# libsigrok library
+if test "x$with_libsigrok" = "xyes"
+then
+ SAVE_CPPFLAGS="$CPPFLAGS"
+ SAVE_LDFLAGS="$LDFLAGS"
+ CPPFLAGS="$CPPFLAGS $with_libsigrok_cflags"
+ LDFLAGS="$LDFLAGS $with_libsigrok_ldflags"
+
+ AC_CHECK_LIB(sigrok, sr_init,
+ [
+ AC_DEFINE(HAVE_LIBSIGROK, 1, [Define to 1 if you have the sigrok library (-lsigrok).])
+ ],
+ [with_libsigrok="no (libsigrok not found)"])
+
+ CPPFLAGS="$SAVE_CPPFLAGS"
+ LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_libsigrok" = "xyes"
+then
+ BUILD_WITH_LIBSIGROK_CFLAGS="$with_libsigrok_cflags"
+ BUILD_WITH_LIBSIGROK_LDFLAGS="$with_libsigrok_ldflags"
+ AC_SUBST(BUILD_WITH_LIBSIGROK_CFLAGS)
+ AC_SUBST(BUILD_WITH_LIBSIGROK_LDFLAGS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBSIGROK, test "x$with_libsigrok" = "xyes")
+# }}}
+
# --with-libstatgrab {{{
with_libstatgrab_cflags=""
with_libstatgrab_ldflags=""
AC_PLUGIN([rrdtool], [$with_librrd], [RRDTool output plugin])
AC_PLUGIN([sensors], [$with_libsensors], [lm_sensors statistics])
AC_PLUGIN([serial], [$plugin_serial], [serial port traffic])
+AC_PLUGIN([sigrok], [$with_libsigrok], [sigrok acquisition sources])
AC_PLUGIN([snmp], [$with_libnetsnmp], [SNMP querying plugin])
AC_PLUGIN([swap], [$plugin_swap], [Swap usage statistics])
AC_PLUGIN([syslog], [$have_syslog], [Syslog logging plugin])
librouteros . . . . . $with_librouteros
librrd . . . . . . . $with_librrd
libsensors . . . . . $with_libsensors
+ libsigrok . . . . . $with_libsigrok
libstatgrab . . . . . $with_libstatgrab
libtokyotyrant . . . $with_libtokyotyrant
libupsclient . . . . $with_libupsclient
rrdtool . . . . . . . $enable_rrdtool
sensors . . . . . . . $enable_sensors
serial . . . . . . . $enable_serial
+ sigrok . . . . . . . $enable_sigrok
snmp . . . . . . . . $enable_snmp
swap . . . . . . . . $enable_swap
syslog . . . . . . . $enable_syslog
diff --git a/src/Makefile.am b/src/Makefile.am
index c3e596d4a5fea92d6853521fb9ee19458c5a94d4..bdde5a3518d3703596898a6c9d212b3a602e8b77 100644 (file)
--- a/src/Makefile.am
+++ b/src/Makefile.am
collectd_DEPENDENCIES += serial.la
endif
+if BUILD_PLUGIN_SIGROK
+pkglib_LTLIBRARIES += sigrok.la
+sigrok_la_SOURCES = sigrok.c
+sigrok_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBSIGROK_CFLAGS)
+sigrok_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBSIGROK_LDFLAGS)
+sigrok_la_LIBADD = -lsigrok
+collectd_LDADD += "-dlopen" sigrok.la
+collectd_DEPENDENCIES += sigrok.la
+endif
+
if BUILD_PLUGIN_SNMP
pkglib_LTLIBRARIES += snmp.la
snmp_la_SOURCES = snmp.c
diff --git a/src/collectd.conf.in b/src/collectd.conf.in
index 80aba6a46fbc2e84cc341d3ef33234015de6fa40..e471a7d9c57f24a21a46a76b2d5edf5f26593bc1 100644 (file)
--- a/src/collectd.conf.in
+++ b/src/collectd.conf.in
@LOAD_PLUGIN_RRDTOOL@LoadPlugin rrdtool
#@BUILD_PLUGIN_SENSORS_TRUE@LoadPlugin sensors
#@BUILD_PLUGIN_SERIAL_TRUE@LoadPlugin serial
+#@BUILD_PLUGIN_SIGROK_TRUE@LoadPlugin sigrok
#@BUILD_PLUGIN_SNMP_TRUE@LoadPlugin snmp
#@BUILD_PLUGIN_SWAP_TRUE@LoadPlugin swap
#@BUILD_PLUGIN_TABLE_TRUE@LoadPlugin table
# IgnoreSelected false
#</Plugin>
+#<Plugin sigrok>
+# LogLevel 3
+# <Device "AC Voltage">
+# Driver "fluke-dmm"
+# MinimumInterval 10
+# Conn "/dev/ttyUSB2"
+# </Device>
+# <Device "Sound Level">
+# Driver "cem-dt-885x"
+# Conn "/dev/ttyUSB1"
+# </Device>
+#</Plugin>
+
#<Plugin snmp>
# <Data "powerplus_voltge_input">
# Type "voltage"
diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod
index 11db1ccdc3d1e07c49f7c6efc93761ba43c0e21b..7b9a5598b77c65fae169edc835295c0d2801b15a 100644 (file)
--- a/src/collectd.conf.pod
+++ b/src/collectd.conf.pod
=back
+=head2 Plugin C<sigrok>
+
+The I<sigrok plugin> uses I<libsigrok> to retrieve measurements from any device
+supported by the L<sigrok|http://sigrok.org/> project.
+
+B<Synopsis>
+
+ <Plugin sigrok>
+ LogLevel 3
+ <Device "AC Voltage">
+ Driver "fluke-dmm"
+ MinimumInterval 10
+ Conn "/dev/ttyUSB2"
+ </Device>
+ <Device "Sound Level">
+ Driver "cem-dt-885x"
+ Conn "/dev/ttyUSB1"
+ </Device>
+ </Plugin>
+
+=over 4
+
+=item B<LogLevel> B<0-5>
+
+The I<sigrok> logging level to pass on to the I<collectd> log, as a number
+between B<0> and B<5> (inclusive). These levels correspond to C<None>,
+C<Errors>, C<Warnings>, C<Informational>, C<Debug >and C<Spew>, respectively.
+The default is B<2> (C<Warnings>). The I<sigrok> log messages, regardless of
+their level, are always submitted to I<collectd> at its INFO log level.
+
+=item E<lt>B<Device> I<Name>E<gt>
+
+A sigrok-supported device, uniquely identified by this section's options. The
+I<Name> is passed to I<collectd> as the I<plugin instance>.
+
+=item B<Driver> I<DriverName>
+
+The sigrok driver to use for this device.
+
+=item B<Conn> I<ConnectionSpec>
+
+If the device cannot be auto-discovered, or more than one might be discovered
+by the driver, I<ConnectionSpec> specifies the connection string to the device.
+It can be of the form of a device path (e.g.E<nbsp>C</dev/ttyUSB2>), or, in
+case of a non-serial USB-connected device, the USB I<VendorID>B<.>I<ProductID>
+separated by a period (e.g.E<nbsp>C<0403.6001>). A USB device can also be
+specified as I<Bus>B<.>I<Address> (e.g.E<nbsp>C<1.41>).
+
+=item B<SerialComm> I<SerialSpec>
+
+For serial devices with non-standard port settings, this option can be used
+to specify them in a form understood by I<sigrok>, e.g.E<nbsp>C<9600/8n1>.
+This should not be necessary; drivers know how to communicate with devices they
+support.
+
+=item B<MinimumInterval> I<Seconds>
+
+Specifies the minimum time between measurement dispatches to I<collectd>, in
+seconds. Since some I<sigrok> supported devices can acquire measurements many
+times per second, it may be necessary to throttle these. For example, the
+I<RRD plugin> cannot process writes more than once per second.
+
+The default B<MinimumInterval> is B<0>, meaning measurements received from the
+device are always dispatched to I<collectd>. When throttled, unused
+measurements are discarded.
+
+=back
+
=head2 Plugin C<snmp>
Since the configuration of the C<snmp plugin> is a little more complicated than
diff --git a/src/sigrok.c b/src/sigrok.c
--- /dev/null
+++ b/src/sigrok.c
@@ -0,0 +1,399 @@
+/*
+ * collectd - src/sigrok.c
+ * Copyright (C) 2013 Bert Vermeulen <bert@biot.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <pthread.h>
+
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+
+/* Minimum interval between dispatches coming from this plugin. The RRD
+ * plugin, at least, complains when written to with sub-second intervals.*/
+#define DEFAULT_MIN_DISPATCH_INTERVAL TIME_T_TO_CDTIME_T(0)
+
+static pthread_t sr_thread;
+static int sr_thread_running = FALSE;
+GSList *config_devices;
+static int num_devices;
+static int loglevel = SR_LOG_WARN;
+static struct sr_context *sr_ctx;
+
+struct config_device {
+ char *name;
+ char *driver;
+ char *conn;
+ char *serialcomm;
+ struct sr_dev_inst *sdi;
+ cdtime_t min_dispatch_interval;
+ cdtime_t last_dispatch;
+};
+
+
+static int sigrok_log_callback(void*cb_data __attribute__((unused)),
+ int msg_loglevel, const char *format, va_list args)
+{
+ char s[512];
+
+ if (msg_loglevel <= loglevel) {
+ vsnprintf(s, 512, format, args);
+ plugin_log(LOG_INFO, "sigrok plugin: %s", s);
+ }
+
+ return 0;
+}
+
+static int sigrok_config_device(oconfig_item_t *ci)
+{
+ struct config_device *cfdev;
+ int i;
+
+ if (!(cfdev = malloc(sizeof(struct config_device)))) {
+ ERROR("sigrok plugin: malloc() failed.");
+ return -1;
+ }
+ memset(cfdev, 0, sizeof(*cfdev));
+ if (cf_util_get_string(ci, &cfdev->name)) {
+ free(cfdev);
+ WARNING("sigrok plugin: Invalid device name.");
+ return -1;
+ }
+ cfdev->min_dispatch_interval = DEFAULT_MIN_DISPATCH_INTERVAL;
+
+ for (i = 0; i < ci->children_num; i++) {
+ oconfig_item_t *item = ci->children + i;
+ if (!strcasecmp(item->key, "driver"))
+ cf_util_get_string(item, &cfdev->driver);
+ else if (!strcasecmp(item->key, "conn"))
+ cf_util_get_string(item, &cfdev->conn);
+ else if (!strcasecmp(item->key, "serialcomm"))
+ cf_util_get_string(item, &cfdev->serialcomm);
+ else if (!strcasecmp(item->key, "minimuminterval"))
+ cf_util_get_cdtime(item, &cfdev->min_dispatch_interval);
+ else
+ WARNING("sigrok plugin: Invalid keyword \"%s\".",
+ item->key);
+ }
+
+ config_devices = g_slist_append(config_devices, cfdev);
+
+ return 0;
+}
+
+static int sigrok_config(oconfig_item_t *ci)
+{
+ int i;
+
+ for (i = 0; i < ci->children_num; i++) {
+ oconfig_item_t *item = ci->children + i;
+ if (strcasecmp("LogLevel", item->key) == 0) {
+ int status;
+ int tmp = -1;
+
+ status = cf_util_get_int (item, &tmp);
+ if (status != 0)
+ continue;
+ else if ((tmp < 0) || (tmp > 5)) {
+ ERROR ("sigrok plugin: The \"LogLevel\" "
+ "configuration option expects "
+ "an integer between 0 and 5 "
+ "(inclusive); you provided %i.",
+ tmp);
+ continue;
+ }
+ loglevel = tmp;
+ } else if (!strcasecmp(item->key, "Device"))
+ sigrok_config_device(item);
+ else
+ WARNING("sigrok plugin: Invalid keyword \"%s\".",
+ item->key);
+ }
+
+ return 0;
+}
+
+static char *sigrok_value_type(const struct sr_datafeed_analog *analog)
+{
+ char *s;
+
+ if (analog->mq == SR_MQ_VOLTAGE)
+ s = "voltage";
+ else if (analog->mq == SR_MQ_CURRENT)
+ s = "current";
+ else if (analog->mq == SR_MQ_FREQUENCY)
+ s = "frequency";
+ else if (analog->mq == SR_MQ_POWER)
+ s = "power";
+ else if (analog->mq == SR_MQ_TEMPERATURE)
+ s = "temperature";
+ else if (analog->mq == SR_MQ_RELATIVE_HUMIDITY)
+ s = "humidity";
+ else if (analog->mq == SR_MQ_SOUND_PRESSURE_LEVEL)
+ s = "spl";
+ else
+ s = "gauge";
+
+ return s;
+}
+
+static void sigrok_feed_callback(const struct sr_dev_inst *sdi,
+ const struct sr_datafeed_packet *packet, void *cb_data)
+{
+ const struct sr_datafeed_analog *analog;
+ struct config_device *cfdev;
+ GSList *l;
+ value_t value;
+ value_list_t vl = VALUE_LIST_INIT;
+
+ /* Find this device's configuration. */
+ cfdev = NULL;
+ for (l = config_devices; l; l = l->next) {
+ cfdev = l->data;
+ if (cfdev->sdi == sdi) {
+ /* Found it. */
+ break;
+ }
+ cfdev = NULL;
+ }
+
+ if (!cfdev) {
+ ERROR("sigrok plugin: Received data from driver \"%s\" but "
+ "can't find a configuration / device matching "
+ "it.", sdi->driver->name);
+ return;
+ }
+
+ if (packet->type == SR_DF_END) {
+ /* TODO: try to restart acquisition after a delay? */
+ WARNING("sigrok plugin: acquisition for \"%s\" ended.",
+ cfdev->name);
+ return;
+ }
+
+ if (packet->type != SR_DF_ANALOG)
+ return;
+
+ if ((cfdev->min_dispatch_interval != 0)
+ && ((cdtime() - cfdev->last_dispatch)
+ < cfdev->min_dispatch_interval))
+ return;
+
+ /* Ignore all but the first sample on the first probe. */
+ analog = packet->payload;
+ value.gauge = analog->data[0];
+ vl.values = &value;
+ vl.values_len = 1;
+ sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+ sstrncpy(vl.plugin, "sigrok", sizeof(vl.plugin));
+ ssnprintf(vl.plugin_instance, sizeof(vl.plugin_instance),
+ "%s", cfdev->name);
+ sstrncpy(vl.type, sigrok_value_type(analog), sizeof(vl.type));
+
+ plugin_dispatch_values(&vl);
+ cfdev->last_dispatch = cdtime();
+}
+
+static void sigrok_free_drvopts(struct sr_config *src)
+{
+ g_variant_unref(src->data);
+ g_free(src);
+}
+
+static int sigrok_init_driver(struct config_device *cfdev,
+ struct sr_dev_driver *drv)
+{
+ struct sr_config *src;
+ GSList *devlist, *drvopts;
+ char hwident[512];
+
+ if (sr_driver_init(sr_ctx, drv) != SR_OK)
+ /* Error was logged by libsigrok. */
+ return -1;
+
+ drvopts = NULL;
+ if (cfdev->conn) {
+ if (!(src = malloc(sizeof(struct sr_config))))
+ return -1;
+ src->key = SR_CONF_CONN;
+ src->data = g_variant_new_string(cfdev->conn);
+ drvopts = g_slist_append(drvopts, src);
+ }
+ if (cfdev->serialcomm) {
+ if (!(src = malloc(sizeof(struct sr_config))))
+ return -1;
+ src->key = SR_CONF_SERIALCOMM;
+ src->data = g_variant_new_string(cfdev->serialcomm);
+ drvopts = g_slist_append(drvopts, src);
+ }
+ devlist = sr_driver_scan(drv, drvopts);
+ g_slist_free_full(drvopts, (GDestroyNotify)sigrok_free_drvopts);
+ if (!devlist) {
+ /* Not an error, but the user should know about it. */
+ WARNING("sigrok plugin: No device found for \"%s\".",
+ cfdev->name);
+ return 0;
+ }
+
+ if (g_slist_length(devlist) > 1) {
+ INFO("sigrok plugin: %d sigrok devices for device entry "
+ "\"%s\": must be 1.",
+ g_slist_length(devlist), cfdev->name);
+ return -1;
+ }
+ cfdev->sdi = devlist->data;
+ g_slist_free(devlist);
+ ssnprintf(hwident, sizeof(hwident), "%s %s %s",
+ cfdev->sdi->vendor ? cfdev->sdi->vendor : "",
+ cfdev->sdi->model ? cfdev->sdi->model : "",
+ cfdev->sdi->version ? cfdev->sdi->version : "");
+ INFO("sigrok plugin: Device \"%s\" is a %s", cfdev->name, hwident);
+
+ if (sr_dev_open(cfdev->sdi) != SR_OK)
+ return -1;
+
+ if (sr_session_dev_add(cfdev->sdi) != SR_OK)
+ return -1;
+
+ return 1;
+}
+
+static void *sigrok_read_thread(void *arg __attribute__((unused)))
+{
+ struct sr_dev_driver *drv, **drvlist;
+ GSList *l;
+ struct config_device *cfdev;
+ int ret, i;
+
+ sr_log_callback_set(sigrok_log_callback, NULL);
+ sr_log_loglevel_set(loglevel);
+
+ if ((ret = sr_init(&sr_ctx)) != SR_OK) {
+ ERROR("sigrok plugin: Failed to initialize libsigrok: %s.",
+ sr_strerror(ret));
+ return NULL;
+ }
+
+ if (!sr_session_new())
+ return NULL;
+
+ num_devices = 0;
+ drvlist = sr_driver_list();
+ for (l = config_devices; l; l = l->next) {
+ cfdev = l->data;
+ drv = NULL;
+ for (i = 0; drvlist[i]; i++) {
+ if (!strcmp(drvlist[i]->name, cfdev->driver)) {
+ drv = drvlist[i];
+ break;
+ }
+ }
+ if (!drv) {
+ ERROR("sigrok plugin: Unknown driver \"%s\".",
+ cfdev->driver);
+ return NULL;
+ }
+
+ if ((ret = sigrok_init_driver(cfdev, drv)) < 0)
+ /* Error was already logged. */
+ return NULL;
+
+ num_devices += ret;
+ }
+
+ if (num_devices > 0) {
+ /* Do this only when we're sure there's hardware to talk to. */
+ if (sr_session_datafeed_callback_add(sigrok_feed_callback, NULL)
+ != SR_OK)
+ return NULL;
+
+ /* Start acquisition on all devices. */
+ if (sr_session_start() != SR_OK)
+ return NULL;
+
+ /* Main loop, runs forever. */
+ sr_session_run();
+
+ sr_session_stop();
+ sr_session_dev_remove_all();
+ }
+
+ sr_session_destroy();
+
+ sr_exit(sr_ctx);
+
+ pthread_exit(NULL);
+ sr_thread_running = FALSE;
+
+ return NULL;
+}
+
+static int sigrok_init(void)
+{
+ int status;
+
+ if (sr_thread_running) {
+ ERROR("sigrok plugin: Thread already running.");
+ return -1;
+ }
+
+ if ((status = plugin_thread_create(&sr_thread, NULL, sigrok_read_thread,
+ NULL)) != 0) {
+ ERROR("sigrok plugin: Failed to create thread: %s.",
+ strerror(status));
+ return -1;
+ }
+ sr_thread_running = TRUE;
+
+ return 0;
+}
+
+static int sigrok_shutdown(void)
+{
+ struct config_device *cfdev;
+ GSList *l;
+
+ if (sr_thread_running) {
+ pthread_cancel(sr_thread);
+ pthread_join(sr_thread, NULL);
+ }
+
+ for (l = config_devices; l; l = l->next) {
+ cfdev = l->data;
+ free(cfdev->name);
+ free(cfdev->driver);
+ free(cfdev->conn);
+ free(cfdev->serialcomm);
+ free(cfdev);
+ }
+ g_slist_free(config_devices);
+
+ return 0;
+}
+
+void module_register(void)
+{
+ plugin_register_complex_config("sigrok", sigrok_config);
+ plugin_register_init("sigrok", sigrok_init);
+ plugin_register_shutdown("sigrok", sigrok_shutdown);
+}
diff --git a/src/types.db b/src/types.db
index f1d30f0d1a17df08e41124ce73bac2fde3c0e240..723b477d72e45e1c4eac75ef719a4aa7d06ec641 100644 (file)
--- a/src/types.db
+++ b/src/types.db
snr value:GAUGE:0:U
spam_check value:GAUGE:0:U
spam_score value:GAUGE:U:U
+spl value:GAUGE:U:U
swap_io value:DERIVE:0:U
swap value:GAUGE:0:1099511627776
tcp_connections value:GAUGE:0:4294967295
-temperature value:GAUGE:-273.15:U
+temperature value:GAUGE:U:U
threads value:GAUGE:0:U
time_dispersion value:GAUGE:-1000000:1000000
timeleft value:GAUGE:0:U