dcttech-usbrelay: implement multiplexer driver for USB relay card
[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         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
119 static GSList *scan(struct sr_dev_driver *di, GSList *options)
120 {
121         const char *conn;
122         GSList *devices;
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          */
143
144         devices = NULL;
145         drvc = di->context;
146         drvc->instances = NULL;
147
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);
212
213         return devices;
214 }
215
216 static int dev_open(struct sr_dev_inst *sdi)
217 {
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         }
226
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);
232
233         return SR_OK;
234 }
235
236 static int dev_close(struct sr_dev_inst *sdi)
237 {
238         struct dev_context *devc;
239
240         devc = sdi->priv;
241
242         if (devc->hid_dev) {
243                 hid_close(devc->hid_dev);
244                 devc->hid_dev = NULL;
245         }
246
247         return SR_OK;
248 }
249
250 static int config_get(uint32_t key, GVariant **data,
251         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
252 {
253         gboolean on;
254         int ret;
255
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         }
267
268         switch (key) {
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;
275         default:
276                 return SR_ERR_NA;
277         }
278 }
279
280 static int config_set(uint32_t key, GVariant *data,
281         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
282 {
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                 }
302         }
303
304         return SR_OK;
305 }
306
307 static int config_list(uint32_t key, GVariant **data,
308         const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
309 {
310
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         }
321
322         switch (key) {
323         case SR_CONF_DEVICE_OPTIONS:
324                 *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg));
325                 return SR_OK;
326         default:
327                 return SR_ERR_NA;
328         }
329 }
330
331 static struct sr_dev_driver dcttech_usbrelay_driver_info = {
332         .name = "dcttech-usbrelay",
333         .longname = "dcttech usbrelay",
334         .api_version = 1,
335         .init = std_init,
336         .cleanup = std_cleanup,
337         .scan = scan,
338         .dev_list = std_dev_list,
339         .dev_clear = std_dev_clear,
340         .config_get = config_get,
341         .config_set = config_set,
342         .config_list = config_list,
343         .dev_open = dev_open,
344         .dev_close = dev_close,
345         .dev_acquisition_start = std_dummy_dev_acquisition_start,
346         .dev_acquisition_stop = std_dummy_dev_acquisition_stop,
347         .context = NULL,
348 };
349 SR_REGISTER_DEV_DRIVER(dcttech_usbrelay_driver_info);