]> sigrok.org Git - libsigrok.git/commitdiff
brymen-dmm: Add support for Brymen BM857
authorAlexandru Gagniuc <redacted>
Mon, 26 Nov 2012 23:09:05 +0000 (17:09 -0600)
committerUwe Hermann <redacted>
Sat, 2 Feb 2013 10:19:27 +0000 (11:19 +0100)
This patch might also work for a number of other Brymen models
-- 859(a), 867, 869---
including their respective rebadges from Greenlee, Extech, and Amprobe.

Signed-off-by: Alexandru Gagniuc <redacted>
hardware/brymen-dmm/Makefile.am
hardware/brymen-dmm/api.c
hardware/brymen-dmm/parser.c [new file with mode: 0644]
hardware/brymen-dmm/protocol.c
hardware/brymen-dmm/protocol.h

index baec07a2d7d1ab21482f59abafb3fa30f3695067..17c5afa1dd00654cacc425b5eeebc98ca107c83b 100644 (file)
@@ -24,6 +24,7 @@ noinst_LTLIBRARIES = libsigrok_hw_brymen_dmm.la
 
 libsigrok_hw_brymen_dmm_la_SOURCES = \
        api.c \
+       parser.c \
        protocol.c \
        protocol.h
 
index 2a524c84f4870f627044e3f754c3e9058fa70578..697959f40d46570ef38722eb6d36517cdc255c57 100644 (file)
  */
 
 #include <glib.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
 #include "libsigrok.h"
 #include "libsigrok-internal.h"
 #include "protocol.h"
 
+static const int hwopts[] = {
+       SR_CONF_CONN,
+       SR_CONF_SERIALCOMM,
+       0,
+};
+
+static const int hwcaps[] = {
+       SR_CONF_MULTIMETER,
+       SR_CONF_LIMIT_SAMPLES,
+       SR_CONF_CONTINUOUS,
+       SR_CONF_LIMIT_MSEC,
+       0,
+};
+
 SR_PRIV struct sr_dev_driver brymen_dmm_driver_info;
 static struct sr_dev_driver *di = &brymen_dmm_driver_info;
 
+static int hw_init(struct sr_context *sr_ctx)
+{
+       struct drv_context *drvc;
+
+       if (!(drvc = g_try_malloc0(sizeof(struct drv_context)))) {
+               sr_err("Driver context malloc failed.");
+               return SR_ERR_MALLOC;
+       }
+
+       drvc->sr_ctx = sr_ctx;
+       di->priv = drvc;
+
+       return SR_OK;
+}
+
+static void free_instance(void *inst)
+{
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       if (!(sdi = inst))
+               return;
+       if (!(devc = sdi->priv))
+               return;
+       sr_serial_dev_inst_free(devc->serial);
+       sr_dev_inst_free(sdi);
+}
+
 /* Properly close and free all devices. */
 static int clear_instances(void)
 {
-       struct sr_dev_inst *sdi;
        struct drv_context *drvc;
-       struct dev_context *devc;
-       GSList *l;
 
        if (!(drvc = di->priv))
                return SR_OK;
 
-       for (l = drvc->instances; l; l = l->next) {
-               if (!(sdi = l->data))
-                       continue;
-               if (!(devc = sdi->priv))
-                       continue;
-
-               /* TODO */
-
-               sr_dev_inst_free(sdi);
-       }
-
-       g_slist_free(drvc->instances);
+       g_slist_free_full(drvc->instances, free_instance);
        drvc->instances = NULL;
 
        return SR_OK;
 }
 
