]> sigrok.org Git - libsigrok.git/blobdiff - src/hardware/rdtech-um/protocol.c
output/csv: use intermediate time_t var, silence compiler warning
[libsigrok.git] / src / hardware / rdtech-um / protocol.c
index 763ad95b892dda70bdb83974c792e4f7ad13ea82..806f9c2d49f0dc7b38691f2c69b823a1f3e2b12c 100644 (file)
 /* Command code to request another poll response. */
 #define UM_CMD_POLL 0xf0
 
-static const struct binary_analog_channel rdtech_default_channels[] = {
-       { "V", { 2, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "I", { 4, BVT_BE_UINT16, 0.001, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE },
-       { "D+", { 96, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "D-", { 98, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "T", { 10, BVT_BE_UINT16, 1.0, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS },
+static const struct rdtech_um_channel_desc default_channels[] = {
+       { "V", { 2, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "I", { 4, BVT_BE_UINT16, }, { 1, 1e3, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE },
+       { "D+", { 96, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "D-", { 98, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "T", { 10, BVT_BE_UINT16, }, { 1, 1, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS },
        /* Threshold-based recording (mWh) */
-       { "E", { 106, BVT_BE_UINT32, 0.001, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
-       ALL_ZERO,
+       { "E", { 106, BVT_BE_UINT32, }, { 1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
 };
 
-static const struct binary_analog_channel rdtech_um25c_channels[] = {
-       { "V", { 2, BVT_BE_UINT16, 0.001, }, 3, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "I", { 4, BVT_BE_UINT16, 0.0001, }, 4, SR_MQ_CURRENT, SR_UNIT_AMPERE },
-       { "D+", { 96, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "D-", { 98, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "T", { 10, BVT_BE_UINT16, 1.0, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS },
+static const struct rdtech_um_channel_desc um25c_channels[] = {
+       { "V", { 2, BVT_BE_UINT16, }, { 1, 1e3, }, 3, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "I", { 4, BVT_BE_UINT16, }, { 100, 1e6, }, 4, SR_MQ_CURRENT, SR_UNIT_AMPERE },
+       { "D+", { 96, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "D-", { 98, BVT_BE_UINT16, }, { 10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "T", { 10, BVT_BE_UINT16, }, { 1, 1, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS },
        /* Threshold-based recording (mWh) */
-       { "E", { 106, BVT_BE_UINT32, 0.001, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
-       ALL_ZERO,
+       { "E", { 106, BVT_BE_UINT32, }, { 1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
 };
 
 static gboolean csum_ok_fff1(const uint8_t *buf, size_t len)
@@ -100,9 +98,9 @@ static gboolean csum_ok_um34c(const uint8_t *buf, size_t len)
 }
 
 static const struct rdtech_um_profile um_profiles[] = {
-       { "UM24C", RDTECH_UM24C, rdtech_default_channels, csum_ok_fff1, },
-       { "UM25C", RDTECH_UM25C, rdtech_um25c_channels, csum_ok_fff1, },
-       { "UM34C", RDTECH_UM34C, rdtech_default_channels, csum_ok_um34c, },
+       { "UM24C", RDTECH_UM24C, ARRAY_AND_SIZE(default_channels), csum_ok_fff1, },
+       { "UM25C", RDTECH_UM25C, ARRAY_AND_SIZE(um25c_channels), csum_ok_fff1, },
+       { "UM34C", RDTECH_UM34C, ARRAY_AND_SIZE(default_channels), csum_ok_um34c, },
 };
 
 static const struct rdtech_um_profile *find_profile(uint16_t id)
@@ -163,8 +161,12 @@ SR_PRIV int rdtech_um_poll(const struct sr_dev_inst *sdi, gboolean force)
        uint8_t req;
        int ret;
 
-       /* Check for expired intervals or forced requests. */
+       /* Don't send request when receive data is being accumulated. */
        devc = sdi->priv;
+       if (!force && devc->buflen)
+               return SR_OK;
+
+       /* Check for expired intervals or forced requests. */
        now = g_get_monotonic_time() / 1000;
        elapsed = now - devc->cmd_sent_at;
        if (!force && elapsed < POLL_PERIOD_MS)
@@ -183,62 +185,147 @@ SR_PRIV int rdtech_um_poll(const struct sr_dev_inst *sdi, gboolean force)
        return SR_OK;
 }
 
-static void handle_poll_data(const struct sr_dev_inst *sdi)
+static int process_data(struct sr_dev_inst *sdi,
+       const uint8_t *data, size_t dlen)
 {
        struct dev_context *devc;
+       const struct rdtech_um_profile *p;
        size_t ch_idx;
-       GSList *ch;
+       float v;
+       int ret;
 
        devc = sdi->priv;
-       sr_spew("Received poll packet (len: %zu).", devc->buflen);
-       if (devc->buflen != POLL_RECV_LEN) {
-               sr_err("Unexpected poll packet length: %zu", devc->buflen);
-               return;
+       p = devc->profile;
+
+       sr_spew("Received poll packet (len: %zu).", dlen);
+       if (dlen < POLL_RECV_LEN) {
+               sr_err("Insufficient response data length: %zu", dlen);
+               return SR_ERR_DATA;
+       }
+
+       if (!p->csum_ok(data, POLL_RECV_LEN)) {
+               sr_err("Packet checksum verification failed.");
+               return SR_ERR_DATA;
        }
 
-       ch_idx = 0;
-       for (ch = sdi->channels; ch; ch = g_slist_next(ch)) {
-               bv_send_analog_channel(sdi, ch->data,
-                       &devc->profile->channels[ch_idx],
-                       devc->buf, devc->buflen);
-               ch_idx++;
+       ret = SR_OK;
+       std_session_send_df_frame_begin(sdi);
+       for (ch_idx = 0; ch_idx < p->channel_count; ch_idx++) {
+               ret = bv_get_value_len(&v, &p->channels[ch_idx].spec, data, dlen);
+               if (ret != SR_OK)
+                       break;
+               ret = feed_queue_analog_submit_one(devc->feeds[ch_idx], v, 1);
+               if (ret != SR_OK)
+                       break;
        }
+       std_session_send_df_frame_end(sdi);
+
+       sr_sw_limits_update_frames_read(&devc->limits, 1);
+       if (sr_sw_limits_check(&devc->limits))
+               sr_dev_acquisition_stop(sdi);
 
-       sr_sw_limits_update_samples_read(&devc->limits, 1);
+       return ret;
 }
 
-static void recv_poll_data(struct sr_dev_inst *sdi, struct sr_serial_dev_inst *serial)
+static int accum_data(struct sr_dev_inst *sdi, struct sr_serial_dev_inst *serial)
 {
        struct dev_context *devc;
        const struct rdtech_um_profile *p;
-       int len;
-
-       /* Serial data arrived. */
+       uint8_t *rdptr;
+       size_t space, rcvd, rdlen;
+       int ret;
+       gboolean do_sync_check;
+       size_t sync_len, sync_idx;
+
+       /*
+        * Receive data became available. Drain the serial transport.
+        * Grab incoming data in as large a chunk as possible. Also
+        * copes with zero receive data length, as some transports may
+        * trigger periodically without data really being available.
+        */
        devc = sdi->priv;
        p = devc->profile;
-       while (devc->buflen < POLL_RECV_LEN) {
-               len = serial_read_nonblocking(serial, devc->buf + devc->buflen, 1);
-               if (len < 1)
-                       return;
-               devc->buflen += len;
-
-               /* Check if the poll model ID matches the profile. */
-               if (devc->buflen == 2 && RB16(devc->buf) != p->model_id) {
-                       sr_warn("Illegal model ID in poll response (0x%.4" PRIx16 "),"
-                               " skipping 1 byte.",
-                               RB16(devc->buf));
-                       devc->buflen--;
-                       memmove(devc->buf, devc->buf + 1, devc->buflen);
+       rdptr = &devc->buf[devc->buflen];
+       space = sizeof(devc->buf) - devc->buflen;
+       do_sync_check = FALSE;
+       sync_len = sizeof(uint16_t);
+       while (space) {
+               ret = serial_read_nonblocking(serial, rdptr, space);
+               if (ret < 0)
+                       return SR_ERR_IO;
+               rcvd = (size_t)ret;
+               if (rcvd == 0)
+                       break;
+               if (rcvd > space)
+                       return SR_ERR_BUG;
+               if (devc->buflen < sync_len)
+                       do_sync_check = TRUE;
+               devc->buflen += rcvd;
+               if (devc->buflen < sync_len)
+                       do_sync_check = FALSE;
+               space -= rcvd;
+               rdptr += rcvd;
+       }
+
+       /*
+        * Synchronize to the packetized input stream. Check the model
+        * ID at the start of receive data. Which is a weak condition,
+        * but going out of sync should be rare, and repeated attempts
+        * to synchronize should eventually succeed. Try to rate limit
+        * the emission of diagnostics messages. (Re-)run this logic
+        * at the first reception which makes enough data available,
+        * but not during subsequent accumulation of more data.
+        *
+        * Reducing redundancy in the implementation at the same time as
+        * increasing robustness would involve the creation of a checker
+        * routine, which just gets called for every byte position until
+        * it succeeds. Similar to what a previous implementation of the
+        * read loop did, which was expensive on the serial transport.
+        */
+       sync_idx = 0;
+       if (do_sync_check && read_u16be(&devc->buf[sync_idx]) != p->model_id)
+               sr_warn("Unexpected response data, trying to synchronize.");
+       while (do_sync_check) {
+               if (sync_idx + sync_len >= devc->buflen)
+                       break;
+               if (read_u16be(&devc->buf[sync_idx]) == p->model_id)
+                       break;
+               sync_idx++;
+       }
+       if (do_sync_check && sync_idx) {
+               sr_dbg("Skipping %zu bytes in attempt to sync.", sync_idx);
+               sync_len = devc->buflen - sync_idx;
+               if (sync_len)
+                       memmove(&devc->buf[0], &devc->buf[sync_idx], sync_len);
+               devc->buflen -= sync_idx;
+       }
+
+       /*
+        * Process packets as their reception completes. Periodically
+        * re-transmit poll requests. Discard consumed data after all
+        * processing has completed.
+        */
+       rdptr = devc->buf;
+       rdlen = devc->buflen;
+       ret = SR_OK;
+       while (ret == SR_OK && rdlen >= POLL_RECV_LEN) {
+               ret = process_data(sdi, rdptr, rdlen);
+               if (ret != SR_OK) {
+                       sr_err("Processing response packet failed.");
+                       break;
                }
+               rdptr += POLL_RECV_LEN;
+               rdlen -= POLL_RECV_LEN;
+
+               if (0 && !sr_sw_limits_check(&devc->limits))
+                       (void)rdtech_um_poll(sdi, FALSE);
        }
+       rcvd = rdptr - devc->buf;
+       devc->buflen -= rcvd;
+       if (devc->buflen)
+               memmove(&devc->buf[0], rdptr, devc->buflen);
 
-       if (devc->buflen != POLL_RECV_LEN)
-               sr_warn("Skipping packet, unexpected receive length.");
-       else if (!p->csum_ok(devc->buf, devc->buflen))
-               sr_warn("Skipping packet, checksum verification failed.");
-       else
-               handle_poll_data(sdi);
-       devc->buflen = 0;
+       return ret;
 }
 
 SR_PRIV int rdtech_um_receive_data(int fd, int revents, void *cb_data)
@@ -246,6 +333,7 @@ SR_PRIV int rdtech_um_receive_data(int fd, int revents, void *cb_data)
        struct sr_dev_inst *sdi;
        struct dev_context *devc;
        struct sr_serial_dev_inst *serial;
+       int ret;
 
        (void)fd;
 
@@ -254,10 +342,18 @@ SR_PRIV int rdtech_um_receive_data(int fd, int revents, void *cb_data)
        if (!(devc = sdi->priv))
                return TRUE;
 
-       /* Drain and process receive data as it becomes available. */
+       /*
+        * Drain and process receive data as it becomes available.
+        * Terminate acquisition upon receive or processing error.
+        */
        serial = sdi->conn;
-       if (revents == G_IO_IN)
-               recv_poll_data(sdi, serial);
+       if (revents == G_IO_IN) {
+               ret = accum_data(sdi, serial);
+               if (ret != SR_OK) {
+                       sr_dev_acquisition_stop(sdi);
+                       return TRUE;
+               }
+       }
 
        /* Check configured acquisition limits. */
        if (sr_sw_limits_check(&devc->limits)) {
@@ -265,7 +361,7 @@ SR_PRIV int rdtech_um_receive_data(int fd, int revents, void *cb_data)
                return TRUE;
        }
 
-       /* Periodically emit measurement requests. */
+       /* Periodically retransmit measurement requests. */
        (void)rdtech_um_poll(sdi, FALSE);
 
        return TRUE;