X-Git-Url: https://sigrok.org/gitweb/?a=blobdiff_plain;f=src%2Fhardware%2Fdevantech-eth008%2Fprotocol.c;h=afbbea9720ae2a61d2d8969c8f131651f18e4f1d;hb=204dd31fa1074a78fbe3bf04208776a4a3615a1c;hp=8c436ea3bfd952ec72e44cb9e671853e18d210af;hpb=6af128b64262914c45475376c0590354a003b6ed;p=libsigrok.git diff --git a/src/hardware/devantech-eth008/protocol.c b/src/hardware/devantech-eth008/protocol.c index 8c436ea3..afbbea97 100644 --- a/src/hardware/devantech-eth008/protocol.c +++ b/src/hardware/devantech-eth008/protocol.c @@ -19,11 +19,20 @@ /* * Communicate to the Devantech ETH008 relay card via TCP and Ethernet. + * Also supports other cards when their protocol is similar enough. + * USB and Modbus attached cards are not covered by this driver. * * See http://www.robot-electronics.co.uk/files/eth008b.pdf for device - * capabilities and a protocol discussion. - * See https://github.com/devantech/devantech_eth_python for Python - * source code which is maintained by the vendor. + * capabilities and a protocol discussion. See other devices' documents + * for additional features (digital input, analog input, TCP requests + * which ETH008 does not implement). + * See https://github.com/devantech/devantech_eth_python for MIT licensed + * Python source code which is maintained by the vendor. + * This sigrok driver implementation was created based on information in + * version 0.1.2 of the Python code (corresponds to commit 0c0080b88e29), + * and PDF files that are referenced in the shop's product pages (which + * also happen to provide ZIP archives with examples that are written + * using other programming languages). * * The device provides several means of communication: HTTP requests * (as well as an interactive web form). Raw TCP communication with @@ -34,13 +43,7 @@ * because it is assumed that this existed in all firmware versions. * The firmware interestingly accepts concurrent network connections * (up to five of them, all share the same password). Which means that - * the peripheral's state can change even while we control it. - * - * It's assumed that WLAN models differ from Ethernet devices in terms - * of their hardware, but TCP communication should not bother about the - * underlying physics, and WLAN cards can re-use model IDs and firmware - * implementations. Given sigrok's abstraction of the serial transport - * those cards could also be attached by means of COM ports. + * the peripheral's state can change even while we are controlling it. * * TCP communication seems to rely on network fragmentation and assumes * that software stacks provide all of a request in a single receive @@ -48,7 +51,7 @@ * could become an issue when long distances and tunnels are involved. * This sigrok driver also assumes complete reception within a single * receive call. The short length of binary transmission helps here - * (the largest payloads has a length of three bytes). + * (the largest payloads has a length of four bytes). * * The lack of length specs as well as termination in the protocol * (both binary as well as text variants over TCP sockets) results in @@ -73,9 +76,44 @@ * and even across hardware revisions (model upgrades). Firmware just * happens to not respond to unknown requests. * + * Support for models with differing features also was kept somehwat + * simple and straight forward. The mapping of digital outputs to relay + * numbers from the user's perspective is incomplete for those cards + * where users decide whether relays are attached to digital outputs. + * When an individual physical channel can be operated in different + * modes, or when its value gets presented in different formats, then + * these values are not synchronized. This applies for digital inputs + * which are the result of applying a threshold to an analog value. + * * TODO - * - Add support for other models. Currently exclusively supports the - * ETH008-B model which was used during implementation of the driver. + * - Add support for other models. + * - The Ethernet (and Wifi) devices should work as they are with + * the current implementation. + * https://www.robot-electronics.co.uk/files/eth484b.pdf. + * - USB could get added here with reasonable effort. Serial over + * CDC is transparently supported (lack of framing prevents the + * use of variable length requests or responses, but should not + * apply to these models anyway). The protocol radically differs + * from Ethernet variants: + * https://www.robot-electronics.co.uk/files/usb-rly16b.pdf + * - 0x38 get serial number, yields 8 bytes + * - 0x5a get software version, yields module ID 9, 1 byte version + * - 0x5b get relay states, yields 1 byte current state + * - 0x5c set relay state, takes 1 byte for all 8 relays + * - 0x5d get supply voltage, yields 1 byte in 0.1V steps + * - 0x5e set individual relay, takes 3 more bytes: relay number, + * hi/lo pulse time in 10ms steps + * - for interactive use? 'd' all relays on, 'e'..'l' relay 1..8 on, + * 'n' all relays off, 'o'..'v' relay 1..8 off + * - Modbus may or may not be a good match for this driver, or may + * better be kept in yet another driver? Requests and responses + * again differ from Ethernet and USB models, refer to traditional + * "coils" and have their individual and grouped access. + * https://www.robot-electronics.co.uk/files/mbh88.pdf + * - Reconsider the relation of relay channels, and digital outputs + * and their analog sampling and digital input interpretation. The + * current implementation is naive, assumes the simple DO/DI/AI + * groups and ignores their interaction within the firmware. * - Add support for password protection? * - See command 0x79 to "login" (beware of the differing return value * compared to other commands), command 0x7a to check if passwords @@ -102,6 +140,12 @@ enum cmd_code { CMD_DIGITAL_INACTIVE = 0x21, CMD_DIGITAL_SET_OUTPUTS = 0x23, CMD_DIGITAL_GET_OUTPUTS = 0x24, + CMD_DIGITAL_GET_INPUTS = 0x25, + CMD_DIGITAL_ACTIVE_1MS = 0x26, + CMD_DIGITAL_INACTIVE_1MS = 0x27, + CMD_ANALOG_GET_INPUT = 0x32, + CMD_ANALOG_GET_INPUT_12BIT = 0x33, + CMD_ANALOG_GET_ALL_VOLTAGES = 0x34, CMD_ASCII_TEXT_COMMAND = 0x3a, CMD_GET_SERIAL_NUMBER = 0x77, CMD_GET_SUPPLY_VOLTS = 0x78, @@ -256,7 +300,7 @@ SR_PRIV int devantech_eth008_cache_state(const struct sr_dev_inst *sdi) struct dev_context *devc; size_t rx_size; uint8_t req[1], *wrptr; - uint8_t rsp[3]; + uint8_t rsp[4]; const uint8_t *rdptr; uint32_t have; int ret; @@ -268,32 +312,70 @@ SR_PRIV int devantech_eth008_cache_state(const struct sr_dev_inst *sdi) if (!devc) return SR_ERR_ARG; - rx_size = devc->model->width_do; - if (rx_size > sizeof(rsp)) - return SR_ERR_NA; + /* Get the state of digital outputs when the model supports them. */ + if (devc->model->ch_count_do) { + rx_size = devc->model->width_do; + if (rx_size > sizeof(rsp)) + return SR_ERR_NA; - wrptr = req; - write_u8_inc(&wrptr, CMD_DIGITAL_GET_OUTPUTS); - ret = send_then_recv(serial, req, wrptr - req, rsp, rx_size); - if (ret != SR_OK) - return ret; - rdptr = rsp; + wrptr = req; + write_u8_inc(&wrptr, CMD_DIGITAL_GET_OUTPUTS); + ret = send_then_recv(serial, req, wrptr - req, rsp, rx_size); + if (ret != SR_OK) + return ret; + rdptr = rsp; - switch (rx_size) { - case 1: - have = read_u8_inc(&rdptr); - break; - case 2: - have = read_u16le_inc(&rdptr); - break; - case 3: - have = read_u24le_inc(&rdptr); - break; - default: - return SR_ERR_NA; + switch (rx_size) { + case 1: + have = read_u8_inc(&rdptr); + break; + case 2: + have = read_u16le_inc(&rdptr); + break; + case 3: + have = read_u24le_inc(&rdptr); + break; + default: + return SR_ERR_NA; + } + have &= devc->mask_do; + devc->curr_do = have; + } + + /* + * Get the state of digital inputs when the model supports them. + * (Sending unsupported requests to unaware firmware versions + * yields no response. That's why requests must be conditional.) + * + * Caching the state of analog inputs is condidered undesirable. + * Firmware does conversion at the very moment when the request + * is received to get a voltage reading. + */ + if (devc->model->ch_count_di) { + rx_size = devc->model->width_di; + if (rx_size > sizeof(rsp)) + return SR_ERR_NA; + + wrptr = req; + write_u8_inc(&wrptr, CMD_DIGITAL_GET_INPUTS); + ret = send_then_recv(serial, req, wrptr - req, rsp, rx_size); + if (ret != SR_OK) + return ret; + rdptr = rsp; + + switch (rx_size) { + case 2: + have = read_u16be_inc(&rdptr); + break; + case 4: + have = read_u32be_inc(&rdptr); + break; + default: + return SR_ERR_NA; + } + have &= (1UL << devc->model->ch_count_di) - 1; + devc->curr_di = have; } - have &= devc->mask_do; - devc->curr_do = have; return SR_OK; } @@ -317,7 +399,7 @@ SR_PRIV int devantech_eth008_query_do(const struct sr_dev_inst *sdi, return ret; /* - * Only reject unexpected requeusts after the update. Get the + * Only reject unexpected requests after the update. Get the * individual channel's state from the cache of all channels. */ if (!cg) @@ -406,6 +488,96 @@ SR_PRIV int devantech_eth008_setup_do(const struct sr_dev_inst *sdi, return SR_OK; } +SR_PRIV int devantech_eth008_query_di(const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg, gboolean *on) +{ + struct dev_context *devc; + struct channel_group_context *cgc; + uint32_t have; + int ret; + + /* Unconditionally update the internal cache. */ + ret = devantech_eth008_cache_state(sdi); + if (ret != SR_OK) + return ret; + + /* + * Only reject unexpected requests after the update. Get the + * individual channel's state from the cache of all channels. + */ + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + if (!cg) + return SR_ERR_ARG; + cgc = cg->priv; + if (!cgc) + return SR_ERR_BUG; + if (cgc->index >= devc->model->ch_count_di) + return SR_ERR_ARG; + have = devc->curr_di; + have >>= cgc->index; + have &= 1 << 0; + if (on) + *on = have ? TRUE : FALSE; + + return SR_OK; +} + +SR_PRIV int devantech_eth008_query_ai(const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg, uint16_t *adc_value) +{ + struct sr_serial_dev_inst *serial; + struct dev_context *devc; + struct channel_group_context *cgc; + uint8_t req[2], *wrptr; + uint8_t rsp[2]; + const uint8_t *rdptr; + uint32_t have; + int ret; + + serial = sdi->conn; + if (!serial) + return SR_ERR_ARG; + devc = sdi->priv; + if (!devc) + return SR_ERR_ARG; + if (!cg) + return SR_ERR_ARG; + cgc = cg->priv; + if (!cgc) + return SR_ERR_ARG; + if (cgc->index >= devc->model->ch_count_ai) + return SR_ERR_ARG; + + wrptr = req; + write_u8_inc(&wrptr, CMD_ANALOG_GET_INPUT); + write_u8_inc(&wrptr, cgc->number & 0xff); + ret = send_then_recv(serial, req, wrptr - req, rsp, sizeof(rsp)); + if (ret != SR_OK) + return ret; + rdptr = rsp; + + /* + * The interpretation of analog readings differs across models. + * All firmware versions provide an ADC result in BE format in + * a 16bit response. Some models provide 10 significant digits, + * others provide 12 bits. Full scale can either be 3V3 or 5V0. + * Some devices are 5V tolerant but won't read more than 3V3 + * values (and clip above that full scale value). Some firmware + * versions support request 0x33 in addition to 0x32. + * + * This is why this implementation provides the result to the + * caller as a unit-less value. It is also what the firmware's + * web interface does. + */ + have = read_u16be_inc(&rdptr); + if (adc_value) + *adc_value = have; + + return SR_OK; +} + SR_PRIV int devantech_eth008_query_supply(const struct sr_dev_inst *sdi, const struct sr_channel_group *cg, uint16_t *millivolts) {