]> sigrok.org Git - libsigrok.git/blob - src/hardware/dcttech-usbrelay/api.c
dcttech-usbrelay: accept conn=sn=<serno> user specs in probe
[libsigrok.git] / src / hardware / dcttech-usbrelay / api.c
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>
21
22 #include <ctype.h>
23 #include <hidapi.h>
24 #include <string.h>
25
26 #include "protocol.h"
27
28 static const uint32_t scanopts[] = {
29         SR_CONF_CONN,
30 };
31
32 static const uint32_t drvopts[] = {
33         SR_CONF_MULTIPLEXER,
34 };
35
36 static 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
41 static const uint32_t devopts_cg[] = {
42         SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET,
43 };
44
45 static struct sr_dev_driver dcttech_usbrelay_driver_info;
46
47 static struct sr_dev_inst *probe_device_common(const char *path,
48         uint16_t vid, uint16_t pid, const char *want_serno,
49         const wchar_t *vendor, const wchar_t *product)
50 {
51         char nonws[16], *s, *endp;
52         unsigned long relay_count;
53         hid_device *hid;
54         int ret;
55         char serno[SERNO_LENGTH + 1];
56         uint8_t curr_state;
57         uint8_t report[1 + REPORT_BYTECOUNT];
58         GString *txt;
59         size_t snr_pos;
60         char c;
61         struct sr_dev_inst *sdi;
62         struct dev_context *devc;
63         struct channel_group_context *cgc;
64         size_t idx, nr;
65         struct sr_channel_group *cg;
66
67         /*
68          * Get relay count from product string. Weak condition,
69          * accept any trailing number regardless of preceeding text.
70          */
71         snprintf(nonws, sizeof(nonws), "%ls", product);
72         s = nonws;
73         s += strlen(s);
74         while (s > nonws && isdigit((int)s[-1]))
75                 s--;
76         ret = sr_atoul_base(s, &relay_count, &endp, 10);
77         if (ret != SR_OK || !endp || *endp)
78                 return NULL;
79         if (!relay_count)
80                 return NULL;
81         sr_info("Relay count %lu from product string %s.", relay_count, nonws);
82
83         /* Open device, need to communicate to identify. */
84         if (vid && pid)
85                 hid = hid_open(vid, pid, NULL);
86         else
87                 hid = hid_open_path(path);
88         if (!hid) {
89                 sr_err("Cannot open %s.", path);
90                 return NULL;
91         }
92
93         /* Get an HID report. */
94         hid_set_nonblocking(hid, 0);
95         memset(&report, 0, sizeof(report));
96         report[0] = REPORT_NUMBER;
97         ret = hid_get_feature_report(hid, report, sizeof(report));
98         hid_close(hid);
99         if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
100                 txt = sr_hexdump_new(report, sizeof(report));
101                 sr_spew("Got report bytes: %s, rc %d.", txt->str, ret);
102                 sr_hexdump_free(txt);
103         }
104         if (ret < 0) {
105                 sr_err("Cannot read %s: %ls.", path, hid_error(NULL));
106                 return NULL;
107         }
108         if (ret != sizeof(report)) {
109                 sr_err("Unexpected HID report length %d from %s.", ret, path);
110                 return NULL;
111         }
112
113         /*
114          * Serial number must be all printable characters. Relay state
115          * is for information only, gets re-retrieved before configure
116          * API calls (get/set).
117          */
118         memset(serno, 0, sizeof(serno));
119         for (snr_pos = 0; snr_pos < SERNO_LENGTH; snr_pos++) {
120                 c = report[1 + snr_pos];
121                 serno[snr_pos] = c;
122                 if (c < 0x20 || c > 0x7e) {
123                         sr_warn("Skipping %s, non-printable serial.", path);
124                         return NULL;
125                 }
126         }
127         curr_state = report[1 + STATE_INDEX];
128         sr_info("HID report data: serial number %s, relay state 0x%02x.",
129                 serno, curr_state);
130
131         /* Optionally filter by serial number. */
132         if (want_serno && *want_serno && strcmp(serno, want_serno) != 0) {
133                 sr_dbg("Serial number does not match user spec. Skipping.");
134                 return NULL;
135         }
136
137         /* Create a device instance. */
138         sdi = g_malloc0(sizeof(*sdi));
139         sdi->vendor = g_strdup_printf("%ls", vendor);
140         sdi->model = g_strdup_printf("%ls", product);
141         sdi->serial_num = g_strdup(serno);
142         sdi->connection_id = g_strdup(path);
143         sdi->driver = &dcttech_usbrelay_driver_info;
144         sdi->inst_type = SR_INST_USB;
145
146         /* Create channels (groups). */
147         devc = g_malloc0(sizeof(*devc));
148         sdi->priv = devc;
149         devc->hid_path = g_strdup(path);
150         devc->usb_vid = vid;
151         devc->usb_pid = pid;
152         devc->relay_count = relay_count;
153         devc->relay_mask = (1U << relay_count) - 1;
154         for (idx = 0; idx < devc->relay_count; idx++) {
155                 nr = idx + 1;
156                 cg = g_malloc0(sizeof(*cg));
157                 cg->name = g_strdup_printf("R%zu", nr);
158                 cgc = g_malloc0(sizeof(*cgc));
159                 cg->priv = cgc;
160                 cgc->number = nr;
161                 sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
162         }
163
164         return sdi;
165 }
166
167 static struct sr_dev_inst *probe_device_enum(struct hid_device_info *dev,
168         const char *want_serno)
169 {
170         return probe_device_common(dev->path, 0, 0, want_serno,
171                 dev->manufacturer_string, dev->product_string);
172 }
173
174 static struct sr_dev_inst *probe_device_conn(const char *path)
175 {
176         char vid_pid[12];
177         uint16_t vid, pid;
178         const char *s;
179         char *endp;
180         unsigned long num;
181         hid_device *dev;
182         gboolean ok;
183         int ret;
184         wchar_t vendor[32], product[32];
185
186         /*
187          * The hidapi(3) library's API strives for maximum portability,
188          * thus won't provide ways of getting a path from alternative
189          * presentations like VID:PID pairs, bus.addr specs, etc. The
190          * typical V-USB setup neither provides reliable serial numbers
191          * (that USB enumeration would cover). So this driver's support
192          * for conn= specs beyond Unix style path names is limited, too.
193          * This implementation tries "VID.PID" then assumes "path". The
194          * inability to even get the path for a successfully opened HID
195          * results in redundancy across the places which open devices.
196          */
197
198         /* Check for "<vid>.<pid>" specs. */
199         vid = pid = 0;
200         s = path;
201         ret = sr_atoul_base(s, &num, &endp, 16);
202         if (ret == SR_OK && endp && endp == s + 4 && *endp == '.' && num) {
203                 vid = num;
204                 s = ++endp;
205         }
206         ret = sr_atoul_base(s, &num, &endp, 16);
207         if (ret == SR_OK && endp && endp == s + 4 && *endp == '\0' && num) {
208                 pid = num;
209                 s = ++endp;
210         }
211         if (vid && pid) {
212                 snprintf(vid_pid, sizeof(vid_pid), "%04x.%04x", vid, pid);
213                 path = vid_pid;
214                 sr_dbg("Using VID.PID %s.", path);
215         }
216
217         /* Open the device, get vendor and product strings. */
218         if (vid && pid)
219                 dev = hid_open(vid, pid, NULL);
220         else
221                 dev = hid_open_path(path);
222         if (!dev) {
223                 sr_err("Cannot open %s.", path);
224                 return NULL;
225         }
226         ok = TRUE;
227         ret = hid_get_manufacturer_string(dev, vendor, ARRAY_SIZE(vendor));
228         if (ret != 0)
229                 ok = FALSE;
230         if (!wcslen(vendor))
231                 ok = FALSE;
232         ret = hid_get_product_string(dev, product, ARRAY_SIZE(product));
233         if (ret != 0)
234                 ok = FALSE;
235         if (!wcslen(product))
236                 ok = FALSE;
237         hid_close(dev);
238         if (!ok)
239                 return NULL;
240
241         return probe_device_common(path, vid, pid, NULL, vendor, product);
242 }
243
244 static GSList *scan(struct sr_dev_driver *di, GSList *options)
245 {
246         const char *conn;
247         GSList *devices;
248         struct drv_context *drvc;
249         char want_serno[SERNO_LENGTH + 1];
250         struct hid_device_info *devs, *curdev;
251         wchar_t *ws;
252         char nonws[32];
253         struct sr_dev_inst *sdi;
254
255         /* Get optional conn= spec when provided. */
256         conn = NULL;
257         (void)sr_serial_extract_options(options, &conn, NULL);
258         if (conn && !*conn)
259                 conn = NULL;
260
261         devices = NULL;
262         drvc = di->context;
263         drvc->instances = NULL;
264
265         /*
266          * The firmware is V-USB based. The USB VID:PID identification
267          * is shared across several projects. Need to inspect the vendor
268          * and product _strings_ to actually identify the device.
269          *
270          * The USB serial number need not be present nor reliable. The
271          * HID report content will carry the board's serial number.
272          * When users specify "sn=..." connection strings, then run a
273          * regular USB enumation, and filter the result set by serial
274          * numbers which only become available with HID reports.
275          *
276          * When other connection strings were specified, then have
277          * HIDAPI open _this_ device and skip the enumeration. Which
278          * allows users to specify paths that need not match the
279          * enumeration's details.
280          */
281         memset(want_serno, 0, sizeof(want_serno));
282         if (conn && g_str_has_prefix(conn, "sn=")) {
283                 conn += strlen("sn=");
284                 snprintf(want_serno, sizeof(want_serno), "%s", conn);
285                 conn = NULL;
286         }
287         if (conn) {
288                 sr_info("Checking HID path %s.", conn);
289                 sdi = probe_device_conn(conn);
290                 if (!sdi)
291                         sr_warn("Failed to communicate to %s.", conn);
292                 else
293                         devices = g_slist_append(devices, sdi);
294         }
295         devs = hid_enumerate(VENDOR_ID, PRODUCT_ID);
296         for (curdev = devs; curdev; curdev = curdev->next) {
297                 if (conn)
298                         break;
299                 if (!curdev->vendor_id || !curdev->product_id)
300                         continue;
301                 if (!curdev->manufacturer_string || !curdev->product_string)
302                         continue;
303                 if (!*curdev->manufacturer_string || !*curdev->product_string)
304                         continue;
305                 sr_dbg("Checking %04hx:%04hx, vendor %ls, product %ls.",
306                         curdev->vendor_id, curdev->product_id,
307                         curdev->manufacturer_string, curdev->product_string);
308
309                 /* Check USB details retrieved by enumeration. */
310                 ws = curdev->manufacturer_string;
311                 if (!ws || !wcslen(ws))
312                         continue;
313                 snprintf(nonws, sizeof(nonws), "%ls", ws);
314                 if (strcmp(nonws, VENDOR_STRING) != 0)
315                         continue;
316                 ws = curdev->product_string;
317                 if (!ws || !wcslen(ws))
318                         continue;
319                 snprintf(nonws, sizeof(nonws), "%ls", ws);
320                 if (!g_str_has_prefix(nonws, PRODUCT_STRING_PREFIX))
321                         continue;
322
323                 /* Identify device by communicating to it. */
324                 sr_info("Checking HID path %s.", curdev->path);
325                 sdi = probe_device_enum(curdev, want_serno);
326                 if (!sdi)
327                         continue;
328                 devices = g_slist_append(devices, sdi);
329         }
330         hid_free_enumeration(devs);
331
332         return devices;
333 }
334
335 static int dev_open(struct sr_dev_inst *sdi)
336 {
337         struct dev_context *devc;
338
339         devc = sdi->priv;
340
341         if (devc->hid_dev) {
342                 hid_close(devc->hid_dev);
343                 devc->hid_dev = NULL;
344         }
345
346         if (devc->usb_vid && devc->usb_pid)
347                 devc->hid_dev = hid_open(devc->usb_vid, devc->usb_pid, NULL);
348         else
349                 devc->hid_dev = hid_open_path(devc->hid_path);
350         if (!devc->hid_dev)
351                 return SR_ERR_IO;
352
353         (void)dcttech_usbrelay_update_state(sdi);
354
355         return SR_OK;
356 }
357
358 static int dev_close(struct sr_dev_inst *sdi)
359 {
360         struct dev_context *devc;
361
362         devc = sdi->priv;
363
364         if (devc->hid_dev) {
365                 hid_close(devc->hid_dev);
366                 devc->hid_dev = NULL;
367         }
368
369         return SR_OK;
370 }
371
372 static int config_get(uint32_t key, GVariant **data,
373         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
374 {
375         gboolean on;
376         int ret;
377
378         if (!cg) {
379                 switch (key) {
380                 case SR_CONF_CONN:
381                         if (!sdi->connection_id)
382                                 return SR_ERR_NA;
383                         *data = g_variant_new_string(sdi->connection_id);
384                         return SR_OK;
385                 default:
386                         return SR_ERR_NA;
387                 }
388         }
389
390         switch (key) {
391         case SR_CONF_ENABLED:
392                 ret = dcttech_usbrelay_query_cg(sdi, cg, &on);
393                 if (ret != SR_OK)
394                         return ret;
395                 *data = g_variant_new_boolean(on);
396                 return SR_OK;
397         default:
398                 return SR_ERR_NA;
399         }
400 }
401
402 static int config_set(uint32_t key, GVariant *data,
403         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
404 {
405         gboolean on;
406
407         if (!cg) {
408                 switch (key) {
409                 case SR_CONF_ENABLED:
410                         /* Enable/disable all channels at the same time. */
411                         on = g_variant_get_boolean(data);
412                         return dcttech_usbrelay_switch_cg(sdi, cg, on);
413                 default:
414                         return SR_ERR_NA;
415                 }
416         } else {
417                 switch (key) {
418                 case SR_CONF_ENABLED:
419                         on = g_variant_get_boolean(data);
420                         return dcttech_usbrelay_switch_cg(sdi, cg, on);
421                 default:
422                         return SR_ERR_NA;
423                 }
424         }
425
426         return SR_OK;
427 }
428
429 static int config_list(uint32_t key, GVariant **data,
430         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
431 {
432
433         if (!cg) {
434                 switch (key) {
435                 case SR_CONF_SCAN_OPTIONS:
436                 case SR_CONF_DEVICE_OPTIONS:
437                         return STD_CONFIG_LIST(key, data, sdi, cg,
438                                 scanopts, drvopts, devopts);
439                 default:
440                         return SR_ERR_NA;
441                 }
442         }
443
444         switch (key) {
445         case SR_CONF_DEVICE_OPTIONS:
446                 *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg));
447                 return SR_OK;
448         default:
449                 return SR_ERR_NA;
450         }
451 }
452
453 static struct sr_dev_driver dcttech_usbrelay_driver_info = {
454         .name = "dcttech-usbrelay",
455         .longname = "dcttech usbrelay",
456         .api_version = 1,
457         .init = std_init,
458         .cleanup = std_cleanup,
459         .scan = scan,
460         .dev_list = std_dev_list,
461         .dev_clear = std_dev_clear,
462         .config_get = config_get,
463         .config_set = config_set,
464         .config_list = config_list,
465         .dev_open = dev_open,
466         .dev_close = dev_close,
467         .dev_acquisition_start = std_dummy_dev_acquisition_start,
468         .dev_acquisition_stop = std_dummy_dev_acquisition_stop,
469         .context = NULL,
470 };
471 SR_REGISTER_DEV_DRIVER(dcttech_usbrelay_driver_info);