]> sigrok.org Git - libsigrok.git/blob - src/hardware/devantech-eth008/protocol.c
devantech-eth008: support digital and analog input channels
[libsigrok.git] / src / hardware / devantech-eth008 / protocol.c
1 /*
2  * This file is part of the libsigrok project.
3  *
4  * Copyright (C) 2023 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 /*
21  * Communicate to the Devantech ETH008 relay card via TCP and Ethernet.
22  *
23  * See http://www.robot-electronics.co.uk/files/eth008b.pdf for device
24  * capabilities and a protocol discussion.
25  * See https://github.com/devantech/devantech_eth_python for Python
26  * source code which is maintained by the vendor. The untested parts
27  * of this sigrok driver are based on version 0.1.2 of this Python
28  * code which is MIT licensed (corresponds to commit 0c0080b88e29),
29  * and example code in ZIP archives provided on the shop's products'
30  * pages.
31  *
32  * The device provides several means of communication: HTTP requests
33  * (as well as an interactive web form). Raw TCP communication with
34  * binary requests and responses. Text requests and responses over
35  * TCP sockets. Some of these depend on the firmware version. Version
36  * checks before command transmission is essentially non-existent in
37  * this sigrok driver implementation. Binary transmission is preferred
38  * because it is assumed that this existed in all firmware versions.
39  * The firmware interestingly accepts concurrent network connections
40  * (up to five of them, all share the same password). Which means that
41  * the peripheral's state can change even while we control it.
42  *
43  * It's assumed that WLAN models differ from Ethernet devices in terms
44  * of their hardware, but TCP communication should not bother about the
45  * underlying physics, and WLAN cards can re-use model IDs and firmware
46  * implementations. Given sigrok's abstraction of the serial transport
47  * those cards could also be attached by means of COM ports.
48  *
49  * TCP communication seems to rely on network fragmentation and assumes
50  * that software stacks provide all of a request in a single receive
51  * call on the firmware side. Which works for local communication, but
52  * could become an issue when long distances and tunnels are involved.
53  * This sigrok driver also assumes complete reception within a single
54  * receive call. The short length of binary transmission helps here
55  * (the largest payloads has a length of three bytes).
56  *
57  * The lack of length specs as well as termination in the protocol
58  * (both binary as well as text variants over TCP sockets) results in
59  * the inability to synchronize to the firmware when connecting and
60  * after hiccups in an established connection. The fixed length of
61  * requests and responses for binary payloads helps a little bit,
62  * assuming that TCP connect is used to recover. The overhead of
63  * HTTP requests and responses is considered undesirable for this
64  * sigrok driver implementation. [This also means that a transport
65  * which lacks the concept of network frames cannot send passwords.]
66  * The binary transport appears to lack HELLO or NOP requests that
67  * could be used to synchronize. Firmware just would not respond to
68  * unsupported commands. Maybe a repeated sequence of identity reads
69  * combined with a read timeout could help synchronize, but only if
70  * the response is known because the model was identified before.
71  *
72  * The sigrok driver source code was phrased with the addition of more
73  * models in mind. Only few code paths require adjustment when similar
74  * variants of requests or responses are involved in the communication
75  * to relay cards that support between two and twenty channels. Chances
76  * are good, existing firmware is compatible across firmware versions,
77  * and even across hardware revisions (model upgrades). Firmware just
78  * happens to not respond to unknown requests.
79  *
80  * TODO
81  * - Add support for other models. Currently exclusively supports the
82  *   ETH008-B model which was used during implementation of the driver.
83  *   (Descriptions for more models were added, their operation is yet
84  *   to get verified.) Getting relay state involves variable length
85  *   responses, bits appear to be in little endian presentation.
86  * - Add support for absent relay output channels (ETH484 lacks R5..R8).
87  * - Add support for digital inputs. ETH484 has command 0x25 which gets
88  *   two bytes, the second byte carries eight digital input bits.
89  *   ETH1610 has 16 inputs, evaluates both bytes. Is data format u16be?
90  *   ETH8020 support code is inconsistent, implements two accessors
91  *   which either retrieve two or three bytes, while callers access the
92  *   fourth byte of these responses? Cannot have worked, seems untested.
93  * - Add support for analog inputs. ETH484 has command 0x32 which takes
94  *   a channel number, and gets two bytes which carry a u16be value(?).
95  *   So does ETH8020. Channel count differs across models.
96  * - Are there other models of interest? ETH1610 product page reads
97  *   as if the card had 10 relays (strict output), and 16 inputs which
98  *   could either be used in analog mode, or simply get interpreted as
99  *   digital input?
100  * - Add support for password protection?
101  *   - See command 0x79 to "login" (beware of the differing return value
102  *     compared to other commands), command 0x7a to check if passwords
103  *     are involved and whether the login needs refreshing, command 0x7b
104  *     for immediate "logout" in contrast to expiration.
105  *   - Alternatively consider switching to the "text protocol" in that
106  *     use case, which can send an optional password in every request
107  *     that controls relays (command 0x3a).
108  *   - How to specify the password in applications and how to pass them
109  *     to this driver is yet another issue that needs consideration.
110  */
111
112 #include "config.h"
113
114 #include <string.h>
115
116 #include "protocol.h"
117
118 #define READ_TIMEOUT_MS 20
119
120 enum cmd_code {
121         CMD_GET_MODULE_INFO = 0x10,
122         CMD_DIGITAL_ACTIVE = 0x20,
123         CMD_DIGITAL_INACTIVE = 0x21,
124         CMD_DIGITAL_SET_OUTPUTS = 0x23,
125         CMD_DIGITAL_GET_OUTPUTS = 0x24,
126         CMD_DIGITAL_GET_INPUTS = 0x25,
127         CMD_ANALOG_GET_INPUT = 0x32,
128         CMD_ASCII_TEXT_COMMAND = 0x3a,
129         CMD_GET_SERIAL_NUMBER = 0x77,
130         CMD_GET_SUPPLY_VOLTS = 0x78,
131         CMD_PASSWORD_ENTRY = 0x79,
132         CMD_GET_UNLOCK_TIME = 0x7a,
133         CMD_IMMEDIATE_LOGOUT = 0x7b,
134 };
135
136 /*
137  * Transmit a request to the relay card. Checks that all bytes get sent,
138  * short writes are considered fatal.
139  */
140 static int send_request(struct sr_serial_dev_inst *ser,
141         const uint8_t *data, size_t dlen)
142 {
143         int ret;
144         size_t written;
145
146         if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
147                 GString *txt = sr_hexdump_new(data, dlen);
148                 sr_spew("TX --> %s.", txt->str);
149                 sr_hexdump_free(txt);
150         }
151         ret = serial_write_blocking(ser, data, dlen, 0);
152         if (ret < 0)
153                 return ret;
154         written = (size_t)ret;
155         if (written != dlen)
156                 return SR_ERR_DATA;
157         return SR_OK;
158 }
159
160 /*
161  * Receive a response from the relay card. Assumes fixed size payload,
162  * considers short reads fatal.
163  */
164 static int recv_response(struct sr_serial_dev_inst *ser,
165         uint8_t *data, size_t dlen)
166 {
167         int ret;
168         size_t got;
169
170         ret = serial_read_blocking(ser, data, dlen, READ_TIMEOUT_MS);
171         if (ret < 0)
172                 return ret;
173         got = (size_t)ret;
174         if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
175                 GString *txt = sr_hexdump_new(data, got);
176                 sr_spew("<-- RX %s.", txt->str);
177                 sr_hexdump_free(txt);
178         }
179         if (got != dlen)
180                 return SR_ERR_DATA;
181         return SR_OK;
182 }
183
184 /* Send a request then receive a response. Convenience routine. */
185 static int send_then_recv(struct sr_serial_dev_inst *serial,
186         const uint8_t *tx_data, size_t tx_length,
187         uint8_t *rx_data, size_t rx_length)
188 {
189         int ret;
190
191         if (tx_data && tx_length) {
192                 ret = send_request(serial, tx_data, tx_length);
193                 if (ret != SR_OK)
194                         return ret;
195         }
196
197         if (rx_data && rx_length) {
198                 ret = recv_response(serial, rx_data, rx_length);
199                 if (ret != SR_OK)
200                         return ret;
201         }
202
203         return SR_OK;
204 }
205
206 /* Identify the relay card, gather version information details. */
207 SR_PRIV int devantech_eth008_get_model(struct sr_serial_dev_inst *serial,
208         uint8_t *model_code, uint8_t *hw_version, uint8_t *fw_version)
209 {
210         uint8_t req[1], *wrptr;
211         uint8_t rsp[3], v8;
212         const uint8_t *rdptr;
213         int ret;
214
215         if (model_code)
216                 *model_code = 0;
217         if (hw_version)
218                 *hw_version = 0;
219         if (fw_version)
220                 *fw_version = 0;
221
222         wrptr = req;
223         write_u8_inc(&wrptr, CMD_GET_MODULE_INFO);
224         ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
225         if (ret != SR_OK)
226                 return ret;
227         rdptr = rsp;
228
229         v8 = read_u8_inc(&rdptr);
230         if (model_code)
231                 *model_code = v8;
232         v8 = read_u8_inc(&rdptr);
233         if (hw_version)
234                 *hw_version = v8;
235         v8 = read_u8_inc(&rdptr);
236         if (fw_version)
237                 *fw_version = v8;
238
239         return SR_OK;
240 }
241
242 /* Get the relay card's serial number (its MAC address). */
243 SR_PRIV int devantech_eth008_get_serno(struct sr_serial_dev_inst *serial,
244         char *text_buffer, size_t text_length)
245 {
246         uint8_t req[1], *wrptr;
247         uint8_t rsp[6], b;
248         const uint8_t *rdptr, *endptr;
249         size_t written;
250         int ret;
251
252         if (text_buffer && !text_length)
253                 return SR_ERR_ARG;
254         if (text_buffer)
255                 memset(text_buffer, 0, text_length);
256
257         wrptr = req;
258         write_u8_inc(&wrptr, CMD_GET_SERIAL_NUMBER);
259         ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
260         if (ret != SR_OK)
261                 return ret;
262         rdptr = rsp;
263
264         endptr = rsp + sizeof(rsp);
265         while (rdptr < endptr && text_buffer && text_length >= 3) {
266                 b = read_u8_inc(&rdptr);
267                 written = snprintf(text_buffer, text_length, "%02x", b);
268                 text_buffer += written;
269                 text_length -= written;
270         }
271
272         return SR_OK;
273 }
274
275 /* Update an internal cache from the relay card's current state. */
276 SR_PRIV int devantech_eth008_cache_state(const struct sr_dev_inst *sdi)
277 {
278         struct sr_serial_dev_inst *serial;
279         struct dev_context *devc;
280         size_t rx_size;
281         uint8_t req[1], *wrptr;
282         uint8_t rsp[3];
283         const uint8_t *rdptr;
284         uint32_t have;
285         int ret;
286
287         serial = sdi->conn;
288         if (!serial)
289                 return SR_ERR_ARG;
290         devc = sdi->priv;
291         if (!devc)
292                 return SR_ERR_ARG;
293
294         /* Unconditionally get the state of digital output. */
295         rx_size = devc->model->width_do;
296         if (rx_size > sizeof(rsp))
297                 return SR_ERR_NA;
298
299         wrptr = req;
300         write_u8_inc(&wrptr, CMD_DIGITAL_GET_OUTPUTS);
301         ret = send_then_recv(serial, req, wrptr - req, rsp, rx_size);
302         if (ret != SR_OK)
303                 return ret;
304         rdptr = rsp;
305
306         switch (rx_size) {
307         case 1:
308                 have = read_u8_inc(&rdptr);
309                 break;
310         case 2:
311                 have = read_u16le_inc(&rdptr);
312                 break;
313         case 3:
314                 have = read_u24le_inc(&rdptr);
315                 break;
316         default:
317                 return SR_ERR_NA;
318         }
319         have &= devc->mask_do;
320         devc->curr_do = have;
321
322         /*
323          * Get the state of digital inputs when the model supports them.
324          * Firmware of other models happens to not respond to unknown
325          * requests. Responses seem to have identical size across all
326          * models. Payload is assumed to be u16 be formatted. Must be
327          * verified when other models are seen.
328          *
329          * Caching the state of analog inputs is condidered undesirable.
330          */
331         if (devc->model->ch_count_di) {
332                 rx_size = sizeof(uint16_t);
333                 if (rx_size > sizeof(rsp))
334                         return SR_ERR_NA;
335
336                 wrptr = req;
337                 write_u8_inc(&wrptr, CMD_DIGITAL_GET_INPUTS);
338                 ret = send_then_recv(serial, req, wrptr - req, rsp, rx_size);
339                 if (ret != SR_OK)
340                         return ret;
341                 rdptr = rsp;
342
343                 switch (rx_size) {
344                 case 2:
345                         have = read_u16be_inc(&rdptr);
346                         break;
347                 default:
348                         return SR_ERR_NA;
349                 }
350                 have &= (1UL << devc->model->ch_count_di) - 1;
351                 devc->curr_di = have;
352         }
353
354         return SR_OK;
355 }
356
357 /* Query the state of an individual relay channel. */
358 SR_PRIV int devantech_eth008_query_do(const struct sr_dev_inst *sdi,
359         const struct sr_channel_group *cg, gboolean *on)
360 {
361         struct dev_context *devc;
362         struct channel_group_context *cgc;
363         uint32_t have;
364         int ret;
365
366         devc = sdi->priv;
367         if (!devc)
368                 return SR_ERR_ARG;
369
370         /* Unconditionally update the internal cache. */
371         ret = devantech_eth008_cache_state(sdi);
372         if (ret != SR_OK)
373                 return ret;
374
375         /*
376          * Only reject unexpected requeusts after the update. Get the
377          * individual channel's state from the cache of all channels.
378          */
379         if (!cg)
380                 return SR_ERR_ARG;
381         cgc = cg->priv;
382         if (!cgc)
383                 return SR_ERR_BUG;
384         if (cgc->index >= devc->model->ch_count_do)
385                 return SR_ERR_ARG;
386         have = devc->curr_do;
387         have >>= cgc->index;
388         have &= 1 << 0;
389         if (on)
390                 *on = have ? TRUE : FALSE;
391
392         return SR_OK;
393 }
394
395 /*
396  * Manipulate the state of an individual relay channel (when cg is given).
397  * Or set/clear all channels at the same time (when cg is NULL).
398  */
399 SR_PRIV int devantech_eth008_setup_do(const struct sr_dev_inst *sdi,
400         const struct sr_channel_group *cg, gboolean on)
401 {
402         struct sr_serial_dev_inst *serial;
403         struct dev_context *devc;
404         size_t width_do;
405         struct channel_group_context *cgc;
406         size_t number;
407         uint32_t reg;
408         uint8_t req[4], *wrptr, cmd;
409         uint8_t rsp[1], v8;
410         const uint8_t *rdptr;
411         int ret;
412
413         serial = sdi->conn;
414         if (!serial)
415                 return SR_ERR_ARG;
416         devc = sdi->priv;
417         if (!devc)
418                 return SR_ERR_ARG;
419         cgc = cg ? cg->priv : NULL;
420         if (cgc && cgc->index >= devc->model->ch_count_do)
421                 return SR_ERR_ARG;
422
423         width_do = devc->model->width_do;
424         if (1 + width_do > sizeof(req))
425                 return SR_ERR_NA;
426
427         wrptr = req;
428         if (cgc) {
429                 /* Manipulate an individual channel. */
430                 cmd = on ? CMD_DIGITAL_ACTIVE : CMD_DIGITAL_INACTIVE;
431                 number = cgc->number;
432                 write_u8_inc(&wrptr, cmd);
433                 write_u8_inc(&wrptr, number & 0xff);
434                 write_u8_inc(&wrptr, 0); /* Just set/clear, no pulse. */
435         } else {
436                 /* Manipulate all channels at the same time. */
437                 reg = on ? devc->mask_do : 0;
438                 write_u8_inc(&wrptr, CMD_DIGITAL_SET_OUTPUTS);
439                 switch (width_do) {
440                 case 1:
441                         write_u8_inc(&wrptr, reg & 0xff);
442                         break;
443                 case 2:
444                         write_u16le_inc(&wrptr, reg & 0xffff);
445                         break;
446                 case 3:
447                         write_u24le_inc(&wrptr, reg & 0xffffff);
448                         break;
449                 default:
450                         return SR_ERR_NA;
451                 }
452         }
453         ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
454         if (ret != SR_OK)
455                 return ret;
456         rdptr = rsp;
457
458         v8 = read_u8_inc(&rdptr);
459         if (v8 != 0)
460                 return SR_ERR_DATA;
461
462         return SR_OK;
463 }
464
465 SR_PRIV int devantech_eth008_query_di(const struct sr_dev_inst *sdi,
466         const struct sr_channel_group *cg, gboolean *on)
467 {
468         struct dev_context *devc;
469         struct channel_group_context *cgc;
470         uint32_t have;
471         int ret;
472
473         /* Unconditionally update the internal cache. */
474         ret = devantech_eth008_cache_state(sdi);
475         if (ret != SR_OK)
476                 return ret;
477
478         /*
479          * Only reject unexpected requeusts after the update. Get the
480          * individual channel's state from the cache of all channels.
481          */
482         devc = sdi->priv;
483         if (!devc)
484                 return SR_ERR_ARG;
485         if (!cg)
486                 return SR_ERR_ARG;
487         cgc = cg->priv;
488         if (!cgc)
489                 return SR_ERR_BUG;
490         if (cgc->index >= devc->model->ch_count_di)
491                 return SR_ERR_ARG;
492         have = devc->curr_di;
493         have >>= cgc->index;
494         have &= 1 << 0;
495         if (on)
496                 *on = have ? TRUE : FALSE;
497
498         return SR_OK;
499 }
500
501 SR_PRIV int devantech_eth008_query_ai(const struct sr_dev_inst *sdi,
502         const struct sr_channel_group *cg, uint16_t *adc_value)
503 {
504         struct sr_serial_dev_inst *serial;
505         struct dev_context *devc;
506         struct channel_group_context *cgc;
507         uint8_t req[2], *wrptr;
508         uint8_t rsp[2];
509         const uint8_t *rdptr;
510         uint32_t have;
511         int ret;
512
513         serial = sdi->conn;
514         if (!serial)
515                 return SR_ERR_ARG;
516         devc = sdi->priv;
517         if (!devc)
518                 return SR_ERR_ARG;
519         if (!cg)
520                 return SR_ERR_ARG;
521         cgc = cg->priv;
522         if (!cgc)
523                 return SR_ERR_ARG;
524         if (cgc->index >= devc->model->ch_count_ai)
525                 return SR_ERR_ARG;
526
527         wrptr = req;
528         write_u8_inc(&wrptr, CMD_ANALOG_GET_INPUT);
529         write_u8_inc(&wrptr, cgc->number & 0xff);
530         ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
531         if (ret != SR_OK)
532                 return ret;
533         rdptr = rsp;
534
535         /*
536          * TODO The u16 BE format is a guess. Needs verification.
537          * As is the unit-less nature of that value.
538          */
539         have = read_u16be_inc(&rdptr);
540         if (adc_value)
541                 *adc_value = have;
542
543         return SR_OK;
544 }
545
546 SR_PRIV int devantech_eth008_query_supply(const struct sr_dev_inst *sdi,
547         const struct sr_channel_group *cg, uint16_t *millivolts)
548 {
549         struct sr_serial_dev_inst *serial;
550         uint8_t req[1], *wrptr;
551         uint8_t rsp[1];
552         const uint8_t *rdptr;
553         uint16_t have;
554         int ret;
555
556         (void)cg;
557
558         serial = sdi->conn;
559         if (!serial)
560                 return SR_ERR_ARG;
561
562         wrptr = req;
563         write_u8_inc(&wrptr, CMD_GET_SUPPLY_VOLTS);
564         ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
565         if (ret != SR_OK)
566                 return ret;
567         rdptr = rsp;
568
569         /* Gets a byte for voltage in units of 0.1V. Scale up to mV. */
570         have = read_u8_inc(&rdptr);
571         have *= 100;
572         if (millivolts)
573                 *millivolts = have;
574
575         return SR_OK;
576 }