]>
Commit | Line | Data |
---|---|---|
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 | * Also supports other cards when their protocol is similar enough. | |
23 | * USB and Modbus attached cards are not covered by this driver. | |
24 | * | |
25 | * See http://www.robot-electronics.co.uk/files/eth008b.pdf for device | |
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). | |
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 | |
46 | * the peripheral's state can change even while we are controlling it. | |
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 | |
54 | * (the largest payloads has a length of four bytes). | |
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 | * | |
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 | * | |
88 | * TODO | |
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. | |
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 | ||
133 | #include "protocol.h" | |
134 | ||
135 | #define READ_TIMEOUT_MS 20 | |
136 | ||
137 | enum 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, | |
143 | CMD_DIGITAL_GET_INPUTS = 0x25, | |
144 | CMD_DIGITAL_ACTIVE_1MS = 0x26, | |
145 | CMD_DIGITAL_INACTIVE_1MS = 0x27, | |
146 | CMD_ANALOG_GET_INPUT = 0x32, | |
147 | CMD_ANALOG_GET_INPUT_12BIT = 0x33, | |
148 | CMD_ANALOG_GET_ALL_VOLTAGES = 0x34, | |
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 | */ | |
161 | static 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 | */ | |
185 | static 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. */ | |
206 | static 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. */ | |
228 | SR_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) | |
230 | { | |
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). */ | |
264 | SR_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. */ | |
297 | SR_PRIV int devantech_eth008_cache_state(const struct sr_dev_inst *sdi) | |
298 | { | |
299 | struct sr_serial_dev_inst *serial; | |
300 | struct dev_context *devc; | |
301 | size_t rx_size; | |
302 | uint8_t req[1], *wrptr; | |
303 | uint8_t rsp[4]; | |
304 | const uint8_t *rdptr; | |
305 | uint32_t have; | |
306 | int ret; | |
307 | ||
308 | serial = sdi->conn; | |
309 | if (!serial) | |
310 | return SR_ERR_ARG; | |
311 | devc = sdi->priv; | |
312 | if (!devc) | |
313 | return SR_ERR_ARG; | |
314 | ||
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; | |
320 | ||
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; | |
327 | ||
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; | |
343 | } | |
344 | ||
345 | /* | |
346 | * Get the state of digital inputs when the model supports them. | |
347 | * (Sending unsupported requests to unaware firmware versions | |
348 | * yields no response. That's why requests must be conditional.) | |
349 | * | |
350 | * Caching the state of analog inputs is condidered undesirable. | |
351 | * Firmware does conversion at the very moment when the request | |
352 | * is received to get a voltage reading. | |
353 | */ | |
354 | if (devc->model->ch_count_di) { | |
355 | rx_size = devc->model->width_di; | |
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; | |
370 | case 4: | |
371 | have = read_u32be_inc(&rdptr); | |
372 | break; | |
373 | default: | |
374 | return SR_ERR_NA; | |
375 | } | |
376 | have &= (1UL << devc->model->ch_count_di) - 1; | |
377 | devc->curr_di = have; | |
378 | } | |
379 | ||
380 | return SR_OK; | |
381 | } | |
382 | ||
383 | /* Query the state of an individual relay channel. */ | |
384 | SR_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 | /* | |
402 | * Only reject unexpected requests after the update. Get the | |
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 | */ | |
425 | SR_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; | |
434 | uint8_t req[4], *wrptr, cmd; | |
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; | |
469 | case 2: | |
470 | write_u16le_inc(&wrptr, reg & 0xffff); | |
471 | break; | |
472 | case 3: | |
473 | write_u24le_inc(&wrptr, reg & 0xffffff); | |
474 | break; | |
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 | ||
491 | SR_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 | /* | |
505 | * Only reject unexpected requests after the update. Get the | |
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 | ||
527 | SR_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 | /* | |
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. | |
573 | */ | |
574 | have = read_u16be_inc(&rdptr); | |
575 | if (adc_value) | |
576 | *adc_value = have; | |
577 | ||
578 | return SR_OK; | |
579 | } | |
580 | ||
581 | SR_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; | |
609 | ||
610 | return SR_OK; | |
611 | } |