]> sigrok.org Git - libsigrok.git/commitdiff
appa-55ii: driver implementation with Live and Memory data source support
authorAurelien Jacobs <redacted>
Wed, 11 Dec 2013 23:22:32 +0000 (00:22 +0100)
committerBert Vermeulen <redacted>
Mon, 23 Dec 2013 00:21:52 +0000 (01:21 +0100)
configure.ac
hardware/appa-55ii/api.c
hardware/appa-55ii/protocol.c
hardware/appa-55ii/protocol.h

index 3ee34a89c3f52b074d6fa29eea11f49cedc2673b..cbe91cf39de17db843a8663db2679752e2b602ee 100644 (file)
@@ -278,10 +278,10 @@ PKG_CHECK_MODULES([libserialport], [libserialport >= 0.1.0],
        [have_libserialport="yes"; CFLAGS="$CFLAGS $libserialport_CFLAGS";
        LIBS="$LIBS $libserialport_LIBS";
        SR_PKGLIBS="$SR_PKGLIBS libserialport"],
-       [have_libserialport="no"; HW_AGILENT_DMM="no"; HW_BRYMEN_DMM="no";
-       HW_CEM_DT_885X="no"; HW_CENTER_3XX="no"; HW_COLEAD_SLM="no";
-       HW_FLUKE_DMM="no"; HW_GMC_MH_1X_2X="no"; HW_HAMEG_HMO="no";
-       HW_LINK_MSO19="no"; HW_MIC_985XX="no";
+       [have_libserialport="no"; HW_AGILENT_DMM="no"; HW_APPA_55II="no";
+       HW_BRYMEN_DMM="no"; HW_CEM_DT_885X="no"; HW_CENTER_3XX="no";
+       HW_COLEAD_SLM="no"; HW_FLUKE_DMM="no"; HW_GMC_MH_1X_2X="no";
+       HW_HAMEG_HMO="no"; HW_LINK_MSO19="no"; HW_MIC_985XX="no";
        HW_NORMA_DMM="no"; HW_OLS="no"; HW_RIGOL_DS="no";
        HW_SERIAL_DMM="no"; HW_TELEINFO="no"; HW_TONDAJ_SL_814="no"])
 
index be9b389972427bcd27c618c83a4cbc5d1aa20920..af8fdac2e633bf0e1df9b818ff8cb6da938b31a3 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <string.h>
 #include "protocol.h"
 
+static const int32_t hwopts[] = {
+       SR_CONF_CONN,
+       SR_CONF_SERIALCOMM,
+};
+
+static const int32_t hwcaps[] = {
+       SR_CONF_THERMOMETER,
+       SR_CONF_LIMIT_SAMPLES,
+       SR_CONF_LIMIT_MSEC,
+       SR_CONF_CONTINUOUS,
+       SR_CONF_DATA_SOURCE,
+};
+
+static const char *data_sources[] = {
+       "Live",
+       "Memory",
+};
+
 SR_PRIV struct sr_dev_driver appa_55ii_driver_info;
 static struct sr_dev_driver *di = &appa_55ii_driver_info;
 
@@ -30,16 +49,76 @@ static int init(struct sr_context *sr_ctx)
 static GSList *scan(GSList *options)
 {
        struct drv_context *drvc;
-       GSList *devices;
+       struct dev_context *devc;
+       struct sr_serial_dev_inst *serial;
+       struct sr_dev_inst *sdi;
+       struct sr_probe *probe;
+       GSList *devices = NULL, *l;
+       const char *conn = NULL, *serialcomm = NULL;
+       uint8_t buf[50];
+       size_t len = sizeof(buf);
+
+       for (l = options; l; l = l->next) {
+               struct sr_config *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;
+       if (!serialcomm)
+               serialcomm = "9600/8n1";
+
+       if (!(serial = sr_serial_dev_inst_new(conn, serialcomm)))
+               return NULL;
+       if (serial_open(serial, SERIAL_RDONLY | SERIAL_NONBLOCK) != SR_OK)
+               return NULL;
 
-       (void)options;
+       sr_info("Probing serial port %s.", conn);
 
-       devices = NULL;
        drvc = di->priv;
        drvc->instances = NULL;
+       serial_flush(serial);
+
+       /* Let's get a bit of data and see if we can find a packet. */
+       if (serial_stream_detect(serial, buf, &len, 25,
+                                appa_55ii_packet_valid, 500, 9600) != SR_OK)
+               goto scan_cleanup;
+
+       sr_info("Found device on port %s.", conn);
+
+       if (!(sdi = sr_dev_inst_new(0, SR_ST_INACTIVE, "APPA", "55II", "")))
+               goto scan_cleanup;
+
+       if (!(devc = g_try_malloc0(sizeof(struct dev_context)))) {
+               sr_err("Device context malloc failed.");
+               goto scan_cleanup;
+       }
+
+       devc->data_source = DEFAULT_DATA_SOURCE;
+
+       sdi->inst_type = SR_INST_SERIAL;
+       sdi->conn = serial;
+       sdi->priv = devc;
+       sdi->driver = di;
 
-       /* TODO: scan for devices, either based on a SR_CONF_CONN option
-        * or on a USB scan. */
+       if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "T1")))
+               goto scan_cleanup;
+       sdi->probes = g_slist_append(sdi->probes, probe);
+       if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "T2")))
+               goto scan_cleanup;
+       sdi->probes = g_slist_append(sdi->probes, probe);
+
+       drvc->instances = g_slist_append(drvc->instances, sdi);
+       devices = g_slist_append(devices, sdi);
+
+scan_cleanup:
+       serial_close(serial);
 
        return devices;
 }
