]> sigrok.org Git - libsigrok.git/blobdiff - src/hardware/serial-lcr/api.c
serial: extend stream detect for variable length packet checkers
[libsigrok.git] / src / hardware / serial-lcr / api.c
index bbec3809df006722b06ca98c81ea94582445fcbc..7f3fc5362ae1b980991857be4b88d7d66d2be0ad 100644 (file)
@@ -2,6 +2,7 @@
  * This file is part of the libsigrok project.
  *
  * Copyright (C) 2014 Janne Huttunen <jahuttun@gmail.com>
+ * Copyright (C) 2019 Gerhard Sittig <gerhard.sittig@gmx.net>
  *
  * 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
  */
 
 #include <config.h>
-#include <stdint.h>
-#include <string.h>
-#include <math.h>
 #include <glib.h>
 #include <libsigrok/libsigrok.h>
 #include "libsigrok-internal.h"
+#include <math.h>
+#include "protocol.h"
+#include <stdint.h>
+#include <string.h>
 
-#define LOG_PREFIX "serial-lcr-es51919"
+static const uint32_t scanopts[] = {
+       SR_CONF_CONN,
+       SR_CONF_SERIALCOMM,
+};
+
+static const uint32_t drvopts[] = {
+       SR_CONF_LCRMETER,
+};
 
