]> sigrok.org Git - libsigrok.git/blobdiff - src/hardware/rdtech-tc/protocol.c
output/csv: use intermediate time_t var, silence compiler warning
[libsigrok.git] / src / hardware / rdtech-tc / protocol.c
index 39d52ba5c3ea6935e127f6f48e68a1a992b79f67..291604a0c524e6887381d77d9a837263751793c2 100644 (file)
 #include "libsigrok-internal.h"
 #include "protocol.h"
 
-#define SERIAL_WRITE_TIMEOUT_MS 1
-
-#define TC_POLL_PERIOD_MS 100
-#define TC_TIMEOUT_MS 1000
-
-static const char *poll_cmd = "getva";
+#define PROBE_TO_MS    1000
+#define WRITE_TO_MS    1
+#define POLL_PERIOD_MS 100
 
 /*
  * Response data (raw sample data) consists of three adjacent chunks
@@ -56,6 +53,9 @@ static const char *poll_cmd = "getva";
 #define OFF_PAC2 (1 * PAC_LEN)
 #define OFF_PAC3 (2 * PAC_LEN)
 #define TC_POLL_LEN (3 * PAC_LEN)
+#if TC_POLL_LEN > RDTECH_TC_RSPBUFSIZE
+#  error "response length exceeds receive buffer space"
+#endif
 
 #define OFF_MODEL 4
 #define LEN_MODEL 4
@@ -65,33 +65,32 @@ static const char *poll_cmd = "getva";
 
 #define OFF_SERIAL 12
 
-static const uint8_t AES_KEY[] = {
+static const uint8_t aes_key[] = {
        0x58, 0x21, 0xfa, 0x56, 0x01, 0xb2, 0xf0, 0x26,
        0x87, 0xff, 0x12, 0x04, 0x62, 0x2a, 0x4f, 0xb0,
        0x86, 0xf4, 0x02, 0x60, 0x81, 0x6f, 0x9a, 0x0b,
        0xa7, 0xf1, 0x06, 0x61, 0x9a, 0xb8, 0x72, 0x88,
 };
 
-static const struct binary_analog_channel rdtech_tc_channels[] = {
-       { "V",  {   0 + 48, BVT_LE_UINT32, 1e-4, }, 4, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "I",  {   0 + 52, BVT_LE_UINT32, 1e-5, }, 5, SR_MQ_CURRENT, SR_UNIT_AMPERE },
-       { "D+", {  64 + 32, BVT_LE_UINT32, 1e-2, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "D-", {  64 + 36, BVT_LE_UINT32, 1e-2, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
-       { "E0", {  64 + 12, BVT_LE_UINT32, 1e-3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
-       { "E1", {  64 + 20, BVT_LE_UINT32, 1e-3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
-       ALL_ZERO,
+static const struct rdtech_tc_channel_desc rdtech_tc_channels[] = {
+       { "V",  {   0 + 48, BVT_LE_UINT32, }, { 100, 1e6, }, 4, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "I",  {   0 + 52, BVT_LE_UINT32, }, {  10, 1e6, }, 5, SR_MQ_CURRENT, SR_UNIT_AMPERE },
+       { "D+", {  64 + 32, BVT_LE_UINT32, }, {  10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "D-", {  64 + 36, BVT_LE_UINT32, }, {  10, 1e3, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "E0", {  64 + 12, BVT_LE_UINT32, }, {   1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
+       { "E1", {  64 + 20, BVT_LE_UINT32, }, {   1, 1e3, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
 };
 
 static gboolean check_pac_crc(uint8_t *data)
 {
-       uint16_t crc;
-       uint32_t crc_field;
+       uint16_t crc_calc;
+       uint32_t crc_recv;
 
-       crc = sr_crc16(SR_CRC16_DEFAULT_INIT, data, PAC_CRC_POS);
-       crc_field = read_u32le(&data[PAC_CRC_POS]);
-       if (crc != crc_field) {
+       crc_calc = sr_crc16(SR_CRC16_DEFAULT_INIT, data, PAC_CRC_POS);
+       crc_recv = read_u32le(&data[PAC_CRC_POS]);
+       if (crc_calc != crc_recv) {
                sr_spew("CRC error. Calculated: %0x" PRIx16 ", expected: %0x" PRIx32,
-                       crc, crc_field);
+                       crc_calc, crc_recv);
                return FALSE;
        }
 
@@ -103,7 +102,7 @@ static int process_poll_pkt(struct dev_context *devc, uint8_t *dst)
        struct aes256_ctx ctx;
        gboolean ok;
 
-       aes256_set_decrypt_key(&ctx, AES_KEY);
+       aes256_set_decrypt_key(&ctx, aes_key);
        aes256_decrypt(&ctx, TC_POLL_LEN, dst, devc->buf);
 
        ok = TRUE;
@@ -123,21 +122,58 @@ static int process_poll_pkt(struct dev_context *devc, uint8_t *dst)
                return SR_ERR_DATA;
        }
 
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               static const size_t chunk_max = 32;
+
+               const uint8_t *rdptr;
+               size_t rdlen, chunk_addr, chunk_len;
+               GString *txt;
+
+               sr_spew("check passed on decrypted receive data");
+               rdptr = dst;
+               rdlen = TC_POLL_LEN;
+               chunk_addr = 0;
+               while (rdlen) {
+                       chunk_len = rdlen;
+                       if (chunk_len > chunk_max)
+                               chunk_len = chunk_max;
+                       txt = sr_hexdump_new(rdptr, chunk_len);
+                       sr_spew("%04zx  %s", chunk_addr, txt->str);
+                       sr_hexdump_free(txt);
+                       chunk_addr += chunk_len;
+                       rdptr += chunk_len;
+                       rdlen -= chunk_len;
+               }
+       }
+
        return SR_OK;
 }
 
 SR_PRIV int rdtech_tc_probe(struct sr_serial_dev_inst *serial, struct dev_context *devc)
 {
+       static const char *poll_cmd_cdc = "getva";
+       static const char *poll_cmd_ble = "bgetva\r\n";
+
        int len;
        uint8_t poll_pkt[TC_POLL_LEN];
 
-       if (serial_write_blocking(serial, poll_cmd, strlen(poll_cmd),
-                       SERIAL_WRITE_TIMEOUT_MS) < 0) {
+       /* Construct the request text. Which differs across transports. */
+       devc->is_bluetooth = ser_name_is_bt(serial);
+       snprintf(devc->req_text, sizeof(devc->req_text), "%s",
+               devc->is_bluetooth ? poll_cmd_ble : poll_cmd_cdc);
+       sr_dbg("is bluetooth %d -> poll request '%s'.",
+               devc->is_bluetooth, devc->req_text);
+
+       /* Transmit the request. */
+       len = serial_write_blocking(serial,
+               devc->req_text, strlen(devc->req_text), WRITE_TO_MS);
+       if (len < 0) {
                sr_err("Failed to send probe request.");
                return SR_ERR;
        }
 
