Skip to content

Commit 321f85f

Browse files
committed
dcttech-usbrelay: implement multiplexer driver for USB relay card
Implement support for the "www.dcttech.com USBRelay<n>" card. This V-USB based HID device communicates HID reports to control up to 8 relays. The driver depends on the HIDAPI external dependency for maximum portability. Support for the conn= parameters is limited. A path that HIDAPI can open is expected, which depends on the platform and HIDAPI implementation, and may not always be expressed by means of sigrok command line options. See README.devices for a discussion. The USB serial number is not considered. This is an implementation detail of the relay card's firmware. HID reports carry a five character identifier for the board. Relay state is cached in the driver. It's assumed that relay state won't change outside of libsigrok control as long as the device is opened. The single request to activate/deactivate all relays at once is supported.
1 parent 64d54a7 commit 321f85f

File tree

4 files changed

+388
-70
lines changed

4 files changed

+388
-70
lines changed

configure.ac

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ SR_DRIVER([Center 3xx], [center-3xx], [serial_comm])
269269
SR_DRIVER([ChronoVu LA], [chronovu-la], [libusb libftdi])
270270
SR_DRIVER([Colead SLM], [colead-slm], [serial_comm])
271271
SR_DRIVER([Conrad DIGI 35 CPU], [conrad-digi-35-cpu], [serial_comm])
272-
SR_DRIVER([dcttech usbrelay], [dcttech-usbrelay])
272+
SR_DRIVER([dcttech usbrelay], [dcttech-usbrelay], [libhidapi])
273273
SR_DRIVER([demo], [demo])
274274
SR_DRIVER([DreamSourceLab DSLogic], [dreamsourcelab-dslogic], [libusb])
275275
SR_DRIVER([Fluke 45], [fluke-45])

src/hardware/dcttech-usbrelay/api.c

Lines changed: 252 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -18,119 +18,314 @@
1818
*/
1919

2020
#include <config.h>
21+
22+
#include <hidapi.h>
23+
#include <string.h>
24+
2125
#include "protocol.h"
2226

27+
static const uint32_t scanopts[] = {
28+
SR_CONF_CONN,
29+
};
30+
31+
static const uint32_t drvopts[] = {
32+
SR_CONF_MULTIPLEXER,
33+
};
34+
35+
static const uint32_t devopts[] = {
36+
SR_CONF_CONN | SR_CONF_GET,
37+
SR_CONF_ENABLED | SR_CONF_SET, /* Enable/disable all relays at once. */
38+
};
39+
40+
static const uint32_t devopts_cg[] = {
41+
SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET,
42+
};
43+
2344
static struct sr_dev_driver dcttech_usbrelay_driver_info;
2445

