/*
* 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,
.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);
/*
* 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;
}