X-Git-Url: https://sigrok.org/gitweb/?p=libsigrok.git;a=blobdiff_plain;f=src%2Fhardware%2Frdtech-um%2Fprotocol.c;h=806f9c2d49f0dc7b38691f2c69b823a1f3e2b12c;hp=763ad95b892dda70bdb83974c792e4f7ad13ea82;hb=HEAD;hpb=ae33433bec2a5648fed05594810a9a2dca933586 diff --git a/src/hardware/rdtech-um/protocol.c b/src/hardware/rdtech-um/protocol.c index 763ad95b..806f9c2d 100644 --- a/src/hardware/rdtech-um/protocol.c +++ b/src/hardware/rdtech-um/protocol.c @@ -39,26 +39,24 @@ /* 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;