-static int hw_init(void)
+static GSList *brymen_scan(const char *conn, const char *serialcomm)
 {
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
        struct drv_context *drvc;
+       struct sr_probe *probe;
+       struct sr_serial_dev_inst *serial;
+       GSList *devices;
+       int ret;
 
-       if (!(drvc = g_try_malloc0(sizeof(struct drv_context)))) {
-               sr_err("Driver context malloc failed.");
-               return SR_ERR_MALLOC;
+       if (!(serial = sr_serial_dev_inst_new(conn, serialcomm)))
+               return NULL;
+
+       if (serial_open(serial, SERIAL_RDWR|SERIAL_NONBLOCK) != SR_OK)
+               return NULL;
+
+       sr_info("Probing port %s.", conn);
+
+       devices = NULL;
+
+       /* Request reading */
+       if (brymen_packet_request(serial) == -1) {
+               sr_err("Unable to send command. code: %d.", errno);
+               goto scan_cleanup;
        }
 
-       /* TODO */
+       uint8_t buf[128];
+       size_t len = 128;
 
-       di->priv = drvc;
+       ret = brymen_stream_detect(serial, buf, &len, brymen_packet_length,
+                            brymen_packet_is_valid, 1000, 9600);
+       if (ret != SR_OK)
+               goto scan_cleanup;
 
-       return SR_OK;
+       sr_info("Found device on port %s.", conn);
+
+       if (!(sdi = sr_dev_inst_new(0, SR_ST_INACTIVE, "Brymen", "BM85x", "")))
+               goto scan_cleanup;
+
+       if (!(devc = g_try_malloc0(sizeof(struct dev_context)))) {
+               sr_err("Device context malloc failed.");
+               goto scan_cleanup;
+       }
+
+       devc->serial = serial;
+       drvc = di->priv;
+       sdi->priv = devc;
+       sdi->driver = di;
+
+       if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "P1")))
+               goto scan_cleanup;
+
+       sdi->probes = g_slist_append(sdi->probes, probe);
+       drvc->instances = g_slist_append(drvc->instances, sdi);
+       devices = g_slist_append(devices, sdi);
+
+
+scan_cleanup:
+       serial_close(serial);
+
+       return devices;
 }
 
 static GSList *hw_scan(GSList *options)
 {
        struct drv_context *drvc;
-       GSList *devices;
+       struct sr_config *src;
+       GSList *devices, *l;
+       const char *conn, *serialcomm;
 
        (void)options;
 
@@ -80,7 +159,29 @@ static GSList *hw_scan(GSList *options)
        drvc = di->priv;
        drvc->instances = NULL;
 
-       /* TODO */
+       conn = serialcomm = NULL;
+       for (l = options; l; l = l->next) {
+               src = l->data;
+               switch (src->key) {
+                       case SR_CONF_CONN:
+                               conn = src->value;
+                               break;
+                       case SR_CONF_SERIALCOMM:
+                               serialcomm = src->value;
+                               break;
+               }
+       }
+       if (!conn) {
+               return NULL;
+       }
+
+       if (serialcomm) {
+               /* Use the provided comm specs. */
+               devices = brymen_scan(conn, serialcomm);
+       } else {
+               /* But 9600 8N1 should work all of the time */
+               devices = brymen_scan(conn, "9600/8n1/dtr=1/rts=1");
+       }
 
        return devices;
 }
@@ -96,14 +197,34 @@ static GSList *hw_dev_list(void)
 
 static int hw_dev_open(struct sr_dev_inst *sdi)
 {
-       /* TODO */
+       struct dev_context *devc;
+
+       if (!(devc = sdi->priv)) {
+               sr_err("sdi->priv was NULL.");
+               return SR_ERR_BUG;
+       }
+
+       if (serial_open(devc->serial, SERIAL_RDWR | SERIAL_NONBLOCK) != SR_OK)
+               return SR_ERR;
+
+       sdi->status = SR_ST_ACTIVE;
 
        return SR_OK;
 }
 
 static int hw_dev_close(struct sr_dev_inst *sdi)
 {
-       /* TODO */
+       struct dev_context *devc;
+
+       if (!(devc = sdi->priv)) {
+               sr_err("sdi->priv was NULL.");
+               return SR_ERR_BUG;
+       }
+
+       if (devc->serial && devc->serial->fd != -1) {
+               serial_close(devc->serial);
+               sdi->status = SR_ST_INACTIVE;
+       }
 
        return SR_OK;
 }
@@ -112,27 +233,33 @@ static int hw_cleanup(void)
 {
        clear_instances();
 
-       /* TODO */
-
        return SR_OK;
 }
 
