]> sigrok.org Git - libsigrok.git/blame - src/hardware/devantech-eth008/protocol.c
output/csv: use intermediate time_t var, silence compiler warning
[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.
c3fc22f2
GS
22 * Also supports other cards when their protocol is similar enough.
23 * USB and Modbus attached cards are not covered by this driver.
8712c783
GS
24 *
25 * See http://www.robot-electronics.co.uk/files/eth008b.pdf for device
c3fc22f2
GS
26 * capabilities and a protocol discussion. See other devices' documents
27 * for additional features (digital input, analog input, TCP requests
28 * which ETH008 does not implement).
29 * See https://github.com/devantech/devantech_eth_python for MIT licensed
30 * Python source code which is maintained by the vendor.
31 * This sigrok driver implementation was created based on information in
32 * version 0.1.2 of the Python code (corresponds to commit 0c0080b88e29),
33 * and PDF files that are referenced in the shop's product pages (which
34 * also happen to provide ZIP archives with examples that are written
35 * using other programming languages).
8712c783
GS
36 *
37 * The device provides several means of communication: HTTP requests
38 * (as well as an interactive web form). Raw TCP communication with
39 * binary requests and responses. Text requests and responses over
40 * TCP sockets. Some of these depend on the firmware version. Version
41 * checks before command transmission is essentially non-existent in
42 * this sigrok driver implementation. Binary transmission is preferred
43 * because it is assumed that this existed in all firmware versions.
44 * The firmware interestingly accepts concurrent network connections
45 * (up to five of them, all share the same password). Which means that
c3fc22f2 46 * the peripheral's state can change even while we are controlling it.
8712c783
GS
47 *
48 * TCP communication seems to rely on network fragmentation and assumes
49 * that software stacks provide all of a request in a single receive
50 * call on the firmware side. Which works for local communication, but
51 * could become an issue when long distances and tunnels are involved.
52 * This sigrok driver also assumes complete reception within a single
53 * receive call. The short length of binary transmission helps here
c3fc22f2 54 * (the largest payloads has a length of four bytes).
8712c783
GS
55 *
56 * The lack of length specs as well as termination in the protocol
57 * (both binary as well as text variants over TCP sockets) results in
58 * the inability to synchronize to the firmware when connecting and
59 * after hiccups in an established connection. The fixed length of
60 * requests and responses for binary payloads helps a little bit,
61 * assuming that TCP connect is used to recover. The overhead of
62 * HTTP requests and responses is considered undesirable for this
63 * sigrok driver implementation. [This also means that a transport
64 * which lacks the concept of network frames cannot send passwords.]
65 * The binary transport appears to lack HELLO or NOP requests that
66 * could be used to synchronize. Firmware just would not respond to
67 * unsupported commands. Maybe a repeated sequence of identity reads
68 * combined with a read timeout could help synchronize, but only if
69 * the response is known because the model was identified before.
70 *
71 * The sigrok driver source code was phrased with the addition of more
72 * models in mind. Only few code paths require adjustment when similar
73 * variants of requests or responses are involved in the communication
74 * to relay cards that support between two and twenty channels. Chances
75 * are good, existing firmware is compatible across firmware versions,
76 * and even across hardware revisions (model upgrades). Firmware just
77 * happens to not respond to unknown requests.
78 *
c3fc22f2
GS
79 * Support for models with differing features also was kept somehwat
80 * simple and straight forward. The mapping of digital outputs to relay
81 * numbers from the user's perspective is incomplete for those cards
82 * where users decide whether relays are attached to digital outputs.
83 * When an individual physical channel can be operated in different
84 * modes, or when its value gets presented in different formats, then
85 * these values are not synchronized. This applies for digital inputs
86 * which are the result of applying a threshold to an analog value.
87 *
8712c783 88 * TODO
c3fc22f2
GS
89 * - Add support for other models.
90 * - The Ethernet (and Wifi) devices should work as they are with
91 * the current implementation.
92 * https://www.robot-electronics.co.uk/files/eth484b.pdf.
93 * - USB could get added here with reasonable effort. Serial over
94 * CDC is transparently supported (lack of framing prevents the
95 * use of variable length requests or responses, but should not
96 * apply to these models anyway). The protocol radically differs
97 * from Ethernet variants:
98 * https://www.robot-electronics.co.uk/files/usb-rly16b.pdf
99 * - 0x38 get serial number, yields 8 bytes
100 * - 0x5a get software version, yields module ID 9, 1 byte version
101 * - 0x5b get relay states, yields 1 byte current state
102 * - 0x5c set relay state, takes 1 byte for all 8 relays
103 * - 0x5d get supply voltage, yields 1 byte in 0.1V steps
104 * - 0x5e set individual relay, takes 3 more bytes: relay number,
105 * hi/lo pulse time in 10ms steps
106 * - for interactive use? 'd' all relays on, 'e'..'l' relay 1..8 on,
107 * 'n' all relays off, 'o'..'v' relay 1..8 off
108 * - Modbus may or may not be a good match for this driver, or may
109 * better be kept in yet another driver? Requests and responses
110 * again differ from Ethernet and USB models, refer to traditional
111 * "coils" and have their individual and grouped access.
112 * https://www.robot-electronics.co.uk/files/mbh88.pdf
113 * - Reconsider the relation of relay channels, and digital outputs
114 * and their analog sampling and digital input interpretation. The
115 * current implementation is naive, assumes the simple DO/DI/AI
116 * groups and ignores their interaction within the firmware.
8712c783
GS
117 * - Add support for password protection?
118 * - See command 0x79 to "login" (beware of the differing return value
119 * compared to other commands), command 0x7a to check if passwords
120 * are involved and whether the login needs refreshing, command 0x7b
121 * for immediate "logout" in contrast to expiration.
122 * - Alternatively consider switching to the "text protocol" in that
123 * use case, which can send an optional password in every request
124 * that controls relays (command 0x3a).
125 * - How to specify the password in applications and how to pass them
126 * to this driver is yet another issue that needs consideration.
127 */
128
129#include "config.h"
130
131#include <string.h>
132
c12ca361
GS
133#include "protocol.h"
134
8712c783
GS
135#define READ_TIMEOUT_MS 20
136
137enum cmd_code {
138 CMD_GET_MODULE_INFO = 0x10,
139 CMD_DIGITAL_ACTIVE = 0x20,
140 CMD_DIGITAL_INACTIVE = 0x21,
141 CMD_DIGITAL_SET_OUTPUTS = 0x23,
142 CMD_DIGITAL_GET_OUTPUTS = 0x24,
0220481e 143 CMD_DIGITAL_GET_INPUTS = 0x25,
c3fc22f2
GS
144 CMD_DIGITAL_ACTIVE_1MS = 0x26,
145 CMD_DIGITAL_INACTIVE_1MS = 0x27,
0220481e 146 CMD_ANALOG_GET_INPUT = 0x32,
c3fc22f2
GS
147 CMD_ANALOG_GET_INPUT_12BIT = 0x33,
148 CMD_ANALOG_GET_ALL_VOLTAGES = 0x34,
8712c783
GS
149 CMD_ASCII_TEXT_COMMAND = 0x3a,
150 CMD_GET_SERIAL_NUMBER = 0x77,
151 CMD_GET_SUPPLY_VOLTS = 0x78,
152 CMD_PASSWORD_ENTRY = 0x79,
153 CMD_GET_UNLOCK_TIME = 0x7a,
154 CMD_IMMEDIATE_LOGOUT = 0x7b,
155};
156
157/*
158 * Transmit a request to the relay card. Checks that all bytes get sent,
159 * short writes are considered fatal.
160 */
161static int send_request(struct sr_serial_dev_inst *ser,
162 const uint8_t *data, size_t dlen)
163{
164 int ret;
165 size_t written;
166
167 if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
168 GString *txt = sr_hexdump_new(data, dlen);
169 sr_spew("TX --> %s.", txt->str);
170 sr_hexdump_free(txt);
171 }
172 ret = serial_write_blocking(ser, data, dlen, 0);
173 if (ret < 0)
174 return ret;
175 written = (size_t)ret;
176 if (written != dlen)
177 return SR_ERR_DATA;
178 return SR_OK;
179}
180
181/*
182 * Receive a response from the relay card. Assumes fixed size payload,
183 * considers short reads fatal.
184 */
185static int recv_response(struct sr_serial_dev_inst *ser,
186 uint8_t *data, size_t dlen)
187{
188 int ret;
189 size_t got;
190
191 ret = serial_read_blocking(ser, data, dlen, READ_TIMEOUT_MS);
192 if (ret < 0)
193 return ret;
194 got = (size_t)ret;
195 if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
196 GString *txt = sr_hexdump_new(data, got);
197 sr_spew("<-- RX %s.", txt->str);
198 sr_hexdump_free(txt);
199 }
200 if (got != dlen)
201 return SR_ERR_DATA;
202 return SR_OK;
203}
204
205/* Send a request then receive a response. Convenience routine. */
206static int send_then_recv(struct sr_serial_dev_inst *serial,
207 const uint8_t *tx_data, size_t tx_length,
208 uint8_t *rx_data, size_t rx_length)
209{
210 int ret;
211
212 if (tx_data && tx_length) {
213 ret = send_request(serial, tx_data, tx_length);
214 if (ret != SR_OK)
215 return ret;
216 }
217
218 if (rx_data && rx_length) {
219 ret = recv_response(serial, rx_data, rx_length);
220 if (ret != SR_OK)
221 return ret;
222 }
223
224 return SR_OK;
225}
226
227/* Identify the relay card, gather version information details. */
228SR_PRIV int devantech_eth008_get_model(struct sr_serial_dev_inst *serial,
229 uint8_t *model_code, uint8_t *hw_version, uint8_t *fw_version)
c12ca361 230{
8712c783
GS
231 uint8_t req[1], *wrptr;
232 uint8_t rsp[3], v8;
233 const uint8_t *rdptr;
234 int ret;
235
236 if (model_code)
237 *model_code = 0;
238 if (hw_version)
239 *hw_version = 0;
240 if (fw_version)
241 *fw_version = 0;
242
243 wrptr = req;
244 write_u8_inc(&wrptr, CMD_GET_MODULE_INFO);
245 ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
246 if (ret != SR_OK)
247 return ret;
248 rdptr = rsp;
249
250 v8 = read_u8_inc(&rdptr);
251 if (model_code)
252 *model_code = v8;
253 v8 = read_u8_inc(&rdptr);
254 if (hw_version)
255 *hw_version = v8;
256 v8 = read_u8_inc(&rdptr);
257 if (fw_version)
258 *fw_version = v8;
259
260 return SR_OK;
261}
262
263/* Get the relay card's serial number (its MAC address). */
264SR_PRIV int devantech_eth008_get_serno(struct sr_serial_dev_inst *serial,
265 char *text_buffer, size_t text_length)
266{
267 uint8_t req[1], *wrptr;
268 uint8_t rsp[6], b;
269 const uint8_t *rdptr, *endptr;
270 size_t written;
271 int ret;
272
273 if (text_buffer && !text_length)
274 return SR_ERR_ARG;
275 if (text_buffer)
276 memset(text_buffer, 0, text_length);
277
278 wrptr = req;
279 write_u8_inc(&wrptr, CMD_GET_SERIAL_NUMBER);
280 ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
281 if (ret != SR_OK)
282 return ret;
283 rdptr = rsp;
284
285 endptr = rsp + sizeof(rsp);
286 while (rdptr < endptr && text_buffer && text_length >= 3) {
287 b = read_u8_inc(&rdptr);
288 written = snprintf(text_buffer, text_length, "%02x", b);
289 text_buffer += written;
290 text_length -= written;
291 }
292
293 return SR_OK;
294}
295
296/* Update an internal cache from the relay card's current state. */
297SR_PRIV int devantech_eth008_cache_state(const struct sr_dev_inst *sdi)
298{
299 struct sr_serial_dev_inst *serial;
c12ca361 300 struct dev_context *devc;
8712c783
GS
301 size_t rx_size;
302 uint8_t req[1], *wrptr;
c3fc22f2 303 uint8_t rsp[4];
8712c783
GS
304 const uint8_t *rdptr;
305 uint32_t have;
306 int ret;
c12ca361 307
8712c783
GS
308 serial = sdi->conn;
309 if (!serial)
310 return SR_ERR_ARG;
311 devc = sdi->priv;
312 if (!devc)
313 return SR_ERR_ARG;
c12ca361 314
a04cd800
GS
315 /* Get the state of digital outputs when the model supports them. */
316 if (devc->model->ch_count_do) {
317 rx_size = devc->model->width_do;
318 if (rx_size > sizeof(rsp))
319 return SR_ERR_NA;
c12ca361 320
a04cd800
GS
321 wrptr = req;
322 write_u8_inc(&wrptr, CMD_DIGITAL_GET_OUTPUTS);
323 ret = send_then_recv(serial, req, wrptr - req, rsp, rx_size);
324 if (ret != SR_OK)
325 return ret;
326 rdptr = rsp;
c12ca361 327
a04cd800
GS
328 switch (rx_size) {
329 case 1:
330 have = read_u8_inc(&rdptr);
331 break;
332 case 2:
333 have = read_u16le_inc(&rdptr);
334 break;
335 case 3:
336 have = read_u24le_inc(&rdptr);
337 break;
338 default:
339 return SR_ERR_NA;
340 }
341 have &= devc->mask_do;
342 devc->curr_do = have;
c12ca361 343 }
8712c783 344
0220481e
GS
345 /*
346 * Get the state of digital inputs when the model supports them.
c3fc22f2
GS
347 * (Sending unsupported requests to unaware firmware versions
348 * yields no response. That's why requests must be conditional.)
0220481e
GS
349 *
350 * Caching the state of analog inputs is condidered undesirable.
c3fc22f2
GS
351 * Firmware does conversion at the very moment when the request
352 * is received to get a voltage reading.
0220481e
GS
353 */
354 if (devc->model->ch_count_di) {
c3fc22f2 355 rx_size = devc->model->width_di;
0220481e
GS
356 if (rx_size > sizeof(rsp))
357 return SR_ERR_NA;
358
359 wrptr = req;
360 write_u8_inc(&wrptr, CMD_DIGITAL_GET_INPUTS);
361 ret = send_then_recv(serial, req, wrptr - req, rsp, rx_size);
362 if (ret != SR_OK)
363 return ret;
364 rdptr = rsp;
365
366 switch (rx_size) {
367 case 2:
368 have = read_u16be_inc(&rdptr);
369 break;
c3fc22f2
GS
370 case 4:
371 have = read_u32be_inc(&rdptr);
372 break;
0220481e
GS
373 default:
374 return SR_ERR_NA;
375 }
376 have &= (1UL << devc->model->ch_count_di) - 1;
377 devc->curr_di = have;
378 }
379
8712c783
GS
380 return SR_OK;
381}
382
383/* Query the state of an individual relay channel. */
384SR_PRIV int devantech_eth008_query_do(const struct sr_dev_inst *sdi,
385 const struct sr_channel_group *cg, gboolean *on)
386{
387 struct dev_context *devc;
388 struct channel_group_context *cgc;
389 uint32_t have;
390 int ret;
391
392 devc = sdi->priv;
393 if (!devc)
394 return SR_ERR_ARG;
395
396 /* Unconditionally update the internal cache. */
397 ret = devantech_eth008_cache_state(sdi);
398 if (ret != SR_OK)
399 return ret;
400
401 /*
c3fc22f2 402 * Only reject unexpected requests after the update. Get the
8712c783
GS
403 * individual channel's state from the cache of all channels.
404 */
405 if (!cg)
406 return SR_ERR_ARG;
407 cgc = cg->priv;
408 if (!cgc)
409 return SR_ERR_BUG;
410 if (cgc->index >= devc->model->ch_count_do)
411 return SR_ERR_ARG;
412 have = devc->curr_do;
413 have >>= cgc->index;
414 have &= 1 << 0;
415 if (on)
416 *on = have ? TRUE : FALSE;
417
418 return SR_OK;
419}
420
421/*
422 * Manipulate the state of an individual relay channel (when cg is given).
423 * Or set/clear all channels at the same time (when cg is NULL).
424 */
425SR_PRIV int devantech_eth008_setup_do(const struct sr_dev_inst *sdi,
426 const struct sr_channel_group *cg, gboolean on)
427{
428 struct sr_serial_dev_inst *serial;
429 struct dev_context *devc;
430 size_t width_do;
431 struct channel_group_context *cgc;
432 size_t number;
433 uint32_t reg;
6af128b6 434 uint8_t req[4], *wrptr, cmd;
8712c783
GS
435 uint8_t rsp[1], v8;
436 const uint8_t *rdptr;
437 int ret;
438
439 serial = sdi->conn;
440 if (!serial)
441 return SR_ERR_ARG;
442 devc = sdi->priv;
443 if (!devc)
444 return SR_ERR_ARG;
445 cgc = cg ? cg->priv : NULL;
446 if (cgc && cgc->index >= devc->model->ch_count_do)
447 return SR_ERR_ARG;
448
449 width_do = devc->model->width_do;
450 if (1 + width_do > sizeof(req))
451 return SR_ERR_NA;
452
453 wrptr = req;
454 if (cgc) {
455 /* Manipulate an individual channel. */
456 cmd = on ? CMD_DIGITAL_ACTIVE : CMD_DIGITAL_INACTIVE;
457 number = cgc->number;
458 write_u8_inc(&wrptr, cmd);
459 write_u8_inc(&wrptr, number & 0xff);
460 write_u8_inc(&wrptr, 0); /* Just set/clear, no pulse. */
461 } else {
462 /* Manipulate all channels at the same time. */
463 reg = on ? devc->mask_do : 0;
464 write_u8_inc(&wrptr, CMD_DIGITAL_SET_OUTPUTS);
465 switch (width_do) {
466 case 1:
467 write_u8_inc(&wrptr, reg & 0xff);
468 break;
6af128b6
GS
469 case 2:
470 write_u16le_inc(&wrptr, reg & 0xffff);
471 break;
472 case 3:
473 write_u24le_inc(&wrptr, reg & 0xffffff);
474 break;
8712c783
GS
475 default:
476 return SR_ERR_NA;
477 }
478 }
479 ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
480 if (ret != SR_OK)
481 return ret;
482 rdptr = rsp;
483
484 v8 = read_u8_inc(&rdptr);
485 if (v8 != 0)
486 return SR_ERR_DATA;
487
488 return SR_OK;
489}
490
0220481e
GS
491SR_PRIV int devantech_eth008_query_di(const struct sr_dev_inst *sdi,
492 const struct sr_channel_group *cg, gboolean *on)
493{
494 struct dev_context *devc;
495 struct channel_group_context *cgc;
496 uint32_t have;
497 int ret;
498
499 /* Unconditionally update the internal cache. */
500 ret = devantech_eth008_cache_state(sdi);
501 if (ret != SR_OK)
502 return ret;
503
504 /*
c3fc22f2 505 * Only reject unexpected requests after the update. Get the
0220481e
GS
506 * individual channel's state from the cache of all channels.
507 */
508 devc = sdi->priv;
509 if (!devc)
510 return SR_ERR_ARG;
511 if (!cg)
512 return SR_ERR_ARG;
513 cgc = cg->priv;
514 if (!cgc)
515 return SR_ERR_BUG;
516 if (cgc->index >= devc->model->ch_count_di)
517 return SR_ERR_ARG;
518 have = devc->curr_di;
519 have >>= cgc->index;
520 have &= 1 << 0;
521 if (on)
522 *on = have ? TRUE : FALSE;
523
524 return SR_OK;
525}
526
527SR_PRIV int devantech_eth008_query_ai(const struct sr_dev_inst *sdi,
528 const struct sr_channel_group *cg, uint16_t *adc_value)
529{
530 struct sr_serial_dev_inst *serial;
531 struct dev_context *devc;
532 struct channel_group_context *cgc;
533 uint8_t req[2], *wrptr;
534 uint8_t rsp[2];
535 const uint8_t *rdptr;
536 uint32_t have;
537 int ret;
538
539 serial = sdi->conn;
540 if (!serial)
541 return SR_ERR_ARG;
542 devc = sdi->priv;
543 if (!devc)
544 return SR_ERR_ARG;
545 if (!cg)
546 return SR_ERR_ARG;
547 cgc = cg->priv;
548 if (!cgc)
549 return SR_ERR_ARG;
550 if (cgc->index >= devc->model->ch_count_ai)
551 return SR_ERR_ARG;
552
553 wrptr = req;
554 write_u8_inc(&wrptr, CMD_ANALOG_GET_INPUT);
555 write_u8_inc(&wrptr, cgc->number & 0xff);
556 ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
557 if (ret != SR_OK)
558 return ret;
559 rdptr = rsp;
560
561 /*
c3fc22f2
GS
562 * The interpretation of analog readings differs across models.
563 * All firmware versions provide an ADC result in BE format in
564 * a 16bit response. Some models provide 10 significant digits,
565 * others provide 12 bits. Full scale can either be 3V3 or 5V0.
566 * Some devices are 5V tolerant but won't read more than 3V3
567 * values (and clip above that full scale value). Some firmware
568 * versions support request 0x33 in addition to 0x32.
569 *
570 * This is why this implementation provides the result to the
571 * caller as a unit-less value. It is also what the firmware's
572 * web interface does.
0220481e
GS
573 */
574 have = read_u16be_inc(&rdptr);
575 if (adc_value)
576 *adc_value = have;
577
578 return SR_OK;
579}
580
8712c783
GS
581SR_PRIV int devantech_eth008_query_supply(const struct sr_dev_inst *sdi,
582 const struct sr_channel_group *cg, uint16_t *millivolts)
583{
584 struct sr_serial_dev_inst *serial;
585 uint8_t req[1], *wrptr;
586 uint8_t rsp[1];
587 const uint8_t *rdptr;
588 uint16_t have;
589 int ret;
590
591 (void)cg;
592
593 serial = sdi->conn;
594 if (!serial)
595 return SR_ERR_ARG;
596
597 wrptr = req;
598 write_u8_inc(&wrptr, CMD_GET_SUPPLY_VOLTS);
599 ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
600 if (ret != SR_OK)
601 return ret;
602 rdptr = rsp;
603
604 /* Gets a byte for voltage in units of 0.1V. Scale up to mV. */
605 have = read_u8_inc(&rdptr);
606 have *= 100;
607 if (millivolts)
608 *millivolts = have;
c12ca361 609
8712c783 610 return SR_OK;
c12ca361 611}