uni-t-ut181a: implement device driver for the UNI-T UT181A multimeter

Extend the previously introduced skeleton driver for UNI-T UT181A. Introduce
support for the full multimeter's protocol as it was documented by the ut181a
project. Which covers the retrieval of live readings, saved measurements, and
recordings, in all of the meter's modes and including relative, min/max, and
peak submodes. This implementation also parses compare mode (limits check)
responses, although it cannot express the result in terms of the session feed.

Announce the device as a multimeter as well as a thermometer, it supports
up to two probes including difference mode. When in doubt, prefer usability
over feature coverage (the driver side reflects all properties of the meter,
but not all features can get controlled by the driver). The probe routine
requires that users specify the serial port, and enable serial communication
on the meter.

Several TODO items remain. Comments in the driver code discuss limitations
of the current implementation, as well as cases where the meter's features
don't map well to sigrok's internal presentation. This implementation also
contains (optional, off by default) diagnostics for research on the serial
protocol.
This commit is contained in:
Gerhard Sittig 2019-12-14 15:14:46 +01:00 committed by Uwe Hermann
parent 3094e9d8ca
commit ebc5110989
5 changed files with 3739 additions and 63 deletions

View File

@ -403,6 +403,7 @@ a short list for convenience:
- UNI-T UT61B/C/D: Press the "REL/RS232/USB" button for roughly 1 second.
- UNI-T UT71x: Press the "SEND/-/MAXMIN" button for roughly 1 second.
Briefly pressing the "EXIT" button leaves this mode again.
- UNI-T UT181A: In the "SETUP" menu set "Communication" to "ON".
- UNI-T UT325: Briefly press the "SEND" button (as per manual). However, it
appears that in practice you don't have to press the button (at least on
some versions of the device), simply connect the device via USB.

View File

@ -317,7 +317,7 @@ SR_DRIVER([Teleinfo], [teleinfo], [serial_comm])
SR_DRIVER([Testo], [testo], [libusb])
SR_DRIVER([Tondaj SL-814], [tondaj-sl-814], [serial_comm])
SR_DRIVER([UNI-T DMM], [uni-t-dmm], [libusb])
SR_DRIVER([UNI-T UT181A], [uni-t-ut181a])
SR_DRIVER([UNI-T UT181A], [uni-t-ut181a], [serial_comm])
SR_DRIVER([UNI-T UT32x], [uni-t-ut32x], [serial_comm])
SR_DRIVER([Yokogawa DL/DLM], [yokogawa-dlm])
SR_DRIVER([ZEROPLUS Logic Cube], [zeroplus-logic-cube], [libusb])

View File

