X-Git-Url: https://sigrok.org/gitweb/?p=libsigrok.git;a=blobdiff_plain;f=src%2Fhardware%2Funi-t-ut181a%2Fapi.c;h=0beb2245cf405bc4a53534125e80825d90209452;hp=403eca744d0811f3ea1ef4010fc286231695b5f4;hb=ebc51109899008694c50977cb2574050b1bc0c89;hpb=3094e9d8ca2b8cb7aad1c3bf41b47f0087e45ff8 diff --git a/src/hardware/uni-t-ut181a/api.c b/src/hardware/uni-t-ut181a/api.c index 403eca74..0beb2245 100644 --- a/src/hardware/uni-t-ut181a/api.c +++ b/src/hardware/uni-t-ut181a/api.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 @@ -20,115 +20,519 @@ #include #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 GSList *scan(struct sr_dev_driver *di, GSList *options) -{ - struct drv_context *drvc; - GSList *devices; +static const uint32_t drvopts[] = { + SR_CONF_MULTIMETER, + SR_CONF_THERMOMETER, /* Supports two temperature probes and diffs. */ +}; - (void)options; +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, +}; - devices = NULL; - drvc = di->context; - drvc->instances = NULL; +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; - /* TODO: scan for devices, either based on a SR_CONF_CONN option - * or on a USB scan. */ + 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 devices; + return SR_OK; } -static int dev_open(struct sr_dev_inst *sdi) +/* + * 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) { - (void)sdi; + struct dev_context *devc; + struct sr_serial_dev_inst *serial; + gint64 deadline; + int ret; - /* TODO: get handle from sdi->conn and open it. */ + 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 int dev_close(struct sr_dev_inst *sdi) +static GSList *scan(struct sr_dev_driver *di, GSList *options) { - (void)sdi; + 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; + + /* + * 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; - /* TODO: get handle from sdi->conn and close it. */ + devices = 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; + } - return SR_OK; + 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]); + } + + /* + * 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; + } + + /* + * 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; + } + + devc->disable_feed = 0; + serial_close(serial); + + devices = g_slist_append(devices, sdi); + + 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; + + 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); - (void)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,