]> sigrok.org Git - libsigrok.git/commitdiff
teleinfo: actual parser implementation
authorAurelien Jacobs <redacted>
Thu, 3 Oct 2013 19:20:18 +0000 (21:20 +0200)
committerBert Vermeulen <redacted>
Thu, 24 Oct 2013 14:41:11 +0000 (15:41 +0100)
hardware/teleinfo/api.c
hardware/teleinfo/protocol.c
hardware/teleinfo/protocol.h

index 4d8cecf0c20d3f00d085a918ae40b1b2e4349ba8..604dec7c7b2a53bb0e0bac386696b94e61506137 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <stdlib.h>
+#include <glib.h>
+#include "libsigrok.h"
+#include "libsigrok-internal.h"
 #include "protocol.h"
 
+static const int32_t hwopts[] = {
+       SR_CONF_CONN,
+       SR_CONF_SERIALCOMM,
+};
+
+static const int32_t hwcaps[] = {
+       SR_CONF_ENERGYMETER,
+       SR_CONF_LIMIT_SAMPLES,
+       SR_CONF_LIMIT_MSEC,
+       SR_CONF_CONTINUOUS,
+};
+
 SR_PRIV struct sr_dev_driver teleinfo_driver_info;
 static struct sr_dev_driver *di = &teleinfo_driver_info;
 
@@ -30,16 +46,120 @@ 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[292];
+       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 = "1200/7e1";
+
+       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, len,
+                                teleinfo_packet_valid, 3000, 1200) != SR_OK)
+               goto scan_cleanup;
+
+       sr_info("Found device on port %s.", conn);
+
+       if (!(sdi = sr_dev_inst_new(0, SR_ST_INACTIVE, "EDF", "Teleinfo", "")))
+               goto scan_cleanup;
+
+       if (!(devc = g_try_malloc0(sizeof(struct dev_context)))) {
+               sr_err("Device context malloc failed.");
+               goto scan_cleanup;
+       }
+
+       devc->optarif = teleinfo_get_optarif(buf);
+
+       sdi->inst_type = SR_INST_SERIAL;
+       sdi->conn = serial;
+       sdi->priv = devc;
+       sdi->driver = di;
+
+       if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "P")))
+               goto scan_cleanup;
+       sdi->probes = g_slist_append(sdi->probes, probe);
+
+       if (devc->optarif == OPTARIF_BASE) {
+               if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "BASE")))
+                       goto scan_cleanup;
+               sdi->probes = g_slist_append(sdi->probes, probe);
+       } else if (devc->optarif == OPTARIF_HC) {
+               if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "HP")))
+                       goto scan_cleanup;
+               sdi->probes = g_slist_append(sdi->probes, probe);
+               if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "HC")))
+                       goto scan_cleanup;
+               sdi->probes = g_slist_append(sdi->probes, probe);
+       } else if (devc->optarif == OPTARIF_EJP) {
+               if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "HN")))
+                       goto scan_cleanup;
+               sdi->probes = g_slist_append(sdi->probes, probe);
+               if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "HPM")))
+                       goto scan_cleanup;
+               sdi->probes = g_slist_append(sdi->probes, probe);
+       } else if (devc->optarif == OPTARIF_BBR) {
+               if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "HPJB")))
+                       goto scan_cleanup;
+               sdi->probes = g_slist_append(sdi->probes, probe);
+               if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "HPJW")))
+                       goto scan_cleanup;
+               sdi->probes = g_slist_append(sdi->probes, probe);
+               if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "HPJR")))
+                       goto scan_cleanup;
+               sdi->probes = g_slist_append(sdi->probes, probe);
+               if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "HCJB")))
+                       goto scan_cleanup;
+               sdi->probes = g_slist_append(sdi->probes, probe);
+               if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "HCJW")))
+                       goto scan_cleanup;
+               sdi->probes = g_slist_append(sdi->probes, probe);
+               if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "HCJR")))
+                       goto scan_cleanup;
+               sdi->probes = g_slist_append(sdi->probes, probe);
+       }
+
+       if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "IINST")))
+               goto scan_cleanup;
+       sdi->probes = g_slist_append(sdi->probes, probe);
 
