]> sigrok.org Git - libsigrok.git/blobdiff - src/hardware/dcttech-usbrelay/api.c
dcttech-usbrelay: support conn=vid.pid specs in addition to paths
[libsigrok.git] / src / hardware / dcttech-usbrelay / api.c
index 9751ef5b022f5f494036bf516580e7cf997f9a0c..a24025ec84376878d17c778bb4808f4fd622547a 100644 (file)
  */
 
 #include <config.h>
+
+#include <ctype.h>
+#include <hidapi.h>
+#include <string.h>
+
 #include "protocol.h"
 
+static const uint32_t scanopts[] = {
+       SR_CONF_CONN,
+};
+
+static const uint32_t drvopts[] = {
+       SR_CONF_MULTIPLEXER,
+};
+
+static const uint32_t devopts[] = {
+       SR_CONF_CONN | SR_CONF_GET,
+       SR_CONF_ENABLED | SR_CONF_SET, /* Enable/disable all relays at once. */
+};
+
+static const uint32_t devopts_cg[] = {
+       SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET,
+};
+
 static struct sr_dev_driver dcttech_usbrelay_driver_info;
 
+static struct sr_dev_inst *probe_device_common(const char *path,
+       uint16_t vid, uint16_t pid,
+       const wchar_t *vendor, const wchar_t *product)
+{
+       char nonws[16], *s, *endp;
+       unsigned long relay_count;
+       hid_device *hid;
+       int ret;
+       char serno[SERNO_LENGTH + 1];
+       uint8_t curr_state;
+       uint8_t report[1 + REPORT_BYTECOUNT];
+       GString *txt;
+       size_t snr_pos;
+       char c;
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       struct channel_group_context *cgc;
+       size_t idx, nr;
+       struct sr_channel_group *cg;
+
+       /*
+        * Get relay count from product string. Weak condition,
+        * accept any trailing number regardless of preceeding text.
+        */
+       snprintf(nonws, sizeof(nonws), "%ls", product);
+       s = nonws;
+       s += strlen(s);
+       while (s > nonws && isdigit((int)s[-1]))
+               s--;
+       ret = sr_atoul_base(s, &relay_count, &endp, 10);
+       if (ret != SR_OK || !endp || *endp)
+               return NULL;
+       if (!relay_count)
+               return NULL;
+       sr_info("Relay count %lu from product string %s.", relay_count, nonws);
+
+       /* Open device, need to communicate to identify. */
+       if (vid && pid)
+               hid = hid_open(vid, pid, NULL);
+       else
+               hid = hid_open_path(path);
+       if (!hid) {
+               sr_err("Cannot open %s.", path);
+               return NULL;
+       }
+
+       /* Get an HID report. */
+       hid_set_nonblocking(hid, 0);
+       memset(&report, 0, sizeof(report));
+       report[0] = REPORT_NUMBER;
+       ret = hid_get_feature_report(hid, report, sizeof(report));
+       hid_close(hid);
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               txt = sr_hexdump_new(report, sizeof(report));
+               sr_spew("Got report bytes: %s, rc %d.", txt->str, ret);
+               sr_hexdump_free(txt);
+       }
+       if (ret < 0) {
+               sr_err("Cannot read %s: %ls.", path, hid_error(NULL));
+               return NULL;
+       }
+       if (ret != sizeof(report)) {
+               sr_err("Unexpected HID report length %d from %s.", ret, path);
+               return NULL;
+       }
+
+       /*
+        * Serial number must be all printable characters. Relay state
+        * is for information only, gets re-retrieved before configure
+        * API calls (get/set).
+        */
+       memset(serno, 0, sizeof(serno));
+       for (snr_pos = 0; snr_pos < SERNO_LENGTH; snr_pos++) {
+               c = report[1 + snr_pos];
+               serno[snr_pos] = c;
+               if (c < 0x20 || c > 0x7e) {
+                       sr_warn("Skipping %s, non-printable serial.", path);
+                       return NULL;
+               }
+       }
+       curr_state = report[1 + STATE_INDEX];
+       sr_info("HID report data: serial number %s, relay state 0x%02x.",
+               serno, curr_state);
+
+       /* Create a device instance. */
+       sdi = g_malloc0(sizeof(*sdi));
+       sdi->vendor = g_strdup_printf("%ls", vendor);
+       sdi->model = g_strdup_printf("%ls", product);
+       sdi->serial_num = g_strdup(serno);
+       sdi->connection_id = g_strdup(path);
+       sdi->driver = &dcttech_usbrelay_driver_info;
+       sdi->inst_type = SR_INST_USB;
+
+       /* Create channels (groups). */
+       devc = g_malloc0(sizeof(*devc));
+       sdi->priv = devc;
+       devc->hid_path = g_strdup(path);
+       devc->usb_vid = vid;
+       devc->usb_pid = pid;
+       devc->relay_count = relay_count;
+       devc->relay_mask = (1U << relay_count) - 1;
+       for (idx = 0; idx < devc->relay_count; idx++) {
+               nr = idx + 1;
+               cg = g_malloc0(sizeof(*cg));
+               cg->name = g_strdup_printf("R%zu", nr);
+               cgc = g_malloc0(sizeof(*cgc));
+               cg->priv = cgc;
+               cgc->number = nr;
+               sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
+       }
+
+       return sdi;
+}
+
+static struct sr_dev_inst *probe_device_enum(struct hid_device_info *dev)
+{
+       return probe_device_common(dev->path, 0, 0,
+               dev->manufacturer_string, dev->product_string);
+}
+
+static struct sr_dev_inst *probe_device_conn(const char *path)
+{
+       char vid_pid[12];
+       uint16_t vid, pid;
+       const char *s;
+       char *endp;
+       unsigned long num;
+       hid_device *dev;
+       gboolean ok;
+       int ret;
+       wchar_t vendor[32], product[32];
+
+       /*
+        * The hidapi(3) library's API strives for maximum portability,
+        * thus won't provide ways of getting a path from alternative
+        * presentations like VID:PID pairs, bus.addr specs, etc. The
+        * typical V-USB setup neither provides reliable serial numbers
+        * (that USB enumeration would cover). So this driver's support
+        * for conn= specs beyond Unix style path names is limited, too.
+        * This implementation tries "VID.PID" then assumes "path". The
+        * inability to even get the path for a successfully opened HID
+        * results in redundancy across the places which open devices.
+        */
+
+       /* Check for "<vid>.<pid>" specs. */
+       vid = pid = 0;
+       s = path;
+       ret = sr_atoul_base(s, &num, &endp, 16);
+       if (ret == SR_OK && endp && endp == s + 4 && *endp == '.' && num) {
+               vid = num;
+               s = ++endp;
+       }
+       ret = sr_atoul_base(s, &num, &endp, 16);
+       if (ret == SR_OK && endp && endp == s + 4 && *endp == '\0' && num) {
+               pid = num;
+               s = ++endp;
+       }
+       if (vid && pid) {
+               snprintf(vid_pid, sizeof(vid_pid), "%04x.%04x", vid, pid);
+               path = vid_pid;
+               sr_dbg("Using VID.PID %s.", path);
+       }
+
+       /* Open the device, get vendor and product strings. */
+       if (vid && pid)
+               dev = hid_open(vid, pid, NULL);
+       else
+               dev = hid_open_path(path);
+       if (!dev) {
+               sr_err("Cannot open %s.", path);
+               return NULL;
+       }
+       ok = TRUE;
+       ret = hid_get_manufacturer_string(dev, vendor, ARRAY_SIZE(vendor));
+       if (ret != 0)
+               ok = FALSE;
+       if (!wcslen(vendor))
+               ok = FALSE;
+       ret = hid_get_product_string(dev, product, ARRAY_SIZE(product));
+       if (ret != 0)
+               ok = FALSE;
+       if (!wcslen(product))
+               ok = FALSE;
+       hid_close(dev);
+       if (!ok)
+               return NULL;
+
+       return probe_device_common(path, vid, pid, vendor, product);
+}
+
 static GSList *scan(struct sr_dev_driver *di, GSList *options)
 {
-       struct drv_context *drvc;
+       const char *conn;
        GSList *devices;
+       struct drv_context *drvc;
+       struct hid_device_info *devs, *curdev;
+       wchar_t *ws;
+       char nonws[32];
+       struct sr_dev_inst *sdi;
 
-       (void)options;
+       /* Get optional conn= spec when provided. */
+       conn = NULL;
+       (void)sr_serial_extract_options(options, &conn, NULL);
+       if (conn && !*conn)
+               conn = NULL;
 
        devices = NULL;
        drvc = di->context;
        drvc->instances = NULL;
 
-       /* TODO: scan for devices, either based on a SR_CONF_CONN option
-        * or on a USB scan. */
+       /*
+        * The firmware is V-USB based. The USB VID:PID identification
+        * is shared across several projects. Need to inspect the vendor
+        * and product _strings_ to actually identify the device. The
+        * USB serial number need not be present nor reliable. The HID
+        * report content will carry the board's serial number.
+        *
+        * When conn= was specified, then have HIDAPI open _this_ device
+        * and skip the enumeration. Which allows users to specify paths
+        * that need not match the enumeration's details.
+        */
+       if (conn) {
+               sr_info("Checking HID path %s.", conn);
+               sdi = probe_device_conn(conn);
+               if (!sdi)
+                       sr_warn("Failed to communicate to %s.", conn);
+               else
+                       devices = g_slist_append(devices, sdi);
+       }
+       devs = hid_enumerate(VENDOR_ID, PRODUCT_ID);
+       for (curdev = devs; curdev; curdev = curdev->next) {
+               if (conn)
+                       break;
+               if (!curdev->vendor_id || !curdev->product_id)
+                       continue;
+               if (!curdev->manufacturer_string || !curdev->product_string)
+                       continue;
+               if (!*curdev->manufacturer_string || !*curdev->product_string)
+                       continue;
+               sr_dbg("Checking %04hx:%04hx, vendor %ls, product %ls.",
+                       curdev->vendor_id, curdev->product_id,
+                       curdev->manufacturer_string, curdev->product_string);
+
+               /* Check USB details retrieved by enumeration. */
+               ws = curdev->manufacturer_string;
+               if (!ws || !wcslen(ws))
+                       continue;
+               snprintf(nonws, sizeof(nonws), "%ls", ws);
+               if (strcmp(nonws, VENDOR_STRING) != 0)
+                       continue;
+               ws = curdev->product_string;
+               if (!ws || !wcslen(ws))
+                       continue;
+               snprintf(nonws, sizeof(nonws), "%ls", ws);
+               if (!g_str_has_prefix(nonws, PRODUCT_STRING_PREFIX))
+                       continue;
+
+               /* Identify device by communicating to it. */
+               sr_info("Checking HID path %s.", curdev->path);
+               sdi = probe_device_enum(curdev);
+               if (!sdi) {
+                       sr_warn("Failed to communicate to %s.", curdev->path);
+                       continue;
+               }
+               devices = g_slist_append(devices, sdi);
+       }
+       hid_free_enumeration(devs);
 
        return devices;
 }
 
 static int dev_open(struct sr_dev_inst *sdi)
 {
-       (void)sdi;
+       struct dev_context *devc;
+
+       devc = sdi->priv;
+
+       if (devc->hid_dev) {
+               hid_close(devc->hid_dev);
+               devc->hid_dev = NULL;
+       }
+
+       if (devc->usb_vid && devc->usb_pid)
+               devc->hid_dev = hid_open(devc->usb_vid, devc->usb_pid, NULL);
+       else
+               devc->hid_dev = hid_open_path(devc->hid_path);
+       if (!devc->hid_dev)
+               return SR_ERR_IO;
 
-       /* TODO: get handle from sdi->conn and open it. */
+       (void)dcttech_usbrelay_update_state(sdi);
 
        return SR_OK;
 }
 
 static int dev_close(struct sr_dev_inst *sdi)
 {
-       (void)sdi;
+       struct dev_context *devc;
 
-       /* TODO: get handle from sdi->conn and close it. */
+       devc = sdi->priv;
+
+       if (devc->hid_dev) {
+               hid_close(devc->hid_dev);
+               devc->hid_dev = NULL;
+       }
 
        return SR_OK;
 }