46+
static struct sr_dev_inst *probe_device(const char *path, size_t relay_count)
47+
{
48+
hid_device *hid;
49+
int ret;
50+
char serno[SERNO_LENGTH + 1];
51+
uint8_t curr_state;
52+
uint8_t report[1 + REPORT_BYTECOUNT];
53+
GString *txt;
54+
size_t snr_pos;
55+
char c;
56+
struct sr_dev_inst *sdi;
57+
struct dev_context *devc;
58+
struct channel_group_context *cgc;
59+
size_t idx, nr;
60+
struct sr_channel_group *cg;
61+
62+
/* Open device, need to communicate to identify. */
63+
hid = hid_open_path(path);
64+
if (!hid)
65+
return NULL;
66+
67+
/* Get an HID report. */
68+
hid_set_nonblocking(hid, 0);
69+
memset(&report, 0, sizeof(report));
70+
report[0] = REPORT_NUMBER;
71+
ret = hid_get_feature_report(hid, report, sizeof(report));
72+
hid_close(hid);
73+
if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
74+
txt = sr_hexdump_new(report, sizeof(report));
75+
sr_spew("raw report, rc %d, bytes %s", ret, txt->str);
76+
sr_hexdump_free(txt);
77+
}
78+
if (ret != sizeof(report))
79+
return NULL;
80+
81+
/*
82+
* Serial number must be all printable characters. Relay state
83+
* is for information only, gets re-retrieved before configure
84+
* API calls (get/set).
85+
*/
86+
memset(serno, 0, sizeof(serno));
87+
for (snr_pos = 0; snr_pos < SERNO_LENGTH; snr_pos++) {
88+
c = report[1 + snr_pos];
89+
serno[snr_pos] = c;
90+
if (c < 0x20 || c > 0x7e)
91+
return NULL;
92+
}
93+
curr_state = report[1 + STATE_INDEX];
94+
sr_spew("report data, serno[%s], relays 0x%02x.", serno, curr_state);
95+
96+
/*
97+
* Create a device instance, create channels (groups). The
98+
* caller fills in vendor, model, conn from USB enum details.
99+
*/
100+
sdi = g_malloc0(sizeof(*sdi));
101+
devc = g_malloc0(sizeof(*devc));
102+
sdi->priv = devc;
103+
devc->hid_path = g_strdup(path);
104+
devc->relay_count = relay_count;
105+
devc->relay_mask = (1U << relay_count) - 1;
106+
for (idx = 0; idx < devc->relay_count; idx++) {
107+
nr = idx + 1;
108+
cg = g_malloc0(sizeof(*cg));
109+
cg->name = g_strdup_printf("R%zu", nr);
110+
cgc = g_malloc0(sizeof(*cgc));
111+
cg->priv = cgc;
112+
cgc->number = nr;
113+
sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
114+
}
115+
116+
return sdi;
117+
}
118+
25119
static GSList *scan(struct sr_dev_driver *di, GSList *options)
26120
{
27-
struct drv_context *drvc;
121+
const char *conn;
28122
GSList *devices;
29-
30-
(void)options;
123+
struct drv_context *drvc;
124+
struct hid_device_info *devs, *curdev;
125+
int ret;
126+
wchar_t *ws;
127+
char nonws[32];
128+
char *s, *endp;
129+
unsigned long relay_count;
130+
struct sr_dev_inst *sdi;
131+
132+
/* Get optional conn= spec when provided. */
133+
conn = NULL;
134+
(void)sr_serial_extract_options(options, &conn, NULL);
135+
if (conn && !*conn)
136+
conn = NULL;
137+
/*
138+
* TODO Accept different types of conn= specs? Either paths that
139+
* hidapi(3) can open. Or bus.addr specs that we can check for
140+
* during USB enumeration. Derive want_path, want_bus, want_addr
141+
* here from the optional conn= spec.
142+
*/
31143

32144
devices = NULL;
33145
drvc = di->context;
34146
drvc->instances = NULL;
35147

36-
/* TODO: scan for devices, either based on a SR_CONF_CONN option
37-
* or on a USB scan. */
148+
/*
149+
* The firmware is V-USB based. The USB VID:PID identification
150+
* is shared across several projects. Need to inspect the vendor
151+
* and product _strings_ to actually identify the device.
152+
*
153+
* The USB serial number need not be present nor reliable. The
154+
* HID report contains a five character string which may serve
155+
* as an identification for boards (is said to differ between
156+
* boards). The last byte encodes the current relays state.
157+
*/
158+
devs = hid_enumerate(VENDOR_ID, PRODUCT_ID);
159+
for (curdev = devs; curdev; curdev = curdev->next) {
160+
if (!curdev->vendor_id || !curdev->product_id)
161+
continue;
162+
if (!curdev->manufacturer_string || !curdev->product_string)
163+
continue;
164+
if (!*curdev->manufacturer_string || !*curdev->product_string)
165+
continue;
166+
if (conn && strcmp(curdev->path, conn) != 0) {
167+
sr_dbg("skipping %s, conn= mismatch", curdev->path);
168+
continue;
169+
}
170+
sr_dbg("checking %04hx:%04hx, vendor %ls, product %ls.",
171+
curdev->vendor_id, curdev->product_id,
172+
curdev->manufacturer_string, curdev->product_string);
173+
174+
/* Check USB details retrieved by enumeration. */
175+
ws = curdev->manufacturer_string;
176+
if (!ws || !wcslen(ws))
177+
continue;
178+
snprintf(nonws, sizeof(nonws), "%ls", ws);
179+
if (strcmp(nonws, VENDOR_STRING) != 0)
180+
continue;
181+
ws = curdev->product_string;
182+
if (!ws || !wcslen(ws))
183+
continue;
184+
snprintf(nonws, sizeof(nonws), "%ls", ws);
185+
s = nonws;
186+
if (!g_str_has_prefix(s, PRODUCT_STRING_PREFIX))
187+
continue;
188+
s += strlen(PRODUCT_STRING_PREFIX);
189+
ret = sr_atoul_base(s, &relay_count, &endp, 10);
190+
if (ret != SR_OK || !endp || *endp)
191+
continue;
192+
sr_info("Found: HID path %s, relay count %lu.",
193+
curdev->path, relay_count);
194+
195+
/* Identify device by communicating to it. */
196+
sdi = probe_device(curdev->path, relay_count);
197+
if (!sdi) {
198+
sr_warn("Failed to communicate to %s.", curdev->path);
199+
continue;
200+
}
201+
202+
/* Amend driver instance from USB enumeration details. */
203+
sdi->vendor = g_strdup_printf("%ls", curdev->manufacturer_string);
204+
sdi->model = g_strdup_printf("%ls", curdev->product_string);
205+
sdi->conn = g_strdup(curdev->path);
206+
sdi->driver = &dcttech_usbrelay_driver_info;
207+
sdi->inst_type = SR_INST_USB;
208+
209+
devices = g_slist_append(devices, sdi);
210+
}
211+
hid_free_enumeration(devs);
38212

39213
return devices;
40214
}
41215