@ -1,7 +1,7 @@
/*
* This file is part of the libsigrok project.
*
* Copyright (C) 2019 Gerhard Sittig <gerhard.sittig@gmx.net>
* Copyright (C) 2019-2020 Gerhard Sittig <gerhard.sittig@gmx.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,115 +20,519 @@
#include <config.h>
#include "protocol.h"
static struct sr_dev_driver uni_t_ut181a_driver_info;
static const uint32_t scanopts[] = {
SR_CONF_CONN,
SR_CONF_SERIALCOMM,
};
static const uint32_t drvopts[] = {
SR_CONF_MULTIMETER,
SR_CONF_THERMOMETER, /* Supports two temperature probes and diffs. */
};
static const uint32_t devopts[] = {
SR_CONF_CONN | SR_CONF_GET,
SR_CONF_CONTINUOUS,
SR_CONF_LIMIT_FRAMES | SR_CONF_GET | SR_CONF_SET,
SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET,
SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
SR_CONF_DATA_SOURCE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
SR_CONF_DATALOG | SR_CONF_GET,
SR_CONF_MEASURED_QUANTITY | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
SR_CONF_RANGE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
};
static const char *channel_names[] = {
[UT181A_CH_MAIN] = "P1",
[UT181A_CH_AUX1] = "P2",
[UT181A_CH_AUX2] = "P3",
[UT181A_CH_AUX3] = "P4",
[UT181A_CH_BAR] = "bar",
#if UT181A_WITH_TIMESTAMP
[UT181A_CH_TIME] = "TS",
#endif
};
/*
* (Re-)retrieve the list of recordings and their names. These can change
* without the driver's being aware, the set is under user control.
*
* TODO Need to re-allocate the list of recording names when a larger
* recordings count is seen than previously allocated? This implementation
* assumes a known maximum number of recordings, the manual is vague on
* these limits.
*/
static int ut181a_update_recordings(const struct sr_dev_inst *sdi)
{
struct dev_context *devc;
struct sr_serial_dev_inst *serial;
size_t rec_count, rec_idx;
int ret;
if (!sdi)
return SR_ERR_ARG;
devc = sdi->priv;
serial = sdi->conn;
ret = ut181a_send_cmd_get_recs_count(serial);
if (ret < 0)
return ret;
ret = ut181a_configure_waitfor(devc, FALSE, 0, 0,
FALSE, TRUE, FALSE, FALSE);
if (ret < 0)
return ret;
ret = ut181a_waitfor_response(sdi, 100);
if (ret < 0)
return ret;
rec_count = devc->wait_state.data_value;
if (rec_count > ARRAY_SIZE(devc->record_names))
rec_count = ARRAY_SIZE(devc->record_names);
for (rec_idx = 0; rec_idx < rec_count; rec_idx++) {
devc->info.rec_info.rec_idx = rec_idx;
ret = ut181a_send_cmd_get_rec_info(serial, rec_idx);
if (ret < 0)
return ret;
ret = ut181a_configure_waitfor(devc,
FALSE, CMD_CODE_GET_REC_INFO, 0,
FALSE, FALSE, FALSE, FALSE);
if (ret < 0)
return ret;
ret = ut181a_waitfor_response(sdi, 100);
if (ret < 0)
return ret;
}
devc->record_count = rec_count;
devc->data_source_count = DATA_SOURCE_REC_FIRST + devc->record_count;
return SR_OK;
}
/*
* Retrieve the device's current state. Run monitor mode for some time
* until the 'mode' (meter's current function) became available. There
* is no other way of querying the meter's current state.
*/
static int ut181a_query_initial_state(struct sr_dev_inst *sdi, int timeout_ms)
{
struct dev_context *devc;
struct sr_serial_dev_inst *serial;
gint64 deadline;
int ret;
if (!sdi)
return SR_ERR_ARG;
devc = sdi->priv;
serial = sdi->conn;
devc->info.meas_head.mode = 0;
ret = ut181a_send_cmd_monitor(serial, TRUE);
if (ret < 0)
return ret;
ret = ut181a_configure_waitfor(devc, FALSE, 0, 0,
TRUE, FALSE, FALSE, FALSE);
if (ret < 0)
return ret;
deadline = g_get_monotonic_time();
deadline += timeout_ms * 1000;
while (1) {
ret = ut181a_waitfor_response(sdi, 100);
if (ret < 0)
return ret;
if (devc->info.meas_head.mode)
break;
if (g_get_monotonic_time() >= deadline)
return SR_ERR_DATA;
}
(void)ut181a_send_cmd_monitor(serial, FALSE);
ret = ut181a_configure_waitfor(devc, TRUE, 0, 0,
FALSE, FALSE, FALSE, FALSE);
if (ret < 0)
return ret;
(void)ut181a_waitfor_response(sdi, 100);
return SR_OK;
}
static GSList *scan(struct sr_dev_driver *di, GSList *options)
{
struct drv_context *drvc;
GSList *devices;
const char *conn, *serialcomm;
struct sr_config *src;
GSList *l, *devices;
struct sr_serial_dev_inst *serial;
int ret;
char conn_id[64];
struct sr_dev_inst *sdi;
struct dev_context *devc;
size_t idx, ds_idx;
(void)options;
/*
* Implementor's note:
* Do _not_ add a default conn value here. Always expect users to
* specify the connection. Never match in the absence of a user spec.
*
* Motivation: There is no way to identify the DMM itself. Neither
* are the cable nor its chip unique to the device. They are not even
* specific to the series or the vendor. The DMM ships with a generic
* CP2110 USB-to-UART bridge. Attempts to auto probe will disturb
* other types of devices which may be attached to the probed conn.
*
* On the other hand it's perfectly fine to communicate to the
* device and assume that the device model will accept the requests,
* once the user specified the connection (and the driver), and thus
* instructed this driver to start such activity.
*/
conn = NULL;
serialcomm = "9600/8n1";
for (l = options; l; l = l->next) {
src = l->data;
switch (src->key) {
case SR_CONF_CONN:
conn = g_variant_get_string(src->data, NULL);
break;
case SR_CONF_SERIALCOMM:
serialcomm = g_variant_get_string(src->data, NULL);
break;
}
}
if (!conn)
return NULL;
devices = NULL;
drvc = di->context;
drvc->instances = NULL;
serial = sr_serial_dev_inst_new(conn, serialcomm);
ret = serial_open(serial, SERIAL_RDWR);
snprintf(conn_id, sizeof(conn_id), "%s", serial->port);
serial_flush(serial);
/*
* We cannot identify the device at this point in time.
* Successful open shall suffice for now. More activity
* will communicate to the device later, after the driver
* instance got created. See below for details.
*/
if (ret != SR_OK) {
serial_close(serial);
sr_serial_dev_inst_free(serial);
return devices;
}
/* TODO: scan for devices, either based on a SR_CONF_CONN option
* or on a USB scan. */
sdi = g_malloc0(sizeof(*sdi));
sdi->status = SR_ST_INACTIVE;
sdi->vendor = g_strdup("UNI-T");
sdi->model = g_strdup("UT181A");
sdi->inst_type = SR_INST_SERIAL;
sdi->conn = serial;
sdi->connection_id = g_strdup(conn_id);
devc = g_malloc0(sizeof(*devc));
sdi->priv = devc;
sr_sw_limits_init(&devc->limits);
for (idx = 0; idx < ARRAY_SIZE(channel_names); idx++) {
sr_channel_new(sdi, idx, SR_CHANNEL_ANALOG, TRUE,
channel_names[idx]);
}
return devices;
}
/*
* Run monitor mode for a while to determine the current state
* of the device (which cannot get queried by other means). This
* also deals with devices which happen to already be in monitor
* mode when we connect to them. As a byproduct this query drains
* potentially pending RX data, before getting recording details.
*/
devc->disable_feed = 1;
ret = ut181a_query_initial_state(sdi, 2000);
if (ret < 0) {
serial_close(serial);
sr_serial_dev_inst_free(serial);
return devices;
}
static int dev_open(struct sr_dev_inst *sdi)
{
(void)sdi;
/*
* Number of recordings and their names are dynamic and under
* the user's control. Prepare for a maximum number of string
* labels, and fetch (and re-fetch) their names and current
* count on demand.
*/
devc->data_source_names[DATA_SOURCE_LIVE] = "Live";
devc->data_source_names[DATA_SOURCE_SAVE] = "Save";
for (idx = 0; idx < MAX_REC_COUNT; idx++) {
ds_idx = DATA_SOURCE_REC_FIRST + idx;
devc->data_source_names[ds_idx] = &devc->record_names[idx][0];
}
devc->data_source_count = DATA_SOURCE_REC_FIRST;
ret = ut181a_update_recordings(sdi);
devc->data_source_count = DATA_SOURCE_REC_FIRST + devc->record_count;
if (ret < 0) {
serial_close(serial);
sr_serial_dev_inst_free(serial);
return devices;
}
/* TODO: get handle from sdi->conn and open it. */
devc->disable_feed = 0;
serial_close(serial);
return SR_OK;
}
devices = g_slist_append(devices, sdi);
static int dev_close(struct sr_dev_inst *sdi)
{
(void)sdi;
/* TODO: get handle from sdi->conn and close it. */
return SR_OK;
return std_scan_complete(di, devices);
}
static int config_get(uint32_t key, GVariant **data,
const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
{
int ret;
struct dev_context *devc;
const struct mqopt_item *mqitem;
GVariant *arr[2];
const char *range;
(void)sdi;
(void)data;
(void)cg;
ret = SR_OK;
devc = sdi->priv;
switch (key) {
/* TODO */
case SR_CONF_CONN:
*data = g_variant_new_string(sdi->connection_id);
break;
case SR_CONF_LIMIT_FRAMES:
case SR_CONF_LIMIT_SAMPLES:
case SR_CONF_LIMIT_MSEC:
if (!devc)
return SR_ERR_ARG;
return sr_sw_limits_config_get(&devc->limits, key, data);
case SR_CONF_DATA_SOURCE:
if (!devc)
return SR_ERR_ARG;
*data = g_variant_new_string(devc->data_source_names[devc->data_source]);
break;
case SR_CONF_DATALOG:
if (!devc)
return SR_ERR_ARG;
*data = g_variant_new_boolean(devc->is_recording ? TRUE : FALSE);
break;
case SR_CONF_MEASURED_QUANTITY:
if (!devc)
return SR_ERR_ARG;
mqitem = ut181a_get_mqitem_from_mode(devc->info.meas_head.mode);
if (!mqitem)
return SR_ERR_NA;
arr[0] = g_variant_new_uint32(mqitem->mq);
arr[1] = g_variant_new_uint64(mqitem->mqflags);
*data = g_variant_new_tuple(arr, ARRAY_SIZE(arr));
break;
case SR_CONF_RANGE:
if (!devc)
return SR_ERR_ARG;
range = ut181a_get_range_from_packet_bytes(devc);
if (!range || !*range)
return SR_ERR_NA;
*data = g_variant_new_string(range);
break;
default:
return SR_ERR_NA;
}
return ret;
return SR_OK;
}
static int config_set(uint32_t key, GVariant *data,
const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
{
struct dev_context *devc;
ssize_t idx;
GVariant *tuple_child;
enum sr_mq mq;
enum sr_mqflag mqflags;
uint16_t mode;
int ret;
size_t rec_no;
const char *range;
(void)sdi;
(void)data;
(void)cg;
ret = SR_OK;
devc = sdi->priv;
switch (key) {
/* TODO */
case SR_CONF_LIMIT_FRAMES:
case SR_CONF_LIMIT_SAMPLES:
case SR_CONF_LIMIT_MSEC:
if (!devc)
return SR_ERR_ARG;
return sr_sw_limits_config_set(&devc->limits, key, data);
case SR_CONF_DATA_SOURCE:
if (!devc)
return SR_ERR_ARG;
/* Prefer data source names for the lookup. */
idx = std_str_idx(data, devc->data_source_names, devc->data_source_count);
if (idx >= 0) {
devc->data_source = idx;
break;
}
/*
* Support record number (1-based) as a fallback. The DMM
* "supports" ambiguous recording names (keeps offering a
* previously stored name for each new recording, neither
* automatically increments nor suggests timestamps).
*/
if (sr_atoi(g_variant_get_string(data, NULL), &ret) != SR_OK)
return SR_ERR_ARG;
if (ret <= 0)
return SR_ERR_ARG;
rec_no = ret;
if (rec_no > devc->record_count)
return SR_ERR_ARG;
devc->data_source = DATA_SOURCE_REC_FIRST + rec_no - 1;
break;
case SR_CONF_MEASURED_QUANTITY:
if (!devc)
return SR_ERR_ARG;
tuple_child = g_variant_get_child_value(data, 0);
mq = g_variant_get_uint32(tuple_child);
g_variant_unref(tuple_child);
tuple_child = g_variant_get_child_value(data, 1);
mqflags = g_variant_get_uint64(tuple_child);
g_variant_unref(tuple_child);
mode = ut181a_get_mode_from_mq_flags(mq, mqflags);
if (!mode)
return SR_ERR_NA;
ret = ut181a_send_cmd_setmode(sdi->conn, mode);
if (ret < 0)
return ret;
ret = ut181a_waitfor_response(sdi->conn, 100);
if (ret < 0)
return ret;
if (devc->info.rsp_head.rsp_type != RSP_TYPE_REPLY_CODE)
return SR_ERR_DATA;
if (!devc->info.reply_code.ok)
return SR_ERR_DATA;
break;
case SR_CONF_RANGE:
range = g_variant_get_string(data, NULL);
return ut181a_set_range_from_text(sdi, range);
default:
ret = SR_ERR_NA;
return SR_ERR_NA;
}
return ret;
return SR_OK;
}
static int config_list(uint32_t key, GVariant **data,
const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
{
struct dev_context *devc;
int ret;
(void)sdi;
(void)data;
(void)cg;
ret = SR_OK;
devc = sdi ? sdi->priv : NULL;
switch (key) {
/* TODO */
case SR_CONF_SCAN_OPTIONS:
case SR_CONF_DEVICE_OPTIONS:
return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
case SR_CONF_DATA_SOURCE:
if (!devc)
return SR_ERR_NA;
ret = ut181a_update_recordings(sdi);
if (ret < 0)
return ret;
*data = g_variant_new_strv(devc->data_source_names, devc->data_source_count);
break;
case SR_CONF_MEASURED_QUANTITY:
*data = ut181a_get_mq_flags_list();
break;
case SR_CONF_RANGE:
*data = ut181a_get_ranges_list();
break;
default:
return SR_ERR_NA;
}
return ret;
return SR_OK;
}
static int dev_acquisition_start(const struct sr_dev_inst *sdi)
{
/* TODO: configure hardware, reset acquisition state, set up
* callbacks and send header packet. */
struct dev_context *devc;
struct sr_serial_dev_inst *serial;
int ret;
size_t rec_idx;
(void)sdi;
devc = sdi->priv;
serial = sdi->conn;
serial_flush(serial);
/*
* Send an acquisition start command which depends on the
* currently selected data source. Enter monitor mode for
* Live readings, get saved or recorded data otherwise. The
* latter require queries for sample counts, then run chunked
* download sequences (single item for Save, set of samples
* for Recordings).
*/
if (devc->data_source == DATA_SOURCE_LIVE) {
ret = ut181a_send_cmd_monitor(serial, TRUE);
} else if (devc->data_source == DATA_SOURCE_SAVE) {
/*
* There is only one sequence of saved measurements in
* the device, but its length is yet unknown. Determine
* the number of saved items, and initiate the reception
* of the first value. Completion of data reception will
* drive subsequent progress.
*/
ret = ut181a_send_cmd_get_save_count(serial);
if (ret < 0)
return ret;
ret = ut181a_configure_waitfor(devc, FALSE, 0, 0,
FALSE, FALSE, TRUE, FALSE);
if (ret < 0)
return ret;
ret = ut181a_waitfor_response(sdi, 200);
if (ret < 0)
return ret;
devc->info.save_info.save_count = devc->wait_state.data_value;
devc->info.save_info.save_idx = 0;
ret = ut181a_send_cmd_get_saved_value(serial, 0);
} else if (devc->data_source >= DATA_SOURCE_REC_FIRST) {
/*
* When we get here, the data source got selected, which
* includes an update of the device's list of recordings.
* So the index should be good, just the number of samples
* in that recording is yet unknown. Get the sample count
* and initiate the reception of the first chunk, completed
* reception of a chunk advances through the sequence.
*/
rec_idx = devc->data_source - DATA_SOURCE_REC_FIRST;
if (rec_idx >= devc->record_count)
return SR_ERR_DATA;
devc->info.rec_info.rec_count = devc->record_count;
devc->info.rec_info.rec_idx = rec_idx;
devc->info.rec_info.auto_next = 0;
devc->info.rec_info.auto_feed = 1;
ret = ut181a_send_cmd_get_rec_info(serial, rec_idx);
if (ret < 0)
return ret;
ret = ut181a_configure_waitfor(devc,
FALSE, CMD_CODE_GET_REC_INFO, 0,
FALSE, FALSE, FALSE, FALSE);
if (ret < 0)
return ret;
ret = ut181a_waitfor_response(sdi, 200);
if (ret < 0)
return ret;
devc->info.rec_data.samples_total = devc->wait_state.data_value;
devc->info.rec_data.samples_curr = 0;
ret = ut181a_send_cmd_get_rec_samples(serial, rec_idx, 0);
}
if (ret < 0)
return ret;
sr_sw_limits_acquisition_start(&devc->limits);
devc->recv_count = 0;
std_session_send_df_header(sdi);
serial_source_add(sdi->session, serial, G_IO_IN, 10,
ut181a_handle_events, (void *)sdi);
return SR_OK;
}
static int dev_acquisition_stop(struct sr_dev_inst *sdi)
{
/* TODO: stop acquisition. */
(void)sdi;
sdi->status = SR_ST_STOPPING;
/* Initiate stop here. Activity happens in ut181a_handle_events(). */
return SR_OK;
}
@ -145,8 +549,8 @@ static struct sr_dev_driver uni_t_ut181a_driver_info = {
.config_get = config_get,
.config_set = config_set,
.config_list = config_list,
.dev_open = dev_open,
.dev_close = dev_close,
.dev_open = std_serial_dev_open,
.dev_close = std_serial_dev_close,
.dev_acquisition_start = dev_acquisition_start,
.dev_acquisition_stop = dev_acquisition_stop,
.context = NULL,

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
/*
* This file is part of the libsigrok project.
*
* Copyright (C) 2019 Gerhard Sittig <gerhard.sittig@gmx.net>
* Copyright (C) 2019-2020 Gerhard Sittig <gerhard.sittig@gmx.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,16 +20,419 @@
#ifndef LIBSIGROK_HARDWARE_UNI_T_UT181A_PROTOCOL_H
#define LIBSIGROK_HARDWARE_UNI_T_UT181A_PROTOCOL_H
#include <stdint.h>
#include <glib.h>
#include <libsigrok/libsigrok.h>
#include <stdint.h>
#include "libsigrok-internal.h"
#define LOG_PREFIX "uni-t-ut181a"
struct dev_context {
/*
* Optional features. Tunables.
*/
#define UT181A_WITH_TIMESTAMP 0
#define UT181A_WITH_SER_ECHO 0
/*
* The largest frame we expect to receive is chunked record data. Which
* can span up to 256 items which each occupy 9 bytes, plus some header
* before the items array. Be generous and prepare to receive several
* frames in a row, e.g. when synchronizing to the packet stream at the
* start of a session or after communication failure.
*
* The largest frame we expect to transmit is a "start record" command.
* Which contains 18 bytes of payload (plus 6 bytes of frame envelope).
*/
#define RECV_BUFF_SIZE 4096
#define SEND_BUFF_SIZE 32
#define SEND_TO_MS 100
/*
* The device can hold several recordings, their number is under the
* user's control and dynamic at runtime. It's assumed that there is an
* absolute upper bound of 20 recordings at any time. Names are under
* user control, too (auto-preset, then editable), and a maximum label
* length is assumed from the protocol description.
*
* Update 2020-03-17
* Turns out that 20 is *not* the limit on the number of recordings.
* Nor do I believe that 20K or 10K is the limit. It may be the total
* of the number of recordings and their sample counts which may not
* exceed 10K, while saved measurements can be up to 20K? This is just
* a guess though, the "Operating Manual" does not specify a limit,
* nor does it discuss a dependency beyond mentioning the 10K/20K
* figures.
*/
#define MAX_REC_COUNT 20
#define MAX_REC_NAMELEN 12
#define MAX_RANGE_INDEX 8
/* Literals look weird as numbers. LE format makes them readable on the wire. */
#define FRAME_MAGIC 0xcdab /* Becomes the AB CD byte sequence. */
#define REPLY_CODE_OK 0x4b4f /* Becomes the "OK" text. */
#define REPLY_CODE_ERR 0x5245 /* Becomes the "ER" text. */
enum ut181a_channel_idx {
UT181A_CH_MAIN,
UT181A_CH_AUX1,
UT181A_CH_AUX2,
UT181A_CH_AUX3,
UT181A_CH_BAR,
#if UT181A_WITH_TIMESTAMP
UT181A_CH_TIME,
#endif
};
SR_PRIV int uni_t_ut181a_receive_data(int fd, int revents, void *cb_data);
enum ut181_cmd_code {
CMD_CODE_INVALID = 0x00,
CMD_CODE_SET_MODE = 0x01,
CMD_CODE_SET_RANGE = 0x02,
CMD_CODE_SET_REFERENCE = 0x03,
CMD_CODE_SET_MIN_MAX = 0x04,
CMD_CODE_SET_MONITOR = 0x05,
CMD_CODE_SAVE_MEAS = 0x06,
CMD_CODE_GET_SAVED_MEAS = 0x07,
CMD_CODE_GET_SAVED_COUNT = 0x08,
CMD_CODE_DEL_SAVED_MEAS = 0x09,
CMD_CODE_START_REC = 0x0a,
CMD_CODE_STOP_REC = 0x0b,
CMD_CODE_GET_REC_INFO = 0x0c,
CMD_CODE_GET_REC_SAMPLES = 0x0d,
CMD_CODE_GET_RECS_COUNT = 0x0e,
CMD_CODE_BTN_PRESS = 0x12,
};
enum ut181_rsp_type {
RSP_TYPE_REPLY_CODE = 0x01,
RSP_TYPE_MEASUREMENT = 0x02,
RSP_TYPE_SAVE = 0x03,
RSP_TYPE_REC_INFO = 0x04,
RSP_TYPE_REC_DATA = 0x05,
RSP_TYPE_REPLY_DATA = 0x72, /* 'r' */
};
/*
* TODO
* - See if there is a pattern to these number codes.
* - [3:0] == 2 relative mode (when available)
* - [7:4] == 3 peak mode aka max/min (when available)
* (but there is command 4 set max/min on/off too)
*/
enum ut181a_mode_code {
/* V AC */
MODE_V_AC = 0x1111,
MODE_V_AC_REL = 0x1112,
MODE_V_AC_Hz = 0x1121,
MODE_V_AC_PEAK = 0x1131,
MODE_V_AC_LOWPASS = 0x1141,
MODE_V_AC_LOWPASS_REL = 0x1142,
MODE_V_AC_dBV = 0x1151,
MODE_V_AC_dBV_REL = 0x1152,
MODE_V_AC_dBm = 0x1161,
MODE_V_AC_dBm_REL = 0x1162,
/* mV AC */
MODE_mV_AC = 0x2111,
MODE_mV_AC_REL = 0x2112,
MODE_mV_AC_Hz = 0x2121,
MODE_mV_AC_PEAK = 0x2131,
MODE_mV_AC_ACDC = 0x2141,
MODE_mV_AC_ACDC_REL = 0x2142,
/* V DC */
MODE_V_DC = 0x3111,
MODE_V_DC_REL = 0x3112,
MODE_V_DC_ACDC = 0x3121,
MODE_V_DC_ACDC_REL = 0x3122,
MODE_V_DC_PEAK = 0x3131,
/* mV DC */
MODE_mV_DC = 0x4111,
MODE_mV_DC_REL = 0x4112,
MODE_mV_DC_PEAK = 0x4121, /* TODO Check number code, is it 0x4131? */
/* temperature Celsius */
MODE_TEMP_C_T1_and_T2 = 0x4211,
MODE_TEMP_C_T1_and_T2_REL = 0x4212,
MODE_TEMP_C_T2_and_T1 = 0x4221,
MODE_TEMP_C_T2_and_T1_REL = 0x4222,
MODE_TEMP_C_T1_minus_T2 = 0x4231, /* XXX exception, not PEAK */
MODE_TEMP_C_T2_minus_T1 = 0x4241,
/* temperature Farenheit */
MODE_TEMP_F_T1_and_T2 = 0x4311,
MODE_TEMP_F_T1_and_T2_REL = 0x4312,
MODE_TEMP_F_T2_and_T1 = 0x4321,
MODE_TEMP_F_T2_and_T1_REL = 0x4322,
MODE_TEMP_F_T1_minus_T2 = 0x4331,
MODE_TEMP_F_T2_minus_T1 = 0x4341, /* XXX exception, not PEAK */
/* resistance, continuity, conductivity */
MODE_RES = 0x5111,
MODE_RES_REL = 0x5112,
MODE_CONT_SHORT = 0x5211,
MODE_CONT_OPEN = 0x5212,
MODE_COND = 0x5311,
MODE_COND_REL = 0x5312,
/* diode, capacitance */
MODE_DIODE = 0x6111,
MODE_DIODE_ALARM = 0x6112, /* XXX exception, not REL */
MODE_CAP = 0x6211,
MODE_CAP_REL = 0x6212,
/* frequency, duty cycle, pulse width */
MODE_FREQ = 0x7111,
MODE_FREQ_REL = 0x7112,
MODE_DUTY = 0x7211,
MODE_DUTY_REL = 0x7212,
MODE_PULSEWIDTH = 0x7311,
MODE_PULSEWIDTH_REL = 0x7312,
/* uA DC */
MODE_uA_DC = 0x8111,
MODE_uA_DC_REL = 0x8112,
MODE_uA_DC_ACDC = 0x8121,
MODE_uA_DC_ACDC_REL = 0x8122,
MODE_uA_DC_PEAK = 0x8131,
/* uA AC */
MODE_uA_AC = 0x8211,
MODE_uA_AC_REL = 0x8212,
MODE_uA_AC_Hz = 0x8221,
MODE_uA_AC_PEAK = 0x8231,
/* mA DC */
MODE_mA_DC = 0x9111,
MODE_mA_DC_REL = 0x9112,
MODE_mA_DC_ACDC = 0x9121,
MODE_mA_DC_ACDC_REL = 0x9122,
MODE_mA_DC_ACDC_PEAK = 0x9131,
/* mA AC */
MODE_mA_AC = 0x9211,
MODE_mA_AC_REL = 0x9212,
MODE_mA_AC_Hz = 0x9221,
MODE_mA_AC_PEAK = 0x9231,
/* A DC */
MODE_A_DC = 0xa111,
MODE_A_DC_REL = 0xa112,
MODE_A_DC_ACDC = 0xa121,
MODE_A_DC_ACDC_REL = 0xa122,
MODE_A_DC_PEAK = 0xa131,
/* A AC */
MODE_A_AC = 0xa211,
MODE_A_AC_REL = 0xa212,
MODE_A_AC_Hz = 0xa221,
MODE_A_AC_PEAK = 0xa231,
};
/* Maximum number of UT181A modes which map to one MQ item. */
#define MODE_COUNT_PER_MQ_MQF 15
struct mqopt_item {
enum sr_mq mq;
enum sr_mqflag mqflags;
enum ut181a_mode_code modes[MODE_COUNT_PER_MQ_MQF];
};
struct mq_scale_params {
int scale;
enum sr_mq mq;
enum sr_mqflag mqflags;
enum sr_unit unit;
};
struct value_params {
float value;
int digits;
gboolean ol_neg, ol_pos;
};
struct feed_buffer {
struct sr_datafeed_packet packet;
struct sr_datafeed_analog analog;
struct sr_analog_encoding encoding;
struct sr_analog_meaning meaning;
struct sr_analog_spec spec;
int scale;
float main_value; /* TODO double, for epoch timestamps */
};
struct ut181a_info {
struct {
enum ut181_rsp_type rsp_type;
} rsp_head;
struct {
uint16_t code;
gboolean ok;
} reply_code;
struct {
uint32_t stamp;
time_t epoch;
} save_time;
struct {
uint8_t misc1, misc2, range;
uint16_t mode;
uint8_t is_type;
gboolean is_norm, is_rel, is_minmax, is_peak;
gboolean has_hold, has_aux1, has_aux2, has_bar;
gboolean is_rec, is_comp, is_auto_range;
gboolean has_lead_err, has_high_volt;
} meas_head;
union {
struct {
float main_value;
uint8_t main_prec;
char main_unit[8];
float aux1_value;
uint8_t aux1_prec;
char aux1_unit[8];
float aux2_value;
uint8_t aux2_prec;
char aux2_unit[8];
float bar_value;
char bar_unit[8];
} norm;
struct {
enum {
COMP_MODE_INNER = 0,
COMP_MODE_OUTER = 1,
COMP_MODE_BELOW = 2,
COMP_MODE_ABOVE = 3,
} mode;
gboolean fail;
int digits;
float limit_high;
float limit_low;
} comp;
struct {
float rel_value;
uint8_t rel_prec;
char rel_unit[8];
float ref_value;
uint8_t ref_prec;
char ref_unit[8];
float abs_value;
uint8_t abs_prec;
char abs_unit[8];
float bar_value;
char bar_unit[8];
} rel;
struct {
float curr_value;
uint8_t curr_prec;
float max_value;
uint8_t max_prec;
uint32_t max_stamp;
float avg_value;
uint8_t avg_prec;
uint32_t avg_stamp;
float min_value;
uint8_t min_prec;
uint32_t min_stamp;
char all_unit[8];
} minmax;
struct {
float max_value;
uint8_t max_prec;
char max_unit[8];
float min_value;
uint8_t min_prec;
char min_unit[8];
} peak;
} meas_data;
struct {
size_t save_idx;
size_t save_count;
} save_info;
struct {
size_t rec_count;
size_t rec_idx;
gboolean auto_feed;
gboolean auto_next;
char name[12];
char unit[8];
uint16_t interval;
uint32_t duration;
uint32_t samples;
float max_value, avg_value, min_value;
uint8_t max_prec, avg_prec, min_prec;
uint32_t start_stamp;
} rec_info;
struct {
size_t rec_idx;
size_t samples_total;
size_t samples_curr;
uint8_t samples_chunk;
} rec_data;
struct {
enum ut181_cmd_code code;
uint16_t data;
} reply_data;
};
enum ut181a_data_source {
DATA_SOURCE_LIVE,
DATA_SOURCE_SAVE,
DATA_SOURCE_REC_FIRST,
DATA_SOURCE_MAX = DATA_SOURCE_REC_FIRST + MAX_REC_COUNT,
};
struct dev_context {
struct sr_sw_limits limits;
enum ut181a_data_source data_source;
size_t data_source_count;
const char *data_source_names[DATA_SOURCE_MAX + 1];
size_t record_count;
char record_names[MAX_REC_COUNT][MAX_REC_NAMELEN];
gboolean is_monitoring;
gboolean is_recording;
/* Reception of serial communication data. */
uint8_t recv_buff[RECV_BUFF_SIZE];
size_t recv_count;
/* Meter's internal state tracking. */
int disable_feed;
gboolean frame_started;
struct ut181a_info info;
/* Management for request/response pairs. */
struct wait_state {
gboolean want_code, got_code;
enum ut181_cmd_code want_data; gboolean got_data;
enum ut181_rsp_type want_rsp_type;
gboolean got_rsp_type;
gboolean want_measure, got_measure;
gboolean got_rec_count;
gboolean got_save_count;
gboolean got_sample_count;
size_t response_count;
gboolean code_ok;
size_t data_value;
} wait_state;
struct {
char unit_text[12];
} last_data;
};
SR_PRIV const struct mqopt_item *ut181a_get_mqitem_from_mode(uint16_t mode);
SR_PRIV uint16_t ut181a_get_mode_from_mq_flags(enum sr_mq mq, enum sr_mqflag mqflags);
SR_PRIV GVariant *ut181a_get_mq_flags_list_item(enum sr_mq mq, enum sr_mqflag mqflag);
SR_PRIV GVariant *ut181a_get_mq_flags_list(void);
SR_PRIV int ut181a_send_cmd_monitor(struct sr_serial_dev_inst *serial, gboolean on);
SR_PRIV int ut181a_send_cmd_setmode(struct sr_serial_dev_inst *serial, uint16_t mode);
SR_PRIV int ut181a_send_cmd_setrange(struct sr_serial_dev_inst *serial, uint8_t range);
SR_PRIV int ut181a_send_cmd_get_save_count(struct sr_serial_dev_inst *serial);
SR_PRIV int ut181a_send_cmd_get_saved_value(struct sr_serial_dev_inst *serial, size_t idx);
SR_PRIV int ut181a_send_cmd_get_recs_count(struct sr_serial_dev_inst *serial);
SR_PRIV int ut181a_send_cmd_get_rec_info(struct sr_serial_dev_inst *serial, size_t idx);
SR_PRIV int ut181a_send_cmd_get_rec_samples(struct sr_serial_dev_inst *serial, size_t idx, size_t off);
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);
SR_PRIV int ut181a_waitfor_response(const struct sr_dev_inst *sdi, int timeout_ms);
SR_PRIV int ut181a_handle_events(int fd, int revents, void *cb_data);
SR_PRIV GVariant *ut181a_get_ranges_list(void);
SR_PRIV const char *ut181a_get_range_from_packet_bytes(struct dev_context *devc);
SR_PRIV int ut181a_set_range_from_text(const struct sr_dev_inst *sdi, const char *text);
#endif