From: Nathael Pajani Date: Mon, 26 Apr 2021 22:00:44 +0000 (+0200) Subject: First version of datalog for TorusTech X-Git-Url: http://git.techno-innov.fr/?a=commitdiff_plain;h=bdbab0310828ef5453ef5592309f2ba6b2bd2716;p=soft%2Flpc82x%2Fexanh First version of datalog for TorusTech --- diff --git a/host/exanh_datalog/.gitignore b/host/exanh_datalog/.gitignore new file mode 100644 index 0000000..4e601ef --- /dev/null +++ b/host/exanh_datalog/.gitignore @@ -0,0 +1,3 @@ +exanh_datalog +exanh_datalog_armhf +*.log diff --git a/host/exanh_datalog/Makefile b/host/exanh_datalog/Makefile new file mode 100644 index 0000000..b0e856f --- /dev/null +++ b/host/exanh_datalog/Makefile @@ -0,0 +1,32 @@ +#CROSS_COMPILE ?= arm-linux-gnueabihf- +CC = $(CROSS_COMPILE)gcc + +CFLAGS = -Wall -O2 -Wextra + +EXEC = exanh_datalog + +all: $(EXEC) + + +OBJDIR = objs +SRC = $(shell find . -name \*.c) +OBJS = ${SRC:%.c=${OBJDIR}/%.o} +INCLUDES = includes/ + +${OBJDIR}/%.o: %.c + @mkdir -p $(dir $@) + @echo "-- compiling" $< + @$(CC) -MMD -MP -MF ${OBJDIR}/$*.d $(CFLAGS) $< -c -o $@ -I$(INCLUDES) + +$(EXEC): $(OBJS) + @echo "Linking $@ ..." + @$(CC) $(LDFLAGS) -o $@ $(OBJS) + @echo Done. + + +clean: + find ${OBJDIR} -name "*.o" -exec rm {} \; + find ${OBJDIR} -name "*.d" -exec rm {} \; + +mrproper: clean + rm -f $(EXEC) diff --git a/host/exanh_datalog/main.c b/host/exanh_datalog/main.c new file mode 100644 index 0000000..870d7aa --- /dev/null +++ b/host/exanh_datalog/main.c @@ -0,0 +1,576 @@ +/**************************************************************************** + * Get data from sensors and decode + * + * main.c + * + * + * Copyright 2013-2014 Nathael Pajani + * + * + * 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 . + * + ****************************************************************************/ + + +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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(""); /* Goto terminal home (top left) */ + } + printf("Sensor %d, pkt %d, seq %d\n", info->addr, pkt_count[info->addr], info->seqnum); + if (debug == 3) { + printf("\tSoil: %d\n", info->raw_humidity); + printf("\tLux: %d, IR: %d, UV: %d\n", info->lux, info->ir, info->uv); + printf("\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("\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; +} + + diff --git a/host/exanh_datalog/serial_utils.c b/host/exanh_datalog/serial_utils.c new file mode 100644 index 0000000..700c11b --- /dev/null +++ b/host/exanh_datalog/serial_utils.c @@ -0,0 +1,59 @@ +/********************************************************************* + * + * Serial utility functions + * + * + * Copyright 2012-2014 Nathael Pajani + * + * 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 . + * + *********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define SERIAL_BAUD B115200 + + +int serial_setup(char* name) +{ + struct termios tio; + int fd = -1; + + /* Open serial port */ + fd = open(name, O_RDWR | O_NONBLOCK); + if (fd < 0) { + perror("Unable to open communication with companion chip"); + return -1; + } + /* Setup serial port */ + memset(&tio, 0, sizeof(tio)); + tio.c_cflag = CS8 | CREAD | CLOCAL; /* 8n1, see termios.h for more information */ + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 5; + cfsetospeed(&tio, SERIAL_BAUD); + cfsetispeed(&tio, SERIAL_BAUD); + tcsetattr(fd, TCSANOW, &tio); + + return fd; +} + diff --git a/host/exanh_datalog/serial_utils.h b/host/exanh_datalog/serial_utils.h new file mode 100644 index 0000000..7c17bcf --- /dev/null +++ b/host/exanh_datalog/serial_utils.h @@ -0,0 +1,32 @@ +/********************************************************************* + * + * Serial utility functions + * + * + * Copyright 2012 Nathael Pajani + * + * 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 . + * + *********************************************************************/ +#ifndef SERIAL_UTILS_H +#define SERIAL_UTILS_H + +/* Setup serial comunication, using name if given or saved name if name is NULL + * SERIAL_BAUD B38400 + * c_cflag (CS7 | PARENB | CREAD | CLOCAL) (7e1) + */ +int serial_setup(char* name); + +#endif /* SERIAL_UTILS_H */ +