@@ -54,123 +133,142 @@ static int dev_clear(void)
        return std_dev_clear(di, NULL);
 }
 
-static int dev_open(struct sr_dev_inst *sdi)
-{
-       (void)sdi;
-
-       /* TODO: get handle from sdi->conn and open it. */
-
-       sdi->status = SR_ST_ACTIVE;
-
-       return SR_OK;
-}
-
-static int dev_close(struct sr_dev_inst *sdi)
-{
-       (void)sdi;
-
-       /* TODO: get handle from sdi->conn and close it. */
-
-       sdi->status = SR_ST_INACTIVE;
-
-       return SR_OK;
-}
-
 static int cleanup(void)
 {
-       dev_clear();
-
-       /* TODO: free other driver resources, if any. */
-
-       return SR_OK;
+       return dev_clear();
 }
 
 static int config_get(int key, GVariant **data, const struct sr_dev_inst *sdi,
-               const struct sr_probe_group *probe_group)
+                      const struct sr_probe_group *probe_group)
 {
-       int ret;
+       struct dev_context *devc = sdi->priv;
 
-       (void)sdi;
-       (void)data;
        (void)probe_group;
 
-       ret = SR_OK;
        switch (key) {
-       /* TODO */
+       case SR_CONF_LIMIT_SAMPLES:
+               *data = g_variant_new_uint64(devc->limit_samples);
+               break;
+       case SR_CONF_LIMIT_MSEC:
+               *data = g_variant_new_uint64(devc->limit_msec);
+               break;
+       case SR_CONF_DATA_SOURCE:
+               *data = g_variant_new_string(data_sources[devc->data_source]);
+               break;
        default:
                return SR_ERR_NA;
        }
 
-       return ret;
+       return SR_OK;
 }
 
 static int config_set(int key, GVariant *data, const struct sr_dev_inst *sdi,
                const struct sr_probe_group *probe_group)
 {
-       int ret;
+       struct dev_context *devc;
 
-       (void)data;
        (void)probe_group;
 
        if (sdi->status != SR_ST_ACTIVE)
                return SR_ERR_DEV_CLOSED;
 
-       ret = SR_OK;
+       if (!(devc = sdi->priv)) {
+               sr_err("sdi->priv was NULL.");
+               return SR_ERR_BUG;
+       }
+
        switch (key) {
-       /* TODO */
+       case SR_CONF_LIMIT_SAMPLES:
+               devc->limit_samples = g_variant_get_uint64(data);
+               sr_dbg("Setting sample limit to %" PRIu64 ".", devc->limit_samples);
+               break;
+       case SR_CONF_LIMIT_MSEC:
+               devc->limit_msec = g_variant_get_uint64(data);
+               sr_dbg("Setting time limit to %" PRIu64 "ms.", devc->limit_msec);
+               break;
+       case SR_CONF_DATA_SOURCE: {
+               const char *tmp_str = g_variant_get_string(data, NULL);
+               unsigned int i;
+               for (i=0; i<ARRAY_SIZE(data_sources); i++)
+                       if (!strcmp(tmp_str, data_sources[i])) {
+                               devc->data_source = i;
+                               break;
+                       }
+               if (i == ARRAY_SIZE(data_sources))
+                       return SR_ERR;
+               break;
+       }
        default:
-               ret = SR_ERR_NA;
+               return SR_ERR_NA;
        }
 
-       return ret;
+       return SR_OK;
 }
 
 static int config_list(int key, GVariant **data, const struct sr_dev_inst *sdi,
                const struct sr_probe_group *probe_group)
 {
-       int ret;
-
        (void)sdi;
-       (void)data;
        (void)probe_group;
 
-       ret = SR_OK;
        switch (key) {
-       /* TODO */
+       case SR_CONF_SCAN_OPTIONS:
+               *data = g_variant_new_fixed_array(G_VARIANT_TYPE_INT32,
+                               hwopts, ARRAY_SIZE(hwopts), sizeof(int32_t));
+               break;
+       case SR_CONF_DEVICE_OPTIONS:
+               *data = g_variant_new_fixed_array(G_VARIANT_TYPE_INT32,
+                               hwcaps, ARRAY_SIZE(hwcaps), sizeof(int32_t));
+               break;
+       case SR_CONF_DATA_SOURCE:
+               *data = g_variant_new_strv(data_sources, ARRAY_SIZE(data_sources));
+               break;
        default:
                return SR_ERR_NA;
        }
 
-       return ret;
+       return SR_OK;
 }
 
 static int dev_acquisition_start(const struct sr_dev_inst *sdi,
-                                   void *cb_data)
+                                 void *cb_data)
 {
-       (void)sdi;
-       (void)cb_data;
+       struct sr_serial_dev_inst *serial = sdi->conn;
+       struct dev_context *devc;
 
        if (sdi->status != SR_ST_ACTIVE)
                return SR_ERR_DEV_CLOSED;
 
-       /* TODO: configure hardware, reset acquisition state, set up
-        * callbacks and send header packet. */
+       if (!(devc = sdi->priv)) {
+               sr_err("sdi->priv was NULL.");
+               return SR_ERR_BUG;
+       }
 
-       return SR_OK;
-}
+       devc->session_cb_data = cb_data;
 
