]> sigrok.org Git - libsigrok.git/commitdiff
devantech-eth008: support digital and analog input channels
authorGerhard Sittig <redacted>
Mon, 25 Sep 2023 12:28:59 +0000 (14:28 +0200)
committerGerhard Sittig <redacted>
Mon, 25 Sep 2023 17:30:18 +0000 (19:30 +0200)
Implement support for digital input and analog input on those models
which provide these features beyond digital output (relays).

This was written without access to the hardware, and is untested.

This completes support for the cards' features. Other models (ETH1610)
could get added when their model ID is known. WLAN enabled cards are
assumed to share model IDs with their Ethernet counterparts.

src/hardware/devantech-eth008/api.c
src/hardware/devantech-eth008/protocol.c
src/hardware/devantech-eth008/protocol.h

index 3eadb1fbc22d46d577ddb61b877a99213138c803..88cf45ca296844c69bd2a8a7dd8964df358ebac8 100644 (file)
@@ -42,15 +42,19 @@ static const uint32_t devopts_cg_do[] = {
        SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET,
 };
 
+static const uint32_t devopts_cg_di[] = {
+       SR_CONF_ENABLED | SR_CONF_GET,
+};
+
 static const uint32_t devopts_cg_ai[] = {
        SR_CONF_VOLTAGE | SR_CONF_GET,
 };
 
 static const struct devantech_eth008_model models[] = {
-       { 18, "ETH002",   2, 0, 1, 0, },
-       { 19, "ETH008",   8, 0, 1, 0, },
-       { 20, "ETH484",  16, 0, 2, 0x00f0, },
-       { 21, "ETH8020", 20, 0, 3, 0, },
+       { 18, "ETH002",   2, 0, 0, 0, 1, 0, },
+       { 19, "ETH008",   8, 0, 0, 0, 1, 0, },
+       { 20, "ETH484",  16, 8, 4, 0, 2, 0x00f0, },
+       { 21, "ETH8020", 20, 8, 8, 0, 3, 0, },
 };
 
 static const struct devantech_eth008_model *find_model(uint8_t code)
@@ -80,7 +84,7 @@ static struct sr_dev_inst *probe_device_conn(const char *conn)
        gboolean has_serno_cmd;
        char snr_txt[16];
        struct channel_group_context *cgc;
-       size_t ch_idx, nr, do_idx;
+       size_t ch_idx, nr, do_idx, di_idx, ai_idx;
        struct sr_channel_group *cg;
        char cg_name[24];
        int ret;
@@ -130,10 +134,10 @@ static struct sr_dev_inst *probe_device_conn(const char *conn)
 
        ch_idx = 0;
        devc->mask_do = (1UL << devc->model->ch_count_do) - 1;
-       devc->mask_do &= ~model->mask_do_missing;
+       devc->mask_do &= ~devc->model->mask_do_missing;
        for (do_idx = 0; do_idx < devc->model->ch_count_do; do_idx++) {
                nr = do_idx + 1;
-               if (model->mask_do_missing & (1UL << do_idx))
+               if (devc->model->mask_do_missing & (1UL << do_idx))
                        continue;
                snprintf(cg_name, sizeof(cg_name), "DO%zu", nr);
                cgc = g_malloc0(sizeof(*cgc));
@@ -144,6 +148,28 @@ static struct sr_dev_inst *probe_device_conn(const char *conn)
                (void)cg;
                ch_idx++;
        }
