]> sigrok.org Git - libsigrok.git/commitdiff
devantech-eth008: first driver implementation, relay output only
authorGerhard Sittig <redacted>
Sun, 24 Sep 2023 09:05:08 +0000 (11:05 +0200)
committerGerhard Sittig <redacted>
Mon, 25 Sep 2023 17:30:18 +0000 (19:30 +0200)
Implement common support code to communicate raw binary requests and
responses over TCP sockets, and ETH008 specific requests to control
up to 8 relays as well as getting the card's supply voltage.

Other cards with differing channel counts, and with features beyond
relay control, have not been available during creation of this driver.
Their addition is prepared but untested. Password protection is not
implemented, but outlined in developer comments.

configure.ac
src/hardware/devantech-eth008/api.c
src/hardware/devantech-eth008/protocol.c
src/hardware/devantech-eth008/protocol.h

index 4102e74951ae7f408ef47ec7a0a722094581e6d8..8b051b8866b8ebd518566620ec3e88637476cf99 100644 (file)
@@ -306,7 +306,7 @@ SR_DRIVER([Colead SLM], [colead-slm], [serial_comm])
 SR_DRIVER([Conrad DIGI 35 CPU], [conrad-digi-35-cpu], [serial_comm])
 SR_DRIVER([dcttech usbrelay], [dcttech-usbrelay], [libhidapi])
 SR_DRIVER([demo], [demo])
-SR_DRIVER([Devantech ETH008], [devantech-eth008])
+SR_DRIVER([Devantech ETH008], [devantech-eth008], [serial_comm])
 SR_DRIVER([DreamSourceLab DSLogic], [dreamsourcelab-dslogic], [libusb])
 SR_DRIVER([Fluke 45], [fluke-45])
 SR_DRIVER([Fluke DMM], [fluke-dmm], [serial_comm])
index 8411d9adc1f0e9e874b78ef61499b97ede6b40ba..a40a1fd98b581ece1721f51db8c884edad407eb2 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <config.h>
+#include "config.h"
+
+#include <string.h>
+
 #include "protocol.h"
 
-static struct sr_dev_driver devantech_eth008_driver_info;
+#define VENDOR_TEXT    "Devantech"
 