-struct lcr_es51919_info {
-       struct sr_dev_driver di;
-       const char *vendor;
-       const char *model;
+static const uint32_t devopts[] = {
+       SR_CONF_CONTINUOUS,
+       SR_CONF_LIMIT_FRAMES | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_LIMIT_MSEC | SR_CONF_SET,
+       SR_CONF_OUTPUT_FREQUENCY | SR_CONF_GET | SR_CONF_LIST,
+       SR_CONF_EQUIV_CIRCUIT_MODEL | SR_CONF_GET | SR_CONF_LIST,
 };
 
-static int dev_clear(const struct sr_dev_driver *di)
+static struct sr_dev_inst *scan_packet_check_devinst;
+
+static void scan_packet_check_setup(struct sr_dev_inst *sdi)
+{
+       scan_packet_check_devinst = sdi;
+}
+
+static gboolean scan_packet_check_func(const uint8_t *buf)
+{
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       const struct lcr_info *lcr;
+       struct lcr_parse_info *info;
+
+       /* Get a reference to the LCR model that is getting checked. */
+       sdi = scan_packet_check_devinst;
+       if (!sdi)
+               return FALSE;
+       devc = sdi->priv;
+       if (!devc)
+               return FALSE;
+       lcr = devc->lcr_info;
+       if (!lcr)
+               return FALSE;
+
+       /* Synchronize to the stream of LCR packets. */
+       if (!lcr->packet_valid(buf))
+               return FALSE;
+
+       /* Have LCR packets _processed_, gather current configuration. */
+       info = &devc->parse_info;
+       memset(info, 0, sizeof(*info));
+       if (lcr->packet_parse(buf, NULL, NULL, info) == SR_OK) {
+                devc->output_freq = info->output_freq;
+                if (info->circuit_model)
+                        devc->circuit_model = info->circuit_model;
+        }
+
+       return TRUE;
+}
+
+static int scan_lcr_port(const struct lcr_info *lcr,
+       const char *conn, struct sr_serial_dev_inst *serial)
 {
-       return std_dev_clear_with_callback(di, es51919_serial_clean);
+       size_t len;
+       uint8_t buf[128];
+       int ret;
+       size_t dropped;
+
+       if (serial_open(serial, SERIAL_RDWR) != SR_OK)
+               return SR_ERR_IO;
+       sr_info("Probing serial port %s.", conn);
+
+       /*
+        * See if we can detect a device of specified type.
+        *
+        * No supported device provides a means to "identify" yet. No
+        * supported device requires "packet request" yet. They all just
+        * send data periodically. So we check if the packets match the
+        * probed device's expected format.
+        */
+       if (lcr->packet_request) {
+               ret = lcr->packet_request(serial);
+               if (ret < 0) {
+                       sr_err("Failed to request packet: %d.", ret);
+                       goto scan_port_cleanup;
+               }
+       }
+       len = sizeof(buf);
+       ret = serial_stream_detect(serial, buf, &len,
+               lcr->packet_size, lcr->packet_valid, NULL, NULL, 3000);
+       if (ret != SR_OK)
+               goto scan_port_cleanup;
+
+       /*
+        * If the packets were found to match after more than two packets
+        * got dropped, something is wrong. This is worth warning about,
+        * but isn't fatal. The dropped bytes might be due to nonstandard
+        * cables that ship with some devices.
+        */
+       dropped = len - lcr->packet_size;
+       if (dropped > 2 * lcr->packet_size)
+               sr_warn("Had to drop unexpected amounts of data.");
+
+       /* Create a device instance for the found device. */
+       sr_info("Found %s %s device on port %s.", lcr->vendor, lcr->model, conn);
+
+scan_port_cleanup:
+       /* Keep serial port open if probe succeeded. */
+       if (ret != SR_OK)
+               serial_close(serial);
+
+       return ret;
+}
+
+static struct sr_dev_inst *create_lcr_sdi(struct lcr_info *lcr,
+       struct sr_serial_dev_inst *serial)
+{
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       size_t ch_idx;
+       const char **ch_fmts;
+       const char *fmt;
+       char ch_name[8];
+
+       sdi = g_malloc0(sizeof(*sdi));
+       sdi->status = SR_ST_INACTIVE;
+       sdi->vendor = g_strdup(lcr->vendor);
+       sdi->model = g_strdup(lcr->model);
+       sdi->inst_type = SR_INST_SERIAL;
+       sdi->conn = serial;
+       devc = g_malloc0(sizeof(*devc));
+       sdi->priv = devc;
+       devc->lcr_info = lcr;
+       sr_sw_limits_init(&devc->limits);
+       ch_fmts = lcr->channel_formats;
+       for (ch_idx = 0; ch_idx < lcr->channel_count; ch_idx++) {
+               fmt = (ch_fmts && ch_fmts[ch_idx]) ? ch_fmts[ch_idx] : "P%zu";
+               snprintf(ch_name, sizeof(ch_name), fmt, ch_idx + 1);
+               sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, ch_name);
+       }
+
+       return sdi;
+}
+
+static int read_lcr_port(struct sr_dev_inst *sdi,
+       const struct lcr_info *lcr, struct sr_serial_dev_inst *serial)
+{
+       size_t len;
+       uint8_t buf[128];
+       int ret;
+
+       serial_flush(serial);
+       if (lcr->packet_request) {
+               ret = lcr->packet_request(serial);
+               if (ret < 0) {
+                       sr_err("Failed to request packet: %d.", ret);
+                       return ret;
+               }
+       }
+
+       /*
+        * Receive a few more packets (and process them!) to have the
+        * current output frequency and circuit model parameter values
+        * detected. The above "stream detect" phase only synchronized
+        * to the packets by checking their validity, but it cannot
+        * provide details. This phase here runs a modified "checker"
+        * routine which also extracts details from LCR packets after
+        * the device got detected and parameter storage was prepared.
+        */
+       sr_info("Retrieving current acquisition parameters.");
+       len = sizeof(buf);
+       scan_packet_check_setup(sdi);
+       ret = serial_stream_detect(serial, buf, &len,
+               lcr->packet_size, scan_packet_check_func, NULL, NULL, 1500);
+       scan_packet_check_setup(NULL);
+
+       return ret;
 }
 
 static GSList *scan(struct sr_dev_driver *di, GSList *options)
 {
-       struct lcr_es51919_info *lcr;
+       struct lcr_info *lcr;
+       struct sr_config *src;
+       GSList *l, *devices;
+       const char *conn, *serialcomm;
+       struct sr_serial_dev_inst *serial;
+       int ret;
        struct sr_dev_inst *sdi;
 
-       lcr = (struct lcr_es51919_info *)di;
+       lcr = (struct lcr_info *)di;
 
-       if (!(sdi = es51919_serial_scan(options, lcr->vendor, lcr->model)))
+       /* Get serial port name and communication parameters. */
+       conn = NULL;
+       serialcomm = lcr->comm;
+       for (l = options; l; l = l->next) {
+               src = l->data;
+               switch (src->key) {
+               case SR_CONF_CONN:
+                       conn = g_variant_get_string(src->data, NULL);
+                       break;
+               case SR_CONF_SERIALCOMM:
+                       serialcomm = g_variant_get_string(src->data, NULL);
+                       break;
+               }
+       }
+       if (!conn)
                return NULL;
 
-       return std_scan_complete(di, g_slist_append(NULL, sdi));
+       devices = NULL;
+       /* TODO Handle ambiguous conn= specs, see serial-dmm. */
+
+       /* Open the serial port, check data packets. */
+       serial = sr_serial_dev_inst_new(conn, serialcomm);
+       ret = scan_lcr_port(lcr, conn, serial);
+       if (ret != SR_OK) {
+               /* Probe failed, release 'serial'. */
+               sr_serial_dev_inst_free(serial);
+       } else {
+               /* Create and return device instance, keep 'serial' alive. */
+               sdi = create_lcr_sdi(lcr, serial);
+               devices = g_slist_append(devices, sdi);
+               (void)read_lcr_port(sdi, lcr, serial);
+               serial_close(serial);
+       }
+
+       return std_scan_complete(di, devices);
+}
+
+static int config_get(uint32_t key, GVariant **data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct dev_context *devc;
+       const struct lcr_info *lcr;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+
+       switch (key) {
+       case SR_CONF_LIMIT_FRAMES:
+       case SR_CONF_LIMIT_MSEC:
+               return sr_sw_limits_config_get(&devc->limits, key, data);
+       case SR_CONF_OUTPUT_FREQUENCY:
+               *data = g_variant_new_double(devc->output_freq);
+               return SR_OK;
+       case SR_CONF_EQUIV_CIRCUIT_MODEL:
+               if (!devc->circuit_model)
+                       return SR_ERR_NA;
+               *data = g_variant_new_string(devc->circuit_model);
+               return SR_OK;
+       default:
+               lcr = devc->lcr_info;
+               if (!lcr)
+                       return SR_ERR_NA;
+               if (!lcr->config_get)
+                       return SR_ERR_NA;
+               return lcr->config_get(key, data, sdi, cg);
+       }
+       /* UNREACH */
+}
+
+static int config_set(uint32_t key, GVariant *data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct dev_context *devc;
+       const struct lcr_info *lcr;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+
+       switch (key) {
+       case SR_CONF_LIMIT_FRAMES:
+       case SR_CONF_LIMIT_MSEC:
+               return sr_sw_limits_config_set(&devc->limits, key, data);
+       default:
+               lcr = devc->lcr_info;
+               if (!lcr)
+                       return SR_ERR_NA;
+               if (!lcr->config_set)
+                       return SR_ERR_NA;
+               return lcr->config_set(key, data, sdi, cg);
+       }
+       /* UNREACH */
+}
+
+static int config_list(uint32_t key, GVariant **data,
+       const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       struct dev_context *devc;
+       const struct lcr_info *lcr;
+
+       switch (key) {
+       case SR_CONF_SCAN_OPTIONS:
+       case SR_CONF_DEVICE_OPTIONS:
+               return STD_CONFIG_LIST(key, data, sdi, cg,
+                       scanopts, drvopts, devopts);
+       default:
+               break;
+       }
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       switch (key) {
+       default:
+               lcr = devc->lcr_info;
+               if (!lcr || !lcr->config_list)
+                       return SR_ERR_NA;
+               return lcr->config_list(key, data, sdi, cg);
+       }
+       /* UNREACH */
+}
+
+static int dev_acquisition_start(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct sr_serial_dev_inst *serial;
+
+       devc = sdi->priv;
+
+       /*
+        * Clear values that were gathered during scan or in a previous
+        * acquisition, so that this acquisition's data feed immediately
+        * starts with meta packets before first measurement values, and
+        * also communicates subsequent parameter changes.
+        */
+       devc->output_freq = 0;
+       devc->circuit_model = NULL;
+
+       sr_sw_limits_acquisition_start(&devc->limits);
+       std_session_send_df_header(sdi);
+
+       serial = sdi->conn;
+       serial_source_add(sdi->session, serial, G_IO_IN, 50,
+               lcr_receive_data, (void *)sdi);
+
+       return SR_OK;
 }
 
 #define LCR_ES51919(id, vendor, model) \
