1 /****************************************************************************
2 * Get data from sensors and decode
7 * Copyright 2013-2014 Nathael Pajani <nathael.pajani@ed3l.fr>
10 * This program is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation, either version 3 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23 ****************************************************************************/
26 /* this protocol handler is designed to run on a host replacing the DTPlug,
27 * but should be easy to use as a source for the protocol handling code for
41 #include <sys/types.h>
43 #include <sys/select.h>
45 #include <arpa/inet.h>
47 #include "serial_utils.h"
52 #define PROG_NAME "Sensors polling and decode"
55 #define MIN_SECS_SINCE_EPOCH 1580000000
58 /* Set to 0, 1 or 2 to change debug level */
66 uint16_t raw_humidity; /* Soil */
72 uint16_t humidity; /* From BME */
76 void help(char *prog_name)
78 fprintf(stderr, "---------------- "PROG_NAME" --------------------------------\n");
79 fprintf(stderr, "Usage: %s [options]\n" \
80 " Available options:\n" \
81 " \t -d | --device : Serial device to use for serial communication with the module\n" \
82 " \t -c | --chain : Sensor Chain number (used to store data to the right directory)\n" \
83 " \t -p | --period : delay in seconds between two sensors data request\n" \
84 " \t -V | --verbose : be more verbose\n" \
85 " \t -D | --debug : add full debug\n" \
86 " \t -h | --help : Print this help\n" \
87 " \t -v | --version : Print programm version\n" \
88 " All other arguments are data for the command, handled depending on the command.\n", prog_name);
89 fprintf(stderr, "-----------------------------------------------------------------------\n");
93 /*****************************************************************************/
94 /* Address handling */
97 uint8_t addr_list[NB_ADDRESS + 1];
98 uint8_t addr_retry[NB_ADDRESS + 1];
99 uint16_t last_seq_num[NB_ADDRESS + 1];
100 uint8_t probe_sensors[NB_ADDRESS + 1];
101 int pkt_count[NB_ADDRESS];
103 int get_next_free_addr(void)
106 /* Start at 1, do not use address 0 */
107 for (i = 1; i < NB_ADDRESS; i++) {
108 if (addr_list[i] == 0) {
116 /*****************************************************************************/
117 /* Requests to sensors */
119 void send_request(int slave_fd, int addr_index)
125 buf[2] = 'a'; /* Request all sensor info */
127 write(slave_fd, buf, 3);
129 printf("Sent request to %d(%d)\n", addr_index, addr_list[addr_index]);
133 void send_new_address(int slave_fd)
139 buf[2] = get_next_free_addr();
141 write(slave_fd, buf, 3);
143 printf("New address sent: %d\n", buf[2]);
147 void set_led(int slave_fd, struct sensor_info* info)
153 red = (info->raw_humidity - 1500) / 10;
158 buf[3] = red; /* Red */
159 buf[4] = green; /* Green */
160 buf[5] = 0; /* Blue */
162 write(slave_fd, buf, 6);
164 printf("Led color sent: %d : %d - %d\n", buf[1], buf[3], buf[4]);
168 void clear_leds(int slave_fd)
175 buf[3] = 0; /* Red */
176 buf[4] = 0; /* Green */
177 buf[5] = 0; /* Blue */
179 write(slave_fd, buf, 6);
181 printf("Leds cleared\n");
185 /*****************************************************************************/
186 /* Packets handling */
191 int bad_checksums = 0;
193 int packet_slice(char c)
199 } else if (c == '$') {
201 /* Set packet length to 19 so that next character received (request type)
202 will terminate the packet */
203 data_idx = PKT_SIZE - 1;
209 if (data_idx == (PKT_SIZE - 1)) {
218 int packet_check(void)
221 case '#' : { /* Data packet */
224 for (i = 0; i < (PKT_SIZE - 1); i++) {
227 if ((sum & 0xFF) == data[(PKT_SIZE - 1)]) {
228 printf("Packet OK\n");
229 return 1; /* Checksum OK */
231 printf("Bad packet (cksum)\n");
232 bad_checksums++; /* Count bad checksum for current address */
236 case '$' : /* Request from device */
237 printf("Request received: %c\n", data[(PKT_SIZE - 1)]);
238 if (data[(PKT_SIZE - 1)] == 'c') {
239 /* Request for a new address */
247 /*****************************************************************************/
248 void parse_packet(struct sensor_info* info)
250 uint16_t* vars = (uint16_t*)data;
253 info->addr = data[1] & 0x1F;
254 info->raw_humidity = (uint16_t)htons(vars[1]);
255 info->lux = (uint16_t)htons(vars[2]);
256 info->ir = (uint16_t)htons(vars[3]);
257 info->uv = (uint16_t)htons(vars[4]);
258 info->pressure = (uint16_t)htons(vars[5]);
259 info->comp_temp = (int16_t)htons(vars[6]);
260 info->humidity = (uint16_t)htons(vars[7]);
261 info->seqnum = (uint16_t)htons(vars[8]);
263 pkt_count[info->addr]++;
266 void display_info(struct sensor_info* info)
271 printf("
\e[H"); /* Goto terminal home (top left) */
273 printf("
\e[KSensor %d, pkt %d, seq %d\n", info->addr, pkt_count[info->addr], info->seqnum);
275 printf("
\e[K\tSoil: %d\n", info->raw_humidity);
276 printf("
\e[K\tLux: %d, IR: %d, UV: %d\n", info->lux, info->ir, info->uv);
277 printf("
\e[K\tPatm: %d hPa, Temp: %d,%02d degC, Humidity: %d,%d rH\n\n",
279 info->comp_temp / 10, (info->comp_temp > 0) ? (info->comp_temp % 10) : ((-info->comp_temp) % 10),
280 info->humidity / 10, info->humidity % 10);
286 #define LOGFILE_NAME_LEN 100
288 void store_info(struct sensor_info* info, int chain, time_t seconds)
290 char logfile_name[LOGFILE_NAME_LEN];
293 int logfile = 0, ret = 0;
295 gmtime_r(&seconds, &time);
296 snprintf(logfile_name, LOGFILE_NAME_LEN, "chain%d/sensor%d/%d-%02d-%02d.log",
298 (time.tm_year + 1900), (time.tm_mon + 1), time.tm_mday);
300 /* Upon each day change, create a new file with it's header */
301 if (stat(logfile_name, &statbuf) == -1) {
302 logfile = open(logfile_name, (O_CREAT | O_WRONLY), (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
304 /* Write header to file */
305 char header[LINE_LEN];
307 nb = snprintf(header, LINE_LEN, "# Data logs for sensor %d on chain %d - %s#\n",
308 info->addr, chain, ctime(&seconds));
309 ret = write(logfile, header, nb);
311 perror("Write error for hearder");
312 fprintf(stderr, "Error while writting first part of header to log file %s (%d) for sensor %d\n",
313 logfile_name, logfile, info->addr);
317 nb = snprintf(header, LINE_LEN,
318 "# 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");
319 ret = write(logfile, header, nb);
321 perror("Write error for hearder");
322 fprintf(stderr, "Error while writting second part of header to log file %s (%d) for sensor %d\n",
323 logfile_name, logfile, info->addr);
328 perror("Create logfile error");
329 fprintf(stderr, "Unable to create log file %s for sensor %d\n", logfile_name, info->addr);
335 logfile = open(logfile_name, (O_APPEND | O_WRONLY));
339 nb = snprintf(line, LINE_LEN, "%ld, %d, %d, %d, %d, %d, %d, %d, %d\n",
340 seconds, info->seqnum, info->raw_humidity,
341 info->lux, info->ir, info->uv,
342 info->pressure, info->comp_temp, info->humidity);
343 ret = write(logfile, line, nb);
345 perror("Write error for data");
346 fprintf(stderr, "Error while writting data line to log file %s (%d) for sensor %d\n",
347 logfile_name, logfile, info->addr);
351 perror("Error openning logfile for data");
352 fprintf(stderr, "Unable to open logfile %s for sensor %d\n", logfile_name, info->addr);
356 /*****************************************************************************/
357 /* Main part of programm */
359 int main(int argc, char* argv[])
366 struct timeval start_tstamp;
369 int option_index = 0;
372 struct option long_options[] = {
373 {"device", required_argument, 0, 'd'},
374 {"chain", required_argument, 0, 'c'},
375 {"period", required_argument, 0, 'p'},
376 {"debug", no_argument, 0, 'D'},
377 {"verbose", no_argument, 0, 'V'},
378 {"help", no_argument, 0, 'h'},
379 {"version", no_argument, 0, 'v'},
383 /* If time is not yet setup, wait for system to fix this */
385 gettimeofday(&start_tstamp, NULL);
386 } while (start_tstamp.tv_sec < MIN_SECS_SINCE_EPOCH);
388 c = getopt_long(argc, argv, "d:c:p:DVhv", long_options, &option_index);
390 /* no more options to parse */
401 period = strtoul(optarg, NULL, 10);
406 chain = strtoul(optarg, NULL, 10);
413 __attribute__ ((fallthrough));
420 printf("%s Version %s\n", PROG_NAME, VERSION);
433 /* Need Serial port as parameter */
434 if (device == NULL) {
435 printf("Error, need serial (tty) device\n");
440 memset(addr_list, 0, NB_ADDRESS);
441 memset(pkt_count, 0, NB_ADDRESS * sizeof(int));
444 slave_fd = serial_setup(device);
446 printf("Unable to open specified serial port %s\n", device);
450 printf("Logs started on %s\n", ctime(&(start_tstamp.tv_sec)));
452 /* ************************************************* */
453 /* And never stop handling data ! */
455 static int addr_index = 0;
456 static time_t last = 0;
457 int len = 0, ret = 0;
462 gettimeofday(&now, NULL); /* Get seconds since epoch */
464 /* Send periodic requests for data */
465 if (now.tv_sec >= (last + period)) {
466 addr_index = NB_ADDRESS - 1;
471 clear_leds(slave_fd);
472 /* Reset all retry counters */
473 memset(addr_retry, MAX_RETRY, NB_ADDRESS);
475 if (addr_index > 0) {
476 send_request(slave_fd, addr_index);
477 data_idx = 0; /* Moving to next sensor, reset data index for packet Rx */
478 bad_checksums = 0; /* Reset bad checksums for current address */
481 usleep(100 * 1000); /* Leave some time for the sensor to reply, and for the system */
483 memset(buf, 0, BUF_SIZE);
484 /* Get serial data and try to build a packet */
485 len = read(slave_fd, buf, BUF_SIZE);
486 /* FIXME */ //printf("read: len: %d, idx: %d\n", len, data_idx);
488 if (errno == EAGAIN) {
489 /* Nothing to do, we're in non-blocking mode */
492 printf("\nError, got activity on serial link, but no data ... End of file.\n");
494 perror("serial read error");
499 slave_fd = serial_setup(device);
501 } while (slave_fd < 0);
505 /* We are receiving something, log to stderr */
510 ret = packet_slice(buf[idx]);
511 /* Check return code to know if we have enough data for a packet */
513 /* check that the packet is valid */
514 ret = packet_check();
518 struct sensor_info info;
519 /* Valid packet received, parse data */
522 printf("Packet from sensor %d\n", info.addr);
524 if (info.seqnum == last_seq_num[info.addr]) {
525 /* Ignore duplicated packet */
527 printf("Dup packet from sensor %d (seq: %d)\n", info.addr, info.seqnum);
531 /* Check data validity */
532 if ((info.raw_humidity < 1000) || (info.raw_humidity > 4000)) {
534 printf("Invalid raw soil humidity [1000 .. 4000] : %d\n", info.raw_humidity);
538 if ((info.pressure < 800) || (info.pressure > 1200)) {
540 printf("Invalid pressure [800 .. 1200] : %d\n", info.pressure);
544 if ((info.comp_temp < -400) || (info.comp_temp > 1000)) {
546 printf("Invalid temperature [-400 .. 1000] : %d\n", info.comp_temp);
550 if (info.humidity > 1000) {
552 printf("Invalid air rel humidity [0 .. 1000] : %d\n", info.humidity);
557 /* New packet received */
558 last_seq_num[info.addr] = info.seqnum;
561 /* New device ? store address */
562 if ((info.addr < NB_ADDRESS) && (addr_list[info.addr] == 0)) {
563 addr_list[info.addr] = info.addr;
565 printf("New device detected : %d\n", info.addr);
569 /* Store data to log file */
570 store_info(&info, chain, now.tv_sec);
573 set_led(slave_fd, &info);
577 /* Checksum error for all three packets, retry */
578 if ((addr_retry[addr_index + 1] > 0) && (bad_checksums == 3)) {
580 addr_retry[addr_index]--;
584 /* Done handling received data, need to send a new address to a sensor ? */
585 send_new_address(slave_fd);
591 } /* End of infinite loop */