]> sigrok.org Git - libsigrok.git/blame - src/hardware/dcttech-usbrelay/api.c
dcttech-usbrelay: accept conn= spec different from hidapi enum details
[libsigrok.git] / src / hardware / dcttech-usbrelay / api.c
CommitLineData
64d54a71
GS
1/*
2 * This file is part of the libsigrok project.
3 *
4 * Copyright (C) 2021 Gerhard Sittig <gerhard.sittig@gmx.net>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <config.h>
321f85fb 21
0498ef4e 22#include <ctype.h>
321f85fb
GS
23#include <hidapi.h>
24#include <string.h>
25
64d54a71
GS
26#include "protocol.h"
27
321f85fb
GS
28static const uint32_t scanopts[] = {
29 SR_CONF_CONN,
30};
31
32static const uint32_t drvopts[] = {
33 SR_CONF_MULTIPLEXER,
34};
35
36static const uint32_t devopts[] = {
37 SR_CONF_CONN | SR_CONF_GET,
38 SR_CONF_ENABLED | SR_CONF_SET, /* Enable/disable all relays at once. */
39};
40
41static const uint32_t devopts_cg[] = {
42 SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET,
43};
44
64d54a71
GS
45static struct sr_dev_driver dcttech_usbrelay_driver_info;
46
0498ef4e
GS
47static struct sr_dev_inst *probe_device_common(const char *path,
48 const wchar_t *vendor, const wchar_t *product)
321f85fb 49{
0498ef4e
GS
50 char nonws[16], *s, *endp;
51 unsigned long relay_count;
321f85fb
GS
52 hid_device *hid;
53 int ret;
54 char serno[SERNO_LENGTH + 1];
55 uint8_t curr_state;
56 uint8_t report[1 + REPORT_BYTECOUNT];
57 GString *txt;
58 size_t snr_pos;
59 char c;
60 struct sr_dev_inst *sdi;
61 struct dev_context *devc;
62 struct channel_group_context *cgc;
63 size_t idx, nr;
64 struct sr_channel_group *cg;
65
0498ef4e
GS
66 /*
67 * Get relay count from product string. Weak condition,
68 * accept any trailing number regardless of preceeding text.
69 */
70 snprintf(nonws, sizeof(nonws), "%ls", product);
71 s = nonws;
72 s += strlen(s);
73 while (s > nonws && isdigit((int)s[-1]))
74 s--;
75 ret = sr_atoul_base(s, &relay_count, &endp, 10);
76 if (ret != SR_OK || !endp || *endp)
77 return NULL;
78 if (!relay_count)
79 return NULL;
80 sr_info("Relay count %lu from product string %s.", relay_count, nonws);
81
321f85fb 82 /* Open device, need to communicate to identify. */
0498ef4e 83 hid = hid_open_path(path);
996331ce 84 if (!hid) {
0498ef4e 85 sr_err("Cannot open %s.", path);
321f85fb 86 return NULL;
996331ce 87 }
321f85fb
GS
88
89 /* Get an HID report. */
90 hid_set_nonblocking(hid, 0);
91 memset(&report, 0, sizeof(report));
92 report[0] = REPORT_NUMBER;
93 ret = hid_get_feature_report(hid, report, sizeof(report));
94 hid_close(hid);
95 if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
96 txt = sr_hexdump_new(report, sizeof(report));
0498ef4e 97 sr_spew("Got report bytes: %s, rc %d.", txt->str, ret);
321f85fb
GS
98 sr_hexdump_free(txt);
99 }
996331ce 100 if (ret < 0) {
0498ef4e 101 sr_err("Cannot read %s: %ls.", path, hid_error(NULL));
996331ce
GS
102 return NULL;
103 }
104 if (ret != sizeof(report)) {
0498ef4e 105 sr_err("Unexpected HID report length %d from %s.", ret, path);
321f85fb 106 return NULL;
996331ce 107 }
321f85fb
GS
108
109 /*
110 * Serial number must be all printable characters. Relay state
111 * is for information only, gets re-retrieved before configure
112 * API calls (get/set).
113 */
114 memset(serno, 0, sizeof(serno));
115 for (snr_pos = 0; snr_pos < SERNO_LENGTH; snr_pos++) {
116 c = report[1 + snr_pos];
117 serno[snr_pos] = c;
996331ce 118 if (c < 0x20 || c > 0x7e) {
0498ef4e 119 sr_warn("Skipping %s, non-printable serial.", path);
321f85fb 120 return NULL;
996331ce 121 }
321f85fb
GS
122 }
123 curr_state = report[1 + STATE_INDEX];
0498ef4e
GS
124 sr_info("HID report data: serial number %s, relay state 0x%02x.",
125 serno, curr_state);
321f85fb 126
996331ce 127 /* Create a device instance. */
321f85fb 128 sdi = g_malloc0(sizeof(*sdi));
0498ef4e
GS
129 sdi->vendor = g_strdup_printf("%ls", vendor);
130 sdi->model = g_strdup_printf("%ls", product);
e333a40c 131 sdi->serial_num = g_strdup(serno);
0498ef4e 132 sdi->connection_id = g_strdup(path);
996331ce
GS
133 sdi->driver = &dcttech_usbrelay_driver_info;
134 sdi->inst_type = SR_INST_USB;
135
136 /* Create channels (groups). */
321f85fb
GS
137 devc = g_malloc0(sizeof(*devc));
138 sdi->priv = devc;
0498ef4e 139 devc->hid_path = g_strdup(path);
321f85fb
GS
140 devc->relay_count = relay_count;
141 devc->relay_mask = (1U << relay_count) - 1;
142 for (idx = 0; idx < devc->relay_count; idx++) {
143 nr = idx + 1;
144 cg = g_malloc0(sizeof(*cg));
145 cg->name = g_strdup_printf("R%zu", nr);
146 cgc = g_malloc0(sizeof(*cgc));
147 cg->priv = cgc;
148 cgc->number = nr;
149 sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
150 }
151
152 return sdi;
153}
154
0498ef4e
GS
155static struct sr_dev_inst *probe_device_enum(struct hid_device_info *dev)
156{
157 return probe_device_common(dev->path,
158 dev->manufacturer_string, dev->product_string);
159}
160
161static struct sr_dev_inst *probe_device_path(const char *path)
162{
163 hid_device *dev;
164 gboolean ok;
165 int ret;
166 wchar_t vendor[32], product[32];
167
168 /*
169 * TODO Accept different types of conn= specs? Either paths that
170 * hidapi(3) can open. Or bus.addr specs that we can check for
171 * during USB enumeration and derive a hidapi(3) compatible path
172 * from? Is some "unescaping" desirable for platforms which have
173 * colons in hidapi(3) paths that collide with how conn= specs
174 * are passed in sigrok? This would be the place to translate
175 * the 'path' to a canonical format.
176 */
177
178 dev = hid_open_path(path);
179 if (!dev) {
180 sr_err("Cannot open %s.", path);
181 return NULL;
182 }
183
184 ok = TRUE;
185 ret = hid_get_manufacturer_string(dev, vendor, ARRAY_SIZE(vendor));
186 if (ret != 0)
187 ok = FALSE;
188 if (!wcslen(vendor))
189 ok = FALSE;
190 ret = hid_get_product_string(dev, product, ARRAY_SIZE(product));
191 if (ret != 0)
192 ok = FALSE;
193 if (!wcslen(product))
194 ok = FALSE;
195 hid_close(dev);
196 if (!ok)
197 return NULL;
198
199 return probe_device_common(path, vendor, product);
200}
201
64d54a71
GS
202static GSList *scan(struct sr_dev_driver *di, GSList *options)
203{
321f85fb 204 const char *conn;
64d54a71 205 GSList *devices;
321f85fb
GS
206 struct drv_context *drvc;
207 struct hid_device_info *devs, *curdev;
321f85fb
GS
208 wchar_t *ws;
209 char nonws[32];
321f85fb
GS
210 struct sr_dev_inst *sdi;
211
212 /* Get optional conn= spec when provided. */
213 conn = NULL;
214 (void)sr_serial_extract_options(options, &conn, NULL);
215 if (conn && !*conn)
216 conn = NULL;
64d54a71
GS
217
218 devices = NULL;
219 drvc = di->context;
220 drvc->instances = NULL;
221
321f85fb
GS
222 /*
223 * The firmware is V-USB based. The USB VID:PID identification
224 * is shared across several projects. Need to inspect the vendor
0498ef4e
GS
225 * and product _strings_ to actually identify the device. The
226 * USB serial number need not be present nor reliable. The HID
227 * report content will carry the board's serial number.
321f85fb 228 *
0498ef4e
GS
229 * When conn= was specified, then have HIDAPI open _this_ device
230 * and skip the enumeration. Which allows users to specify paths
231 * that need not match the enumeration's details.
321f85fb 232 */
0498ef4e
GS
233 if (conn) {
234 sr_info("Checking HID path %s.", conn);
235 sdi = probe_device_path(conn);
236 if (!sdi)
237 sr_warn("Failed to communicate to %s.", conn);
238 else
239 devices = g_slist_append(devices, sdi);
240 }
321f85fb
GS
241 devs = hid_enumerate(VENDOR_ID, PRODUCT_ID);
242 for (curdev = devs; curdev; curdev = curdev->next) {
0498ef4e
GS
243 if (conn)
244 break;
321f85fb
GS
245 if (!curdev->vendor_id || !curdev->product_id)
246 continue;
247 if (!curdev->manufacturer_string || !curdev->product_string)
248 continue;
249 if (!*curdev->manufacturer_string || !*curdev->product_string)
250 continue;
0498ef4e 251 sr_dbg("Checking %04hx:%04hx, vendor %ls, product %ls.",
321f85fb
GS
252 curdev->vendor_id, curdev->product_id,
253 curdev->manufacturer_string, curdev->product_string);
254
255 /* Check USB details retrieved by enumeration. */
256 ws = curdev->manufacturer_string;
257 if (!ws || !wcslen(ws))
258 continue;
259 snprintf(nonws, sizeof(nonws), "%ls", ws);
260 if (strcmp(nonws, VENDOR_STRING) != 0)
261 continue;
262 ws = curdev->product_string;
263 if (!ws || !wcslen(ws))
264 continue;
265 snprintf(nonws, sizeof(nonws), "%ls", ws);
0498ef4e 266 if (!g_str_has_prefix(nonws, PRODUCT_STRING_PREFIX))
321f85fb 267 continue;
321f85fb
GS
268
269 /* Identify device by communicating to it. */
0498ef4e
GS
270 sr_info("Checking HID path %s.", curdev->path);
271 sdi = probe_device_enum(curdev);
321f85fb
GS
272 if (!sdi) {
273 sr_warn("Failed to communicate to %s.", curdev->path);
274 continue;
275 }
321f85fb
GS
276 devices = g_slist_append(devices, sdi);
277 }
278 hid_free_enumeration(devs);
64d54a71
GS
279
280 return devices;
281}
282
283static int dev_open(struct sr_dev_inst *sdi)
284{
321f85fb
GS
285 struct dev_context *devc;
286
287 devc = sdi->priv;
288
289 if (devc->hid_dev) {
290 hid_close(devc->hid_dev);
291 devc->hid_dev = NULL;
292 }
64d54a71 293
321f85fb
GS
294 devc->hid_dev = hid_open_path(devc->hid_path);
295 if (!devc->hid_dev)
296 return SR_ERR_IO;
297
298 (void)dcttech_usbrelay_update_state(sdi);
64d54a71
GS
299
300 return SR_OK;
301}
302
303static int dev_close(struct sr_dev_inst *sdi)
304{
321f85fb
GS
305 struct dev_context *devc;
306
307 devc = sdi->priv;
64d54a71 308
321f85fb
GS
309 if (devc->hid_dev) {
310 hid_close(devc->hid_dev);
311 devc->hid_dev = NULL;
312 }
64d54a71
GS
313
314 return SR_OK;
315}
316
317static int config_get(uint32_t key, GVariant **data,
318 const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
319{
321f85fb 320 gboolean on;
64d54a71
GS
321 int ret;
322
321f85fb
GS
323 if (!cg) {
324 switch (key) {
325 case SR_CONF_CONN:
e333a40c 326 if (!sdi->connection_id)
321f85fb 327 return SR_ERR_NA;
e333a40c 328 *data = g_variant_new_string(sdi->connection_id);
321f85fb
GS
329 return SR_OK;
330 default:
331 return SR_ERR_NA;
332 }
333 }
64d54a71 334
64d54a71 335 switch (key) {
321f85fb
GS
336 case SR_CONF_ENABLED:
337 ret = dcttech_usbrelay_query_cg(sdi, cg, &on);
338 if (ret != SR_OK)
339 return ret;
340 *data = g_variant_new_boolean(on);
341 return SR_OK;
64d54a71
GS
342 default:
343 return SR_ERR_NA;
344 }
64d54a71
GS
345}
346
347static int config_set(uint32_t key, GVariant *data,
348 const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
349{
321f85fb
GS
350 gboolean on;
351
352 if (!cg) {
353 switch (key) {
354 case SR_CONF_ENABLED:
355 /* Enable/disable all channels at the same time. */
356 on = g_variant_get_boolean(data);
357 return dcttech_usbrelay_switch_cg(sdi, cg, on);
358 default:
359 return SR_ERR_NA;
360 }
361 } else {
362 switch (key) {
363 case SR_CONF_ENABLED:
364 on = g_variant_get_boolean(data);
365 return dcttech_usbrelay_switch_cg(sdi, cg, on);
366 default:
367 return SR_ERR_NA;
368 }
64d54a71
GS
369 }
370
321f85fb 371 return SR_OK;
64d54a71
GS
372}
373
374static int config_list(uint32_t key, GVariant **data,
375 const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
376{
64d54a71 377
321f85fb
GS
378 if (!cg) {
379 switch (key) {
380 case SR_CONF_SCAN_OPTIONS:
381 case SR_CONF_DEVICE_OPTIONS:
382 return STD_CONFIG_LIST(key, data, sdi, cg,
383 scanopts, drvopts, devopts);
384 default:
385 return SR_ERR_NA;
386 }
387 }
64d54a71 388
64d54a71 389 switch (key) {
321f85fb
GS
390 case SR_CONF_DEVICE_OPTIONS:
391 *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg));
392 return SR_OK;
64d54a71
GS
393 default:
394 return SR_ERR_NA;
395 }
64d54a71
GS
396}
397
398static struct sr_dev_driver dcttech_usbrelay_driver_info = {
399 .name = "dcttech-usbrelay",
400 .longname = "dcttech usbrelay",
401 .api_version = 1,
402 .init = std_init,
403 .cleanup = std_cleanup,
404 .scan = scan,
405 .dev_list = std_dev_list,
406 .dev_clear = std_dev_clear,
407 .config_get = config_get,
408 .config_set = config_set,
409 .config_list = config_list,
410 .dev_open = dev_open,
411 .dev_close = dev_close,
321f85fb
GS
412 .dev_acquisition_start = std_dummy_dev_acquisition_start,
413 .dev_acquisition_stop = std_dummy_dev_acquisition_stop,
64d54a71
GS
414 .context = NULL,
415};
416SR_REGISTER_DEV_DRIVER(dcttech_usbrelay_driver_info);