]> sigrok.org Git - libsigrok.git/commitdiff
gwinstek-psp: Initial implementation
authorettom <redacted>
Tue, 24 Oct 2023 19:43:06 +0000 (22:43 +0300)
committerSoeren Apel <redacted>
Wed, 16 Oct 2024 21:46:52 +0000 (23:46 +0200)
src/hardware/gwinstek-psp/api.c
src/hardware/gwinstek-psp/protocol.c
src/hardware/gwinstek-psp/protocol.h

index c3fa623c23dc86efee46a0c5d34eb9b111a126f8..f27737584b96d4bc9288b86a4ef3b0415cb9f1ea 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <math.h>
 #include <config.h>
 #include "protocol.h"
 
 static struct sr_dev_driver gwinstek_psp_driver_info;
 
-static GSList *scan(struct sr_dev_driver *di, GSList *options)
+static const uint32_t scanopts[] = {
+       SR_CONF_CONN,
+       SR_CONF_SERIALCOMM,
+       SR_CONF_FORCE_DETECT,
+};
+
+static const uint32_t drvopts[] = {
+       SR_CONF_POWER_SUPPLY,
+};
+
+static const uint32_t devopts[] = {
+       SR_CONF_CONN | SR_CONF_GET,
+       SR_CONF_CONTINUOUS,
+       SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_VOLTAGE | SR_CONF_GET,
+       SR_CONF_VOLTAGE_TARGET | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_CURRENT | SR_CONF_GET,
+       SR_CONF_CURRENT_LIMIT | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_OVER_TEMPERATURE_PROTECTION_ACTIVE | SR_CONF_GET,
+       SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET,
+};
+
+/* Voltage and current ranges. Values are: Min, max, step. */
+static const double volts_20[] = { 0, 20, 0.01 };
+static const double volts_40[] = { 0, 40, 0.01 };
+static const double volts_60[] = { 0, 60, 0.01 };
+static const double amps_3_5[] = { 0, 3.5, 0.01 };
+static const double amps_5[] = { 0, 5, 0.01 };
+static const double amps_10[] = { 0, 10, 0.01 };
+
+static const struct gwinstek_psp_model models[] = {
+       { "GW Instek", "PSP-603", volts_60, amps_3_5 },
+       { "GW Instek", "PSP-405", volts_40, amps_5 },
+       { "GW Instek", "PSP-2010", volts_20, amps_10 }
+};
+
+static const struct gwinstek_psp_model *model_lookup(const char *id_text)
 {
-       struct drv_context *drvc;
-       GSList *devices;
+       size_t idx;
+       const struct gwinstek_psp_model *check;
 
-       (void)options;
+       if (!id_text || !*id_text)
+               return NULL;
 
-       devices = NULL;
-       drvc = di->context;
-       drvc->instances = NULL;
+       for (idx = 0; idx < ARRAY_SIZE(models); idx++) {
+               check = &models[idx];
+               if (!check->name || !check->name[0])
+                       continue;
+               if (g_ascii_strncasecmp(id_text, check->name, strlen(check->name)) != 0)
+                       continue;
 
-       /* TODO: scan for devices, either based on a SR_CONF_CONN option
-        * or on a USB scan. */
+               return check;
+       }
+       sr_dbg("Could not find a matching model for: [%s].", id_text);
 
-       return devices;
+       return NULL;
 }
 
-static int dev_open(struct sr_dev_inst *sdi)
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
 {
-       (void)sdi;
+       struct sr_config *src;
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       struct sr_serial_dev_inst *serial;
+       const char *conn, *serialcomm;
+       const char *force_detect;
+       const struct gwinstek_psp_model *model;
+       GSList *l;
+
+       conn = NULL;
+       serialcomm = NULL;
+       force_detect = NULL;
+
+       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;
+               case SR_CONF_FORCE_DETECT:
+                       force_detect = g_variant_get_string(src->data, NULL);
+                       break;
+               default:
+                       sr_err("Unknown option %d, skipping.", src->key);
+                       break;
+               }
+       }
 
-       /* TODO: get handle from sdi->conn and open it. */
+       if (!conn)
+               return NULL;
+       if (!serialcomm)
+               serialcomm = "2400/8n1";
+       if (!force_detect) {
+               sr_err("The gwinstek-psp driver requires the force_detect parameter");
+               return NULL;
+       }
 
