1374 lines
42 KiB
C
1374 lines
42 KiB
C
/*
|
|
* This file is part of the libsigrok project.
|
|
*
|
|
* Copyright (C) 2018 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
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
*
|
|
* EEVblog 121GW 19-bytes binary protocol parser.
|
|
*
|
|
* @internal
|
|
*
|
|
* Note that this protocol is different from other meters. We need not
|
|
* decode the LCD presentation (segments a-g and dot of seven segment
|
|
* displays). Neither need we decode a textual presentation consisting
|
|
* of number strings with decimals, and scale/quantity suffixes. Instead
|
|
* a binary packet is received which contains an unsigned mantissa for
|
|
* the value, and a number of boolean flags as well as bitfields for modes
|
|
* and ranges.
|
|
*
|
|
* But the protocol is also similar to the four-display variant of the
|
|
* metex14 protocol. A single DMM packet contains information for two
|
|
* displays and a bargraph, as well as several flags corresponding to
|
|
* display indicators and global device state. The vendor's documentation
|
|
* refers to these sections as "main", "sub", "bar", and "icon".
|
|
*
|
|
* It's essential to understand that the serial-dmm API is only able to
|
|
* communicate a single float value (including its precision and quantity
|
|
* details) in a single parse call. Which is why we keep a channel index
|
|
* in the 'info' structure, and run the parse routine several times upon
|
|
* reception of a single packet. This approach is shared with the metex14
|
|
* parser.
|
|
*
|
|
* The parse routine here differs from other DMM parsers which typically
|
|
* are split into routines which parse a value (get a number and exponent),
|
|
* parse flags, and handle flags which were parsed before. The 121GW
|
|
* meter's packets don't fit this separation naturally, getting the value
|
|
* and related flags heavily depends on which display shall get inspected,
|
|
* thus should be done at the same time. Filling in an 'info' structure
|
|
* from packet content first, and mapping this 'info' to the 'analog'
|
|
* details then still is very useful for maintainability.
|
|
*
|
|
* TODO:
|
|
* - The meter is feature packed. This implementation does support basic
|
|
* operation (voltage, current, power, resistance, continuity, diode,
|
|
* capacitance, temperature). Support for remaining modes, previously
|
|
* untested ranges, and advanced features (DC+AC, VA power, dB gain,
|
|
* burden voltage) may be missing or incomplete. Ranges support and
|
|
* value scaling should be considered "under development" in general
|
|
* until test coverage was increased. Some flags are not evaluated
|
|
* correctly yet, or not at all (min/max/avg, memory).
|
|
* - Test previously untested modes: current, power, gain, sub display
|
|
* modes. Test untested ranges (voltage above 30V, temperature above
|
|
* 30deg (into the hundreds), negative temperatures, large resistors,
|
|
* large capacitors). Test untested features (min/max/avg, 1ms peak,
|
|
* log memory).
|
|
* - It's assumed that a continuous data stream was arranged for. This
|
|
* implementation does not support the "packet request" API. Also I
|
|
* was to understand that once the request was sent (write 0300 to
|
|
* handle 9, after connecting) no further request is needed. Only
|
|
* the loss of communication may need recovery, which we leave as an
|
|
* option for later improvement, or as a feature of an external helper
|
|
* which feeds the COM port from Bluetooth communication data, or
|
|
* abstracts away the BLE communication.
|
|
*
|
|
* Implementation notes:
|
|
* - Yes some ranges seem duplicate but that's fine. The meter's packets
|
|
* do provide multiple range indices for some of the modes which do
|
|
* communicate values in the same range of values.
|
|
* - Some of the packet's bits don't match the available documentation.
|
|
* Some of the meter's features are not available to the PC side by
|
|
* means of inspecting packets.
|
|
* - Bit 5 of "bar value" was seen with value 1 in FREQ and OHM:
|
|
* f2 17 84 21 21 08 00 00 00 64 01 01 17 12 37 02 40 00 7d
|
|
* So we keep the test around but accept when it fails.
|
|
* - The "gotta beep" activity of continuity/break test mode is not
|
|
* available in the packets.
|
|
* - The interpretation of range indices depends on the specific mode
|
|
* (meter's function, and range when selectable by the user like mV).
|
|
* As does the precision of results.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <ctype.h>
|
|
#include <glib.h>
|
|
#include <math.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include "libsigrok/libsigrok.h"
|
|
#include "libsigrok-internal.h"
|
|
|
|
#define LOG_PREFIX "eev121gw"
|
|
|
|
/*
|
|
* TODO:
|
|
* When these bit field extraction helpers move to some common location,
|
|
* their names may need adjustment to reduce the potential for conflicts.
|
|
*/
|
|
// #define BIT(n) (1UL << (n))
|
|
#define MASK(len) ((1UL << (len)) - 1)
|
|
#define FIELD_PL(v, pos, len) (((v) >> (pos)) & MASK(len))
|
|
#define FIELD_NL(v, name) FIELD_PL(v, POS_ ## name, LEN_ ## name)
|
|
#define FIELD_NB(v, name) FIELD_PL(v, POS_ ## name, 1)
|
|
|
|
/*
|
|
* Support compile time checks for expected sizeof() results etc, like
|
|
* STATIC_ASSERT(sizeof(struct packet) == 19, "packet size");
|
|
* Probably should go to some common location.
|
|
* See http://www.pixelbeat.org/programming/gcc/static_assert.html for details.
|
|
*/
|
|
#define ASSERT_CONCAT_(a, b) a ## b
|
|
#define ASSERT_CONCAT(a, b) ASSERT_CONCAT_(a, b)
|
|
/* These can't be used after statements in c89. */
|
|
#ifdef __COUNTER__
|
|
#define STATIC_ASSERT(e, m) \
|
|
; enum { ASSERT_CONCAT(static_assert_, __COUNTER__) = 1 / (int)(!!(e)) }
|
|
#else
|
|
/*
|
|
* This can't be used twice on the same line so ensure if using in headers
|
|
* that the headers are not included twice (by wrapping in #ifndef...#endif).
|
|
* Note it doesn't cause an issue when used on same line of separate modules
|
|
* compiled with gcc -combine -fwhole-program.
|
|
*/
|
|
#define STATIC_ASSERT(e, m) \
|
|
; enum { ASSERT_CONCAT(assert_line_, __LINE__) = 1 / (int)(!!(e)) }
|
|
#endif
|
|
|
|
/*
|
|
* Symbolic identifiers for access to the packet's payload. "Offsets"
|
|
* address bytes within the packet. "Positions" specify the (lowest)
|
|
* bit number of a field, "lengths" specify the fields' number of bits.
|
|
* "Values" specify magic values or fixed content (SBZ, RSV, etc).
|
|
*/
|
|
enum eev121gw_packet_offs {
|
|
OFF_START_CMD,
|
|
#define VAL_START_CMD 0xf2
|
|
OFF_SERIAL_3,
|
|
OFF_SERIAL_2,
|
|
OFF_SERIAL_1,
|
|
OFF_SERIAL_0,
|
|
#define POS_SERIAL_YEAR 24
|
|
#define LEN_SERIAL_YEAR 8
|
|
#define POS_SERIAL_MONTH 20
|
|
#define LEN_SERIAL_MONTH 4
|
|
#define POS_SERIAL_NUMBER 0
|
|
#define LEN_SERIAL_NUMBER 20
|
|
OFF_MAIN_MODE,
|
|
#define POS_MAIN_MODE_VAL_U 6
|
|
#define LEN_MAIN_MODE_VAL_U 2
|
|
#define POS_MAIN_MODE_RSV_5 5
|
|
#define POS_MAIN_MODE_MODE 0
|
|
#define LEN_MAIN_MODE_MODE 5
|
|
OFF_MAIN_RANGE,
|
|
#define POS_MAIN_RANGE_OFL 7
|
|
#define POS_MAIN_RANGE_SIGN 6
|
|
#define POS_MAIN_RANGE_DEGC 5
|
|
#define POS_MAIN_RANGE_DEGF 4
|
|
#define POS_MAIN_RANGE_RANGE 0
|
|
#define LEN_MAIN_RANGE_RANGE 4
|
|
OFF_MAIN_VAL_H,
|
|
OFF_MAIN_VAL_L,
|
|
OFF_SUB_MODE,
|
|
#define POS_SUB_MODE_MODE 0
|
|
#define LEN_SUB_MODE_MODE 8
|
|
OFF_SUB_RANGE,
|
|
#define POS_SUB_RANGE_OFL 7
|
|
#define POS_SUB_RANGE_SIGN 6
|
|
#define POS_SUB_RANGE_K 5
|
|
#define POS_SUB_RANGE_HZ 4
|
|
#define POS_SUB_RANGE_RSV_3 3
|
|
#define POS_SUB_RANGE_POINT 0
|
|
#define LEN_SUB_RANGE_POINT 3
|
|
OFF_SUB_VAL_H,
|
|
OFF_SUB_VAL_L,
|
|
OFF_BAR_STATUS,
|
|
#define POS_BAR_STATUS_RSV_5 5
|
|
#define LEN_BAR_STATUS_RSV_5 3
|
|
#define POS_BAR_STATUS_USE 4
|
|
#define POS_BAR_STATUS_150 3
|
|
#define POS_BAR_STATUS_SIGN 2
|
|
#define POS_BAR_STATUS_1K_500 0
|
|
#define LEN_BAR_STATUS_1K_500 2
|
|
OFF_BAR_VALUE,
|
|
#define POS_BAR_VALUE_RSV_6 6
|
|
#define LEN_BAR_VALUE_RSV_6 2
|
|
#define POS_BAR_VALUE_RSV_5 5
|
|
#define POS_BAR_VALUE_VALUE 0
|
|
#define LEN_BAR_VALUE_VALUE 5
|
|
OFF_ICON_STS_1,
|
|
#define POS_ICON_STS1_DEGC 7
|
|
#define POS_ICON_STS1_1KHZ 6
|
|
#define POS_ICON_STS1_1MSPK 5
|
|
#define POS_ICON_STS1_DCAC 3
|
|
#define LEN_ICON_STS1_DCAC 2
|
|
#define POS_ICON_STS1_AUTO 2
|
|
#define POS_ICON_STS1_APO 1
|
|
#define POS_ICON_STS1_BAT 0
|
|
OFF_ICON_STS_2,
|
|
#define POS_ICON_STS2_DEGF 7
|
|
#define POS_ICON_STS2_BT 6
|
|
#define POS_ICON_STS2_UNK 5 /* TODO: What is this flag? 20mA loop current? */
|
|
#define POS_ICON_STS2_REL 4
|
|
#define POS_ICON_STS2_DBM 3
|
|
#define POS_ICON_STS2_MINMAX 0 /* TODO: How to interpret the 3-bit field? */
|
|
#define LEN_ICON_STS2_MINMAX 3
|
|
OFF_ICON_STS_3,
|
|
#define POS_ICON_STS3_RSV_7 7
|
|
#define POS_ICON_STS3_TEST 6
|
|
#define POS_ICON_STS3_MEM 4 /* TODO: How to interpret the 2-bit field? */
|
|
#define LEN_ICON_STS3_MEM 2
|
|
#define POS_ICON_STS3_AHOLD 2
|
|
#define LEN_ICON_STS3_AHOLD 2
|
|
#define POS_ICON_STS3_AC 1
|
|
#define POS_ICON_STS3_DC 0
|
|
OFF_CHECKSUM,
|
|
/* This is not an offset, but the packet's "byte count". */
|
|
PACKET_LAST_OFF,
|
|
};
|
|
|
|
STATIC_ASSERT(PACKET_LAST_OFF == EEV121GW_PACKET_SIZE,
|
|
"byte offsets vs packet length mismatch");
|
|
|
|
enum mode_codes {
|
|
/* Modes for 'main' and 'sub' displays. */
|
|
MODE_LOW_Z = 0,
|
|
MODE_DC_V = 1,
|
|
MODE_AC_V = 2,
|
|
MODE_DC_MV = 3,
|
|
MODE_AC_MV = 4,
|
|
MODE_TEMP = 5,
|
|
MODE_FREQ = 6,
|
|
MODE_PERIOD = 7,
|
|
MODE_DUTY = 8,
|
|
MODE_RES = 9,
|
|
MODE_CONT = 10,
|
|
MODE_DIODE = 11,
|
|
MODE_CAP = 12,
|
|
MODE_AC_UVA = 13,
|
|
MODE_AC_MVA = 14,
|
|
MODE_AC_VA = 15,
|
|
MODE_AC_UA = 16,
|
|
MODE_DC_UA = 17,
|
|
MODE_AC_MA = 18,
|
|
MODE_DC_MA = 19,
|
|
MODE_AC_A = 20,
|
|
MODE_DC_A = 21,
|
|
MODE_DC_UVA = 22,
|
|
MODE_DC_MVA = 23,
|
|
MODE_DC_VA = 24,
|
|
/* More modes for 'sub' display. */
|
|
MODE_SUB_TEMPC = 100,
|
|
MODE_SUB_TEMPF = 105,
|
|
MODE_SUB_BATT = 110,
|
|
MODE_SUB_APO_ON = 120,
|
|
MODE_SUB_APO_OFF = 125,
|
|
MODE_SUB_YEAR = 130,
|
|
MODE_SUB_DATE = 135,
|
|
MODE_SUB_TIME = 140,
|
|
MODE_SUB_B_VOLT = 150,
|
|
MODE_SUB_LCD = 160,
|
|
MODE_SUB_CONT_PARM_0 = 170,
|
|
MODE_SUB_CONT_PARM_1 = 171,
|
|
MODE_SUB_CONT_PARM_2 = 172,
|
|
MODE_SUB_CONT_PARM_3 = 173,
|
|
MODE_SUB_DBM = 180,
|
|
MODE_SUB_IVAL = 190,
|
|
};
|
|
|
|
enum range_codes {
|
|
RANGE_0,
|
|
RANGE_1,
|
|
RANGE_2,
|
|
RANGE_3,
|
|
RANGE_4,
|
|
RANGE_5,
|
|
RANGE_6,
|
|
RANGE_MAX,
|
|
};
|
|
|
|
enum bar_range_codes {
|
|
BAR_RANGE_5,
|
|
BAR_RANGE_50,
|
|
BAR_RANGE_500,
|
|
BAR_RANGE_1000,
|
|
};
|
|
#define BAR_VALUE_MAX 25
|
|
|
|
enum acdc_codes {
|
|
ACDC_NONE,
|
|
ACDC_DC,
|
|
ACDC_AC,
|
|
ACDC_ACDC,
|
|
};
|
|
|
|
SR_PRIV const char *eev121gw_channel_formats[EEV121GW_DISPLAY_COUNT] = {
|
|
/*
|
|
* TODO:
|
|
* The "main", "sub", "bar" names were taken from the packet
|
|
* description. Will users prefer "primary", "secondary", and
|
|
* "bargraph" names? Or even-length "pri", "sec", "bar" instead?
|
|
*/
|
|
"main", "sub", "bar",
|
|
};
|
|
|
|
/*
|
|
* See page 69 in the 2018-09-24 manual for a table of modes and their
|
|
* respective ranges ("Calibration Reference Table"). This is the input
|
|
* to get the number of significant digits, and the decimal's position.
|
|
*/
|
|
struct mode_range_item {
|
|
const char *desc; /* Description, for diagnostics. */
|
|
int digits; /* Number of significant digits, see @ref sr_analog_encoding. */
|
|
int factor; /* Factor to convert the uint to a float. */
|
|
};
|
|
|
|
struct mode_range_items {
|
|
size_t range_count;
|
|
const struct mode_range_item ranges[RANGE_MAX];
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_lowz = {
|
|
.range_count = 1,
|
|
.ranges = {
|
|
{ .desc = "600.0V", .digits = 1, .factor = 1, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_volts = {
|
|
.range_count = 4,
|
|
.ranges = {
|
|
{ .desc = "5.0000V", .digits = 4, .factor = 4, },
|
|
{ .desc = "50.000V", .digits = 3, .factor = 3, },
|
|
{ .desc = "500.00V", .digits = 2, .factor = 2, },
|
|
{ .desc = "600.0V", .digits = 1, .factor = 1, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_millivolts = {
|
|
.range_count = 2,
|
|
.ranges = {
|
|
{ .desc = "50.000mV", .digits = 6, .factor = 6, },
|
|
{ .desc = "500.00mV", .digits = 5, .factor = 5, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_temp = {
|
|
.range_count = 1,
|
|
.ranges = {
|
|
{ .desc = "-200.0C ~ 1350.0C", .digits = 1, .factor = 1, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_freq = {
|
|
.range_count = 5,
|
|
.ranges = {
|
|
{ .desc = "99.999Hz", .digits = 3, .factor = 3, },
|
|
{ .desc = "999.99Hz", .digits = 2, .factor = 2, },
|
|
{ .desc = "9.9999kHz", .digits = 1, .factor = 1, },
|
|
{ .desc = "99.999kHz", .digits = 0, .factor = 0, },
|
|
{ .desc = "999.99kHz", .digits = -1, .factor = -1, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_period = {
|
|
.range_count = 3,
|
|
.ranges = {
|
|
{ .desc = "9.9999ms", .digits = 7, .factor = 7, },
|
|
{ .desc = "99.999ms", .digits = 6, .factor = 6, },
|
|
{ .desc = "999.99ms", .digits = 5, .factor = 5, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_duty = {
|
|
.range_count = 1,
|
|
.ranges = {
|
|
{ .desc = "99.9%", .digits = 1, .factor = 1, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_res = {
|
|
.range_count = 7,
|
|
.ranges = {
|
|
{ .desc = "50.000R", .digits = 3, .factor = 3, },
|
|
{ .desc = "500.00R", .digits = 2, .factor = 2, },
|
|
{ .desc = "5.0000k", .digits = 1, .factor = 1, },
|
|
{ .desc = "50.000k", .digits = 0, .factor = 0, },
|
|
{ .desc = "500.00k", .digits = -1, .factor = -1, },
|
|
{ .desc = "5.0000M", .digits = -2, .factor = -2, },
|
|
{ .desc = "50.000M", .digits = -3, .factor = -3, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_cont = {
|
|
.range_count = 1,
|
|
.ranges = {
|
|
{ .desc = "500.00R", .digits = 2, .factor = 2, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_diode = {
|
|
.range_count = 2,
|
|
.ranges = {
|
|
{ .desc = "3.0000V", .digits = 4, .factor = 4, },
|
|
{ .desc = "15.000V", .digits = 3, .factor = 3, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_cap = {
|
|
.range_count = 6,
|
|
.ranges = {
|
|
{ .desc = "10.00n", .digits = 11, .factor = 11, },
|
|
{ .desc = "100.0n", .digits = 10, .factor = 10, },
|
|
{ .desc = "1.000u", .digits = 9, .factor = 9, },
|
|
{ .desc = "10.00u", .digits = 8, .factor = 8, },
|
|
{ .desc = "100.0u", .digits = 7, .factor = 7, },
|
|
{ .desc = "10.00m", .digits = 5, .factor = 5, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_pow_va = {
|
|
.range_count = 4,
|
|
.ranges = {
|
|
{ .desc = "2500.0mVA", .digits = 4, .factor = 4, },
|
|
{ .desc = "25000.mVA", .digits = 3, .factor = 3, },
|
|
{ .desc = "25.000VA", .digits = 3, .factor = 3, },
|
|
{ .desc = "500.00VA", .digits = 2, .factor = 2, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_pow_mva = {
|
|
.range_count = 4,
|
|
.ranges = {
|
|
{ .desc = "25.000mVA", .digits = 6, .factor = 6, },
|
|
{ .desc = "250.00mVA", .digits = 5, .factor = 5, },
|
|
{ .desc = "250.00mVA", .digits = 5, .factor = 5, },
|
|
{ .desc = "2500.0mVA", .digits = 4, .factor = 4, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_pow_uva = {
|
|
.range_count = 4,
|
|
.ranges = {
|
|
{ .desc = "250.00uVA", .digits = 8, .factor = 8, },
|
|
{ .desc = "2500.0uVA", .digits = 7, .factor = 7, },
|
|
{ .desc = "2500.0uVA", .digits = 7, .factor = 7, },
|
|
{ .desc = "25000.uVA", .digits = 6, .factor = 6, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_curr_a = {
|
|
.range_count = 3,
|
|
.ranges = {
|
|
{ .desc = "500.00mA", .digits = 5, .factor = 5, },
|
|
{ .desc = "5.0000A", .digits = 4, .factor = 4, },
|
|
{ .desc = "10.000A", .digits = 3, .factor = 3, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_curr_ma = {
|
|
.range_count = 2,
|
|
.ranges = {
|
|
{ .desc = "5.0000mA", .digits = 7, .factor = 7, },
|
|
{ .desc = "50.000mA", .digits = 6, .factor = 6, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_curr_ua = {
|
|
.range_count = 2,
|
|
.ranges = {
|
|
{ .desc = "50.000uA", .digits = 9, .factor = 9, },
|
|
{ .desc = "500.00uA", .digits = 8, .factor = 8, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items *mode_ranges_main[] = {
|
|
[MODE_LOW_Z] = &mode_ranges_lowz,
|
|
[MODE_DC_V] = &mode_ranges_volts,
|
|
[MODE_AC_V] = &mode_ranges_volts,
|
|
[MODE_DC_MV] = &mode_ranges_millivolts,
|
|
[MODE_AC_MV] = &mode_ranges_millivolts,
|
|
[MODE_TEMP] = &mode_ranges_temp,
|
|
[MODE_FREQ] = &mode_ranges_freq,
|
|
[MODE_PERIOD] = &mode_ranges_period,
|
|
[MODE_DUTY] = &mode_ranges_duty,
|
|
[MODE_RES] = &mode_ranges_res,
|
|
[MODE_CONT] = &mode_ranges_cont,
|
|
[MODE_DIODE] = &mode_ranges_diode,
|
|
[MODE_CAP] = &mode_ranges_cap,
|
|
[MODE_DC_VA] = &mode_ranges_pow_va,
|
|
[MODE_AC_VA] = &mode_ranges_pow_va,
|
|
[MODE_DC_MVA] = &mode_ranges_pow_mva,
|
|
[MODE_AC_MVA] = &mode_ranges_pow_mva,
|
|
[MODE_DC_UVA] = &mode_ranges_pow_uva,
|
|
[MODE_AC_UVA] = &mode_ranges_pow_uva,
|
|
[MODE_DC_A] = &mode_ranges_curr_a,
|
|
[MODE_AC_A] = &mode_ranges_curr_a,
|
|
[MODE_DC_MA] = &mode_ranges_curr_ma,
|
|
[MODE_AC_MA] = &mode_ranges_curr_ma,
|
|
[MODE_DC_UA] = &mode_ranges_curr_ua,
|
|
[MODE_AC_UA] = &mode_ranges_curr_ua,
|
|
};
|
|
|
|
/*
|
|
* The secondary display encodes SI units / scaling differently from the
|
|
* main display, and fewer ranges are available. So we share logic between
|
|
* displays for scaling, but have to keep separate tables for the displays.
|
|
*/
|
|
|
|
static const struct mode_range_items mode_ranges_temp_sub = {
|
|
.range_count = 2,
|
|
.ranges = {
|
|
[1] = { .desc = "sub 100.0C", .digits = 1, .factor = 1, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_freq_sub = {
|
|
.range_count = 4,
|
|
.ranges = {
|
|
[1] = { .desc = "999.9Hz", .digits = 1, .factor = 1, },
|
|
[2] = { .desc = "99.99Hz", .digits = 2, .factor = 2, },
|
|
[3] = { .desc = "9.999kHz", .digits = 3, .factor = 3, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_batt_sub = {
|
|
.range_count = 2,
|
|
.ranges = {
|
|
[1] = { .desc = "sub 10.0V", .digits = 1, .factor = 1, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_gain_sub = {
|
|
.range_count = 4,
|
|
.ranges = {
|
|
[1] = { .desc = "dbm 5000.0dBm", .digits = 1, .factor = 1, },
|
|
[2] = { .desc = "dbm 500.00dBm", .digits = 2, .factor = 2, },
|
|
[3] = { .desc = "dbm 50.000dBm", .digits = 3, .factor = 3, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items mode_ranges_diode_sub = {
|
|
.range_count = 1,
|
|
.ranges = {
|
|
[0] = { .desc = "diode 15.0V", .digits = 0, .factor = 0, },
|
|
},
|
|
};
|
|
|
|
/* TODO: Complete the list of ranges. Only tested with low voltages so far. */
|
|
static const struct mode_range_items mode_ranges_volts_sub = {
|
|
.range_count = 5,
|
|
.ranges = {
|
|
[4] = { .desc = "5.0000V", .digits = 4, .factor = 4, },
|
|
},
|
|
};
|
|
|
|
/* TODO: Complete the list of ranges. Only tested with low voltages so far. */
|
|
static const struct mode_range_items mode_ranges_mamps_sub = {
|
|
.range_count = 3,
|
|
.ranges = {
|
|
[2] = { .desc = "500.00mA", .digits = 5, .factor = 5, },
|
|
},
|
|
};
|
|
|
|
static const struct mode_range_items *mode_ranges_sub[] = {
|
|
[MODE_DC_V] = &mode_ranges_volts_sub,
|
|
[MODE_AC_V] = &mode_ranges_volts_sub,
|
|
[MODE_DC_A] = &mode_ranges_mamps_sub,
|
|
[MODE_AC_A] = &mode_ranges_mamps_sub,
|
|
[MODE_FREQ] = &mode_ranges_freq_sub,
|
|
[MODE_DIODE] = &mode_ranges_diode_sub,
|
|
[MODE_SUB_TEMPC] = &mode_ranges_temp_sub,
|
|
[MODE_SUB_TEMPF] = &mode_ranges_temp_sub,
|
|
[MODE_SUB_BATT] = &mode_ranges_batt_sub,
|
|
[MODE_SUB_DBM] = &mode_ranges_gain_sub,
|
|
};
|
|
|
|
static const struct mode_range_item *mode_range_get_scale(
|
|
enum eev121gw_display display,
|
|
enum mode_codes mode, enum range_codes range)
|
|
{
|
|
const struct mode_range_items *items;
|
|
const struct mode_range_item *item;
|
|
|
|
if (display == EEV121GW_DISPLAY_MAIN) {
|
|
if (mode >= ARRAY_SIZE(mode_ranges_main))
|
|
return NULL;
|
|
items = mode_ranges_main[mode];
|
|
if (!items || !items->range_count)
|
|
return NULL;
|
|
if (range >= items->range_count)
|
|
return NULL;
|
|
item = &items->ranges[range];
|
|
return item;
|
|
}
|
|
if (display == EEV121GW_DISPLAY_SUB) {
|
|
if (mode >= ARRAY_SIZE(mode_ranges_sub))
|
|
return NULL;
|
|
items = mode_ranges_sub[mode];
|
|
if (!items || !items->range_count)
|
|
return NULL;
|
|
if (range >= items->range_count)
|
|
return NULL;
|
|
item = &items->ranges[range];
|
|
if (!item->desc || !*item->desc)
|
|
return NULL;
|
|
return item;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
SR_PRIV gboolean sr_eev121gw_packet_valid(const uint8_t *buf)
|
|
{
|
|
uint8_t csum;
|
|
size_t idx;
|
|
|
|
/* Leading byte, literal / fixed value. */
|
|
if (buf[OFF_START_CMD] != VAL_START_CMD)
|
|
return FALSE;
|
|
|
|
/* Check some always-zero bits in reserved locations. */
|
|
if (FIELD_NB(buf[OFF_MAIN_MODE], MAIN_MODE_RSV_5))
|
|
return FALSE;
|
|
if (FIELD_NB(buf[OFF_SUB_RANGE], SUB_RANGE_RSV_3))
|
|
return FALSE;
|
|
if (FIELD_NL(buf[OFF_BAR_STATUS], BAR_STATUS_RSV_5))
|
|
return FALSE;
|
|
if (FIELD_NL(buf[OFF_BAR_VALUE], BAR_VALUE_RSV_6))
|
|
return FALSE;
|
|
/* See TODO for bit 5 of "bar value" not always being 0. */
|
|
if (0 && FIELD_NB(buf[OFF_BAR_VALUE], BAR_VALUE_RSV_5))
|
|
return FALSE;
|
|
if (FIELD_NB(buf[OFF_ICON_STS_3], ICON_STS3_RSV_7))
|
|
return FALSE;
|
|
|
|
/* Checksum, XOR over all previous bytes. */
|
|
csum = 0x00;
|
|
for (idx = OFF_START_CMD; idx < OFF_CHECKSUM; idx++)
|
|
csum ^= buf[idx];
|
|
if (csum != buf[OFF_CHECKSUM]) {
|
|
/* Non-critical condition, almost expected to see invalid data. */
|
|
sr_spew("Packet csum: want %02x, got %02x.", csum, buf[OFF_CHECKSUM]);
|
|
return FALSE;
|
|
}
|
|
|
|
sr_spew("Packet valid.");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Parse a protocol packet.
|
|
*
|
|
* @param[in] buf Buffer containing the protocol packet. Must not be NULL.
|
|
* @param[out] floatval Pointer to a float variable. That variable will be
|
|
* modified in-place depending on the protocol packet.
|
|
* Must not be NULL.
|
|
* @param[out] analog Pointer to a struct sr_datafeed_analog. The struct will
|
|
* be filled with data according to the protocol packet.
|
|
* Must not be NULL.
|
|
* @param[out] info Pointer to a struct eevblog_121gw_info. The struct will be
|
|
* filled with data according to the protocol packet.
|
|
* Must not be NULL.
|
|
*
|
|
* @return SR_OK upon success, SR_ERR upon failure. Upon errors, the
|
|
* 'analog' variable contents are undefined and should not be used.
|
|
*/
|
|
SR_PRIV int sr_eev121gw_parse(const uint8_t *buf, float *floatval,
|
|
struct sr_datafeed_analog *analog, void *info)
|
|
{
|
|
struct eev121gw_info *info_local;
|
|
enum eev121gw_display display;
|
|
const char *channel_name;
|
|
uint32_t raw_serial;
|
|
uint8_t raw_main_mode, raw_main_range;
|
|
uint16_t raw_main_value;
|
|
uint8_t raw_sub_mode, raw_sub_range;
|
|
uint16_t raw_sub_value;
|
|
uint8_t raw_bar_status, raw_bar_value;
|
|
uint8_t raw_icon_stat_1, raw_icon_stat_2, raw_icon_stat_3;
|
|
uint32_t uint_value;
|
|
enum mode_codes main_mode;
|
|
enum range_codes main_range;
|
|
enum mode_codes sub_mode;
|
|
enum range_codes sub_range;
|
|
const struct mode_range_item *scale;
|
|
gboolean is_dc, is_sign, use_sign;
|
|
gboolean is_k;
|
|
unsigned int cont_code;
|
|
|
|
info_local = info;
|
|
display = info_local->ch_idx;
|
|
channel_name = eev121gw_channel_formats[display];
|
|
memset(info_local, 0, sizeof(*info_local));
|
|
*floatval = 0.0f;
|
|
|
|
/*
|
|
* Get the packet's bytes into native C language typed variables.
|
|
* This keeps byte position references out of logic/calculations.
|
|
* The start command and CRC were verified before we get here.
|
|
*/
|
|
raw_serial = RB32(&buf[OFF_SERIAL_3]);
|
|
raw_main_mode = R8(&buf[OFF_MAIN_MODE]);
|
|
raw_main_range = R8(&buf[OFF_MAIN_RANGE]);
|
|
raw_main_value = RB16(&buf[OFF_MAIN_VAL_H]);
|
|
raw_sub_mode = R8(&buf[OFF_SUB_MODE]);
|
|
raw_sub_range = R8(&buf[OFF_SUB_RANGE]);
|
|
raw_sub_value = RB16(&buf[OFF_SUB_VAL_H]);
|
|
raw_bar_status = R8(&buf[OFF_BAR_STATUS]);
|
|
raw_bar_value = R8(&buf[OFF_BAR_VALUE]);
|
|
raw_icon_stat_1 = R8(&buf[OFF_ICON_STS_1]);
|
|
raw_icon_stat_2 = R8(&buf[OFF_ICON_STS_2]);
|
|
raw_icon_stat_3 = R8(&buf[OFF_ICON_STS_3]);
|
|
|
|
/*
|
|
* Packets contain a YEAR-MONTH date spec. It's uncertain how
|
|
* this data relates to the device's production or the firmware
|
|
* version. It certainly is not the current date either. Only
|
|
* optionally log this information, it's consistent across all
|
|
* packets (won't change within a session), and will be noisy if
|
|
* always enabled.
|
|
*
|
|
* Packets also contain a user adjustable device identification
|
|
* number (see the SETUP options). This is motivated by support
|
|
* for multiple devices, but won't change here within a session.
|
|
* The user chose to communicate to one specific device when the
|
|
* session started, by means of the conn= spec.
|
|
*
|
|
* It was suggested that this 'serial' field might be used as an
|
|
* additional means to check for a packet's validity (or absence
|
|
* of communication errors). This remains as an option for future
|
|
* improvement.
|
|
*/
|
|
if (0) {
|
|
unsigned int ser_year, ser_mon, ser_nr;
|
|
|
|
ser_year = FIELD_NL(raw_serial, SERIAL_YEAR);
|
|
ser_mon = FIELD_NL(raw_serial, SERIAL_MONTH);
|
|
ser_nr = FIELD_NL(raw_serial, SERIAL_NUMBER);
|
|
sr_spew("Packet: Y-M %x-%x, nr %x.", ser_year, ser_mon, ser_nr);
|
|
}
|
|
|
|
switch (display) {
|
|
|
|
case EEV121GW_DISPLAY_MAIN:
|
|
/*
|
|
* Get those fields which correspond to the main display.
|
|
* The value's mantissa has 18 bits. The sign is separate
|
|
* (and is not universally applicable, mode needs to get
|
|
* inspected). The range's scaling and precision also
|
|
* depend on the mode.
|
|
*/
|
|
main_mode = FIELD_NL(raw_main_mode, MAIN_MODE_MODE);
|
|
main_range = FIELD_NL(raw_main_range, MAIN_RANGE_RANGE);
|
|
scale = mode_range_get_scale(EEV121GW_DISPLAY_MAIN,
|
|
main_mode, main_range);
|
|
if (!scale)
|
|
return SR_ERR_NA;
|
|
info_local->factor = scale->factor;
|
|
info_local->digits = scale->digits;
|
|
|
|
uint_value = raw_main_value;
|
|
uint_value |= FIELD_NL(raw_main_mode, MAIN_MODE_VAL_U) << 16;
|
|
info_local->uint_value = uint_value;
|
|
info_local->is_ofl = FIELD_NB(raw_main_range, MAIN_RANGE_OFL);
|
|
|
|
switch (main_mode) {
|
|
case MODE_LOW_Z:
|
|
is_dc = FALSE;
|
|
if (FIELD_NB(raw_icon_stat_3, ICON_STS3_DC))
|
|
is_dc = TRUE;
|
|
if (FIELD_NB(raw_icon_stat_3, ICON_STS3_AC))
|
|
is_dc = FALSE;
|
|
use_sign = is_dc;
|
|
break;
|
|
case MODE_DC_V:
|
|
case MODE_DC_MV:
|
|
case MODE_TEMP:
|
|
case MODE_DC_UVA:
|
|
case MODE_DC_MVA:
|
|
case MODE_DC_VA:
|
|
case MODE_DC_UA:
|
|
case MODE_DC_MA:
|
|
case MODE_DC_A:
|
|
use_sign = TRUE;
|
|
break;
|
|
default:
|
|
use_sign = FALSE;
|
|
break;
|
|
}
|
|
if (use_sign) {
|
|
is_sign = FIELD_NB(raw_main_range, MAIN_RANGE_SIGN);
|
|
info_local->is_neg = is_sign;
|
|
}
|
|
|
|
switch (main_mode) {
|
|
case MODE_LOW_Z:
|
|
info_local->is_voltage = TRUE;
|
|
/* TODO: Need to determine AC/DC here? */
|
|
info_local->is_volt = TRUE;
|
|
info_local->is_low_pass = TRUE;
|
|
break;
|
|
case MODE_DC_V:
|
|
info_local->is_voltage = TRUE;
|
|
info_local->is_dc = TRUE;
|
|
info_local->is_volt = TRUE;
|
|
break;
|
|
case MODE_AC_V:
|
|
info_local->is_voltage = TRUE;
|
|
info_local->is_volt = TRUE;
|
|
info_local->is_ac = TRUE;
|
|
break;
|
|
case MODE_DC_MV:
|
|
info_local->is_voltage = TRUE;
|
|
info_local->is_dc = TRUE;
|
|
info_local->is_volt = TRUE;
|
|
break;
|
|
case MODE_AC_MV:
|
|
info_local->is_voltage = TRUE;
|
|
info_local->is_volt = TRUE;
|
|
info_local->is_ac = TRUE;
|
|
break;
|
|
case MODE_TEMP:
|
|
info_local->is_temperature = TRUE;
|
|
if (FIELD_NB(raw_main_range, MAIN_RANGE_DEGC))
|
|
info_local->is_celsius = TRUE;
|
|
if (FIELD_NB(raw_main_range, MAIN_RANGE_DEGF))
|
|
info_local->is_fahrenheit = TRUE;
|
|
break;
|
|
case MODE_FREQ:
|
|
info_local->is_frequency = TRUE;
|
|
info_local->is_hertz = TRUE;
|
|
break;
|
|
case MODE_PERIOD:
|
|
info_local->is_period = TRUE;
|
|
info_local->is_seconds = TRUE;
|
|
break;
|
|
case MODE_DUTY:
|
|
info_local->is_duty_cycle = TRUE;
|
|
info_local->is_percent = TRUE;
|
|
break;
|
|
case MODE_RES:
|
|
info_local->is_resistance = TRUE;
|
|
info_local->is_ohm = TRUE;
|
|
break;
|
|
case MODE_CONT:
|
|
info_local->is_continuity = TRUE;
|
|
info_local->is_ohm = TRUE;
|
|
/*
|
|
* In continuity mode the packet provides the
|
|
* resistance in ohms (500R range), but seems to
|
|
* _not_ carry the "boolean" open/closed state
|
|
* which controls the beeper. Users can select
|
|
* whether to trigger at 30R or 300R, and whether
|
|
* to trigger on values below (continuity) or
|
|
* above (cable break) the limit, but we cannot
|
|
* tell what the currently used setting is. So
|
|
* we neither get the beeper's state, nor can we
|
|
* derive it from other information.
|
|
*/
|
|
break;
|
|
case MODE_DIODE:
|
|
info_local->is_diode = TRUE;
|
|
info_local->is_dc = TRUE;
|
|
info_local->is_volt = TRUE;
|
|
break;
|
|
case MODE_CAP:
|
|
info_local->is_capacitance = TRUE;
|
|
info_local->is_farad = TRUE;
|
|
break;
|
|
case MODE_AC_UVA:
|
|
info_local->is_power = TRUE;
|
|
info_local->is_ac = TRUE;
|
|
info_local->is_volt_ampere = TRUE;
|
|
break;
|
|
case MODE_AC_MVA:
|
|
info_local->is_power = TRUE;
|
|
info_local->is_ac = TRUE;
|
|
info_local->is_volt_ampere = TRUE;
|
|
break;
|
|
case MODE_AC_VA:
|
|
info_local->is_power = TRUE;
|
|
info_local->is_ac = TRUE;
|
|
info_local->is_volt_ampere = TRUE;
|
|
break;
|
|
case MODE_AC_UA:
|
|
info_local->is_current = TRUE;
|
|
info_local->is_ac = TRUE;
|
|
info_local->is_ampere = TRUE;
|
|
break;
|
|
case MODE_DC_UA:
|
|
info_local->is_current = TRUE;
|
|
info_local->is_dc = TRUE;
|
|
info_local->is_ampere = TRUE;
|
|
break;
|
|
case MODE_AC_MA:
|
|
info_local->is_current = TRUE;
|
|
info_local->is_ac = TRUE;
|
|
info_local->is_ampere = TRUE;
|
|
break;
|
|
case MODE_DC_MA:
|
|
info_local->is_current = TRUE;
|
|
info_local->is_dc = TRUE;
|
|
info_local->is_ampere = TRUE;
|
|
break;
|
|
case MODE_AC_A:
|
|
info_local->is_current = TRUE;
|
|
info_local->is_ac = TRUE;
|
|
info_local->is_ampere = TRUE;
|
|
break;
|
|
case MODE_DC_A:
|
|
info_local->is_current = TRUE;
|
|
info_local->is_dc = TRUE;
|
|
info_local->is_ampere = TRUE;
|
|
break;
|
|
case MODE_DC_UVA:
|
|
info_local->is_power = TRUE;
|
|
info_local->is_dc = TRUE;
|
|
info_local->is_volt_ampere = TRUE;
|
|
break;
|
|
case MODE_DC_MVA:
|
|
info_local->is_power = TRUE;
|
|
info_local->is_dc = TRUE;
|
|
info_local->is_volt_ampere = TRUE;
|
|
break;
|
|
case MODE_DC_VA:
|
|
info_local->is_power = TRUE;
|
|
info_local->is_dc = TRUE;
|
|
info_local->is_volt_ampere = TRUE;
|
|
break;
|
|
/* Modes 100-199 only apply to the secondary display. */
|
|
default:
|
|
return SR_ERR_NA;
|
|
}
|
|
|
|
/*
|
|
* Inspect the "icons" section, since it is associated
|
|
* with the primary display and global device state.
|
|
*/
|
|
if (FIELD_NB(raw_icon_stat_1, ICON_STS1_1KHZ))
|
|
info_local->is_low_pass = TRUE;
|
|
if (FIELD_NB(raw_icon_stat_1, ICON_STS1_1MSPK))
|
|
info_local->is_1ms_peak = TRUE;
|
|
switch (FIELD_NL(raw_icon_stat_1, ICON_STS1_DCAC)) {
|
|
case ACDC_ACDC:
|
|
info_local->is_ac = TRUE;
|
|
info_local->is_dc = TRUE;
|
|
break;
|
|
case ACDC_AC:
|
|
info_local->is_ac = TRUE;
|
|
break;
|
|
case ACDC_DC:
|
|
info_local->is_dc = TRUE;
|
|
break;
|
|
case ACDC_NONE:
|
|
/* EMPTY */
|
|
break;
|
|
}
|
|
if (FIELD_NB(raw_icon_stat_1, ICON_STS1_AUTO))
|
|
info_local->is_auto_range = TRUE;
|
|
if (FIELD_NB(raw_icon_stat_1, ICON_STS1_APO))
|
|
info_local->is_auto_poweroff = TRUE;
|
|
if (FIELD_NB(raw_icon_stat_1, ICON_STS1_BAT))
|
|
info_local->is_low_batt = TRUE;
|
|
if (FIELD_NB(raw_icon_stat_2, ICON_STS2_BT))
|
|
info_local->is_bt = TRUE;
|
|
/* TODO: Is this the "20mA loop current" flag? */
|
|
if (FIELD_NB(raw_icon_stat_2, ICON_STS2_UNK))
|
|
info_local->is_loop_current = TRUE;
|
|
if (FIELD_NB(raw_icon_stat_2, ICON_STS2_REL))
|
|
info_local->is_rel = TRUE;
|
|
/* dBm only applies to secondary display, not main. */
|
|
switch (FIELD_NL(raw_icon_stat_2, ICON_STS2_MINMAX)) {
|
|
/* TODO: Do inspect the min/max/avg flags. */
|
|
default:
|
|
/* EMPTY */
|
|
break;
|
|
}
|
|
if (FIELD_NB(raw_icon_stat_3, ICON_STS3_TEST))
|
|
info_local->is_test = TRUE;
|
|
/* TODO: How to interpret the 2-bit MEM field? */
|
|
if (FIELD_NL(raw_icon_stat_3, ICON_STS3_MEM))
|
|
info_local->is_mem = TRUE;
|
|
if (FIELD_NL(raw_icon_stat_3, ICON_STS3_AHOLD))
|
|
info_local->is_hold = TRUE;
|
|
/* TODO: Are these for the secondary display? See status-2 ACDC. */
|
|
if (FIELD_NB(raw_icon_stat_3, ICON_STS3_AC))
|
|
info_local->is_ac = TRUE;
|
|
if (FIELD_NB(raw_icon_stat_3, ICON_STS3_DC))
|
|
info_local->is_dc = TRUE;
|
|
|
|
sr_spew("Disp '%s', value: %lu (ov %d, neg %d), mode %d, range %d.",
|
|
channel_name,
|
|
(unsigned long)info_local->uint_value,
|
|
info_local->is_ofl, info_local->is_neg,
|
|
(int)main_mode, (int)main_range);
|
|
/* Advance to the number and units conversion below. */
|
|
break;
|
|
|
|
case EEV121GW_DISPLAY_SUB:
|
|
/*
|
|
* Get those fields which correspond to the secondary
|
|
* display. The value's mantissa has 16 bits. The sign
|
|
* is separate is only applies to some of the modes.
|
|
* Scaling and precision also depend on the mode. The
|
|
* interpretation of the secondary display is different
|
|
* from the main display: The 'range' is not an index
|
|
* into ranges, instead it's the decimal's position.
|
|
* Yet more scaling depends on the mode, to complicate
|
|
* matters. The secondary display uses modes 100-199,
|
|
* and some of the 0-24 modes as well.
|
|
*/
|
|
sub_mode = FIELD_NL(raw_sub_mode, SUB_MODE_MODE);
|
|
sub_range = FIELD_NL(raw_sub_range, SUB_RANGE_POINT);
|
|
scale = mode_range_get_scale(EEV121GW_DISPLAY_SUB,
|
|
sub_mode, sub_range);
|
|
if (!scale)
|
|
return SR_ERR_NA;
|
|
info_local->factor = scale->factor;
|
|
info_local->digits = scale->digits;
|
|
|
|
info_local->uint_value = raw_sub_value;
|
|
info_local->is_ofl = FIELD_NB(raw_sub_range, SUB_RANGE_OFL);
|
|
|
|
switch (sub_mode) {
|
|
case MODE_DC_V:
|
|
case MODE_AC_V:
|
|
case MODE_DC_A:
|
|
case MODE_AC_A:
|
|
case MODE_SUB_TEMPC:
|
|
case MODE_SUB_TEMPF:
|
|
case MODE_SUB_B_VOLT:
|
|
case MODE_SUB_DBM:
|
|
use_sign = TRUE;
|
|
break;
|
|
default:
|
|
use_sign = FALSE;
|
|
break;
|
|
}
|
|
if (use_sign) {
|
|
is_sign = FIELD_NB(raw_sub_range, SUB_RANGE_SIGN);
|
|
info_local->is_neg = is_sign;
|
|
}
|
|
is_k = FIELD_NB(raw_sub_range, SUB_RANGE_K);
|
|
|
|
/*
|
|
* TODO: Re-check the power mode display as more data becomes
|
|
* available.
|
|
*
|
|
* The interpretation of the secondary display in power (VA)
|
|
* modes is uncertain. The mode suggests A or uA units but the
|
|
* value is supposed to be mA without a reliable condition
|
|
* for us to check...
|
|
*
|
|
* f2 17 84 21 21 18 02 00 00 01 04 00 0b 00 00 0a 40 00 3f
|
|
* f2 17 84 21 21 18 02 00 00 15 03 00 00 00 00 0a 40 00 27
|
|
* DC VA DC V / DC A
|
|
* 25.000VA dot 4 / dot 3
|
|
*
|
|
* f2 17 84 21 21 18 00 00 26 01 04 4c 57 00 00 0e 40 00 0f
|
|
* f2 17 84 21 21 18 00 00 26 15 02 00 c7 00 00 0e 40 00 c1
|
|
* 3.8mVA DC 1.9543V
|
|
* 1.98mA (!) DC A + dot 2 -> milli(!) amps?
|
|
*
|
|
* f2 17 84 21 21 17 00 07 85 01 04 4c 5a 00 00 0e 40 00 a9
|
|
* f2 17 84 21 21 17 00 07 85 13 04 26 7b 00 00 0e 40 00 f0
|
|
* 1.925mVA DC 1.9546V
|
|
* 0.9852mA
|
|
*
|
|
* f2 17 84 21 21 16 02 11 e0 01 04 26 39 00 02 0e 40 00 d2
|
|
* f2 17 84 21 21 16 02 11 e0 11 04 12 44 00 02 0e 40 00 8b
|
|
* 457.6uVA DC 0.9785V
|
|
* 0.4676mA (!) DC uA + dot 4 -> milli(!) amps?
|
|
*/
|
|
|
|
switch (sub_mode) {
|
|
case MODE_DC_V:
|
|
info_local->is_voltage = TRUE;
|
|
info_local->is_volt = TRUE;
|
|
break;
|
|
case MODE_DC_A:
|
|
info_local->is_current = TRUE;
|
|
info_local->is_ampere = TRUE;
|
|
break;
|
|
case MODE_FREQ:
|
|
info_local->is_frequency = TRUE;
|
|
info_local->is_hertz = TRUE;
|
|
if (is_k) {
|
|
info_local->factor -= 3;
|
|
info_local->digits -= 3;
|
|
}
|
|
info_local->is_ofl = FALSE;
|
|
break;
|
|
case MODE_SUB_TEMPC:
|
|
info_local->is_temperature = TRUE;
|
|
info_local->is_celsius = TRUE;
|
|
break;
|
|
case MODE_SUB_TEMPF:
|
|
info_local->is_temperature = TRUE;
|
|
info_local->is_fahrenheit = TRUE;
|
|
break;
|
|
case MODE_SUB_BATT:
|
|
/* TODO: How to communicate it's the *battery* voltage? */
|
|
info_local->is_voltage = TRUE;
|
|
info_local->is_volt = TRUE;
|
|
break;
|
|
case MODE_SUB_DBM:
|
|
info_local->is_gain = TRUE;
|
|
info_local->is_dbm = TRUE;
|
|
break;
|
|
case MODE_SUB_CONT_PARM_0:
|
|
case MODE_SUB_CONT_PARM_1:
|
|
case MODE_SUB_CONT_PARM_2:
|
|
case MODE_SUB_CONT_PARM_3:
|
|
/*
|
|
* These "continuity parameters" are special. The
|
|
* least significant bits represent the options:
|
|
*
|
|
* 0xaa = 170 => down 30
|
|
* 0xab = 171 => up 30
|
|
* 0xac = 172 => down 300
|
|
* 0xad = 173 => up 300
|
|
*
|
|
* bit 0 value 0 -> close (cont)
|
|
* bit 0 value 1 -> open (break)
|
|
* bit 1 value 0 -> 30R limit
|
|
* bit 1 value 1 -> 300R limit
|
|
*
|
|
* This "display value" is only seen during setup
|
|
* but not during regular operation of continuity
|
|
* mode. :( In theory we could somehow pass the
|
|
* 30/300 ohm limit to sigrok, but that'd be of
|
|
* somewhat limited use.
|
|
*/
|
|
cont_code = sub_mode - MODE_SUB_CONT_PARM_0;
|
|
info_local->is_resistance = TRUE;
|
|
info_local->is_ohm = TRUE;
|
|
info_local->uint_value = (cont_code & 0x02) ? 300 : 30;
|
|
info_local->is_neg = FALSE;
|
|
info_local->is_ofl = FALSE;
|
|
info_local->factor = 0;
|
|
info_local->digits = 0;
|
|
break;
|
|
case MODE_DIODE:
|
|
/* Displays configured diode test voltage. */
|
|
info_local->is_voltage = TRUE;
|
|
info_local->is_volt = TRUE;
|
|
break;
|
|
|
|
/* Reflecting these to users seems pointless, ignore them. */
|
|
case MODE_SUB_APO_ON:
|
|
case MODE_SUB_APO_OFF:
|
|
case MODE_SUB_LCD:
|
|
case MODE_SUB_YEAR:
|
|
case MODE_SUB_DATE:
|
|
case MODE_SUB_TIME:
|
|
return SR_ERR_NA;
|
|
|
|
/* Unknown / unsupported sub display mode. */
|
|
default:
|
|
return SR_ERR_NA;
|
|
}
|
|
|
|
sr_spew("disp '%s', value: %lu (ov %d, neg %d), mode %d, range %d",
|
|
channel_name,
|
|
(unsigned long)info_local->uint_value,
|
|
info_local->is_ofl, info_local->is_neg,
|
|
(int)sub_mode, (int)sub_range);
|
|
/* Advance to the number and units conversion below. */
|
|
break;
|
|
|
|
case EEV121GW_DISPLAY_BAR:
|
|
/*
|
|
* Get those fields which correspond to the bargraph.
|
|
* There are 26 segments (ticks 0-25), several ranges
|
|
* apply (up to 5, or up to 10, several decades). The
|
|
* bargraph does not apply to all modes and ranges,
|
|
* hence there is a "use" flag (negative logic, blank
|
|
* signal). Bit 5 was also found to have undocumented
|
|
* values, we refuse to use the bargraph value then.
|
|
*/
|
|
if (FIELD_NB(raw_bar_status, BAR_STATUS_USE))
|
|
return SR_ERR_NA;
|
|
if (FIELD_NB(raw_bar_value, BAR_VALUE_RSV_5))
|
|
return SR_ERR_NA;
|
|
uint_value = FIELD_NL(raw_bar_value, BAR_VALUE_VALUE);
|
|
if (uint_value > BAR_VALUE_MAX)
|
|
uint_value = BAR_VALUE_MAX;
|
|
info_local->is_neg = FIELD_NB(raw_bar_status, BAR_STATUS_SIGN);
|
|
switch (FIELD_NL(raw_bar_status, BAR_STATUS_1K_500)) {
|
|
case BAR_RANGE_5:
|
|
/* Full range 5.0, in steps of 0.2 each. */
|
|
uint_value *= 5000 / BAR_VALUE_MAX;
|
|
info_local->factor = 3;
|
|
info_local->digits = 1;
|
|
break;
|
|
case BAR_RANGE_50:
|
|
/* Full range 50, in steps of 2 each. */
|
|
uint_value *= 50 / BAR_VALUE_MAX;
|
|
info_local->factor = 0;
|
|
info_local->digits = 0;
|
|
break;
|
|
case BAR_RANGE_500:
|
|
/* Full range 500, in steps of 20 each. */
|
|
uint_value *= 500 / BAR_VALUE_MAX;
|
|
info_local->factor = 0;
|
|
info_local->digits = -1;
|
|
break;
|
|
case BAR_RANGE_1000:
|
|
/* Full range 1000, in steps of 40 each. */
|
|
uint_value *= 1000 / BAR_VALUE_MAX;
|
|
info_local->factor = 0;
|
|
info_local->digits = -1;
|
|
break;
|
|
default:
|
|
return SR_ERR_NA;
|
|
}
|
|
info_local->uint_value = uint_value;
|
|
info_local->is_unitless = TRUE;
|
|
sr_spew("Disp '%s', value: %u.", channel_name,
|
|
(unsigned int)info_local->uint_value);
|
|
/* Advance to the number and units conversion below. */
|
|
break;
|
|
|
|
default:
|
|
/* Unknown display, programmer's error, ShouldNotHappen(TM). */
|
|
sr_err("Disp '-?-'.");
|
|
return SR_ERR_ARG;
|
|
}
|
|
|
|
/*
|
|
* Convert the unsigned mantissa and its modifiers to a float
|
|
* analog value, including scale and quantity. Do the conversion
|
|
* first, and optionally override the result with 'inf' later.
|
|
* Apply the sign last so that +inf and -inf are supported.
|
|
*/
|
|
*floatval = info_local->uint_value;
|
|
if (info_local->factor)
|
|
*floatval *= powf(10, -info_local->factor);
|
|
if (info_local->is_ofl)
|
|
*floatval = INFINITY;
|
|
if (info_local->is_neg)
|
|
*floatval = -*floatval;
|
|
|
|
analog->encoding->digits = info_local->digits;
|
|
analog->spec->spec_digits = info_local->digits;
|
|
|
|
/*
|
|
* Communicate the measured quantity.
|
|
*/
|
|
/* Determine the quantity itself. */
|
|
if (info_local->is_voltage)
|
|
analog->meaning->mq = SR_MQ_VOLTAGE;
|
|
if (info_local->is_current)
|
|
analog->meaning->mq = SR_MQ_CURRENT;
|
|
if (info_local->is_power)
|
|
analog->meaning->mq = SR_MQ_POWER;
|
|
if (info_local->is_gain)
|
|
analog->meaning->mq = SR_MQ_GAIN;
|
|
if (info_local->is_resistance)
|
|
analog->meaning->mq = SR_MQ_RESISTANCE;
|
|
if (info_local->is_capacitance)
|
|
analog->meaning->mq = SR_MQ_CAPACITANCE;
|
|
if (info_local->is_diode)
|
|
analog->meaning->mq = SR_MQ_VOLTAGE;
|
|
if (info_local->is_temperature)
|
|
analog->meaning->mq = SR_MQ_TEMPERATURE;
|
|
if (info_local->is_continuity)
|
|
analog->meaning->mq = SR_MQ_CONTINUITY;
|
|
if (info_local->is_frequency)
|
|
analog->meaning->mq = SR_MQ_FREQUENCY;
|
|
if (info_local->is_period)
|
|
analog->meaning->mq = SR_MQ_TIME;
|
|
if (info_local->is_duty_cycle)
|
|
analog->meaning->mq = SR_MQ_DUTY_CYCLE;
|
|
if (info_local->is_unitless)
|
|
analog->meaning->mq = SR_MQ_COUNT;
|
|
/* Add AC / DC / DC+AC flags. */
|
|
if (info_local->is_ac)
|
|
analog->meaning->mqflags |= SR_MQFLAG_AC;
|
|
if (info_local->is_dc)
|
|
analog->meaning->mqflags |= SR_MQFLAG_DC;
|
|
/* Specify units. */
|
|
if (info_local->is_ampere)
|
|
analog->meaning->unit = SR_UNIT_AMPERE;
|
|
if (info_local->is_volt)
|
|
analog->meaning->unit = SR_UNIT_VOLT;
|
|
if (info_local->is_volt_ampere)
|
|
analog->meaning->unit = SR_UNIT_VOLT_AMPERE;
|
|
if (info_local->is_dbm)
|
|
analog->meaning->unit = SR_UNIT_DECIBEL_VOLT;
|
|
if (info_local->is_ohm)
|
|
analog->meaning->unit = SR_UNIT_OHM;
|
|
if (info_local->is_farad)
|
|
analog->meaning->unit = SR_UNIT_FARAD;
|
|
if (info_local->is_celsius)
|
|
analog->meaning->unit = SR_UNIT_CELSIUS;
|
|
if (info_local->is_fahrenheit)
|
|
analog->meaning->unit = SR_UNIT_FAHRENHEIT;
|
|
if (info_local->is_hertz)
|
|
analog->meaning->unit = SR_UNIT_HERTZ;
|
|
if (info_local->is_seconds)
|
|
analog->meaning->unit = SR_UNIT_SECOND;
|
|
if (info_local->is_percent)
|
|
analog->meaning->unit = SR_UNIT_PERCENTAGE;
|
|
if (info_local->is_loop_current)
|
|
analog->meaning->unit = SR_UNIT_PERCENTAGE;
|
|
if (info_local->is_unitless)
|
|
analog->meaning->unit = SR_UNIT_UNITLESS;
|
|
if (info_local->is_logic)
|
|
analog->meaning->unit = SR_UNIT_UNITLESS;
|
|
/* Add other indicator flags. */
|
|
if (info_local->is_diode) {
|
|
analog->meaning->mqflags |= SR_MQFLAG_DIODE;
|
|
analog->meaning->mqflags |= SR_MQFLAG_DC;
|
|
}
|
|
if (info_local->is_min)
|
|
analog->meaning->mqflags |= SR_MQFLAG_MIN;
|
|
if (info_local->is_max)
|
|
analog->meaning->mqflags |= SR_MQFLAG_MAX;
|
|
if (info_local->is_avg)
|
|
analog->meaning->mqflags |= SR_MQFLAG_AVG;
|
|
/* TODO: How to communicate info_local->is_1ms_peak? */
|
|
if (info_local->is_rel)
|
|
analog->meaning->mqflags |= SR_MQFLAG_RELATIVE;
|
|
if (info_local->is_hold)
|
|
analog->meaning->mqflags |= SR_MQFLAG_HOLD;
|
|
/* TODO: How to communicate info_local->is_low_pass? */
|
|
if (info_local->is_mem) /* XXX Is REF appropriate here? */
|
|
analog->meaning->mqflags |= SR_MQFLAG_REFERENCE;
|
|
if (info_local->is_auto_range)
|
|
analog->meaning->mqflags |= SR_MQFLAG_AUTORANGE;
|
|
/* TODO: How to communicate info->is_test? What's its meaning at all? */
|
|
/* TODO: How to communicate info->is_auto_poweroff? */
|
|
/* TODO: How to communicate info->is_low_batt? */
|
|
|
|
return SR_OK;
|
|
}
|
|
|
|
/*
|
|
* Parse the same packet multiple times, to extract individual analog
|
|
* values which correspond to several displays of the device. Make sure
|
|
* to keep the channel index in place, even if the parse routine will
|
|
* clear the info structure.
|
|
*/
|
|
SR_PRIV int sr_eev121gw_3displays_parse(const uint8_t *buf, float *floatval,
|
|
struct sr_datafeed_analog *analog, void *info)
|
|
{
|
|
struct eev121gw_info *info_local;
|
|
size_t ch_idx;
|
|
int rc;
|
|
|
|
info_local = info;
|
|
ch_idx = info_local->ch_idx;
|
|
rc = sr_eev121gw_parse(buf, floatval, analog, info);
|
|
info_local->ch_idx = ch_idx + 1;
|
|
|
|
return rc;
|
|
}
|