+ struct dev_context *devc;
+ gint64 deadline, delay;
+ struct wait_state *state;
+
+ devc = sdi->priv;
+ state = &devc->wait_state;
+ state->response_count = 0;
+
+ deadline = g_get_monotonic_time();
+ deadline += timeout_ms * 1000;
+ delay = 0;
+ while (1) {
+ gboolean got_wanted;
+ if (g_get_monotonic_time() >= deadline)
+ return SR_ERR_DATA;
+ if (delay)
+ g_usleep(delay);
+ delay = 100;
+ ut181a_handle_events(-1, G_IO_IN, (void *)sdi);
+ got_wanted = FALSE;
+ if (state->want_code && state->got_code)
+ got_wanted = TRUE;
+ if (state->want_data && state->got_data)
+ got_wanted = TRUE;
+ if (state->want_rsp_type && state->got_rsp_type)
+ got_wanted = TRUE;
+ if (state->want_measure && state->got_measure)
+ got_wanted = TRUE;
+ if (state->want_data == CMD_CODE_GET_RECS_COUNT && state->got_rec_count)
+ got_wanted = TRUE;
+ if (state->want_data == CMD_CODE_GET_SAVED_COUNT && state->got_save_count)
+ got_wanted = TRUE;
+ if (state->want_data == CMD_CODE_GET_REC_INFO && state->got_sample_count)
+ got_wanted = TRUE;
+ if (got_wanted)
+ return SR_OK;
+ }
+}
+
+/**
+ * Get measurement value and precision details from protocol's raw bytes.
+ */
+static int ut181a_get_value_params(struct value_params *params, float value, uint8_t prec)
+{
+
+ if (!params)
+ return SR_ERR_ARG;
+
+ memset(params, 0, sizeof(*params));
+ params->value = value;
+ params->digits = (prec >> 4) & 0x0f;
+ params->ol_neg = (prec & (1 << 1)) ? 1 : 0;
+ params->ol_pos = (prec & (1 << 0)) ? 1 : 0;
+
+ return SR_OK;
+}
+
+static void ut181a_cond_stop_acquisition(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+
+ if (!sdi)
+ return;
+ devc = sdi->priv;
+ if (!devc)
+ return;
+
+ if (sdi->status == SR_ST_ACTIVE)
+ sr_dev_acquisition_stop(sdi);
+}
+
+/**
+ * Send meta packet with samplerate to the session feed.
+ *
+ * @param[in] sdi The device instance.
+ * @param[in] interval The sample interval in seconds.
+ *
+ * @returns SR_OK upon success, SR_ERR_* upon error.
+ *
+ * The DMM records data at intervals which are multiples of seconds.
+ * The @ref SR_CONF_SAMPLERATE key cannot express the rate values which
+ * are below 1Hz. Instead the @ref SR_CONF_SAMPLE_INTERVAL key is sent,
+ * which applications may or may not support.
+ */
+static int ut181a_feed_send_rate(struct sr_dev_inst *sdi, int interval)
+{
+#if 1
+ return sr_session_send_meta(sdi,
+ SR_CONF_SAMPLE_INTERVAL, g_variant_new_uint64(interval));
+#else
+ uint64_t rate;
+
+ /*
+ * In theory we know the sample interval, and could provide a
+ * corresponding sample rate. In practice the interval has a
+ * resolution of seconds, which translates to rates below 1Hz,
+ * which we cannot express. So let's keep the routine here for
+ * awareness, and send a rate of 0.
+ */
+ (void)interval;
+ rate = 0;
+
+ return sr_session_send_meta(sdi,
+ SR_CONF_SAMPLERATE, g_variant_new_uint64(rate));
+#endif
+}
+
+/**
+ * Initialize session feed buffer before submission of values.
+ */
+static int ut181a_feedbuff_initialize(struct feed_buffer *buff)
+{
+
+ memset(buff, 0, sizeof(*buff));
+
+ /*
+ * NOTE: The 'digits' fields get updated later from sample data.
+ * As do the MQ and unit fields and the channel list.
+ */
+ memset(&buff->packet, 0, sizeof(buff->packet));
+ sr_analog_init(&buff->analog, &buff->encoding, &buff->meaning, &buff->spec, 0);
+ buff->analog.meaning->mq = 0;
+ buff->analog.meaning->mqflags = 0;
+ buff->analog.meaning->unit = 0;
+ buff->analog.meaning->channels = NULL;
+ buff->analog.encoding->unitsize = sizeof(buff->main_value);
+ buff->analog.encoding->digits = 0;
+ buff->analog.spec->spec_digits = 0;
+ buff->analog.num_samples = 1;
+ buff->analog.data = &buff->main_value;
+ buff->packet.type = SR_DF_ANALOG;
+ buff->packet.payload = &buff->analog;
+
+ return SR_OK;
+}
+
+/**
+ * Setup feed buffer's MQ, MQ flags, and unit before submission of values.
+ */
+static int ut181a_feedbuff_setup_unit(struct feed_buffer *buff, const char *text)
+{
+ int ret;
+ struct mq_scale_params scale;
+
+ /* Derive MQ, flags, unit, and scale from caller's unit text. */
+ ret = ut181a_get_mq_details_from_text(&scale, text);
+ if (ret < 0)
+ return ret;
+ buff->scale = scale.scale;
+ buff->analog.meaning->mq = scale.mq;
+ buff->analog.meaning->mqflags = scale.mqflags;
+ buff->analog.meaning->unit = scale.unit;
+
+ return SR_OK;
+}
+
+/**
+ * Setup feed buffer's measurement value details before submission of values.
+ */
+static int ut181a_feedbuff_setup_value(struct feed_buffer *buff,
+ struct value_params *value)
+{
+
+ if (!buff || !value)
+ return SR_ERR_ARG;
+
+ if (buff->scale) {
+ value->value *= pow(10, buff->scale);
+ value->digits += -buff->scale;
+ }
+ if (value->ol_neg)
+ value->value = -INFINITY;
+ if (value->ol_pos)
+ value->value = +INFINITY;
+
+ buff->main_value = value->value;
+ buff->analog.encoding->digits = value->digits;
+ buff->analog.spec->spec_digits = value->digits;
+
+ return SR_OK;
+}
+
+/**
+ * Setup feed buffer's channel before submission of values.
+ */
+static int ut181a_feedbuff_setup_channel(struct feed_buffer *buff,
+ enum ut181a_channel_idx ch, struct sr_dev_inst *sdi)
+{
+
+ if (!buff || !sdi)
+ return SR_ERR_ARG;
+ if (!buff->analog.meaning)
+ return SR_ERR_ARG;
+
+ g_slist_free(buff->analog.meaning->channels);
+ buff->analog.meaning->channels = g_slist_append(NULL,
+ g_slist_nth_data(sdi->channels, ch));
+
+ return SR_OK;
+}
+
+/**
+ * Send previously configured feed buffer's content to the session.
+ */
+static int ut181a_feedbuff_send_feed(struct feed_buffer *buff,
+ struct sr_dev_inst *sdi, size_t count)
+{
+ int ret;
+ struct dev_context *devc;
+
+ if (!buff || !sdi)
+ return SR_ERR_ARG;
+
+ if (sdi->status != SR_ST_ACTIVE)
+ return SR_OK;
+ devc = sdi->priv;
+ if (!devc || devc->disable_feed)
+ return SR_OK;
+
+ ret = sr_session_send(sdi, &buff->packet);
+ if (ret == SR_OK && count && sdi->priv) {
+ sr_sw_limits_update_samples_read(&devc->limits, count);
+ if (sr_sw_limits_check(&devc->limits))
+ ut181a_cond_stop_acquisition(sdi);
+ }
+
+ return ret;
+}
+
+/**
+ * Release previously allocated resources in the feed buffer.
+ */
+static int ut181a_feedbuff_cleanup(struct feed_buffer *buff)
+{
+ if (!buff)
+ return SR_ERR_ARG;
+
+ if (buff->analog.meaning)
+ g_slist_free(buff->analog.meaning->channels);
+
+ return SR_OK;
+}
+
+static int ut181a_feedbuff_start_frame(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ int ret;
+
+ devc = sdi->priv;
+ if (devc->disable_feed)
+ return SR_OK;
+ if (devc->frame_started)
+ return SR_OK;
+
+ ret = std_session_send_df_frame_begin(sdi);
+ if (ret == SR_OK)
+ devc->frame_started = TRUE;
+
+ return ret;
+}
+
+static int ut181a_feedbuff_count_frame(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ int ret;
+
+ devc = sdi->priv;
+ if (devc->disable_feed)
+ return SR_OK;
+ if (!devc->frame_started)
+ return SR_OK;
+
+ ret = std_session_send_df_frame_end(sdi);
+ if (ret != SR_OK)
+ return ret;
+ devc->frame_started = FALSE;
+
+ sr_sw_limits_update_frames_read(&devc->limits, 1);
+ if (sr_sw_limits_check(&devc->limits))
+ ut181a_cond_stop_acquisition(sdi);
+
+ return SR_OK;
+}
+
+/* Deserializing helpers which also advance the read pointer. */
+
+static int check_len(size_t *got, size_t want)
+{
+
+ if (!got)
+ return SR_ERR_ARG;
+ if (want > *got)
+ return SR_ERR_DATA;
+
+ return SR_OK;
+}
+
+static void advance_len(const uint8_t **p, size_t *l, size_t sz)
+{
+
+ if (p)
+ *p += sz;
+ if (l)
+ *l -= sz;
+}
+
+static int consume_u8(uint8_t *v, const uint8_t **p, size_t *l)
+{
+ size_t sz;
+ int ret;
+
+ if (v)
+ *v = 0;
+
+ sz = sizeof(uint8_t);
+ ret = check_len(l, sz);
+ if (ret != SR_OK)
+ return ret;
+
+ if (v)
+ *v = R8(*p);
+ advance_len(p, l, sz);
+
+ return SR_OK;
+}
+
+static int consume_u16(uint16_t *v, const uint8_t **p, size_t *l)
+{
+ size_t sz;
+ int ret;
+
+ if (v)
+ *v = 0;
+
+ sz = sizeof(uint16_t);
+ ret = check_len(l, sz);
+ if (ret != SR_OK)
+ return ret;
+
+ if (v)
+ *v = RL16(*p);
+ advance_len(p, l, sz);
+
+ return SR_OK;
+}
+
+static int consume_u32(uint32_t *v, const uint8_t **p, size_t *l)
+{
+ size_t sz;
+ int ret;
+
+ if (v)
+ *v = 0;
+
+ sz = sizeof(uint32_t);
+ ret = check_len(l, sz);
+ if (ret != SR_OK)
+ return ret;
+
+ if (v)
+ *v = RL32(*p);
+ advance_len(p, l, sz);
+
+ return SR_OK;
+}
+
+static int consume_flt(float *v, const uint8_t **p, size_t *l)
+{
+ size_t sz;
+ int ret;
+
+ if (v)
+ *v = 0;
+
+ sz = sizeof(float);
+ ret = check_len(l, sz);
+ if (ret != SR_OK)
+ return ret;
+
+ if (v)
+ *v = RLFL(*p);
+ advance_len(p, l, sz);
+
+ return SR_OK;
+}
+
+/*
+ * Fills the caller's text buffer from input data. Also trims and NUL
+ * terminates the buffer content so that callers don't have to.
+ */
+static int consume_str(char *buff, size_t sz, const uint8_t **p, size_t *l)
+{
+ int ret;
+ const char *v;
+
+ if (buff)
+ *buff = '\0';
+
+ ret = check_len(l, sz);
+ if (ret != SR_OK)
+ return ret;
+
+ /*
+ * Quickly grab current position. Immediate bailout if there is
+ * no caller buffer to fill in. Simpilifies the remaining logic.
+ */
+ v = (const char *)*p;
+ advance_len(p, l, sz);
+ if (!buff)
+ return SR_OK;
+
+ /*
+ * Trim leading space off the input text. Then copy the remaining
+ * input data to the caller's buffer. This operation is bounded,
+ * and adds the NUL termination. Then trim trailing space.
+ *
+ * The resulting buffer content is known to be NUL terminated.
+ * It has at most the requested size (modulo the termination).
+ * The content may be empty, which can be acceptable to callers.
+ * So these need to check for and handle that condition.
+ */
+ memset(buff, 0, sz);
+ while (sz && isspace(*v)) {
+ v++;
+ sz--;
+ }
+ if (sz)
+ snprintf(buff, sz, "%s", v);
+ buff[sz] = '\0';
+ sz = strlen(buff);
+ while (sz && isspace(buff[sz - 1])) {
+ buff[--sz] = '\0';
+ }
+
+ return SR_OK;
+}
+
+/* Process a DMM packet (a frame in the serial protocol). */
+static int process_packet(struct sr_dev_inst *sdi, uint8_t *pkt, size_t len)
+{
+ struct dev_context *devc;
+ struct wait_state *state;
+ struct ut181a_info *info;
+ uint16_t got_magic, got_length, got_cs, want_cs;
+ const uint8_t *cs_data, *payload;
+ size_t cs_dlen, pl_dlen;
+ uint8_t rsp_type;
+ enum sr_mqflag add_mqflags;
+ char unit_buff[8], rec_name_buff[11];
+ const char *unit_text, *rec_name;
+ struct feed_buffer feedbuff;
+ struct value_params value;
+ const struct mqopt_item *mqitem;
+ int ret;
+ uint8_t v8; uint16_t v16; uint32_t v32; float vf;
+
+ /*
+ * Cope with different calling contexts. The packet parser can
+ * get invoked outside of data acquisition, during preparation
+ * or in shutdown paths.
+ */
+ devc = sdi ? sdi->priv : NULL;
+ state = devc ? &devc->wait_state : NULL;
+ info = devc ? &devc->info : NULL;
+ if (FRAME_DUMP_FRAME && sr_log_loglevel_get() >= FRAME_DUMP_LEVEL) {
+ GString *spew;
+ spew = sr_hexdump_new(pkt, len);
+ FRAME_DUMP_CALL("RX frame, %zu bytes: %s", len, spew->str);
+ sr_hexdump_free(spew);
+ }
+
+ /*
+ * Check the frame envelope. Redundancy with common reception
+ * logic is perfectly fine. Several code paths end up here, we
+ * need to gracefully deal with incomplete or incorrect data.
+ *
+ * This stage uses random access to arbitrary positions in the
+ * packet which surround the payload. Before the then available
+ * payload gets consumed in a strict serial manner.
+ */
+ if (len < 3 * sizeof(uint16_t)) {
+ /* Need at least magic, length, checksum. */
+ if (FRAME_DUMP_CSUM) {
+ FRAME_DUMP_CALL("Insufficient frame data, need %zu, got %zu.",
+ 3 * sizeof(uint16_t), len);
+ }
+ return SR_ERR_DATA;
+ }
+
+ got_magic = RL16(&pkt[0]);
+ if (got_magic != FRAME_MAGIC) {
+ if (FRAME_DUMP_CSUM) {
+ FRAME_DUMP_CALL("Frame magic mismatch, want 0x%04x, got 0x%04x.",
+ (unsigned int)FRAME_MAGIC, (unsigned int)got_magic);
+ }
+ return SR_ERR_DATA;
+ }
+
+ got_length = RL16(&pkt[sizeof(uint16_t)]);
+ if (got_length != len - 2 * sizeof(uint16_t)) {
+ if (FRAME_DUMP_CSUM) {
+ FRAME_DUMP_CALL("Frame length mismatch, want %zu, got %u.",
+ len - 2 * sizeof(uint16_t), got_length);
+ }
+ return SR_ERR_DATA;
+ }
+
+ payload = &pkt[2 * sizeof(uint16_t)];
+ pl_dlen = got_length - sizeof(uint16_t);
+
+ cs_data = &pkt[sizeof(uint16_t)];
+ cs_dlen = len - 2 * sizeof(uint16_t);
+ want_cs = ut181a_checksum(cs_data, cs_dlen);
+ got_cs = RL16(&pkt[len - sizeof(uint16_t)]);
+ if (got_cs != want_cs) {
+ if (FRAME_DUMP_CSUM) {
+ FRAME_DUMP_CALL("Frame checksum mismatch, want 0x%04x, got 0x%04x.",
+ (unsigned int)want_cs, (unsigned int)got_cs);
+ }
+ return SR_ERR_DATA;
+ }
+ if (state)
+ state->response_count++;
+ if (FRAME_DUMP_BYTES && sr_log_loglevel_get() >= FRAME_DUMP_LEVEL) {
+ GString *spew;
+ spew = sr_hexdump_new(payload, pl_dlen);
+ FRAME_DUMP_CALL("RX payload, %zu bytes: %s", pl_dlen, spew->str);
+ sr_hexdump_free(spew);
+ }
+
+ /*
+ * Interpret the frame's payload data. The first byte contains
+ * a packet type which specifies how to interpret the remainder.
+ */
+ ret = consume_u8(&v8, &payload, &pl_dlen);
+ if (ret != SR_OK) {
+ sr_err("Insufficient payload data, need packet type.");
+ return ret;
+ }
+ rsp_type = v8;
+ if (info)
+ info->rsp_head.rsp_type = rsp_type;
+
+ add_mqflags = 0;
+ switch (rsp_type) {
+ case RSP_TYPE_REPLY_CODE:
+ /*
+ * Reply code: One 16bit item with either 'OK' or 'ER'
+ * "string literals" to communicate boolean state.
+ */
+ ret = consume_u16(&v16, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ if (info) {
+ info->reply_code.code = v16;
+ info->reply_code.ok = v16 == REPLY_CODE_OK;
+ }
+ if (state && state->want_code) {
+ state->got_code = TRUE;
+ state->code_ok = v16 == REPLY_CODE_OK;
+ }
+ break;
+ case RSP_TYPE_SAVE:
+ /*
+ * Saved measurement: A 32bit timestamp, followed by a
+ * measurement (FALLTHROUGH).
+ */
+ ret = consume_u32(&v32, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ if (info)
+ info->save_time.stamp = v32;
+ v32 = ut181a_get_epoch_for_timestamp(v32);
+ if (info)
+ info->save_time.epoch = v32;
+
+#if UT181A_WITH_TIMESTAMP
+ if (devc) {
+ ret = ut181a_feedbuff_start_frame(sdi);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ ret = SR_OK;
+ ret |= ut181a_feedbuff_initialize(&feedbuff);
+ ret |= ut181a_feedbuff_setup_channel(&feedbuff, UT181A_CH_TIME, sdi);
+ ret |= ut181a_feedbuff_setup_unit(&feedbuff, "timestamp");
+ ret |= ut181a_get_value_params(&value, v32, 0x00);
+ ret |= ut181a_feedbuff_setup_value(&feedbuff, &value);
+ ret |= ut181a_feedbuff_send_feed(&feedbuff, sdi, 0);
+ ret |= ut181a_feedbuff_cleanup(&feedbuff);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ }
+#endif
+ if (info)
+ info->save_info.save_idx++;
+
+ /* FALLTHROUGH */
+ case RSP_TYPE_MEASUREMENT:
+ /*
+ * A measurement. Starts with a common header, which
+ * specifies the layout of the remainder (variants, with
+ * optional fields, depending on preceeding fields).
+ *
+ * Only useful to process when 'info' (and thus 'devc')
+ * are available.
+ */
+ if (!info)
+ return SR_ERR_NA;
+
+ /*
+ * Get the header fields (misc1, misc2, mode, and range),
+ * derive local packet type details and flags from them.
+ */
+ ret = consume_u8(&v8, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_head.misc1 = v8;
+ info->meas_head.has_hold = (v8 & 0x80) ? 1 : 0;
+ info->meas_head.is_type = (v8 & 0x70) >> 4;
+ info->meas_head.is_norm = (info->meas_head.is_type == 0) ? 1 : 0;
+ info->meas_head.is_rel = (info->meas_head.is_type == 1) ? 1 : 0;
+ info->meas_head.is_minmax = (info->meas_head.is_type == 2) ? 1 : 0;
+ info->meas_head.is_peak = (info->meas_head.is_type == 4) ? 1 : 0;
+ info->meas_head.has_bar = (v8 & 0x8) ? 1 : 0;
+ info->meas_head.has_aux2 = (v8 & 0x4) ? 1 : 0;
+ info->meas_head.has_aux1 = (v8 & 0x2) ? 1 : 0;
+
+ ret = consume_u8(&v8, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_head.misc2 = v8;
+ info->meas_head.is_rec = (v8 & 0x20) ? 1 : 0;
+ if (devc)
+ devc->is_recording = info->meas_head.is_rec;
+ info->meas_head.is_comp = (v8 & 0x10) ? 1 : 0;
+ info->meas_head.has_lead_err = (v8 & 0x8) ? 1 : 0;
+ info->meas_head.has_high_volt = (v8 & 0x2) ? 1 : 0;
+ info->meas_head.is_auto_range = (v8 & 0x1) ? 1 : 0;
+
+ ret = consume_u16(&v16, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_head.mode = v16;
+ mqitem = ut181a_get_mqitem_from_mode(v16);
+ if (!mqitem || !mqitem->mq)
+ return SR_ERR_DATA;
+ add_mqflags |= mqitem->mqflags;
+ if (info->meas_head.has_hold)
+ add_mqflags |= SR_MQFLAG_HOLD;
+ if (info->meas_head.is_auto_range)
+ add_mqflags |= SR_MQFLAG_AUTORANGE;
+ if (add_mqflags & SR_MQFLAG_DIODE)
+ add_mqflags |= SR_MQFLAG_DC;
+
+ ret = consume_u8(&v8, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_head.range = v8;
+
+ if (state && state->want_measure)
+ state->got_measure = TRUE;
+
+ ret = ut181a_feedbuff_start_frame(sdi);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+
+ /*
+ * The remaining measurement's layout depends on type.
+ * - Normal measurement:
+ * - Main value (4/1/8 value/precision/unit).
+ * - Aux1 value (4/1/8 value/precision/unit) when AUX1
+ * flag active.
+ * - Aux2 value (4/1/8 value/precision/unit) when AUX2
+ * flag active.
+ * - Bargraph (4/8 value/unit) when BAR flag active.
+ * - COMP result when COMP flag active.
+ * - Always 1/1/1/4 mode/flags/digits/limit: type
+ * of check, PASS/FAIL verdict, limit values'
+ * precision, upper or only limit.
+ * - Conditional 4 limit: Lower limit for checks
+ * which involve two limit values.
+ * - Relative measurement:
+ * - Relative value (4/1/8 value/precision/unit).
+ * - Reference value (4/1/8 value/precision/unit),
+ * when AUX1 active (practically always).
+ * - Absolute value (4/1/8 value/precision/unit),
+ * when AUX2 active (practically always).
+ * - Bargraph (4/8 value/unit) when BAR flag active.
+ * - Min/Max measurement:
+ * - All fields always present, no conditions.
+ * - One common unit spec at the end which applies to
+ * all curr/max/avg/min values.
+ * - Current value (4/1 value/precision).
+ * - Maximum value (4/1/4 value/precision/time).
+ * - Average value (4/1/4 value/precision/time).
+ * - Minimum value (4/1/4 value/precision/time).
+ * - Common unit text (8).
+ * - Peak measurement:
+ * - All fields always present.
+ * - Maximum value (4/1/8 value/precision/unit).
+ * - Minimum value (4/1/8 value/precision/unit).
+ */
+ ret = ut181a_feedbuff_initialize(&feedbuff);
+ if (info->meas_head.is_norm) {
+ /* Main value, unconditional. Get details. */
+ ret = consume_flt(&vf, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.norm.main_value = vf;
+ ret = consume_u8(&v8, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.norm.main_prec = v8;
+ ret = consume_str(&unit_buff[0], 8, &payload, &pl_dlen);
+ unit_text = &unit_buff[0];
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ snprintf(info->meas_data.norm.main_unit,
+ sizeof(info->meas_data.norm.main_unit),
+ "%s", unit_text);
+ unit_text = info->meas_data.norm.main_unit;
+
+ /* Submit main value to session feed. */
+ ret = SR_OK;
+ ret |= ut181a_feedbuff_setup_channel(&feedbuff, UT181A_CH_MAIN, sdi);
+ ret |= ut181a_feedbuff_setup_unit(&feedbuff, unit_text);
+ feedbuff.analog.meaning->mqflags |= add_mqflags;
+ ret |= ut181a_get_value_params(&value, vf, v8);
+ ret |= ut181a_feedbuff_setup_value(&feedbuff, &value);
+ ret |= ut181a_feedbuff_send_feed(&feedbuff, sdi, 1);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ }
+ if (info->meas_head.is_norm && info->meas_head.has_aux1) {
+ /* Aux1 value, optional. Get details. */
+ ret = consume_flt(&vf, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.norm.aux1_value = vf;
+ ret = consume_u8(&v8, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.norm.aux1_prec = v8;
+ ret = consume_str(&unit_buff[0], 8, &payload, &pl_dlen);
+ unit_text = &unit_buff[0];
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ snprintf(info->meas_data.norm.aux1_unit,
+ sizeof(info->meas_data.norm.aux1_unit),
+ "%s", unit_text);
+ unit_text = info->meas_data.norm.aux1_unit;
+
+ /* Submit aux1 value to session feed. */
+ ret = SR_OK;
+ ret |= ut181a_feedbuff_setup_channel(&feedbuff, UT181A_CH_AUX1, sdi);
+ ret |= ut181a_feedbuff_setup_unit(&feedbuff, unit_text);
+ ret |= ut181a_get_value_params(&value, vf, v8);
+ ret |= ut181a_feedbuff_setup_value(&feedbuff, &value);
+ ret |= ut181a_feedbuff_send_feed(&feedbuff, sdi, 0);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ }
+ if (info->meas_head.is_norm && info->meas_head.has_aux2) {
+ /* Aux2 value, optional. Get details. */
+ ret = consume_flt(&vf, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.norm.aux2_value = vf;
+ ret = consume_u8(&v8, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.norm.aux2_prec = v8;
+ ret = consume_str(&unit_buff[0], 8, &payload, &pl_dlen);
+ unit_text = &unit_buff[0];
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ snprintf(info->meas_data.norm.aux2_unit,
+ sizeof(info->meas_data.norm.aux2_unit),
+ "%s", unit_text);
+ unit_text = info->meas_data.norm.aux2_unit;
+
+ /* Submit aux2 value to session feed. */
+ ret = SR_OK;
+ ret |= ut181a_feedbuff_setup_channel(&feedbuff, UT181A_CH_AUX2, sdi);
+ ret |= ut181a_feedbuff_setup_unit(&feedbuff, unit_text);
+ ret |= ut181a_get_value_params(&value, vf, v8);
+ ret |= ut181a_feedbuff_setup_value(&feedbuff, &value);
+ ret |= ut181a_feedbuff_send_feed(&feedbuff, sdi, 0);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ }
+ if (info->meas_head.is_norm && info->meas_head.has_bar) {
+ /* Bargraph value, optional. */
+ ret = consume_flt(&vf, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.norm.bar_value = vf;
+ ret = consume_str(&unit_buff[0], 8, &payload, &pl_dlen);
+ unit_text = &unit_buff[0];
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ snprintf(info->meas_data.norm.bar_unit,
+ sizeof(info->meas_data.norm.bar_unit),
+ "%s", unit_text);
+ unit_text = info->meas_data.norm.bar_unit;
+
+ /* Submit bargraph value to session feed. */
+ ret = 0;
+ ret |= ut181a_feedbuff_setup_channel(&feedbuff, UT181A_CH_BAR, sdi);
+ ret |= ut181a_feedbuff_setup_unit(&feedbuff, unit_text);
+ ret |= ut181a_get_value_params(&value, vf, 0x00);
+ ret |= ut181a_feedbuff_setup_value(&feedbuff, &value);
+ ret |= ut181a_feedbuff_send_feed(&feedbuff, sdi, 0);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ }
+ if (info->meas_head.is_norm && info->meas_head.is_comp) {
+ /* COMP result, optional. Get details. */
+ ret = consume_u8(&v8, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ if (v8 > COMP_MODE_ABOVE)
+ return SR_ERR_DATA;
+ info->meas_data.comp.mode = v8;
+ ret = consume_u8(&v8, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.comp.fail = v8 ? TRUE : FALSE;
+ ret = consume_u8(&v8, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.comp.digits = v8 & 0x0f;
+ ret = consume_flt(&vf, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.comp.limit_high = vf;
+ if (info->meas_data.comp.mode <= COMP_MODE_OUTER) {
+ ret = consume_flt(&vf, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.comp.limit_low = vf;
+ }
+
+ /* TODO
+ * How to present this result to the feed? This
+ * implementation extracts and interprets the
+ * fields, but does not pass the values to the
+ * session. Which MQ to use for PASS/FAIL checks?
+ */
+ static const char *mode_text[] = {
+ [COMP_MODE_INNER] = "INNER",
+ [COMP_MODE_OUTER] = "OUTER",
+ [COMP_MODE_BELOW] = "BELOW",
+ [COMP_MODE_ABOVE] = "ABOVE",
+ };
+
+ if (info->meas_data.comp.mode <= COMP_MODE_OUTER) {
+ sr_dbg("Unprocessed COMP result:"
+ " mode %s, %s, digits %d, low %f, high %f",
+ mode_text[info->meas_data.comp.mode],
+ info->meas_data.comp.fail ? "FAIL" : "PASS",
+ info->meas_data.comp.digits,
+ info->meas_data.comp.limit_low,
+ info->meas_data.comp.limit_high);
+ } else {
+ sr_dbg("Unprocessed COMP result:"
+ " mode %s, %s, digits %d, limit %f",
+ mode_text[info->meas_data.comp.mode],
+ info->meas_data.comp.fail ? "FAIL" : "PASS",
+ info->meas_data.comp.digits,
+ info->meas_data.comp.limit_high);
+ }
+ }
+ if (info->meas_head.is_norm) {
+ /* Normal measurement code path done. */
+ ret = ut181a_feedbuff_cleanup(&feedbuff);
+ ret = ut181a_feedbuff_count_frame(sdi);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ break;
+ }
+
+ if (info->meas_head.is_rel) {
+ /* Relative value, unconditional. Get details. */
+ ret = consume_flt(&vf, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.rel.rel_value = vf;
+ ret = consume_u8(&v8, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.rel.rel_prec = v8;
+ ret = consume_str(&unit_buff[0], 8, &payload, &pl_dlen);
+ unit_text = &unit_buff[0];
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ snprintf(info->meas_data.rel.rel_unit,
+ sizeof(info->meas_data.rel.rel_unit),
+ "%s", unit_text);
+ unit_text = info->meas_data.rel.rel_unit;
+
+ /* Submit relative value to session feed. */
+ ret = SR_OK;
+ ret |= ut181a_feedbuff_setup_channel(&feedbuff, UT181A_CH_MAIN, sdi);
+ ret |= ut181a_feedbuff_setup_unit(&feedbuff, unit_text);
+ feedbuff.analog.meaning->mqflags |= add_mqflags;
+ feedbuff.analog.meaning->mqflags |= SR_MQFLAG_RELATIVE;
+ ret |= ut181a_get_value_params(&value, vf, v8);
+ ret |= ut181a_feedbuff_setup_value(&feedbuff, &value);
+ ret |= ut181a_feedbuff_send_feed(&feedbuff, sdi, 1);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ }
+ if (info->meas_head.is_rel && info->meas_head.has_aux1) {
+ /* Reference value, "conditional" in theory. */
+ ret = consume_flt(&vf, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.rel.ref_value = vf;
+ ret = consume_u8(&v8, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.rel.ref_prec = v8;
+ ret = consume_str(&unit_buff[0], 8, &payload, &pl_dlen);
+ unit_text = &unit_buff[0];
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ snprintf(info->meas_data.rel.ref_unit,
+ sizeof(info->meas_data.rel.ref_unit),
+ "%s", unit_text);
+ unit_text = info->meas_data.rel.ref_unit;
+
+ /* Submit reference value to session feed. */
+ ret = SR_OK;
+ ret |= ut181a_feedbuff_setup_channel(&feedbuff, UT181A_CH_AUX1, sdi);
+ ret |= ut181a_feedbuff_setup_unit(&feedbuff, unit_text);
+ feedbuff.analog.meaning->mqflags |= SR_MQFLAG_REFERENCE;
+ ret |= ut181a_get_value_params(&value, vf, v8);
+ ret |= ut181a_feedbuff_setup_value(&feedbuff, &value);
+ ret |= ut181a_feedbuff_send_feed(&feedbuff, sdi, 0);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ }
+ if (info->meas_head.is_rel && info->meas_head.has_aux2) {
+ /* Absolute value, "conditional" in theory. */
+ ret = consume_flt(&vf, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.rel.abs_value = vf;
+ ret = consume_u8(&v8, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.rel.abs_prec = v8;
+ ret = consume_str(&unit_buff[0], 8, &payload, &pl_dlen);
+ unit_text = &unit_buff[0];
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ snprintf(info->meas_data.rel.abs_unit,
+ sizeof(info->meas_data.rel.abs_unit),
+ "%s", unit_text);
+ unit_text = info->meas_data.rel.abs_unit;
+
+ /* Submit absolute value to session feed. */
+ ret = SR_OK;
+ ret |= ut181a_feedbuff_setup_channel(&feedbuff, UT181A_CH_AUX2, sdi);
+ ret |= ut181a_feedbuff_setup_unit(&feedbuff, unit_text);
+ ret |= ut181a_get_value_params(&value, vf, v8);
+ ret |= ut181a_feedbuff_setup_value(&feedbuff, &value);
+ ret |= ut181a_feedbuff_send_feed(&feedbuff, sdi, 0);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ }
+ if (info->meas_head.is_rel && info->meas_head.has_bar) {
+ /* Bargraph value, conditional. */
+ ret = consume_flt(&vf, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.rel.bar_value = vf;
+ ret = consume_str(&unit_buff[0], 8, &payload, &pl_dlen);
+ unit_text = &unit_buff[0];
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ snprintf(info->meas_data.rel.bar_unit,
+ sizeof(info->meas_data.rel.bar_unit),
+ "%s", unit_text);
+ unit_text = info->meas_data.rel.bar_unit;
+
+ /* Submit bargraph value to session feed. */
+ ret = SR_OK;
+ ret |= ut181a_feedbuff_setup_channel(&feedbuff, UT181A_CH_BAR, sdi);
+ ret |= ut181a_feedbuff_setup_unit(&feedbuff, unit_text);
+ ret |= ut181a_get_value_params(&value, vf, 0x00);
+ ret |= ut181a_feedbuff_setup_value(&feedbuff, &value);
+ ret |= ut181a_feedbuff_send_feed(&feedbuff, sdi, 0);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ }
+ if (info->meas_head.is_rel) {
+ /* Relative measurement code path done. */
+ ret = ut181a_feedbuff_cleanup(&feedbuff);
+ ret = ut181a_feedbuff_count_frame(sdi);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ break;
+ }
+
+ if (info->meas_head.is_minmax) {
+ /*
+ * Min/max measurement values, none of them are
+ * conditional in practice (all are present).
+ * This is special in that all of curr, max, avg,
+ * and min values share the same unit text which
+ * is only at the end of the data fields.
+ */
+ ret = SR_OK;
+ ret |= consume_flt(&info->meas_data.minmax.curr_value, &payload, &pl_dlen);
+ ret |= consume_u8(&info->meas_data.minmax.curr_prec, &payload, &pl_dlen);
+ ret |= consume_flt(&info->meas_data.minmax.max_value, &payload, &pl_dlen);
+ ret |= consume_u8(&info->meas_data.minmax.max_prec, &payload, &pl_dlen);
+ ret |= consume_u32(&info->meas_data.minmax.max_stamp, &payload, &pl_dlen);
+ ret |= consume_flt(&info->meas_data.minmax.avg_value, &payload, &pl_dlen);
+ ret |= consume_u8(&info->meas_data.minmax.avg_prec, &payload, &pl_dlen);
+ ret |= consume_u32(&info->meas_data.minmax.avg_stamp, &payload, &pl_dlen);
+ ret |= consume_flt(&info->meas_data.minmax.min_value, &payload, &pl_dlen);
+ ret |= consume_u8(&info->meas_data.minmax.min_prec, &payload, &pl_dlen);
+ ret |= consume_u32(&info->meas_data.minmax.min_stamp, &payload, &pl_dlen);
+ ret |= consume_str(&unit_buff[0], 8, &payload, &pl_dlen);
+ unit_text = &unit_buff[0];
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ snprintf(info->meas_data.minmax.all_unit,
+ sizeof(info->meas_data.minmax.all_unit),
+ "%s", unit_text);
+ unit_text = info->meas_data.minmax.all_unit;
+
+ /* Submit the current value. */
+ vf = info->meas_data.minmax.curr_value;
+ v8 = info->meas_data.minmax.curr_prec;
+ ret = SR_OK;
+ ret |= ut181a_feedbuff_setup_channel(&feedbuff, UT181A_CH_MAIN, sdi);
+ ret |= ut181a_feedbuff_setup_unit(&feedbuff, unit_text);
+ feedbuff.analog.meaning->mqflags |= add_mqflags;
+ ret |= ut181a_get_value_params(&value, vf, v8);
+ ret |= ut181a_feedbuff_setup_value(&feedbuff, &value);
+ ret |= ut181a_feedbuff_send_feed(&feedbuff, sdi, 1);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+
+ /* Submit the maximum value. */
+ vf = info->meas_data.minmax.max_value;
+ v8 = info->meas_data.minmax.max_prec;
+ ret = SR_OK;
+ ret |= ut181a_feedbuff_setup_channel(&feedbuff, UT181A_CH_AUX1, sdi);
+ ret |= ut181a_feedbuff_setup_unit(&feedbuff, unit_text);
+ feedbuff.analog.meaning->mqflags |= SR_MQFLAG_MAX;
+ ret |= ut181a_get_value_params(&value, vf, v8);
+ ret |= ut181a_feedbuff_setup_value(&feedbuff, &value);
+ ret |= ut181a_feedbuff_send_feed(&feedbuff, sdi, 0);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+
+ /* Submit the average value. */
+ vf = info->meas_data.minmax.avg_value;
+ v8 = info->meas_data.minmax.avg_prec;
+ ret = SR_OK;
+ ret |= ut181a_feedbuff_setup_channel(&feedbuff, UT181A_CH_AUX2, sdi);
+ ret |= ut181a_feedbuff_setup_unit(&feedbuff, unit_text);
+ feedbuff.analog.meaning->mqflags |= SR_MQFLAG_AVG;
+ ret |= ut181a_get_value_params(&value, vf, v8);
+ ret |= ut181a_feedbuff_setup_value(&feedbuff, &value);
+ ret |= ut181a_feedbuff_send_feed(&feedbuff, sdi, 0);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+
+ /* Submit the minimum value. */
+ vf = info->meas_data.minmax.min_value;
+ v8 = info->meas_data.minmax.min_prec;
+ ret = SR_OK;
+ ret |= ut181a_feedbuff_setup_channel(&feedbuff, UT181A_CH_AUX3, sdi);
+ ret |= ut181a_feedbuff_setup_unit(&feedbuff, unit_text);
+ feedbuff.analog.meaning->mqflags |= SR_MQFLAG_MIN;
+ ret |= ut181a_get_value_params(&value, vf, v8);
+ ret |= ut181a_feedbuff_setup_value(&feedbuff, &value);
+ ret |= ut181a_feedbuff_send_feed(&feedbuff, sdi, 0);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ }
+ if (info->meas_head.is_minmax) {
+ /* Min/max measurement code path done. */
+ ret = ut181a_feedbuff_cleanup(&feedbuff);
+ ret = ut181a_feedbuff_count_frame(sdi);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ break;
+ }
+
+ if (info->meas_head.is_peak) {
+ /* Maximum value, unconditional. Get details. */
+ ret = consume_flt(&vf, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.peak.max_value = vf;
+ ret = consume_u8(&v8, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.peak.max_prec = v8;
+ ret = consume_str(&unit_buff[0], 8, &payload, &pl_dlen);
+ unit_text = &unit_buff[0];
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ snprintf(info->meas_data.peak.max_unit,
+ sizeof(info->meas_data.peak.max_unit),
+ "%s", unit_text);
+ unit_text = info->meas_data.peak.max_unit;
+
+ /* Submit max value to session feed. */
+ ret = SR_OK;
+ ret |= ut181a_feedbuff_setup_channel(&feedbuff, UT181A_CH_AUX1, sdi);
+ ret |= ut181a_feedbuff_setup_unit(&feedbuff, unit_text);
+ feedbuff.analog.meaning->mqflags |= add_mqflags; /* ??? */
+ feedbuff.analog.meaning->mqflags |= SR_MQFLAG_MAX;
+ ret |= ut181a_get_value_params(&value, vf, v8);
+ ret |= ut181a_feedbuff_setup_value(&feedbuff, &value);
+ ret |= ut181a_feedbuff_send_feed(&feedbuff, sdi, 1);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+
+ /* Minimum value, unconditional. Get details. */
+ ret = consume_flt(&vf, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.peak.min_value = vf;
+ ret = consume_u8(&v8, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->meas_data.peak.min_prec = v8;
+ ret = consume_str(&unit_buff[0], 8, &payload, &pl_dlen);
+ unit_text = &unit_buff[0];
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ snprintf(info->meas_data.peak.min_unit,
+ sizeof(info->meas_data.peak.min_unit),
+ "%s", unit_text);
+ unit_text = info->meas_data.peak.min_unit;
+
+ /* Submit min value to session feed. */
+ ret = SR_OK;
+ ret |= ut181a_feedbuff_setup_channel(&feedbuff, UT181A_CH_AUX3, sdi);
+ ret |= ut181a_feedbuff_setup_unit(&feedbuff, unit_text);
+ feedbuff.analog.meaning->mqflags |= SR_MQFLAG_MIN;
+ ret |= ut181a_get_value_params(&value, vf, v8);
+ ret |= ut181a_feedbuff_setup_value(&feedbuff, &value);
+ ret |= ut181a_feedbuff_send_feed(&feedbuff, sdi, 0);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ }
+ if (info->meas_head.is_peak) {
+ /* Relative measurement code path done. */
+ ret = ut181a_feedbuff_cleanup(&feedbuff);
+ ret = ut181a_feedbuff_count_frame(sdi);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ break;
+ }
+
+ /* ShouldNeverHappen(TM) */
+ sr_dbg("Unhandled measurement type.");
+ return SR_ERR_DATA;
+
+ case RSP_TYPE_REC_INFO:
+ /*
+ * Not useful to process without 'devc' or 'info'.
+ * The caller provided the recording's index (the
+ * protocol won't in the response).
+ */
+ if (!devc || !info)
+ return SR_ERR_ARG;
+
+ /*
+ * Record information:
+ * - User specified recording's name (11 ASCIIZ chars).
+ * - Unit text (8).
+ * - Interval, duration, sample count (2/4/4).
+ * - Max/avg/min values and precision (4+1/4+1/4+1).
+ * - Time when recording started (4).
+ *
+ * Notice that the recording name needs to get trimmed
+ * due to limited text editing capabilities of the DMM
+ * UI. The name need not be unique, and typically isn't
+ * (again: because of limited editing, potential numbers
+ * in names are not auto incremented in the firmware).
+ */
+ ret = consume_str(&rec_name_buff[0], 11, &payload, &pl_dlen);
+ rec_name = &rec_name_buff[0];
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ if (!*rec_name)
+ return SR_ERR_DATA;
+ snprintf(devc->record_names[info->rec_info.rec_idx],
+ sizeof(devc->record_names[info->rec_info.rec_idx]),
+ "%s", rec_name);
+ snprintf(info->rec_info.name, sizeof(info->rec_info.name),
+ "%s", rec_name);
+ ret = consume_str(&unit_buff[0], 8, &payload, &pl_dlen);
+ unit_text = &unit_buff[0];
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ snprintf(info->rec_info.unit,
+ sizeof(info->rec_info.unit),
+ "%s", unit_text);
+ unit_text = info->rec_info.unit;
+ ret = SR_OK;
+ ret |= consume_u16(&info->rec_info.interval, &payload, &pl_dlen);
+ ret |= consume_u32(&info->rec_info.duration, &payload, &pl_dlen);
+ ret |= consume_u32(&info->rec_info.samples, &payload, &pl_dlen);
+ ret |= consume_flt(&info->rec_info.max_value, &payload, &pl_dlen);
+ ret |= consume_u8(&info->rec_info.max_prec, &payload, &pl_dlen);
+ ret |= consume_flt(&info->rec_info.avg_value, &payload, &pl_dlen);
+ ret |= consume_u8(&info->rec_info.avg_prec, &payload, &pl_dlen);
+ ret |= consume_flt(&info->rec_info.min_value, &payload, &pl_dlen);
+ ret |= consume_u8(&info->rec_info.min_prec, &payload, &pl_dlen);
+ ret |= consume_u32(&v32, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->rec_info.start_stamp = ut181a_get_epoch_for_timestamp(v32);
+
+ /*
+ * Cheat, provide sample count as if it was reply data.
+ * Some api.c code paths assume to find this detail there.
+ * Keep the last unit text at hand, subsequent reception
+ * of record data will reference it.
+ */
+ if (state && state->want_data == CMD_CODE_GET_REC_INFO) {
+ state->got_sample_count = TRUE;
+ state->data_value = info->rec_info.samples;
+ }
+ snprintf(devc->last_data.unit_text,
+ sizeof(devc->last_data.unit_text),
+ "%s", unit_text);
+
+ /*
+ * Optionally automatically forward the sample interval
+ * to the session feed, before record data is sent.
+ */
+ if (devc->info.rec_info.auto_feed) {
+ ret = ut181a_feed_send_rate(sdi, info->rec_info.interval);
+ }
+
+ break;
+
+ case RSP_TYPE_REC_DATA:
+ /*
+ * We expect record data only during acquisitions from
+ * that data source, and depend on being able to feed
+ * data to the session.
+ */
+ if (sdi->status != SR_ST_ACTIVE)
+ break;
+ if (!devc || devc->disable_feed || !info)
+ break;
+ ret = ut181a_feedbuff_initialize(&feedbuff);
+ ret = ut181a_feedbuff_setup_channel(&feedbuff, UT181A_CH_MAIN, sdi);
+ ret = ut181a_feedbuff_setup_unit(&feedbuff, devc->last_data.unit_text);
+
+ /*
+ * Record data:
+ * - u8 sample count for this data chunk, then the
+ * corresponding number of samples, each is 9 bytes:
+ * - f32 value
+ * - u8 precision
+ * - u32 timestamp
+ */
+ ret = consume_u8(&info->rec_data.samples_chunk, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ info->rec_data.samples_curr += info->rec_data.samples_chunk;
+ while (info->rec_data.samples_chunk--) {
+ /*
+ * Implementation detail: Consume all received
+ * data, yet skip processing when a limit was
+ * reached and previously terminated acquisition.
+ */
+ ret = SR_OK;
+ ret |= consume_flt(&vf, &payload, &pl_dlen);
+ ret |= consume_u8(&v8, &payload, &pl_dlen);
+ ret |= consume_u32(&v32, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+
+ if (sdi->status != SR_ST_ACTIVE)
+ continue;
+
+ ret = ut181a_feedbuff_start_frame(sdi);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+
+ ret = SR_OK;
+ ret |= ut181a_get_value_params(&value, vf, v8);
+ ret |= ut181a_feedbuff_setup_value(&feedbuff, &value);
+ ret |= ut181a_feedbuff_send_feed(&feedbuff, sdi, 1);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+
+ ret = ut181a_feedbuff_count_frame(sdi);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ }
+ ret = ut181a_feedbuff_cleanup(&feedbuff);
+ break;
+
+ case RSP_TYPE_REPLY_DATA:
+ /*
+ * Reply data. Generic 16bit value preceeded by 8bit
+ * request code.
+ */
+ ret = SR_OK;
+ ret |= consume_u8(&v8, &payload, &pl_dlen);
+ ret |= consume_u16(&v16, &payload, &pl_dlen);
+ if (ret != SR_OK)
+ return SR_ERR_DATA;
+ if (info) {
+ info->reply_data.code = v8;
+ info->reply_data.data = v16;
+ }
+ if (state && state->want_data && state->want_data == v8) {
+ state->got_data = TRUE;
+ state->data_value = v16;
+ if (v8 == CMD_CODE_GET_RECS_COUNT)
+ state->got_rec_count = TRUE;
+ if (v8 == CMD_CODE_GET_SAVED_COUNT)
+ state->got_save_count = TRUE;
+ if (v8 == CMD_CODE_GET_REC_INFO)
+ state->got_sample_count = TRUE;
+ }
+ break;
+
+ default:
+ if (FRAME_DUMP_PARSE)
+ FRAME_DUMP_CALL("Unhandled response type 0x%02x", rsp_type);
+ return SR_ERR_NA;
+ }
+ if (state && state->want_rsp_type == rsp_type)
+ state->got_rsp_type = TRUE;
+ if (FRAME_DUMP_REMAIN && pl_dlen) {
+ GString *txt;
+ txt = sr_hexdump_new(payload, pl_dlen);
+ FRAME_DUMP_CALL("Unprocessed response data: %s", txt->str);
+ sr_hexdump_free(txt);
+ }
+
+ /* Unconditionally check, we may have hit a time limit. */
+ if (sr_sw_limits_check(&devc->limits)) {
+ ut181a_cond_stop_acquisition(sdi);
+ return SR_OK;
+ }
+
+ /*
+ * Only emit next requests for chunked downloads after successful
+ * reception and consumption of the currently received item(s).
+ */
+ if (devc) {
+ struct sr_serial_dev_inst *serial;
+ serial = sdi->conn;
+
+ switch (rsp_type) {
+ case RSP_TYPE_SAVE:
+ if (!info)
+ break;
+ /* Sample count was incremented during reception above. */
+ if (info->save_info.save_idx >= info->save_info.save_count) {
+ ut181a_cond_stop_acquisition(sdi);
+ break;
+ }
+ ret = ut181a_send_cmd_get_saved_value(serial, info->save_info.save_idx);
+ if (ret < 0)
+ ut181a_cond_stop_acquisition(sdi);
+ break;
+ case RSP_TYPE_REC_DATA:
+ if (!info)
+ break;
+ /*
+ * The sample count was incremented above during
+ * reception, because of variable length chunks
+ * of sample data.
+ */
+ if (info->rec_data.samples_curr >= info->rec_data.samples_total) {
+ ut181a_cond_stop_acquisition(sdi);
+ break;
+ }
+ ret = ut181a_send_cmd_get_rec_samples(serial,
+ info->rec_data.rec_idx, info->rec_data.samples_curr);
+ if (ret < 0)
+ ut181a_cond_stop_acquisition(sdi);
+ break;
+ default:
+ /* EMPTY */
+ break;
+ }
+ }
+
+ return SR_OK;
+}
+
+/* Process a previously received RX buffer. May find none or several packets. */
+static int process_buffer(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ uint8_t *pkt;
+ uint16_t v16;
+ size_t pkt_len, remain, idx;
+ int ret;
+
+ devc = sdi->priv;
+
+ /*
+ * Specifically do not insist on finding the packet boundary at
+ * the edge of the most recently received data chunk. Serial ports
+ * might involve hardware buffers (FIFO). We want to sync as fast
+ * as possible.
+ *
+ * Handle the synchronized situation first. Process complete and
+ * valid packets that reside at the start of the buffer. Continue
+ * reception when partially valid data was received but does not
+ * yet span a complete frame. Break out if data was received that
+ * failed verification. Assume temporary failure and try to sync
+ * to the input stream again.
+ *
+ * This logic is a little more complex than the typical DMM parser
+ * because of the variable frame length of the UT181A protocol. A
+ * frame always contains a magic (u16) and a length (u16), then a
+ * number of bytes according to length. The frame ends there, the
+ * checksum field is covered by the length value. packet processing
+ * will verify the checksum.
+ */
+ pkt = &devc->recv_buff[0];
+ do {
+ /* Search for (the start of) a valid packet. */
+ if (devc->recv_count < 2 * sizeof(uint16_t)) {
+ /* Need more RX data for magic and length. */
+ return SR_OK;
+ }
+ v16 = RL16(&pkt[0]);
+ if (v16 != FRAME_MAGIC) {
+ /* Not the expected magic marker. */
+ if (FRAME_DUMP_CSUM) {
+ FRAME_DUMP_CALL("Not a frame marker -> re-sync");
+ }
+ break;
+ }
+ v16 = RL16(&pkt[sizeof(uint16_t)]);
+ if (v16 < sizeof(uint16_t)) {
+ /* Insufficient length value, need at least checksum. */
+ if (FRAME_DUMP_CSUM) {
+ FRAME_DUMP_CALL("Too small a length -> re-sync");
+ }
+ break;
+ }
+ /* TODO Can we expect a maximum length value? */
+ pkt_len = 2 * sizeof(uint16_t) + v16;
+ if (pkt_len >= sizeof(devc->recv_buff)) {
+ /* Frame will never fit in RX buffer. Invalid RX data? */
+ if (FRAME_DUMP_CSUM) {
+ FRAME_DUMP_CALL("Excessive length -> re-sync");
+ }
+ break;
+ }
+ if (pkt_len > devc->recv_count) {
+ /* Need more RX data to complete the frame. */
+ return SR_OK;
+ }
+
+ /* Process the packet which completed reception. */
+ if (FRAME_DUMP_CSUM && sr_log_loglevel_get() >= FRAME_DUMP_LEVEL) {
+ GString *spew;
+ spew = sr_hexdump_new(pkt, pkt_len);
+ FRAME_DUMP_CALL("Found RX frame, %zu bytes: %s", pkt_len, spew->str);
+ sr_hexdump_free(spew);
+ }
+ ret = process_packet(sdi, pkt, pkt_len);
+ if (ret == SR_ERR_DATA) {
+ /* Verification failed, might be invalid RX data. */
+ if (FRAME_DUMP_CSUM) {
+ FRAME_DUMP_CALL("RX frame processing failed -> re-sync");
+ }
+ break;
+ }
+ remain = devc->recv_count - pkt_len;
+ if (remain)
+ memmove(&pkt[0], &pkt[pkt_len], remain);
+ devc->recv_count -= pkt_len;
+ } while (1);
+ if (devc->recv_count < 2 * sizeof(uint16_t)) {
+ /* Assume incomplete reception. Re-check later. */
+ return SR_OK;
+ }
+
+ /*
+ * Data was received but failed the test for a valid frame. Try to
+ * synchronize to the next frame marker. Make sure to skip the
+ * current position which might have been a marker yet the frame
+ * check failed.
+ */
+ if (FRAME_DUMP_CSUM) {
+ FRAME_DUMP_CALL("Trying to re-sync on RX frame");
+ }
+ for (idx = 1; idx < devc->recv_count; idx++) {
+ if (devc->recv_count - idx < sizeof(uint16_t)) {
+ /* Nothing found. Drop all but the last byte here. */
+ pkt[0] = pkt[idx];
+ devc->recv_count = 1;
+ if (FRAME_DUMP_CSUM) {
+ FRAME_DUMP_CALL("Dropping %zu bytes, still not in sync", idx);
+ }
+ return SR_OK;
+ }
+ v16 = RL16(&pkt[idx]);
+ if (v16 != FRAME_MAGIC)
+ continue;
+ /*
+ * Found a frame marker at offset 'idx'. Discard data
+ * before the marker. Next receive starts another attempt
+ * to interpret the frame, and may search the next marker
+ * upon failure.
+ */
+ if (FRAME_DUMP_CSUM) {
+ FRAME_DUMP_CALL("Dropping %zu bytes, next marker found", idx);
+ }
+ remain = devc->recv_count - idx;
+ if (remain)
+ memmove(&pkt[0], &pkt[idx], remain);
+ devc->recv_count -= idx;
+ break;
+ }
+
+ return SR_OK;
+}
+
+/* Gets invoked when RX data is available. */
+static int ut181a_receive_data(struct sr_dev_inst *sdi)
+{
+ struct dev_context *devc;
+ struct sr_serial_dev_inst *serial;
+ size_t len;
+ uint8_t *data;
+ ssize_t slen;
+ GString *spew;
+
+ devc = sdi->priv;
+ serial = sdi->conn;
+
+ /*
+ * Discard receive data when the buffer is exhausted. This shall
+ * allow to (re-)synchronize to the data stream when we find it
+ * in an arbitrary state. (Takes a while to exhaust the buffer.
+ * Data is seriously unusable when we get here.)
+ */
+ if (devc->recv_count == sizeof(devc->recv_buff)) {
+ if (FRAME_DUMP_RXDATA)
+ FRAME_DUMP_CALL("Discarding RX buffer (space exhausted)");
+ (void)process_packet(sdi, &devc->recv_buff[0], devc->recv_count);
+ devc->recv_count = 0;
+ }
+
+ /*
+ * Drain more data from the serial port, and check the receive
+ * buffer for packets. Process what was found to be complete.
+ */
+ len = sizeof(devc->recv_buff) - devc->recv_count;
+ data = &devc->recv_buff[devc->recv_count];
+ slen = serial_read_nonblocking(serial, data, len);
+ if (slen < 0) {
+ if (FRAME_DUMP_RXDATA)
+ FRAME_DUMP_CALL("UART RX failed, rc %zd", slen);
+ return 0;
+ }
+ len = slen;
+ if (FRAME_DUMP_RXDATA && sr_log_loglevel_get() >= FRAME_DUMP_LEVEL) {
+ spew = sr_hexdump_new(data, len);
+ FRAME_DUMP_CALL("UART RX, %zu bytes: %s", len, spew->str);
+ sr_hexdump_free(spew);
+ }
+ devc->recv_count += len;
+ process_buffer(sdi);
+
+ return 0;
+}
+
+SR_PRIV int ut181a_handle_events(int fd, int revents, void *cb_data)
+{
+ struct sr_dev_inst *sdi;
+ struct sr_serial_dev_inst *serial;