-       return SR_OK;
+       model = model_lookup(force_detect);
+
+       if (!model) {
+               sr_err("Unsupported model ID '%s', aborting.", force_detect);
+               return NULL;
+       }
+
+       serial = sr_serial_dev_inst_new(conn, serialcomm);
+       if (serial_open(serial, SERIAL_RDWR) != SR_OK)
+               return NULL;
+
+       sdi = g_malloc0(sizeof(*sdi));
+       sdi->status = SR_ST_INACTIVE;
+       sdi->vendor = g_strdup(model->vendor);
+       sdi->model = g_strdup(model->name);
+       sdi->inst_type = SR_INST_SERIAL;
+       sdi->conn = serial;
+       sdi->connection_id = g_strdup(conn);
+
+       sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "V");
+       sr_channel_new(sdi, 1, SR_CHANNEL_ANALOG, TRUE, "I");
+
+       devc = g_malloc0(sizeof(*devc));
+       sr_sw_limits_init(&devc->limits);
+       g_mutex_init(&devc->rw_mutex);
+       devc->model = model;
+       devc->msg_terminator_len = 2;
+       sdi->priv = devc;
+
+       /* Get current status of device. */
+       if (gwinstek_psp_get_all_values(serial, devc) < 0)
+               goto exit_err;
+
+       if (gwinstek_psp_check_terminator(serial, devc) < 0)
+               goto exit_err;
+
+       if (gwinstek_psp_get_initial_voltage_target(devc) < 0)
+               goto exit_err;
+
+       serial_close(serial);
+
+       return std_scan_complete(di, g_slist_append(NULL, sdi));
+
+exit_err:
+       sr_dev_inst_free(sdi);
+       g_free(devc);
+       sr_dbg("Scan failed.");
+
+       return NULL;
 }
 
 static int dev_close(struct sr_dev_inst *sdi)
 {
-       (void)sdi;
+       struct dev_context *devc;
 
-       /* TODO: get handle from sdi->conn and close it. */
+       devc = (sdi) ? sdi->priv : NULL;
+       if (devc) {
+               serial_flush(sdi->conn);
+               g_mutex_clear(&devc->rw_mutex);
+       }
 
-       return SR_OK;
+       return std_serial_dev_close(sdi);
 }
 
 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;
 
-       (void)sdi;
-       (void)data;
        (void)cg;
 
