]> sigrok.org Git - libsigrok.git/commitdiff
icstation-usbrelay: Initial ICStation USBRelay driver.
authorFrank Stettner <redacted>
Sat, 7 Aug 2021 12:16:42 +0000 (14:16 +0200)
committerGerhard Sittig <redacted>
Sun, 19 Feb 2023 16:36:05 +0000 (17:36 +0100)
Implement support for several ICStation USBRelay models with 2, 4, and 8
channels. Allows to identify the cards after power-on, and subsequent
relay control.

IMPORTANT: An implementation detail in the vendor firmware prevents the
host application from identifying the device after entering command mode.
Identification will fail, and the identification request instead gets
mistaken for another relay control request (turns on relays 1-4, 6, 8).
A power cycle is required before the host application can reconnect to
the device.

configure.ac
src/hardware/icstation-usbrelay/api.c
src/hardware/icstation-usbrelay/protocol.c
src/hardware/icstation-usbrelay/protocol.h

index 5202f5f16a1f635dc17112c60b2459e72b389b88..c8c51eb9486a345a1e3fa9db656d01a492e9fd66 100644 (file)
@@ -321,7 +321,7 @@ SR_DRIVER([HP 3457A], [hp-3457a])
 SR_DRIVER([HP 3478A], [hp-3478a], [libgpib])
 SR_DRIVER([hp-59306a], [hp-59306a])
 SR_DRIVER([Hung-Chang DSO-2100], [hung-chang-dso-2100], [libieee1284])
-SR_DRIVER([icstation-usbrelay], [icstation-usbrelay])
+SR_DRIVER([ICStation USBRelay], [icstation-usbrelay], [serial_comm])
 SR_DRIVER([Ikalogic Scanalogic-2], [ikalogic-scanalogic2], [libusb])
 SR_DRIVER([Ikalogic Scanaplus], [ikalogic-scanaplus], [libftdi])
 SR_DRIVER([IPDBG LA], [ipdbg-la])
index 0e329beb2ea5eb6e132b1b047d4f56aa332b6b2b..db576737d8951e6d8f8b3889d363e4666c802ffa 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the libsigrok project.
  *
- * Copyright (C) 2021 Frank Stettner <frank-stettner@gmx.net>
+ * Copyright (C) 2021-2023 Frank Stettner <frank-stettner@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 "protocol.h"
 
-static struct sr_dev_driver icstation_usbrelay_driver_info;
+#define SERIALCOMM "9600/8n1"
 
-static GSList *scan(struct sr_dev_driver *di, GSList *options)
-{
-       struct drv_context *drvc;
-       GSList *devices;
+static const uint32_t scanopts[] = {
+       SR_CONF_CONN,
+       SR_CONF_SERIALCOMM,
+};
 
-       (void)options;
+static const uint32_t drvopts[] = {
+       SR_CONF_MULTIPLEXER,
+};
 
-       devices = NULL;
-       drvc = di->context;
-       drvc->instances = NULL;
+static const uint32_t devopts[] = {
+       SR_CONF_CONN | SR_CONF_GET,
+       SR_CONF_ENABLED | SR_CONF_SET, /* Enable/disable all relays at once. */
+};
 
-       /* TODO: scan for devices, either based on a SR_CONF_CONN option
-        * or on a USB scan. */
+static const uint32_t devopts_cg[] = {
+       SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET,
+};
 
-       return devices;
-}
+static const struct ics_usbrelay_profile supported_ics_usbrelay[] = {
+       { ICSE012A, 0xAB, "ICSE012A", 4 },
+       { ICSE013A, 0xAD, "ICSE013A", 2 },
+       { ICSE014A, 0xAC, "ICSE014A", 8 },
+};
 
