diff --git a/VERSION b/VERSION index 7ec1d6db..ccbccc3d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1.0 +2.2.0 diff --git a/basic_pkt_fwd/Makefile b/basic_pkt_fwd/Makefile index fa20a531..3964c899 100644 --- a/basic_pkt_fwd/Makefile +++ b/basic_pkt_fwd/Makefile @@ -35,17 +35,17 @@ LGW_INC += $(LGW_PATH)/inc/loragw_hal.h ### Linking options ifeq ($(CFG_SPI),native) - LIBS := -lloragw -lrt -lpthread + LIBS := -lloragw -lrt -lpthread -lm else ifeq ($(CFG_SPI),ftdi) - LIBS := -lloragw -lrt -lpthread -lmpsse + LIBS := -lloragw -lrt -lpthread -lmpsse -lm else ifeq ($(CFG_SPI),mac) $(error [error] Option mac not supported for SPI here) else # keep compatibility with SX1301 HAL version 1.2.x and bellow ifeq ($(LGW_PHY),native) - LIBS := -lloragw -lrt -lpthread + LIBS := -lloragw -lrt -lpthread -lm else ifeq ($(LGW_PHY),ftdi) - LIBS := -lloragw -lrt -lpthread -lmpsse + LIBS := -lloragw -lrt -lpthread -lmpsse -lm else ifeq ($(LGW_PHY),mac) $(error [error] Option mac not supported for SPI here) else diff --git a/beacon_pkt_fwd/Makefile b/beacon_pkt_fwd/Makefile index b98925b1..c070ce62 100644 --- a/beacon_pkt_fwd/Makefile +++ b/beacon_pkt_fwd/Makefile @@ -36,17 +36,17 @@ LGW_INC += $(LGW_PATH)/inc/loragw_gps.h ### Linking options ifeq ($(CFG_SPI),native) - LIBS := -lloragw -lrt -lpthread + LIBS := -lloragw -lrt -lpthread -lm else ifeq ($(CFG_SPI),ftdi) - LIBS := -lloragw -lrt -lpthread -lmpsse + LIBS := -lloragw -lrt -lpthread -lmpsse -lm else ifeq ($(CFG_SPI),mac) $(error [error] Option mac not supported for SPI here) else # keep compatibility with SX1301 HAL version 1.2.x and bellow ifeq ($(LGW_PHY),native) - LIBS := -lloragw -lrt -lpthread + LIBS := -lloragw -lrt -lpthread -lm else ifeq ($(LGW_PHY),ftdi) - LIBS := -lloragw -lrt -lpthread -lmpsse + LIBS := -lloragw -lrt -lpthread -lmpsse -lm else ifeq ($(LGW_PHY),mac) $(error [error] Option mac not supported for SPI here) else diff --git a/gps_pkt_fwd/Makefile b/gps_pkt_fwd/Makefile index ea8873fe..cf7176a6 100644 --- a/gps_pkt_fwd/Makefile +++ b/gps_pkt_fwd/Makefile @@ -36,17 +36,17 @@ LGW_INC += $(LGW_PATH)/inc/loragw_gps.h ### Linking options ifeq ($(CFG_SPI),native) - LIBS := -lloragw -lrt -lpthread + LIBS := -lloragw -lrt -lpthread -lm else ifeq ($(CFG_SPI),ftdi) - LIBS := -lloragw -lrt -lpthread -lmpsse + LIBS := -lloragw -lrt -lpthread -lmpsse -lm else ifeq ($(CFG_SPI),mac) $(error [error] Option mac not supported for SPI here) else # keep compatibility with SX1301 HAL version 1.2.x and bellow ifeq ($(LGW_PHY),native) - LIBS := -lloragw -lrt -lpthread + LIBS := -lloragw -lrt -lpthread -lm else ifeq ($(LGW_PHY),ftdi) - LIBS := -lloragw -lrt -lpthread -lmpsse + LIBS := -lloragw -lrt -lpthread -lmpsse -lm else ifeq ($(LGW_PHY),mac) $(error [error] Option mac not supported for SPI here) else diff --git a/poly_pkt_fwd/Makefile b/poly_pkt_fwd/Makefile index 66ec746b..0b60d191 100644 --- a/poly_pkt_fwd/Makefile +++ b/poly_pkt_fwd/Makefile @@ -19,6 +19,7 @@ RELEASE_VERSION := $(shell cat ../VERSION) CC := $(CROSS_COMPILE)gcc AR := $(CROSS_COMPILE)ar +#CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I. CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I. VFLAG := -D VERSION_STRING="\"$(RELEASE_VERSION)\"" @@ -36,24 +37,25 @@ LGW_INC += $(LGW_PATH)/inc/loragw_gps.h ### Linking options ifeq ($(CFG_SPI),native) - LIBS := -lloragw -lrt -lpthread + LIBS := -lloragw -lrt -lpthread -lm else ifeq ($(CFG_SPI),ftdi) - LIBS := -lloragw -lrt -lpthread -lmpsse + LIBS := -lloragw -lrt -lpthread -lmpsse -lm else ifeq ($(CFG_SPI),mac) - LIBS := -lloragw -lpthread -lmpsse + LIBS := -lloragw -lpthread -lmpsse -lm else # keep compatibility with SX1301 HAL version 1.2.x and bellow ifeq ($(LGW_PHY),native) - LIBS := -lloragw -lrt -lpthread + LIBS := -lloragw -lrt -lpthread -lm else ifeq ($(LGW_PHY),ftdi) - LIBS := -lloragw -lrt -lpthread -lmpsse + LIBS := -lloragw -lrt -lpthread -lmpsse -lm else ifeq ($(LGW_PHY),mac) - LIBS := -lloragw -lpthread -lmpsse + LIBS := -lloragw -lpthread -lmpsse -lm else $(error [error] Can't find configuration for SPI phy) endif endif + ### General build targets all: $(APP_NAME) @@ -73,15 +75,18 @@ obj/parson.o: src/parson.c inc/parson.h obj/monitor.o: src/monitor.c inc/monitor.h $(CC) -c $(CFLAGS) $< -o $@ +obj/gpiolib.o: src/gpiolib.c inc/gpiolib.h + $(CC) -c $(CFLAGS) $< -o $@ + obj/ghost.o: src/ghost.c inc/ghost.h $(LGW_INC) $(CC) -I$(LGW_PATH)/inc -c $(CFLAGS) $< -o $@ ### Main program compilation and assembly -obj/$(APP_NAME).o: src/$(APP_NAME).c $(LGW_INC) inc/parson.h inc/base64.h inc/ghost.h inc/monitor.h +obj/$(APP_NAME).o: src/$(APP_NAME).c $(LGW_INC) inc/parson.h inc/base64.h inc/ghost.h inc/monitor.h inc/gpiolib.h $(CC) -c $(CFLAGS) $(VFLAG) -I$(LGW_PATH)/inc $< -o $@ -$(APP_NAME): obj/$(APP_NAME).o $(LGW_PATH)/libloragw.a obj/parson.o obj/base64.o obj/ghost.o obj/monitor.o - $(CC) -L$(LGW_PATH) $< obj/parson.o obj/base64.o obj/ghost.o obj/monitor.o -o $@ $(LIBS) +$(APP_NAME): obj/$(APP_NAME).o $(LGW_PATH)/libloragw.a obj/parson.o obj/base64.o obj/ghost.o obj/monitor.o obj/gpiolib.o + $(CC) -L$(LGW_PATH) $< obj/parson.o obj/base64.o obj/ghost.o obj/monitor.o obj/gpiolib.o -o $@ $(LIBS) ### EOF diff --git a/poly_pkt_fwd/inc/gpiolib.h b/poly_pkt_fwd/inc/gpiolib.h new file mode 100644 index 00000000..ed68dcee --- /dev/null +++ b/poly_pkt_fwd/inc/gpiolib.h @@ -0,0 +1,21 @@ +#ifndef _GPIOLIB_H_ + +/* returns -1 or the file descriptor of the gpio value file */ +int gpio_export(int gpio); +/* Set direction to 2 = high output, 1 low output, 0 input */ +int gpio_direction(int gpio, int dir); +/* Release the GPIO to be claimed by other processes or a kernel driver */ +void gpio_unexport(int gpio); +/* Single GPIO read */ +int gpio_read(int gpio); +/* Set GPIO to val (1 = high) */ +int gpio_write(int gpio, int val); +/* Set which edge(s) causes the value select to return */ +int gpio_setedge(int gpio, int rising, int falling); +/* Blocks on select until GPIO toggles on edge */ +int gpio_select(int gpio); + +/* Return the GPIO file descriptor */ +int gpio_getfd(int gpio); + +#endif //_GPIOLIB_H_ \ No newline at end of file diff --git a/poly_pkt_fwd/local_conf-ic880a.json b/poly_pkt_fwd/local_conf-ic880a.json new file mode 100644 index 00000000..3b7ffc79 --- /dev/null +++ b/poly_pkt_fwd/local_conf-ic880a.json @@ -0,0 +1,25 @@ +{ +/* Put there parameters that are different for each gateway (eg. pointing one gateway to a test server while the others stay in production) */ +/* Settings defined in global_conf will be overwritten by those in local_conf */ + "gateway_conf": { + /* you must pick a unique 64b number for each gateway (represented by an hex string) */ + "gateway_ID": "AA555A000004BABA", + /* Email of gateway operator, max 40 chars*/ + "contact_email": "operator@gateway.tst", + /* Public description of this device, max 64 chars */ + "description": "Update me", + + /* For ic880A plate */ + /* https://github.com/ch2i/iC880A-Raspberry-PI */ + "led_heartbeat": 4, /* GPIO4 Blue */ + "led_down": 18, /* GPIO18 White */ + "led_error": 23, /* GPIO23 Red */ + "led_packet": 24, /* GPIO24 Green */ + + /* Enter VALID GPS coordinates below before enabling fake GPS */ + "fake_gps": false, + "ref_latitude": 10, + "ref_longitude": 20, + "ref_altitude": -1 + } +} diff --git a/poly_pkt_fwd/local_conf-linklabs.json b/poly_pkt_fwd/local_conf-linklabs.json new file mode 100644 index 00000000..36e36aa0 --- /dev/null +++ b/poly_pkt_fwd/local_conf-linklabs.json @@ -0,0 +1,23 @@ +{ +/* Put there parameters that are different for each gateway (eg. pointing one gateway to a test server while the others stay in production) */ +/* Settings defined in global_conf will be overwritten by those in local_conf */ + "gateway_conf": { + /* you must pick a unique 64b number for each gateway (represented by an hex string) */ + "gateway_ID": "AA555A000004BABA", + /* Email of gateway operator, max 40 chars*/ + "contact_email": "operator@gateway.tst", + /* Public description of this device, max 64 chars */ + "description": "Update me", + + /* For Linklabs boards */ + "pin_pps": 4, /* GPIO4 PPS from GPS */ + "led_pps": 25, /* GPIO25 PPS RED */ + "led_packet": 27, /* GPIO27 RED */ + + /* Enter VALID GPS coordinates below before enabling fake GPS */ + "fake_gps": false, + "ref_latitude": 10, + "ref_longitude": 20, + "ref_altitude": -1 + } +} diff --git a/poly_pkt_fwd/readme.md b/poly_pkt_fwd/readme.md index e1e5a67a..443a772f 100644 --- a/poly_pkt_fwd/readme.md +++ b/poly_pkt_fwd/readme.md @@ -30,6 +30,102 @@ up/down stream, all can be (de)activated by modifying the json. To learn more about the network protocol between the gateway and the server, please read the PROTOCOL.TXT document. +Specific Raspberry PI boards version features +============================================= + +To be able to drive Raspbery PI concentrator on boards LED, this program +use ~~bcm2835 library, you need to install them before anything. +see http://www.airspayce.com/mikem/bcm2835/~~ gpiolib which control GPIO using +linux filesystem, making this packet forwarder more compatible with different +target. + +This version has been written to works with Linklabs board and also with +ic880a concentrator + Raspberry Pi Plate. +https://github.com/ch2i/iC880A-Raspberry-PI + +Option for LED are in the configuration file so it should works with +any board just adjusting GPIO settings. Settings are the following, +a name and the GPIO pin number. Names are : + +- `led_heartbeat` LED used for hearbeat, will always blink when running +- `led_down` LED used for downstream, will blink on each downstream from server +- `led_error` LED used for hearbeat, will blink when a error occured +- `led_packet` LED will blink on each packet received +- `led_pps` LED used for GPS PPS indicator (mainly linklabs board) +- `pin_pps` Pin where PPS signal is connected to (if any, mainly linklabs board) +- `pin_reset` GPIO pin connected to SX1301 concentrator reset + +GPS PPS pin on linklabs boards are not connected to LED but on a GPIO, so `led_pps` +and `pin_pps` are used for a "software link", incoming PPS signal going to GPIO input +is redirected to GPIO led output, thus for example, with linklabs, configuration can be + + +```json + "pin_pps": 4, /* GPIO4 PPS from GPS */ + "led_pps": 25, /* GPIO25 PPS RED */ + "led_packet": 27, /* GPIO27 RED */ +``` + +for ic880a RPI plate (4 leds), configuration can be +```json + "led_heartbeat": 4, /* GPIO4 Blue */ + "led_down": 18, /* GPIO18 White */ + "led_error": 23, /* GPIO23 Red */ + "led_packet": 24, /* GPIO24 Green */ + "pin_reset": 17, /* GPIO17 concentrator reset */ + +``` + +And for RAK831 RPI Zero plate configuration can be (no led pins) +```json + "pin_reset": 25, /* GPIO25 concentrator reset */ +``` + + +An extented Log ouput on each packet received has also been added to this version, this +can be usefull for monitoring packet recevided by concentrator, regardless if they are or +not send the gateway. It's logged as info with the following informations + +INFO: [#DeviceAddr] containing the device Addr (as seen on TTN dashboard) so you can +filter with a grep for example +`tail -f /var/log/lora_pkt_fwd.log | grep "\[\#"` + +then it's followed by : + +- `jRQ` for join request +- `jAC` for join accept +- `uUP` for unconfirmed up +- `uDN` for unconfirmed down +- `cUP` for confirmed up +- `cDN` for confirmed down +- `RFU` for RFU + +Other following data are classic information of frame received. Here below an example of log + +``` +root@pi01(ro):~# tail -f /var/log/lora_pkt_fwd.log +INFO: [up] PUSH_ACK for server log.gatewaystats.org received in 26 ms +INFO: [down] for server log.gatewaystats.org PULL_ACK received in 25 ms +INFO: [#25FAAE33] RFU CRC:Bad Freq:867.30MHz ch:4 RFch:0 LORA[SF7 125Khz 2/3] RSSI:-107dB SNR:-11.5dB Size:232b +INFO: [down] for server router.eu.thethings.network PULL_ACK received in 40 ms +INFO: [down] for server log.gatewaystats.org PULL_ACK received in 26 ms +INFO: [#1DCDB85F] cUP CRC:OK Freq:867.10MHz ch:3 RFch:0 LORA[SF12 125Khz 4/5] RSSI:-65dB SNR:+9.0dB Size:16b Data:'gF+4zR0ArAEBqkBtIvDn+A==' +INFO: [up] PUSH_ACK for server router.eu.thethings.network received in 40 ms +INFO: [up] PUSH_ACK for server log.gatewaystats.org received in 26 ms +INFO: [down] for server router.eu.thethings.network serv_addr[ic]PULL_RESP received :) +INFO: [down] a packet will be sent on timestamp value 2481851356 +INFO: [#87802833] jAC CRC:Bad Freq:867.50MHz ch:5 RFch:0 LORA[SF7 125Khz 4/7] RSSI:-107dB SNR:-11.0dB Size:20b +INFO: [down] for server router.eu.thethings.network PULL_ACK received in 43 ms +INFO: [#DD5343A9] jAC CRC:Bad Freq:868.10MHz ch:0 RFch:1 LORA[SF7 125Khz 4/5] RSSI:-105dB SNR:-7.0dB Size:23b +INFO: [#37F76D0D] jRQ CRC:Bad Freq:867.10MHz ch:3 RFch:0 LORA[SF7 125Khz 4/5] RSSI:-102dB SNR:-7.0dB Size:23b +INFO: [#1D57298D] uUP CRC:OK Freq:867.30MHz ch:4 RFch:0 LORA[SF7 125Khz 4/5] RSSI:-95dB SNR:-3.0dB Size:23b Data:'QI0pVx2ADgABMTFQD+MBsz7x6VL877c=' +INFO: [#1D57298D] uUP CRC:OK Freq:867.50MHz ch:5 RFch:0 LORA[SF7 125Khz 4/5] RSSI:-49dB SNR:+7.5dB Size:23b Data:'QI0pVx2ADgABMTFQD+MBsz7x6VL877c=' +INFO: [up] PUSH_ACK for server router.eu.thethings.network received in 42 ms +INFO: [up] PUSH_ACK for server log.gatewaystats.org received in 27 ms + +``` + + 2. System schematic and definitions ------------------------------------ diff --git a/poly_pkt_fwd/src/gpiolib.c b/poly_pkt_fwd/src/gpiolib.c new file mode 100644 index 00000000..d149c1b0 --- /dev/null +++ b/poly_pkt_fwd/src/gpiolib.c @@ -0,0 +1,210 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "gpiolib.h" + +int gpio_direction(int gpio, int dir) +{ + int ret = 0; + char buf[50]; + sprintf(buf, "/sys/class/gpio/gpio%d/direction", gpio); + int gpiofd = open(buf, O_WRONLY); + if(gpiofd < 0) { + perror("Couldn't open IRQ file"); + ret = -1; + } + + if(dir == 2 && gpiofd){ + if (3 != write(gpiofd, "high", 3)) { + perror("Couldn't set GPIO direction to out"); + ret = -2; + } + } + + if(dir == 1 && gpiofd){ + if (3 != write(gpiofd, "out", 3)) { + perror("Couldn't set GPIO direction to out"); + ret = -3; + } + } + else if(gpiofd) { + if(2 != write(gpiofd, "in", 2)) { + perror("Couldn't set GPIO directio to in"); + ret = -4; + } + } + + close(gpiofd); + return ret; +} + +int gpio_setedge(int gpio, int rising, int falling) +{ + int ret = 0; + char buf[50]; + sprintf(buf, "/sys/class/gpio/gpio%d/edge", gpio); + int gpiofd = open(buf, O_WRONLY); + if(gpiofd < 0) { + perror("Couldn't open IRQ file"); + ret = -1; + } + + if(gpiofd && rising && falling) { + if(4 != write(gpiofd, "both", 4)) { + perror("Failed to set IRQ to both falling & rising"); + ret = -2; + } + } else { + if(rising && gpiofd) { + if(6 != write(gpiofd, "rising", 6)) { + perror("Failed to set IRQ to rising"); + ret = -2; + } + } else if(falling && gpiofd) { + if(7 != write(gpiofd, "falling", 7)) { + perror("Failed to set IRQ to falling"); + ret = -3; + } + } + } + + close(gpiofd); + + return ret; +} + +int gpio_export(int gpio) +{ + int efd; + char buf[50]; + int ret; + + /* Quick test if it has already been exported */ + sprintf(buf, "/sys/class/gpio/gpio%d/value", gpio); + efd = open(buf, O_WRONLY); + if(efd != -1) { + close(efd); + return 0; + } + + efd = open("/sys/class/gpio/export", O_WRONLY); + + if(efd != -1) { + sprintf(buf, "%d", gpio); + ret = write(efd, buf, strlen(buf)); + if(ret < 0) { + perror("Export failed"); + return -2; + } + close(efd); + } else { + // If we can't open the export file, we probably + // dont have any gpio permissions + return -1; + } + return 0; +} + +void gpio_unexport(int gpio) +{ + int gpiofd; + char buf[50]; + gpiofd = open("/sys/class/gpio/unexport", O_WRONLY); + sprintf(buf, "%d", gpio); + write(gpiofd, buf, strlen(buf)); + close(gpiofd); +} + +int gpio_getfd(int gpio) +{ + char buf[50]; + int gpiofd; + sprintf(buf, "/sys/class/gpio/gpio%d/value", gpio); + gpiofd = open(buf, O_RDWR); + if(gpiofd < 0) { + fprintf(stderr, "Failed to open gpio %d value\n", gpio); + perror("gpio failed"); + } + + return gpiofd; +} + +int gpio_read(int gpio) +{ + char in[3] = {0, 0, 0}; + char buf[50]; + int nread, gpiofd; + sprintf(buf, "/sys/class/gpio/gpio%d/value", gpio); + gpiofd = open(buf, O_RDWR); + if(gpiofd < 0) { + fprintf(stderr, "Failed to open gpio %d value\n", gpio); + perror("gpio failed"); + } + + do { + nread = read(gpiofd, in, 1); + } while (nread == 0); + if(nread == -1){ + perror("GPIO Read failed"); + return -1; + } + + close(gpiofd); + return atoi(in); +} + +int gpio_write(int gpio, int val) +{ + char buf[50]; + int ret, gpiofd; + sprintf(buf, "/sys/class/gpio/gpio%d/value", gpio); + gpiofd = open(buf, O_RDWR); + if(gpiofd > 0) { + snprintf(buf, 2, "%d", val); + ret = write(gpiofd, buf, 2); + if(ret < 0) { + perror("failed to set gpio"); + return 1; + } + + close(gpiofd); + if(ret == 2) return 0; + } + return 1; +} + + +int gpio_select(int gpio) +{ + char gpio_irq[64]; + int buf, irqfd; + fd_set fds; + FD_ZERO(&fds); + + snprintf(gpio_irq, sizeof(gpio_irq), "/sys/class/gpio/gpio%d/value", gpio); + irqfd = open(gpio_irq, O_RDONLY, S_IRUSR); + if(irqfd < 1) { + perror("Couldn't open the value file"); + return -1; + } + + // Read first since there is always an initial status + read(irqfd, &buf, sizeof(buf)); + + while(1) { + FD_SET(irqfd, &fds); + select(irqfd + 1, NULL, NULL, &fds, NULL); + if(FD_ISSET(irqfd, &fds)) + { + FD_CLR(irqfd, &fds); //Remove the filedes from set + // Clear the junk data in the IRQ file + read(irqfd, &buf, sizeof(buf)); + return 1; + } + } +} diff --git a/poly_pkt_fwd/src/poly_pkt_fwd.c b/poly_pkt_fwd/src/poly_pkt_fwd.c index 298de462..1bcab756 100644 --- a/poly_pkt_fwd/src/poly_pkt_fwd.c +++ b/poly_pkt_fwd/src/poly_pkt_fwd.c @@ -16,6 +16,9 @@ License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Ruud Vlaming + + + */ @@ -49,6 +52,8 @@ Maintainer: Ruud Vlaming #include /* gai_strerror */ #include + +#include #include #include @@ -68,6 +73,9 @@ Maintainer: Ruud Vlaming #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #define STRINGIFY(x) #x #define STR(x) STRINGIFY(x) +#ifndef MSG +#define MSG(args...) printf(args) /* message that is destined to the user */ +#endif #define TRACE() fprintf(stderr, "@ %s %d\n", __FUNCTION__, __LINE__); /* -------------------------------------------------------------------------- */ @@ -90,7 +98,7 @@ Maintainer: Ruud Vlaming #define DEFAULT_KEEPALIVE 5 /* default time interval for downstream keep-alive packet */ -#define DEFAULT_STAT 30 /* default time interval for statistics */ +#define DEFAULT_STAT 30/* default time interval for statistics */ #define PUSH_TIMEOUT_MS 100 #define PULL_TIMEOUT_MS 200 #define GPS_REF_MAX_AGE 30 /* maximum admitted delay in seconds of GPS loss before considering latest GPS sync unusable */ @@ -118,9 +126,21 @@ Maintainer: Ruud Vlaming #define STATUS_SIZE 328 #define TX_BUFF_SIZE ((540 * NB_PKT_MAX) + 30 + STATUS_SIZE) +#ifndef NOT_A_PIN +#define NOT_A_PIN 0xFF +#endif + /* -------------------------------------------------------------------------- */ /* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */ +static char *short_options = "c:l:h"; +static struct option long_options[] = { + {"config-dir", 1, 0, 'c'}, + {"logfile", 1, 0, 'l'}, + {"help", 0, 0, 'h'}, + {0, 0, 0, 0}, +}; + /* signal handling variables */ volatile bool exit_sig = false; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ volatile bool quit_sig = false; /* 1 -> application terminates without shutting down the hardware */ @@ -142,6 +162,15 @@ static int keepalive_time = DEFAULT_KEEPALIVE; /* send a PULL_DATA request every /* statistics collection configuration variables */ static unsigned stat_interval = DEFAULT_STAT; /* time interval (in sec) at which statistics are collected and displayed */ +/* LED */ +static int led_heartbeat = NOT_A_PIN;/* heartbeat LED */ +static int led_down = NOT_A_PIN; /* downlink for server */ +static int led_error = NOT_A_PIN; /* error LED */ +static int led_packet = NOT_A_PIN; /* packet led */ +static int led_pps = NOT_A_PIN; /* PPS GPS Led (linklabs board) */ +static int pin_pps = NOT_A_PIN; /* PPS GPS pin (LinkLabs board) */ +static int pin_reset = NOT_A_PIN; /* Concentrator Reset pin */ + /* gateway <-> MAC protocol variables */ static uint32_t net_mac_h; /* Most Significant Nibble, network order */ static uint32_t net_mac_l; /* Least Significant Nibble, network order */ @@ -727,7 +756,56 @@ static int parse_gateway_configuration(const char * conf_file) { stat_interval = (unsigned)json_value_get_number(val); MSG("INFO: statistics display interval is configured to %i seconds\n", stat_interval); } + + /* get hearttbeat GPIO LED (optional) */ + val = json_object_get_value(conf_obj, "led_heartbeat"); + if (val != NULL) { + led_heartbeat = (unsigned)json_value_get_number(val); + MSG("INFO: LED heartbeat is configured to GPIO%i\n", led_heartbeat); + } + + /* get downlink GPIO LED (optional) */ + val = json_object_get_value(conf_obj, "led_down"); + if (val != NULL) { + led_down = (unsigned)json_value_get_number(val); + MSG("INFO: LED downlink is configured to GPIO%i\n", led_down); + } + + /* get error GPIO LED (optional) */ + val = json_object_get_value(conf_obj, "led_error"); + if (val != NULL) { + led_error = (unsigned)json_value_get_number(val); + MSG("INFO: LED error is configured to GPIO%i\n", led_error); + } + + /* get packet GPIO LED (optional) */ + val = json_object_get_value(conf_obj, "led_packet"); + if (val != NULL) { + led_packet = (unsigned)json_value_get_number(val); + MSG("INFO: LED packet is configured to GPIO%i\n", led_packet); + } + + /* get PPS PIN Input LED (optional) */ + val = json_object_get_value(conf_obj, "pin_pps"); + if (val != NULL) { + pin_pps = (unsigned)json_value_get_number(val); + MSG("INFO: GPS PPS pin is configured to GPIO%i\n", pin_pps); + } + /* get PPS GPIO LED (optional) */ + val = json_object_get_value(conf_obj, "led_pps"); + if (val != NULL) { + led_pps = (unsigned)json_value_get_number(val); + MSG("INFO: LED pps is configured to GPIO%i\n", led_pps); + } + + /* get Reset PIN */ + val = json_object_get_value(conf_obj, "pin_reset"); + if (val != NULL) { + pin_reset = (unsigned)json_value_get_number(val); + MSG("INFO: SX1301 RESET is configured to GPIO%i\n", pin_reset); + } + /* get time-out value (in ms) for upstream datagrams (optional) */ val = json_object_get_value(conf_obj, "push_timeout_ms"); if (val != NULL) { @@ -1008,22 +1086,162 @@ double difftimespec(struct timespec end, struct timespec beginning) { return x; } + + +void log_packet( struct lgw_pkt_rx_s* p) { + + uint8_t mtype = (*p->payload) ; + + /* writing devAddr */ + printf( "INFO: [#"); + for (int j=4; j>0; j--) { + printf("%02X", p->payload[j]); + } + printf( "] "); + + // RFU & Major + //printf("%X:%X ", (mtype>>2)&0x07, (mtype)&0x03 ) ; + + // Message type (3 bits) + switch ( (mtype>>5)&0x07 ) { + case 0x00: printf("jRQ"); break; /* join request */ + case 0x01: printf("jAC"); break; /* join accept */ + case 0x02: printf("uUP"); break; /* unconfirmed up */ + case 0x03: printf("uDN"); break; /* unconfirmed down */ + case 0x04: printf("cUP"); break; /* Confirmed up */ + case 0x05: printf("cDN"); break; /* Confirmed down */ + case 0x06: printf("RFU"); break; + case 0x07: printf("Prp"); break; + } + + + /* writing RX frequency */ + printf( " CRC:"); + + /* writing CRC status */ + switch(p->status) { + case STAT_CRC_OK: printf("OK "); break; + case STAT_CRC_BAD: printf("Bad "); break; + case STAT_NO_CRC: printf("None "); break; + case STAT_UNDEFINED:printf("Undefined "); break; + default: printf("???? "); + } + + /* writing Frequency, RX modem/IF chan and RF chan */ + printf("Freq:%3.2fMHz ch:%d RFch:%u ", p->freq_hz/1000000.0f, p->if_chain, p->rf_chain); + + /* writing modulation */ + switch(p->modulation) { + case MOD_LORA:printf("LORA["); break; + case MOD_FSK: printf("FSK["); break; + default: printf("???["); + } + + if (p->modulation == MOD_LORA) { + switch (p->datarate) { + case DR_LORA_SF7: printf("SF7 "); break; + case DR_LORA_SF8: printf("SF8 "); break; + case DR_LORA_SF9: printf("SF9 "); break; + case DR_LORA_SF10:printf("SF10 "); break; + case DR_LORA_SF11:printf("SF11 "); break; + case DR_LORA_SF12:printf("SF12 "); break; + default: printf("??? "); + } + } else if (p->modulation == MOD_FSK) { + printf("FSK:%6u ", p->datarate); + } else { + printf("??? "); + } + + /* writing bandwidth */ + switch(p->bandwidth) { + case BW_500KHZ: printf("500Khz "); break; + case BW_250KHZ: printf("250Khz "); break; + case BW_125KHZ: printf("125Khz "); break; + case BW_62K5HZ: printf("62K5hz "); break; + case BW_31K2HZ: printf("31K2hz "); break; + case BW_15K6HZ: printf("15K6hz "); break; + case BW_7K8HZ: printf("7K8hz "); break; + case BW_UNDEFINED:printf("0 "); break; + default: printf("-1 "); + } + + /* writing coderate */ + switch (p->coderate) { + case CR_LORA_4_5: printf("4/5"); break; + case CR_LORA_4_6: printf("2/3"); break; + case CR_LORA_4_7: printf("4/7"); break; + case CR_LORA_4_8: printf("1/2"); break; + case CR_UNDEFINED:printf("Undefined"); break; + default: printf("???"); + } + + /* writing packet RSSI + average SNR */ + printf("] RSSI:%+.0fdB SNR:%+.1fdB ", p->rssi, p->snr); + + /* writing packet size */ + printf("Size:%db ", p->size); + + /* writing status */ + if (p->status==STAT_CRC_OK || p->status==STAT_NO_CRC ) { + char buff[341]; /* 255 bytes = 340 chars in b64 + null char */ + int j = bin_to_b64(p->payload, p->size, buff, sizeof(buff)); + if (j>0) { + printf("Data:'%s'", buff); + } else { + printf("Error Encoding data"); + } + + /* + printf(" => "); + // writing hex-encoded payload (bundled in 32-bit words) + for (int j = 0; j < p->size; ++j) { + if ((j > 0) && (j%4 == 0)) printf("-"); + printf("%02X", p->payload[j]); + } + */ + } + + /* end of line */ + printf("\n"); + + /* whole paylaod + if (p->status==STAT_CRC_OK || p->status==STAT_NO_CRC ) { + printf("HEX Payload: "); + // writing hex-encoded payload (bundled in 32-bit words) + for (int j = 0; j < p->size; ++j) { + printf(" %02X", p->payload[j]); + } + } + printf("\n"); + */ + +} + + +// Value : 1=Low Output (2=High Output) +void gpio_config(int * gpio, int value) { + if ( *gpio != NOT_A_PIN ) { + // Went fine + if ( gpio_export(*gpio) > -1 ) { + // Set value and direction + gpio_direction(*gpio,value); + } else { + MSG("INFO: can't export GPIO%d pin\n", *gpio); + // Can't export set to not a pin + *gpio = NOT_A_PIN; + } + } +} + + void usage(char *proc_name) { fprintf(stderr, "Usage: %s [-c config_dir] [-l logfile]\n", proc_name); exit(1); } -static char *short_options = "c:l:h"; -static struct option long_options[] = { - {"config-dir", 1, 0, 'c'}, - {"logfile", 1, 0, 'l'}, - {"help", 0, 0, 'h'}, - {0, 0, 0, 0}, -}; - /* -------------------------------------------------------------------------- */ /* --- MAIN FUNCTION -------------------------------------------------------- */ - int main(int argc, char *argv[]) { struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ @@ -1034,7 +1252,7 @@ int main(int argc, char *argv[]) char *global_cfg_name= "global_conf.json"; /* contain global (typ. network-wide) configuration */ char *local_cfg_name = "local_conf.json"; /* contain node specific configuration, overwrite global parameters for parameters that are defined in both */ char *debug_cfg_name = "debug_conf.json"; /* if present, all other configuration files are ignored */ - + int opt_ind = 0; char cfg_dir[PATH_MAX] = {0}; @@ -1125,7 +1343,7 @@ int main(int argc, char *argv[]) exit(1); } } - + /* display version informations */ MSG("*** Poly Packet Forwarder for Lora Gateway ***\nVersion: " VERSION_STRING "\n"); MSG("*** Lora concentrator HAL library version info ***\n%s\n***\n", lgw_version_info()); @@ -1164,6 +1382,25 @@ int main(int argc, char *argv[]) exit(EXIT_FAILURE); } + gpio_config(&led_down, 1); // 1=Low Output (2=High Output) + gpio_config(&led_heartbeat, 1); // 1=Low Output (2=High Output) + gpio_config(&led_error, 1); // 1=Low Output (2=High Output) + gpio_config(&led_packet, 1); // 1=Low Output (2=High Output) + gpio_config(&led_pps, 1); // 1=Low Output (2=High Output) + gpio_config(&pin_pps, 0); // 0=Input + gpio_config(&pin_reset, 0); // 1=Low Output (2=High Output) + + // reset the concentrator + if (pin_reset != NOT_A_PIN) { + MSG("INFO: Reseting concentrator with GPIO%d\n", pin_reset); + // Assert reset + gpio_write(pin_reset,1); + // Wait 100ms + usleep(100000); + // Free reset + gpio_write(pin_reset,0); + } + /* Start GPS a.s.a.p., to allow it to lock */ if (gps_enabled == true) { if (gps_fake_enable == false) { @@ -1490,6 +1727,12 @@ int main(int argc, char *argv[]) report_ready = true; pthread_mutex_unlock(&mx_stat_rep); } + + uint32_t trig_cnt_us; + if (lgw_get_trigcnt(&trig_cnt_us) == LGW_HAL_SUCCESS && trig_cnt_us == 0x7E000000) { + MSG("ERROR: [main] unintended SX1301 reset detected, terminating packet forwarder.\n"); + exit(EXIT_FAILURE); + } } /* wait for upstream thread to finish (1 fetch cycle max) */ @@ -1519,7 +1762,17 @@ int main(int argc, char *argv[]) } else { MSG("WARNING: failed to stop concentrator successfully\n"); } - } + } + + // Light off the LED + if (led_down!=NOT_A_PIN) { gpio_write(led_down , 0); gpio_unexport(led_down); } + if (led_heartbeat!=NOT_A_PIN){ gpio_write(led_heartbeat, 0); gpio_unexport(led_heartbeat); } + if (led_error!=NOT_A_PIN) { gpio_write(led_error , 0); gpio_unexport(led_error ); } + if (led_packet!=NOT_A_PIN) { gpio_write(led_packet , 0); gpio_unexport(led_packet ); } + if (led_pps!=NOT_A_PIN) { gpio_write(led_pps , 0); gpio_unexport(led_pps ); } + if (pin_pps!=NOT_A_PIN) { gpio_unexport(pin_pps); } + if (pin_reset!=NOT_A_PIN) { gpio_unexport(pin_reset); } + } MSG("INFO: Exiting packet forwarder program\n"); @@ -1557,6 +1810,10 @@ void thread_up(void) { uint8_t token_h; /* random token for acknowledgement matching */ uint8_t token_l; /* random token for acknowledgement matching */ + int led_heartbeat_cnt=0; + int led_error_cnt=0; + int led_packet_cnt=0; + /* ping measurement variables */ struct timespec send_time; struct timespec recv_time; @@ -1593,7 +1850,15 @@ void thread_up(void) { if (ghoststream_enabled == true) nb_pkt = ghost_get(NB_PKT_MAX-nb_pkt, &rxpkt[nb_pkt]) + nb_pkt; - //TODO this test should in fact be before the ghost packets are collected. + // Report on LED GPS PPS Signal (blink when satellites OK) + // Signal is reversed, like this when running (even with no GPS) + // the led will be on, and if PPS signal from GPS is okay the led + // will blink off for a short time + if (led_pps!=NOT_A_PIN && pin_pps!=NOT_A_PIN) { + gpio_write( led_pps, gpio_read(pin_pps) ); + } + + //TODO this test should in fact be before the ghost packets are collected. pthread_mutex_unlock(&mx_concent); if (nb_pkt == LGW_HAL_ERROR) { MSG("ERROR: [up] failed packet fetch, exiting\n"); @@ -1607,6 +1872,30 @@ void thread_up(void) { /* wait a short time if no packets, nor status report */ if ((nb_pkt == 0) && (send_report == false)) { wait_ms(FETCH_SLEEP_MS); + if (led_heartbeat != NOT_A_PIN) { + led_heartbeat_cnt++; + if (led_heartbeat_cnt == 40) { + gpio_write(led_heartbeat, 1); + } + else if (led_heartbeat_cnt >= 80) { + gpio_write(led_heartbeat, 0); + led_heartbeat_cnt=0; + } + } + + // Led time out expired + if (led_error != NOT_A_PIN) { + if (--led_error_cnt==0) { + gpio_write(led_error, 0); + } + } + if (led_packet != NOT_A_PIN) { + if (--led_packet_cnt==0) { + gpio_write(led_packet, 0); + } + } + + continue; } @@ -1642,11 +1931,17 @@ void thread_up(void) { for (i=0; i < nb_pkt; ++i) { p = &rxpkt[i]; + // Display information + log_packet(p); + /* basic packet filtering */ pthread_mutex_lock(&mx_meas_up); meas_nb_rx_rcv += 1; switch(p->status) { case STAT_CRC_OK: + // Green led + led_packet_cnt=FETCH_SLEEP_MS*10; + gpio_write(led_packet, 1); meas_nb_rx_ok += 1; if (!fwd_valid_pkt) { pthread_mutex_unlock(&mx_meas_up); @@ -1654,6 +1949,9 @@ void thread_up(void) { } break; case STAT_CRC_BAD: + // Red led + led_error_cnt=FETCH_SLEEP_MS*10; + gpio_write(led_error, 1); meas_nb_rx_bad += 1; if (!fwd_error_pkt) { pthread_mutex_unlock(&mx_meas_up); @@ -1661,6 +1959,11 @@ void thread_up(void) { } break; case STAT_NO_CRC: + // Green and Red led + led_packet_cnt=FETCH_SLEEP_MS*10; + led_error_cnt=FETCH_SLEEP_MS*10; + gpio_write(led_packet, 1); + gpio_write(led_error, 1); meas_nb_rx_nocrc += 1; if (!fwd_nocrc_pkt) { pthread_mutex_unlock(&mx_meas_up); @@ -1668,6 +1971,9 @@ void thread_up(void) { } break; default: + // Red led + led_error_cnt=FETCH_SLEEP_MS*10; + gpio_write(led_error, 1); MSG("WARNING: [up] received packet with unknown status %u (size %u, modulation %u, BW %u, DR %u, RSSI %.1f)\n", p->status, p->size, p->modulation, p->bandwidth, p->datarate, p->rssi); pthread_mutex_unlock(&mx_meas_up); continue; /* skip that packet */ @@ -2097,6 +2403,10 @@ void thread_down(void* pic) { break; } + if (led_down!=NOT_A_PIN) { + gpio_write(led_down, 1); + } + /* generate random token for request */ token_h = (uint8_t)rand(); /* random token */ token_l = (uint8_t)rand(); /* random token */ @@ -2216,9 +2526,12 @@ void thread_down(void* pic) { } else { /* out-of-sync token */ MSG("INFO: [down] for server %s, received out-of-sync ACK\n",serv_addr[ic]); } + if (led_down!=NOT_A_PIN) { + gpio_write(led_down, 0); + } + continue; } - //TODO: This might generate to much logging data. The reporting should be reevaluated and an option -q should be added. /* the datagram is a PULL_RESP */ diff --git a/readme.md b/readme.md index 88638ac6..e2c8308d 100644 --- a/readme.md +++ b/readme.md @@ -102,6 +102,10 @@ Please refer to the script header for more details. 5. Changelog ------------- +### v2.2.0 - 2018-01-02 ### + +* Added features to poly_pkt_fwd, see dedicated [read.me](https://github.com/ch2i/packet_forwarder/blob/master/poly_pkt_fwd/readme.md) + ### v2.1.0 - 2015-06-29 ### * Added helper script for concentrator reset through GPIO, needed on IoT Starter Kit (reset_pkt_fwd.sh).