-static int dev_acquisition_stop(struct sr_dev_inst *sdi, void *cb_data)
-{
-       (void)cb_data;
+       /*
+        * Reset the number of samples to take. If we've already collected our
+        * quota, but we start a new session, and don't reset this, we'll just
+        * quit without acquiring any new samples.
+        */
+       devc->num_samples = 0;
+       devc->start_time = g_get_monotonic_time();
 
-       if (sdi->status != SR_ST_ACTIVE)
-               return SR_ERR_DEV_CLOSED;
+       /* Send header packet to the session bus. */
+       std_session_send_df_header(cb_data, LOG_PREFIX);
 
-       /* TODO: stop acquisition. */
+       /* Poll every 50ms, or whenever some data comes in. */
+       serial_source_add(serial, G_IO_IN, 50, appa_55ii_receive_data, (void *)sdi);
 
        return SR_OK;
 }
 
+static int dev_acquisition_stop(struct sr_dev_inst *sdi, void *cb_data)
+{
+       return std_serial_dev_acquisition_stop(sdi, cb_data, std_serial_dev_close,
+                                              sdi->conn, LOG_PREFIX);
+}
+
 SR_PRIV struct sr_dev_driver appa_55ii_driver_info = {
        .name = "appa-55ii",
        .longname = "APPA 55II",
@@ -183,9 +281,8 @@ SR_PRIV struct sr_dev_driver appa_55ii_driver_info = {
        .config_get = config_get,
        .config_set = config_set,
        .config_list = config_list,
-       .dev_open = dev_open,
-       .dev_close = dev_close,
+       .dev_open = std_serial_dev_open,
+       .dev_close = std_serial_dev_close,
        .dev_acquisition_start = dev_acquisition_start,
        .dev_acquisition_stop = dev_acquisition_stop,
-       .priv = NULL,
 };
index 0b1f250c1ab577569ff69c16f091368307db748c..c3a715be0076c0f27a508e3636b5d9445759ec90 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <string.h>
+#include <math.h>
 #include "protocol.h"
 
+typedef enum {
+    LIVE_DATA    = 0x00,
+    LOG_METADATA = 0x11,
+    LOG_DATA     = 0x14,
+    LOG_START    = 0x18,
+    LOG_END      = 0x19,
+} PacketType;
+
+static gboolean appa_55ii_checksum(const uint8_t *buf)
+{
+       int i, size = buf[3]+4, checksum = 0;
+       for (i=0; i<size; i++)
+               checksum += buf[i];
+       return buf[size] == (checksum & 0xFF);
+}
+
+SR_PRIV gboolean appa_55ii_packet_valid(const uint8_t *buf)
+{
+       if (buf[0] == 0x55 && buf[1] == 0x55 && buf[3] <= 32
+           && appa_55ii_checksum(buf))
+               return TRUE;
+       return FALSE;
+}
+
+static uint64_t appa_55ii_flags(const uint8_t *buf)
+{
+       uint8_t disp_mode = buf[4+13];
+       uint64_t flags = 0;
+
+       if ((disp_mode & 0xF0) == 0x20)  flags |= SR_MQFLAG_HOLD;
+       if ((disp_mode & 0x0C) == 0x04)  flags |= SR_MQFLAG_MAX;
+       if ((disp_mode & 0x0C) == 0x08)  flags |= SR_MQFLAG_MIN;
+       if ((disp_mode & 0x0C) == 0x0C)  flags |= SR_MQFLAG_AVG;
+
+       return flags;
+}
+
+static float appa_55ii_temp(const uint8_t *buf, int probe)
+{
+       const uint8_t *ptr = buf + 4 + 14 + 3*probe;
+       int16_t temp = RL16(ptr);
+       uint8_t flags = ptr[2];
+
+            if (flags & 0x60)  return INFINITY;
+       else if (flags & 1)     return (float)temp / 10;
+       else                    return (float)temp;
+}
+
+static void appa_55ii_live_data(struct sr_dev_inst *sdi, const uint8_t *buf)
+{
+       struct dev_context *devc = sdi->priv;
+       struct sr_datafeed_packet packet;
+       struct sr_datafeed_analog analog = { 0 };
+       float values[APPA_55II_NUM_PROBES], *val_ptr = values;
+       int i;
+
+       if (devc->data_source != DATA_SOURCE_LIVE)
+               return;
+
+       analog.num_samples = 1;
+       analog.mq = SR_MQ_TEMPERATURE;
+       analog.unit = SR_UNIT_CELSIUS;
+       analog.mqflags = appa_55ii_flags(buf);
+       analog.data = values;
+
+       for (i=0; i<APPA_55II_NUM_PROBES; i++) {
+               struct sr_probe *probe = g_slist_nth_data(sdi->probes, i);
+               if (!probe->enabled)
+                       continue;
+               analog.probes = g_slist_append(analog.probes, probe);
+               *val_ptr++ = appa_55ii_temp(buf, i);
+       }
+
+       packet.type = SR_DF_ANALOG;
+       packet.payload = &analog;
+       sr_session_send(devc->session_cb_data, &packet);
+       g_slist_free(analog.probes);
+
+       devc->num_samples++;
+}
+
+static void appa_55ii_log_metadata(struct sr_dev_inst *sdi, const uint8_t *buf)
+{
+       struct dev_context *devc = sdi->priv;
+       devc->num_log_records = (buf[5] << 8) + buf[4];
+}
+
+static void appa_55ii_log_data_parse(struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc = sdi->priv;
+       int offset = 0;
+
+       while (devc->log_buf_len >= 20 && devc->num_log_records > 0) {
+               const uint8_t *buf = devc->log_buf + offset;
+               struct sr_datafeed_packet packet;
+               struct sr_datafeed_analog analog = { 0 };
+               float values[APPA_55II_NUM_PROBES], *val_ptr = values;
+               int i;
+
+               /* FIXME: timestamp should be sent in the packet */
+               sr_dbg("Timestamp: %02d:%02d:%02d", buf[2], buf[3], buf[4]);
+
+               analog.num_samples = 1;
+               analog.mq = SR_MQ_TEMPERATURE;
+               analog.unit = SR_UNIT_CELSIUS;
+               analog.data = values;
+
+               for (i=0; i<APPA_55II_NUM_PROBES; i++) {
+                       int16_t temp = RL16(buf+12+2*i);
+                       struct sr_probe *probe = g_slist_nth_data(sdi->probes, i);
+                       if (!probe->enabled)
+                               continue;
+                       analog.probes = g_slist_append(analog.probes, probe);
+                       *val_ptr++ = temp == 0x7FFF ? INFINITY : (float)temp / 10;
+               }
+
+               packet.type = SR_DF_ANALOG;
+               packet.payload = &analog;
+               sr_session_send(devc->session_cb_data, &packet);
+               g_slist_free(analog.probes);
+
+               devc->num_samples++;
+               devc->log_buf_len -= 20;
+               offset += 20;
+               devc->num_log_records--;
+       }
+
+       memmove(devc->log_buf, devc->log_buf + offset, devc->log_buf_len);
+}
+
+static void appa_55ii_log_data(struct sr_dev_inst *sdi, const uint8_t *buf)
+{
+       struct dev_context *devc = sdi->priv;
+       const uint8_t *ptr = buf + 4;;
+       unsigned int size = buf[3];
+
+       if (devc->data_source != DATA_SOURCE_MEMORY)
+               return;
+
+       while (size > 0) {
+               int s = MIN(size, sizeof(devc->log_buf) - devc->log_buf_len);
+               memcpy(devc->log_buf + devc->log_buf_len, ptr, s);
+               devc->log_buf_len += s;
+               size -= s;
+               ptr += s;
+
+               appa_55ii_log_data_parse(sdi);
+       }
+}
+
+static void appa_55ii_log_end(struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc = sdi->priv;
+
+       if (devc->data_source != DATA_SOURCE_MEMORY)
+               return;
+
+       sdi->driver->dev_acquisition_stop(sdi, devc->session_cb_data);
+}
+
+static const uint8_t *appa_55ii_parse_data(struct sr_dev_inst *sdi,
+                                           const uint8_t *buf, int len)
+{
+       if (len < 5)
+               return NULL;  /* need more data */
+
+       if (buf[0] != 0x55 || buf[1] != 0x55)
+               return buf + 1;  /* try to re-synchronize on a packet start */
+
+       if (len < 5+buf[3])
+               return NULL;  /* need more data */
+
+       if (!appa_55ii_checksum(buf))
+               return buf + 4 + buf[3] + 1;  /* skip broken packet */
+
+       switch ((PacketType) buf[2]) {
+       case LIVE_DATA:     appa_55ii_live_data(sdi, buf);     break;
+       case LOG_METADATA:  appa_55ii_log_metadata(sdi, buf);  break;
+       case LOG_DATA:      appa_55ii_log_data(sdi, buf);      break;
+       case LOG_START:                                        break;
+       case LOG_END:       appa_55ii_log_end(sdi);            break;
+       }
+
+       return buf + 4 + buf[3] + 1;
+}
+
 SR_PRIV int appa_55ii_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;
+       const uint8_t *ptr, *next_ptr, *end_ptr;
+       int len;
 
        (void)fd;
 
-       if (!(sdi = cb_data))
+       if (!(sdi = cb_data) || !(devc = sdi->priv) || revents != G_IO_IN)
                return TRUE;
+       serial = sdi->conn;
+
+       /* Try to get as much data as the buffer can hold. */
+       len = sizeof(devc->buf) - devc->buf_len;
+       len = serial_read(serial, devc->buf + devc->buf_len, len);
+       if (len < 1) {
+               sr_err("Serial port read error: %d.", len);
+               return FALSE;
+       }
+       devc->buf_len += len;
+
+       /* Now look for packets in that data. */
+       ptr = devc->buf;
+       end_ptr = ptr + devc->buf_len;
+       while ((next_ptr = appa_55ii_parse_data(sdi, ptr, end_ptr - ptr)))
+               ptr = next_ptr;
 
-       if (!(devc = sdi->priv))
+       /* If we have any data left, move it to the beginning of our buffer. */
+       memmove(devc->buf, ptr, end_ptr - ptr);
+       devc->buf_len -= ptr - devc->buf;
+
+       /* If buffer is full and no valid packet was found, wipe buffer. */
+       if (devc->buf_len >= sizeof(devc->buf)) {
+               devc->buf_len = 0;
+               return FALSE;
+       }
+
+       if (devc->limit_samples && devc->num_samples >= devc->limit_samples) {
+               sr_info("Requested number of samples reached.");
+               sdi->driver->dev_acquisition_stop(sdi, devc->session_cb_data);
                return TRUE;
+       }
 
-       if (revents == G_IO_IN) {
-               /* TODO */
+       if (devc->limit_msec) {
+               int64_t time = (g_get_monotonic_time() - devc->start_time) / 1000;
+               if (time > (int64_t)devc->limit_msec) {
+                       sr_info("Requested time limit reached.");
+                       sdi->driver->dev_acquisition_stop(sdi, devc->session_cb_data);
+                       return TRUE;
+               }
        }
 
        return TRUE;
index 7288d1d4f051bfb4e2055e1f1540f8c5efbef100..d7c83858b1a73b91e88f4f5257c972fceac15318 100644 (file)
 #define sr_warn(s, args...) sr_warn(LOG_PREFIX s, ## args)
 #define sr_err(s, args...) sr_err(LOG_PREFIX s, ## args)
 
+#define APPA_55II_NUM_PROBES  2
+#define APPA_55II_BUF_SIZE    (4 + 32 + 1)
+#define DEFAULT_DATA_SOURCE   DATA_SOURCE_LIVE
+
+enum {
+       DATA_SOURCE_LIVE,
+       DATA_SOURCE_MEMORY,
+};
+
 /** Private, per-device-instance driver context. */
 struct dev_context {
-       /* Model-specific information */
-
        /* Acquisition settings */
+       uint64_t limit_samples;   /**< The sampling limit (in number of samples). */
+       uint64_t limit_msec;      /**< The time limit (in milliseconds). */
+       gboolean data_source;     /**< Whether to read live samples or memory */
+       void *session_cb_data;    /**< Opaque pointer passed in by the frontend. */
 
        /* Operational state */
+       uint64_t num_samples;     /**< The number of already received samples. */
+       int64_t start_time;       /**< The time at which sampling started. */
 
        /* Temporary state across callbacks */
-
+       uint8_t buf[APPA_55II_BUF_SIZE];
+       unsigned int buf_len;
+       uint8_t log_buf[64];
+       unsigned int log_buf_len;
+       unsigned int num_log_records;
 };
 
+SR_PRIV gboolean appa_55ii_packet_valid(const uint8_t *buf);
 SR_PRIV int appa_55ii_receive_data(int fd, int revents, void *cb_data);
 
 #endif