]> git.itanic.dy.fi Git - BME280_driver/commitdiff
Introduce bmed
authorTimo Kokkonen <timo.t.kokkonen@iki.fi>
Wed, 21 Oct 2020 18:07:18 +0000 (21:07 +0300)
committerTimo Kokkonen <timo.t.kokkonen@iki.fi>
Wed, 21 Oct 2020 18:07:18 +0000 (21:07 +0300)
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 <timo.t.kokkonen@iki.fi>
Makefile [new file with mode: 0644]
bmed.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
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 (file)
index 0000000..c00cfbb
--- /dev/null
+++ b/bmed.c
@@ -0,0 +1,543 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/i2c-dev.h>
+#include <fcntl.h>
+#include <math.h>
+#include <time.h>
+#include <pthread.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/epoll.h>
+
+#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, &reg_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);
+       }
+}