-static int dev_open(struct sr_dev_inst *sdi)
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
 {
-       (void)sdi;
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       struct sr_serial_dev_inst *serial;
+       GSList *devices;
+       size_t i, ch_idx;
+       const char *conn, *serialcomm;
+       int ret;
+       uint8_t device_id;
+       const struct ics_usbrelay_profile *profile;
+       struct sr_channel_group *cg;
+       struct channel_group_context *cgc;
 
-       /* TODO: get handle from sdi->conn and open it. */
+       devices = NULL;
 
-       return SR_OK;
-}
+       /* Only scan for a device when conn= was specified. */
+       conn = NULL;
+       serialcomm = SERIALCOMM;
+       if (sr_serial_extract_options(options, &conn, &serialcomm) != SR_OK)
+               return NULL;
+
+       serial = sr_serial_dev_inst_new(conn, serialcomm);
+       if (serial_open(serial, SERIAL_RDWR) != SR_OK)
+               return NULL;
+
+       /* Get device model. */
+       ret = icstation_usbrelay_identify(serial, &device_id);
+       if (ret != SR_OK) {
+               sr_err("Cannot retrieve identification details.");
+               serial_close(serial);
+               return NULL;
+       }
 
-static int dev_close(struct sr_dev_inst *sdi)
-{
-       (void)sdi;
+       for (i = 0; i < ARRAY_SIZE(supported_ics_usbrelay); i++) {
+               profile = &supported_ics_usbrelay[i];
+               if (device_id != profile->id)
+                       continue;
+               sdi = g_malloc0(sizeof(*sdi));
+               sdi->status = SR_ST_INACTIVE;
+               sdi->vendor = g_strdup("ICStation");
+               sdi->model = g_strdup(profile->modelname);
+               sdi->inst_type = SR_INST_SERIAL;
+               sdi->conn = serial;
+               sdi->connection_id = g_strdup(conn);
+
+               devc = g_malloc0(sizeof(*devc));
+               sdi->priv = devc;
+               devc->relay_count = profile->nb_channels;
+               devc->relay_mask = (1U << devc->relay_count) - 1;
+               /* Assume that all relays are off at the start. */
+               devc->relay_state = 0;
+               for (ch_idx = 0; ch_idx < devc->relay_count; ch_idx++) {
+                       cg = g_malloc0(sizeof(*cg));
+                       cg->name = g_strdup_printf("R%zu", ch_idx + 1);
+                       cgc = g_malloc0(sizeof(*cgc));
+                       cg->priv = cgc;
+                       cgc->index = ch_idx;
+                       sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
+               }
+
+               devices = g_slist_append(devices, sdi);
+               break;
+       }
 
-       /* TODO: get handle from sdi->conn and close it. */
+       serial_close(serial);
+       if (!devices) {
+               sr_serial_dev_inst_free(serial);
+               sr_warn("Unknown device identification 0x%02hhx.", device_id);
+       }
 
-       return SR_OK;
+       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)
 {
-       int ret;
-
-       (void)sdi;
-       (void)data;
-       (void)cg;
+       struct dev_context *devc;
+       struct channel_group_context *cgc;
+       uint8_t mask;
+       gboolean on;
+
+       if (!sdi || !data)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+
+       if (!cg) {
+               switch (key) {
+               case SR_CONF_CONN:
+                       *data = g_variant_new_string(sdi->connection_id);
+                       break;
+               default:
+                       return SR_ERR_NA;
+               }
+       }
 
-       ret = SR_OK;
        switch (key) {
-       /* TODO */
+       case SR_CONF_ENABLED:
+               cgc = cg->priv;
+               mask = 1U << cgc->index;
+               on = devc->relay_state & mask;
+               *data = g_variant_new_boolean(on);
+               return SR_OK;
        default:
                return SR_ERR_NA;
        }
 
-       return ret;
+       return SR_OK;
 }
 
 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 icstation_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 icstation_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)
