From: Uwe Hermann Date: Wed, 30 Jan 2013 17:21:07 +0000 (+0100) Subject: Initial driver implementation for MIC 98583. X-Git-Tag: dsupstream~286 X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=0cd8e23140612703406a57316bb0a507fb8f1994;p=libsigrok.git Initial driver implementation for MIC 98583. This is a USB/serial (Prolific) based temperature and humidity logger. --- diff --git a/hardware/mic-985xx/api.c b/hardware/mic-985xx/api.c index f2fe5a91..b8983a6d 100644 --- a/hardware/mic-985xx/api.c +++ b/hardware/mic-985xx/api.c @@ -20,16 +20,39 @@ #include "protocol.h" -SR_PRIV struct sr_dev_driver mic_985xx_driver_info; -static struct sr_dev_driver *di = &mic_985xx_driver_info; +static const int hwopts[] = { + SR_CONF_CONN, + SR_CONF_SERIALCOMM, + 0, +}; + +static const int hwcaps[] = { + SR_CONF_THERMOMETER, + SR_CONF_HYGROMETER, + SR_CONF_LIMIT_SAMPLES, + SR_CONF_LIMIT_MSEC, + SR_CONF_CONTINUOUS, + 0, +}; -/* Properly close and free all devices. */ -static int clear_instances(void) +SR_PRIV struct sr_dev_driver mic_98583_driver_info; + +SR_PRIV const struct mic_dev_info mic_devs[] = { + { + "MIC", "98583", "38400/8n2", 32000, TRUE, TRUE, + &mic_98583_driver_info, receive_data_MIC_98583, + }, +}; + +static int clear_instances(int idx) { struct sr_dev_inst *sdi; struct drv_context *drvc; struct dev_context *devc; GSList *l; + struct sr_dev_driver *di; + + di = mic_devs[idx].di; if (!(drvc = di->priv)) return SR_OK; @@ -39,9 +62,7 @@ static int clear_instances(void) continue; if (!(devc = sdi->priv)) continue; - - /* TODO */ - + sr_serial_dev_inst_free(devc->serial); sr_dev_inst_free(sdi); } @@ -51,108 +72,184 @@ static int clear_instances(void) return SR_OK; } -static int hw_init(struct sr_context *sr_ctx) +static int hw_init(struct sr_context *sr_ctx, int idx) { - return std_hw_init(sr_ctx, di, DRIVER_LOG_DOMAIN); + sr_dbg("Selected '%s' subdriver.", mic_devs[idx].di->name); + + return std_hw_init(sr_ctx, mic_devs[idx].di, DRIVER_LOG_DOMAIN); } -static GSList *hw_scan(GSList *options) +static GSList *scan(const char *conn, const char *serialcomm, int idx) { + struct sr_dev_inst *sdi; struct drv_context *drvc; + struct dev_context *devc; + struct sr_probe *probe; + struct sr_serial_dev_inst *serial; GSList *devices; - (void)options; + if (!(serial = sr_serial_dev_inst_new(conn, serialcomm))) + return NULL; + if (serial_open(serial, SERIAL_RDWR | SERIAL_NONBLOCK) != SR_OK) + return NULL; + + drvc = mic_devs[idx].di->priv; devices = NULL; - drvc = di->priv; - drvc->instances = NULL; + serial_flush(serial); - /* TODO */ + /* TODO: Query device type. */ + // ret = mic_cmd_get_device_info(serial); + + sr_info("Found device on port %s.", conn); + + /* TODO: Fill in version from protocol response. */ + if (!(sdi = sr_dev_inst_new(0, SR_ST_INACTIVE, mic_devs[idx].vendor, + mic_devs[idx].device, ""))) + goto scan_cleanup; + + if (!(devc = g_try_malloc0(sizeof(struct dev_context)))) { + sr_err("Device context malloc failed."); + goto scan_cleanup; + } + + devc->serial = serial; + + sdi->priv = devc; + sdi->driver = mic_devs[idx].di; + + if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "Temperature"))) + goto scan_cleanup; + sdi->probes = g_slist_append(sdi->probes, probe); + + if (!(probe = sr_probe_new(1, SR_PROBE_ANALOG, TRUE, "Humidity"))) + 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; } -static GSList *hw_dev_list(void) +static GSList *hw_scan(GSList *options, int idx) +{ + struct sr_config *src; + GSList *l, *devices; + const char *conn, *serialcomm; + + conn = serialcomm = NULL; + for (l = options; l; l = l->next) { + src = l->data; + switch (src->key) { + case SR_CONF_CONN: + conn = src->value; + break; + case SR_CONF_SERIALCOMM: + serialcomm = src->value; + break; + } + } + if (!conn) + return NULL; + + if (serialcomm) { + /* Use the provided comm specs. */ + devices = scan(conn, serialcomm, idx); + } else { + /* Try the default. */ + devices = scan(conn, mic_devs[idx].conn, idx); + } + + return devices; +} + +static GSList *hw_dev_list(int idx) { struct drv_context *drvc; - drvc = di->priv; + drvc = mic_devs[idx].di->priv; return drvc->instances; } static int hw_dev_open(struct sr_dev_inst *sdi) { - (void)sdi; + struct dev_context *devc; - /* TODO */ + devc = sdi->priv; - return SR_OK; -} - -static int hw_dev_close(struct sr_dev_inst *sdi) -{ - (void)sdi; + if (serial_open(devc->serial, SERIAL_RDWR | SERIAL_NONBLOCK) != SR_OK) + return SR_ERR; - /* TODO */ + sdi->status = SR_ST_ACTIVE; return SR_OK; } -static int hw_cleanup(void) +static int hw_dev_close(struct sr_dev_inst *sdi) { - clear_instances(); + struct dev_context *devc; - /* TODO */ + devc = sdi->priv; + + if (devc->serial && devc->serial->fd != -1) { + serial_close(devc->serial); + sdi->status = SR_ST_INACTIVE; + } return SR_OK; } -static int hw_config_get(int id, const void **value, - const struct sr_dev_inst *sdi) +static int hw_cleanup(int idx) { - (void)sdi; - (void)value; - - switch (id) { - /* TODO */ - default: - return SR_ERR_ARG; - } + clear_instances(idx); return SR_OK; } -static int hw_config_set(int id, const void *value, - const struct sr_dev_inst *sdi) +static int config_set(int id, const void *value, const struct sr_dev_inst *sdi) { - (void)value; - - int ret; + struct dev_context *devc; - if (sdi->status != SR_ST_ACTIVE) { - sr_err("Device inactive, can't set config options."); + if (sdi->status != SR_ST_ACTIVE) return SR_ERR; - } - ret = SR_OK; + devc = sdi->priv; + switch (id) { - /* TODO */ + case SR_CONF_LIMIT_SAMPLES: + devc->limit_samples = *(const uint64_t *)value; + sr_dbg("Setting sample limit to %" PRIu64 ".", + devc->limit_samples); + break; + case SR_CONF_LIMIT_MSEC: + devc->limit_msec = *(const uint64_t *)value; + sr_dbg("Setting time limit to %" PRIu64 "ms.", + devc->limit_msec); + break; default: - sr_err("Unknown hardware capability: %d.", id); - ret = SR_ERR_ARG; + sr_err("Unknown config: %d.", id); + return SR_ERR_ARG; } - return ret; + return SR_OK; } -static int hw_config_list(int key, const void **data, - const struct sr_dev_inst *sdi) +static int config_list(int key, const void **data, const struct sr_dev_inst *sdi) { (void)sdi; - (void)data; switch (key) { + case SR_CONF_SCAN_OPTIONS: + *data = hwopts; + break; + case SR_CONF_DEVICE_OPTIONS: + *data = hwcaps; + break; default: return SR_ERR_ARG; } @@ -161,45 +258,101 @@ static int hw_config_list(int key, const void **data, } static int hw_dev_acquisition_start(const struct sr_dev_inst *sdi, - void *cb_data) + void *cb_data, int idx) { - (void)sdi; - (void)cb_data; + struct sr_datafeed_packet packet; + struct sr_datafeed_header header; + struct dev_context *devc; + + devc = sdi->priv; + + devc->cb_data = cb_data; - /* TODO */ + sr_dbg("Starting acquisition."); + + devc->num_samples = 0; + devc->starttime = g_get_monotonic_time(); + + /* Send header packet to the session bus. */ + sr_dbg("Sending SR_DF_HEADER."); + packet.type = SR_DF_HEADER; + packet.payload = (uint8_t *)&header; + header.feed_version = 1; + gettimeofday(&header.starttime, NULL); + sr_session_send(devc->cb_data, &packet); + + /* Poll every 100ms, or whenever some data comes in. */ + sr_source_add(devc->serial->fd, G_IO_IN, 100, + mic_devs[idx].receive_data, (void *)sdi); return SR_OK; } static int hw_dev_acquisition_stop(struct sr_dev_inst *sdi, void *cb_data) { - (void)cb_data; + struct sr_datafeed_packet packet; + struct dev_context *devc; if (sdi->status != SR_ST_ACTIVE) { sr_err("Device inactive, can't stop acquisition."); return SR_ERR; } - /* TODO */ + devc = sdi->priv; + + sr_dbg("Stopping acquisition."); + + sr_source_remove(devc->serial->fd); + hw_dev_close((struct sr_dev_inst *)sdi); + + /* Send end packet to the session bus. */ + sr_dbg("Sending SR_DF_END."); + packet.type = SR_DF_END; + sr_session_send(cb_data, &packet); return SR_OK; } -SR_PRIV struct sr_dev_driver mic_985xx_driver_info = { - .name = "mic-985xx", - .longname = "MIC 985xx", - .api_version = 1, - .init = hw_init, - .cleanup = hw_cleanup, - .scan = hw_scan, - .dev_list = hw_dev_list, - .dev_clear = clear_instances, - .config_get = hw_config_get, - .config_set = hw_config_set, - .config_list = hw_config_list, - .dev_open = hw_dev_open, - .dev_close = hw_dev_close, - .dev_acquisition_start = hw_dev_acquisition_start, - .dev_acquisition_stop = hw_dev_acquisition_stop, - .priv = NULL, +/* Driver-specific API function wrappers */ +#define HW_INIT(X) \ +static int hw_init_##X(struct sr_context *sr_ctx) { return hw_init(sr_ctx, X); } +#define HW_CLEANUP(X) \ +static int hw_cleanup_##X(void) { return hw_cleanup(X); } +#define HW_SCAN(X) \ +static GSList *hw_scan_##X(GSList *options) { return hw_scan(options, X); } +#define HW_DEV_LIST(X) \ +static GSList *hw_dev_list_##X(void) { return hw_dev_list(X); } +#define CLEAR_INSTANCES(X) \ +static int clear_instances_##X(void) { return clear_instances(X); } +#define HW_DEV_ACQUISITION_START(X) \ +static int hw_dev_acquisition_start_##X(const struct sr_dev_inst *sdi, \ +void *cb_data) { return hw_dev_acquisition_start(sdi, cb_data, X); } + +/* Driver structs and API function wrappers */ +#define DRV(ID, ID_UPPER, NAME, LONGNAME) \ +HW_INIT(ID_UPPER) \ +HW_CLEANUP(ID_UPPER) \ +HW_SCAN(ID_UPPER) \ +HW_DEV_LIST(ID_UPPER) \ +CLEAR_INSTANCES(ID_UPPER) \ +HW_DEV_ACQUISITION_START(ID_UPPER) \ +SR_PRIV struct sr_dev_driver ID##_driver_info = { \ + .name = NAME, \ + .longname = LONGNAME, \ + .api_version = 1, \ + .init = hw_init_##ID_UPPER, \ + .cleanup = hw_cleanup_##ID_UPPER, \ + .scan = hw_scan_##ID_UPPER, \ + .dev_list = hw_dev_list_##ID_UPPER, \ + .dev_clear = clear_instances_##ID_UPPER, \ + .config_get = NULL, \ + .config_set = config_set, \ + .config_list = config_list, \ + .dev_open = hw_dev_open, \ + .dev_close = hw_dev_close, \ + .dev_acquisition_start = hw_dev_acquisition_start_##ID_UPPER, \ + .dev_acquisition_stop = hw_dev_acquisition_stop, \ + .priv = NULL, \ }; + +DRV(mic_98583, MIC_98583, "mic-98583", "MIC 98583") diff --git a/hardware/mic-985xx/protocol.c b/hardware/mic-985xx/protocol.c index 93269207..ed1aae7a 100644 --- a/hardware/mic-985xx/protocol.c +++ b/hardware/mic-985xx/protocol.c @@ -20,12 +20,153 @@ #include "protocol.h" -SR_PRIV int mic_985xx_receive_data(int fd, int revents, void *cb_data) +#define PACKET_SIZE 10 + +static int mic_send(struct sr_serial_dev_inst *serial, const char *cmd) { - (void)fd; + int ret; + + if ((ret = serial_write(serial, cmd, strlen(cmd))) < 0) { + sr_err("Error sending '%s' command: %d.", cmd, ret); + return SR_ERR; + } + + return SR_OK; +} + +SR_PRIV int mic_cmd_get_device_info(struct sr_serial_dev_inst *serial) +{ + return mic_send(serial, "I\r"); +} + +static int mic_cmd_set_realtime_mode(struct sr_serial_dev_inst *serial) +{ + return mic_send(serial, "S 1 M 2 32 3\r"); +} + +static gboolean packet_valid(const uint8_t *buf) +{ + if (buf[0] != 'v' || buf[1] != ' ' || buf[5] != ' ' || buf[9] != '\r') + return FALSE; + + if (!isdigit(buf[2]) || !isdigit(buf[3]) || !isdigit(buf[4])) + return FALSE; + + if (!isdigit(buf[6]) || !isdigit(buf[7]) || !isdigit(buf[8])) + return FALSE; + + return TRUE; +} + +static int packet_parse(const char *buf, float *temp, float *humidity) +{ + char tmp[4]; + + /* Packet format: "v ttt hhh\r". */ + + /* TODO: Sanity check on buf. For now we assume well-formed ASCII. */ + + tmp[3] = '\0'; + + strncpy((char *)&tmp, &buf[2], 3); + *temp = g_ascii_strtoull((const char *)&tmp, NULL, 10) / 10; + + strncpy((char *)&tmp, &buf[6], 3); + *humidity = g_ascii_strtoull((const char *)&tmp, NULL, 10) / 10; + + return SR_OK; +} + +static int handle_packet(const uint8_t *buf, struct sr_dev_inst *sdi, int idx) +{ + float temperature, humidity; + struct sr_datafeed_packet packet; + struct sr_datafeed_analog analog; + struct dev_context *devc; + GSList *l; + int ret; - const struct sr_dev_inst *sdi; + (void)idx; + + devc = sdi->priv; + + ret = packet_parse((const char *)buf, &temperature, &humidity); + if (ret < 0) { + sr_err("Failed to parse packet."); + return SR_ERR; + } + + /* Common values for both probes. */ + packet.type = SR_DF_ANALOG; + packet.payload = &analog; + analog.num_samples = 1; + + /* Temperature. */ + l = g_slist_copy(sdi->probes); + l = g_slist_remove_link(l, g_slist_nth(l, 1)); + analog.probes = l; + analog.mq = SR_MQ_TEMPERATURE; + analog.unit = SR_UNIT_CELSIUS; /* TODO: Use C/F correctly. */ + analog.data = &temperature; + sr_session_send(devc->cb_data, &packet); + g_slist_free(l); + + /* Humidity. */ + l = g_slist_copy(sdi->probes); + l = g_slist_remove_link(l, g_slist_nth(l, 0)); + analog.probes = l; + analog.mq = SR_MQ_RELATIVE_HUMIDITY; + analog.unit = SR_UNIT_PERCENTAGE; + analog.data = &humidity; + sr_session_send(devc->cb_data, &packet); + g_slist_free(l); + + devc->num_samples++; + + return SR_OK; +} + +static void handle_new_data(struct sr_dev_inst *sdi, int idx) +{ struct dev_context *devc; + int len, i, offset = 0; + + devc = sdi->priv; + + /* Try to get as much data as the buffer can hold. */ + len = SERIAL_BUFSIZE - devc->buflen; + len = serial_read(devc->serial, devc->buf + devc->buflen, len); + if (len < 1) { + sr_err("Serial port read error: %d.", len); + return; + } + + devc->buflen += len; + + /* Now look for packets in that data. */ + while ((devc->buflen - offset) >= PACKET_SIZE) { + if (packet_valid(devc->buf + offset)) { + handle_packet(devc->buf + offset, sdi, idx); + offset += PACKET_SIZE; + } else { + offset++; + } + } + + /* If we have any data left, move it to the beginning of our buffer. */ + for (i = 0; i < devc->buflen - offset; i++) + devc->buf[i] = devc->buf[offset + i]; + devc->buflen -= offset; +} + +static int receive_data(int fd, int revents, int idx, void *cb_data) +{ + struct sr_dev_inst *sdi; + struct dev_context *devc; + int64_t t; + static gboolean first_time = TRUE; + + (void)fd; if (!(sdi = cb_data)) return TRUE; @@ -34,8 +175,37 @@ SR_PRIV int mic_985xx_receive_data(int fd, int revents, void *cb_data) return TRUE; if (revents == G_IO_IN) { - /* TODO */ + /* New data arrived. */ + handle_new_data(sdi, idx); + } else { + /* Timeout. */ + if (first_time) { + mic_cmd_set_realtime_mode(devc->serial); + first_time = FALSE; + } + } + + if (devc->limit_samples && devc->num_samples >= devc->limit_samples) { + sr_info("Requested number of samples reached."); + sdi->driver->dev_acquisition_stop(sdi, cb_data); + return TRUE; + } + + if (devc->limit_msec) { + t = (g_get_monotonic_time() - devc->starttime) / 1000; + if (t > (int64_t)devc->limit_msec) { + sr_info("Requested time limit reached."); + sdi->driver->dev_acquisition_stop(sdi, cb_data); + return TRUE; + } } return TRUE; } + +#define RECEIVE_DATA(ID_UPPER) \ +SR_PRIV int receive_data_##ID_UPPER(int fd, int revents, void *cb_data) { \ + return receive_data(fd, revents, ID_UPPER, cb_data); } + +/* Driver-specific receive_data() wrappers */ +RECEIVE_DATA(MIC_98583) diff --git a/hardware/mic-985xx/protocol.h b/hardware/mic-985xx/protocol.h index 0b1efdd8..e854b1d4 100644 --- a/hardware/mic-985xx/protocol.h +++ b/hardware/mic-985xx/protocol.h @@ -22,6 +22,8 @@ #define LIBSIGROK_HARDWARE_MIC_985XX_PROTOCOL_H #include +#include +#include #include #include "libsigrok.h" #include "libsigrok-internal.h" @@ -35,6 +37,28 @@ #define sr_warn(s, args...) sr_warn(DRIVER_LOG_DOMAIN s, ## args) #define sr_err(s, args...) sr_err(DRIVER_LOG_DOMAIN s, ## args) +/* Note: When adding entries here, don't forget to update MIC_DEV_COUNT. */ +enum { + MIC_98583, +}; + +#define MIC_DEV_COUNT 1 + +struct mic_dev_info { + char *vendor; + char *device; + char *conn; + uint32_t max_sample_points; + gboolean has_temperature; + gboolean has_humidity; + struct sr_dev_driver *di; + int (*receive_data)(int, int, void *); +}; + +extern SR_PRIV const struct mic_dev_info mic_devs[MIC_DEV_COUNT]; + +#define SERIAL_BUFSIZE 256 + /** Private, per-device-instance driver context. */ struct dev_context { /** The current sampling limit (in number of samples). */ @@ -48,8 +72,18 @@ struct dev_context { /** The current number of already received samples. */ uint64_t num_samples; + + int64_t starttime; + + struct sr_serial_dev_inst *serial; + + uint8_t buf[SERIAL_BUFSIZE]; + int bufoffset; + int buflen; }; -SR_PRIV int mic_985xx_receive_data(int fd, int revents, void *cb_data); +SR_PRIV int receive_data_MIC_98583(int fd, int revents, void *cb_data); + +SR_PRIV int mic_cmd_get_device_info(struct sr_serial_dev_inst *serial); #endif diff --git a/hwdriver.c b/hwdriver.c index aec83b52..840a5fb6 100644 --- a/hwdriver.c +++ b/hwdriver.c @@ -93,7 +93,7 @@ extern SR_PRIV struct sr_dev_driver demo_driver_info; extern SR_PRIV struct sr_dev_driver lascar_el_usb_driver_info; #endif #ifdef HAVE_HW_MIC_985XX -extern SR_PRIV struct sr_dev_driver mic_985xx_driver_info; +extern SR_PRIV struct sr_dev_driver mic_98583_driver_info; #endif #ifdef HAVE_HW_NEXUS_OSCIPRIME extern SR_PRIV struct sr_dev_driver nexus_osciprime_driver_info; @@ -170,7 +170,7 @@ static struct sr_dev_driver *drivers_list[] = { &lascar_el_usb_driver_info, #endif #ifdef HAVE_HW_MIC_985XX - &mic_985xx_driver_info, + &mic_98583_driver_info, #endif #ifdef HAVE_HW_NEXUS_OSCIPRIME &nexus_osciprime_driver_info,