42216
static int dev_open(struct sr_dev_inst *sdi)
43217
{
44-
(void)sdi;
218+
struct dev_context *devc;
219+
220+
devc = sdi->priv;
221+
222+
if (devc->hid_dev) {
223+
hid_close(devc->hid_dev);
224+
devc->hid_dev = NULL;
225+
}
45226

46-
/* TODO: get handle from sdi->conn and open it. */
227+
devc->hid_dev = hid_open_path(devc->hid_path);
228+
if (!devc->hid_dev)
229+
return SR_ERR_IO;
230+
231+
(void)dcttech_usbrelay_update_state(sdi);
47232

48233
return SR_OK;
49234
}
50235

51236
static int dev_close(struct sr_dev_inst *sdi)
52237
{
53-
(void)sdi;
238+
struct dev_context *devc;
239+
240+
devc = sdi->priv;
54241

55-
/* TODO: get handle from sdi->conn and close it. */
242+
if (devc->hid_dev) {
243+
hid_close(devc->hid_dev);
244+
devc->hid_dev = NULL;
245+
}
56246

57247
return SR_OK;
58248
}
59249

60250
static int config_get(uint32_t key, GVariant **data,
61251
const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
62252
{
253+
gboolean on;
63254
int ret;
64255

65-
(void)sdi;
66-
(void)data;
67-
(void)cg;
256+
if (!cg) {
257+
switch (key) {
258+
case SR_CONF_CONN:
259+
if (!sdi->conn)
260+
return SR_ERR_NA;
261+
*data = g_variant_new_string(sdi->conn);
262+
return SR_OK;
263+
default:
264+
return SR_ERR_NA;
265+
}
266+
}
68267

69-
ret = SR_OK;
70268
switch (key) {
71-
/* TODO */
269+
case SR_CONF_ENABLED:
270+
ret = dcttech_usbrelay_query_cg(sdi, cg, &on);
271+
if (ret != SR_OK)
272+
return ret;
273+
*data = g_variant_new_boolean(on);
274+
return SR_OK;
72275
default:
73276
return SR_ERR_NA;
74277
}
75-
76-
return ret;
77278
}
78279

79280
static int config_set(uint32_t key, GVariant *data,
80281
const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
81282
{
82-
int ret;
83-
84-
(void)sdi;
85-
(void)data;
86-
(void)cg;
87-
88-
ret = SR_OK;
89-
switch (key) {
90-
/* TODO */
91-
default:
92-
ret = SR_ERR_NA;
283+
gboolean on;
284+
285+
if (!cg) {
286+
switch (key) {
287+
case SR_CONF_ENABLED:
288+
/* Enable/disable all channels at the same time. */
289+
on = g_variant_get_boolean(data);
290+
return dcttech_usbrelay_switch_cg(sdi, cg, on);
291+
default:
292+
return SR_ERR_NA;
293+
}
294+
} else {
295+
switch (key) {
296+
case SR_CONF_ENABLED:
297+
on = g_variant_get_boolean(data);
298+
return dcttech_usbrelay_switch_cg(sdi, cg, on);
299+
default:
300+
return SR_ERR_NA;
301+
}
93302
}
94303

95-
return ret;
304+
return SR_OK;
96305
}
97306

98307
static int config_list(uint32_t key, GVariant **data,
99308
const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
100309
{
101-
int ret;
102310

103-
(void)sdi;
104-
(void)data;
105-
(void)cg;
311+
if (!cg) {
312+
switch (key) {
313+
case SR_CONF_SCAN_OPTIONS:
314+
case SR_CONF_DEVICE_OPTIONS:
315+
return STD_CONFIG_LIST(key, data, sdi, cg,
316+
scanopts, drvopts, devopts);
317+
default:
318+
return SR_ERR_NA;
319+
}
320+
}
106321

107-
ret = SR_OK;
108322
switch (key) {
109-
/* TODO */
323+
case SR_CONF_DEVICE_OPTIONS:
324+
*data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg));
325+
return SR_OK;
110326
default:
111327
return SR_ERR_NA;
112328
}
113-
114-
return ret;
115-
}
116-
117-
static int dev_acquisition_start(const struct sr_dev_inst *sdi)
118-
{
119-
/* TODO: configure hardware, reset acquisition state, set up
120-
* callbacks and send header packet. */
121-
122-
(void)sdi;
123-
124-
return SR_OK;
125-
}
126-
127-
static int dev_acquisition_stop(struct sr_dev_inst *sdi)
128-
{
129-
/* TODO: stop acquisition. */
130-
131-
(void)sdi;
132-
133-
return SR_OK;
134329
}
135330

136331
static struct sr_dev_driver dcttech_usbrelay_driver_info = {
@@ -147,8 +342,8 @@ static struct sr_dev_driver dcttech_usbrelay_driver_info = {
147342
.config_list = config_list,
148343
.dev_open = dev_open,
149344
.dev_close = dev_close,
150-
.dev_acquisition_start = dev_acquisition_start,
151-
.dev_acquisition_stop = dev_acquisition_stop,
345+
.dev_acquisition_start = std_dummy_dev_acquisition_start,
346+
.dev_acquisition_stop = std_dummy_dev_acquisition_stop,
152347
.context = NULL,
153348
};
154349
SR_REGISTER_DEV_DRIVER(dcttech_usbrelay_driver_info);

0 commit comments

Comments
 (0)