]> sigrok.org Git - libsigrok.git/blob - src/hardware/devantech-eth008/protocol.c
devantech-eth008: make request for digital output optional
[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         /* Get the state of digital outputs when the model supports them. */
295         if (devc->model->ch_count_do) {
296                 rx_size = devc->model->width_do;
297                 if (rx_size > sizeof(rsp))
298                         return SR_ERR_NA;
299
300                 wrptr = req;
301                 write_u8_inc(&wrptr, CMD_DIGITAL_GET_OUTPUTS);
302                 ret = send_then_recv(serial, req, wrptr - req, rsp, rx_size);
303                 if (ret != SR_OK)
304                         return ret;
305                 rdptr = rsp;
306
307                 switch (rx_size) {
308                 case 1:
309                         have = read_u8_inc(&rdptr);
310                         break;
311                 case 2:
312                         have = read_u16le_inc(&rdptr);
313                         break;
314                 case 3:
315                         have = read_u24le_inc(&rdptr);
316                         break;
317                 default:
318                         return SR_ERR_NA;
319                 }
320                 have &= devc->mask_do;
321                 devc->curr_do = have;
322         }
323
324         /*
325          * Get the state of digital inputs when the model supports them.
326          * Firmware of other models happens to not respond to unknown
327          * requests. Responses seem to have identical size across all
328          * models. Payload is assumed to be u16 be formatted. Must be
329          * verified when other models are seen.
330          *
331          * Caching the state of analog inputs is condidered undesirable.
332          */
333         if (devc->model->ch_count_di) {
334                 rx_size = sizeof(uint16_t);
335                 if (rx_size > sizeof(rsp))
336                         return SR_ERR_NA;
337
338                 wrptr = req;
339                 write_u8_inc(&wrptr, CMD_DIGITAL_GET_INPUTS);
340                 ret = send_then_recv(serial, req, wrptr - req, rsp, rx_size);
341                 if (ret != SR_OK)
342                         return ret;
343                 rdptr = rsp;
344
345                 switch (rx_size) {
346                 case 2:
347                         have = read_u16be_inc(&rdptr);
348                         break;
349                 default:
350                         return SR_ERR_NA;
351                 }
352                 have &= (1UL << devc->model->ch_count_di) - 1;
353                 devc->curr_di = have;
354         }
355
356         return SR_OK;
357 }
358
359 /* Query the state of an individual relay channel. */
360 SR_PRIV int devantech_eth008_query_do(const struct sr_dev_inst *sdi,
361         const struct sr_channel_group *cg, gboolean *on)
362 {
363         struct dev_context *devc;
364         struct channel_group_context *cgc;
365         uint32_t have;
366         int ret;
367
368         devc = sdi->priv;
369         if (!devc)
370                 return SR_ERR_ARG;
371
372         /* Unconditionally update the internal cache. */
373         ret = devantech_eth008_cache_state(sdi);
374         if (ret != SR_OK)
375                 return ret;
376
377         /*
378          * Only reject unexpected requeusts after the update. Get the
379          * individual channel's state from the cache of all channels.
380          */
381         if (!cg)
382                 return SR_ERR_ARG;
383         cgc = cg->priv;
384         if (!cgc)
385                 return SR_ERR_BUG;
386         if (cgc->index >= devc->model->ch_count_do)
387                 return SR_ERR_ARG;
388         have = devc->curr_do;
389         have >>= cgc->index;
390         have &= 1 << 0;
391         if (on)
392                 *on = have ? TRUE : FALSE;
393
394         return SR_OK;
395 }
396
397 /*
398  * Manipulate the state of an individual relay channel (when cg is given).
399  * Or set/clear all channels at the same time (when cg is NULL).
400  */
401 SR_PRIV int devantech_eth008_setup_do(const struct sr_dev_inst *sdi,
402         const struct sr_channel_group *cg, gboolean on)
403 {
404         struct sr_serial_dev_inst *serial;
405         struct dev_context *devc;
406         size_t width_do;
407         struct channel_group_context *cgc;
408         size_t number;
409         uint32_t reg;
410         uint8_t req[4], *wrptr, cmd;
411         uint8_t rsp[1], v8;
412         const uint8_t *rdptr;
413         int ret;
414
415         serial = sdi->conn;
416         if (!serial)
417                 return SR_ERR_ARG;
418         devc = sdi->priv;
419         if (!devc)
420                 return SR_ERR_ARG;
421         cgc = cg ? cg->priv : NULL;
422         if (cgc && cgc->index >= devc->model->ch_count_do)
423                 return SR_ERR_ARG;
424
425         width_do = devc->model->width_do;
426         if (1 + width_do > sizeof(req))
427                 return SR_ERR_NA;
428
429         wrptr = req;
430         if (cgc) {
431                 /* Manipulate an individual channel. */
432                 cmd = on ? CMD_DIGITAL_ACTIVE : CMD_DIGITAL_INACTIVE;
433                 number = cgc->number;
434                 write_u8_inc(&wrptr, cmd);
435                 write_u8_inc(&wrptr, number & 0xff);
436                 write_u8_inc(&wrptr, 0); /* Just set/clear, no pulse. */
437         } else {
438                 /* Manipulate all channels at the same time. */
439                 reg = on ? devc->mask_do : 0;
440                 write_u8_inc(&wrptr, CMD_DIGITAL_SET_OUTPUTS);
441                 switch (width_do) {
442                 case 1:
443                         write_u8_inc(&wrptr, reg & 0xff);
444                         break;
445                 case 2:
446                         write_u16le_inc(&wrptr, reg & 0xffff);
447                         break;
448                 case 3:
449                         write_u24le_inc(&wrptr, reg & 0xffffff);
450                         break;
451                 default:
452                         return SR_ERR_NA;
453                 }
454         }
455         ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
456         if (ret != SR_OK)
457                 return ret;
458         rdptr = rsp;
459
460         v8 = read_u8_inc(&rdptr);
461         if (v8 != 0)
462                 return SR_ERR_DATA;
463
464         return SR_OK;
465 }
466
467 SR_PRIV int devantech_eth008_query_di(const struct sr_dev_inst *sdi,
468         const struct sr_channel_group *cg, gboolean *on)
469 {
470         struct dev_context *devc;
471         struct channel_group_context *cgc;
472         uint32_t have;
473         int ret;
474
475         /* Unconditionally update the internal cache. */
476         ret = devantech_eth008_cache_state(sdi);
477         if (ret != SR_OK)
478                 return ret;
479
480         /*
481          * Only reject unexpected requeusts after the update. Get the
482          * individual channel's state from the cache of all channels.
483          */
484         devc = sdi->priv;
485         if (!devc)
486                 return SR_ERR_ARG;
487         if (!cg)
488                 return SR_ERR_ARG;
489         cgc = cg->priv;
490         if (!cgc)
491                 return SR_ERR_BUG;
492         if (cgc->index >= devc->model->ch_count_di)
493                 return SR_ERR_ARG;
494         have = devc->curr_di;
495         have >>= cgc->index;
496         have &= 1 << 0;
497         if (on)
498                 *on = have ? TRUE : FALSE;
499
500         return SR_OK;
501 }
502
503 SR_PRIV int devantech_eth008_query_ai(const struct sr_dev_inst *sdi,
504         const struct sr_channel_group *cg, uint16_t *adc_value)
505 {
506         struct sr_serial_dev_inst *serial;
507         struct dev_context *devc;
508         struct channel_group_context *cgc;
509         uint8_t req[2], *wrptr;
510         uint8_t rsp[2];
511         const uint8_t *rdptr;
512         uint32_t have;
513         int ret;
514
515         serial = sdi->conn;
516         if (!serial)
517                 return SR_ERR_ARG;
518         devc = sdi->priv;
519         if (!devc)
520                 return SR_ERR_ARG;
521         if (!cg)
522                 return SR_ERR_ARG;
523         cgc = cg->priv;
524         if (!cgc)
525                 return SR_ERR_ARG;
526         if (cgc->index >= devc->model->ch_count_ai)
527                 return SR_ERR_ARG;
528
529         wrptr = req;
530         write_u8_inc(&wrptr, CMD_ANALOG_GET_INPUT);
531         write_u8_inc(&wrptr, cgc->number & 0xff);
532         ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
533         if (ret != SR_OK)
534                 return ret;
535         rdptr = rsp;
536
537         /*
538          * TODO The u16 BE format is a guess. Needs verification.
539          * As is the unit-less nature of that value.
540          */
541         have = read_u16be_inc(&rdptr);
542         if (adc_value)
543                 *adc_value = have;
544
545         return SR_OK;
546 }
547
548 SR_PRIV int devantech_eth008_query_supply(const struct sr_dev_inst *sdi,
549         const struct sr_channel_group *cg, uint16_t *millivolts)
550 {
551         struct sr_serial_dev_inst *serial;
552         uint8_t req[1], *wrptr;
553         uint8_t rsp[1];
554         const uint8_t *rdptr;
555         uint16_t have;
556         int ret;
557
558         (void)cg;
559
560         serial = sdi->conn;
561         if (!serial)
562                 return SR_ERR_ARG;
563
564         wrptr = req;
565         write_u8_inc(&wrptr, CMD_GET_SUPPLY_VOLTS);
566         ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
567         if (ret != SR_OK)
568                 return ret;
569         rdptr = rsp;
570
571         /* Gets a byte for voltage in units of 0.1V. Scale up to mV. */
572         have = read_u8_inc(&rdptr);
573         have *= 100;
574         if (millivolts)
575                 *millivolts = have;
576
577         return SR_OK;
578 }