-       len = serial_read_blocking(serial, devc->buf, TC_POLL_LEN, TC_TIMEOUT_MS);
+       /* Receive a response. */
+       len = serial_read_blocking(serial, devc->buf, TC_POLL_LEN, PROBE_TO_MS);
        if (len != TC_POLL_LEN) {
                sr_err("Failed to read probe response.");
                return SR_ERR;
@@ -149,6 +185,7 @@ SR_PRIV int rdtech_tc_probe(struct sr_serial_dev_inst *serial, struct dev_contex
        }
 
        devc->channels = rdtech_tc_channels;
+       devc->channel_count = ARRAY_SIZE(rdtech_tc_channels);
        devc->dev_info.model_name = g_strndup((const char *)&poll_pkt[OFF_MODEL], LEN_MODEL);
        devc->dev_info.fw_ver = g_strndup((const char *)&poll_pkt[OFF_FW_VER], LEN_FW_VER);
        devc->dev_info.serial_num = read_u32le(&poll_pkt[OFF_SERIAL]);
@@ -156,70 +193,131 @@ SR_PRIV int rdtech_tc_probe(struct sr_serial_dev_inst *serial, struct dev_contex
        return SR_OK;
 }
 
-SR_PRIV int rdtech_tc_poll(const struct sr_dev_inst *sdi)
+SR_PRIV int rdtech_tc_poll(const struct sr_dev_inst *sdi, gboolean force)
 {
        struct dev_context *devc;
+       int64_t now, elapsed;
        struct sr_serial_dev_inst *serial;
+       int len;
+
+       /*
+        * Don't send the request while receive data is being accumulated.
+        * Defer request transmission when a previous request has not yet
+        * seen any response data at all (more probable to happen shortly
+        * after connecting to the peripheral).
+        */
+       devc = sdi->priv;
+       if (!force) {
+               if (devc->rdlen)
+                       return SR_OK;
+               if (!devc->rx_after_tx)
+                       return SR_OK;
+       }
+
+       /*
+        * Send the request when the transmit interval was reached. Or
+        * when the caller forced the transmission.
+        */
+       now = g_get_monotonic_time() / 1000;
+       elapsed = now - devc->cmd_sent_at;
+       if (!force && elapsed < POLL_PERIOD_MS)
+               return SR_OK;
 
+       /*
+        * Transmit another measurement request. Only advance the
+        * interval after successful transmission.
+        */
        serial = sdi->conn;
-       if (serial_write_blocking(serial, poll_cmd, strlen(poll_cmd),
-                       SERIAL_WRITE_TIMEOUT_MS) < 0) {
+       len = serial_write_blocking(serial,
+               devc->req_text, strlen(devc->req_text), WRITE_TO_MS);
+       if (len < 0) {
                sr_err("Unable to send poll request.");
                return SR_ERR;
        }
-
-       devc = sdi->priv;
-       devc->cmd_sent_at = g_get_monotonic_time() / 1000;
+       devc->cmd_sent_at = now;
+       devc->rx_after_tx = 0;
 
        return SR_OK;
 }
 
-static void handle_poll_data(const struct sr_dev_inst *sdi)
+static int handle_poll_data(struct sr_dev_inst *sdi)
 {
        struct dev_context *devc;
        uint8_t poll_pkt[TC_POLL_LEN];
-       size_t i;
-       GSList *ch;
+       size_t ch_idx;
+       const struct rdtech_tc_channel_desc *pch;
+       int ret;
+       float v;
 
        devc = sdi->priv;
-       sr_spew("Received poll packet (len: %zu).", devc->buflen);
-       if (devc->buflen != TC_POLL_LEN) {
-               sr_err("Unexpected poll packet length: %zu", devc->buflen);
-               return;
+       sr_spew("Received poll packet (len: %zu).", devc->rdlen);
+       if (devc->rdlen < TC_POLL_LEN) {
+               sr_err("Insufficient poll packet length: %zu", devc->rdlen);
+               return SR_ERR_DATA;
        }
 
        if (process_poll_pkt(devc, poll_pkt) != SR_OK) {
                sr_err("Failed to process poll packet.");
-               return;
+               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->channels[i], poll_pkt, TC_POLL_LEN);
+       ret = SR_OK;
+       std_session_send_df_frame_begin(sdi);
+       for (ch_idx = 0; ch_idx < devc->channel_count; ch_idx++) {
+               pch = &devc->channels[ch_idx];
+               ret = bv_get_value_len(&v, &pch->spec, poll_pkt, TC_POLL_LEN);
+               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_samples_read(&devc->limits, 1);
+       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 recv_poll_data(struct sr_dev_inst *sdi, struct sr_serial_dev_inst *serial)
 {
        struct dev_context *devc;
+       size_t space;
        int len;
+       int ret;
 
-       /* Serial data arrived. */
+       /* Receive data became available. Drain the transport layer. */
        devc = sdi->priv;
-       while (devc->buflen < TC_POLL_LEN) {
-               len = serial_read_nonblocking(serial, devc->buf + devc->buflen, 1);
-               if (len < 1)
-                       return;
-
-               devc->buflen++;
+       while (devc->rdlen < TC_POLL_LEN) {
+               space = sizeof(devc->buf) - devc->rdlen;
+               len = serial_read_nonblocking(serial,
+                       &devc->buf[devc->rdlen], space);
+               if (len < 0)
+                       return SR_ERR_IO;
+               if (len == 0)
+                       return SR_OK;
+               devc->rdlen += len;
+               devc->rx_after_tx += len;
        }
 
-       if (devc->buflen == TC_POLL_LEN)
-               handle_poll_data(sdi);
+       /*
+        * TODO Want to (re-)synchronize to the packet stream? The
+        * 'pac1' string literal would be a perfect match for that.
+        */
+
+       /* Process packets when their reception has completed. */
+       while (devc->rdlen >= TC_POLL_LEN) {
+               ret = handle_poll_data(sdi);
+               if (ret != SR_OK)
+                       return ret;
+               devc->rdlen -= TC_POLL_LEN;
+               if (devc->rdlen)
+                       memmove(devc->buf, &devc->buf[TC_POLL_LEN], devc->rdlen);
+       }
 
-       devc->buflen = 0;
+       return SR_OK;
 }
 
 SR_PRIV int rdtech_tc_receive_data(int fd, int revents, void *cb_data)
@@ -227,7 +325,7 @@ SR_PRIV int rdtech_tc_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;
 
@@ -236,19 +334,22 @@ SR_PRIV int rdtech_tc_receive_data(int fd, int revents, void *cb_data)
        if (!(devc = sdi->priv))
                return TRUE;
 
+       /* Handle availability of receive data. */
        serial = sdi->conn;
-       if (revents == G_IO_IN)
-               recv_poll_data(sdi, serial);
+       if (revents == G_IO_IN) {
+               ret = recv_poll_data(sdi, serial);
+               if (ret != SR_OK)
+                       sr_dev_acquisition_stop(sdi);
+       }
 
+       /* 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 > TC_POLL_PERIOD_MS)
-               rdtech_tc_poll(sdi);
+       /* Periodically retransmit measurement requests. */
+       (void)rdtech_tc_poll(sdi, FALSE);
 
        return TRUE;
 }