-       ret = SR_OK;
+       if (!sdi || !data)
+               return SR_ERR_ARG;
+
+       devc = sdi->priv;
+
+       if (key != SR_CONF_CONN && gwinstek_psp_get_all_values(sdi->conn, devc) < 0)
+               return SR_ERR;
+
        switch (key) {
-       /* TODO */
+       case SR_CONF_CONN:
+               *data = g_variant_new_string(sdi->connection_id);
+               break;
+       case SR_CONF_VOLTAGE:
+               *data = g_variant_new_double(devc->voltage_or_0);
+               break;
+       case SR_CONF_VOLTAGE_TARGET:
+               *data = g_variant_new_double(devc->voltage_target);
+               break;
+       case SR_CONF_CURRENT:
+               *data = g_variant_new_double(devc->current);
+               break;
+       case SR_CONF_CURRENT_LIMIT:
+               *data = g_variant_new_double(devc->current_limit);
+               break;
+       case SR_CONF_ENABLED:
+               *data = g_variant_new_boolean(devc->output_enabled);
+               break;
+       case SR_CONF_OVER_TEMPERATURE_PROTECTION_ACTIVE:
+               *data = g_variant_new_boolean(devc->otp_active);
+               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)
 {
-       int ret;
+       struct dev_context *devc;
+       int voltage_limit;
+       double dval;
+       gboolean bval;
+       char msg[20];
 
-       (void)sdi;
-       (void)data;
        (void)cg;
 
-       ret = SR_OK;
+       devc = sdi->priv;
+
        switch (key) {
-       /* TODO */
+       case SR_CONF_LIMIT_MSEC:
+       case SR_CONF_LIMIT_SAMPLES:
+               return sr_sw_limits_config_set(&devc->limits, key, data);
+       case SR_CONF_VOLTAGE_TARGET:
+               dval = g_variant_get_double(data);
+               if (dval < devc->model->voltage[0] || dval > devc->model->voltage[1])
+                       return SR_ERR_ARG;
+
+               /* Set voltage output limit to the next positive integer. No need
+                * to check against limits, device handles overflow correctly. */
+               voltage_limit = (int) ceil(dval);
+               if (devc->voltage_limit == voltage_limit) {
+                       sr_dbg("Correct limit (%dV) already set", voltage_limit);
+               } else {
+                       sr_snprintf_ascii(msg, sizeof(msg), "SU %d\r\n", voltage_limit);
+                       if (gwinstek_psp_send_cmd(sdi->conn, devc, msg, TRUE) < 0)
+                               return SR_ERR;
+               }
+
+               /* Set voltage output level */
+               sr_snprintf_ascii(msg, sizeof(msg), "SV %05.2f\r\n", dval);
+               if (gwinstek_psp_send_cmd(sdi->conn, devc, msg, TRUE) < 0)
+                       return SR_ERR;
+               devc->set_voltage_target = dval;
+               devc->set_voltage_target_updated = g_get_monotonic_time();
+               break;
+       case SR_CONF_CURRENT_LIMIT:
+               dval = g_variant_get_double(data);
+               if (dval < devc->model->current[0] || dval > devc->model->current[1])
+                       return SR_ERR_ARG;
+
+               sr_snprintf_ascii(msg, sizeof(msg), "SI %04.2f\r\n", dval);
+               if (gwinstek_psp_send_cmd(sdi->conn, devc, msg, TRUE) < 0)
+                       return SR_ERR;
+               break;
+       case SR_CONF_ENABLED:
+               bval = g_variant_get_boolean(data);
+               /* Set always so it is possible turn off with sigrok-cli. */
+               if (gwinstek_psp_send_cmd(sdi->conn, devc,
+                                         bval ? "KOE\r\n" : "KOD\r\n", TRUE) < 0)
+                       return SR_ERR;
+               break;
        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)
 {
-       int ret;
+       struct dev_context *devc;
 
-       (void)sdi;
-       (void)data;
-       (void)cg;
+       devc = (sdi) ? sdi->priv : NULL;
 
-       ret = SR_OK;
        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_VOLTAGE_TARGET:
+               if (!devc || !devc->model)
+                       return SR_ERR_ARG;
+               *data = std_gvar_min_max_step_array(devc->model->voltage);
+               break;
+       case SR_CONF_CURRENT_LIMIT:
+               if (!devc || !devc->model)
+                       return SR_ERR_ARG;
+               *data = std_gvar_min_max_step_array(devc->model->current);
+               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;
 
-       (void)sdi;
+       devc = sdi->priv;
 
-       return SR_OK;
-}
-
-static int dev_acquisition_stop(struct sr_dev_inst *sdi)
-{
-       /* TODO: stop acquisition. */
+       sr_sw_limits_acquisition_start(&devc->limits);
+       std_session_send_df_header(sdi);
 
-       (void)sdi;
+       devc->next_req_time = 0;
+       serial = sdi->conn;
+       serial_source_add(sdi->session, serial, G_IO_IN,
+                       GWINSTEK_PSP_PROCESSING_TIME_MS,
+                       gwinstek_psp_receive_data, (void *)sdi);
 
        return SR_OK;
 }
 
 static struct sr_dev_driver gwinstek_psp_driver_info = {
        .name = "gwinstek-psp",
-       .longname = "GW Instek PSP",
+       .longname = "GW Instek PSP series",
        .api_version = 1,
        .init = std_init,
        .cleanup = std_cleanup,
@@ -145,10 +354,10 @@ static struct sr_dev_driver gwinstek_psp_driver_info = {
        .config_get = config_get,
        .config_set = config_set,
        .config_list = config_list,
-       .dev_open = dev_open,
+       .dev_open = std_serial_dev_open,
        .dev_close = dev_close,
        .dev_acquisition_start = dev_acquisition_start,
-       .dev_acquisition_stop = dev_acquisition_stop,
+       .dev_acquisition_stop = std_serial_dev_acquisition_stop,
        .context = NULL,
 };
 SR_REGISTER_DEV_DRIVER(gwinstek_psp_driver_info);
index 9034786922dd58c34be63dda3bacf0f36c244cba..01ce79c79f7908045dc0a51f56f1e1fac1bc1082 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <config.h>
 #include "protocol.h"
+#include <config.h>
+#include <math.h>
+
+#define CMD_ALL_QUERY "L\r\n"
+#define RESPONSE_ALL_QUERY_LEN 37
+
+static void give_device_time_to_process(struct dev_context *devc)
+{
+       int64_t sleeping_time;
+
+       if (!devc->next_req_time)
+               return;
+
+       sleeping_time = devc->next_req_time - g_get_monotonic_time();
+       if (sleeping_time > 0) {
+               g_usleep(sleeping_time);
+               sr_spew("Sleeping %" PRIi64 " us for processing", sleeping_time);
+       }
+}
+
+SR_PRIV int gwinstek_psp_send_cmd(struct sr_serial_dev_inst *serial,
+    struct dev_context *devc, const char *cmd, gboolean lock)
+{
+       int ret;
+
+       if (lock)
+               g_mutex_lock(&devc->rw_mutex);
+
+       give_device_time_to_process(devc);
+
+       sr_dbg("Sending '%s'.", cmd);
+       if ((ret = serial_write_blocking(serial, cmd, strlen(cmd), 0)) < 0) {
+               sr_err("Error sending command: %d.", ret);
+       }
+
+       devc->next_req_time =
+               g_get_monotonic_time() + GWINSTEK_PSP_PROCESSING_TIME_MS * 1000;
+
+       if (lock)
+               g_mutex_unlock(&devc->rw_mutex);
+
+       return ret;
+}
+
+/*
+ * Check for extra LF or CRLF (depends on whether device is in URPSP1 or URPSP2 mode.
+ * Must be called right after calling gwinstek_psp_get_all_values.
+ */
+SR_PRIV int gwinstek_psp_check_terminator(struct sr_serial_dev_inst *serial,
+    struct dev_context *devc)
+{
+       int bytes_left, ret;
+
+       g_mutex_lock(&devc->rw_mutex);
+       /* Sleep for a while to be extra sure the device has sent everything */
+       g_usleep(20 * 1000);
+
+       bytes_left = serial_has_receive_data(serial);
+       sr_dbg("%d bytes left in buffer", bytes_left);
+
+       if (bytes_left == 0) {
+               /* 2, must already be set if we got here */
+               sr_dbg("Device is in URPSP2 mode, terminator is CRLF");
+       } else if (bytes_left == 1) {
+               devc->msg_terminator_len = 3;
+               sr_dbg("Device is in URPSP1 mode, terminator is CRCRLF");
+       } else {
+               sr_err("Don't know how to deal with %d bytes left", bytes_left);
+               g_mutex_unlock(&devc->rw_mutex);
+               return SR_ERR;
+       }
+
+       ret = serial_flush(serial);
+
+       g_mutex_unlock(&devc->rw_mutex);
+       return ret;
+}
+
+/*
+ * Can we trust that the reported voltage is the same as the voltage target? If
+ * the output is off or the device is in CV mode, the answer is likely yes.
+ * Only run this once during the initialization, since naively detecting CV
+ * mode is not terribly reliable, especially when there is an ongoing
+ * transition from CV to CC or vice-versa.
+ */
+SR_PRIV int gwinstek_psp_get_initial_voltage_target(struct dev_context *devc)
+{
+       if (!devc->output_enabled || (fabs(devc->current - devc->current_limit) >= 0.01)) {
+               devc->voltage_target = devc->voltage;
+               sr_dbg("Set initial voltage target to %.2f", devc->voltage_target);
+       } else {
+               /* Would it be more correct to fail the scan here? */
+               sr_warn("Could not determine actual voltage target, falling back to 0");
+               devc->voltage_target = 0;
+       }
+
+       return SR_OK;
+}
+
+SR_PRIV int gwinstek_psp_get_all_values(struct sr_serial_dev_inst *serial,
+    struct dev_context *devc)
+{
+       int ret, bytes_to_read;
+       char buf[50], *b;
+       uint64_t now, delta;
+
+       g_mutex_lock(&devc->rw_mutex);
+
+       now = g_get_monotonic_time();
+       delta = now - devc->last_status_query_time;
+       if (delta <= GWINSTEK_PSP_STATUS_POLL_TIME_MS * 1000) {
+               sr_spew("Last status query was only %" PRIu64 "us ago, returning", delta);
+               g_mutex_unlock(&devc->rw_mutex);
+               return SR_OK;
+       }
+
+       ret = gwinstek_psp_send_cmd(serial, devc, CMD_ALL_QUERY, FALSE);
+       devc->last_status_query_time = now;
+
+       if (ret < 0) {
+               g_mutex_unlock(&devc->rw_mutex);
+               return ret;
+       }
+
+       b = buf;
+       memset(buf, 0, sizeof(buf));
+       bytes_to_read = RESPONSE_ALL_QUERY_LEN + devc->msg_terminator_len;
+       ret = serial_read_blocking(serial, b, bytes_to_read, 1000);
+
+       if (ret < 0) {
+               sr_err("Error %d reading from device.", ret);
+               g_mutex_unlock(&devc->rw_mutex);
+               return ret;
+       }
+
+       sr_dbg("Received: '%s'", buf);
+
+       if (ret != bytes_to_read || sscanf(buf, "V%fA%fW%fU%dI%f", &devc->voltage,
+                       &devc->current, &devc->power,
+                       &devc->voltage_limit, &devc->current_limit) != 5) {
+               sr_err("Parsing status payload failed");
+               while (serial_read_blocking(serial, b, 1, 1000) > 0);
+               g_mutex_unlock(&devc->rw_mutex);
+               return SR_ERR;
+       }
+
+       devc->output_enabled = (buf[31] == '1');
+       devc->otp_active = (buf[32] == '1');
+
+       if (devc->output_enabled) {
+               devc->voltage_or_0 = devc->voltage;
+       } else {
+               devc->voltage_target = devc->voltage;
+               devc->voltage_target_updated = g_get_monotonic_time();
+               devc->voltage_or_0 = 0;
+       }
+
+       sr_spew("Status: voltage_or_0=%.2f, voltage_target=%.2f, current=%.3f, power=%.1f, "
+               "voltage_limit=%d, current_limit=%.2f",
+               devc->voltage_or_0, devc->voltage_target, devc->current, devc->power,
+               devc->voltage_limit, devc->current_limit);
+
+       g_mutex_unlock(&devc->rw_mutex);
+       return SR_OK;
+}
 
 SR_PRIV int gwinstek_psp_receive_data(int fd, int revents, void *cb_data)
 {
-       const struct sr_dev_inst *sdi;
+       struct sr_dev_inst *sdi;
        struct dev_context *devc;
+       struct sr_serial_dev_inst *serial;
+       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;
+       gboolean otp_active_prev;
+       gboolean output_enabled_prev;
+       GSList *l;
 
        (void)fd;
+       (void)revents;
 
        sdi = cb_data;
        if (!sdi)
@@ -35,9 +209,74 @@ SR_PRIV int gwinstek_psp_receive_data(int fd, int revents, void *cb_data)
        if (!devc)
                return TRUE;
 
-       if (revents == G_IO_IN) {
-               /* TODO */
+       serial = sdi->conn;
+
+       otp_active_prev = devc->otp_active;
+       output_enabled_prev = devc->output_enabled;
+
+       gwinstek_psp_get_all_values(serial, devc);
+
+       if (otp_active_prev != devc->otp_active) {
+               sr_session_send_meta(sdi, SR_CONF_OVER_TEMPERATURE_PROTECTION_ACTIVE,
+                                    g_variant_new_boolean(devc->otp_active));
+       }
+
+       if (output_enabled_prev != devc->output_enabled) {
+               sr_session_send_meta(sdi, SR_CONF_ENABLED,
+                                    g_variant_new_boolean(devc->output_enabled));
        }
 
+       if (devc->set_voltage_target != devc->voltage_target &&
+           (devc->set_voltage_target_updated + 1000 * 1000) <
+                   devc->voltage_target_updated) {
+               /* The device reports a voltage target that is different from
+                * the one that was last set. Trust the device if the information
+                * is more recent. */
+               sr_dbg("Updating session voltage target to %.2f",
+                      devc->voltage_target);
+               sr_session_send_meta(sdi, SR_CONF_VOLTAGE_TARGET,
+                                    g_variant_new_double(devc->voltage_target));
+               devc->set_voltage_target = devc->voltage_target;
+               devc->set_voltage_target_updated = g_get_monotonic_time();
+       }
+
+       /* Note: digits/spec_digits will be overridden later. */
+       sr_analog_init(&analog, &encoding, &meaning, &spec, 0);
+
+       packet.type = SR_DF_ANALOG;
+       packet.payload = &analog;
+       meaning.mqflags = SR_MQFLAG_DC;
+       analog.num_samples = 1;
+
+       /* Voltage */
+       l = g_slist_copy(sdi->channels);
+       l = g_slist_remove_link(l, g_slist_nth(l, 1));
+       meaning.channels = l;
+       meaning.mq = SR_MQ_VOLTAGE;
+       meaning.mqflags = SR_MQFLAG_DC;
+       meaning.unit = SR_UNIT_VOLT;
+       encoding.digits = 2;
+       spec.spec_digits = 2;
+       analog.data = &devc->voltage_or_0;
+       sr_session_send(sdi, &packet);
+       g_slist_free(l);
+
+       /* Current */
+       l = g_slist_copy(sdi->channels);
+       l = g_slist_remove_link(l, g_slist_nth(l, 0));
+       meaning.channels = l;
+       meaning.mq = SR_MQ_CURRENT;
+       meaning.unit = SR_UNIT_AMPERE;
+       encoding.digits = 3;
+       spec.spec_digits = 3;
+       analog.data = &devc->current;
+       sr_session_send(sdi, &packet);
+       g_slist_free(l);
+
+       sr_sw_limits_update_samples_read(&devc->limits, 1);
+
+       if (sr_sw_limits_check(&devc->limits))
+               sr_dev_acquisition_stop(sdi);
+
        return TRUE;
 }
index f477c7fcad1638f88fe002e4e62e3fd8472fa159..a76a6c20fea99f70d1b5768b4be24388520203cc 100644 (file)
 
 #define LOG_PREFIX "gwinstek-psp"
 
+#define GWINSTEK_PSP_PROCESSING_TIME_MS 50
+#define GWINSTEK_PSP_STATUS_POLL_TIME_MS 245 /**< 'L' query response time. */
+
+/* Information on single model */
+struct gwinstek_psp_model {
+       const char *vendor;    /**< Vendor name */
+       const char *name;      /**< Model name */
+       const double *voltage; /**< References: Min, max, step */
+       const double *current; /**< References: Min, max, step */
+};
+
 struct dev_context {
+       const struct gwinstek_psp_model *model; /**< Model information. */
+
+       struct sr_sw_limits limits;
+       int64_t next_req_time;
+       int64_t last_status_query_time;
+       GMutex rw_mutex;
+
+       float power;            /**< Last power value [W] read from device. */
+       float current;          /**< Last current value [A] read from device. */
+       float current_limit;    /**< Output current set. */
+       float voltage;          /**< Last voltage value [V] read from device. */
+       float voltage_or_0;     /**< Same, but 0 if output is off. */
+       int voltage_limit;      /**< Output voltage limit. */
+
+       /*< Output voltage target. The device has no means to query this
+        * directly. It's equal to the voltage if the output is disabled
+        * (detectable) or the device is in CV mode (undetectable).*/
+       float voltage_target;
+       int64_t voltage_target_updated; /**< When device last reported a voltage target. */
+
+       float set_voltage_target;           /**< The last set output voltage target. */
+       int64_t set_voltage_target_updated; /**< When the voltage target was last set. */
+
+       gboolean output_enabled; /**< Is the output enabled? */
+       gboolean otp_active;     /**< Is the overtemperature protection active? */
+
+       int msg_terminator_len; /** < 2 or 3, depending on the URPSP1/2 setting */
 };
 
+SR_PRIV int gwinstek_psp_send_cmd(struct sr_serial_dev_inst *serial,
+    struct dev_context *devc, const char* cmd, gboolean lock);
+SR_PRIV int gwinstek_psp_check_terminator(struct sr_serial_dev_inst *serial,
+    struct dev_context *devc);
+SR_PRIV int gwinstek_psp_get_initial_voltage_target(struct dev_context *devc);
+SR_PRIV int gwinstek_psp_get_all_values(struct sr_serial_dev_inst *serial,
+       struct dev_context *devc);
 SR_PRIV int gwinstek_psp_receive_data(int fd, int revents, void *cb_data);
 
 #endif