]> sigrok.org Git - libsigrok.git/blob - src/hardware/dcttech-usbrelay/api.c
dcttech-usbrelay: accept conn= spec different from hidapi enum details
[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         const wchar_t *vendor, const wchar_t *product)
49 {
50         char nonws[16], *s, *endp;
51         unsigned long relay_count;
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
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
82         /* Open device, need to communicate to identify. */
83         hid = hid_open_path(path);
84         if (!hid) {
85                 sr_err("Cannot open %s.", path);
86                 return NULL;
87         }
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));
97                 sr_spew("Got report bytes: %s, rc %d.", txt->str, ret);
98                 sr_hexdump_free(txt);
99         }
100         if (ret < 0) {
101                 sr_err("Cannot read %s: %ls.", path, hid_error(NULL));
102                 return NULL;
103         }
104         if (ret != sizeof(report)) {
105                 sr_err("Unexpected HID report length %d from %s.", ret, path);
106                 return NULL;
107         }
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;
118                 if (c < 0x20 || c > 0x7e) {
119                         sr_warn("Skipping %s, non-printable serial.", path);
120                         return NULL;
121                 }
122         }
123         curr_state = report[1 + STATE_INDEX];
124         sr_info("HID report data: serial number %s, relay state 0x%02x.",
125                 serno, curr_state);
126
127         /* Create a device instance. */
128         sdi = g_malloc0(sizeof(*sdi));
129         sdi->vendor = g_strdup_printf("%ls", vendor);
130         sdi->model = g_strdup_printf("%ls", product);
131         sdi->serial_num = g_strdup(serno);
132         sdi->connection_id = g_strdup(path);
133         sdi->driver = &dcttech_usbrelay_driver_info;
134         sdi->inst_type = SR_INST_USB;
135
136         /* Create channels (groups). */
137         devc = g_malloc0(sizeof(*devc));
138         sdi->priv = devc;
139         devc->hid_path = g_strdup(path);
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
155 static 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
161 static 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
202 static GSList *scan(struct sr_dev_driver *di, GSList *options)
203 {
204         const char *conn;
205         GSList *devices;
206         struct drv_context *drvc;
207         struct hid_device_info *devs, *curdev;
208         wchar_t *ws;
209         char nonws[32];
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;
217
218         devices = NULL;
219         drvc = di->context;
220         drvc->instances = NULL;
221
222         /*
223          * The firmware is V-USB based. The USB VID:PID identification
224          * is shared across several projects. Need to inspect the vendor
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.
228          *
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.
232          */
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         }
241         devs = hid_enumerate(VENDOR_ID, PRODUCT_ID);
242         for (curdev = devs; curdev; curdev = curdev->next) {
243                 if (conn)
244                         break;
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;
251                 sr_dbg("Checking %04hx:%04hx, vendor %ls, product %ls.",
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);
266                 if (!g_str_has_prefix(nonws, PRODUCT_STRING_PREFIX))
267                         continue;
268
269                 /* Identify device by communicating to it. */
270                 sr_info("Checking HID path %s.", curdev->path);
271                 sdi = probe_device_enum(curdev);
272                 if (!sdi) {
273                         sr_warn("Failed to communicate to %s.", curdev->path);
274                         continue;
275                 }
276                 devices = g_slist_append(devices, sdi);
277         }
278         hid_free_enumeration(devs);
279
280         return devices;
281 }
282
283 static int dev_open(struct sr_dev_inst *sdi)
284 {
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         }
293
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);
299
300         return SR_OK;
301 }
302
303 static int dev_close(struct sr_dev_inst *sdi)
304 {
305         struct dev_context *devc;
306
307         devc = sdi->priv;
308
309         if (devc->hid_dev) {
310                 hid_close(devc->hid_dev);
311                 devc->hid_dev = NULL;
312         }
313
314         return SR_OK;
315 }
316
317 static int config_get(uint32_t key, GVariant **data,
318         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
319 {
320         gboolean on;
321         int ret;
322
323         if (!cg) {
324                 switch (key) {
325                 case SR_CONF_CONN:
326                         if (!sdi->connection_id)
327                                 return SR_ERR_NA;
328                         *data = g_variant_new_string(sdi->connection_id);
329                         return SR_OK;
330                 default:
331                         return SR_ERR_NA;
332                 }
333         }
334
335         switch (key) {
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;
342         default:
343                 return SR_ERR_NA;
344         }
345 }
346
347 static int config_set(uint32_t key, GVariant *data,
348         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
349 {
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                 }
369         }
370
371         return SR_OK;
372 }
373
374 static int config_list(uint32_t key, GVariant **data,
375         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
376 {
377
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         }
388
389         switch (key) {
390         case SR_CONF_DEVICE_OPTIONS:
391                 *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg));
392                 return SR_OK;
393         default:
394                 return SR_ERR_NA;
395         }
396 }
397
398 static 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,
412         .dev_acquisition_start = std_dummy_dev_acquisition_start,
413         .dev_acquisition_stop = std_dummy_dev_acquisition_stop,
414         .context = NULL,
415 };
416 SR_REGISTER_DEV_DRIVER(dcttech_usbrelay_driver_info);