-       /* 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, "PAPP")))
+               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;
 }
@@ -56,9 +176,10 @@ static int dev_clear(void)
 
 static int dev_open(struct sr_dev_inst *sdi)
 {
-       (void)sdi;
+       struct sr_serial_dev_inst *serial = sdi->conn;
 
-       /* TODO: get handle from sdi->conn and open it. */
+       if (serial_open(serial, SERIAL_RDONLY | SERIAL_NONBLOCK) != SR_OK)
+               return SR_ERR;
 
        sdi->status = SR_ST_ACTIVE;
 
@@ -67,104 +188,107 @@ static int dev_open(struct sr_dev_inst *sdi)
 
 static int dev_close(struct sr_dev_inst *sdi)
 {
-       (void)sdi;
+       struct sr_serial_dev_inst *serial = sdi->conn;
 
-       /* TODO: get handle from sdi->conn and close it. */
-
-       sdi->status = SR_ST_INACTIVE;
+       if (serial && serial->fd != -1) {
+               serial_close(serial);
+               sdi->status = SR_ST_INACTIVE;
+       }
 
        return SR_OK;
 }
 
 static int cleanup(void)
 {
-       dev_clear();
-
-       /* TODO: free other driver resources, if any. */
-
-       return SR_OK;
-}
-
-static int config_get(int key, GVariant **data, const struct sr_dev_inst *sdi)
-{
-       int ret;
-
-       (void)sdi;
-       (void)data;
-
-       ret = SR_OK;
-       switch (key) {
-       /* TODO */
-       default:
-               return SR_ERR_NA;
-       }
-
-       return ret;
+       return dev_clear();
 }
 
 static int config_set(int key, GVariant *data, const struct sr_dev_inst *sdi)
 {
-       int ret;
-
-       (void)data;
+       struct dev_context *devc;
 
        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;
        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)
 {
-       int ret;
-
        (void)sdi;
-       (void)data;
 
-       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;
        default:
                return SR_ERR_NA;
        }
 
-       return ret;
+       return SR_OK;
 }
 
-static int dev_acquisition_start(const struct sr_dev_inst *sdi,
-                                   void *cb_data)
+static int dev_acquisition_start(const struct sr_dev_inst *sdi, 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. */
+       sr_source_add(serial->fd, G_IO_IN, 50, teleinfo_receive_data, (void *)sdi);
 
        return SR_OK;
 }
 
