X-Git-Url: https://sigrok.org/gitweb/?p=libsigrok.git;a=blobdiff_plain;f=src%2Fscpi%2Fscpi.c;h=0f767f2a2de1c6862b61620d6e2dc0b9178c2f7d;hp=fc7e9939c0e72ae5dfe173b25147ead17ffe5143;hb=9b915e3a41311bff4d7089f564f3b2716f2004a7;hpb=0a1f7b09b3fa4cc4da29c7acf53717e14b004b63 diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index fc7e9939..0f767f2a 100644 --- a/src/scpi/scpi.c +++ b/src/scpi/scpi.c @@ -2,6 +2,7 @@ * This file is part of the libsigrok project. * * Copyright (C) 2013 poljar (Damir Jelić) + * Copyright (C) 2015 Bert Vermeulen * * 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 @@ -29,6 +30,16 @@ #define SCPI_READ_RETRIES 100 #define SCPI_READ_RETRY_TIMEOUT_US (10 * 1000) +static const char *scpi_vendors[][2] = { + { "Agilent Technologies", "Agilent" }, + { "CHROMA", "Chroma" }, + { "Chroma ATE", "Chroma" }, + { "HEWLETT-PACKARD", "HP" }, + { "Keysight Technologies", "Keysight" }, + { "PHILIPS", "Philips" }, + { "RIGOL TECHNOLOGIES", "Rigol" }, +}; + /** * Parse a string representation of a boolean-like value into a gboolean. * Similar to sr_parse_boolstring but rejects strings which do not represent @@ -89,8 +100,8 @@ static const struct sr_scpi_dev_inst *scpi_devs[] = { #ifdef HAVE_LIBGPIB &scpi_libgpib_dev, #endif -#ifdef HAVE_LIBSERIALPORT - &scpi_serial_dev, /* must be last as it matches any resource */ +#ifdef HAVE_SERIAL_COMM + &scpi_serial_dev, /* Must be last as it matches any resource. */ #endif }; @@ -122,6 +133,183 @@ static struct sr_dev_inst *sr_scpi_scan_resource(struct drv_context *drvc, return sdi; } +/** + * Send a SCPI command with a variadic argument list without mutex. + * + * @param scpi Previously initialized SCPI device structure. + * @param format Format string. + * @param args Argument list. + * + * @return SR_OK on success, SR_ERR on failure. + */ +static int scpi_send_variadic(struct sr_scpi_dev_inst *scpi, + const char *format, va_list args) +{ + va_list args_copy; + char *buf; + int len, ret; + + /* Get length of buffer required. */ + va_copy(args_copy, args); + len = sr_vsnprintf_ascii(NULL, 0, format, args_copy); + va_end(args_copy); + + /* Allocate buffer and write out command. */ + buf = g_malloc0(len + 2); + sr_vsprintf_ascii(buf, format, args); + if (buf[len - 1] != '\n') + buf[len] = '\n'; + + /* Send command. */ + ret = scpi->send(scpi->priv, buf); + + /* Free command buffer. */ + g_free(buf); + + return ret; +} + +/** + * Send a SCPI command without mutex. + * + * @param scpi Previously initialized SCPI device structure. + * @param format Format string, to be followed by any necessary arguments. + * + * @return SR_OK on success, SR_ERR on failure. + */ +static int scpi_send(struct sr_scpi_dev_inst *scpi, const char *format, ...) +{ + va_list args; + int ret; + + va_start(args, format); + ret = scpi_send_variadic(scpi, format, args); + va_end(args); + + return ret; +} + +/** + * Send data to SCPI device without mutex. + * + * TODO: This is only implemented in TcpRaw, but never used. + * TODO: Use Mutex at all? + * + * @param scpi Previously initialised SCPI device structure. + * @param buf Buffer with data to send. + * @param len Number of bytes to send. + * + * @return Number of bytes read, or SR_ERR upon failure. + */ +static int scpi_write_data(struct sr_scpi_dev_inst *scpi, char *buf, int maxlen) +{ + return scpi->write_data(scpi->priv, buf, maxlen); +} + +/** + * Read part of a response from SCPI device without mutex. + * + * @param scpi Previously initialised SCPI device structure. + * @param buf Buffer to store result. + * @param maxlen Maximum number of bytes to read. + * + * @return Number of bytes read, or SR_ERR upon failure. + */ +static int scpi_read_data(struct sr_scpi_dev_inst *scpi, char *buf, int maxlen) +{ + return scpi->read_data(scpi->priv, buf, maxlen); +} + +/** + * Do a non-blocking read of up to the allocated length, and + * check if a timeout has occured, without mutex. + * + * @param scpi Previously initialised SCPI device structure. + * @param response Buffer to which the response is appended. + * @param abs_timeout_us Absolute timeout in microseconds + * + * @return read length on success, SR_ERR* on failure. + */ +static int scpi_read_response(struct sr_scpi_dev_inst *scpi, + GString *response, gint64 abs_timeout_us) +{ + int len, space; + + space = response->allocated_len - response->len; + len = scpi->read_data(scpi->priv, &response->str[response->len], space); + + if (len < 0) { + sr_err("Incompletely read SCPI response."); + return SR_ERR; + } + + if (len > 0) { + g_string_set_size(response, response->len + len); + return len; + } + + if (g_get_monotonic_time() > abs_timeout_us) { + sr_err("Timed out waiting for SCPI response."); + return SR_ERR_TIMEOUT; + } + + return 0; +} + +/** + * Send a SCPI command, receive the reply and store the reply in + * scpi_response, without mutex. + * + * @param scpi Previously initialised SCPI device structure. + * @param command The SCPI command to send to the device. + * @param scpi_response Pointer where to store the SCPI response. + * + * @return SR_OK on success, SR_ERR on failure. + */ +static int scpi_get_data(struct sr_scpi_dev_inst *scpi, + const char *command, GString **scpi_response) +{ + int ret; + GString *response; + int space; + gint64 timeout; + + /* Optionally send caller provided command. */ + if (command) { + if (scpi_send(scpi, command) != SR_OK) + return SR_ERR; + } + + /* Initiate SCPI read operation. */ + if (sr_scpi_read_begin(scpi) != SR_OK) + return SR_ERR; + + /* Keep reading until completion or until timeout. */ + timeout = g_get_monotonic_time() + scpi->read_timeout_us; + + response = *scpi_response; + + while (!sr_scpi_read_complete(scpi)) { + /* Resize the buffer when free space drops below a threshold. */ + space = response->allocated_len - response->len; + if (space < 128) { + int oldlen = response->len; + g_string_set_size(response, oldlen + 1024); + g_string_set_size(response, oldlen); + } + + /* Read another chunk of the response. */ + ret = scpi_read_response(scpi, response, timeout); + + if (ret < 0) + return ret; + if (ret > 0) + timeout = g_get_monotonic_time() + scpi->read_timeout_us; + } + + return SR_OK; +} + SR_PRIV GSList *sr_scpi_scan(struct drv_context *drvc, GSList *options, struct sr_dev_inst *(*probe_device)(struct sr_scpi_dev_inst *scpi)) { @@ -190,7 +378,7 @@ SR_PRIV struct sr_scpi_dev_inst *scpi_dev_inst_new(struct drv_context *drvc, scpi = g_malloc(sizeof(*scpi)); *scpi = *scpi_dev; scpi->priv = g_malloc0(scpi->priv_size); - scpi->read_timeout_ms = 1000; + scpi->read_timeout_us = 1000 * 1000; params = g_strsplit(resource, "/", 0); if (scpi->dev_inst_new(scpi->priv, drvc, resource, params, serialcomm) != SR_OK) { @@ -214,9 +402,28 @@ SR_PRIV struct sr_scpi_dev_inst *scpi_dev_inst_new(struct drv_context *drvc, */ SR_PRIV int sr_scpi_open(struct sr_scpi_dev_inst *scpi) { + g_mutex_init(&scpi->scpi_mutex); + return scpi->open(scpi); } +/** + * Get the connection ID of the SCPI device. + * + * Callers must free the allocated memory regardless of the routine's + * return code. See @ref g_free(). + * + * @param[in] scpi Previously initialized SCPI device structure. + * @param[out] connection_id Pointer where to store the connection ID. + * + * @return SR_OK on success, SR_ERR on failure. + */ +SR_PRIV int sr_scpi_connection_id(struct sr_scpi_dev_inst *scpi, + char **connection_id) +{ + return scpi->connection_id(scpi, connection_id); +} + /** * Add an event source for an SCPI device. * @@ -268,7 +475,9 @@ SR_PRIV int sr_scpi_send(struct sr_scpi_dev_inst *scpi, int ret; va_start(args, format); - ret = sr_scpi_send_variadic(scpi, format, args); + g_mutex_lock(&scpi->scpi_mutex); + ret = scpi_send_variadic(scpi, format, args); + g_mutex_unlock(&scpi->scpi_mutex); va_end(args); return ret; @@ -286,24 +495,11 @@ SR_PRIV int sr_scpi_send(struct sr_scpi_dev_inst *scpi, SR_PRIV int sr_scpi_send_variadic(struct sr_scpi_dev_inst *scpi, const char *format, va_list args) { - va_list args_copy; - char *buf; - int len, ret; - - /* Get length of buffer required. */ - va_copy(args_copy, args); - len = vsnprintf(NULL, 0, format, args_copy); - va_end(args_copy); - - /* Allocate buffer and write out command. */ - buf = g_malloc(len + 1); - vsprintf(buf, format, args); - - /* Send command. */ - ret = scpi->send(scpi->priv, buf); + int ret; - /* Free command buffer. */ - g_free(buf); + g_mutex_lock(&scpi->scpi_mutex); + ret = scpi_send_variadic(scpi, format, args); + g_mutex_unlock(&scpi->scpi_mutex); return ret; } @@ -332,7 +528,37 @@ SR_PRIV int sr_scpi_read_begin(struct sr_scpi_dev_inst *scpi) SR_PRIV int sr_scpi_read_data(struct sr_scpi_dev_inst *scpi, char *buf, int maxlen) { - return scpi->read_data(scpi->priv, buf, maxlen); + int ret; + + g_mutex_lock(&scpi->scpi_mutex); + ret = scpi_read_data(scpi, buf, maxlen); + g_mutex_unlock(&scpi->scpi_mutex); + + return ret; +} + +/** + * Send data to SCPI device. + * + * TODO: This is only implemented in TcpRaw, but never used. + * TODO: Use Mutex at all? + * + * @param scpi Previously initialised SCPI device structure. + * @param buf Buffer with data to send. + * @param len Number of bytes to send. + * + * @return Number of bytes read, or SR_ERR upon failure. + */ +SR_PRIV int sr_scpi_write_data(struct sr_scpi_dev_inst *scpi, + char *buf, int maxlen) +{ + int ret; + + g_mutex_lock(&scpi->scpi_mutex); + ret = scpi_write_data(scpi, buf, maxlen); + g_mutex_unlock(&scpi->scpi_mutex); + + return ret; } /** @@ -356,70 +582,57 @@ SR_PRIV int sr_scpi_read_complete(struct sr_scpi_dev_inst *scpi) */ SR_PRIV int sr_scpi_close(struct sr_scpi_dev_inst *scpi) { - return scpi->close(scpi); + int ret; + + g_mutex_lock(&scpi->scpi_mutex); + ret = scpi->close(scpi); + g_mutex_unlock(&scpi->scpi_mutex); + g_mutex_clear(&scpi->scpi_mutex); + + return ret; } /** * Free SCPI device. * - * @param scpi Previously initialized SCPI device structure. - * - * @return SR_OK on success, SR_ERR on failure. + * @param scpi Previously initialized SCPI device structure. If NULL, + * this function does nothing. */ SR_PRIV void sr_scpi_free(struct sr_scpi_dev_inst *scpi) { + if (!scpi) + return; + scpi->free(scpi->priv); g_free(scpi->priv); + g_free(scpi->actual_channel_name); g_free(scpi); } /** * Send a SCPI command, receive the reply and store the reply in scpi_response. * - * @param scpi Previously initialised SCPI device structure. - * @param command The SCPI command to send to the device (can be NULL). - * @param scpi_response Pointer where to store the SCPI response. + * Callers must free the allocated memory regardless of the routine's + * return code. See @ref g_free(). + * + * @param[in] scpi Previously initialised SCPI device structure. + * @param[in] command The SCPI command to send to the device (can be NULL). + * @param[out] scpi_response Pointer where to store the SCPI response. * * @return SR_OK on success, SR_ERR* on failure. */ SR_PRIV int sr_scpi_get_string(struct sr_scpi_dev_inst *scpi, const char *command, char **scpi_response) { - char buf[256]; - int len; GString *response; - gint64 laststart; - unsigned int elapsed_ms; - - if (command) - if (sr_scpi_send(scpi, command) != SR_OK) - return SR_ERR; - - if (sr_scpi_read_begin(scpi) != SR_OK) - return SR_ERR; - - laststart = g_get_monotonic_time(); - - response = g_string_new(""); *scpi_response = NULL; - while (!sr_scpi_read_complete(scpi)) { - len = sr_scpi_read_data(scpi, buf, sizeof(buf)); - if (len < 0) { - sr_err("Incompletely read SCPI response."); + response = g_string_sized_new(1024); + if (sr_scpi_get_data(scpi, command, &response) != SR_OK) { + if (response) g_string_free(response, TRUE); - return SR_ERR; - } else if (len > 0) { - laststart = g_get_monotonic_time(); - } - g_string_append_len(response, buf, len); - elapsed_ms = (g_get_monotonic_time() - laststart) / 1000; - if (elapsed_ms >= scpi->read_timeout_ms) { - sr_err("Timed out waiting for SCPI response."); - g_string_free(response, TRUE); - return SR_ERR; - } + return SR_ERR; } /* Get rid of trailing linefeed if present */ @@ -438,6 +651,40 @@ SR_PRIV int sr_scpi_get_string(struct sr_scpi_dev_inst *scpi, return SR_OK; } +/** + * Do a non-blocking read of up to the allocated length, and + * check if a timeout has occured. + * + * @param scpi Previously initialised SCPI device structure. + * @param response Buffer to which the response is appended. + * @param abs_timeout_us Absolute timeout in microseconds + * + * @return read length on success, SR_ERR* on failure. + */ +SR_PRIV int sr_scpi_read_response(struct sr_scpi_dev_inst *scpi, + GString *response, gint64 abs_timeout_us) +{ + int ret; + + g_mutex_lock(&scpi->scpi_mutex); + ret = scpi_read_response(scpi, response, abs_timeout_us); + g_mutex_unlock(&scpi->scpi_mutex); + + return ret; +} + +SR_PRIV int sr_scpi_get_data(struct sr_scpi_dev_inst *scpi, + const char *command, GString **scpi_response) +{ + int ret; + + g_mutex_lock(&scpi->scpi_mutex); + ret = scpi_get_data(scpi, command, scpi_response); + g_mutex_unlock(&scpi->scpi_mutex); + + return ret; +} + /** * Send a SCPI command, read the reply, parse it as a bool value and store the * result in scpi_response. @@ -484,6 +731,7 @@ SR_PRIV int sr_scpi_get_int(struct sr_scpi_dev_inst *scpi, const char *command, int *scpi_response) { int ret; + struct sr_rational ret_rational; char *response; response = NULL; @@ -492,10 +740,14 @@ SR_PRIV int sr_scpi_get_int(struct sr_scpi_dev_inst *scpi, if (ret != SR_OK && !response) return ret; - if (sr_atoi(response, scpi_response) == SR_OK) - ret = SR_OK; - else + ret = sr_parse_rational(response, &ret_rational); + if (ret == SR_OK && (ret_rational.p % ret_rational.q) == 0) { + *scpi_response = ret_rational.p / ret_rational.q; + } else { + sr_dbg("get_int: non-integer rational=%" PRId64 "/%" PRIu64, + ret_rational.p, ret_rational.q); ret = SR_ERR_DATA; + } g_free(response); @@ -556,7 +808,7 @@ SR_PRIV int sr_scpi_get_double(struct sr_scpi_dev_inst *scpi, if (ret != SR_OK && !response) return ret; - if (sr_atod(response, scpi_response) == SR_OK) + if (sr_atod_ascii(response, scpi_response) == SR_OK) ret = SR_OK; else ret = SR_ERR_DATA; @@ -580,6 +832,7 @@ SR_PRIV int sr_scpi_get_opc(struct sr_scpi_dev_inst *scpi) gboolean opc; for (i = 0; i < SCPI_READ_RETRIES; i++) { + opc = FALSE; sr_scpi_get_bool(scpi, SCPI_CMD_OPC, &opc); if (opc) return SR_OK; @@ -593,14 +846,15 @@ SR_PRIV int sr_scpi_get_opc(struct sr_scpi_dev_inst *scpi) * Send a SCPI command, read the reply, parse it as comma separated list of * floats and store the as an result in scpi_response. * - * @param scpi Previously initialised SCPI device structure. - * @param command The SCPI command to send to the device (can be NULL). - * @param scpi_response Pointer where to store the parsed result. + * Callers must free the allocated memory (unless it's NULL) regardless of + * the routine's return code. See @ref g_array_free(). + * + * @param[in] scpi Previously initialised SCPI device structure. + * @param[in] command The SCPI command to send to the device (can be NULL). + * @param[out] scpi_response Pointer where to store the parsed result. * * @return SR_OK upon successfully parsing all values, SR_ERR* upon a parsing - * error or upon no response. The allocated response must be freed by - * the caller in the case of an SR_OK as well as in the case of - * parsing error. + * error or upon no response. */ SR_PRIV int sr_scpi_get_floatv(struct sr_scpi_dev_inst *scpi, const char *command, GArray **scpi_response) @@ -611,6 +865,7 @@ SR_PRIV int sr_scpi_get_floatv(struct sr_scpi_dev_inst *scpi, gchar **ptr, **tokens; GArray *response_array; + *scpi_response = NULL; response = NULL; tokens = NULL; @@ -637,7 +892,6 @@ SR_PRIV int sr_scpi_get_floatv(struct sr_scpi_dev_inst *scpi, if (ret != SR_OK && response_array->len == 0) { g_array_free(response_array, TRUE); - *scpi_response = NULL; return SR_ERR_DATA; } @@ -650,14 +904,15 @@ SR_PRIV int sr_scpi_get_floatv(struct sr_scpi_dev_inst *scpi, * Send a SCPI command, read the reply, parse it as comma separated list of * unsigned 8 bit integers and store the as an result in scpi_response. * - * @param scpi Previously initialised SCPI device structure. - * @param command The SCPI command to send to the device (can be NULL). - * @param scpi_response Pointer where to store the parsed result. + * Callers must free the allocated memory (unless it's NULL) regardless of + * the routine's return code. See @ref g_array_free(). + * + * @param[in] scpi Previously initialised SCPI device structure. + * @param[in] command The SCPI command to send to the device (can be NULL). + * @param[out] scpi_response Pointer where to store the parsed result. * * @return SR_OK upon successfully parsing all values, SR_ERR* upon a parsing - * error or upon no response. The allocated response must be freed by - * the caller in the case of an SR_OK as well as in the case of - * parsing error. + * error or upon no response. */ SR_PRIV int sr_scpi_get_uint8v(struct sr_scpi_dev_inst *scpi, const char *command, GArray **scpi_response) @@ -667,6 +922,7 @@ SR_PRIV int sr_scpi_get_uint8v(struct sr_scpi_dev_inst *scpi, gchar **ptr, **tokens; GArray *response_array; + *scpi_response = NULL; response = NULL; tokens = NULL; @@ -693,7 +949,6 @@ SR_PRIV int sr_scpi_get_uint8v(struct sr_scpi_dev_inst *scpi, if (response_array->len == 0) { g_array_free(response_array, TRUE); - *scpi_response = NULL; return SR_ERR_DATA; } @@ -702,14 +957,156 @@ SR_PRIV int sr_scpi_get_uint8v(struct sr_scpi_dev_inst *scpi, return ret; } +/** + * Send a SCPI command, read the reply, parse it as binary data with a + * "definite length block" header and store the as an result in scpi_response. + * + * Callers must free the allocated memory (unless it's NULL) regardless of + * the routine's return code. See @ref g_byte_array_free(). + * + * @param[in] scpi Previously initialised SCPI device structure. + * @param[in] command The SCPI command to send to the device (can be NULL). + * @param[out] scpi_response Pointer where to store the parsed result. + * + * @return SR_OK upon successfully parsing all values, SR_ERR* upon a parsing + * error or upon no response. + */ +SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, + const char *command, GByteArray **scpi_response) +{ + int ret; + GString* response; + gsize oldlen; + char buf[10]; + long llen; + long datalen; + gint64 timeout; + + *scpi_response = NULL; + + g_mutex_lock(&scpi->scpi_mutex); + + if (command) + if (scpi_send(scpi, command) != SR_OK) { + g_mutex_unlock(&scpi->scpi_mutex); + return SR_ERR; + } + + if (sr_scpi_read_begin(scpi) != SR_OK) { + g_mutex_unlock(&scpi->scpi_mutex); + return SR_ERR; + } + + /* + * Assume an initial maximum length, optionally gets adjusted below. + * Prepare a NULL return value for when error paths will be taken. + */ + response = g_string_sized_new(1024); + + timeout = g_get_monotonic_time() + scpi->read_timeout_us; + + /* Get (the first chunk of) the response. */ + do { + ret = scpi_read_response(scpi, response, timeout); + if (ret < 0) { + g_mutex_unlock(&scpi->scpi_mutex); + g_string_free(response, TRUE); + return ret; + } + } while (response->len < 2); + + /* + * SCPI protocol data blocks are preceeded with a length spec. + * The length spec consists of a '#' marker, one digit which + * specifies the character count of the length spec, and the + * respective number of characters which specify the data block's + * length. Raw data bytes follow (thus one must no longer assume + * that the received input stream would be an ASCIIZ string). + * + * Get the data block length, and strip off the length spec from + * the input buffer, leaving just the data bytes. + */ + if (response->str[0] != '#') { + g_mutex_unlock(&scpi->scpi_mutex); + g_string_free(response, TRUE); + return SR_ERR_DATA; + } + buf[0] = response->str[1]; + buf[1] = '\0'; + ret = sr_atol(buf, &llen); + if ((ret != SR_OK) || (llen == 0)) { + g_mutex_unlock(&scpi->scpi_mutex); + g_string_free(response, TRUE); + return ret; + } + + while (response->len < (unsigned long)(2 + llen)) { + ret = scpi_read_response(scpi, response, timeout); + if (ret < 0) { + g_mutex_unlock(&scpi->scpi_mutex); + g_string_free(response, TRUE); + return ret; + } + } + + memcpy(buf, &response->str[2], llen); + buf[llen] = '\0'; + ret = sr_atol(buf, &datalen); + if ((ret != SR_OK) || (datalen == 0)) { + g_mutex_unlock(&scpi->scpi_mutex); + g_string_free(response, TRUE); + return ret; + } + g_string_erase(response, 0, 2 + llen); + + /* + * Re-allocate the buffer size to the now known length + * and keep reading more chunks of response data. + */ + oldlen = response->len; + g_string_set_size(response, datalen); + g_string_set_size(response, oldlen); + + if (oldlen < (unsigned long)(datalen)) { + do { + oldlen = response->len; + ret = scpi_read_response(scpi, response, timeout); + + /* On timeout truncate the buffer and send the partial response + * instead of getting stuck on timeouts... + */ + if (ret == SR_ERR_TIMEOUT) { + datalen = oldlen; + break; + } + if (ret < 0) { + g_mutex_unlock(&scpi->scpi_mutex); + g_string_free(response, TRUE); + return ret; + } + if (ret > 0) + timeout = g_get_monotonic_time() + scpi->read_timeout_us; + } while (response->len < (unsigned long)(datalen)); + } + + g_mutex_unlock(&scpi->scpi_mutex); + + /* Convert received data to byte array. */ + *scpi_response = g_byte_array_new_take( + (guint8*)g_string_free(response, FALSE), datalen); + + return SR_OK; +} + /** * Send the *IDN? SCPI command, receive the reply, parse it and store the * reply as a sr_scpi_hw_info structure in the supplied scpi_response pointer. * - * The hw_info structure must be freed by the caller via sr_scpi_hw_info_free(). + * Callers must free the allocated memory regardless of the routine's + * return code. See @ref sr_scpi_hw_info_free(). * - * @param scpi Previously initialised SCPI device structure. - * @param scpi_response Pointer where to store the hw_info structure. + * @param[in] scpi Previously initialised SCPI device structure. + * @param[out] scpi_response Pointer where to store the hw_info structure. * * @return SR_OK upon success, SR_ERR* on failure. */ @@ -720,7 +1117,9 @@ SR_PRIV int sr_scpi_get_hw_id(struct sr_scpi_dev_inst *scpi, char *response; gchar **tokens; struct sr_scpi_hw_info *hw_info; + gchar *idn_substr; + *scpi_response = NULL; response = NULL; tokens = NULL; @@ -728,30 +1127,46 @@ SR_PRIV int sr_scpi_get_hw_id(struct sr_scpi_dev_inst *scpi, if (ret != SR_OK && !response) return ret; - sr_info("Got IDN string: '%s'", response); - /* * The response to a '*IDN?' is specified by the SCPI spec. It contains * a comma-separated list containing the manufacturer name, instrument * model, serial number of the instrument and the firmware version. + * + * BEWARE! Although strictly speaking a smaller field count is invalid, + * this implementation also accepts IDN responses with one field less, + * and assumes that the serial number is missing. Some GWInstek DMMs + * were found to do this. Keep warning about this condition, which may + * need more consideration later. */ tokens = g_strsplit(response, ",", 0); - - for (num_tokens = 0; tokens[num_tokens] != NULL; num_tokens++); - - if (num_tokens < 4) { - sr_dbg("IDN response not according to spec: %80.s.", response); + num_tokens = g_strv_length(tokens); + if (num_tokens < 3) { + sr_dbg("IDN response not according to spec: '%s'", response); g_strfreev(tokens); g_free(response); return SR_ERR_DATA; } + if (num_tokens < 4) { + sr_warn("Short IDN response, assume missing serial number."); + } g_free(response); hw_info = g_malloc0(sizeof(struct sr_scpi_hw_info)); - hw_info->manufacturer = g_strstrip(g_strdup(tokens[0])); + + idn_substr = g_strstr_len(tokens[0], -1, "IDN "); + if (idn_substr == NULL) + hw_info->manufacturer = g_strstrip(g_strdup(tokens[0])); + else + hw_info->manufacturer = g_strstrip(g_strdup(idn_substr + 4)); + hw_info->model = g_strstrip(g_strdup(tokens[1])); - hw_info->serial_number = g_strstrip(g_strdup(tokens[2])); - hw_info->firmware_version = g_strstrip(g_strdup(tokens[3])); + if (num_tokens < 4) { + hw_info->serial_number = g_strdup("Unknown"); + hw_info->firmware_version = g_strstrip(g_strdup(tokens[2])); + } else { + hw_info->serial_number = g_strstrip(g_strdup(tokens[2])); + hw_info->firmware_version = g_strstrip(g_strdup(tokens[3])); + } g_strfreev(tokens); @@ -763,17 +1178,216 @@ SR_PRIV int sr_scpi_get_hw_id(struct sr_scpi_dev_inst *scpi, /** * Free a sr_scpi_hw_info struct. * - * @param hw_info Pointer to the struct to free. - * - * This function is safe to call with a NULL pointer. + * @param hw_info Pointer to the struct to free. If NULL, this + * function does nothing. */ SR_PRIV void sr_scpi_hw_info_free(struct sr_scpi_hw_info *hw_info) { - if (hw_info) { - g_free(hw_info->manufacturer); - g_free(hw_info->model); - g_free(hw_info->serial_number); - g_free(hw_info->firmware_version); - g_free(hw_info); + if (!hw_info) + return; + + g_free(hw_info->manufacturer); + g_free(hw_info->model); + g_free(hw_info->serial_number); + g_free(hw_info->firmware_version); + g_free(hw_info); +} + +/** + * Remove potentially enclosing pairs of quotes, un-escape content. + * This implementation modifies the caller's buffer when quotes are found + * and doubled quote characters need to get removed from the content. + * + * @param[in, out] s The SCPI string to check and un-quote. + * + * @return The start of the un-quoted string. + */ +SR_PRIV const char *sr_scpi_unquote_string(char *s) +{ + size_t s_len; + char quotes[3]; + char *rdptr; + + /* Immediately bail out on invalid or short input. */ + if (!s || !*s) + return s; + s_len = strlen(s); + if (s_len < 2) + return s; + + /* Check for matching quote characters front and back. */ + if (s[0] != '\'' && s[0] != '"') + return s; + if (s[0] != s[s_len - 1]) + return s; + + /* Need to strip quotes, and un-double quote chars inside. */ + quotes[0] = quotes[1] = *s; + quotes[2] = '\0'; + s[s_len - 1] = '\0'; + s++; + rdptr = s; + while ((rdptr = strstr(rdptr, quotes)) != NULL) { + memmove(rdptr, rdptr + 1, strlen(rdptr)); + rdptr++; + } + + return s; +} + +SR_PRIV const char *sr_vendor_alias(const char *raw_vendor) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(scpi_vendors); i++) { + if (!g_ascii_strcasecmp(raw_vendor, scpi_vendors[i][0])) + return scpi_vendors[i][1]; + } + + return raw_vendor; +} + +SR_PRIV const char *sr_scpi_cmd_get(const struct scpi_command *cmdtable, + int command) +{ + unsigned int i; + const char *cmd; + + if (!cmdtable) + return NULL; + + cmd = NULL; + for (i = 0; cmdtable[i].string; i++) { + if (cmdtable[i].command == command) { + cmd = cmdtable[i].string; + break; + } + } + + return cmd; +} + +SR_PRIV int sr_scpi_cmd(const struct sr_dev_inst *sdi, + const struct scpi_command *cmdtable, + int channel_command, const char *channel_name, + int command, ...) +{ + struct sr_scpi_dev_inst *scpi; + va_list args; + int ret; + const char *channel_cmd; + const char *cmd; + + scpi = sdi->conn; + + if (!(cmd = sr_scpi_cmd_get(cmdtable, command))) { + /* Device does not implement this command, that's OK. */ + return SR_OK; + } + + g_mutex_lock(&scpi->scpi_mutex); + + /* Select channel. */ + channel_cmd = sr_scpi_cmd_get(cmdtable, channel_command); + if (channel_cmd && channel_name && + g_strcmp0(channel_name, scpi->actual_channel_name)) { + sr_spew("sr_scpi_cmd(): new channel = %s", channel_name); + g_free(scpi->actual_channel_name); + scpi->actual_channel_name = g_strdup(channel_name); + ret = scpi_send(scpi, channel_cmd, channel_name); + if (ret != SR_OK) + return ret; + } + + va_start(args, command); + ret = scpi_send_variadic(scpi, cmd, args); + va_end(args); + + g_mutex_unlock(&scpi->scpi_mutex); + + return ret; +} + +SR_PRIV int sr_scpi_cmd_resp(const struct sr_dev_inst *sdi, + const struct scpi_command *cmdtable, + int channel_command, const char *channel_name, + GVariant **gvar, const GVariantType *gvtype, int command, ...) +{ + struct sr_scpi_dev_inst *scpi; + va_list args; + const char *channel_cmd; + const char *cmd; + GString *response; + char *s; + gboolean b; + double d; + int ret; + + scpi = sdi->conn; + + if (!(cmd = sr_scpi_cmd_get(cmdtable, command))) { + /* Device does not implement this command. */ + return SR_ERR_NA; + } + + g_mutex_lock(&scpi->scpi_mutex); + + /* Select channel. */ + channel_cmd = sr_scpi_cmd_get(cmdtable, channel_command); + if (channel_cmd && channel_name && + g_strcmp0(channel_name, scpi->actual_channel_name)) { + sr_spew("sr_scpi_cmd_get(): new channel = %s", channel_name); + g_free(scpi->actual_channel_name); + scpi->actual_channel_name = g_strdup(channel_name); + ret = scpi_send(scpi, channel_cmd, channel_name); + if (ret != SR_OK) + return ret; } + + va_start(args, command); + ret = scpi_send_variadic(scpi, cmd, args); + va_end(args); + if (ret != SR_OK) { + g_mutex_unlock(&scpi->scpi_mutex); + return ret; + } + + response = g_string_sized_new(1024); + ret = scpi_get_data(scpi, NULL, &response); + if (ret != SR_OK) { + g_mutex_unlock(&scpi->scpi_mutex); + if (response) + g_string_free(response, TRUE); + return ret; + } + + g_mutex_unlock(&scpi->scpi_mutex); + + /* Get rid of trailing linefeed if present */ + if (response->len >= 1 && response->str[response->len - 1] == '\n') + g_string_truncate(response, response->len - 1); + + /* Get rid of trailing carriage return if present */ + if (response->len >= 1 && response->str[response->len - 1] == '\r') + g_string_truncate(response, response->len - 1); + + s = g_string_free(response, FALSE); + + ret = SR_OK; + if (g_variant_type_equal(gvtype, G_VARIANT_TYPE_BOOLEAN)) { + if ((ret = parse_strict_bool(s, &b)) == SR_OK) + *gvar = g_variant_new_boolean(b); + } else if (g_variant_type_equal(gvtype, G_VARIANT_TYPE_DOUBLE)) { + if ((ret = sr_atod_ascii(s, &d)) == SR_OK) + *gvar = g_variant_new_double(d); + } else if (g_variant_type_equal(gvtype, G_VARIANT_TYPE_STRING)) { + *gvar = g_variant_new_string(s); + } else { + sr_err("Unable to convert to desired GVariant type."); + ret = SR_ERR_NA; + } + + g_free(s); + + return ret; }