+       for (di_idx = 0; di_idx < devc->model->ch_count_di; di_idx++) {
+               nr = di_idx + 1;
+               snprintf(cg_name, sizeof(cg_name), "DI%zu", nr);
+               cgc = g_malloc0(sizeof(*cgc));
+               cg = sr_channel_group_new(sdi, cg_name, cgc);
+               cgc->index = di_idx;
+               cgc->number = nr;
+               cgc->ch_type = DV_CH_DIGITAL_INPUT;
+               (void)cg;
+               ch_idx++;
+       }
+       for (ai_idx = 0; ai_idx < devc->model->ch_count_ai; ai_idx++) {
+               nr = ai_idx + 1;
+               snprintf(cg_name, sizeof(cg_name), "AI%zu", nr);
+               cgc = g_malloc0(sizeof(*cgc));
+               cg = sr_channel_group_new(sdi, cg_name, cgc);
+               cgc->index = ai_idx;
+               cgc->number = nr;
+               cgc->ch_type = DV_CH_ANALOG_INPUT;
+               (void)cg;
+               ch_idx++;
+       }
        if (1) {
                /* Create an analog channel for the supply voltage. */
                snprintf(cg_name, sizeof(cg_name), "Vsupply");
@@ -231,8 +257,22 @@ static int config_get(uint32_t key, GVariant **data,
                        *data = g_variant_new_boolean(on);
                        return SR_OK;
                }
+               if (cgc->ch_type == DV_CH_DIGITAL_INPUT) {
+                       ret = devantech_eth008_query_di(sdi, cg, &on);
+                       if (ret != SR_OK)
+                               return ret;
+                       *data = g_variant_new_boolean(on);
+                       return SR_OK;
+               }
                return SR_ERR_NA;
        case SR_CONF_VOLTAGE:
+               if (cgc->ch_type == DV_CH_ANALOG_INPUT) {
+                       ret = devantech_eth008_query_ai(sdi, cg, &vin);
+                       if (ret != SR_OK)
+                               return ret;
+                       *data = g_variant_new_uint32(vin);
+                       return SR_OK;
+               }
                if (cgc->ch_type == DV_CH_SUPPLY_VOLTAGE) {
                        ret = devantech_eth008_query_supply(sdi, cg, &vin);
                        if (ret != SR_OK)
@@ -307,6 +347,14 @@ static int config_list(uint32_t key, GVariant **data,
                        *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_do));
                        return SR_OK;
                }
+               if (cgc->ch_type == DV_CH_DIGITAL_INPUT) {
+                       *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_di));
+                       return SR_OK;
+               }
+               if (cgc->ch_type == DV_CH_ANALOG_INPUT) {
+                       *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_ai));
+                       return SR_OK;
+               }
                if (cgc->ch_type == DV_CH_SUPPLY_VOLTAGE) {
                        *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_ai));
                        return SR_OK;
index 8c436ea3bfd952ec72e44cb9e671853e18d210af..e2c3445a966a4e99551aa3300ab0f03351eb14a5 100644 (file)
  * 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.
+ * source code which is maintained by the vendor. The untested parts
+ * of this sigrok driver are based on version 0.1.2 of this Python
+ * code which is MIT licensed (corresponds to commit 0c0080b88e29),
+ * and example code in ZIP archives provided on the shop's products'
+ * pages.
  *
  * The device provides several means of communication: HTTP requests
  * (as well as an interactive web form). Raw TCP communication with
  * TODO
  * - Add support for other models. Currently exclusively supports the
  *   ETH008-B model which was used during implementation of the driver.
+ *   (Descriptions for more models were added, their operation is yet
+ *   to get verified.) Getting relay state involves variable length
+ *   responses, bits appear to be in little endian presentation.
+ * - Add support for absent relay output channels (ETH484 lacks R5..R8).
+ * - Add support for digital inputs. ETH484 has command 0x25 which gets
+ *   two bytes, the second byte carries eight digital input bits.
+ *   ETH1610 has 16 inputs, evaluates both bytes. Is data format u16be?
+ *   ETH8020 support code is inconsistent, implements two accessors
+ *   which either retrieve two or three bytes, while callers access the
+ *   fourth byte of these responses? Cannot have worked, seems untested.
+ * - Add support for analog inputs. ETH484 has command 0x32 which takes
+ *   a channel number, and gets two bytes which carry a u16be value(?).
+ *   So does ETH8020. Channel count differs across models.
+ * - Are there other models of interest? ETH1610 product page reads
+ *   as if the card had 10 relays (strict output), and 16 inputs which
+ *   could either be used in analog mode, or simply get interpreted as
+ *   digital input?
  * - 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 +123,8 @@ enum cmd_code {
        CMD_DIGITAL_INACTIVE = 0x21,
        CMD_DIGITAL_SET_OUTPUTS = 0x23,
        CMD_DIGITAL_GET_OUTPUTS = 0x24,
+       CMD_DIGITAL_GET_INPUTS = 0x25,
+       CMD_ANALOG_GET_INPUT = 0x32,
        CMD_ASCII_TEXT_COMMAND = 0x3a,
        CMD_GET_SERIAL_NUMBER = 0x77,
        CMD_GET_SUPPLY_VOLTS = 0x78,
@@ -268,6 +291,7 @@ SR_PRIV int devantech_eth008_cache_state(const struct sr_dev_inst *sdi)
        if (!devc)
                return SR_ERR_ARG;
 
+       /* Unconditionally get the state of digital output. */
        rx_size = devc->model->width_do;
        if (rx_size > sizeof(rsp))
                return SR_ERR_NA;
@@ -295,6 +319,38 @@ SR_PRIV int devantech_eth008_cache_state(const struct sr_dev_inst *sdi)
        have &= devc->mask_do;
        devc->curr_do = have;
 
+       /*
+        * Get the state of digital inputs when the model supports them.
+        * Firmware of other models happens to not respond to unknown
+        * requests. Responses seem to have identical size across all
+        * models. Payload is assumed to be u16 be formatted. Must be
+        * verified when other models are seen.
+        *
+        * Caching the state of analog inputs is condidered undesirable.
+        */
+       if (devc->model->ch_count_di) {
+               rx_size = sizeof(uint16_t);
+               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;
+               default:
+                       return SR_ERR_NA;
+               }
+               have &= (1UL << devc->model->ch_count_di) - 1;
+               devc->curr_di = have;
+       }
+
        return SR_OK;
 }
 