+static int dev_acquisition_stop(struct sr_dev_inst *sdi, void *cb_data)
+{
+       return std_dev_acquisition_stop_serial(sdi, cb_data, dev_close,
+                                              sdi->conn, LOG_PREFIX);
+}
+
 SR_PRIV struct sr_dev_driver teleinfo_driver_info = {
        .name = "teleinfo",
        .longname = "Teleinfo",
@@ -174,12 +298,10 @@ SR_PRIV struct sr_dev_driver teleinfo_driver_info = {
        .scan = scan,
        .dev_list = dev_list,
        .dev_clear = dev_clear,
-       .config_get = config_get,
        .config_set = config_set,
        .config_list = config_list,
        .dev_open = dev_open,
        .dev_close = dev_close,
        .dev_acquisition_start = dev_acquisition_start,
        .dev_acquisition_stop = dev_acquisition_stop,
-       .priv = NULL,
 };
index a7fde947a432bbfe343f364a91b78a36ea6c3665..28dfcfa1fbd3b950ebb22a6ce9945dbfce3ea9b8 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
 #include "protocol.h"
 
+#define STX  0x02
+#define ETX  0x03
+#define EOT  0x04
+#define LF   0x0A
+#define CR   0x0D
+
+static gboolean teleinfo_control_check(char *label, char *data, char control)
+{
+       int sum = 0;
+       while (*label)
+               sum += *label++;
+       sum += ' ';
+       while (*data)
+               sum += *data++;
+       return ((sum & 0x3F) + ' ') == control;
+}
+
+static gint teleinfo_probe_compare(gconstpointer a, gconstpointer b)
+{
+       const struct sr_probe *probe = a;
+       const char *name = b;
+       return strcmp(probe->name, name);
+}
+
+static struct sr_probe *teleinfo_find_probe(struct sr_dev_inst *sdi,
+                                            const char *name)
+{
+       GSList *elem = g_slist_find_custom(sdi->probes, name,
+                                          teleinfo_probe_compare);
+       return elem ? elem->data : NULL;
+}
+
+static void teleinfo_send_value(struct sr_dev_inst *sdi, const char *probe_name,
+                                float value, int mq, int unit)
+{
+       struct dev_context *devc = sdi->priv;
+       struct sr_datafeed_packet packet;
+       struct sr_datafeed_analog analog = { 0 };
+       struct sr_probe *probe = teleinfo_find_probe(sdi, probe_name);
+
+       if (!probe || !probe->enabled)
+               return;
+
+       analog.probes = g_slist_append(analog.probes, probe);
+       analog.num_samples = 1;
+       analog.mq = mq;
+       analog.unit = unit;
+       analog.data = &value;
+
+       packet.type = SR_DF_ANALOG;
+       packet.payload = &analog;
+       sr_session_send(devc->session_cb_data, &packet);
+       g_slist_free(analog.probes);
+}
+
+static void teleinfo_handle_mesurement(struct sr_dev_inst *sdi,
+                                       const char *label, const char *data,
+                                       char *optarif)
+{
+       struct dev_context *devc;
+       int v = atoi(data);
+
+       if (!sdi || !(devc = sdi->priv)) {
+               if (optarif && !strcmp(label, "OPTARIF"))
+                       strcpy(optarif, data);
+               return;
+       }
+
+       if (!strcmp(label, "ADCO")) {
+               devc->num_samples++;
+       } else if (!strcmp(label, "BASE")) {
+               teleinfo_send_value(sdi, "BASE", v, SR_MQ_POWER, SR_UNIT_WATT_HOUR);
+       } else if (!strcmp(label, "HCHP")) {
+               teleinfo_send_value(sdi, "HP"  , v, SR_MQ_POWER, SR_UNIT_WATT_HOUR);
+       } else if (!strcmp(label, "HCHC")) {
+               teleinfo_send_value(sdi, "HC"  , v, SR_MQ_POWER, SR_UNIT_WATT_HOUR);
+       } else if (!strcmp(label, "EJPHN")) {
+               teleinfo_send_value(sdi, "HN"  , v, SR_MQ_POWER, SR_UNIT_WATT_HOUR);
+       } else if (!strcmp(label, "EJPHPM")) {
+               teleinfo_send_value(sdi, "HPM" , v, SR_MQ_POWER, SR_UNIT_WATT_HOUR);
+       } else if (!strcmp(label, "BBRHPJB")) {
+               teleinfo_send_value(sdi, "HPJB", v, SR_MQ_POWER, SR_UNIT_WATT_HOUR);
+       } else if (!strcmp(label, "BBRHPJW")) {
+               teleinfo_send_value(sdi, "HPJW", v, SR_MQ_POWER, SR_UNIT_WATT_HOUR);
+       } else if (!strcmp(label, "BBRHPJR")) {
+               teleinfo_send_value(sdi, "HPJR", v, SR_MQ_POWER, SR_UNIT_WATT_HOUR);
+       } else if (!strcmp(label, "BBRHCJB")) {
+               teleinfo_send_value(sdi, "HCJB", v, SR_MQ_POWER, SR_UNIT_WATT_HOUR);
+       } else if (!strcmp(label, "BBRHCJW")) {
+               teleinfo_send_value(sdi, "HCJW", v, SR_MQ_POWER, SR_UNIT_WATT_HOUR);
+       } else if (!strcmp(label, "BBRHCJR")) {
+               teleinfo_send_value(sdi, "HCJR", v, SR_MQ_POWER, SR_UNIT_WATT_HOUR);
+       } else if (!strcmp(label, "IINST")) {
+               teleinfo_send_value(sdi, "IINST", v, SR_MQ_CURRENT, SR_UNIT_AMPERE);
+       } else if (!strcmp(label, "PAPP")) {
+               teleinfo_send_value(sdi, "PAPP", v, SR_MQ_POWER, SR_UNIT_VOLT_AMPERE);
+       }
+}
+
+static gboolean teleinfo_parse_group(struct sr_dev_inst *sdi,
+                                     const uint8_t *group, char *optarif)
+{
+       char label[9], data[13], control, cr;
+       const char *str = (const char *) group;
+       if (sscanf(str, "\x0A%8s %13s %c%c", label, data, &control, &cr) != 4
+           || cr != CR)
+               return FALSE;
+       if (!teleinfo_control_check(label, data, control))
+               return FALSE;
+       teleinfo_handle_mesurement(sdi, label, data, optarif);
+       return TRUE;
+}
+
+static const uint8_t *teleinfo_parse_data(struct sr_dev_inst *sdi,
+                                          const uint8_t *buf, int len,
+                                          char *optarif)
+{
+       const uint8_t *group_start = memchr(buf, LF, len);
+       if (!group_start)
+               return NULL;
+
+       const uint8_t *group_end = memchr(group_start, CR,
+                                         len - (group_start - buf));
+       if (!group_end)
+               return NULL;
+
+       teleinfo_parse_group(sdi, group_start, optarif);
+       return group_end + 1;
+}
+
+SR_PRIV int teleinfo_get_optarif(const uint8_t *buf)
+{
+       const uint8_t *ptr = buf;
+       char optarif[5] = { 0 };
+
+       while ((ptr = teleinfo_parse_data(NULL, ptr, 292-(ptr-buf), optarif)));
+       if (!strcmp(optarif, "BASE"))
+               return OPTARIF_BASE;
+       else if (!strcmp(optarif, "HC.."))
+               return OPTARIF_HC;
+       else if (!strcmp(optarif, "EJP."))
+               return OPTARIF_EJP;
+       else if (!strncmp(optarif, "BBR", 3))
+               return OPTARIF_BBR;
+       return OPTARIF_NONE;
+}
+
+SR_PRIV gboolean teleinfo_packet_valid(const uint8_t *buf)
+{
+       return !!teleinfo_get_optarif(buf);
+}
+
 SR_PRIV int teleinfo_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;
 
-       if (!(devc = sdi->priv))
-               return TRUE;
+       /* Try to get as much data as the buffer can hold. */
+       len = TELEINFO_BUF_SIZE - 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;
 
-       if (revents == G_IO_IN) {
-               /* TODO */
+       /* Now look for packets in that data. */
+       ptr = devc->buf;
+       end_ptr = ptr + devc->buf_len;
+       while ((next_ptr = teleinfo_parse_data(sdi, ptr, end_ptr - ptr, NULL)))
+               ptr = next_ptr;
+
+       /* 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 >= TELEINFO_BUF_SIZE) {
+               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 (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 3485d1068967800892b717cb17451e8a2f25c900..46d2efb31ae04f53babdb9882fecdb15e0156889 100644 (file)
 #define sr_warn(s, args...) sr_warn(LOG_PREFIX s, ## args)
 #define sr_err(s, args...) sr_err(LOG_PREFIX s, ## args)
 
+enum optarif {
+       OPTARIF_NONE,
+       OPTARIF_BASE,
+       OPTARIF_HC,
+       OPTARIF_EJP,
+       OPTARIF_BBR,
+};
+
+#define TELEINFO_BUF_SIZE 256
+
 /** 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). */
+       void *session_cb_data;    /**< Opaque pointer passed in by the frontend. */
 
        /* Operational state */
+       enum optarif optarif;     /**< The device mode (which mesures are reported) */
+       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[TELEINFO_BUF_SIZE];
+       int buf_len;
 };
 
+SR_PRIV gboolean teleinfo_packet_valid(const uint8_t *buf);
 SR_PRIV int teleinfo_receive_data(int fd, int revents, void *cb_data);
+SR_PRIV int teleinfo_get_optarif(const uint8_t *buf);
 
 #endif