X-Git-Url: https://sigrok.org/gitweb/?p=libsigrok.git;a=blobdiff_plain;f=src%2Fscpi%2Fscpi.c;h=c01791ef5ecebc8ac43300db1248a36081951f92;hp=f87bf9eba239325107a1903eb6d682bd6f282028;hb=4c7296644461e39cd06f5f5fa0898a4c189f43e1;hpb=d5a61aaf2d634c4558bda225e64e720104a0203f diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index f87bf9eb..c01791ef 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,7 +100,7 @@ static const struct sr_scpi_dev_inst *scpi_devs[] = { #ifdef HAVE_LIBGPIB &scpi_libgpib_dev, #endif -#ifdef HAVE_LIBSERIALPORT +#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,26 @@ 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. + * + * @param scpi Previously initialized SCPI device structure. + * @param connection_id Pointer where to store the connection ID. The caller + * is responsible for g_free()ing the string when it is no longer needed. + * + * @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 +473,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,26 +493,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_malloc0(len + 2); - vsprintf(buf, format, args); - if (buf[len - 1] != '\n') - buf[len] = '\n'; + int ret; - /* Send command. */ - ret = scpi->send(scpi->priv, buf); - - /* 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; } @@ -334,7 +526,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; } /** @@ -358,20 +580,30 @@ 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); } @@ -388,8 +620,10 @@ SR_PRIV int sr_scpi_get_string(struct sr_scpi_dev_inst *scpi, const char *command, char **scpi_response) { GString *response; - response = g_string_sized_new(1024); + *scpi_response = NULL; + + response = g_string_sized_new(1024); if (sr_scpi_get_data(scpi, command, &response) != SR_OK) { if (response) g_string_free(response, TRUE); @@ -412,53 +646,38 @@ SR_PRIV int sr_scpi_get_string(struct sr_scpi_dev_inst *scpi, return SR_OK; } -SR_PRIV int sr_scpi_get_data(struct sr_scpi_dev_inst *scpi, - const char *command, GString **scpi_response) +/** + * 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 len; - GString *response; - gint64 laststart; - unsigned int elapsed_ms; - unsigned int offset; - int space; - - if (command) { - if (sr_scpi_send(scpi, command) != SR_OK) - return SR_ERR; - } - - if (sr_scpi_read_begin(scpi) != SR_OK) - return SR_ERR; + int ret; - laststart = g_get_monotonic_time(); + g_mutex_lock(&scpi->scpi_mutex); + ret = scpi_read_response(scpi, response, abs_timeout_us); + g_mutex_unlock(&scpi->scpi_mutex); - response = *scpi_response; + return ret; +} - offset = response->len; +SR_PRIV int sr_scpi_get_data(struct sr_scpi_dev_inst *scpi, + const char *command, GString **scpi_response) +{ + int ret; - while (!sr_scpi_read_complete(scpi)) { - space = response->allocated_len - response->len; - if (space < 128) { - g_string_set_size(response, response->len + 1024); - space = response->allocated_len - response->len; - } - len = sr_scpi_read_data(scpi, &response->str[offset], space); - if (len < 0) { - sr_err("Incompletely read SCPI response."); - return SR_ERR; - } else if (len > 0) { - laststart = g_get_monotonic_time(); - } - offset += len; - g_string_set_size(response, offset); - elapsed_ms = (g_get_monotonic_time() - laststart) / 1000; - if (elapsed_ms >= scpi->read_timeout_ms) { - sr_err("Timed out waiting for SCPI response."); - return SR_ERR; - } - } + g_mutex_lock(&scpi->scpi_mutex); + ret = scpi_get_data(scpi, command, scpi_response); + g_mutex_unlock(&scpi->scpi_mutex); - return SR_OK; + return ret; } /** @@ -507,6 +726,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; @@ -515,10 +735,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); @@ -579,7 +803,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; @@ -603,6 +827,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; @@ -634,6 +859,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; @@ -660,7 +886,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; } @@ -690,6 +915,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; @@ -716,7 +942,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; } @@ -743,23 +968,44 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, { 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); - *scpi_response = NULL; + + timeout = g_get_monotonic_time() + scpi->read_timeout_us; /* Get (the first chunk of) the response. */ - ret = sr_scpi_get_data(scpi, command, &response); - if (ret != SR_OK) { - g_string_free(response, TRUE); - return ret; - } + 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. @@ -773,6 +1019,7 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, * 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; } @@ -780,41 +1027,67 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, 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); /* - * If the initially assumed length does not cover the data block - * length, then re-allocate the buffer size to the now known - * length, and keep reading more chunks of response data. + * Re-allocate the buffer size to the now known length + * and keep reading more chunks of response data. */ - if (response->len < (unsigned long)(datalen)) { - int oldlen = response->len; - g_string_set_size(response, datalen); - g_string_set_size(response, oldlen); - } - while (response->len < (unsigned long)(datalen)) { - ret = sr_scpi_get_data(scpi, NULL, &response); - if (ret != SR_OK) { - g_string_free(response, TRUE); - return ret; - } + 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 ret; + return SR_OK; } /** @@ -835,7 +1108,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; @@ -843,30 +1118,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); @@ -878,17 +1169,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; }