-static GSList *scan(struct sr_dev_driver *di, GSList *options)
-{
-       struct drv_context *drvc;
-       GSList *devices;
+static const uint32_t scanopts[] = {
+       SR_CONF_CONN,
+};
+
+static const uint32_t drvopts[] = {
+       SR_CONF_MULTIPLEXER,
+};
 
-       (void)options;
+static const uint32_t devopts[] = {
+       SR_CONF_CONN | SR_CONF_GET,
+       SR_CONF_ENABLED | SR_CONF_SET, /* Enable/disable all relays at once. */
+};
 
-       devices = NULL;
-       drvc = di->context;
-       drvc->instances = NULL;
+static const uint32_t devopts_cg_do[] = {
+       SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET,
+};
+
+static const uint32_t devopts_cg_ai[] = {
+       SR_CONF_VOLTAGE | SR_CONF_GET,
+};
+
+static const struct devantech_eth008_model models[] = {
+       { 19, "ETH008", 8, 0, 1, },
+};
 
-       /* TODO: scan for devices, either based on a SR_CONF_CONN option
-        * or on a USB scan. */
+static const struct devantech_eth008_model *find_model(uint8_t code)
+{
+       size_t idx;
+       const struct devantech_eth008_model *check;
+
+       for (idx = 0; idx < ARRAY_SIZE(models); idx++) {
+               check = &models[idx];
+               if (check->code != code)
+                       continue;
+               return check;
+       }
 
-       return devices;
+       return NULL;
 }
 
-static int dev_open(struct sr_dev_inst *sdi)
+static struct sr_dev_driver devantech_eth008_driver_info;
+
+static struct sr_dev_inst *probe_device_conn(const char *conn)
 {
-       (void)sdi;
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       struct sr_serial_dev_inst *ser;
+       uint8_t code, hwver, fwver;
+       const struct devantech_eth008_model *model;
+       gboolean has_serno_cmd;
+       char snr_txt[16];
+       struct channel_group_context *cgc;
+       size_t ch_idx, nr, do_idx;
+       struct sr_channel_group *cg;
+       char cg_name[24];
+       int ret;
 
-       /* TODO: get handle from sdi->conn and open it. */
+       sdi = g_malloc0(sizeof(*sdi));
+       devc = g_malloc0(sizeof(*devc));
+       sdi->priv = devc;
+       ser = sr_serial_dev_inst_new(conn, NULL);
+       sdi->conn = ser;
+       if (!ser)
+               goto probe_fail;
+       ret = serial_open(ser, 0);
+       if (ret != SR_OK)
+               goto probe_fail;
+
+       ret = devantech_eth008_get_model(ser, &code, &hwver, &fwver);
+       if (ret != SR_OK)
+               goto probe_fail;
+       model = find_model(code);
+       if (!model) {
+               sr_err("Unknown model ID 0x%02x (HW %u, FW %u).",
+                       code, hwver, fwver);
+               goto probe_fail;
+       }
+       devc->model_code = code;
+       devc->hardware_version = hwver;
+       devc->firmware_version = fwver;
+       devc->model = model;
+       sdi->vendor = g_strdup(VENDOR_TEXT);
+       sdi->model = g_strdup(model->name);
+       sdi->version = g_strdup_printf("HW%u FW%u", hwver, fwver);
+       sdi->connection_id = g_strdup(conn);
+       sdi->driver = &devantech_eth008_driver_info;
+       sdi->inst_type = SR_INST_SERIAL;
+
+       has_serno_cmd = TRUE;
+       if (model->min_serno_fw && fwver < model->min_serno_fw)
+               has_serno_cmd = FALSE;
+       if (has_serno_cmd) {
+               snr_txt[0] = '\0';
+               ret = devantech_eth008_get_serno(ser,
+                       snr_txt, sizeof(snr_txt));
+               if (ret != SR_OK)
+                       goto probe_fail;
+               sdi->serial_num = g_strdup(snr_txt);
+       }
 
-       return SR_OK;
+       ch_idx = 0;
+       devc->mask_do = (1UL << devc->model->ch_count_do) - 1;
+       for (do_idx = 0; do_idx < devc->model->ch_count_do; do_idx++) {
+               nr = do_idx + 1;
+               snprintf(cg_name, sizeof(cg_name), "DO%zu", nr);
+               cgc = g_malloc0(sizeof(*cgc));
+               cg = sr_channel_group_new(sdi, cg_name, cgc);
+               cgc->index = do_idx;
+               cgc->number = nr;
+               cgc->ch_type = DV_CH_DIGITAL_OUTPUT;
+               (void)cg;
+               ch_idx++;
+       }
+       if (1) {
+               /* Create an analog channel for the supply voltage. */
+               snprintf(cg_name, sizeof(cg_name), "Vsupply");
+               cgc = g_malloc0(sizeof(*cgc));
+               cg = sr_channel_group_new(sdi, cg_name, cgc);
+               cgc->index = 0;
+               cgc->number = 0;
+               cgc->ch_type = DV_CH_SUPPLY_VOLTAGE;
+               (void)cg;
+               ch_idx++;
+       }
+
+       return sdi;
+
+probe_fail:
+       if (ser) {
+               serial_close(ser);
+               sr_serial_dev_inst_free(ser);
+       }
+       if (devc) {
+               g_free(devc);
+       }
+       if (sdi) {
+               sdi->priv = NULL;
+               sr_dev_inst_free(sdi);
+               sdi = NULL;
+       }
+       return sdi;
 }
 
-static int dev_close(struct sr_dev_inst *sdi)
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
 {
-       (void)sdi;
+       struct drv_context *drvc;
+       const char *conn;
+       GSList *devices;
+       struct sr_dev_inst *sdi;
 
-       /* TODO: get handle from sdi->conn and close it. */
+       drvc = di->context;
+       drvc->instances = NULL;
 
-       return SR_OK;
+       /* A conn= spec is required for the TCP attached device. */
+       conn = NULL;
+       (void)sr_serial_extract_options(options, &conn, NULL);
+       if (!conn || !*conn)
+               return NULL;
+
+       devices = NULL;
+       sdi = probe_device_conn(conn);
+       if (sdi)
+               devices = g_slist_append(devices, sdi);
+
+       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 channel_group_context *cgc;
+       gboolean on;
+       uint16_t vin;
+       double vsupply;
        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;
+       cgc = cg->priv;
+       if (!cgc)
+               return SR_ERR_NA;
        switch (key) {
-       /* TODO */
+       case SR_CONF_ENABLED:
+               if (cgc->ch_type == DV_CH_DIGITAL_OUTPUT) {
+                       ret = devantech_eth008_query_do(sdi, cg, &on);
+                       if (ret != SR_OK)
+                               return ret;
+                       *data = g_variant_new_boolean(on);
+                       return SR_OK;
+               }
+               return SR_ERR_NA;
+       case SR_CONF_VOLTAGE:
+               if (cgc->ch_type == DV_CH_SUPPLY_VOLTAGE) {
+                       ret = devantech_eth008_query_supply(sdi, cg, &vin);
+                       if (ret != SR_OK)
+                               return ret;
+                       vsupply = vin;
+                       vsupply /= 1000.;
+                       *data = g_variant_new_double(vsupply);
+                       return SR_OK;
+               }
+               return SR_ERR_NA;
        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;
+       struct channel_group_context *cgc;
+       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 devantech_eth008_setup_do(sdi, cg, on);
+               default:
+                       return SR_ERR_NA;
+               }
+       }
 
-       ret = SR_OK;
+       cgc = cg->priv;
+       if (!cgc)
+               return SR_ERR_NA;
        switch (key) {
-       /* TODO */
+       case SR_CONF_ENABLED:
+               if (cgc->ch_type != DV_CH_DIGITAL_OUTPUT)
+                       return SR_ERR_NA;
+               on = g_variant_get_boolean(data);
+               return devantech_eth008_setup_do(sdi, cg, on);
        default:
-               ret = SR_ERR_NA;
+               return SR_ERR_NA;
        }
 
-       return ret;
+       /* XXX Is this actually UNREACH? */
+       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;
+       struct channel_group_context *cgc;
+
+       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;
+       cgc = cg->priv;
+       if (!cgc)
+               return SR_ERR_NA;
        switch (key) {
-       /* TODO */
+       case SR_CONF_DEVICE_OPTIONS:
+               if (cgc->ch_type == DV_CH_DIGITAL_OUTPUT) {
+                       *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_do));
+                       return SR_OK;
+               }
+               if (cgc->ch_type == DV_CH_SUPPLY_VOLTAGE) {
+                       *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_ai));
+                       return SR_OK;
+               }
+               return SR_ERR_NA;
        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 devantech_eth008_driver_info = {
@@ -145,10 +323,10 @@ static struct sr_dev_driver devantech_eth008_driver_info = {
        .config_get = config_get,
        .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_open = std_serial_dev_open,
+       .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(devantech_eth008_driver_info);
index 84c9344cd2147c8f569de9a0e5da7a2cadf71d6d..87ac7244772a29df295ac97e4b666f2fa97034be 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <config.h>
+/*
+ * Communicate to the Devantech ETH008 relay card via TCP and Ethernet.
+ *
+ * See http://www.robot-electronics.co.uk/files/eth008b.pdf for device
+ * capabilities and a protocol discussion.
+ * See https://github.com/devantech/devantech_eth_python for Python
+ * source code which is maintained by the vendor.
+ *
+ * The device provides several means of communication: HTTP requests
+ * (as well as an interactive web form). Raw TCP communication with
+ * binary requests and responses. Text requests and responses over
+ * TCP sockets. Some of these depend on the firmware version. Version
+ * checks before command transmission is essentially non-existent in
+ * this sigrok driver implementation. Binary transmission is preferred
+ * because it is assumed that this existed in all firmware versions.
+ * The firmware interestingly accepts concurrent network connections
+ * (up to five of them, all share the same password). Which means that
+ * the peripheral's state can change even while we control it.
+ *
+ * It's assumed that WLAN models differ from Ethernet devices in terms
+ * of their hardware, but TCP communication should not bother about the
+ * underlying physics, and WLAN cards can re-use model IDs and firmware
+ * implementations. Given sigrok's abstraction of the serial transport
+ * those cards could also be attached by means of COM ports.
+ *
+ * TCP communication seems to rely on network fragmentation and assumes
+ * that software stacks provide all of a request in a single receive
+ * call on the firmware side. Which works for local communication, but
+ * could become an issue when long distances and tunnels are involved.
+ * This sigrok driver also assumes complete reception within a single
+ * receive call. The short length of binary transmission helps here
+ * (the largest payloads has a length of three bytes).
+ *
+ * The lack of length specs as well as termination in the protocol
+ * (both binary as well as text variants over TCP sockets) results in
+ * the inability to synchronize to the firmware when connecting and
+ * after hiccups in an established connection. The fixed length of
+ * requests and responses for binary payloads helps a little bit,
+ * assuming that TCP connect is used to recover. The overhead of
+ * HTTP requests and responses is considered undesirable for this
+ * sigrok driver implementation. [This also means that a transport
+ * which lacks the concept of network frames cannot send passwords.]
+ * The binary transport appears to lack HELLO or NOP requests that
+ * could be used to synchronize. Firmware just would not respond to
+ * unsupported commands. Maybe a repeated sequence of identity reads
+ * combined with a read timeout could help synchronize, but only if
+ * the response is known because the model was identified before.
+ *
+ * The sigrok driver source code was phrased with the addition of more
+ * models in mind. Only few code paths require adjustment when similar
+ * variants of requests or responses are involved in the communication
+ * to relay cards that support between two and twenty channels. Chances
+ * are good, existing firmware is compatible across firmware versions,
+ * and even across hardware revisions (model upgrades). Firmware just
+ * happens to not respond to unknown requests.
+ *
+ * TODO
+ * - Add support for other models. Currently exclusively supports the
+ *   ETH008-B model which was used during implementation of the driver.
+ * - Add support for password protection?
+ *   - See command 0x79 to "login" (beware of the differing return value
+ *     compared to other commands), command 0x7a to check if passwords
+ *     are involved and whether the login needs refreshing, command 0x7b
+ *     for immediate "logout" in contrast to expiration.
+ *   - Alternatively consider switching to the "text protocol" in that
+ *     use case, which can send an optional password in every request
+ *     that controls relays (command 0x3a).
+ *   - How to specify the password in applications and how to pass them
+ *     to this driver is yet another issue that needs consideration.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
 #include "protocol.h"
 
-SR_PRIV int devantech_eth008_receive_data(int fd, int revents, void *cb_data)
+#define READ_TIMEOUT_MS        20
+
+enum cmd_code {
+       CMD_GET_MODULE_INFO = 0x10,
+       CMD_DIGITAL_ACTIVE = 0x20,
+       CMD_DIGITAL_INACTIVE = 0x21,
+       CMD_DIGITAL_SET_OUTPUTS = 0x23,
+       CMD_DIGITAL_GET_OUTPUTS = 0x24,
+       CMD_ASCII_TEXT_COMMAND = 0x3a,
+       CMD_GET_SERIAL_NUMBER = 0x77,
+       CMD_GET_SUPPLY_VOLTS = 0x78,
+       CMD_PASSWORD_ENTRY = 0x79,
+       CMD_GET_UNLOCK_TIME = 0x7a,
+       CMD_IMMEDIATE_LOGOUT = 0x7b,
+};
+
+/*
+ * Transmit a request to the relay card. Checks that all bytes get sent,
+ * short writes are considered fatal.
+ */
+static int send_request(struct sr_serial_dev_inst *ser,
+       const uint8_t *data, size_t dlen)
+{
+       int ret;
+       size_t written;
+
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               GString *txt = sr_hexdump_new(data, dlen);
+               sr_spew("TX --> %s.", txt->str);
+               sr_hexdump_free(txt);
+       }
+       ret = serial_write_blocking(ser, data, dlen, 0);
+       if (ret < 0)
+               return ret;
+       written = (size_t)ret;
+       if (written != dlen)
+               return SR_ERR_DATA;
+       return SR_OK;
+}
+
+/*
+ * Receive a response from the relay card. Assumes fixed size payload,
+ * considers short reads fatal.
+ */
+static int recv_response(struct sr_serial_dev_inst *ser,
+       uint8_t *data, size_t dlen)
+{
+       int ret;
+       size_t got;
+
+       ret = serial_read_blocking(ser, data, dlen, READ_TIMEOUT_MS);
+       if (ret < 0)
+               return ret;
+       got = (size_t)ret;
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               GString *txt = sr_hexdump_new(data, got);
+               sr_spew("<-- RX %s.", txt->str);
+               sr_hexdump_free(txt);
+       }
+       if (got != dlen)
+               return SR_ERR_DATA;
+       return SR_OK;
+}
+
+/* Send a request then receive a response. Convenience routine. */
+static int send_then_recv(struct sr_serial_dev_inst *serial,
+       const uint8_t *tx_data, size_t tx_length,
+       uint8_t *rx_data, size_t rx_length)
+{
+       int ret;
+
+       if (tx_data && tx_length) {
+               ret = send_request(serial, tx_data, tx_length);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       if (rx_data && rx_length) {
+               ret = recv_response(serial, rx_data, rx_length);
+               if (ret != SR_OK)
+                       return ret;
+       }
+
+       return SR_OK;
+}
+
+/* Identify the relay card, gather version information details. */
+SR_PRIV int devantech_eth008_get_model(struct sr_serial_dev_inst *serial,
+       uint8_t *model_code, uint8_t *hw_version, uint8_t *fw_version)
 {
-       const struct sr_dev_inst *sdi;
+       uint8_t req[1], *wrptr;
+       uint8_t rsp[3], v8;
+       const uint8_t *rdptr;
+       int ret;
+
+       if (model_code)
+               *model_code = 0;
+       if (hw_version)
+               *hw_version = 0;
+       if (fw_version)
+               *fw_version = 0;
+
+       wrptr = req;
+       write_u8_inc(&wrptr, CMD_GET_MODULE_INFO);
+       ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
+       if (ret != SR_OK)
+               return ret;
+       rdptr = rsp;
+
+       v8 = read_u8_inc(&rdptr);
+       if (model_code)
+               *model_code = v8;
+       v8 = read_u8_inc(&rdptr);
+       if (hw_version)
+               *hw_version = v8;
+       v8 = read_u8_inc(&rdptr);
+       if (fw_version)
+               *fw_version = v8;
+
+       return SR_OK;
+}
+
+/* Get the relay card's serial number (its MAC address). */
+SR_PRIV int devantech_eth008_get_serno(struct sr_serial_dev_inst *serial,
+       char *text_buffer, size_t text_length)
+{
+       uint8_t req[1], *wrptr;
+       uint8_t rsp[6], b;
+       const uint8_t *rdptr, *endptr;
+       size_t written;
+       int ret;
+
+       if (text_buffer && !text_length)
+               return SR_ERR_ARG;
+       if (text_buffer)
+               memset(text_buffer, 0, text_length);
+
+       wrptr = req;
+       write_u8_inc(&wrptr, CMD_GET_SERIAL_NUMBER);
+       ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
+       if (ret != SR_OK)
+               return ret;
+       rdptr = rsp;
+
+       endptr = rsp + sizeof(rsp);
+       while (rdptr < endptr && text_buffer && text_length >= 3) {
+               b = read_u8_inc(&rdptr);
+               written = snprintf(text_buffer, text_length, "%02x", b);
+               text_buffer += written;
+               text_length -= written;
+       }
+
+       return SR_OK;
+}
+
+/* Update an internal cache from the relay card's current state. */
+SR_PRIV int devantech_eth008_cache_state(const struct sr_dev_inst *sdi)
+{
+       struct sr_serial_dev_inst *serial;
        struct dev_context *devc;
+       size_t rx_size;
+       uint8_t req[1], *wrptr;
+       uint8_t rsp[1];
+       const uint8_t *rdptr;
+       uint32_t have;
+       int ret;
 
-       (void)fd;
+       serial = sdi->conn;
+       if (!serial)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
 
-       if (!(sdi = cb_data))
-               return TRUE;
+       rx_size = devc->model->width_do;
+       if (rx_size > sizeof(rsp))
+               return SR_ERR_NA;
 
-       if (!(devc = sdi->priv))
-               return TRUE;
+       wrptr = req;
+       write_u8_inc(&wrptr, CMD_DIGITAL_GET_OUTPUTS);
+       ret = send_then_recv(serial, req, wrptr - req, rsp, rx_size);
+       if (ret != SR_OK)
+               return ret;
+       rdptr = rsp;
 
-       if (revents == G_IO_IN) {
-               /* TODO */
+       switch (rx_size) {
+       case 1:
+               have = read_u8_inc(&rdptr);
+               break;
+       default:
+               return SR_ERR_NA;
        }
+       have &= devc->mask_do;
+       devc->curr_do = have;
+
+       return SR_OK;
+}
+
+/* Query the state of an individual relay channel. */
+SR_PRIV int devantech_eth008_query_do(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, gboolean *on)
+{
+       struct dev_context *devc;
+       struct channel_group_context *cgc;
+       uint32_t have;
+       int ret;
+
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /* Unconditionally update the internal cache. */
+       ret = devantech_eth008_cache_state(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       /*
+        * Only reject unexpected requeusts after the update. Get the
+        * individual channel's state from the cache of all channels.
+        */
+       if (!cg)
+               return SR_ERR_ARG;
+       cgc = cg->priv;
+       if (!cgc)
+               return SR_ERR_BUG;
+       if (cgc->index >= devc->model->ch_count_do)
+               return SR_ERR_ARG;
+       have = devc->curr_do;
+       have >>= cgc->index;
+       have &= 1 << 0;
+       if (on)
+               *on = have ? TRUE : FALSE;
+
+       return SR_OK;
+}
+
+/*
+ * Manipulate the state of an individual relay channel (when cg is given).
+ * Or set/clear all channels at the same time (when cg is NULL).
+ */
+SR_PRIV int devantech_eth008_setup_do(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, gboolean on)
+{
+       struct sr_serial_dev_inst *serial;
+       struct dev_context *devc;
+       size_t width_do;
+       struct channel_group_context *cgc;
+       size_t number;
+       uint32_t reg;
+       uint8_t req[3], *wrptr, cmd;
+       uint8_t rsp[1], v8;
+       const uint8_t *rdptr;
+       int ret;
+
+       serial = sdi->conn;
+       if (!serial)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       cgc = cg ? cg->priv : NULL;
+       if (cgc && cgc->index >= devc->model->ch_count_do)
+               return SR_ERR_ARG;
+
+       width_do = devc->model->width_do;
+       if (1 + width_do > sizeof(req))
+               return SR_ERR_NA;
+
+       wrptr = req;
+       if (cgc) {
+               /* Manipulate an individual channel. */
+               cmd = on ? CMD_DIGITAL_ACTIVE : CMD_DIGITAL_INACTIVE;
+               number = cgc->number;
+               write_u8_inc(&wrptr, cmd);
+               write_u8_inc(&wrptr, number & 0xff);
+               write_u8_inc(&wrptr, 0); /* Just set/clear, no pulse. */
+       } else {
+               /* Manipulate all channels at the same time. */
+               reg = on ? devc->mask_do : 0;
+               write_u8_inc(&wrptr, CMD_DIGITAL_SET_OUTPUTS);
+               switch (width_do) {
+               case 1:
+                       write_u8_inc(&wrptr, reg & 0xff);
+                       break;
+               default:
+                       return SR_ERR_NA;
+               }
+       }
+       ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
+       if (ret != SR_OK)
+               return ret;
+       rdptr = rsp;
+
+       v8 = read_u8_inc(&rdptr);
+       if (v8 != 0)
+               return SR_ERR_DATA;
+
+       return SR_OK;
+}
+
+SR_PRIV int devantech_eth008_query_supply(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, uint16_t *millivolts)
+{
+       struct sr_serial_dev_inst *serial;
+       uint8_t req[1], *wrptr;
+       uint8_t rsp[1];
+       const uint8_t *rdptr;
+       uint16_t have;
+       int ret;
+
+       (void)cg;
+
+       serial = sdi->conn;
+       if (!serial)
+               return SR_ERR_ARG;
+
+       wrptr = req;
+       write_u8_inc(&wrptr, CMD_GET_SUPPLY_VOLTS);
+       ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
+       if (ret != SR_OK)
+               return ret;
+       rdptr = rsp;
+
+       /* Gets a byte for voltage in units of 0.1V. Scale up to mV. */
+       have = read_u8_inc(&rdptr);
+       have *= 100;
+       if (millivolts)
+               *millivolts = have;
 
-       return TRUE;
+       return SR_OK;
 }
index ecf1656389c326579c058255f5fac6c45f0e913b..5834a4c9492ae95d4049d6b89505a55c01715ab9 100644 (file)
 #ifndef LIBSIGROK_HARDWARE_DEVANTECH_ETH008_PROTOCOL_H
 #define LIBSIGROK_HARDWARE_DEVANTECH_ETH008_PROTOCOL_H
 
-#include <stdint.h>
 #include <glib.h>
 #include <libsigrok/libsigrok.h>
+#include <stdint.h>
+
 #include "libsigrok-internal.h"
 
 #define LOG_PREFIX "devantech-eth008"
 
+/*
+ * Models have differing capabilities, and slightly different protocol
+ * variants. Setting the output state of individual relays usually takes
+ * one byte which carries the channel number. Requests are of identical
+ * length. Getting relay state takes a variable number of bytes to carry
+ * the bit fields. Response length depends on the model's relay count.
+ * As does request length for setting the state of several relays at the
+ * same time.
+ *
+ * Some models have digital inputs and analog inputs. These features
+ * currently are not supported in this implementation.
+ */
+struct devantech_eth008_model {
+       uint8_t code;
+       const char *name;
+       size_t ch_count_do;
+       uint8_t min_serno_fw;
+       size_t width_do;
+};
+
+enum devantech_eth008_channel_type {
+       DV_CH_DIGITAL_OUTPUT,
+       DV_CH_SUPPLY_VOLTAGE,
+};
+
+struct channel_group_context {
+       size_t index;
+       size_t number;
+       enum devantech_eth008_channel_type ch_type;
+};
+
 struct dev_context {
+       uint8_t model_code, hardware_version, firmware_version;
+       const struct devantech_eth008_model *model;
+       uint32_t mask_do;
+       uint32_t curr_do;
 };
 
-SR_PRIV int devantech_eth008_receive_data(int fd, int revents, void *cb_data);
+SR_PRIV int devantech_eth008_get_model(struct sr_serial_dev_inst *serial,
+       uint8_t *model_code, uint8_t *hw_version, uint8_t *fw_version);
+SR_PRIV int devantech_eth008_get_serno(struct sr_serial_dev_inst *serial,
+       char *text_buffer, size_t text_length);
+SR_PRIV int devantech_eth008_cache_state(const struct sr_dev_inst *sdi);
+SR_PRIV int devantech_eth008_query_do(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, gboolean *on);
+SR_PRIV int devantech_eth008_setup_do(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, gboolean on);
+SR_PRIV int devantech_eth008_query_supply(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, uint16_t *millivolts);
 
 #endif