]> sigrok.org Git - libsigrok.git/blame - src/hardware/devantech-eth008/protocol.c
devantech-eth008: first driver implementation, relay output only
[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
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
c12ca361
GS
95#include "protocol.h"
96
8712c783
GS
97#define READ_TIMEOUT_MS 20
98
99enum 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 */
117static 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 */
141static 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. */
162static 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. */
184SR_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)
c12ca361 186{
8712c783
GS
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). */
220SR_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. */
253SR_PRIV int devantech_eth008_cache_state(const struct sr_dev_inst *sdi)
254{
255 struct sr_serial_dev_inst *serial;
c12ca361 256 struct dev_context *devc;
8712c783
GS
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;
c12ca361 263
8712c783
GS
264 serial = sdi->conn;
265 if (!serial)
266 return SR_ERR_ARG;
267 devc = sdi->priv;
268 if (!devc)
269 return SR_ERR_ARG;
c12ca361 270
8712c783
GS
271 rx_size = devc->model->width_do;
272 if (rx_size > sizeof(rsp))
273 return SR_ERR_NA;
c12ca361 274
8712c783
GS
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;
c12ca361 281
8712c783
GS
282 switch (rx_size) {
283 case 1:
284 have = read_u8_inc(&rdptr);
285 break;
286 default:
287 return SR_ERR_NA;
c12ca361 288 }
8712c783
GS
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. */
296SR_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 */
337SR_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
397SR_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;
c12ca361 425
8712c783 426 return SR_OK;
c12ca361 427}