]> sigrok.org Git - libsigrok.git/blob - src/hardware/dcttech-usbrelay/api.c
583a12c4117abe03d8dc82a230154cb2e8462789
[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 <hidapi.h>
23 #include <string.h>
24
25 #include "protocol.h"
26
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
44 static struct sr_dev_driver dcttech_usbrelay_driver_info;
45
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         sdi->serial_num = g_strdup(serno);
102         sdi->connection_id = g_strdup(path);
103         devc = g_malloc0(sizeof(*devc));
104         sdi->priv = devc;
105         devc->hid_path = g_strdup(path);
106         devc->relay_count = relay_count;
107         devc->relay_mask = (1U << relay_count) - 1;
108         for (idx = 0; idx < devc->relay_count; idx++) {
109                 nr = idx + 1;
110                 cg = g_malloc0(sizeof(*cg));
111                 cg->name = g_strdup_printf("R%zu", nr);
112                 cgc = g_malloc0(sizeof(*cgc));
113                 cg->priv = cgc;
114                 cgc->number = nr;
115                 sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
116         }
117
118         return sdi;
119 }
120
121 static GSList *scan(struct sr_dev_driver *di, GSList *options)
122 {
123         const char *conn;
124         GSList *devices;
125         struct drv_context *drvc;
126         struct hid_device_info *devs, *curdev;
127         int ret;
128         wchar_t *ws;
129         char nonws[32];
130         char *s, *endp;
131         unsigned long relay_count;
132         struct sr_dev_inst *sdi;
133
134         /* Get optional conn= spec when provided. */
135         conn = NULL;
136         (void)sr_serial_extract_options(options, &conn, NULL);
137         if (conn && !*conn)
138                 conn = NULL;
139         /*
140          * TODO Accept different types of conn= specs? Either paths that
141          * hidapi(3) can open. Or bus.addr specs that we can check for
142          * during USB enumeration. Derive want_path, want_bus, want_addr
143          * here from the optional conn= spec.
144          */
145
146         devices = NULL;
147         drvc = di->context;
148         drvc->instances = NULL;
149
150         /*
151          * The firmware is V-USB based. The USB VID:PID identification
152          * is shared across several projects. Need to inspect the vendor
153          * and product _strings_ to actually identify the device.
154          *
155          * The USB serial number need not be present nor reliable. The
156          * HID report contains a five character string which may serve
157          * as an identification for boards (is said to differ between
158          * boards). The last byte encodes the current relays state.
159          */
160         devs = hid_enumerate(VENDOR_ID, PRODUCT_ID);
161         for (curdev = devs; curdev; curdev = curdev->next) {
162                 if (!curdev->vendor_id || !curdev->product_id)
163                         continue;
164                 if (!curdev->manufacturer_string || !curdev->product_string)
165                         continue;
166                 if (!*curdev->manufacturer_string || !*curdev->product_string)
167                         continue;
168                 if (conn && strcmp(curdev->path, conn) != 0) {
169                         sr_dbg("skipping %s, conn= mismatch", curdev->path);
170                         continue;
171                 }
172                 sr_dbg("checking %04hx:%04hx, vendor %ls, product %ls.",
173                         curdev->vendor_id, curdev->product_id,
174                         curdev->manufacturer_string, curdev->product_string);
175
176                 /* Check USB details retrieved by enumeration. */
177                 ws = curdev->manufacturer_string;
178                 if (!ws || !wcslen(ws))
179                         continue;
180                 snprintf(nonws, sizeof(nonws), "%ls", ws);
181                 if (strcmp(nonws, VENDOR_STRING) != 0)
182                         continue;
183                 ws = curdev->product_string;
184                 if (!ws || !wcslen(ws))
185                         continue;
186                 snprintf(nonws, sizeof(nonws), "%ls", ws);
187                 s = nonws;
188                 if (!g_str_has_prefix(s, PRODUCT_STRING_PREFIX))
189                         continue;
190                 s += strlen(PRODUCT_STRING_PREFIX);
191                 ret = sr_atoul_base(s, &relay_count, &endp, 10);
192                 if (ret != SR_OK || !endp || *endp)
193                         continue;
194                 sr_info("Found: HID path %s, relay count %lu.",
195                         curdev->path, relay_count);
196
197                 /* Identify device by communicating to it. */
198                 sdi = probe_device(curdev->path, relay_count);
199                 if (!sdi) {
200                         sr_warn("Failed to communicate to %s.", curdev->path);
201                         continue;
202                 }
203
204                 /* Amend driver instance from USB enumeration details. */
205                 sdi->vendor = g_strdup_printf("%ls", curdev->manufacturer_string);
206                 sdi->model = g_strdup_printf("%ls", curdev->product_string);
207                 sdi->driver = &dcttech_usbrelay_driver_info;
208                 sdi->inst_type = SR_INST_USB;
209
210                 devices = g_slist_append(devices, sdi);
211         }
212         hid_free_enumeration(devs);
213
214         return devices;
215 }
216
217 static int dev_open(struct sr_dev_inst *sdi)
218 {
219         struct dev_context *devc;
220
221         devc = sdi->priv;
222
223         if (devc->hid_dev) {
224                 hid_close(devc->hid_dev);
225                 devc->hid_dev = NULL;
226         }
227
228         devc->hid_dev = hid_open_path(devc->hid_path);
229         if (!devc->hid_dev)
230                 return SR_ERR_IO;
231
232         (void)dcttech_usbrelay_update_state(sdi);
233
234         return SR_OK;
235 }
236
237 static int dev_close(struct sr_dev_inst *sdi)
238 {
239         struct dev_context *devc;
240
241         devc = sdi->priv;
242
243         if (devc->hid_dev) {
244                 hid_close(devc->hid_dev);
245                 devc->hid_dev = NULL;
246         }
247
248         return SR_OK;
249 }
250
251 static int config_get(uint32_t key, GVariant **data,
252         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
253 {
254         gboolean on;
255         int ret;
256
257         if (!cg) {
258                 switch (key) {
259                 case SR_CONF_CONN:
260                         if (!sdi->connection_id)
261                                 return SR_ERR_NA;
262                         *data = g_variant_new_string(sdi->connection_id);
263                         return SR_OK;
264                 default:
265                         return SR_ERR_NA;
266                 }
267         }
268
269         switch (key) {
270         case SR_CONF_ENABLED:
271                 ret = dcttech_usbrelay_query_cg(sdi, cg, &on);
272                 if (ret != SR_OK)
273                         return ret;
274                 *data = g_variant_new_boolean(on);
275                 return SR_OK;
276         default:
277                 return SR_ERR_NA;
278         }
279 }
280
281 static int config_set(uint32_t key, GVariant *data,
282         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
283 {
284         gboolean on;
285
286         if (!cg) {
287                 switch (key) {
288                 case SR_CONF_ENABLED:
289                         /* Enable/disable all channels at the same time. */
290                         on = g_variant_get_boolean(data);
291                         return dcttech_usbrelay_switch_cg(sdi, cg, on);
292                 default:
293                         return SR_ERR_NA;
294                 }
295         } else {
296                 switch (key) {
297                 case SR_CONF_ENABLED:
298                         on = g_variant_get_boolean(data);
299                         return dcttech_usbrelay_switch_cg(sdi, cg, on);
300                 default:
301                         return SR_ERR_NA;
302                 }
303         }
304
305         return SR_OK;
306 }
307
308 static int config_list(uint32_t key, GVariant **data,
309         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
310 {
311
312         if (!cg) {
313                 switch (key) {
314                 case SR_CONF_SCAN_OPTIONS:
315                 case SR_CONF_DEVICE_OPTIONS:
316                         return STD_CONFIG_LIST(key, data, sdi, cg,
317                                 scanopts, drvopts, devopts);
318                 default:
319                         return SR_ERR_NA;
320                 }
321         }
322
323         switch (key) {
324         case SR_CONF_DEVICE_OPTIONS:
325                 *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg));
326                 return SR_OK;
327         default:
328                 return SR_ERR_NA;
329         }
330 }
331
332 static struct sr_dev_driver dcttech_usbrelay_driver_info = {
333         .name = "dcttech-usbrelay",
334         .longname = "dcttech usbrelay",
335         .api_version = 1,
336         .init = std_init,
337         .cleanup = std_cleanup,
338         .scan = scan,
339         .dev_list = std_dev_list,
340         .dev_clear = std_dev_clear,
341         .config_get = config_get,
342         .config_set = config_set,
343         .config_list = config_list,
344         .dev_open = dev_open,
345         .dev_close = dev_close,
346         .dev_acquisition_start = std_dummy_dev_acquisition_start,
347         .dev_acquisition_stop = std_dummy_dev_acquisition_stop,
348         .context = NULL,
349 };
350 SR_REGISTER_DEV_DRIVER(dcttech_usbrelay_driver_info);