]> sigrok.org Git - libsigrok.git/commitdiff
rdtech-um: Add initial support for the RDTech UMxx series
authorAndreas Sandberg <redacted>
Sun, 28 Oct 2018 22:45:57 +0000 (22:45 +0000)
committerUwe Hermann <redacted>
Thu, 4 Jun 2020 22:25:26 +0000 (00:25 +0200)
This changeset adds support for the RDTech UMxx series of USB power
meters. The driver has been tested with the RDTech UM24C, but should
support the UM24C, UM25C, and the UM34C.

Currently, the driver reports the following channels:
  * V: VBus voltage
  * I: VBus current
  * D+: D+ voltage
  * D-: D- voltage
  * T: Device temperature
  * E: Energy consumed in threshold-based recording mode.

The number of significant digits shown for each channel has been set
to match the number of digits shown on a UM24C.

Missing features:

  * There is currently no support for configuring threshold-based
    recording from sigrok, but this can be done on the device itself.

  * Fast charging mode currently not logged.

Usage example:

sigrok-cli -d rdtech-um:conn=bt/rfcomm/MAC --scan
sigrok-cli -d rdtech-um:conn=/dev/rfcomm0 --scan

Known issues:

  * When using sigrok's Bluetooth transport implementation, the device
    is disconnected between probing and sampling. Some devices (e.g.,
    the UM24C), dislikes this and can't be reconnected reliably for
    sampling. This is not an issue when setting up a rfcomm device
    manually and using it as a serial port.

Kudos to Sven Slootweg for documenting most of the protocol.

Signed-off-by: Andreas Sandberg <redacted>
Makefile.am
configure.ac
src/hardware/rdtech-um/api.c [new file with mode: 0644]
src/hardware/rdtech-um/protocol.c [new file with mode: 0644]
src/hardware/rdtech-um/protocol.h [new file with mode: 0644]

index 0695ee083ea1482634e7149cd9ccdede63490e61..31e4fba0af8814c6d41cb59916f9d4b84874aa65 100644 (file)
@@ -514,6 +514,12 @@ src_libdrivers_la_SOURCES += \
        src/hardware/rdtech-dps/protocol.c \
        src/hardware/rdtech-dps/api.c
 endif
+if HW_RDTECH_UM
+src_libdrivers_la_SOURCES += \
+       src/hardware/rdtech-um/protocol.h \
+       src/hardware/rdtech-um/protocol.c \
+       src/hardware/rdtech-um/api.c
+endif
 if HW_RIGOL_DS
 src_libdrivers_la_SOURCES += \
        src/hardware/rigol-ds/protocol.h \
index 4216e4f6282364ea25b57ba24444d974fc0c0ed0..abd8d056affbb877583986e0a20aa475477ab786 100644 (file)
@@ -302,6 +302,7 @@ SR_DRIVER([OpenBench Logic Sniffer], [openbench-logic-sniffer], [serial_comm])
 SR_DRIVER([PCE PCE-322A], [pce-322a], [serial_comm])
 SR_DRIVER([Pipistrello-OLS], [pipistrello-ols], [libftdi])
 SR_DRIVER([RDTech DPSxxxx/DPHxxxx], [rdtech-dps], [serial_comm])
+SR_DRIVER([RDTech UMXX], [rdtech-um], [serial_comm])
 SR_DRIVER([Rigol DS], [rigol-ds])
 SR_DRIVER([Rohde&Schwarz SME-0x], [rohde-schwarz-sme-0x], [serial_comm])
 SR_DRIVER([Saleae Logic16], [saleae-logic16], [libusb])
