From 601fb67cc4977d7e04e20916bde52147b35df970 Mon Sep 17 00:00:00 2001 From: Alexandru Gagniuc Date: Mon, 26 Nov 2012 17:09:05 -0600 Subject: [PATCH] brymen-dmm: Add support for Brymen BM857 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 --- hardware/brymen-dmm/Makefile.am | 1 + hardware/brymen-dmm/api.c | 289 ++++++++++++++++++++++++++------ hardware/brymen-dmm/parser.c | 279 ++++++++++++++++++++++++++++++ hardware/brymen-dmm/protocol.c | 227 ++++++++++++++++++++++++- hardware/brymen-dmm/protocol.h | 39 ++++- 5 files changed, 776 insertions(+), 59 deletions(-) create mode 100644 hardware/brymen-dmm/parser.c diff --git a/hardware/brymen-dmm/Makefile.am b/hardware/brymen-dmm/Makefile.am index baec07a2..17c5afa1 100644 --- a/hardware/brymen-dmm/Makefile.am +++ b/hardware/brymen-dmm/Makefile.am @@ -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 diff --git a/hardware/brymen-dmm/api.c b/hardware/brymen-dmm/api.c index 2a524c84..697959f4 100644 --- a/hardware/brymen-dmm/api.c +++ b/hardware/brymen-dmm/api.c @@ -18,42 +18,31 @@ */ #include +#include +#include +#include #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; -/* 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); - drvc->instances = NULL; - - return SR_OK; -} - -static int hw_init(void) +static int hw_init(struct sr_context *sr_ctx) { struct drv_context *drvc; @@ -62,17 +51,107 @@ static int hw_init(void) return SR_ERR_MALLOC; } - /* TODO */ - + 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 drv_context *drvc; + + if (!(drvc = di->priv)) + return SR_OK; + + g_slist_free_full(drvc->instances, free_instance); + drvc->instances = NULL; + + return SR_OK; +} + +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 (!(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; + } + + uint8_t buf[128]; + size_t len = 128; + + ret = brymen_stream_detect(serial, buf, &len, brymen_packet_length, + brymen_packet_is_valid, 1000, 9600); + if (ret != SR_OK) + goto scan_cleanup; + + 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 index 00000000..b2c118b2 --- /dev/null +++ b/hardware/brymen-dmm/parser.c @@ -0,0 +1,279 @@ +/* + * This file is part of the sigrok project. + * + * Copyright (C) 2012 Alexandru Gagniuc + * + * 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 . + */ + +#include "protocol.h" +#include +#include +#include + +/* + * 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 diff --git a/hardware/brymen-dmm/protocol.c b/hardware/brymen-dmm/protocol.c index cf59ee25..41bcd4c5 100644 --- a/hardware/brymen-dmm/protocol.c +++ b/hardware/brymen-dmm/protocol.c @@ -17,16 +17,109 @@ * along with this program. If not, see . */ -#include -#include #include "libsigrok.h" #include "libsigrok-internal.h" #include "protocol.h" +#include + +/* --- 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; +} diff --git a/hardware/brymen-dmm/protocol.h b/hardware/brymen-dmm/protocol.h index ee59fd7d..7c981cc1 100644 --- a/hardware/brymen-dmm/protocol.h +++ b/hardware/brymen-dmm/protocol.h @@ -33,6 +33,15 @@ #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; }; -SR_PRIV int brymen_dmm_receive_data(int fd, int revents, void *cb_data); +/** + * 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