*/
#include <config.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(const char *path, size_t 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;
+
+ /* Open device, need to communicate to identify. */
+ hid = hid_open_path(path);
+ if (!hid)
+ 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("raw report, rc %d, bytes %s", ret, txt->str);
+ sr_hexdump_free(txt);
+ }
+ if (ret != sizeof(report))
+ 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)
+ return NULL;
+ }
+ curr_state = report[1 + STATE_INDEX];
+ sr_spew("report data, serno[%s], relays 0x%02x.", serno, curr_state);
+
+ /*
+ * Create a device instance, create channels (groups). The
+ * caller fills in vendor, model, conn from USB enum details.
+ */
+ sdi = g_malloc0(sizeof(*sdi));
+ devc = g_malloc0(sizeof(*devc));
+ sdi->priv = devc;
+ devc->hid_path = g_strdup(path);
+ 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 GSList *scan(struct sr_dev_driver *di, GSList *options)
{
- struct drv_context *drvc;
+ const char *conn;
GSList *devices;
-
- (void)options;
+ struct drv_context *drvc;
+ struct hid_device_info *devs, *curdev;
+ int ret;
+ wchar_t *ws;
+ char nonws[32];
+ char *s, *endp;
+ unsigned long relay_count;
+ struct sr_dev_inst *sdi;
+
+ /* Get optional conn= spec when provided. */
+ conn = NULL;
+ (void)sr_serial_extract_options(options, &conn, NULL);
+ if (conn && !*conn)
+ conn = NULL;
+ /*
+ * TODO Accept different types of conn= specs? Either paths that
+ * hidapi(3) can open. Or bus.addr specs that we can check for
+ * during USB enumeration. Derive want_path, want_bus, want_addr
+ * here from the optional conn= spec.
+ */
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 contains a five character string which may serve
+ * as an identification for boards (is said to differ between
+ * boards). The last byte encodes the current relays state.
+ */
+ devs = hid_enumerate(VENDOR_ID, PRODUCT_ID);
+ for (curdev = devs; curdev; curdev = curdev->next) {
+ if (!curdev->vendor_id || !curdev->product_id)
+ continue;
+ if (!curdev->manufacturer_string || !curdev->product_string)
+ continue;
+ if (!*curdev->manufacturer_string || !*curdev->product_string)
+ continue;
+ if (conn && strcmp(curdev->path, conn) != 0) {
+ sr_dbg("skipping %s, conn= mismatch", curdev->path);
+ 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);
+ s = nonws;
+ if (!g_str_has_prefix(s, PRODUCT_STRING_PREFIX))
+ continue;
+ s += strlen(PRODUCT_STRING_PREFIX);
+ ret = sr_atoul_base(s, &relay_count, &endp, 10);
+ if (ret != SR_OK || !endp || *endp)
+ continue;
+ sr_info("Found: HID path %s, relay count %lu.",
+ curdev->path, relay_count);
+
+ /* Identify device by communicating to it. */
+ sdi = probe_device(curdev->path, relay_count);
+ if (!sdi) {
+ sr_warn("Failed to communicate to %s.", curdev->path);
+ continue;
+ }
+
+ /* Amend driver instance from USB enumeration details. */
+ sdi->vendor = g_strdup_printf("%ls", curdev->manufacturer_string);
+ sdi->model = g_strdup_printf("%ls", curdev->product_string);
+ sdi->conn = g_strdup(curdev->path);
+ sdi->driver = &dcttech_usbrelay_driver_info;
+ sdi->inst_type = SR_INST_USB;
+
+ 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;
+ }
- /* TODO: get handle from sdi->conn and open it. */
+ devc->hid_dev = hid_open_path(devc->hid_path);
+ if (!devc->hid_dev)
+ return SR_ERR_IO;
+
+ (void)dcttech_usbrelay_update_state(sdi);
return SR_OK;
}
static int dev_close(struct sr_dev_inst *sdi)
{
- (void)sdi;
+ struct dev_context *devc;
+
+ devc = sdi->priv;
- /* TODO: get handle from sdi->conn and close it. */
+ if (devc->hid_dev) {
+ hid_close(devc->hid_dev);
+ devc->hid_dev = NULL;
+ }
return SR_OK;
}
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->conn)
+ return SR_ERR_NA;
+ *data = g_variant_new_string(sdi->conn);
+ 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 = {
.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);
*/
#include <config.h>
+
+#include <string.h>
+
#include "protocol.h"
-SR_PRIV int dcttech_usbrelay_receive_data(int fd, int revents, void *cb_data)
+SR_PRIV int dcttech_usbrelay_update_state(const struct sr_dev_inst *sdi)
{
- const struct sr_dev_inst *sdi;
struct dev_context *devc;
+ uint8_t report[1 + REPORT_BYTECOUNT];
+ int ret;
+ GString *txt;
+
+ devc = sdi->priv;
+
+ /* Get another HID report. */
+ memset(report, 0, sizeof(report));
+ report[0] = REPORT_NUMBER;
+ ret = hid_get_feature_report(devc->hid_dev, report, sizeof(report));
+ if (ret != sizeof(report))
+ return SR_ERR_IO;
+ if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+ txt = sr_hexdump_new(report, sizeof(report));
+ sr_spew("got report bytes: %s", txt->str);
+ sr_hexdump_free(txt);
+ }
- (void)fd;
+ /* Update relay state cache from HID report content. */
+ devc->relay_state = report[1 + STATE_INDEX];
+ devc->relay_state &= devc->relay_mask;
+
+ return SR_OK;
+}
+
+SR_PRIV int dcttech_usbrelay_switch_cg(const struct sr_dev_inst *sdi,
+ const struct sr_channel_group *cg, gboolean on)
+{
+ struct dev_context *devc;
+ struct channel_group_context *cgc;
+ gboolean is_all;
+ size_t relay_idx;
+ uint8_t report[1 + REPORT_BYTECOUNT];
+ int ret;
+ GString *txt;
- if (!(sdi = cb_data))
- return TRUE;
+ devc = sdi->priv;
- if (!(devc = sdi->priv))
- return TRUE;
+ /* Determine if all or a single relay should be turned off or on. */
+ is_all = !cg ? TRUE : FALSE;
+ if (is_all) {
+ relay_idx = 0;
+ } else {
+ cgc = cg->priv;
+ relay_idx = cgc->number;
+ }
- if (revents == G_IO_IN) {
- /* TODO */
+ /*
+ * Construct and send the HID report. Notice the weird(?) bit
+ * pattern. Bit 1 is low when all relays are affected at once,
+ * and high to control an individual relay? Bit 0 communicates
+ * whether the relay(s) should be on or off? And all other bits
+ * are always set? It's assumed that the explicit assignment of
+ * full byte values simplifies future maintenance.
+ */
+ memset(report, 0, sizeof(report));
+ report[0] = REPORT_NUMBER;
+ if (is_all) {
+ if (on) {
+ report[1] = 0xfe;
+ } else {
+ report[1] = 0xfc;
+ }
+ } else {
+ if (on) {
+ report[1] = 0xff;
+ report[2] = relay_idx;
+ } else {
+ report[1] = 0xfd;
+ report[2] = relay_idx;
+ }
+ }
+ if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+ txt = sr_hexdump_new(report, sizeof(report));
+ sr_spew("sending report bytes: %s", txt->str);
+ sr_hexdump_free(txt);
}
+ ret = hid_send_feature_report(devc->hid_dev, report, sizeof(report));
+ if (ret != sizeof(report))
+ return SR_ERR_IO;
+
+ /* Update relay state cache (non-fatal). */
+ (void)dcttech_usbrelay_update_state(sdi);
+
+ return SR_OK;
+}
+
+/* Answers the query from cached relay state. Beware of 1-based indexing. */
+SR_PRIV int dcttech_usbrelay_query_cg(const struct sr_dev_inst *sdi,
+ const struct sr_channel_group *cg, gboolean *on)
+{
+ struct dev_context *devc;
+ struct channel_group_context *cgc;
+ size_t relay_idx;
+ uint32_t relay_mask;
+
+ devc = sdi->priv;
+ if (!cg)
+ return SR_ERR_ARG;
+ cgc = cg->priv;
+ relay_idx = cgc->number;
+ if (relay_idx < 1 || relay_idx > devc->relay_count)
+ return SR_ERR_ARG;
+ relay_mask = 1U << (relay_idx - 1);
+
+ *on = devc->relay_state & relay_mask;
- return TRUE;
+ return SR_OK;
}