diff --git a/src/hardware/rdtech-um/api.c b/src/hardware/rdtech-um/api.c
new file mode 100644 (file)
index 0000000..fbc38b1
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2018-2020 Andreas Sandberg <andreas@sandberg.pp.se>
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <config.h>
+#include <glib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+
+#include "protocol.h"
+
+#define RDTECH_UM_SERIALCOMM "115200/8n1"
+
+static const uint32_t scanopts[] = {
+       SR_CONF_CONN,
+       SR_CONF_SERIALCOMM,
+};
+
+static const uint32_t drvopts[] = {
+       SR_CONF_ENERGYMETER,
+};
+
+static const uint32_t devopts[] = {
+       SR_CONF_CONTINUOUS,
+       SR_CONF_LIMIT_SAMPLES | SR_CONF_SET,
+       SR_CONF_LIMIT_MSEC | SR_CONF_SET,
+};
+
+static GSList *rdtech_um_scan(struct sr_dev_driver *di, const char *conn,
+                             const char *serialcomm)
+{
+       struct sr_serial_dev_inst *serial;
+       const struct rdtech_um_profile *p = NULL;
+       GSList *devices = NULL;
+       struct dev_context *devc = NULL;
+       struct sr_dev_inst *sdi = NULL;
+
+       serial = sr_serial_dev_inst_new(conn, serialcomm);
+       if (serial_open(serial, SERIAL_RDWR) != SR_OK)
+               goto err_out;
+
+       p = rdtech_um_probe(serial);
+       if (!p) {
+               sr_err("Failed to find a supported RDTech UM device.");
+               goto err_out_serial;
+       }
+
+       devc = g_malloc0(sizeof(struct dev_context));
+       sdi = g_malloc0(sizeof(struct sr_dev_inst));
+
+       sr_sw_limits_init(&devc->limits);
+       devc->profile = p;
+
+       sdi->status = SR_ST_INACTIVE;
+       sdi->vendor = g_strdup("RDTech");
+       sdi->model = g_strdup(p->model_name);
+       sdi->version = NULL;
+       sdi->inst_type = SR_INST_SERIAL;
+       sdi->conn = serial;
+       sdi->priv = devc;
+
+       for (int i = 0; p->channels[i].name; i++)
+               sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE,
+                              p->channels[i].name);
+
+       devices = g_slist_append(devices, sdi);
+       serial_close(serial);
+       if (!devices)
+               sr_serial_dev_inst_free(serial);
+
+       return std_scan_complete(di, devices);
+
+err_out_serial:
+       serial_close(serial);
+err_out:
+       sr_serial_dev_inst_free(serial);
+
+       return NULL;
+}
+
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
+{
+       struct sr_config *src;
+       const char *conn = NULL;
+       const char *serialcomm = RDTECH_UM_SERIALCOMM;
+
+       for (GSList *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;
+
+       return rdtech_um_scan(di, conn, serialcomm);
+}
+
+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;
+
+       (void)cg;
+
+       devc = sdi->priv;
+
+       return sr_sw_limits_config_set(&devc->limits, key, data);
+}
+
+static int config_list(uint32_t key, GVariant **data,
+                      const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
+{
+       return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
+}
+
+static int dev_acquisition_start(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc = sdi->priv;
+       struct sr_serial_dev_inst *serial = sdi->conn;
+
+       sr_sw_limits_acquisition_start(&devc->limits);
+       std_session_send_df_header(sdi);
+
+       serial_source_add(sdi->session, serial, G_IO_IN, 50,
+                         rdtech_um_receive_data, (void *)sdi);
+
+       return rdtech_um_poll(sdi);
+}
+
+static struct sr_dev_driver rdtech_um_driver_info = {
+       .name = "rdtech-um",
+       .longname = "RDTech UMxx USB power meter",
+       .api_version = 1,
+       .init = std_init,
+       .cleanup = std_cleanup,
+       .scan = scan,
+       .dev_list = std_dev_list,
+       .dev_clear = std_dev_clear,
+       .config_get = NULL,
+       .config_set = config_set,
+       .config_list = config_list,
+       .dev_open = std_serial_dev_open,
+       .dev_close = std_serial_dev_close,
+       .dev_acquisition_start = dev_acquisition_start,
+       .dev_acquisition_stop = std_serial_dev_acquisition_stop,
+       .context = NULL,
+};
+SR_REGISTER_DEV_DRIVER(rdtech_um_driver_info);
diff --git a/src/hardware/rdtech-um/protocol.c b/src/hardware/rdtech-um/protocol.c
new file mode 100644 (file)
index 0000000..25bad0d
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2018-2020 Andreas Sandberg <andreas@sandberg.pp.se>
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+#include "protocol.h"
+
+#define SERIAL_WRITE_TIMEOUT_MS 1
+
+#define UM_POLL_LEN 130
+#define UM_POLL_PERIOD_MS 100
+#define UM_TIMEOUT_MS 1000
+
+#define UM_CMD_POLL 0xf0
+
+static const struct binary_analog_channel rdtech_default_channels[] = {
+       { "V",  { 2, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "I",  { 4, BVT_BE_UINT16, 0.001, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE },
+       { "D+", { 96, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "D-", { 98, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "T", { 10, BVT_BE_UINT16, 1.0, },  0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS },
+       /* Threshold-based recording (mWh) */
+       { "E", { 106, BVT_BE_UINT32, 0.001, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
+       { NULL, },
+};
+
+static const struct binary_analog_channel rdtech_um25c_channels[] = {
+       { "V", { 2, BVT_BE_UINT16, 0.001, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "I", { 4, BVT_BE_UINT16, 0.0001, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE },
+       { "D+", { 96, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "D-", { 98, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT },
+       { "T", { 10, BVT_BE_UINT16, 1.0, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS },
+       /* Threshold-based recording (mWh) */
+       { "E", { 106, BVT_BE_UINT32, 0.001, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR },
+       { NULL, },
+};
+
+static int poll_csum_fff1(char buf[], int len)
+{
+       if (len != UM_POLL_LEN)
+               return 0;
+       else
+               return RB16(&buf[len - 2]) == 0xFFF1;
+}
+
+static int poll_csum_um34c(char buf[], int len)
+{
+       static const int positions[] = {
+               1, 3, 7, 9, 15, 17, 19, 23, 31, 39, 41, 45, 49, 53,
+               55, 57, 59, 63, 67, 69, 73, 79, 83, 89, 97, 99, 109,
+               111, 113, 119, 121, 127,
+       };
+       unsigned int i;
+       uint8_t csum = 0;
+
+       if (len != UM_POLL_LEN)
+               return 0;
+
+       for (i = 0; i < ARRAY_SIZE(positions); i++)
+               csum ^= buf[positions[i]];
+
+       return csum == (uint8_t)buf[len - 1];
+}
+
+static const struct rdtech_um_profile um_profiles[] = {
+       { "UM24C", RDTECH_UM24C, rdtech_default_channels, &poll_csum_fff1, },
+       { "UM25C", RDTECH_UM25C, rdtech_um25c_channels, &poll_csum_fff1, },
+       { "UM34C", RDTECH_UM34C, rdtech_default_channels, &poll_csum_um34c, },
+};
+
+static const struct rdtech_um_profile *find_profile(uint16_t id)
+{
+       unsigned int i;
+       for (i = 0; i < ARRAY_SIZE(um_profiles); i++) {
+               if (um_profiles[i].model_id == id)
+                       return &um_profiles[i];
+       }
+       return NULL;
+}
+
+SR_PRIV const struct rdtech_um_profile *rdtech_um_probe(struct sr_serial_dev_inst *serial)
+{
+       const struct rdtech_um_profile *p;
+       static const uint8_t request = UM_CMD_POLL;
+       char buf[RDTECH_UM_BUFSIZE];
+       int len;
+
+       if (serial_write_blocking(serial, &request, sizeof(request),
+                                  SERIAL_WRITE_TIMEOUT_MS) < 0) {
+               sr_err("Unable to send probe request.");
+               return NULL;
+       }
+
+       len = serial_read_blocking(serial, buf, UM_POLL_LEN, UM_TIMEOUT_MS);
+       if (len != UM_POLL_LEN) {
+               sr_err("Failed to read probe response.");
+               return NULL;
+       }
+
+       p = find_profile(RB16(&buf[0]));
+       if (!p) {
+               sr_err("Unrecognized UM device (0x%.4" PRIx16 ")!", RB16(&buf[0]));
+               return NULL;
+       }
+
+       if (!p->poll_csum(buf, len)) {
+               sr_err("Probe response contains illegal checksum or end marker.\n");
+               return NULL;
+       }
+
+       return p;
+}
+
+SR_PRIV int rdtech_um_poll(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc = sdi->priv;
+       struct sr_serial_dev_inst *serial = sdi->conn;
+       static const uint8_t request = UM_CMD_POLL;
+
+       if (serial_write_blocking(serial, &request, sizeof(request),
+                                  SERIAL_WRITE_TIMEOUT_MS) < 0) {
+               sr_err("Unable to send poll request.");
+               return SR_ERR;
+       }
+
+       devc->cmd_sent_at = g_get_monotonic_time() / 1000;
+
+       return SR_OK;
+}
+
+static void handle_poll_data(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc = sdi->priv;
+       int i;
+       GSList *ch;
+
+       sr_spew("Received poll packet (len: %d).", devc->buflen);
+       if (devc->buflen != UM_POLL_LEN) {
+               sr_err("Unexpected poll packet length: %i", devc->buflen);
+               return;
+       }
+
+       for (ch = sdi->channels, i = 0; ch; ch = g_slist_next(ch), i++)
+               bv_send_analog_channel(sdi, ch->data,
+                                      &devc->profile->channels[i],
+                                      devc->buf, devc->buflen);
+
+       sr_sw_limits_update_samples_read(&devc->limits, 1);
+}
+
+static void recv_poll_data(struct sr_dev_inst *sdi, struct sr_serial_dev_inst *serial)
+{
+       struct dev_context *devc = sdi->priv;
+       const struct rdtech_um_profile *p = devc->profile;
+       int len;
+
+       /* Serial data arrived. */
+       while (devc->buflen < UM_POLL_LEN) {
+               len = serial_read_nonblocking(serial, devc->buf + devc->buflen, 1);
+               if (len < 1)
+                       return;
+
+               devc->buflen++;
+
+               /* Check if the poll model ID matches the profile. */
+               if (devc->buflen == 2 && RB16(devc->buf) != p->model_id) {
+                       sr_warn("Illegal model ID in poll response (0x%.4" PRIx16 "),"
+                               " skipping 1 byte.",
+                               RB16(devc->buf));
+                       devc->buflen--;
+                       memmove(devc->buf, devc->buf + 1, devc->buflen);
+               }
+       }
+
+       if (devc->buflen == UM_POLL_LEN && p->poll_csum(devc->buf, devc->buflen))
+               handle_poll_data(sdi);
+       else
+               sr_warn("Skipping packet with illegal checksum / end marker.");
+
+       devc->buflen = 0;
+}
+
+SR_PRIV int rdtech_um_receive_data(int fd, int revents, void *cb_data)
+{
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       struct sr_serial_dev_inst *serial;
+       int64_t now, elapsed;
+
+       (void)fd;
+
+       if (!(sdi = cb_data))
+               return TRUE;
+
+       if (!(devc = sdi->priv))
+               return TRUE;
+
+       serial = sdi->conn;
+       if (revents == G_IO_IN)
+               recv_poll_data(sdi, serial);
+
+       if (sr_sw_limits_check(&devc->limits)) {
+               sr_dev_acquisition_stop(sdi);
+               return TRUE;
+       }
+
+       now = g_get_monotonic_time() / 1000;
+       elapsed = now - devc->cmd_sent_at;
+
+       if (elapsed > UM_POLL_PERIOD_MS)
+               rdtech_um_poll(sdi);
+
+       return TRUE;
+}
diff --git a/src/hardware/rdtech-um/protocol.h b/src/hardware/rdtech-um/protocol.h
new file mode 100644 (file)
index 0000000..f344f1b
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2018-2020 Andreas Sandberg <andreas@sandberg.pp.se>
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBSIGROK_HARDWARE_RDTECH_UM_PROTOCOL_H
+#define LIBSIGROK_HARDWARE_RDTECH_UM_PROTOCOL_H
+
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "rdtech-um"
+
+#define RDTECH_UM_BUFSIZE 256
+
+enum rdtech_um_model_id {
+       RDTECH_UM24C = 0x0963,
+       RDTECH_UM25C = 0x09c9,
+       RDTECH_UM34C = 0x0d4c,
+};
+
+enum rdtech_um_checksum {
+       RDTECH_CSUM_STATIC_FFF1,
+       RDTECH_CSUM_UM34C,
+};
+
+/* Supported device profiles */
+struct rdtech_um_profile {
+       const char *model_name;
+       enum rdtech_um_model_id model_id;
+       const struct binary_analog_channel *channels;
+
+       /* Verify poll packet checksum; return 1 if OK, 0 otherwise. */
+       int (*poll_csum)(char buf[], int len);
+};
+
+struct dev_context {
+       const struct rdtech_um_profile *profile;
+       struct sr_sw_limits limits;
+
+       char buf[RDTECH_UM_BUFSIZE];
+       int buflen;
+       int64_t cmd_sent_at;
+};
+
+SR_PRIV const struct rdtech_um_profile *rdtech_um_probe(struct sr_serial_dev_inst *serial);
+SR_PRIV int rdtech_um_receive_data(int fd, int revents, void *cb_data);
+SR_PRIV int rdtech_um_poll(const struct sr_dev_inst *sdi);
+
+#endif