]> sigrok.org Git - libsigrok.git/blame - src/hardware/devantech-eth008/protocol.c
devantech-eth008: support digital and analog input channels
[libsigrok.git] / src / hardware / devantech-eth008 / protocol.c
CommitLineData
c12ca361
GS
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
8712c783
GS
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
0220481e
GS
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.
8712c783
GS
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.
0220481e
GS
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?
8712c783
GS
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
c12ca361
GS
116#include "protocol.h"
117
8712c783
GS
118#define READ_TIMEOUT_MS 20
119
120enum 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,
0220481e
GS
126 CMD_DIGITAL_GET_INPUTS = 0x25,
127 CMD_ANALOG_GET_INPUT = 0x32,
8712c783
GS
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 */
140static 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 */
164static 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. */
185static 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. */
207SR_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)
c12ca361 209{
8712c783
GS
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). */
243SR_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. */
276SR_PRIV int devantech_eth008_cache_state(const struct sr_dev_inst *sdi)
277{
278 struct sr_serial_dev_inst *serial;
c12ca361 279 struct dev_context *devc;
8712c783
GS
280 size_t rx_size;
281 uint8_t req[1], *wrptr;
6af128b6 282 uint8_t rsp[3];
8712c783
GS
283 const uint8_t *rdptr;
284 uint32_t have;
285 int ret;
c12ca361 286
8712c783
GS
287 serial = sdi->conn;
288 if (!serial)
289 return SR_ERR_ARG;
290 devc = sdi->priv;
291 if (!devc)
292 return SR_ERR_ARG;
c12ca361 293
0220481e 294 /* Unconditionally get the state of digital output. */
8712c783
GS
295 rx_size = devc->model->width_do;
296 if (rx_size > sizeof(rsp))
297 return SR_ERR_NA;
c12ca361 298
8712c783
GS
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;
c12ca361 305
8712c783
GS
306 switch (rx_size) {
307 case 1:
308 have = read_u8_inc(&rdptr);
309 break;
6af128b6
GS
310 case 2:
311 have = read_u16le_inc(&rdptr);
312 break;
313 case 3:
314 have = read_u24le_inc(&rdptr);
315 break;
8712c783
GS
316 default:
317 return SR_ERR_NA;
c12ca361 318 }
8712c783
GS
319 have &= devc->mask_do;
320 devc->curr_do = have;
321
0220481e
GS
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
8712c783
GS
354 return SR_OK;
355}
356
357/* Query the state of an individual relay channel. */
358SR_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 */
399SR_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;
6af128b6 408 uint8_t req[4], *wrptr, cmd;
8712c783
GS
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;
6af128b6
GS
443 case 2:
444 write_u16le_inc(&wrptr, reg & 0xffff);
445 break;
446 case 3:
447 write_u24le_inc(&wrptr, reg & 0xffffff);
448 break;
8712c783
GS
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
0220481e
GS
465SR_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
501SR_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
8712c783
GS
546SR_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;
c12ca361 574
8712c783 575 return SR_OK;
c12ca361 576}