@@ -406,6 +462,87 @@ 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 requeusts 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;
+
+       /*
+        * TODO The u16 BE format is a guess. Needs verification.
+        * As is the unit-less nature of that value.
+        */
+       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)
 {
index 074a1b2e21b09d226b2ca7c3649ec686137d1001..1075e54b5e7dc3659bc046012d61d8f42810eda6 100644 (file)
  * same time. Some models have gaps in their relay channel numbers
  * (ETH484 misses R5..R8).
  *
- * Some models have digital inputs and analog inputs. These features
- * currently are not supported in this implementation.
+ * ETH484 also has 8 digital inputs, and 4 analog inputs. Features
+ * beyond relay output are untested in this implementation.
+ *
+ * Vendor's support code for ETH8020 suggests that it has 8 digital
+ * inputs and 8 analog inputs. But that digital input supporting code
+ * could never have worked, probably wasn't tested.
+ *
+ * Digital inputs and analog inputs appear to share I/O pins. Users can
+ * read these pins either in terms of an ADC value, or can interpret
+ * them as raw digital input. While not all models with digital inputs
+ * seem to provide all of them in analog form. DI and AI channel counts
+ * may differ depending on the model.
  */
 struct devantech_eth008_model {
        uint8_t code;
        const char *name;
        size_t ch_count_do;
+       size_t ch_count_di;
+       size_t ch_count_ai;
        uint8_t min_serno_fw;
        size_t width_do;
        uint32_t mask_do_missing;
@@ -52,6 +64,8 @@ struct devantech_eth008_model {
 
 enum devantech_eth008_channel_type {
        DV_CH_DIGITAL_OUTPUT,
+       DV_CH_DIGITAL_INPUT,
+       DV_CH_ANALOG_INPUT,
        DV_CH_SUPPLY_VOLTAGE,
 };
 
@@ -66,6 +80,7 @@ struct dev_context {
        const struct devantech_eth008_model *model;
        uint32_t mask_do;
        uint32_t curr_do;
+       uint32_t curr_di;
 };
 
 SR_PRIV int devantech_eth008_get_model(struct sr_serial_dev_inst *serial,
@@ -77,6 +92,10 @@ SR_PRIV int devantech_eth008_query_do(const struct sr_dev_inst *sdi,
        const struct sr_channel_group *cg, gboolean *on);
 SR_PRIV int devantech_eth008_setup_do(const struct sr_dev_inst *sdi,
        const struct sr_channel_group *cg, gboolean on);
+SR_PRIV int devantech_eth008_query_di(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, gboolean *on);
+SR_PRIV int devantech_eth008_query_ai(const struct sr_dev_inst *sdi,
+       const struct sr_channel_group *cg, uint16_t *adc_value);
 SR_PRIV int devantech_eth008_query_supply(const struct sr_dev_inst *sdi,
        const struct sr_channel_group *cg, uint16_t *millivolts);