From 9fdbedb39ea32e4afa29ef7de376a34c136475dc Mon Sep 17 00:00:00 2001 From: Timo Kokkonen Date: Wed, 21 Oct 2020 21:07:18 +0300 Subject: [PATCH] Introduce bmed This is a simple daemon that allows the sensor values and its history to be read over a TCP socket. Signed-off-by: Timo Kokkonen --- Makefile | 27 +++ bmed.c | 543 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 570 insertions(+) create mode 100644 Makefile create mode 100644 bmed.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8198f3c --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +CFLAGS := -Wall -Wextra -g -O2 -D_GNU_SOURCE +ifeq ($(V),1) + Q = + QUIET_CC = + QUIET_LINK = +else + Q = @ + QUIET_CC = @echo " CC " $@; + QUIET_LINK = @echo " LINK " $@; +endif + +all: bmed + +bmed: bmed.o bme280.o + $(QUIET_LINK)gcc -Wall -Wextra -g -lm -lpthread $^ -o $@ + +%.o: %.c + $(QUIET_CC)$(CC) -MMD -MF .$@.d $(CFLAGS) -c $< -o $@ + $(Q)cp .$@.d .$@.P; \ + sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ + -e '/^$$/ d' -e 's/$$/ :/' < .$@.d >> .$@.P; \ + mv .$@.P .$@.d + +FORCE: + +TAGS: FORCE + etags *.[ch] diff --git a/bmed.c b/bmed.c new file mode 100644 index 0000000..c00cfbb --- /dev/null +++ b/bmed.c @@ -0,0 +1,543 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bme280.h" + +struct data_entry { + time_t time; + double temperature; + double humidity; + double pressure; + double dew_point; +}; + +struct bme280 { + struct bme280_dev *dev; + uint8_t dev_addr; + int8_t fd; + struct data_entry data[8192]; + pthread_mutex_t lock; + int epollfd; +}; + +struct event_handler; + +typedef int (handle_event_fn_t)(struct event_handler *); + +struct event_handler { + struct epoll_event ev; + handle_event_fn_t *handle_event; + char *name; +}; + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define max(a, b) ((a) > (b) ? (a) : (b)) + +int register_event_handler(struct bme280 *bme, struct event_handler *handler) +{ + struct epoll_event ev; + int ret; + + bzero(&ev, sizeof(ev)); + + if (handler->ev.data.fd <= 0) { + printf("Invalid file descriptor of %d\n", handler->ev.data.fd); + return -1; + } + + if (!handler->handle_event) { + printf("Handler callback missing\n"); + return -1; + } + + printf("Registering handler for %s, fd %d\n", + handler->name, handler->ev.data.fd); + + ev.data.fd = handler->ev.data.fd; + ev.data.ptr = handler; + ev.events = handler->ev.events; + ret = epoll_ctl(bme->epollfd, EPOLL_CTL_ADD, handler->ev.data.fd, &ev); + if (ret) { + printf("Failed to add epoll_fd: %m\n"); + return -1; + } + + return 0; +} + +int update_event_handler(struct bme280 *bme, struct event_handler *handler) +{ + struct epoll_event ev; + int ret; + + bzero(&ev, sizeof(ev)); + ev.data.fd = handler->ev.data.fd; + ev.data.ptr = handler; + ev.events = handler->ev.events; + ret = epoll_ctl(bme->epollfd, EPOLL_CTL_MOD, handler->ev.data.fd, &ev); + if (ret) { + printf("Failed to add epoll_fd: %m\n"); + return -1; + } + + return 0; +} + +struct connection_state { + struct event_handler ev; + char buf[128]; + int len; + struct bme280 *bme; + int fd; + time_t min_timestamp; +}; + +static int handle_connection_state(struct event_handler *ptr) +{ + struct connection_state *conn = (struct connection_state *)ptr; + struct bme280 *bme = conn->bme; + int len = 0; + int ret, i; + + if (conn->min_timestamp == -1) { + if (!conn->ev.ev.events & EPOLLIN) { + printf("%s: No incoming data\n", __func__); + return 0; + } + + ret = read(conn->fd, conn->buf + conn->len, sizeof(conn->buf) - conn->len); + if (ret < 0) { + printf("%s: read: %m\n", __func__); + return -1; + } + + conn->buf[min((signed)sizeof(conn->buf) - 1, ret)] = '\0'; + + if (ret == 0) + goto out_free; + + for (i = 0; i < ret; i++) + if (conn->buf[i] == '\n') + break; + + /* Did we get newline? */ + if (i == ret) + return 0; /* Not yet */ + + if (i == sizeof(conn->buf)) { + printf("%s Data overflow\n", __func__); + goto out_free; + } + + printf("%s: Got %s", __func__, conn->buf); + + conn->min_timestamp = atoi(conn->buf); + } + + /* Switch to sending data only mode only */ + conn->ev.ev.events = EPOLLOUT; + conn->ev.ev.data.fd = conn->fd; + update_event_handler(bme, &conn->ev); + + for (i = 0; i < (signed)ARRAY_SIZE(bme->data); i++) { + pthread_mutex_lock(&bme->lock); + + /* Skip empty */ + if (!bme->data[i].time) { + pthread_mutex_unlock(&bme->lock); + continue; + } + + /* Skip non-interesting or already sent items */ + if (bme->data[i].time <= conn->min_timestamp) { + pthread_mutex_unlock(&bme->lock); + continue; + } + + len = snprintf(conn->buf, sizeof(conn->buf), + "%ld:%0.4lf:%0.4lf:%0.4lf:%.4lf\n", + bme->data[i].time, + bme->data[i].temperature, + bme->data[i].pressure, + bme->data[i].humidity, + bme->data[i].dew_point); + + ret = send(conn->fd, conn->buf, len, MSG_NOSIGNAL); + if (ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + pthread_mutex_unlock(&bme->lock); + return 0; + } + + printf("%s: send(): %m\n", __func__); + pthread_mutex_unlock(&bme->lock); + goto out_free; + } + + conn->min_timestamp = bme->data[i].time; + pthread_mutex_unlock(&bme->lock); + } + +out_free: + printf("%s: Closing socket %d\n", __func__, conn->fd); + close(conn->fd); + free(conn); + + return 0; +} + +struct listening_socket { + struct event_handler ev; + int fd; + struct bme280 *bme; +}; + +static int handle_incoming_connection(struct event_handler *ptr) +{ + struct sockaddr_in peer; + socklen_t peerlen = 0; + struct listening_socket *listener = (struct listening_socket *)ptr; + struct connection_state *conn; + struct bme280 *bme = listener->bme; + int fd; + + bzero(&peer, sizeof(peer)); + + fd = accept4(listener->fd, (struct sockaddr *)&peer, &peerlen, SOCK_NONBLOCK); + if (fd < 0) { + printf("Error while accept(): %m\n"); + return -1; + } + + conn = calloc(sizeof(*conn), 1); + conn->ev.ev.data.fd = fd; + conn->fd = fd; + conn->ev.ev.events = EPOLLIN; + conn->ev.handle_event = handle_connection_state; + conn->ev.name = "socket"; + conn->bme = bme; + conn->min_timestamp = -1; + conn->len = 0; + + register_event_handler(bme, &conn->ev); + + return 0; +} + +static void *event_handler(void *arg) +{ + struct bme280 *bme = arg; + struct sockaddr_in addr; + struct listening_socket incoming; + int sockfd, ret; + + bzero(&addr, sizeof(addr)); + bzero(&incoming, sizeof(incoming)); + + sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (sockfd < 0) { + printf("Failed to create socket: %m\n"); + return NULL; + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(6000); + addr.sin_addr.s_addr = INADDR_ANY; + + ret = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) { + printf("Failed to bind: %m\n"); + goto close_socket; + } + + ret = listen(sockfd, 5); + if (ret < 0) { + printf("Failed to listen(): %m\n"); + goto close_socket; + } + + bme->epollfd = epoll_create(1); + if (bme->epollfd == -1) { + printf("Failed to epoll_create(): %m\n"); + goto close_socket; + } + + incoming.bme = bme; + incoming.ev.ev.data.fd = sockfd; + incoming.ev.ev.events = EPOLLIN; + incoming.ev.handle_event = handle_incoming_connection; + incoming.ev.name = "listener"; + incoming.fd = sockfd; + + ret = register_event_handler(bme, &incoming.ev); + + while(1) { + struct epoll_event ev; + struct event_handler *h; + + printf("%s: Waiting for events..\n", __func__); + ret = epoll_wait(bme->epollfd, &ev, 1, -1); + if (ret == -1) { + if (errno != EINTR) { + printf("epoll_wait: %m\n"); + return NULL; + } + + continue; + } + + if (ret == 0) { + printf("Timed out\n"); + continue; + } + + h = ev.data.ptr; + + printf("Handling %s %s event for %s\n", + ev.events & EPOLLIN ? "incoming" : "", + ev.events & EPOLLOUT ? "outgoing" : "", + h->name); + h->ev = ev; + h->handle_event(h); + } + +close_socket: + close(sockfd); + + return NULL; +} + +static int8_t i2c_read(uint8_t reg_addr, uint8_t *data, uint32_t len, void *intf_ptr) +{ + struct bme280 id; + int ret; + + id = *((struct bme280 *)intf_ptr); + + ret = write(id.fd, ®_addr, 1); + if (ret < 0) + return ret; + + ret = read(id.fd, data, len); + if (ret < 0) + return ret; + + return 0; +} + +static void delay_us(uint32_t period, void *unused) +{ + (void)unused; + + usleep(period); +} + +static int8_t i2c_write(uint8_t reg_addr, const uint8_t *data, uint32_t len, void *intf_ptr) +{ + uint8_t *buf; + struct bme280 id; + + id = *((struct bme280 *)intf_ptr); + + buf = malloc(len + 1); + buf[0] = reg_addr; + memcpy(buf + 1, data, len); + + if (write(id.fd, buf, len + 1) < (uint16_t)len) { + return BME280_E_COMM_FAIL; + } + + free(buf); + + return BME280_OK; +} + +static double dp(double RH, double T) +{ + double gm, dp; + /* + * Constants and equation taken from: + * https://en.wikipedia.org/wiki/Dew_point#Calculating_the_dew_point + */ +/* double a = 6.112;*/ + double b, c; + double d = 234.5; + + if (T > 0) { + b = 17.368; + c = 238.88; + } else { + b = 17.966; + c = 247.15; + } + + gm = log(RH / 100.0 * exp((b - T / d) * (T / (c + T)))); + dp = c * gm / (b - gm); + + return dp; +} + +static int stream_sensor_data_forced_mode(struct bme280 *bme) +{ + int ret; + uint8_t settings_sel = 0; + uint32_t req_delay; + struct bme280_data comp_data; + struct bme280_dev *dev = bme->dev; + + double t_sum = 0, h_sum = 0, p_sum = 0; + int num = 0, first_run = 1, last_min; + + /* Recommended mode of operation: Indoor navigation */ + dev->settings.osr_h = BME280_OVERSAMPLING_1X; + dev->settings.osr_p = BME280_OVERSAMPLING_16X; + dev->settings.osr_t = BME280_OVERSAMPLING_2X; + dev->settings.filter = BME280_FILTER_COEFF_16; + + settings_sel = BME280_OSR_PRESS_SEL | BME280_OSR_TEMP_SEL | + BME280_OSR_HUM_SEL | BME280_FILTER_SEL; + + ret = bme280_set_sensor_settings(settings_sel, dev); + if (ret) { + fprintf(stderr, "Failed to set sensor settings (code %+d).", ret); + + return ret; + } + + printf("Temperature, Pressure, Humidity\n"); + + /* + * Calculate the minimum delay required between consecutive + * measurement based upon the sensor enabled and the + * oversampling configuration. + */ + req_delay = bme280_cal_meas_delay(&dev->settings); + + while (1) { + struct tm *now; + time_t t; + + ret = bme280_set_sensor_mode(BME280_FORCED_MODE, dev); + if (ret) { + fprintf(stderr, "Failed to set sensor mode (code %+d).", ret); + break; + } + + /* Wait for the measurement to complete and print data */ + dev->delay_us(req_delay * 1000, dev->intf_ptr); + ret = bme280_get_sensor_data(BME280_ALL, &comp_data, dev); + if (ret) { + fprintf(stderr, "Failed to get sensor data (code %+d).", ret); + break; + } + + t_sum += comp_data.temperature; + p_sum += comp_data.pressure; + h_sum += comp_data.humidity; + num++; + + t = time(NULL); + now = localtime(&t); + + if (first_run || now->tm_min != last_min) { + double temp, press, hum, dew; + char s[64]; + long unsigned int i; + + first_run = 0; + last_min = now->tm_min; + + temp = t_sum / (double)num; + press = p_sum * 0.01 / (double)num; + hum = h_sum / (double)num; + + strftime(s, sizeof(s), "%Y.%m.%d %H:%M:%S", now); + dew = dp(hum, temp); + + printf("%s %0.4lf deg C, %0.4lf hPa, %0.4lf%%, dp: %.3f C\n", + s, temp, press, hum, dew); + + pthread_mutex_lock(&bme->lock); + for (i = 0; i < ARRAY_SIZE(bme->data) - 1; i++) + bme->data[i] = bme->data[i + 1]; + + bme->data[i].time = t; + bme->data[i].temperature = temp; + bme->data[i].pressure = press; + bme->data[i].humidity = hum; + bme->data[i].dew_point = dew; + pthread_mutex_unlock(&bme->lock); + + num = 0; + t_sum = p_sum = h_sum = 0; + } + } + + return ret; +} + +int main(int argc, char *argv[]) +{ + pthread_t thread; + struct bme280_dev dev; + struct bme280 id; + int ret; + + bzero(&id, sizeof(id)); + + if (argc < 3) { + fprintf(stderr, "Missing argument for i2c bus and address.\n" + "Usage: %s /dev/i2c-0 0x76\n", argv[0]); + exit(1); + } + + bzero(&id, sizeof(id)); + + if ((id.fd = open(argv[1], O_RDWR)) < 0) { + fprintf(stderr, "Failed to open the i2c bus %s\n", argv[1]); + exit(1); + } + + id.dev_addr = strtol(argv[2], NULL, 0); + if (ioctl(id.fd, I2C_SLAVE, id.dev_addr) < 0) { + fprintf(stderr, "Failed to acquire bus access and/or talk to slave.\n"); + exit(1); + } + + dev.intf = BME280_I2C_INTF; + dev.read = i2c_read; + dev.write = i2c_write; + dev.delay_us = delay_us; + dev.intf_ptr = &id; + + id.dev = &dev; + + ret = bme280_init(&dev); + if (ret) { + fprintf(stderr, "Failed to initialize the device (code %+d).\n", ret); + exit(1); + } + + pthread_mutex_init(&id.lock, NULL); + pthread_create(&thread, NULL, event_handler, &id); + + ret = stream_sensor_data_forced_mode(&id); + if (ret) { + fprintf(stderr, "Failed to stream sensor data (code %+d).\n", ret); + exit(1); + } +} -- 2.45.0