]> sigrok.org Git - libsigrok.git/blob - src/hardware/devantech-eth008/protocol.c
devantech-eth008: first driver implementation, relay 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[1];
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         default:
287                 return SR_ERR_NA;
288         }
289         have &= devc->mask_do;
290         devc->curr_do = have;
291
292         return SR_OK;
293 }
294
295 /* Query the state of an individual relay channel. */
296 SR_PRIV int devantech_eth008_query_do(const struct sr_dev_inst *sdi,
297         const struct sr_channel_group *cg, gboolean *on)
298 {
299         struct dev_context *devc;
300         struct channel_group_context *cgc;
301         uint32_t have;
302         int ret;
303
304         devc = sdi->priv;
305         if (!devc)
306                 return SR_ERR_ARG;
307
308         /* Unconditionally update the internal cache. */
309         ret = devantech_eth008_cache_state(sdi);
310         if (ret != SR_OK)
311                 return ret;
312
313         /*
314          * Only reject unexpected requeusts after the update. Get the
315          * individual channel's state from the cache of all channels.
316          */
317         if (!cg)
318                 return SR_ERR_ARG;
319         cgc = cg->priv;
320         if (!cgc)
321                 return SR_ERR_BUG;
322         if (cgc->index >= devc->model->ch_count_do)
323                 return SR_ERR_ARG;
324         have = devc->curr_do;
325         have >>= cgc->index;
326         have &= 1 << 0;
327         if (on)
328                 *on = have ? TRUE : FALSE;
329
330         return SR_OK;
331 }
332
333 /*
334  * Manipulate the state of an individual relay channel (when cg is given).
335  * Or set/clear all channels at the same time (when cg is NULL).
336  */
337 SR_PRIV int devantech_eth008_setup_do(const struct sr_dev_inst *sdi,
338         const struct sr_channel_group *cg, gboolean on)
339 {
340         struct sr_serial_dev_inst *serial;
341         struct dev_context *devc;
342         size_t width_do;
343         struct channel_group_context *cgc;
344         size_t number;
345         uint32_t reg;
346         uint8_t req[3], *wrptr, cmd;
347         uint8_t rsp[1], v8;
348         const uint8_t *rdptr;
349         int ret;
350
351         serial = sdi->conn;
352         if (!serial)
353                 return SR_ERR_ARG;
354         devc = sdi->priv;
355         if (!devc)
356                 return SR_ERR_ARG;
357         cgc = cg ? cg->priv : NULL;
358         if (cgc && cgc->index >= devc->model->ch_count_do)
359                 return SR_ERR_ARG;
360
361         width_do = devc->model->width_do;
362         if (1 + width_do > sizeof(req))
363                 return SR_ERR_NA;
364
365         wrptr = req;
366         if (cgc) {
367                 /* Manipulate an individual channel. */
368                 cmd = on ? CMD_DIGITAL_ACTIVE : CMD_DIGITAL_INACTIVE;
369                 number = cgc->number;
370                 write_u8_inc(&wrptr, cmd);
371                 write_u8_inc(&wrptr, number & 0xff);
372                 write_u8_inc(&wrptr, 0); /* Just set/clear, no pulse. */
373         } else {
374                 /* Manipulate all channels at the same time. */
375                 reg = on ? devc->mask_do : 0;
376                 write_u8_inc(&wrptr, CMD_DIGITAL_SET_OUTPUTS);
377                 switch (width_do) {
378                 case 1:
379                         write_u8_inc(&wrptr, reg & 0xff);
380                         break;
381                 default:
382                         return SR_ERR_NA;
383                 }
384         }
385         ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
386         if (ret != SR_OK)
387                 return ret;
388         rdptr = rsp;
389
390         v8 = read_u8_inc(&rdptr);
391         if (v8 != 0)
392                 return SR_ERR_DATA;
393
394         return SR_OK;
395 }
396
397 SR_PRIV int devantech_eth008_query_supply(const struct sr_dev_inst *sdi,
398         const struct sr_channel_group *cg, uint16_t *millivolts)
399 {
400         struct sr_serial_dev_inst *serial;
401         uint8_t req[1], *wrptr;
402         uint8_t rsp[1];
403         const uint8_t *rdptr;
404         uint16_t have;
405         int ret;
406
407         (void)cg;
408
409         serial = sdi->conn;
410         if (!serial)
411                 return SR_ERR_ARG;
412
413         wrptr = req;
414         write_u8_inc(&wrptr, CMD_GET_SUPPLY_VOLTS);
415         ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp));
416         if (ret != SR_OK)
417                 return ret;
418         rdptr = rsp;
419
420         /* Gets a byte for voltage in units of 0.1V. Scale up to mV. */
421         have = read_u8_inc(&rdptr);
422         have *= 100;
423         if (millivolts)
424                 *millivolts = have;
425
426         return SR_OK;
427 }