#include "libsigrok-internal.h"
#include "protocol.h"
-#define SERIAL_WRITE_TIMEOUT_MS 1
+/* Read/write timeouts, poll request intervals. */
+#define PROBE_TO_MS 1000
+#define WRITE_TO_MS 1
+#define POLL_PERIOD_MS 100
-#define UM_POLL_LEN 130
-#define UM_POLL_PERIOD_MS 100
-#define UM_TIMEOUT_MS 1000
+/* Expected receive data size for poll responses. */
+#define POLL_RECV_LEN 130
+/* 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 int poll_csum_fff1(char buf[], int len)
+static gboolean csum_ok_fff1(const uint8_t *buf, size_t len)
{
- if (len != UM_POLL_LEN)
- return 0;
- else
- return RB16(&buf[len - 2]) == 0xFFF1;
+ uint16_t csum_recv;
+
+ if (len != POLL_RECV_LEN)
+ return FALSE;
+
+ csum_recv = read_u16be(&buf[len - sizeof(uint16_t)]);
+ if (csum_recv != 0xfff1)
+ return FALSE;
+
+ return TRUE;
}
-static int poll_csum_um34c(char buf[], int len)
+static gboolean csum_ok_um34c(const uint8_t *buf, size_t len)
{
static const int positions[] = {
1, 3, 7, 9, 15, 17, 19, 23, 31, 39, 41, 45, 49, 53,
55, 57, 59, 63, 67, 69, 73, 79, 83, 89, 97, 99, 109,
111, 113, 119, 121, 127,
};
- unsigned int i;
- uint8_t csum = 0;
- if (len != UM_POLL_LEN)
- return 0;
+ size_t i;
+ uint8_t csum_calc, csum_recv;
+
+ if (len != POLL_RECV_LEN)
+ return FALSE;
+ csum_calc = 0;
for (i = 0; i < ARRAY_SIZE(positions); i++)
- csum ^= buf[positions[i]];
+ csum_calc ^= buf[positions[i]];
+ csum_recv = read_u8(&buf[len - sizeof(uint8_t)]);
+ if (csum_recv != csum_calc)
+ return FALSE;
- return csum == (uint8_t)buf[len - 1];
+ return TRUE;
}
static const struct rdtech_um_profile um_profiles[] = {
- { "UM24C", RDTECH_UM24C, rdtech_default_channels, &poll_csum_fff1, },
- { "UM25C", RDTECH_UM25C, rdtech_um25c_channels, &poll_csum_fff1, },
- { "UM34C", RDTECH_UM34C, rdtech_default_channels, &poll_csum_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)
{
- unsigned int i;
+ size_t i;
+ const struct rdtech_um_profile *profile;
+
for (i = 0; i < ARRAY_SIZE(um_profiles); i++) {
- if (um_profiles[i].model_id == id)
- return &um_profiles[i];
+ profile = &um_profiles[i];
+ if (profile->model_id == id)
+ return profile;
}
return NULL;
}
SR_PRIV const struct rdtech_um_profile *rdtech_um_probe(struct sr_serial_dev_inst *serial)
{
const struct rdtech_um_profile *p;
- static const uint8_t request = UM_CMD_POLL;
- char buf[RDTECH_UM_BUFSIZE];
- int len;
-
- if (serial_write_blocking(serial, &request, sizeof(request),
- SERIAL_WRITE_TIMEOUT_MS) < 0) {
- sr_err("Unable to send probe request.");
+ uint8_t req;
+ int ret;
+ uint8_t buf[RDTECH_UM_BUFSIZE];
+ int rcvd;
+ uint16_t model_id;
+
+ req = UM_CMD_POLL;
+ ret = serial_write_blocking(serial, &req, sizeof(req), WRITE_TO_MS);
+ if (ret < 0) {
+ sr_err("Failed to send probe request.");
return NULL;
}
- len = serial_read_blocking(serial, buf, UM_POLL_LEN, UM_TIMEOUT_MS);
- if (len != UM_POLL_LEN) {
+ rcvd = serial_read_blocking(serial, buf, POLL_RECV_LEN, PROBE_TO_MS);
+ if (rcvd != POLL_RECV_LEN) {
sr_err("Failed to read probe response.");
return NULL;
}
- p = find_profile(RB16(&buf[0]));
+ model_id = read_u16be(&buf[0]);
+ p = find_profile(model_id);
if (!p) {
- sr_err("Unrecognized UM device (0x%.4" PRIx16 ")!", RB16(&buf[0]));
+ sr_err("Unrecognized UM device (0x%.4" PRIx16 ").", model_id);
return NULL;
}
- if (!p->poll_csum(buf, len)) {
- sr_err("Probe response contains illegal checksum or end marker.\n");
+ if (!p->csum_ok(buf, rcvd)) {
+ sr_err("Probe response fails checksum verification.");
return NULL;
}
return p;
}
-SR_PRIV int rdtech_um_poll(const struct sr_dev_inst *sdi)
+SR_PRIV int rdtech_um_poll(const struct sr_dev_inst *sdi, gboolean force)
{
- struct dev_context *devc = sdi->priv;
- struct sr_serial_dev_inst *serial = sdi->conn;
- static const uint8_t request = UM_CMD_POLL;
+ struct dev_context *devc;
+ int64_t now, elapsed;
+ struct sr_serial_dev_inst *serial;
+ uint8_t req;
+ int ret;
- if (serial_write_blocking(serial, &request, sizeof(request),
- SERIAL_WRITE_TIMEOUT_MS) < 0) {
+ /* 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)
+ return SR_OK;
+
+ /* Send another poll request. Update interval only on success. */
+ serial = sdi->conn;
+ req = UM_CMD_POLL;
+ ret = serial_write_blocking(serial, &req, sizeof(req), WRITE_TO_MS);
+ if (ret < 0) {
sr_err("Unable to send poll request.");
return SR_ERR;
}
-
- devc->cmd_sent_at = g_get_monotonic_time() / 1000;
+ devc->cmd_sent_at = now;
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 = sdi->priv;
- int i;
- GSList *ch;
-
- sr_spew("Received poll packet (len: %d).", devc->buflen);
- if (devc->buflen != UM_POLL_LEN) {
- sr_err("Unexpected poll packet length: %i", devc->buflen);
- return;
+ struct dev_context *devc;
+ const struct rdtech_um_profile *p;
+ size_t ch_idx;
+ float v;
+ int ret;
+
+ devc = sdi->priv;
+ 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;
}
- for (ch = sdi->channels, i = 0; ch; ch = g_slist_next(ch), i++) {
- bv_send_analog_channel(sdi, ch->data,
- &devc->profile->channels[i],
- devc->buf, devc->buflen);
+ if (!p->csum_ok(data, POLL_RECV_LEN)) {
+ sr_err("Packet checksum verification failed.");
+ return SR_ERR_DATA;
}
- sr_sw_limits_update_samples_read(&devc->limits, 1);
+ 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(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);
+
+ 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 = sdi->priv;
- const struct rdtech_um_profile *p = devc->profile;
- int len;
-
- /* Serial data arrived. */
- while (devc->buflen < UM_POLL_LEN) {
- len = serial_read_nonblocking(serial, devc->buf + devc->buflen, 1);
- if (len < 1)
- return;
-
- devc->buflen++;
-
- /* 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);
- }
+ struct dev_context *devc;
+ const struct rdtech_um_profile *p;
+ 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;
+ 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;
}
- if (devc->buflen == UM_POLL_LEN && p->poll_csum(devc->buf, devc->buflen))
- handle_poll_data(sdi);
- else
- sr_warn("Skipping packet with illegal checksum / end marker.");
+ /*
+ * 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);
- devc->buflen = 0;
+ return ret;
}
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;
- int64_t now, elapsed;
+ int ret;
(void)fd;
if (!(sdi = cb_data))
return TRUE;
-
if (!(devc = sdi->priv))
return TRUE;
+ /*
+ * 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)) {
sr_dev_acquisition_stop(sdi);
return TRUE;
}
- now = g_get_monotonic_time() / 1000;
- elapsed = now - devc->cmd_sent_at;
-
- if (elapsed > UM_POLL_PERIOD_MS)
- rdtech_um_poll(sdi);
+ /* Periodically retransmit measurement requests. */
+ (void)rdtech_um_poll(sdi, FALSE);
return TRUE;
}