-       &((struct lcr_es51919_info) { \
+       &((struct lcr_info) { \
                { \
                        .name = id, \
                        .longname = vendor " " model, \
@@ -61,17 +375,21 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
                        .cleanup = std_cleanup, \
                        .scan = scan, \
                        .dev_list = std_dev_list, \
-                       .dev_clear = dev_clear, \
-                       .config_get = es51919_serial_config_get, \
-                       .config_set = es51919_serial_config_set, \
-                       .config_list = es51919_serial_config_list, \
+                       .dev_clear = std_dev_clear, \
+                       .config_get = config_get, \
+                       .config_set = config_set, \
+                       .config_list = config_list, \
                        .dev_open = std_serial_dev_open, \
                        .dev_close = std_serial_dev_close, \
-                       .dev_acquisition_start = es51919_serial_acquisition_start, \
+                       .dev_acquisition_start = dev_acquisition_start, \
                        .dev_acquisition_stop = std_serial_dev_acquisition_stop, \
                        .context = NULL, \
                }, \
-               vendor, model, \
+               vendor, model, ES51919_CHANNEL_COUNT, NULL, \
+               ES51919_COMM_PARAM, ES51919_PACKET_SIZE, \
+               0, NULL, \
+               es51919_packet_valid, es51919_packet_parse, \
+               NULL, NULL, es51919_config_list, \
        }).di
 
 SR_REGISTER_DEV_DRIVER_LIST(lcr_es51919_drivers,
@@ -80,3 +398,36 @@ SR_REGISTER_DEV_DRIVER_LIST(lcr_es51919_drivers,
        LCR_ES51919("peaktech-2170", "PeakTech", "2170"),
        LCR_ES51919("uni-t-ut612", "UNI-T", "UT612"),
 );
+
+#define LCR_VC4080(id, vendor, model) \
+       &((struct lcr_info) { \
+               { \
+                       .name = id, \
+                       .longname = vendor " " model, \
+                       .api_version = 1, \
+                       .init = std_init, \
+                       .cleanup = std_cleanup, \
+                       .scan = scan, \
+                       .dev_list = std_dev_list, \
+                       .dev_clear = std_dev_clear, \
+                       .config_get = config_get, \
+                       .config_set = config_set, \
+                       .config_list = config_list, \
+                       .dev_open = std_serial_dev_open, \
+                       .dev_close = std_serial_dev_close, \
+                       .dev_acquisition_start = dev_acquisition_start, \
+                       .dev_acquisition_stop = std_serial_dev_acquisition_stop, \
+                       .context = NULL, \
+               }, \
+               vendor, model, \
+               VC4080_CHANNEL_COUNT, vc4080_channel_formats, \
+               VC4080_COMM_PARAM, VC4080_PACKET_SIZE, \
+               500, vc4080_packet_request, \
+               vc4080_packet_valid, vc4080_packet_parse, \
+               NULL, NULL, vc4080_config_list, \
+       }).di
+
+SR_REGISTER_DEV_DRIVER_LIST(lcr_vc4080_drivers,
+       LCR_VC4080("peaktech-2165", "PeakTech", "2165"),
+       LCR_VC4080("voltcraft-4080", "Voltcraft", "4080"),
+);