--- /dev/null
+/****************************************************************************
+ * Get data from sensors and decode
+ *
+ * main.c
+ *
+ *
+ * Copyright 2013-2014 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ ****************************************************************************/
+
+
+/* this protocol handler is designed to run on a host replacing the DTPlug,
+ * but should be easy to use as a source for the protocol handling code for
+ * the DTPlug itself.
+ */
+
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <time.h>
+#include "serial_utils.h"
+
+
+#define BUF_SIZE 100
+
+#define PROG_NAME "Sensors polling and decode"
+#define VERSION "0.1"
+
+#define MIN_SECS_SINCE_EPOCH 1580000000
+#define MAX_RETRY 10
+
+/* Set to 0, 1 or 2 to change debug level */
+int debug = 0;
+
+struct sensor_info {
+ uint8_t addr;
+ uint8_t got_tsl;
+ uint8_t got_veml;
+ uint8_t got_bme;
+ uint16_t raw_humidity; /* Soil */
+ uint16_t lux;
+ uint16_t ir;
+ uint16_t uv;
+ uint16_t pressure;
+ int16_t comp_temp;
+ uint16_t humidity; /* From BME */
+ uint16_t seqnum;
+};
+
+void help(char *prog_name)
+{
+ fprintf(stderr, "---------------- "PROG_NAME" --------------------------------\n");
+ fprintf(stderr, "Usage: %s [options]\n" \
+ " Available options:\n" \
+ " \t -d | --device : Serial device to use for serial communication with the module\n" \
+ " \t -c | --chain : Sensor Chain number (used to store data to the right directory)\n" \
+ " \t -p | --period : delay in seconds between two sensors data request\n" \
+ " \t -V | --verbose : be more verbose\n" \
+ " \t -D | --debug : add full debug\n" \
+ " \t -h | --help : Print this help\n" \
+ " \t -v | --version : Print programm version\n" \
+ " All other arguments are data for the command, handled depending on the command.\n", prog_name);
+ fprintf(stderr, "-----------------------------------------------------------------------\n");
+}
+
+
+/*****************************************************************************/
+/* Address handling */
+
+#define NB_ADDRESS 8
+uint8_t addr_list[NB_ADDRESS + 1];
+uint8_t addr_retry[NB_ADDRESS + 1];
+uint16_t last_seq_num[NB_ADDRESS + 1];
+uint8_t probe_sensors[NB_ADDRESS + 1];
+int pkt_count[NB_ADDRESS];
+
+int get_next_free_addr(void)
+{
+ int i = 0;
+ /* Start at 1, do not use address 0 */
+ for (i = 1; i < NB_ADDRESS; i++) {
+ if (addr_list[i] == 0) {
+ addr_list[i] = i;
+ return i;
+ }
+ }
+ return -1;
+}
+
+/*****************************************************************************/
+/* Requests to sensors */
+
+void send_request(int slave_fd, int addr_index)
+{
+ char buf[4];
+
+ buf[0] = '#';
+ buf[1] = addr_index;
+ buf[2] = 'a'; /* Request all sensor info */
+
+ write(slave_fd, buf, 3);
+ if (debug) {
+ printf("Sent request to %d(%d)\n", addr_index, addr_list[addr_index]);
+ }
+}
+
+void send_new_address(int slave_fd)
+{
+ char buf[4];
+
+ buf[0] = '#';
+ buf[1] = 0xFF;
+ buf[2] = get_next_free_addr();
+
+ write(slave_fd, buf, 3);
+ if (debug) {
+ printf("New address sent: %d\n", buf[2]);
+ }
+}
+
+void set_led(int slave_fd, struct sensor_info* info)
+{
+ uint8_t buf[6];
+ int red = 0;
+ int green = 0;
+
+ red = (info->raw_humidity - 1500) / 10;
+ green = 200 - red;
+ buf[0] = '#';
+ buf[1] = info->addr;
+ buf[2] = 'l';
+ buf[3] = red; /* Red */
+ buf[4] = green; /* Green */
+ buf[5] = 0; /* Blue */
+
+ write(slave_fd, buf, 6);
+ if (debug) {
+ printf("Led color sent: %d : %d - %d\n", buf[1], buf[3], buf[4]);
+ }
+}
+
+void clear_leds(int slave_fd)
+{
+ char buf[6];
+
+ buf[0] = '#';
+ buf[1] = 0xFF;
+ buf[2] = 'l';
+ buf[3] = 0; /* Red */
+ buf[4] = 0; /* Green */
+ buf[5] = 0; /* Blue */
+
+ write(slave_fd, buf, 6);
+ if (debug) {
+ printf("Leds cleared\n");
+ }
+}
+
+/*****************************************************************************/
+/* Packets handling */
+
+#define PKT_SIZE 20
+uint8_t data[150];
+int data_idx = 0;
+int bad_checksums = 0;
+
+int packet_slice(char c)
+{
+ if (data_idx == 0) {
+ if (c == '#') {
+ data[0] = c;
+ data_idx = 1;
+ } else if (c == '$') {
+ data[0] = c;
+ /* Set packet length to 19 so that next character received (request type)
+ will terminate the packet */
+ data_idx = PKT_SIZE - 1;
+ }
+ return 0;
+ }
+ if (data_idx > 0) {
+ data[data_idx] = c;
+ if (data_idx == (PKT_SIZE - 1)) {
+ data_idx = 0;
+ return 1;
+ }
+ data_idx++;
+ }
+ return 0;
+}
+
+int packet_check(void)
+{
+ switch (data[0]) {
+ case '#' : { /* Data packet */
+ int i = 0;
+ uint32_t sum = 0;
+ for (i = 0; i < (PKT_SIZE - 1); i++) {
+ sum += data[i];
+ }
+ if ((sum & 0xFF) == data[(PKT_SIZE - 1)]) {
+ printf("Packet OK\n");
+ return 1; /* Checksum OK */
+ }
+ printf("Bad packet (cksum)\n");
+ bad_checksums++; /* Count bad checksum for current address */
+ return 2;
+ }
+ break;
+ case '$' : /* Request from device */
+ printf("Request received: %c\n", data[(PKT_SIZE - 1)]);
+ if (data[(PKT_SIZE - 1)] == 'c') {
+ /* Request for a new address */
+ return 3;
+ }
+ break;
+ }
+ return 0;
+}
+
+/*****************************************************************************/
+void parse_packet(struct sensor_info* info)
+{
+ uint16_t* vars = (uint16_t*)data;
+
+ /* Decode data */
+ info->addr = data[1] & 0x1F;
+ info->raw_humidity = (uint16_t)htons(vars[1]);
+ info->lux = (uint16_t)htons(vars[2]);
+ info->ir = (uint16_t)htons(vars[3]);
+ info->uv = (uint16_t)htons(vars[4]);
+ info->pressure = (uint16_t)htons(vars[5]);
+ info->comp_temp = (int16_t)htons(vars[6]);
+ info->humidity = (uint16_t)htons(vars[7]);
+ info->seqnum = (uint16_t)htons(vars[8]);
+
+ pkt_count[info->addr]++;
+}
+
+void display_info(struct sensor_info* info)
+{
+ if (debug) {
+ /* Display data */
+ if (debug == 3) {
+ printf("\e[H"); /* Goto terminal home (top left) */
+ }
+ printf("\e[KSensor %d, pkt %d, seq %d\n", info->addr, pkt_count[info->addr], info->seqnum);
+ if (debug == 3) {
+ printf("\e[K\tSoil: %d\n", info->raw_humidity);
+ printf("\e[K\tLux: %d, IR: %d, UV: %d\n", info->lux, info->ir, info->uv);
+ printf("\e[K\tPatm: %d hPa, Temp: %d,%02d degC, Humidity: %d,%d rH\n\n",
+ info->pressure,
+ info->comp_temp / 10, (info->comp_temp > 0) ? (info->comp_temp % 10) : ((-info->comp_temp) % 10),
+ info->humidity / 10, info->humidity % 10);
+ printf("\e[K\n");
+ }
+ }
+}
+
+#define LOGFILE_NAME_LEN 100
+#define LINE_LEN 200
+void store_info(struct sensor_info* info, int chain, time_t seconds)
+{
+ char logfile_name[LOGFILE_NAME_LEN];
+ struct tm time;
+ struct stat statbuf;
+ int logfile = 0, ret = 0;
+
+ gmtime_r(&seconds, &time);
+ snprintf(logfile_name, LOGFILE_NAME_LEN, "chain%d/sensor%d/%d-%02d-%02d.log",
+ chain, info->addr,
+ (time.tm_year + 1900), (time.tm_mon + 1), time.tm_mday);
+
+ /* Upon each day change, create a new file with it's header */
+ if (stat(logfile_name, &statbuf) == -1) {
+ logfile = open(logfile_name, (O_CREAT | O_WRONLY), (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
+ if (logfile > 0) {
+ /* Write header to file */
+ char header[LINE_LEN];
+ int nb = 0;
+ nb = snprintf(header, LINE_LEN, "# Data logs for sensor %d on chain %d - %s#\n",
+ info->addr, chain, ctime(&seconds));
+ ret = write(logfile, header, nb);
+ if (ret != nb) {
+ perror("Write error for hearder");
+ fprintf(stderr, "Error while writting first part of header to log file %s (%d) for sensor %d\n",
+ logfile_name, logfile, info->addr);
+ close(logfile);
+ return;
+ }
+ nb = snprintf(header, LINE_LEN,
+ "# Format : seconds(epoch), seq number, raw soil, raw lux, raw ir, raw uv, press (hPa), temp(1/10°C), air hum(1/10%%)\n#\n\n");
+ ret = write(logfile, header, nb);
+ if (ret != nb) {
+ perror("Write error for hearder");
+ fprintf(stderr, "Error while writting second part of header to log file %s (%d) for sensor %d\n",
+ logfile_name, logfile, info->addr);
+ close(logfile);
+ return;
+ }
+ } else {
+ perror("Create logfile error");
+ fprintf(stderr, "Unable to create log file %s for sensor %d\n", logfile_name, info->addr);
+ return;
+ }
+ close(logfile);
+ }
+
+ logfile = open(logfile_name, (O_APPEND | O_WRONLY));
+ if (logfile > 0) {
+ char line[LINE_LEN];
+ int nb = 0;
+ nb = snprintf(line, LINE_LEN, "%ld, %d, %d, %d, %d, %d, %d, %d, %d\n",
+ seconds, info->seqnum, info->raw_humidity,
+ info->lux, info->ir, info->uv,
+ info->pressure, info->comp_temp, info->humidity);
+ ret = write(logfile, line, nb);
+ if (ret != nb) {
+ perror("Write error for data");
+ fprintf(stderr, "Error while writting data line to log file %s (%d) for sensor %d\n",
+ logfile_name, logfile, info->addr);
+ }
+ close(logfile);
+ } else {
+ perror("Error openning logfile for data");
+ fprintf(stderr, "Unable to open logfile %s for sensor %d\n", logfile_name, info->addr);
+ }
+}
+
+/*****************************************************************************/
+/* Main part of programm */
+
+int main(int argc, char* argv[])
+{
+ /* Serial */
+ char* device = NULL;
+ int slave_fd = 0;
+ int chain = 0;
+ int period = 1;
+ struct timeval start_tstamp;
+
+ while(1) {
+ int option_index = 0;
+ int c = 0;
+
+ struct option long_options[] = {
+ {"device", required_argument, 0, 'd'},
+ {"chain", required_argument, 0, 'c'},
+ {"period", required_argument, 0, 'p'},
+ {"debug", no_argument, 0, 'D'},
+ {"verbose", no_argument, 0, 'V'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'v'},
+ {0, 0, 0, 0}
+ };
+
+ /* If time is not yet setup, wait for system to fix this */
+ do {
+ gettimeofday(&start_tstamp, NULL);
+ } while (start_tstamp.tv_sec < MIN_SECS_SINCE_EPOCH);
+
+ c = getopt_long(argc, argv, "d:c:p:DVhv", long_options, &option_index);
+
+ /* no more options to parse */
+ if (c == -1) break;
+ switch (c) {
+
+ /* d, device */
+ case 'd':
+ device = optarg;
+ break;
+
+ /* p, period */
+ case 'p':
+ period = strtoul(optarg, NULL, 10);
+ break;
+
+ /* c, chain */
+ case 'c':
+ chain = strtoul(optarg, NULL, 10);
+ break;
+
+ /* V, verbose */
+ /* D, debug */
+ case 'D':
+ debug++;
+ __attribute__ ((fallthrough));
+ case 'V':
+ debug++;
+ break;
+
+ /* v, version */
+ case 'v':
+ printf("%s Version %s\n", PROG_NAME, VERSION);
+ return 0;
+ break;
+
+ /* h, help */
+ case 'h':
+ default:
+ help(argv[0]);
+ return 0;
+ }
+ }
+
+
+ /* Need Serial port as parameter */
+ if (device == NULL) {
+ printf("Error, need serial (tty) device\n");
+ help(argv[0]);
+ return -1;
+ }
+
+ memset(addr_list, 0, NB_ADDRESS);
+ memset(pkt_count, 0, NB_ADDRESS * sizeof(int));
+
+ /* Open tty */
+ slave_fd = serial_setup(device);
+ if (slave_fd < 0) {
+ printf("Unable to open specified serial port %s\n", device);
+ return -2;
+ }
+
+ printf("Logs started on %s\n", ctime(&(start_tstamp.tv_sec)));
+
+ /* ************************************************* */
+ /* And never stop handling data ! */
+ while (1) {
+ static int addr_index = 0;
+ static time_t last = 0;
+ int len = 0, ret = 0;
+ struct timeval now;
+ char buf[BUF_SIZE];
+
+ /* Handle time */
+ gettimeofday(&now, NULL); /* Get seconds since epoch */
+
+ /* Send periodic requests for data */
+ if (now.tv_sec >= (last + period)) {
+ addr_index = NB_ADDRESS - 1;
+ last = now.tv_sec;
+ if (debug) {
+ printf("Here\n");
+ }
+ clear_leds(slave_fd);
+ /* Reset all retry counters */
+ memset(addr_retry, MAX_RETRY, NB_ADDRESS);
+ }
+ if (addr_index > 0) {
+ send_request(slave_fd, addr_index);
+ data_idx = 0; /* Moving to next sensor, reset data index for packet Rx */
+ bad_checksums = 0; /* Reset bad checksums for current address */
+ addr_index--;
+ }
+ usleep(100 * 1000); /* Leave some time for the sensor to reply, and for the system */
+
+ memset(buf, 0, BUF_SIZE);
+ /* Get serial data and try to build a packet */
+ len = read(slave_fd, buf, BUF_SIZE);
+ /* FIXME */ //printf("read: len: %d, idx: %d\n", len, data_idx);
+ if (len < 0) {
+ if (errno == EAGAIN) {
+ /* Nothing to do, we're in non-blocking mode */
+ } else {
+ if (len == 0) {
+ printf("\nError, got activity on serial link, but no data ... End of file.\n");
+ } else {
+ perror("serial read error");
+ }
+ close(slave_fd);
+ len = 0;
+ do {
+ slave_fd = serial_setup(device);
+ usleep(10 * 1000);
+ } while (slave_fd < 0);
+ }
+ } else {
+ int idx = 0;
+ /* We are receiving something, log to stderr */
+ if (debug == 2) {
+ write(2, buf, len);
+ }
+ while (idx < len) {
+ ret = packet_slice(buf[idx]);
+ /* Check return code to know if we have enough data for a packet */
+ if (ret == 1) {
+ /* check that the packet is valid */
+ ret = packet_check();
+ }
+ switch (ret) {
+ case 1: {
+ struct sensor_info info;
+ /* Valid packet received, parse data */
+ parse_packet(&info);
+ if (info.seqnum == last_seq_num[info.addr]) {
+ /* Ignore duplicated packet */
+ break;
+ }
+ /* Check data validity */
+ if ((info.raw_humidity < 1000) || (info.raw_humidity > 4000)) {
+ break;
+ }
+ if ((info.pressure < 800) || (info.pressure > 1200)) {
+ break;
+ }
+ if ((info.comp_temp < -400) || (info.comp_temp > 1000)) {
+ break;
+ }
+ if (info.humidity > 1000) {
+ break;
+ }
+
+ /* New packet received */
+ last_seq_num[info.addr] = info.seqnum;
+ display_info(&info);
+
+ /* New device ? store address */
+ if ((info.addr < NB_ADDRESS) && (addr_list[info.addr] == 0)) {
+ addr_list[info.addr] = info.addr;
+ }
+
+ /* Store data to log file */
+ store_info(&info, chain, now.tv_sec);
+
+ /* Update leds */
+ set_led(slave_fd, &info);
+ }
+ break;
+ case 2:
+ /* Checksum error for all three packets, retry */
+ if ((addr_retry[addr_index + 1] > 0) && (bad_checksums == 3)) {
+ addr_index++;
+ addr_retry[addr_index]--;
+ }
+ break;
+ case 3:
+ /* Done handling received data, need to send a new address to a sensor ? */
+ send_new_address(slave_fd);
+ break;
+ }
+ idx++;
+ }
+ }
+ } /* End of infinite loop */
+
+ close(slave_fd);
+ return 0;
+}
+
+