/*
* This file is part of the libsigrok project.
*
- * Copyright (C) 2019 Gerhard Sittig <gerhard.sittig@gmx.net>
+ * Copyright (C) 2019-2020 Gerhard Sittig <gerhard.sittig@gmx.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+/*
+ * This implementation uses protocol information which was provided by
+ * the MIT licensed ut181a project. See Protocol.md for more details:
+ *
+ * https://github.com/antage/ut181a/blob/master/Protocol.md
+ */
+
#include <config.h>
+#include <ctype.h>
+#include <math.h>
+#include <string.h>
+
#include "protocol.h"
-SR_PRIV int uni_t_ut181a_receive_data(int fd, int revents, void *cb_data)
+/*
+ * This driver depends on the user's enabling serial communication in
+ * the multimeter's menu system: SETUP -> Communication -> ON. The BLE
+ * adapter will shutdown within a short period of time when it's not
+ * being communicated to, needs another power cycle to re-connect. The
+ * USB cable does not suffer from such a constraint.
+ *
+ * Developer notes on the UT181A protocol:
+ * - Serial communication over HID or BLE based "cables", UT-D09 or
+ * UT-D07A, bidirectional communication (UT-D04 won't do).
+ * - UART frame format 8n1 at 9600 bps. Variable length DMM packets.
+ * - DMM packet starts with a magic marker, followed by the length,
+ * followed by data bytes and terminated by the checksum field.
+ * The length includes the remainder of the frame. The checksum
+ * includes the length field as well. The checksum value is the
+ * 16bit sum of all preceeding byte values.
+ * - The meter has many features (live readings, saved measurements,
+ * recorded measurement series) with many additional attributes:
+ * relative, min/max/avg, peak, AC+DC, multiple temperature probes,
+ * COMP mode (PASS/FAIL). The protocol reflects this with highly
+ * variable responses, with differing response layouts including
+ * optional field presence.
+ * - Frame field values are communicated in 8/16/32 bit integer as well
+ * as 32bit float formats in little endian presentation. Measurement
+ * values are represented by a combination of a float value and flags
+ * and a precision (digits count) and a text string which encodes the
+ * measured quantity including its flags and another scale factor
+ * (prefix reflecting the current range).
+ * - Response frames often provide a set of values at the same time:
+ * There are multiple displays, like current and min/max/avg values,
+ * relative values including their reference and the absolute value,
+ * differences between probes, etc.
+ * - The meter can hold multiple recordings with user assigned names,
+ * sample interval and duration, including interactive stop of a
+ * currently active recording. These recordings contain samples that
+ * were taken at a user specified interval.
+ * - The meter can store a list of measurements, which get saved upon
+ * user requests, and can span arbitrary modes/functions/layouts per
+ * saved measurement. In contrast to recordings which keep their type
+ * of measurement across the set of samples.
+ *
+ * See https://github.com/antage/ut181a/blob/master/Protocol.md for a
+ * detailled description of the meter's protocol. Some additional notes
+ * in slightly reformatted layout for improved maintainability:
+ * - "Range byte"
+ * 0x00 Auto range
+ * 0x01 60 mV 6 V 600 uA 60 mA 600 R 60 Hz 6 nF
+ * 0x02 600 mV 60 V 6000 uA 600 mA 6 K 600 Hz 60 nF
+ * 0x03 600V (20A is: auto) 60 K 6 KHz 600 nF
+ * 0x04 1000 V 600 K 60 KHz 6 uF
+ * 0x05 6 M 600 KHz 60 uF
+ * 0x06 60 M 6 MHz 600 uF
+ * 0x07 60 MHz 6 mF
+ * 0x08 60 mF
+ * ampere: 20A is auto, not user selectable
+ * continuity: 600 R, not user selectable
+ * conductivity: 60nS, not user selectable
+ * temperature: not user selectable
+ * diode: not user selectable
+ * frequency: all of the above ranges are available
+ * duty cycle, period: 60Hz to 60kHz, not user selectable beyond 60kHz
+ * - DMM response packets in COMP mode (limits check, PASS/FAIL):
+ * - The device supports two limits (upper, lower) and several modes
+ * ("inner", "outer", "below", "above"). The result is boolean for
+ * PASS or FAIL.
+ * - Response packets are NORMAL MEASUREMENTs, with a MAIN value but
+ * without AUX1/AUX2/BAR. Plus some more fields after the bargraph
+ * unit field's position which are specific to COMP mode. Auto range
+ * is off (also in the display).
+ * - Example data for COMP mode responses:
+ * INNER +0mV +3.3mV PASS -- 00 00 03 33 33 53 40 00 00 00 00
+ * INNER +0mV +3.3mV FAIL -- 00 01 03 33 33 53 40 00 00 00 00
+ * INNER +1mV +3.3mV FAIL -- 00 01 03 33 33 53 40 00 00 80 3f
+ * OUTER +0mV +3.3mV PASS -- 01 00 03 33 33 53 40 00 00 00 00
+ * BELOW +30mV PASS -- 02 00 03 00 00 f0 41
+ * - Extra fields:
+ * 1 byte mode, can be 0 to 3 for INNER/OUTER/BELOW/ABOVE
+ * 1 byte test result, bool failure, 0 is PASS, 1 is FAIL
+ * 1 byte digits, *not* shifted as in other precision fields
+ * 4 byte (always) high limit
+ * 4 byte (conditional) low limit, not in all modes
+ *
+ * Implementation notes on this driver version:
+ * - DMM channel assignment for measurement types:
+ * - normal: P1 main, P2 aux1, P3 aux2, P5 bar (as applicable)
+ * - relative: P1 relative, P2 reference, P3 absolute
+ * - min-max: P1 current, P2 maximum, P3 average, P4 minimum
+ * - peak: P2 maximum, P4 minimum
+ * - save/recording: P5 timestamp (in addition to the above)
+ */
+
+/*
+ * TODO:
+ * - General question: How many channels to export? An overlay with ever
+ * changing meanings? Or a multitude where values are sparse?
+ * - Check how the PC side can _set_ the mode and range. Does mode
+ * selection depend on the physical knob? Would assume it does.
+ * The multitude of mode codes (some 70) and the lack of an apparent
+ * formula to them makes this enhancement tedious. Listing too many
+ * items in the "list" query could reduce usability.
+ * - Add support for "COMP mode" (comparison, PASS/FAIL result).
+ * - How to express PASS/FAIL in the data feed submission? There is
+ * SR_UNIT_BOOLEAN but not a good MQ for envelope test results.
+ * - How to communicate limits to the session feed? COMP replies are
+ * normal measurements without aux1 and aux2. Is it appropriate to
+ * re-use DMM channels, or shall we add more of them?
+ * - Communicate timestamps for saved measurements and recordings to the
+ * session feed.
+ * - There is SR_MQ_TIME and SR_MQFLAG_RELATIVE, and SR_UNIT_SECOND.
+ * Absolute time seems appropriate for save, relative (to the start
+ * of the recording) for recordings.
+ * - Unfortunately double data types are not fully operational, so we
+ * use float. Which is limited to 23 bits, thus can only span some
+ * 100 days. But recordings can span longer periods when the sample
+ * interval is large.
+ * - Absolute times suffer from the 23bit limit (epoch time_t values
+ * require 32 bits these days). And they get presented as 1.5Gs,
+ * there seems to be no "date/time" flag or format.
+ * - Dynamically allocate and re-allocate the record name table. There
+ * appears to be no limit of 20 recordings. The manual won't tell, but
+ * it's assumed that a few hundreds or thousands are supported (10K
+ * samples in total? that's a guess though).
+ * - The PC side could initiate to save a live measurement. The command
+ * is there, it's just uncertain which SR_CONF_ key to use, DATALOG
+ * appears to enter/leave a period of recording, not a single shot.
+ * - The PC side could start and stop recordings. But the start command
+ * requires a name, sample interval, and duration, but SR_CONF_DATALOG
+ * is just a boolean. Combining SR_CONF_LIMIT_SAMPLES, _DATALOG, et al
+ * raises the question which order applications will send configure
+ * requests for them.
+ * - How to communicate the LOWPASS condition? PASS/FAIL results for
+ * COMP mode? Timestamps (absolute wall clock times)? High voltage,
+ * lead errors (probe plugs in ampere modes)?
+ */
+
+/*
+ * Development HACK, to see data frame exchange at -l 2 without the
+ * serial spew of -l 5. Also lets you concentrate on some of the code
+ * paths which currently are most interesting during maintenance. :)
+ */
+#if UT181A_WITH_SER_ECHO
+# define FRAME_DUMP_LEVEL SR_LOG_WARN
+# define FRAME_DUMP_CALL sr_warn
+#else
+# define FRAME_DUMP_LEVEL (SR_LOG_SPEW + 1)
+# define sr_nop(...) do { /* EMPTY */ } while (0)
+# define FRAME_DUMP_CALL sr_nop
+#endif
+
+#define FRAME_DUMP_RXDATA 0 /* UART level receive data. */
+#define FRAME_DUMP_CSUM 0 /* Chunking, frame isolation. */
+#define FRAME_DUMP_FRAME 0 /* DMM frames, including envelope. */
+#define FRAME_DUMP_BYTES 0 /* DMM frame's payload data, "DMM packet". */
+#define FRAME_DUMP_PARSE 1 /* Measurement value extraction. */
+#define FRAME_DUMP_REMAIN 1 /* Unprocessed response data. */
+
+/*
+ * TODO Can we collapse several u16 modes in useful ways? Need we keep
+ * them separate for "MQ+flags to mode" lookups, yet mark only some of
+ * them for LIST result sets? Can't filter and need to provide them all
+ * to the user? There are some 70-80 combinations. :-O
+ *
+ * Unfortunately there is no general pattern to these code numbers, or
+ * when there is it's non-obvious. There are _some_ conventions, but also
+ * exceptions, so that programmatic handling fails.
+ *
+ * TODO
+ * - Factor out LOWPASS to a separate mode? At least derive an MQFLAG.
+ */
+static const struct mqopt_item ut181a_mqopts[] = {
+ {
+ SR_MQ_VOLTAGE, SR_MQFLAG_AC, {
+ MODE_V_AC, MODE_V_AC_REL,
+ MODE_mV_AC, MODE_mV_AC_REL,
+ MODE_V_AC_PEAK, MODE_mV_AC_PEAK,
+ MODE_V_AC_LOWPASS, MODE_V_AC_LOWPASS_REL,
+ 0,
+ },
+ },
+ {
+ SR_MQ_VOLTAGE, SR_MQFLAG_DC, {
+ MODE_V_DC, MODE_V_DC_REL,
+ MODE_mV_DC, MODE_mV_DC_REL,
+ MODE_V_DC_PEAK, MODE_mV_DC_PEAK,
+ 0,
+ },
+ },
+ {
+ SR_MQ_VOLTAGE, SR_MQFLAG_DC | SR_MQFLAG_AC, {
+ MODE_V_DC_ACDC, MODE_V_DC_ACDC_REL,
+ MODE_mV_AC_ACDC, MODE_mV_AC_ACDC_REL,
+ 0,
+ },
+ },
+ {
+ SR_MQ_GAIN, 0, {
+ MODE_V_AC_dBV, MODE_V_AC_dBV_REL,
+ MODE_V_AC_dBm, MODE_V_AC_dBm_REL,
+ 0,
+ },
+ },
+ {
+ SR_MQ_CURRENT, SR_MQFLAG_AC, {
+ MODE_A_AC, MODE_A_AC_REL,
+ MODE_A_AC_PEAK,
+ MODE_mA_AC, MODE_mA_AC_REL,
+ MODE_mA_AC_PEAK,
+ MODE_uA_AC, MODE_uA_AC_REL,
+ MODE_uA_AC_PEAK,
+ 0,
+ },
+ },
+ {
+ SR_MQ_CURRENT, SR_MQFLAG_DC, {
+ MODE_A_DC, MODE_A_DC_REL,
+ MODE_A_DC_PEAK,
+ MODE_mA_DC, MODE_mA_DC_REL,
+ MODE_uA_DC, MODE_uA_DC_REL,
+ MODE_uA_DC_PEAK,
+ 0,
+ },
+ },
+ {
+ SR_MQ_CURRENT, SR_MQFLAG_DC | SR_MQFLAG_AC, {
+ MODE_A_DC_ACDC, MODE_A_DC_ACDC_REL,
+ MODE_mA_DC_ACDC, MODE_mA_DC_ACDC_REL,
+ MODE_uA_DC_ACDC, MODE_uA_DC_ACDC_REL,
+ MODE_mA_DC_ACDC_PEAK,
+ 0,
+ },
+ },
+ {
+ SR_MQ_RESISTANCE, 0, {
+ MODE_RES, MODE_RES_REL, 0,
+ },
+ },
+ {
+ SR_MQ_CONDUCTANCE, 0, {
+ MODE_COND, MODE_COND_REL, 0,
+ },
+ },
+ {
+ SR_MQ_CONTINUITY, 0, {
+ MODE_CONT_SHORT, MODE_CONT_OPEN, 0,
+ },
+ },
+ {
+ SR_MQ_VOLTAGE, SR_MQFLAG_DIODE | SR_MQFLAG_DC, {
+ MODE_DIODE, MODE_DIODE_ALARM, 0,
+ },
+ },
+ {
+ SR_MQ_CAPACITANCE, 0, {
+ MODE_CAP, MODE_CAP_REL, 0,
+ },
+ },
+ {
+ SR_MQ_FREQUENCY, 0, {
+ MODE_FREQ, MODE_FREQ_REL,
+ MODE_V_AC_Hz, MODE_mV_AC_Hz,
+ MODE_A_AC_Hz, MODE_mA_AC_Hz, MODE_uA_AC_Hz,
+ 0,
+ },
+ },
+ {
+ SR_MQ_DUTY_CYCLE, 0, {
+ MODE_DUTY, MODE_DUTY_REL, 0,
+ },
+ },
+ {
+ SR_MQ_PULSE_WIDTH, 0, {
+ MODE_PULSEWIDTH, MODE_PULSEWIDTH_REL, 0,
+ },
+ },
+ {
+ SR_MQ_TEMPERATURE, 0, {
+ MODE_TEMP_C_T1_and_T2, MODE_TEMP_C_T1_and_T2_REL,
+ MODE_TEMP_C_T1_minus_T2, MODE_TEMP_F_T1_and_T2,
+ MODE_TEMP_C_T2_and_T1, MODE_TEMP_C_T2_and_T1_REL,
+ MODE_TEMP_C_T2_minus_T1,
+ MODE_TEMP_F_T1_and_T2_REL, MODE_TEMP_F_T1_minus_T2,
+ MODE_TEMP_F_T2_and_T1, MODE_TEMP_F_T2_and_T1_REL,
+ MODE_TEMP_F_T2_minus_T1,
+ 0,
+ },
+ },
+};
+
+SR_PRIV const struct mqopt_item *ut181a_get_mqitem_from_mode(uint16_t mode)
+{
+ size_t mq_idx, mode_idx;
+ const struct mqopt_item *item;
+
+ for (mq_idx = 0; mq_idx < ARRAY_SIZE(ut181a_mqopts); mq_idx++) {
+ item = &ut181a_mqopts[mq_idx];
+ for (mode_idx = 0; mode_idx < ARRAY_SIZE(item->modes); mode_idx++) {
+ if (!item->modes[mode_idx])
+ break;
+ if (item->modes[mode_idx] != mode)
+ continue;
+ /* Found a matching mode. */
+ return item;
+ }
+ }
+ return NULL;
+}
+
+SR_PRIV uint16_t ut181a_get_mode_from_mq_flags(enum sr_mq mq, enum sr_mqflag mqflags)
+{
+ size_t mq_idx;
+ const struct mqopt_item *item;
+
+ for (mq_idx = 0; mq_idx < ARRAY_SIZE(ut181a_mqopts); mq_idx++) {
+ item = &ut181a_mqopts[mq_idx];
+ if (mq != item->mq)
+ continue;
+ /* TODO Need finer checks? Masked? */
+ if (mqflags != item->mqflags)
+ continue;
+ return item->modes[0];
+ }
+ return 0;
+}
+
+SR_PRIV GVariant *ut181a_get_mq_flags_list_item(enum sr_mq mq, enum sr_mqflag mqflag)
+{
+ GVariant *arr[2], *tuple;
+
+ arr[0] = g_variant_new_uint32(mq);
+ arr[1] = g_variant_new_uint64(mqflag);
+ tuple = g_variant_new_tuple(arr, ARRAY_SIZE(arr));
+
+ return tuple;
+}
+
+SR_PRIV GVariant *ut181a_get_mq_flags_list(void)
+{
+ GVariantBuilder gvb;
+ GVariant *tuple, *list;
+ size_t i;
+ const struct mqopt_item *item;
+
+ g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY);
+ for (i = 0; i < ARRAY_SIZE(ut181a_mqopts); i++) {
+ item = &ut181a_mqopts[i];
+ tuple = ut181a_get_mq_flags_list_item(item->mq, item->mqflags);
+ g_variant_builder_add_value(&gvb, tuple);
+ }
+ list = g_variant_builder_end(&gvb);
+
+ return list;
+}
+
+/*
+ * See the Protocol.md document's "Range byte" section. Value 0 is said
+ * to communicate "auto range", while values 1-8 are said to communicate
+ * specific ranges which depend on the meter's current function. Yet
+ * there is another misc flag for auto range.
+ *
+ * From this information, and observed packet content, it is assumed
+ * that the following logic applies:
+ * - Measurements (response packets) carry the "auto" flag, _and_ a
+ * "range" byte, to provide the information that auto ranging was in
+ * effect, and which specific range the automatic detection picked.
+ * - "Set range" requests can request a specific range (values 1-8), or
+ * switch to auto range (value 0).
+ *
+ * This driver implementation returns non-settable string literals for
+ * modes where auto ranging is not user adjustable (high current, diode,
+ * continuity, conductivity, temperature). Setup requests get rejected.
+ * (The local user interface neither responds to RANGE button presses.)
+ */
+static const char *range_auto = "auto";
+static const char *ranges_volt_mv[] = {
+ "60mV", "600mV", NULL,
+};
+static const char *ranges_volt_v[] = {
+ "6V", "60V", "600V", "1000V", NULL,
+};
+static const char *ranges_volt_diode[] = {
+ /* Diode is always auto, not user adjustable. */
+ "3.0V", NULL,
+};
+static const char *ranges_amp_ua[] = {
+ "600uA", "6000uA", NULL,
+};
+static const char *ranges_amp_ma[] = {
+ "60mA", "600mA", NULL,
+};
+static const char *ranges_amp_a[] = {
+ /* The 'A' range is always 20A (in the display, manual says 10A). */
+ "20A", NULL,
+};
+static const char *ranges_ohm_res[] = {
+ /* TODO
+ * Prefer "Ohm" (or "R" for sub-kilo ranges) instead? We try to
+ * keep usability in other places (micro), too, by letting users
+ * type regular non-umlaut text, and avoiding encoding issues.
+ */
+ "600Ω", "6kΩ", "60kΩ", "600kΩ", "6MΩ", "60MΩ", NULL,
+};
+static const char *ranges_ohm_600[] = {
+ /* Continuity is always 600R, not user adjustable. */
+ "600Ω", NULL,
+};
+static const char *ranges_cond[] = {
+ /* Conductivity is always 60nS, not user adjustable. */
+ "60nS", NULL,
+};
+static const char *ranges_capa[] = {
+ "6nF", "60nF", "600nF", "6uF", "60uF", "600uF", "6mF", "600mF", NULL,
+};
+static const char *ranges_freq_full[] = {
+ "60Hz", "600Hz", "6kHz", "60kHz", "600kHz", "6MHz", "60MHz", NULL,
+};
+static const char *ranges_freq_60khz[] = {
+ /* Duty cycle and period only support up to 60kHz. */
+ "60Hz", "600Hz", "6kHz", "60kHz", NULL,
+};
+static const char *ranges_temp_c[] = {
+ /* Temperature always is up to 1000 degree C, not user adjustable. */
+ "1000°C", NULL,
+};
+static const char *ranges_temp_f[] = {
+ /* Temperature always is up to 1832 F, not user adjustable. */
+ "1832F", NULL,
+};
+
+static void ut181a_add_ranges_list(GVariantBuilder *b, const char **l)
+{
+ const char *range;
+
+ while (l && *l && **l) {
+ range = *l++;
+ g_variant_builder_add(b, "s", range);
+ }
+}
+
+SR_PRIV GVariant *ut181a_get_ranges_list(void)
+{
+ GVariantBuilder gvb;
+ GVariant *list;
+
+ /* Also list those ranges which cannot get set? */
+#define WITH_RANGE_LIST_FIXED 1
+
+ g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY);
+ g_variant_builder_add(&gvb, "s", range_auto);
+ ut181a_add_ranges_list(&gvb, ranges_volt_mv);
+ ut181a_add_ranges_list(&gvb, ranges_volt_v);
+ (void)ranges_volt_diode;
+ ut181a_add_ranges_list(&gvb, ranges_amp_ua);
+ ut181a_add_ranges_list(&gvb, ranges_amp_ma);
+#if WITH_RANGE_LIST_FIXED
+ ut181a_add_ranges_list(&gvb, ranges_amp_a);
+#else
+ (void)ranges_amp_a;
+#endif
+ ut181a_add_ranges_list(&gvb, ranges_ohm_res);
+ (void)ranges_ohm_600;
+ ut181a_add_ranges_list(&gvb, ranges_cond);
+ ut181a_add_ranges_list(&gvb, ranges_capa);
+ ut181a_add_ranges_list(&gvb, ranges_freq_full);
+ (void)ranges_freq_60khz;
+#if WITH_RANGE_LIST_FIXED
+ ut181a_add_ranges_list(&gvb, ranges_temp_c);
+ ut181a_add_ranges_list(&gvb, ranges_temp_f);
+#else
+ (void)ranges_temp_c;
+ (void)ranges_temp_f;
+#endif
+ list = g_variant_builder_end(&gvb);
+
+ return list;
+}
+
+SR_PRIV const char *ut181a_get_range_from_packet_bytes(struct dev_context *devc)
+{
+ uint16_t mode;
+ uint8_t range;
+ gboolean is_auto;
+ const char **ranges;
+
+ if (!devc)
+ return NULL;
+ mode = devc->info.meas_head.mode;
+ range = devc->info.meas_head.range;
+ is_auto = devc->info.meas_head.is_auto_range;
+
+ /* Handle the simple cases of "auto" and out of (absolute) limits. */
+ if (is_auto)
+ return range_auto;
+ if (!mode)
+ return NULL;
+ if (!range)
+ return range_auto;
+ if (range > MAX_RANGE_INDEX)
+ return NULL;
+
+ /* Lookup the list of ranges which depend on the meter's current mode. */
+ switch (mode) {
+
+ case MODE_V_AC:
+ case MODE_V_AC_REL:
+ case MODE_V_AC_Hz:
+ case MODE_V_AC_PEAK:
+ case MODE_V_AC_LOWPASS:
+ case MODE_V_AC_LOWPASS_REL:
+ case MODE_V_AC_dBV:
+ case MODE_V_AC_dBV_REL:
+ case MODE_V_AC_dBm:
+ case MODE_V_AC_dBm_REL:
+ case MODE_V_DC:
+ case MODE_V_DC_REL:
+ case MODE_V_DC_ACDC:
+ case MODE_V_DC_ACDC_REL:
+ case MODE_V_DC_PEAK:
+ ranges = ranges_volt_v;
+ break;
+ case MODE_mV_AC:
+ case MODE_mV_AC_REL:
+ case MODE_mV_AC_Hz:
+ case MODE_mV_AC_PEAK:
+ case MODE_mV_AC_ACDC:
+ case MODE_mV_AC_ACDC_REL:
+ case MODE_mV_DC:
+ case MODE_mV_DC_REL:
+ case MODE_mV_DC_PEAK:
+ ranges = ranges_volt_mv;
+ break;
+ case MODE_RES:
+ case MODE_RES_REL:
+ ranges = ranges_ohm_res;
+ break;
+ case MODE_CONT_SHORT:
+ case MODE_CONT_OPEN:
+ ranges = ranges_ohm_600;
+ break;
+ case MODE_COND:
+ case MODE_COND_REL:
+ ranges = ranges_cond;
+ break;
+ case MODE_CAP:
+ case MODE_CAP_REL:
+ ranges = ranges_capa;
+ break;
+ case MODE_FREQ:
+ case MODE_FREQ_REL:
+ ranges = ranges_freq_full;
+ break;
+ case MODE_DUTY:
+ case MODE_DUTY_REL:
+ case MODE_PULSEWIDTH:
+ case MODE_PULSEWIDTH_REL:
+ ranges = ranges_freq_60khz;
+ break;
+ case MODE_uA_DC:
+ case MODE_uA_DC_REL:
+ case MODE_uA_DC_ACDC:
+ case MODE_uA_DC_ACDC_REL:
+ case MODE_uA_DC_PEAK:
+ case MODE_uA_AC:
+ case MODE_uA_AC_REL:
+ case MODE_uA_AC_Hz:
+ case MODE_uA_AC_PEAK:
+ ranges = ranges_amp_ua;
+ break;
+ case MODE_mA_DC:
+ case MODE_mA_DC_REL:
+ case MODE_mA_DC_ACDC:
+ case MODE_mA_DC_ACDC_REL:
+ case MODE_mA_DC_ACDC_PEAK:
+ case MODE_mA_AC:
+ case MODE_mA_AC_REL:
+ case MODE_mA_AC_Hz:
+ case MODE_mA_AC_PEAK:
+ ranges = ranges_amp_ma;
+ break;
+
+ /* Some modes are neither flexible nor adjustable. */
+ case MODE_TEMP_C_T1_and_T2:
+ case MODE_TEMP_C_T1_and_T2_REL:
+ case MODE_TEMP_C_T2_and_T1:
+ case MODE_TEMP_C_T2_and_T1_REL:
+ case MODE_TEMP_C_T1_minus_T2:
+ case MODE_TEMP_C_T2_minus_T1:
+ ranges = ranges_temp_c;
+ break;
+ case MODE_TEMP_F_T1_and_T2:
+ case MODE_TEMP_F_T1_and_T2_REL:
+ case MODE_TEMP_F_T2_and_T1:
+ case MODE_TEMP_F_T2_and_T1_REL:
+ case MODE_TEMP_F_T1_minus_T2:
+ case MODE_TEMP_F_T2_minus_T1:
+ ranges = ranges_temp_f;
+ break;
+ /* Diode, always 3V. */
+ case MODE_DIODE:
+ case MODE_DIODE_ALARM:
+ ranges = ranges_volt_diode;
+ break;
+ /* High current (A range). Always 20A. */
+ case MODE_A_DC:
+ case MODE_A_DC_REL:
+ case MODE_A_DC_ACDC:
+ case MODE_A_DC_ACDC_REL:
+ case MODE_A_DC_PEAK:
+ case MODE_A_AC:
+ case MODE_A_AC_REL:
+ case MODE_A_AC_Hz:
+ case MODE_A_AC_PEAK:
+ ranges = ranges_amp_a;
+ break;
+
+ /* Unknown mode? Programming error? */
+ default:
+ return NULL;
+ }
+
+ /* Lookup the range in the list of the mode's ranges. */
+ while (ranges && *ranges && **ranges && --range > 0) {
+ ranges++;
+ }
+ if (!ranges || !*ranges || !**ranges)
+ return NULL;
+ return *ranges;
+}
+
+SR_PRIV int ut181a_set_range_from_text(const struct sr_dev_inst *sdi, const char *text)
+{
+ struct dev_context *devc;
+ uint16_t mode;
+ const char **ranges;
+ uint8_t range;
+
+ /* We must have determined the meter's current mode first. */
+ if (!sdi)
+ return SR_ERR_ARG;
+ if (!text || !*text)
+ return SR_ERR_ARG;
+ devc = sdi->priv;
+ if (!devc)
+ return SR_ERR_ARG;
+ mode = devc->info.meas_head.mode;
+ if (!mode)
+ return SR_ERR_ARG;
+
+ /* Handle the simple case of "auto" caller spec. */
+ if (strcmp(text, range_auto) == 0) {
+ range = 0;
+ return ut181a_send_cmd_setmode(sdi->conn, range);
+ }
+
+ /* Lookup the list of ranges which depend on the meter's current mode. */
+ switch (mode) {
+
+ /* Map "user servicable" modes to their respective ranges list. */
+ case MODE_V_AC:
+ case MODE_V_AC_REL:
+ case MODE_V_AC_Hz:
+ case MODE_V_AC_PEAK:
+ case MODE_V_AC_LOWPASS:
+ case MODE_V_AC_LOWPASS_REL:
+ case MODE_V_AC_dBV:
+ case MODE_V_AC_dBV_REL:
+ case MODE_V_AC_dBm:
+ case MODE_V_AC_dBm_REL:
+ case MODE_V_DC:
+ case MODE_V_DC_REL:
+ case MODE_V_DC_ACDC:
+ case MODE_V_DC_ACDC_REL:
+ case MODE_V_DC_PEAK:
+ ranges = ranges_volt_v;
+ break;
+ case MODE_mV_AC:
+ case MODE_mV_AC_REL:
+ case MODE_mV_AC_Hz:
+ case MODE_mV_AC_PEAK:
+ case MODE_mV_AC_ACDC:
+ case MODE_mV_AC_ACDC_REL:
+ case MODE_mV_DC:
+ case MODE_mV_DC_REL:
+ case MODE_mV_DC_PEAK:
+ ranges = ranges_volt_mv;
+ break;
+ case MODE_RES:
+ case MODE_RES_REL:
+ ranges = ranges_ohm_res;
+ break;
+ case MODE_CAP:
+ case MODE_CAP_REL:
+ ranges = ranges_capa;
+ break;
+ case MODE_FREQ:
+ case MODE_FREQ_REL:
+ ranges = ranges_freq_full;
+ break;
+ case MODE_DUTY:
+ case MODE_DUTY_REL:
+ case MODE_PULSEWIDTH:
+ case MODE_PULSEWIDTH_REL:
+ ranges = ranges_freq_60khz;
+ break;
+ case MODE_uA_DC:
+ case MODE_uA_DC_REL:
+ case MODE_uA_DC_ACDC:
+ case MODE_uA_DC_ACDC_REL:
+ case MODE_uA_DC_PEAK:
+ case MODE_uA_AC:
+ case MODE_uA_AC_REL:
+ case MODE_uA_AC_Hz:
+ case MODE_uA_AC_PEAK:
+ ranges = ranges_amp_ua;
+ break;
+ case MODE_mA_DC:
+ case MODE_mA_DC_REL:
+ case MODE_mA_DC_ACDC:
+ case MODE_mA_DC_ACDC_REL:
+ case MODE_mA_DC_ACDC_PEAK:
+ case MODE_mA_AC:
+ case MODE_mA_AC_REL:
+ case MODE_mA_AC_Hz:
+ case MODE_mA_AC_PEAK:
+ ranges = ranges_amp_ma;
+ break;
+
+ /*
+ * Some modes use fixed ranges. Accept their specs or refuse to
+ * set a specific range? The meter's UI refuses MANUAL mode and
+ * remains in AUTO mode. So do we here.
+ */
+ case MODE_CONT_SHORT:
+ case MODE_CONT_OPEN:
+ return SR_ERR_NA;
+ ranges = ranges_ohm_600;
+ break;
+ case MODE_COND:
+ case MODE_COND_REL:
+ return SR_ERR_NA;
+ ranges = ranges_cond;
+ break;
+ case MODE_TEMP_C_T1_and_T2:
+ case MODE_TEMP_C_T1_and_T2_REL:
+ case MODE_TEMP_C_T2_and_T1:
+ case MODE_TEMP_C_T2_and_T1_REL:
+ case MODE_TEMP_C_T1_minus_T2:
+ case MODE_TEMP_C_T2_minus_T1:
+ return SR_ERR_NA;
+ ranges = ranges_temp_c;
+ break;
+ case MODE_TEMP_F_T1_and_T2:
+ case MODE_TEMP_F_T1_and_T2_REL:
+ case MODE_TEMP_F_T2_and_T1:
+ case MODE_TEMP_F_T2_and_T1_REL:
+ case MODE_TEMP_F_T1_minus_T2:
+ case MODE_TEMP_F_T2_minus_T1:
+ return SR_ERR_NA;
+ ranges = ranges_temp_f;
+ break;
+ /* Diode, always 3V. */
+ case MODE_DIODE:
+ case MODE_DIODE_ALARM:
+ return SR_ERR_NA;
+ ranges = ranges_volt_diode;
+ break;
+ /* High current (A range). Always 20A. */
+ case MODE_A_DC:
+ case MODE_A_DC_REL:
+ case MODE_A_DC_ACDC:
+ case MODE_A_DC_ACDC_REL:
+ case MODE_A_DC_PEAK:
+ case MODE_A_AC:
+ case MODE_A_AC_REL:
+ case MODE_A_AC_Hz:
+ case MODE_A_AC_PEAK:
+ return SR_ERR_NA;
+ ranges = ranges_amp_a;
+ break;
+
+ /* Unknown mode? Programming error? */
+ default:
+ return SR_ERR_BUG;
+ }
+
+ /* Lookup the range in the list of the mode's ranges. */
+ range = 1;
+ while (ranges && *ranges && **ranges) {
+ if (strcmp(*ranges, text) != 0) {
+ range++;
+ ranges++;
+ continue;
+ }
+ return ut181a_send_cmd_setrange(sdi->conn, range);
+ }
+ return SR_ERR_ARG;
+}
+
+/**
+ * Parse a unit text into scale factor, MQ and flags, and unit.
+ *
+ * @param[out] mqs The scale/MQ/unit details to fill in.
+ * @param[in] text The DMM's "unit text" (string label).
+ *
+ * @returns SR_OK upon success, SR_ERR_* upon error.
+ *
+ * UT181A unit text strings encode several details: They start with an
+ * optional prefix (which communicates a scale factor), specify the unit
+ * of the measured value (which hints towards the measured quantity),
+ * and carry optional attributes (which MQ flags can get derived from).
+ *
+ * See unit.rs for the list of known input strings. Though there are
+ * unexpected differences:
+ * - \u{FFFD}C/F instead of 0xb0 for degree (local platform conversion?)
+ * - 'u' seems to be used for micro, good (no 'micro' umlaut involved)
+ * - '~' (tilde, 0x7e) for Ohm
+ *
+ * Prefixes: p n u m '' k M G
+ *
+ * Units:
+ * - F Farad (m u n)
+ * - dBV, dBm (no prefix)
+ * - ~ (tilde, Ohm) (- k M)
+ * - S Siemens (n)
+ * - % percent (no prefix)
+ * - s seconds (m)
+ * - Hz Hertz (- k M)
+ * - xC, xF degree (no prefix)
+ *
+ * Units with Flags:
+ * - Aac+dc ampere AC+DC (- m u)
+ * - AAC ampere AC (- m u)
+ * - ADC ampere DC (- m u)
+ * - Vac+dc volt AC+DC (- m)
+ * - VAC volt AC (- m)
+ * - VDC volt DC (- m)
+ */
+static int ut181a_get_mq_details_from_text(struct mq_scale_params *mqs, const char *text)
+{
+ char scale_char;
+ int scale;
+ enum sr_mq mq;
+ enum sr_mqflag mqflags;
+ enum sr_unit unit;
+
+ if (!mqs)
+ return SR_ERR_ARG;
+ memset(mqs, 0, sizeof(*mqs));
+
+ /* Start from unknown state, no modifiers. */
+ scale = 0;
+ unit = 0;
+ mq = 0;
+ mqflags = 0;
+
+ /* Derive the scale factor from the optional prefix. */
+ scale_char = *text++;
+ if (scale_char == 'p')
+ scale = -12;
+ else if (scale_char == 'n')
+ scale = -9;
+ else if (scale_char == 'u')
+ scale = -6;
+ else if (scale_char == 'm')
+ scale = -3;
+ else if (scale_char == 'k')
+ scale = +3;
+ else if (scale_char == 'M')
+ scale = +6;
+ else if (scale_char == 'G')
+ scale = +9;
+ else
+ text--;
+
+ /* Guess the MQ (and flags) from the unit text. */
+ if (g_str_has_prefix(text, "F")) {
+ text += strlen("F");
+ unit = SR_UNIT_FARAD;
+ if (!mq)
+ mq = SR_MQ_CAPACITANCE;
+ } else if (g_str_has_prefix(text, "dBV")) {
+ text += strlen("dBV");
+ unit = SR_UNIT_DECIBEL_VOLT;
+ if (!mq)
+ mq = SR_MQ_GAIN;
+ } else if (g_str_has_prefix(text, "dBm")) {
+ text += strlen("dBm");
+ unit = SR_UNIT_DECIBEL_MW;
+ if (!mq)
+ mq = SR_MQ_GAIN;
+ } else if (g_str_has_prefix(text, "~")) {
+ text += strlen("~");
+ unit = SR_UNIT_OHM;
+ if (!mq)
+ mq = SR_MQ_RESISTANCE;
+ } else if (g_str_has_prefix(text, "S")) {
+ text += strlen("S");
+ unit = SR_UNIT_SIEMENS;
+ if (!mq)
+ mq = SR_MQ_CONDUCTANCE;
+ } else if (g_str_has_prefix(text, "%")) {
+ text += strlen("%");
+ unit = SR_UNIT_PERCENTAGE;
+ if (!mq)
+ mq = SR_MQ_DUTY_CYCLE;
+ } else if (g_str_has_prefix(text, "s")) {
+ text += strlen("s");
+ unit = SR_UNIT_SECOND;
+ if (!mq)
+ mq = SR_MQ_PULSE_WIDTH;
+ } else if (g_str_has_prefix(text, "Hz")) {
+ text += strlen("Hz");
+ unit = SR_UNIT_HERTZ;
+ if (!mq)
+ mq = SR_MQ_FREQUENCY;
+ } else if (g_str_has_prefix(text, "\xb0" "C")) {
+ text += strlen("\xb0" "C");
+ unit = SR_UNIT_CELSIUS;
+ if (!mq)
+ mq = SR_MQ_TEMPERATURE;
+ } else if (g_str_has_prefix(text, "\xb0" "F")) {
+ text += strlen("\xb0" "F");
+ unit = SR_UNIT_FAHRENHEIT;
+ if (!mq)
+ mq = SR_MQ_TEMPERATURE;
+ } else if (g_str_has_prefix(text, "A")) {
+ text += strlen("A");
+ unit = SR_UNIT_AMPERE;
+ if (!mq)
+ mq = SR_MQ_CURRENT;
+ } else if (g_str_has_prefix(text, "V")) {
+ text += strlen("V");
+ unit = SR_UNIT_VOLT;
+ if (!mq)
+ mq = SR_MQ_VOLTAGE;
+ } else if (g_str_has_prefix(text, "timestamp")) {
+ /*
+ * The meter never provides this "timestamp" label,
+ * but the driver re-uses common logic here to have
+ * the MQ details filled in for save/record stamps.
+ */
+ text += strlen("timestamp");
+ unit = SR_UNIT_SECOND;
+ if (!mq)
+ mq = SR_MQ_TIME;
+ }
+
+ /* Amend MQ flags from an optional suffix. */
+ if (g_str_has_prefix(text, "ac+dc")) {
+ text += strlen("ac+dc");
+ mqflags |= SR_MQFLAG_AC | SR_MQFLAG_DC;
+ } else if (g_str_has_prefix(text, "AC")) {
+ text += strlen("AC");
+ mqflags |= SR_MQFLAG_AC;
+ } else if (g_str_has_prefix(text, "DC")) {
+ text += strlen("DC");
+ mqflags |= SR_MQFLAG_DC;
+ }
+
+ /* Put all previously determined details into the container. */
+ mqs->scale = scale;
+ mqs->mq = mq;
+ mqs->mqflags = mqflags;
+ mqs->unit = unit;
+
+ return SR_OK;
+}
+
+/*
+ * Break down a packed 32bit timestamp presentation, and create an epoch
+ * value from it. The UT181A protocol encodes timestamps in a 32bit value:
+ *
+ * [5:0] year - 2000
+ * [9:6] month
+ * [14:10] mday
+ * [19:15] hour
+ * [25:20] min
+ * [31:26] sec
+ *
+ * TODO Find a portable and correct conversion helper. The mktime() API
+ * is said to involve timezone details, and modify the environment. Is
+ * strftime("%s") a better approach? Until then mktime() might be good
+ * enough an approach, assuming that the meter will be set to the user's
+ * local time.
+ */
+static time_t ut181a_get_epoch_for_timestamp(uint32_t ts)
+{
+ struct tm t;
+
+ memset(&t, 0, sizeof(t));
+ t.tm_year = ((ts >> 0) & 0x3f) + 2000 - 1900;
+ t.tm_mon = ((ts >> 6) & 0x0f) - 1;
+ t.tm_mday = ((ts >> 10) & 0x1f);
+ t.tm_hour = ((ts >> 15) & 0x1f);
+ t.tm_min = ((ts >> 20) & 0x3f);
+ t.tm_sec = ((ts >> 26) & 0x3f);
+ t.tm_isdst = -1;
+
+ return mktime(&t);
+}
+
+/**
+ * Calculate UT181A specific checksum for serial data frame.
+ *
+ * @param[in] data The payload bytes to calculate the checksum for.
+ * @param[in] dlen The number of payload bytes.
+ *
+ * @returns The checksum value.
+ *
+ * On the wire the checksum covers all fields after the magic and before
+ * the checksum. In other words the checksum covers the length field and
+ * the payload bytes.
+ */
+static uint16_t ut181a_checksum(const uint8_t *data, size_t dlen)
+{
+ uint16_t cs;
+
+ cs = 0;
+ while (dlen-- > 0)
+ cs += *data++;
+
+ return cs;
+}
+
+/**
+ * Send payload bytes via serial comm, add frame envelope and transmit.
+ *
+ * @param[in] serial Serial port.
+ * @param[in] data Payload bytes.
+ * @param[in] dlen Payload length.
+ *
+ * @returns >= 0 upon success, negative upon failure (SR_ERR codes)
+ */
+static int ut181a_send_frame(struct sr_serial_dev_inst *serial,
+ const uint8_t *data, size_t dlen)
+{
+ uint8_t frame_buff[SEND_BUFF_SIZE];
+ size_t frame_off;
+ const uint8_t *cs_data;
+ size_t cs_dlen;
+ uint16_t cs_value;
+ int ret;
+
+ if (FRAME_DUMP_BYTES && sr_log_loglevel_get() >= FRAME_DUMP_LEVEL) {
+ GString *spew;
+ spew = sr_hexdump_new(data, dlen);
+ FRAME_DUMP_CALL("TX payload, %zu bytes: %s", dlen, spew->str);
+ sr_hexdump_free(spew);
+ }
+
+ /*
+ * The frame buffer must hold the magic and length and payload
+ * bytes and checksum. Check for the available space.
+ */
+ if (dlen > sizeof(frame_buff) - 3 * sizeof(uint16_t)) {
+ return SR_ERR_ARG;
+ }
+
+ /*
+ * Create a frame for the payload bytes. The length field's value
+ * also includes the checksum field (spans the remainder of the
+ * frame). The checksum covers everything between the magic and
+ * the checksum field.
+ */
+ frame_off = 0;
+ WL16(&frame_buff[frame_off], FRAME_MAGIC);
+ frame_off += sizeof(uint16_t);
+ WL16(&frame_buff[frame_off], dlen + sizeof(uint16_t));
+ frame_off += sizeof(uint16_t);
+ memcpy(&frame_buff[frame_off], data, dlen);
+ frame_off += dlen;
+ cs_data = &frame_buff[sizeof(uint16_t)];
+ cs_dlen = frame_off - sizeof(uint16_t);
+ cs_value = ut181a_checksum(cs_data, cs_dlen);
+ WL16(&frame_buff[frame_off], cs_value);
+ frame_off += sizeof(uint16_t);
+
+ if (FRAME_DUMP_FRAME && sr_log_loglevel_get() >= FRAME_DUMP_LEVEL) {
+ GString *spew;
+ spew = sr_hexdump_new(frame_buff, frame_off);
+ FRAME_DUMP_CALL("TX frame, %zu bytes: %s", frame_off, spew->str);
+ sr_hexdump_free(spew);
+ }
+
+ ret = serial_write_blocking(serial, frame_buff, frame_off, SEND_TO_MS);
+ if (ret < 0)
+ return ret;
+
+ return SR_OK;
+}
+
+/* Construct and transmit "set mode" command. */
+SR_PRIV int ut181a_send_cmd_setmode(struct sr_serial_dev_inst *serial, uint16_t mode)
+{
+ uint8_t cmd[sizeof(uint8_t) + sizeof(uint16_t)];
+ size_t cmd_off;
+
+ cmd_off = 0;
+ cmd[cmd_off++] = CMD_CODE_SET_MODE;
+ WL16(&cmd[cmd_off], mode);
+ cmd_off += sizeof(uint16_t);
+
+ return ut181a_send_frame(serial, cmd, cmd_off);
+}
+
+/* Construct and transmit "set range" command. */
+SR_PRIV int ut181a_send_cmd_setrange(struct sr_serial_dev_inst *serial, uint8_t range)
+{
+ uint8_t cmd[sizeof(uint8_t) + sizeof(uint8_t)];
+ size_t cmd_off;
+
+ cmd_off = 0;
+ cmd[cmd_off++] = CMD_CODE_SET_RANGE;
+ cmd[cmd_off++] = range;
+
+ return ut181a_send_frame(serial, cmd, cmd_off);
+}
+
+/* Construct and transmit "monitor on/off" command. */
+SR_PRIV int ut181a_send_cmd_monitor(struct sr_serial_dev_inst *serial, gboolean on)
+{
+ uint8_t cmd[sizeof(uint8_t) + sizeof(uint8_t)];
+ size_t cmd_off;
+
+ cmd_off = 0;
+ cmd[cmd_off++] = CMD_CODE_SET_MONITOR;
+ cmd[cmd_off++] = on ? 1 : 0;
+
+ return ut181a_send_frame(serial, cmd, cmd_off);
+}
+
+/* Construct and transmit "get saved measurements count" command. */
+SR_PRIV int ut181a_send_cmd_get_save_count(struct sr_serial_dev_inst *serial)
+{
+ uint8_t cmd;
+
+ cmd = CMD_CODE_GET_SAVED_COUNT;
+ return ut181a_send_frame(serial, &cmd, sizeof(cmd));
+}
+
+/*
+ * Construct and transmit "get saved measurement value" command.
+ * Important: Callers use 0-based index, protocol needs 1-based index.
+ */
+SR_PRIV int ut181a_send_cmd_get_saved_value(struct sr_serial_dev_inst *serial, size_t idx)
+{
+ uint8_t cmd[sizeof(uint8_t) + sizeof(uint16_t)];
+ size_t cmd_off;
+
+ cmd_off = 0;
+ cmd[cmd_off++] = CMD_CODE_GET_SAVED_MEAS;
+ WL16(&cmd[cmd_off], idx + 1);
+ cmd_off += sizeof(uint16_t);
+
+ return ut181a_send_frame(serial, cmd, sizeof(cmd));
+}
+
+/* Construct and transmit "get recordings count" command. */
+SR_PRIV int ut181a_send_cmd_get_recs_count(struct sr_serial_dev_inst *serial)
+{
+ uint8_t cmd;
+
+ cmd = CMD_CODE_GET_RECS_COUNT;
+ return ut181a_send_frame(serial, &cmd, sizeof(cmd));
+}
+
+/*
+ * Construct and transmit "get recording information" command.
+ * Important: Callers use 0-based index, protocol needs 1-based index.
+ */
+SR_PRIV int ut181a_send_cmd_get_rec_info(struct sr_serial_dev_inst *serial, size_t idx)
+{
+ uint8_t cmd[sizeof(uint8_t) + sizeof(uint16_t)];
+ size_t cmd_off;
+
+ cmd_off = 0;
+ cmd[cmd_off++] = CMD_CODE_GET_REC_INFO;
+ WL16(&cmd[cmd_off], idx + 1);
+ cmd_off += sizeof(uint16_t);
+
+ return ut181a_send_frame(serial, cmd, sizeof(cmd));
+}
+
+/*
+ * Construct and transmit "get recording samples" command.
+ * Important: Callers use 0-based index, protocol needs 1-based index.
+ */
+SR_PRIV int ut181a_send_cmd_get_rec_samples(struct sr_serial_dev_inst *serial, size_t idx, size_t off)
+{
+ uint8_t cmd[sizeof(uint8_t) + sizeof(uint16_t) + sizeof(uint32_t)];
+ size_t cmd_off;
+
+ cmd_off = 0;
+ cmd[cmd_off++] = CMD_CODE_GET_REC_SAMPLES;
+ WL16(&cmd[cmd_off], idx + 1);
+ cmd_off += sizeof(uint16_t);
+ WL32(&cmd[cmd_off], off + 1);
+ cmd_off += sizeof(uint32_t);
+
+ return ut181a_send_frame(serial, cmd, sizeof(cmd));
+}
+
+/* TODO
+ * Construct and transmit "record on/off" command. Requires a caption,
+ * an interval, and a duration to start a recording. Recordings can get
+ * stopped upon request, or end when the requested duration has passed.
+ */
+
+/**
+ * Specify which kind of response to wait for.
+ *
+ * @param[in] devc The device context.
+ * @param[in] want_code Reply code wanted, boolean.
+ * @param[in] want_data Reply data wanted, boolean.
+ * @param[in] want_rsp_type Special response type wanted.
+ * @param[in] want_measure Measurement wanted, boolean.
+ * @param[in] want_rec_count Records count wanted, boolean.
+ * @param[in] want_save_count Saved count wanted, boolean.
+ * @param[in] want_sample_count Samples count wanted, boolean.
+ */
+SR_PRIV int ut181a_configure_waitfor(struct dev_context *devc,
+ gboolean want_code, enum ut181_cmd_code want_data,
+ enum ut181_rsp_type want_rsp_type,
+ gboolean want_measure, gboolean want_rec_count,
+ gboolean want_save_count, gboolean want_sample_count)
+{
+
+ if (want_rec_count)
+ want_data = CMD_CODE_GET_RECS_COUNT;
+ if (want_save_count)
+ want_data = CMD_CODE_GET_SAVED_COUNT;
+ if (want_sample_count)
+ want_data = CMD_CODE_GET_REC_SAMPLES;
+
+ memset(&devc->wait_state, 0, sizeof(devc->wait_state));
+ devc->wait_state.want_code = want_code;
+ devc->wait_state.want_data = want_data;
+ devc->wait_state.want_rsp_type = want_rsp_type;
+ devc->wait_state.want_measure = want_measure;
+ memset(&devc->last_data, 0, sizeof(devc->last_data));
+
+ return SR_OK;
+}
+
+/**
+ * Wait for a response (or timeout) after a command was sent.
+ *
+ * @param[in] sdi The device instance.
+ * @param[in] timeout_ms The timeout in milliseconds.
+ *
+ * @returns SR_OK upon success, SR_ERR_* upon error.
+ *
+ * This routine waits for the complete reception of a response (any kind)
+ * after a command was previously sent by the caller, or terminates when
+ * the timeout has expired without reception of a response. Callers need
+ * to check the kind of response (data values, or status, or error codes).
+ */
+SR_PRIV int ut181a_waitfor_response(const struct sr_dev_inst *sdi, int timeout_ms)
{
- const struct sr_dev_inst *sdi;
+ 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;
struct dev_context *devc;
(void)fd;
- if (!(sdi = cb_data))
+ sdi = cb_data;
+ if (!sdi)
return TRUE;
-
- if (!(devc = sdi->priv))
+ serial = sdi->conn;
+ if (!serial)
return TRUE;
+ devc = sdi->priv;
+
+ if (revents & G_IO_IN)
+ (void)ut181a_receive_data(sdi);
- if (revents == G_IO_IN) {
- /* TODO */
+ if (sdi->status == SR_ST_STOPPING) {
+ if (devc->data_source == DATA_SOURCE_LIVE) {
+ sdi->status = SR_ST_INACTIVE;
+ (void)ut181a_send_cmd_monitor(serial, FALSE);
+ (void)ut181a_waitfor_response(sdi, 100);
+ }
+ serial_source_remove(sdi->session, serial);
+ std_session_send_df_end(sdi);
}
return TRUE;