-static int hw_info_get(int info_id, const void **data,
+static int config_list(int key, const void **data,
                       const struct sr_dev_inst *sdi)
 {
-       switch (info_id) {
-       /* TODO */
+       (void)sdi;
+
+       switch (key) {
+               case SR_CONF_SCAN_OPTIONS:
+                       *data = hwopts;
+                       break;
+               case SR_CONF_DEVICE_OPTIONS:
+                       *data = hwcaps;
+                       break;
        default:
-               sr_err("Unknown info_id: %d.", info_id);
+               sr_err("Unknown config key: %d.", key);
                return SR_ERR_ARG;
        }
 
        return SR_OK;
 }
 
-static int hw_dev_config_set(const struct sr_dev_inst *sdi, int hwcap,
-                            const void *value)
+static int hw_dev_config_set(int id, const void *value,
+                            const struct sr_dev_inst *sdi)
 {
+       struct dev_context *devc;
        int ret;
 
        if (sdi->status != SR_ST_ACTIVE) {
@@ -140,11 +267,21 @@ static int hw_dev_config_set(const struct sr_dev_inst *sdi, int hwcap,
                return SR_ERR;
        }
 
+       if (!(devc = sdi->priv)) {
+               sr_err("sdi->priv was NULL.");
+               return SR_ERR_BUG;
+       }
+
        ret = SR_OK;
-       switch (hwcap) {
-       /* TODO */
+       switch (id) {
+               case SR_CONF_LIMIT_SAMPLES:
+               devc->limit_samples = *(const uint64_t*)value;
+               break;
+               case SR_CONF_LIMIT_MSEC:
+               devc->limit_msec = *(const uint64_t*)value;
+               break;
        default:
-               sr_err("Unknown hardware capability: %d.", hwcap);
+               sr_err("Unknown hardware capability: %d.", id);
                ret = SR_ERR_ARG;
        }
 
@@ -154,29 +291,73 @@ static int hw_dev_config_set(const struct sr_dev_inst *sdi, int hwcap,
 static int hw_dev_acquisition_start(const struct sr_dev_inst *sdi,
                                    void *cb_data)
 {
-       /* TODO */
+       struct sr_datafeed_packet packet;
+       struct sr_datafeed_header header;
+       struct dev_context *devc;
+
+       if (!(devc = sdi->priv)) {
+               sr_err("sdi->priv was NULL.");
+               return SR_ERR_BUG;
+       }
+
+       sr_dbg("Starting acquisition.");
+
+       devc->cb_data = cb_data;
+
+       /*
+        * Reset the number of samples to take. If we've already collected our
+        * quota, but we start a new session, and don't reset this, we'll just
+        * quit without acquiring any new samples.
+        */
+       devc->num_samples = 0;
+       devc->starttime = g_get_monotonic_time();
+
+       /* Send header packet to the session bus. */
+       sr_dbg("Sending SR_DF_HEADER.");
+       packet.type = SR_DF_HEADER;
+       packet.payload = &header;
+       header.feed_version = 1;
+       gettimeofday(&header.starttime, NULL);
+       sr_session_send(devc->cb_data, &packet);
+
+       /* Poll every 50ms, or whenever some data comes in. */
+       sr_source_add(devc->serial->fd, G_IO_IN, 50,
+                     brymen_dmm_receive_data, (void *)sdi);
 
        return SR_OK;
 }
 
-static int hw_dev_acquisition_stop(const struct sr_dev_inst *sdi,
-                                  void *cb_data)
+static int hw_dev_acquisition_stop(struct sr_dev_inst *sdi, void *cb_data)
 {
-       (void)cb_data;
+       struct sr_datafeed_packet packet;
+       struct dev_context *devc;
 
        if (sdi->status != SR_ST_ACTIVE) {
                sr_err("Device inactive, can't stop acquisition.");
                return SR_ERR;
        }
 
-       /* TODO */
+       if (!(devc = sdi->priv)) {
+               sr_err("sdi->priv was NULL.");
+               return SR_ERR_BUG;
+       }
+
+       sr_dbg("Stopping acquisition.");
+
+       sr_source_remove(devc->serial->fd);
+       hw_dev_close((struct sr_dev_inst *)sdi);
+
+       /* Send end packet to the session bus. */
+       sr_dbg("Sending SR_DF_END.");
+       packet.type = SR_DF_END;
+       sr_session_send(cb_data, &packet);
 
        return SR_OK;
 }
 
 SR_PRIV struct sr_dev_driver brymen_dmm_driver_info = {
        .name = "brymen-dmm",
-       .longname = "brymen-dmm",
+       .longname = "Brymen BM850 series",
        .api_version = 1,
        .init = hw_init,
        .cleanup = hw_cleanup,
@@ -185,8 +366,8 @@ SR_PRIV struct sr_dev_driver brymen_dmm_driver_info = {
        .dev_clear = clear_instances,
        .dev_open = hw_dev_open,
        .dev_close = hw_dev_close,
-       .info_get = hw_info_get,
-       .dev_config_set = hw_dev_config_set,
+       .config_list = config_list,
+       .config_set = hw_dev_config_set,
        .dev_acquisition_start = hw_dev_acquisition_start,
        .dev_acquisition_stop = hw_dev_acquisition_stop,
        .priv = NULL,
diff --git a/hardware/brymen-dmm/parser.c b/hardware/brymen-dmm/parser.c
new file mode 100644 (file)
index 0000000..b2c118b
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * This file is part of the sigrok project.
+ *
+ * Copyright (C) 2012 Alexandru Gagniuc <mr.nuke.me@gmail.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 3 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 "protocol.h"
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+/*
+ * Flags passed from the DMM.
+ */
+struct brymen_flags {
+       gboolean low_batt;
+       gboolean decibel, duty_cycle, hertz, amp, beep, ohm, fahrenheit;
+       gboolean celsius, capacitance, diode, volt, dc, ac;
+};
+
+struct bm850_command {
+       uint8_t dle;
+       uint8_t stx;
+       uint8_t cmd;
+       uint8_t arg[2];
+       uint8_t checksum;
+       uint8_t dle2;
+       uint8_t etx;
+};
+
+struct brymen_header {
+       uint8_t dle;
+       uint8_t stx;
+       uint8_t cmd;
+       uint8_t len;
+};
+
+struct brymen_tail {
+       uint8_t checksum;
+       uint8_t dle;
+       uint8_t etx;
+};
+
+/*
+ * We only have one command because we only support the BM-857. However, the
+ * driver is easily extensible to support more models, as the protocols are very
+ * similar.
+ */
+enum {
+       BM_CMD_REQUEST_READING = 0x00,
+};
+
+
+static int bm_send_command(uint8_t command, uint8_t arg1, uint8_t arg2,
+                          struct sr_serial_dev_inst *serial)
+{
+       struct bm850_command cmdout = {
+               .dle = 0x10, .stx = 0x02,
+               .cmd = command,
+               .arg = {arg1, arg2},
+               .checksum = arg1^arg2, .dle2 = 0x10, .etx = 0x03};
+               int written;
+               
+               /* TODO: How do we compute the checksum? Hardware seems to ignore it */
+               
+               /* Request reading */
+               written = serial_write(serial, &cmdout, sizeof(cmdout));
+               if(written != sizeof(cmdout))
+                       return SR_ERR;
+               
+               return SR_OK;
+}
+
+SR_PRIV int brymen_packet_request(struct sr_serial_dev_inst *serial)
+{
+       return bm_send_command(BM_CMD_REQUEST_READING, 0, 0, serial);
+}
+
+SR_PRIV int brymen_packet_length(const uint8_t *buf, int *len)
+{
+       struct brymen_header *hdr;
+       const int brymen_max_packet_len = 22;
+       int packet_len;
+       const size_t buflen = *len;
+       
+       hdr = (void*)buf;
+       
+       /* Did we receive a complete header yet? */
+       if (buflen < sizeof(*hdr) )
+               return PACKET_NEED_MORE_DATA;
+       
+       if (hdr->dle != 0x10 || hdr->stx != 0x02)
+               return PACKET_INVALID_HEADER;
+       
+       /* Our packet includes the header, the payload, and the tail */
+       packet_len = sizeof(*hdr) + hdr->len + sizeof(struct brymen_tail);
+       
+       /* In case we pick up an invalid header, limit our search */
+       if (packet_len > brymen_max_packet_len) {
+               sr_spew("Header specifies an invalid payload length: %i.",
+                       hdr->len);
+               return PACKET_INVALID_HEADER;
+       }
+       
+       *len = packet_len;
+       sr_spew("Expecting a %d-byte packet.", *len);
+       return PACKET_HEADER_OK;
+}
+
+SR_PRIV gboolean brymen_packet_is_valid(const uint8_t *buf)
+{
+       struct brymen_header *hdr;
+       struct brymen_tail *tail;
+       int i;
+       uint8_t chksum = 0;
+       const uint8_t *payload = buf + sizeof(struct brymen_header);
+       
+       hdr = (void*)buf;
+       tail = (void*)(payload + hdr->len);
+       
+       for (i = 0; i< hdr->len; i++)
+               chksum ^= payload[i];
+       
+       if (tail->checksum != chksum) {
+               sr_dbg("Packet has invalid checksum 0x%.2x. Expected 0x%.2x",
+                      chksum, tail->checksum);
+               return FALSE;
+       }
+       
+       return TRUE;
+}
+
+static int parse_value(const char *strbuf, const int len, float *floatval)
+{
+       int s, d;
+       char str[32];
+
+       if (strstr(strbuf, "OL")) {
+               sr_dbg("Overlimit.");
+               *floatval = INFINITY;
+               return SR_OK;
+       }
+
+       memset(str, 0, sizeof(str));
+       /* Spaces may interfere with strtod parsing the exponent. Strip them */
+       for (s = 0, d = 0; s < len; s++)
+               if (strbuf[s] != ' ')
+                       str[d++] = strbuf[s];
+               /* YES, it's that simple !*/
+               *floatval = strtod(str, NULL);
+
+       return SR_OK;
+}
+static void parse_flags(const uint8_t *buf, struct brymen_flags *info)
+{
+       const uint8_t * bfunc = buf + sizeof(struct brymen_header);
+
+       info->low_batt          = (bfunc[3] & (1 << 7)) != 0;
+
+       info->decibel           = (bfunc[1] & (1 << 5)) != 0;
+       info->duty_cycle        = (bfunc[1] & (1 << 3)) != 0;
+       info->hertz             = (bfunc[1] & (1 << 2)) != 0;
+       info->amp               = (bfunc[1] & (1 << 1)) != 0;
+       info->beep              = (bfunc[1] & (1 << 0)) != 0;
+
+       info->ohm               = (bfunc[0] & (1 << 7)) != 0;
+       info->fahrenheit        = (bfunc[0] & (1 << 6)) != 0;
+       info->celsius           = (bfunc[0] & (1 << 5)) != 0;
+       info->diode             = (bfunc[0] & (1 << 4)) != 0;
+       info->capacitance       = (bfunc[0] & (1 << 3)) != 0;
+       info->volt              = (bfunc[0] & (1 << 2)) != 0;
+       info->dc                = (bfunc[0] & (1 << 1)) != 0;
+       info->ac                = (bfunc[0] & (1 << 0)) != 0;
+}
+
+SR_PRIV int sr_brymen_parse(const uint8_t *buf, float *floatval,
+                           struct sr_datafeed_analog *analog, void *info)
+{
+       struct brymen_flags flags;
+       struct brymen_header *hdr = (void*) buf;
+       const uint8_t *bfunc = buf + sizeof(struct brymen_header);
+       int asciilen;
+
+       (void)info;
+       analog->mqflags = 0;
+
+       /* Give some debug info about the package */
+       asciilen = hdr->len - 4;
+       sr_dbg("DMM flags: %.2x %.2x %.2x %.2x",
+              bfunc[3], bfunc[2], bfunc[1], bfunc[0]);
+       /* Value is an ASCII string */
+       sr_dbg("DMM packet: \"%.*s\"", asciilen, bfunc + 4);
+
+       parse_flags(buf, &flags);
+       parse_value((const char*)(bfunc + 4), asciilen, floatval);
+
+       if (flags.volt) {
+               analog->mq = SR_MQ_VOLTAGE;
+               analog->unit = SR_UNIT_VOLT;
+       }
+       if (flags.amp) {
+               analog->mq = SR_MQ_CURRENT;
+               analog->unit = SR_UNIT_AMPERE;
+       }
+       if (flags.ohm) {
+               if (flags.beep)
+                       analog->mq = SR_MQ_CONTINUITY;
+               else
+                       analog->mq = SR_MQ_RESISTANCE;
+               analog->unit = SR_UNIT_OHM;
+       }
+       if (flags.hertz) {
+               analog->mq = SR_MQ_FREQUENCY;
+               analog->unit = SR_UNIT_HERTZ;
+       }
+       if (flags.duty_cycle) {
+               analog->mq = SR_MQ_DUTY_CYCLE;
+               analog->unit = SR_UNIT_PERCENTAGE;
+       }
+       if (flags.capacitance) {
+               analog->mq = SR_MQ_CAPACITANCE;
+               analog->unit = SR_UNIT_FARAD;
+       }
+       if (flags.fahrenheit) {
+               analog->mq = SR_MQ_TEMPERATURE;
+               analog->unit = SR_UNIT_FAHRENHEIT;
+       }
+       if (flags.celsius) {
+               analog->mq = SR_MQ_TEMPERATURE;
+               analog->unit = SR_UNIT_CELSIUS;
+       }
+       if (flags.capacitance) {
+               analog->mq = SR_MQ_CAPACITANCE;
+               analog->unit = SR_UNIT_FARAD;
+       }
+       /*
+        * The high-end brymen models have a configurable reference impedance.
+        * When the reference impedance is changed, the DMM sends one packet
+        * with the value of the new reference impedance. Both decibel and ohm
+        * flags are set in this case, so we must be careful to correctly
+        * identify the value as ohm, not dBmW
+        */
+       if (flags.decibel && !flags.ohm) {
+               analog->mq = SR_MQ_POWER;
+               analog->unit = SR_UNIT_DECIBEL_MW;
+               /*
+                * For some reason, dBm measurements are sent by the multimeter
+                * with a value three orders of magnitude smaller than the
+                * displayed value.
+                * */
+               *floatval *= 1000;
+       }
+
+       if (flags.diode)
+               analog->mqflags |= SR_MQFLAG_DIODE;
+       /* We can have both AC+DC in a single measurement */
+       if (flags.ac)
+               analog->mqflags |= SR_MQFLAG_AC;
+       if (flags.dc)
+               analog->mqflags |= SR_MQFLAG_DC;
+
+       if (flags.low_batt)
+               sr_info("Low battery!");
+
+       return SR_OK;
+}
\ No newline at end of file
index cf59ee25e6be17bf1ac87d7156c4269d43fc23b2..41bcd4c528163939a4924042f2813a2c3a76d988 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <stdlib.h>
-#include <glib.h>
 #include "libsigrok.h"
 #include "libsigrok-internal.h"
 #include "protocol.h"
 
+#include <string.h>
+
+/* --- parser.c --- */
+SR_PRIV int sr_brymen_parse(const uint8_t *buf, float *floatval,
+                           struct sr_datafeed_analog *analog, void *info);
+
+
+static void handle_packet(const uint8_t *buf, struct sr_dev_inst *sdi)
+{
+       float floatval;
+       struct dev_context *devc;
+       struct sr_datafeed_packet packet;
+       struct sr_datafeed_analog analog;
+
+       devc = sdi->priv;
+       
+       analog.num_samples = 1;
+       analog.mq = -1;
+
+       sr_brymen_parse(buf, &floatval, &analog, NULL);
+       analog.data = &floatval;
+
+       analog.probes = sdi->probes;
+
+       if (analog.mq != -1) {
+               /* Got a measurement. */
+               packet.type = SR_DF_ANALOG;
+               packet.payload = &analog;
+               sr_session_send(devc->cb_data, &packet);
+               devc->num_samples++;
+       }
+}
+
+static void handle_new_data(struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       int len, status, offset = 0;
+
+       devc = sdi->priv;
+
+       /* Try to get as much data as the buffer can hold. */
+       len = DMM_BUFSIZE - devc->buflen;
+       len = serial_read(devc->serial, devc->buf + devc->buflen, len);
+       if (len < 1) {
+               sr_err("Serial port read error: %d.", len);
+               return;
+       }
+       devc->buflen += len;
+       status = PACKET_INVALID_HEADER;
+
+       /* Now look for packets in that data. */
+       while (status != PACKET_NEED_MORE_DATA) {
+               /* We don't have a header, look for one */
+               if (devc->next_packet_len == 0) {
+                       len = devc->buflen - offset;
+                       status = brymen_packet_length(devc->buf + offset, &len);
+                       if (status == PACKET_HEADER_OK) {
+                               /* We know how large the packet will be */
+                               devc->next_packet_len = len;
+                       } else if (status == PACKET_NEED_MORE_DATA) {
+                               /* We did not yet receive the complete header */
+                               devc->next_packet_len = 0;
+                               break;
+                       } else {
+                               /* Invalid header. Move on */
+                               devc->next_packet_len = 0;
+                               offset++;
+                               continue;
+                       }
+               }
+
+               /* We know how the packet size, but did we receive all of it? */
+               if (devc->buflen - offset < devc->next_packet_len)
+                       break;
+
+               /* We should have a full packet here, so we can check it */
+               if (brymen_packet_is_valid(devc->buf + offset)) {
+                       handle_packet(devc->buf + offset, sdi);
+                       offset += devc->next_packet_len;
+               } else {
+                       offset++;
+               }
+               /* We are done with this packet. Look for a new one. */
+               devc->next_packet_len = 0;
+       }
+
+       /* If we have any data left, move it to the beginning of our buffer. */
+       memmove(devc->buf, devc->buf + offset, devc->buflen - offset);
+       devc->buflen -= offset;
+}
+
 SR_PRIV int brymen_dmm_receive_data(int fd, int revents, void *cb_data)
 {
-       const struct sr_dev_inst *sdi;
+       struct sr_dev_inst *sdi;
        struct dev_context *devc;
+       int ret;
+       int64_t time;
+
+       (void)fd;
 
        if (!(sdi = cb_data))
                return TRUE;
@@ -35,8 +128,134 @@ SR_PRIV int brymen_dmm_receive_data(int fd, int revents, void *cb_data)
                return TRUE;
 
        if (revents == G_IO_IN) {
-               /* TODO */
+               /* Serial data arrived. */
+               handle_new_data(sdi);
+       } else {
+               /* Timeout, send another packet request. */
+               ret = brymen_packet_request(devc->serial);
+               if (ret < 0) {
+                       sr_err("Failed to request packet: %d.", ret);
+                       return FALSE;
+               }
+       }
+
+       if (devc->limit_samples && devc->num_samples >= devc->limit_samples) {
+               sr_info("Requested number of samples reached, stopping.");
+               sdi->driver->dev_acquisition_stop(sdi, cb_data);
+               return TRUE;
+       }
+
+       if (devc->limit_msec) {
+               time = (g_get_monotonic_time() - devc->starttime) / 1000;
+               if (time > (int64_t)devc->limit_msec) {
+                       sr_info("Requested time limit reached, stopping.");
+                       sdi->driver->dev_acquisition_stop(sdi, cb_data);
+                       return TRUE;
+               }
        }
 
        return TRUE;
 }
+
+/**
+ * Try to find a valid packet in a serial data stream.
+ *
+ * @param serial Previously initialized serial port structure.
+ * @param buf Buffer containing the bytes to write.
+ * @param buflen Size of the buffer.
+ * @param packet_size Callback that assesses the size of the incoming packet/
+ * @param is_valid Callback that assesses whether the packet is valid or not.
+ * @param timeout_ms The timeout after which, if no packet is detected, to
+ *                   abort scanning.
+ * @param baudrate The baudrate of the serial port. This parameter is not
+ *                 critical, but it helps fine tune the serial port polling
+ *                 delay.
+ *
+ * @return SR_OK if a valid packet is found within the given timeout,
+ *         SR_ERR upon failure.
+ */
+SR_PRIV int brymen_stream_detect(struct sr_serial_dev_inst *serial,
+                               uint8_t *buf, size_t *buflen,
+                               packet_length_t get_packet_size,
+                               packet_valid_t is_valid,
+                               uint64_t timeout_ms, int baudrate)
+{
+       int64_t start, time, byte_delay_us;
+       size_t ibuf, i, maxlen;
+       int status, len, packet_len, stream_len;
+
+       maxlen = *buflen;
+
+       sr_dbg("Detecting packets on FD %d (timeout = %" PRIu64
+       "ms, baudrate = %d).", serial->fd, timeout_ms, baudrate);
+
+       /* Assume 8n1 transmission. That is 10 bits for every byte. */
+       byte_delay_us = 10 * (1000000 / baudrate);
+       start = g_get_monotonic_time();
+
+       packet_len = i = ibuf = len = 0;
+       while (ibuf < maxlen) {
+               len = serial_read(serial, &buf[ibuf], maxlen - ibuf);
+               if (len > 0) {
+                       ibuf += len;
+                       sr_spew("Read %d bytes", len);
+               }
+
+               time = g_get_monotonic_time() - start;
+               time /= 1000;
+
+               stream_len = ibuf - i;
+               if (stream_len > 0 && packet_len == 0) {
+                       /* How large of a packet are we expecting? */
+                       packet_len = stream_len;
+                       status = get_packet_size(&buf[i], &packet_len);
+                       switch(status) {
+                       case PACKET_HEADER_OK:
+                               /* We know how much data we need to wait for */
+                               break;
+                       case PACKET_NEED_MORE_DATA:
+                               /* We did not receive the full header */
+                               packet_len = 0;
+                               break;
+                       case PACKET_INVALID_HEADER:
+                       default:
+                               /*
+                                * We had enough data, but here was an error in
+                                * parsing the header. Restart parsing from the
+                                * next byte
+                                */
+                               packet_len = 0;
+                               i++;
+                               break;
+                       }
+               }
+
+               if ( (stream_len >= packet_len) && (packet_len != 0) ) {
+                       /* We have at least a packet's worth of data. */
+                       if (is_valid(&buf[i])) {
+                               sr_spew("Found valid %d-byte packet after "
+                               "%" PRIu64 "ms.", packet_len, time);
+                               *buflen = ibuf;
+                               return SR_OK;
+                       } else {
+                               sr_spew("Got %d bytes, but not a valid "
+                               "packet.", packet_len);
+
+                       }
+                       /* Not a valid packet. Continue searching. */
+                       i++;
+                       packet_len = 0;
+               }
+               if (time >= (int64_t)timeout_ms) {
+                       /* Timeout */
+                       sr_dbg("Detection timed out after %dms.", time);
+                       break;
+               }
+               g_usleep(byte_delay_us);
+       }
+
+       *buflen = ibuf;
+       sr_err("Didn't find a valid packet (read %d bytes).", ibuf);
+
+       return SR_ERR;
+}
index ee59fd7d9f0c16a0b41a0b938e723ec768afade5..7c981cc13ee492bf0bd50f96f174a78adbf717b5 100644 (file)
 #define sr_warn(s, args...) sr_warn(DRIVER_LOG_DOMAIN s, ## args)
 #define sr_err(s, args...) sr_err(DRIVER_LOG_DOMAIN s, ## args)
 
+
+#define DMM_BUFSIZE 256
+
+enum packet_len_status {
+       PACKET_HEADER_OK,
+       PACKET_NEED_MORE_DATA,
+       PACKET_INVALID_HEADER,
+};
+
 /** Private, per-device-instance driver context. */
 struct dev_context {
        /** The current sampling limit (in number of samples). */
@@ -46,8 +55,36 @@ struct dev_context {
 
        /** The current number of already received samples. */
        uint64_t num_samples;
+
+       /** Start time of acquisition session */
+       int64_t starttime;
+
+       struct sr_serial_dev_inst *serial;
+
+       uint8_t buf[DMM_BUFSIZE];
+       int bufoffset;
+       int buflen;
+       int next_packet_len;
 };
 
+/**
+ * Callback that assesses the size and status of the incoming packet
+ *
+ * @return PACKET_HEADER_OK - This is a proper packet header.
+ *         PACKET_NEED_MORE_DATA The buffer does not contain the entire header
+ *         PACKET_INVALID_HEADER Not a valid start of packet.
+ */
+typedef int (*packet_length_t)(const uint8_t *buf, int *len);
+
 SR_PRIV int brymen_dmm_receive_data(int fd, int revents, void *cb_data);
+SR_PRIV int brymen_packet_request(struct sr_serial_dev_inst *serial);
+
+SR_PRIV int brymen_packet_length(const uint8_t *buf, int *len);
+SR_PRIV gboolean brymen_packet_is_valid(const uint8_t *buf);
 
+SR_PRIV int brymen_stream_detect(struct sr_serial_dev_inst *serial,
+                                uint8_t *buf, size_t *buflen,
+                                packet_length_t get_packet_size,
+                                packet_valid_t is_valid,
+                                uint64_t timeout_ms, int baudrate);
 #endif