X-Git-Url: https://sigrok.org/gitweb/?p=libsigrok.git;a=blobdiff_plain;f=src%2Fhardware%2Funi-t-ut181a%2Fprotocol.c;h=93a71e3afd075dfcb848b7aedf4d10a50070c481;hp=d3b1d48189b347aa99c18f50d4dc08b4b55490e9;hb=ebc51109899008694c50977cb2574050b1bc0c89;hpb=3094e9d8ca2b8cb7aad1c3bf41b47f0087e45ff8;ds=sidebyside diff --git a/src/hardware/uni-t-ut181a/protocol.c b/src/hardware/uni-t-ut181a/protocol.c index d3b1d481..93a71e3a 100644 --- a/src/hardware/uni-t-ut181a/protocol.c +++ b/src/hardware/uni-t-ut181a/protocol.c @@ -1,7 +1,7 @@ /* * This file is part of the libsigrok project. * - * Copyright (C) 2019 Gerhard Sittig + * Copyright (C) 2019-2020 Gerhard Sittig * * 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 @@ -17,24 +17,2892 @@ * along with this program. If not, see . */ +/* + * 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 +#include +#include +#include + #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;