]> sigrok.org Git - libsigrok.git/blob - src/hardware/devantech-eth008/protocol.c
devantech-eth008: declare more models, still digital-output only
[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.
27  *
28  * The device provides several means of communication: HTTP requests
29  * (as well as an interactive web form). Raw TCP communication with
30  * binary requests and responses. Text requests and responses over
31  * TCP sockets. Some of these depend on the firmware version. Version
32  * checks before command transmission is essentially non-existent in
33  * this sigrok driver implementation. Binary transmission is preferred
34  * because it is assumed that this existed in all firmware versions.
35  * The firmware interestingly accepts concurrent network connections
36  * (up to five of them, all share the same password). Which means that
37  * the peripheral's state can change even while we control it.
38  *
39  * It's assumed that WLAN models differ from Ethernet devices in terms
40  * of their hardware, but TCP communication should not bother about the
41  * underlying physics, and WLAN cards can re-use model IDs and firmware
42  * implementations. Given sigrok's abstraction of the serial transport
43  * those cards could also be attached by means of COM ports.
44  *
45  * TCP communication seems to rely on network fragmentation and assumes
46  * that software stacks provide all of a request in a single receive
47  * call on the firmware side. Which works for local communication, but
48  * could become an issue when long distances and tunnels are involved.
49  * This sigrok driver also assumes complete reception within a single
50  * receive call. The short length of binary transmission helps here
51  * (the largest payloads has a length of three bytes).
52  *
53  * The lack of length specs as well as termination in the protocol
54  * (both binary as well as text variants over TCP sockets) results in
55  * the inability to synchronize to the firmware when connecting and
56  * after hiccups in an established connection. The fixed length of
57  * requests and responses for binary payloads helps a little bit,
58  * assuming that TCP connect is used to recover. The overhead of
59  * HTTP requests and responses is considered undesirable for this
60  * sigrok driver implementation. [This also means that a transport
61  * which lacks the concept of network frames cannot send passwords.]
62  * The binary transport appears to lack HELLO or NOP requests that
63  * could be used to synchronize. Firmware just would not respond to
64  * unsupported commands. Maybe a repeated sequence of identity reads
65  * combined with a read timeout could help synchronize, but only if
66  * the response is known because the model was identified before.
67  *
68  * The sigrok driver source code was phrased with the addition of more
69  * models in mind. Only few code paths require adjustment when similar
70  * variants of requests or responses are involved in the communication
71  * to relay cards that support between two and twenty channels. Chances
72  * are good, existing firmware is compatible across firmware versions,
73  * and even across hardware revisions (model upgrades). Firmware just
74  * happens to not respond to unknown requests.
75  *
76  * TODO
77  * - Add support for other models. Currently exclusively supports the
78  *   ETH008-B model which was used during implementation of the driver.
79  * - Add support for password protection?
80  *   - See command 0x79 to "login" (beware of the differing return value
81  *     compared to other commands), command 0x7a to check if passwords
82  *     are involved and whether the login needs refreshing, command 0x7b
83  *     for immediate "logout" in contrast to expiration.
84  *   - Alternatively consider switching to the "text protocol" in that
85  *     use case, which can send an optional password in every request
86  *     that controls relays (command 0x3a).
87  *   - How to specify the password in applications and how to pass them
88  *     to this driver is yet another issue that needs consideration.
89  */
90
91 #include "config.h"
92
93 #include <string.h>
94
95 #include "protocol.h"
96
97 #define READ_TIMEOUT_MS 20
98
99 enum cmd_code {
100         CMD_GET_MODULE_INFO = 0x10,
101         CMD_DIGITAL_ACTIVE = 0x20,
102         CMD_DIGITAL_INACTIVE = 0x21,
103         CMD_DIGITAL_SET_OUTPUTS = 0x23,
104         CMD_DIGITAL_GET_OUTPUTS = 0x24,
105         CMD_ASCII_TEXT_COMMAND = 0x3a,
106         CMD_GET_SERIAL_NUMBER = 0x77,
107         CMD_GET_SUPPLY_VOLTS = 0x78,
108         CMD_PASSWORD_ENTRY = 0x79,
109         CMD_GET_UNLOCK_TIME = 0x7a,
110         CMD_IMMEDIATE_LOGOUT = 0x7b,
111 };
112
113 /*
114  * Transmit a request to the relay card. Checks that all bytes get sent,
115  * short writes are considered fatal.
116  */
117 static int send_request(struct sr_serial_dev_inst *ser,
118         const uint8_t *data, size_t dlen)
119 {
120         int ret;
121         size_t written;
122
123         if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
124                 GString *txt = sr_hexdump_new(data, dlen);
125                 sr_spew("TX --> %s.", txt->str);
126                 sr_hexdump_free(txt);
127         }
128         ret = serial_write_blocking(ser, data, dlen, 0);
129         if (ret < 0)
130                 return ret;
131         written = (size_t)ret;
132         if (written != dlen)
133                 return SR_ERR_DATA;
134         return SR_OK;
135 }
136
137 /*
138  * Receive a response from the relay card. Assumes fixed size payload,
139  * considers short reads fatal.
140  */
141 static int recv_response(struct sr_serial_dev_inst *ser,
142         uint8_t *data, size_t dlen)
143 {
144         int ret;
145         size_t got;
146
147         ret = serial_read_blocking(ser, data, dlen, READ_TIMEOUT_MS);
148         if (ret < 0)
149                 return ret;
150         got = (size_t)ret;
151         if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
152                 GString *txt = sr_hexdump_new(data, got);
153                 sr_spew("<-- RX %s.", txt->str);
154                 sr_hexdump_free(txt);
155         }
156         if (got != dlen)
157                 return SR_ERR_DATA;
158         return SR_OK;
159 }
160
161 /* Send a request then receive a response. Convenience routine. */
162 static int send_then_recv(struct sr_serial_dev_inst *serial,
163         const uint8_t *tx_data, size_t tx_length,
164         uint8_t *rx_data, size_t rx_length)
165 {
166         int ret;
167
168         if (tx_data && tx_length) {
169                 ret = send_request(serial, tx_data, tx_length);
170                 if (ret != SR_OK)
171                         return ret;
172         }
173
174         if (rx_data && rx_length) {
175                 ret = recv_response(serial, rx_data, rx_length);
176                 if (ret != SR_OK)
177                         return ret;
178         }
179
180         return SR_OK;
181 }
182
183 /* Identify the relay card, gather version information details. */
184 SR_PRIV int devantech_eth008_get_model(struct sr_serial_dev_inst *serial,
185         uint8_t *model_code, uint8_t *hw_version, uint8_t *fw_version)
186 {
187         uint8_t req[1], *wrptr;
188         uint8_t rsp[3], v8;
189         const uint8_t *rdptr;
190         int ret;
191
192         if (model_code)
193                 *model_code = 0;
194         if (hw_version)
195                 *hw_version = 0;
196         if (fw_version)
197                 *fw_version = 0;
198
199         wrptr = req;
200         write_u8_inc(&wrptr, CMD_GET_MODULE_INFO);
201         ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
202         if (ret != SR_OK)
203                 return ret;
204         rdptr = rsp;
205
206         v8 = read_u8_inc(&rdptr);
207         if (model_code)
208                 *model_code = v8;
209         v8 = read_u8_inc(&rdptr);
210         if (hw_version)
211                 *hw_version = v8;
212         v8 = read_u8_inc(&rdptr);
213         if (fw_version)
214                 *fw_version = v8;
215
216         return SR_OK;
217 }
218
219 /* Get the relay card's serial number (its MAC address). */
220 SR_PRIV int devantech_eth008_get_serno(struct sr_serial_dev_inst *serial,
221         char *text_buffer, size_t text_length)
222 {
223         uint8_t req[1], *wrptr;
224         uint8_t rsp[6], b;
225         const uint8_t *rdptr, *endptr;
226         size_t written;
227         int ret;
228
229         if (text_buffer && !text_length)
230                 return SR_ERR_ARG;
231         if (text_buffer)
232                 memset(text_buffer, 0, text_length);
233
234         wrptr = req;
235         write_u8_inc(&wrptr, CMD_GET_SERIAL_NUMBER);
236         ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
237         if (ret != SR_OK)
238                 return ret;
239         rdptr = rsp;
240
241         endptr = rsp + sizeof(rsp);
242         while (rdptr < endptr && text_buffer && text_length >= 3) {
243                 b = read_u8_inc(&rdptr);
244                 written = snprintf(text_buffer, text_length, "%02x", b);
245                 text_buffer += written;
246                 text_length -= written;
247         }
248
249         return SR_OK;
250 }
251
252 /* Update an internal cache from the relay card's current state. */
253 SR_PRIV int devantech_eth008_cache_state(const struct sr_dev_inst *sdi)
254 {
255         struct sr_serial_dev_inst *serial;
256         struct dev_context *devc;
257         size_t rx_size;
258         uint8_t req[1], *wrptr;
259         uint8_t rsp[3];
260         const uint8_t *rdptr;
261         uint32_t have;
262         int ret;
263
264         serial = sdi->conn;
265         if (!serial)
266                 return SR_ERR_ARG;
267         devc = sdi->priv;
268         if (!devc)
269                 return SR_ERR_ARG;
270
271         rx_size = devc->model->width_do;
272         if (rx_size > sizeof(rsp))
273                 return SR_ERR_NA;
274
275         wrptr = req;
276         write_u8_inc(&wrptr, CMD_DIGITAL_GET_OUTPUTS);
277         ret = send_then_recv(serial, req, wrptr - req, rsp, rx_size);
278         if (ret != SR_OK)
279                 return ret;
280         rdptr = rsp;
281
282         switch (rx_size) {
283         case 1:
284                 have = read_u8_inc(&rdptr);
285                 break;
286         case 2:
287                 have = read_u16le_inc(&rdptr);
288                 break;
289         case 3:
290                 have = read_u24le_inc(&rdptr);
291                 break;
292         default:
293                 return SR_ERR_NA;
294         }
295         have &= devc->mask_do;
296         devc->curr_do = have;
297
298         return SR_OK;
299 }
300
301 /* Query the state of an individual relay channel. */
302 SR_PRIV int devantech_eth008_query_do(const struct sr_dev_inst *sdi,
303         const struct sr_channel_group *cg, gboolean *on)
304 {
305         struct dev_context *devc;
306         struct channel_group_context *cgc;
307         uint32_t have;
308         int ret;
309
310         devc = sdi->priv;
311         if (!devc)
312                 return SR_ERR_ARG;
313
314         /* Unconditionally update the internal cache. */
315         ret = devantech_eth008_cache_state(sdi);
316         if (ret != SR_OK)
317                 return ret;
318
319         /*
320          * Only reject unexpected requeusts after the update. Get the
321          * individual channel's state from the cache of all channels.
322          */
323         if (!cg)
324                 return SR_ERR_ARG;
325         cgc = cg->priv;
326         if (!cgc)
327                 return SR_ERR_BUG;
328         if (cgc->index >= devc->model->ch_count_do)
329                 return SR_ERR_ARG;
330         have = devc->curr_do;
331         have >>= cgc->index;
332         have &= 1 << 0;
333         if (on)
334                 *on = have ? TRUE : FALSE;
335
336         return SR_OK;
337 }
338
339 /*
340  * Manipulate the state of an individual relay channel (when cg is given).
341  * Or set/clear all channels at the same time (when cg is NULL).
342  */
343 SR_PRIV int devantech_eth008_setup_do(const struct sr_dev_inst *sdi,
344         const struct sr_channel_group *cg, gboolean on)
345 {
346         struct sr_serial_dev_inst *serial;
347         struct dev_context *devc;
348         size_t width_do;
349         struct channel_group_context *cgc;
350         size_t number;
351         uint32_t reg;
352         uint8_t req[4], *wrptr, cmd;
353         uint8_t rsp[1], v8;
354         const uint8_t *rdptr;
355         int ret;
356
357         serial = sdi->conn;
358         if (!serial)
359                 return SR_ERR_ARG;
360         devc = sdi->priv;
361         if (!devc)
362                 return SR_ERR_ARG;
363         cgc = cg ? cg->priv : NULL;
364         if (cgc && cgc->index >= devc->model->ch_count_do)
365                 return SR_ERR_ARG;
366
367         width_do = devc->model->width_do;
368         if (1 + width_do > sizeof(req))
369                 return SR_ERR_NA;
370
371         wrptr = req;
372         if (cgc) {
373                 /* Manipulate an individual channel. */
374                 cmd = on ? CMD_DIGITAL_ACTIVE : CMD_DIGITAL_INACTIVE;
375                 number = cgc->number;
376                 write_u8_inc(&wrptr, cmd);
377                 write_u8_inc(&wrptr, number & 0xff);
378                 write_u8_inc(&wrptr, 0); /* Just set/clear, no pulse. */
379         } else {
380                 /* Manipulate all channels at the same time. */
381                 reg = on ? devc->mask_do : 0;
382                 write_u8_inc(&wrptr, CMD_DIGITAL_SET_OUTPUTS);
383                 switch (width_do) {
384                 case 1:
385                         write_u8_inc(&wrptr, reg & 0xff);
386                         break;
387                 case 2:
388                         write_u16le_inc(&wrptr, reg & 0xffff);
389                         break;
390                 case 3:
391                         write_u24le_inc(&wrptr, reg & 0xffffff);
392                         break;
393                 default:
394                         return SR_ERR_NA;
395                 }
396         }
397         ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
398         if (ret != SR_OK)
399                 return ret;
400         rdptr = rsp;
401
402         v8 = read_u8_inc(&rdptr);
403         if (v8 != 0)
404                 return SR_ERR_DATA;
405
406         return SR_OK;
407 }
408
409 SR_PRIV int devantech_eth008_query_supply(const struct sr_dev_inst *sdi,
410         const struct sr_channel_group *cg, uint16_t *millivolts)
411 {
412         struct sr_serial_dev_inst *serial;
413         uint8_t req[1], *wrptr;
414         uint8_t rsp[1];
415         const uint8_t *rdptr;
416         uint16_t have;
417         int ret;
418
419         (void)cg;
420
421         serial = sdi->conn;
422         if (!serial)
423                 return SR_ERR_ARG;
424
425         wrptr = req;
426         write_u8_inc(&wrptr, CMD_GET_SUPPLY_VOLTS);
427         ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
428         if (ret != SR_OK)
429                 return ret;
430         rdptr = rsp;
431
432         /* Gets a byte for voltage in units of 0.1V. Scale up to mV. */
433         have = read_u8_inc(&rdptr);
434         have *= 100;
435         if (millivolts)
436                 *millivolts = have;
437
438         return SR_OK;
439 }