From: Frank Stettner Date: Sat, 7 Aug 2021 12:16:42 +0000 (+0200) Subject: icstation-usbrelay: Initial ICStation USBRelay driver. X-Git-Url: http://sigrok.org/gitweb/?p=libsigrok.git;a=commitdiff_plain;h=cefdd911747a4b219ba8d04714e99be91314edf5 icstation-usbrelay: Initial ICStation USBRelay driver. 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. --- diff --git a/configure.ac b/configure.ac index 5202f5f1..c8c51eb9 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) diff --git a/src/hardware/icstation-usbrelay/api.c b/src/hardware/icstation-usbrelay/api.c index 0e329beb..db576737 100644 --- a/src/hardware/icstation-usbrelay/api.c +++ b/src/hardware/icstation-usbrelay/api.c @@ -1,7 +1,7 @@ /* * This file is part of the libsigrok project. * - * Copyright (C) 2021 Frank Stettner + * Copyright (C) 2021-2023 Frank Stettner * * 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 @@ -18,124 +18,224 @@ */ #include + #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); diff --git a/src/hardware/icstation-usbrelay/protocol.c b/src/hardware/icstation-usbrelay/protocol.c index fa32e700..78a99965 100644 --- a/src/hardware/icstation-usbrelay/protocol.c +++ b/src/hardware/icstation-usbrelay/protocol.c @@ -1,7 +1,7 @@ /* * This file is part of the libsigrok project. * - * Copyright (C) 2021 Frank Stettner + * Copyright (C) 2021-2023 Frank Stettner * * 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 @@ -18,24 +18,136 @@ */ #include + #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; } diff --git a/src/hardware/icstation-usbrelay/protocol.h b/src/hardware/icstation-usbrelay/protocol.h index 3c8319d6..bcf9dd07 100644 --- a/src/hardware/icstation-usbrelay/protocol.h +++ b/src/hardware/icstation-usbrelay/protocol.h @@ -1,7 +1,7 @@ /* * This file is part of the libsigrok project. * - * Copyright (C) 2021 Frank Stettner + * Copyright (C) 2021-2023 Frank Stettner * * 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 @@ -20,16 +20,43 @@ #ifndef LIBSIGROK_HARDWARE_ICSTATION_USBRELAY_PROTOCOL_H #define LIBSIGROK_HARDWARE_ICSTATION_USBRELAY_PROTOCOL_H -#include #include #include +#include + #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