@@ -60,77 +355,82 @@ static int dev_close(struct sr_dev_inst *sdi)
 static int config_get(uint32_t key, GVariant **data,
        const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
+       gboolean on;
        int ret;
 
-       (void)sdi;
-       (void)data;
-       (void)cg;
+       if (!cg) {
+               switch (key) {
+               case SR_CONF_CONN:
+                       if (!sdi->connection_id)
+                               return SR_ERR_NA;
+                       *data = g_variant_new_string(sdi->connection_id);
+                       return SR_OK;
+               default:
+                       return SR_ERR_NA;
+               }
+       }
 
-       ret = SR_OK;
        switch (key) {
-       /* TODO */
+       case SR_CONF_ENABLED:
+               ret = dcttech_usbrelay_query_cg(sdi, cg, &on);
+               if (ret != SR_OK)
+                       return ret;
+               *data = g_variant_new_boolean(on);
+               return SR_OK;
        default:
                return SR_ERR_NA;
        }
-
-       return ret;
 }
 
 static int config_set(uint32_t key, GVariant *data,
        const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
-       int ret;
-
-       (void)sdi;
-       (void)data;
-       (void)cg;
-
-       ret = SR_OK;
-       switch (key) {
-       /* TODO */
-       default:
-               ret = SR_ERR_NA;
+       gboolean on;
+
+       if (!cg) {
+               switch (key) {
+               case SR_CONF_ENABLED:
+                       /* Enable/disable all channels at the same time. */
+                       on = g_variant_get_boolean(data);
+                       return dcttech_usbrelay_switch_cg(sdi, cg, on);
+               default:
+                       return SR_ERR_NA;
+               }
+       } else {
+               switch (key) {
+               case SR_CONF_ENABLED:
+                       on = g_variant_get_boolean(data);
+                       return dcttech_usbrelay_switch_cg(sdi, cg, on);
+               default:
+                       return SR_ERR_NA;
+               }
        }
 
-       return ret;
+       return SR_OK;
 }
 
 static int config_list(uint32_t key, GVariant **data,
        const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
-       int ret;
 
-       (void)sdi;
-       (void)data;
-       (void)cg;
+       if (!cg) {
+               switch (key) {
+               case SR_CONF_SCAN_OPTIONS:
+               case SR_CONF_DEVICE_OPTIONS:
+                       return STD_CONFIG_LIST(key, data, sdi, cg,
+                               scanopts, drvopts, devopts);
+               default:
+                       return SR_ERR_NA;
+               }
+       }
 
-       ret = SR_OK;
        switch (key) {
-       /* TODO */
+       case SR_CONF_DEVICE_OPTIONS:
+               *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg));
+               return SR_OK;
        default:
                return SR_ERR_NA;
        }
-
-       return ret;
-}
-
-static int dev_acquisition_start(const struct sr_dev_inst *sdi)
-{
-       /* TODO: configure hardware, reset acquisition state, set up
-        * callbacks and send header packet. */
-
-       (void)sdi;
-
-       return SR_OK;
-}
-
-static int dev_acquisition_stop(struct sr_dev_inst *sdi)
-{
-       /* TODO: stop acquisition. */
-
-       (void)sdi;
-
-       return SR_OK;
 }
 
 static struct sr_dev_driver dcttech_usbrelay_driver_info = {
@@ -147,8 +447,8 @@ static struct sr_dev_driver dcttech_usbrelay_driver_info = {
        .config_list = config_list,
        .dev_open = dev_open,
        .dev_close = dev_close,
-       .dev_acquisition_start = dev_acquisition_start,
-       .dev_acquisition_stop = dev_acquisition_stop,
+       .dev_acquisition_start = std_dummy_dev_acquisition_start,
+       .dev_acquisition_stop = std_dummy_dev_acquisition_stop,
        .context = NULL,
 };
 SR_REGISTER_DEV_DRIVER(dcttech_usbrelay_driver_info);