+static int dev_open(struct sr_dev_inst *sdi)
 {
-       /* TODO: stop acquisition. */
+       struct sr_serial_dev_inst *serial;
+       int ret;
 
-       (void)sdi;
+       if (!sdi)
+               return SR_ERR_ARG;
+       serial = sdi->conn;
+       if (!serial)
+               return SR_ERR_ARG;
+
+       ret = std_serial_dev_open(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Start command mode. */
+       ret = icstation_usbrelay_start(sdi);
+       if (ret != SR_OK) {
+               sr_err("Cannot initiate command mode.");
+               serial_close(serial);
+               return SR_ERR_IO;
+       }
 
        return SR_OK;
 }
 
 static struct sr_dev_driver icstation_usbrelay_driver_info = {
        .name = "icstation-usbrelay",
-       .longname = "icstation-usbrelay",
+       .longname = "ICStation USBRelay",
        .api_version = 1,
        .init = std_init,
        .cleanup = std_cleanup,
@@ -146,9 +246,9 @@ static struct sr_dev_driver icstation_usbrelay_driver_info = {
        .config_set = config_set,
        .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_close = std_serial_dev_close,
+       .dev_acquisition_start = std_dummy_dev_acquisition_start,
+       .dev_acquisition_stop = std_dummy_dev_acquisition_stop,
        .context = NULL,
 };
 SR_REGISTER_DEV_DRIVER(icstation_usbrelay_driver_info);
index fa32e70064e5b0af927d3bf721c9f3972ef80cdc..78a999655cec0dd59f82422e84d6457e7fd43b82 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the libsigrok project.
  *
- * Copyright (C) 2021 Frank Stettner <frank-stettner@gmx.net>
+ * Copyright (C) 2021-2023 Frank Stettner <frank-stettner@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 "protocol.h"
 
-SR_PRIV int icstation_usbrelay_receive_data(int fd, int revents, void *cb_data)
+#define SERIAL_TIMEOUT_MS              1000
+
+#define ICSTATION_USBRELAY_CMD_ID      0x50
+#define ICSTATION_USBRELAY_CMD_START   0x51
+
+static int icstation_usbrelay_send_byte(struct sr_serial_dev_inst *serial,
+       uint8_t b)
+{
+       int ret;
+
+       ret = serial_write_blocking(serial, &b, sizeof(b), SERIAL_TIMEOUT_MS);
+       if (ret < SR_OK)
+               return SR_ERR_IO;
+       if ((size_t)ret != sizeof(b))
+               return SR_ERR_IO;
+
+       return SR_OK;
+}
+
+static int icstation_usbrelay_recv_byte(struct sr_serial_dev_inst *serial,
+       uint8_t *b)
+{
+       int ret;
+
+       ret = serial_read_blocking(serial, b, sizeof(*b), SERIAL_TIMEOUT_MS);
+       if (ret < SR_OK)
+               return SR_ERR_IO;
+       if ((size_t)ret != sizeof(*b))
+               return SR_ERR_IO;
+
+       return SR_OK;
+}
+
+SR_PRIV int icstation_usbrelay_identify(struct sr_serial_dev_inst *serial,
+       uint8_t *id)
+{
+       int ret;
+
+       if (!id)
+               return SR_ERR_ARG;
+
+       /*
+        * Send the identification request. Receive the device firmware's
+        * identification response.
+        *
+        * BEWARE!
+        * A vendor firmware implementation detail prevents the host from
+        * identifying the device again once command mode was entered.
+        * The UART protocol provides no means to leave command mode.
+        * The subsequent identification request is mistaken instead as
+        * another relay control request! Identifying the device will fail.
+        * The device must be power cycled before it identifies again.
+        */
+       ret = icstation_usbrelay_send_byte(serial, ICSTATION_USBRELAY_CMD_ID);
+       if (ret != SR_OK) {
+               sr_dbg("Could not send identification request.");
+               return SR_ERR_IO;
+       }
+       ret = icstation_usbrelay_recv_byte(serial, id);
+       if (ret != SR_OK) {
+               sr_dbg("Could not receive identification response.");
+               return SR_ERR_IO;
+       }
+       sr_dbg("Identification response 0x%02hhx.", *id);
+
+       return SR_OK;
+}
+
+SR_PRIV int icstation_usbrelay_start(const struct sr_dev_inst *sdi)
+{
+       struct sr_serial_dev_inst *serial;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       serial = sdi->conn;
+       if (!serial)
+               return SR_ERR_ARG;
+
+       return icstation_usbrelay_send_byte(serial,
+               ICSTATION_USBRELAY_CMD_START);
+}
+
+SR_PRIV int icstation_usbrelay_switch_cg(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, gboolean on)
 {
-       const struct sr_dev_inst *sdi;
        struct dev_context *devc;
+       struct channel_group_context *cgc;
+       uint8_t state, mask;
+       uint8_t tx_state;
 
-       (void)fd;
+       devc = sdi->priv;
 
-       if (!(sdi = cb_data))
-               return TRUE;
+       /*
+        * The device requires the communication of all relay states
+        * at the same time. Calling applications control individual
+        * relays. The device wants active-low state in the physical
+        * transport. Application uses positive logic (active-high).
+        *
+        * Update the locally cached state from the most recent request.
+        * Invert the result and send it to the device. Only update
+        * the internal cache after successful transmission.
+        */
 
-       if (!(devc = sdi->priv))
-               return TRUE;
+       state = devc->relay_state;
+       if (!cg) {
+               /* Set all relays. */
+               if (on)
+                       state |= devc->relay_mask;
+               else
+                       state &= ~devc->relay_mask;
+       } else {
+               cgc = cg->priv;
+               mask = 1UL << cgc->index;
+               if (on)
+                       state |= mask;
+               else
+                       state &= ~mask;
+       }
 
-       if (revents == G_IO_IN) {
-               /* TODO */
+       tx_state = ~state & devc->relay_mask;
+       sr_spew("Sending status byte: %x", tx_state);
+       if (icstation_usbrelay_send_byte(sdi->conn, tx_state) != SR_OK) {
+               sr_err("Unable to send status byte.");
+               return SR_ERR_IO;
        }
 
-       return TRUE;
+       devc->relay_state = state;
+
+       return SR_OK;
 }
index 3c8319d600723f1092a16c7c946c736e59f736f8..bcf9dd07021593e64d2b90aa9c55704acb9fcf66 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the libsigrok project.
  *
- * Copyright (C) 2021 Frank Stettner <frank-stettner@gmx.net>
+ * Copyright (C) 2021-2023 Frank Stettner <frank-stettner@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
 #ifndef LIBSIGROK_HARDWARE_ICSTATION_USBRELAY_PROTOCOL_H
 #define LIBSIGROK_HARDWARE_ICSTATION_USBRELAY_PROTOCOL_H
 
-#include <stdint.h>
 #include <glib.h>
 #include <libsigrok/libsigrok.h>
+#include <stdint.h>
+
 #include "libsigrok-internal.h"
 
 #define LOG_PREFIX "icstation-usbrelay"
 
+/* Known models. */
+enum icstation_model {
+       ICSE012A = 1,
+       ICSE013A,
+       ICSE014A,
+};
+
+/* Supported device profiles */
+struct ics_usbrelay_profile {
+       enum icstation_model model;
+       uint8_t id;
+       const char *modelname;
+       size_t nb_channels;
+};
+
 struct dev_context {
+       size_t relay_count;
+       uint8_t relay_mask;
+       uint8_t relay_state;
+};
+
+struct channel_group_context {
+       size_t index;
 };
 
-SR_PRIV int icstation_usbrelay_receive_data(int fd, int revents, void *cb_data);
+SR_PRIV int icstation_usbrelay_identify(struct sr_serial_dev_inst *serial,
+       uint8_t *id);
+SR_PRIV int icstation_usbrelay_start(const struct sr_dev_inst *sdi);
+SR_PRIV int icstation_usbrelay_switch_cg(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, gboolean on);
 
 #endif