Version 5.0.6; Changes to documentation, timing and to the state machine
rodzic
b5155a8817
commit
100ec650d9
23
CHANGELOG.md
23
CHANGELOG.md
|
@ -1,6 +1,6 @@
|
|||
# Single Channel LoRaWAN Gateway
|
||||
|
||||
Version 4.0.7, July 22, 2017
|
||||
Version 5.0.2, November 18, 2017
|
||||
Author: M. Westenberg (mw12554@hotmail.com)
|
||||
Copyright: M. Westenberg (mw12554@hotmail.com)
|
||||
|
||||
|
@ -16,6 +16,27 @@ Maintained by Maarten Westenberg (mw12554@hotmail.com)
|
|||
|
||||
# Release Notes
|
||||
|
||||
New Features in version 5.0.5 (Feb 2, 2018)
|
||||
- Change timer functions to now() and secons instead of millis() as the latter one overflows once
|
||||
every 50 days.
|
||||
- Add more debug information
|
||||
- Simplified and enhanced the State Machine function
|
||||
|
||||
New features in version 5.0.4 (January 1, 2018)
|
||||
- Cleanup of the State machine
|
||||
- Separate file for oLED work, support for 1.3" SH3006 chips based oLED.
|
||||
- Still not supported: Multi Frequency works, but with loss of #packages,
|
||||
and some packages are recognizeg at the wrong frequency (but since they are so close that could happen).
|
||||
- In-line documenattion cleaned up
|
||||
|
||||
New features in version 5.0.1 (November 18, 2017)
|
||||
- Changed the state machine to run in user space only
|
||||
- No Watchdog Resets anymore
|
||||
- For each SF, percentage of such packages received of total packages
|
||||
- OTAA and downlink work (again) although not always
|
||||
- Nober of packages per hour displayed in webserver
|
||||
- All Serial communication only when DUSB==1 is defined at compile time
|
||||
|
||||
New features in version 4.0.9 (August 11, 2017)
|
||||
|
||||
- This release contains updates for memory leaks in several Gateway files
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// 1-channel LoRa Gateway for ESP8266
|
||||
// Copyright (c) 2016, 2017 Maarten Westenberg version for ESP8266
|
||||
// Version 5.0.1
|
||||
// Date: 2017-11-15
|
||||
// Version 5.0.6
|
||||
// Date: 2018-02-12
|
||||
//
|
||||
// Based on work done by Thomas Telkamp for Raspberry PI 1ch gateway and many others.
|
||||
// Contibutions of Dorijan Morelj and Andreas Spies for OLED support.
|
||||
|
@ -19,7 +19,7 @@
|
|||
//
|
||||
// ----------------------------------------------------------------------------------------
|
||||
|
||||
#define VERSION "V.5.0.2.H; 171118a nOLED, 15/10"
|
||||
#define VERSION "V.5.0.6.H; 170212a"
|
||||
|
||||
// This value of DEBUG determines whether some parts of code get compiled.
|
||||
// Also this is the initial value of debug parameter.
|
||||
|
@ -33,6 +33,11 @@
|
|||
// If set to 2 it will also print interrupt messages (not recommended)
|
||||
#define DUSB 1
|
||||
|
||||
// Define whether we should do a formatting of SPIFFS when starting the gateway
|
||||
// This is usually a good idea if the webserver is interrupted halfway a writing
|
||||
// operation.
|
||||
#define SPIFF_FORMAT 0
|
||||
|
||||
// The spreading factor is the most important parameter to set for a single channel
|
||||
// gateway. It specifies the speed/datarate in which the gateway and node communicate.
|
||||
// As the name says, in principle the single channel gateway listens to one channel/frequency
|
||||
|
@ -72,7 +77,8 @@
|
|||
// If your pin definitions are different, update the loraModem.h file to reflect these settings.
|
||||
// 1: HALLARD
|
||||
// 2: COMRESULT pin out
|
||||
// 3: Other, define your own in loraModem.h
|
||||
// 3: ESP32 pin out
|
||||
// 4: Other, define your own in loraModem.h
|
||||
#define _PIN_OUT 1
|
||||
|
||||
// Gather statistics on sensor and Wifi status
|
||||
|
@ -102,8 +108,9 @@
|
|||
|
||||
// Define the name of the accesspoint if the gateway is in accesspoint mode (is
|
||||
// getting WiFi SSID and password using WiFiManager)
|
||||
// NOTE: Change the password for your OWN environment to be secure
|
||||
#define AP_NAME "ESP8266-Gway-Things4U"
|
||||
#define AP_PASSWD "MyPw01!"
|
||||
#define AP_PASSWD "ttnAutoPw"
|
||||
|
||||
|
||||
// Defines whether the gateway will also report sensor/status value on MQTT
|
||||
|
@ -120,11 +127,13 @@
|
|||
|
||||
// Will we use Mutex or not?
|
||||
// +SPI is input for SPI, SPO is output for SPI
|
||||
#define MUTEX 1
|
||||
#define MUTEX_SPI 0
|
||||
#define MUTEX_SPO 0
|
||||
// Protect the interrupt module
|
||||
#define MUTEX_INT 0
|
||||
#define MUTEX 0
|
||||
|
||||
// Define if OLED Display is connected to i2c
|
||||
// OLED==1; 0.9 Oled Screen based on SSD1306
|
||||
// OLED==2; 1"3 Oled screens for Wemos, 128x64 SH1106
|
||||
#define OLED 2 // Make define 1 on line if you have an OLED display connected
|
||||
|
||||
|
||||
// Define whether we want to manage the gateway over UDP (next to management
|
||||
// thru webinterface).
|
||||
|
@ -146,7 +155,7 @@
|
|||
#define _LOCUDPPORT 1700 // UDP port of gateway! Often 1700 or 1701 is used for upstream comms
|
||||
|
||||
// Timing
|
||||
#define _MSG_INTERVAL 15
|
||||
#define _MSG_INTERVAL 15 // Reset timer in seconds
|
||||
#define _PULL_INTERVAL 55 // PULL_DATA messages to server to get downstream in milliseconds
|
||||
#define _STAT_INTERVAL 120 // Send a 'stat' message to server
|
||||
#define _NTP_INTERVAL 3600 // How often do we want time NTP synchronization
|
||||
|
@ -163,25 +172,25 @@
|
|||
// Port is UDP port in this program
|
||||
//
|
||||
// Default for testing: Switch off
|
||||
//#define _THINGPORT 1700 // dash.westenberg.org:8057
|
||||
//#define _THINGSERVER "yourServer.com" // Server URL of the LoRa-udp.js handler
|
||||
#define _THINGPORT 57084 // dash.westenberg.org:8057
|
||||
#define _THINGSERVER "westenberg.org" // Server URL of the LoRa-udp.js handler
|
||||
|
||||
// Gateway Ident definitions
|
||||
#define _DESCRIPTION "My ESP Gateway"
|
||||
#define _EMAIL "whoami@hotmail.com"
|
||||
#define _DESCRIPTION "ESP Gateway"
|
||||
#define _EMAIL "mw12554@hotmail.com"
|
||||
#define _PLATFORM "ESP8266"
|
||||
#define _LAT 52.00
|
||||
#define _LON 5.900
|
||||
#define _ALT 00
|
||||
#define _LAT 52
|
||||
#define _LON 5.9
|
||||
#define _ALT 1
|
||||
|
||||
// ntp
|
||||
#define NTP_TIMESERVER "nl.pool.ntp.org" // Country and region specific
|
||||
#define NTP_TIMEZONES 1 // How far is our Timezone from UTC (excl daylight saving/summer time)
|
||||
#define SECS_PER_HOUR 3600
|
||||
#define SECS_IN_HOUR 3600
|
||||
#define NTP_INTR 0 // Do NTP processing with interrupts or in loop();
|
||||
|
||||
#if GATEWAYNODE==1
|
||||
#define _DEVADDR { 0x26, 0x00, 0x00 0x00 }
|
||||
#define _DEVADDR { 0x26, 0x01, 0x00, 0x00 }
|
||||
#define _APPSKEY { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
|
||||
#define _NWKSKEY { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
|
||||
#define _SENSOR_INTERVAL 300
|
||||
|
@ -194,12 +203,6 @@
|
|||
// Serial Port speed
|
||||
#define _BAUDRATE 115200 // Works for debug messages to serial momitor
|
||||
|
||||
// if OLED Display is connected to i2c
|
||||
#define OLED 0 // Make define 1 on line if you have an OLED display connected
|
||||
#define OLED_SCL 5 // GPIO5 / D1
|
||||
#define OLED_SDA 4 // GPIO4 / D2
|
||||
#define OLED_ADDR 0x3C // Default 0x3C for 0.9", for 1.3" it is 0x78
|
||||
|
||||
// Wifi definitions
|
||||
// WPA is an array with SSID and password records. Set WPA size to number of entries in array
|
||||
// When using the WiFiManager, we will overwrite the first entry with the
|
||||
|
@ -218,8 +221,8 @@ struct wpas {
|
|||
//
|
||||
wpas wpa[] = {
|
||||
{ "" , "" }, // Reserved for WiFi Manager
|
||||
{ "aap", "aapPasswd" },
|
||||
{ "ape", "apePasswd" }
|
||||
{ "aap", "noot" },
|
||||
{ "mies", "teun" }
|
||||
};
|
||||
|
||||
// For asserting and testing the following defines are used.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// 1-channel LoRa Gateway for ESP8266
|
||||
// Copyright (c) 2016, 2017 Maarten Westenberg version for ESP8266
|
||||
// Version 5.0.1
|
||||
// Date: 2017-11-15
|
||||
// Version 5.0.6
|
||||
// Date: 2018-02-12
|
||||
// Author: Maarten Westenberg (mw12554@hotmail.com)
|
||||
//
|
||||
// based on work done by Thomas Telkamp for Raspberry PI 1-ch gateway
|
||||
|
@ -62,8 +62,10 @@ extern "C" {
|
|||
#include <mutex.h> // See lib directory
|
||||
#endif
|
||||
|
||||
// Local include files
|
||||
#include "loraModem.h"
|
||||
#include "loraFiles.h"
|
||||
#include "oLED.h"
|
||||
|
||||
|
||||
#if WIFIMANAGER>0
|
||||
|
@ -83,10 +85,7 @@ extern "C" {
|
|||
#include "AES-128_V10.h"
|
||||
#endif
|
||||
|
||||
#if OLED==1
|
||||
#include "SSD1306.h"
|
||||
SSD1306 display(OLED_ADDR, OLED_SDA, OLED_SCL);// i2c ADDR & SDA, SCL on wemos
|
||||
#endif
|
||||
|
||||
|
||||
int debug=1; // Debug level! 0 is no msgs, 1 normal, 2 extensive
|
||||
|
||||
|
@ -102,14 +101,13 @@ byte currentMode = 0x81;
|
|||
//char b64[256];
|
||||
bool sx1272 = true; // Actually we use sx1276/RFM95
|
||||
|
||||
uint32_t cp_nb_rx_rcv;
|
||||
uint32_t cp_nb_rx_ok;
|
||||
uint32_t cp_nb_rx_bad;
|
||||
uint32_t cp_nb_rx_nocrc;
|
||||
uint32_t cp_nb_rx_rcv; // Number of messages received by gateway
|
||||
uint32_t cp_nb_rx_ok; // Number of messages received OK
|
||||
uint32_t cp_nb_rx_bad; // Number of messages received bad
|
||||
uint32_t cp_nb_rx_nocrc; // Number of messages without CRC
|
||||
uint32_t cp_up_pkt_fwd;
|
||||
|
||||
uint8_t MAC_array[6];
|
||||
//char MAC_char[19];
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
|
@ -138,7 +136,12 @@ IPAddress ttnServer; // IP Address of thethingsnetwork server
|
|||
IPAddress thingServer;
|
||||
|
||||
WiFiUDP Udp;
|
||||
uint32_t stattime = 0; // last time we sent a stat message to server
|
||||
|
||||
time_t startTime = 0; // The time in seconds since 1970 that the server started
|
||||
// be aware that UTP time has to succeed for meaningful values.
|
||||
// We use this variable since millis() is reset every 50 days...
|
||||
|
||||
uint32_t statTime = 0; // last time we sent a stat message to server
|
||||
uint32_t pulltime = 0; // last time we sent a pull_data request to server
|
||||
uint32_t lastTmst = 0;
|
||||
#if A_SERVER==1
|
||||
|
@ -322,7 +325,7 @@ int sendNtpRequest(IPAddress timeServerIP) {
|
|||
|
||||
if (!sendUdp( (IPAddress) timeServerIP, (int) 123, packetBuffer, NTP_PACKET_SIZE)) {
|
||||
gwayConfig.ntpErr++;
|
||||
gwayConfig.ntpErrTime = millis();
|
||||
gwayConfig.ntpErrTime = now();
|
||||
return(0);
|
||||
}
|
||||
return(1);
|
||||
|
@ -364,9 +367,9 @@ time_t getNtpTime()
|
|||
secs = packetBuffer[40] << 24;
|
||||
secs |= packetBuffer[41] << 16;
|
||||
secs |= packetBuffer[42] << 8;
|
||||
secs |= packetBuffer[43];\
|
||||
secs |= packetBuffer[43];
|
||||
// UTC is 1 TimeZone correction when no daylight saving time
|
||||
return(secs - 2208988800UL + NTP_TIMEZONES * SECS_PER_HOUR);
|
||||
return(secs - 2208988800UL + NTP_TIMEZONES * SECS_IN_HOUR);
|
||||
}
|
||||
Udp.flush();
|
||||
}
|
||||
|
@ -378,8 +381,12 @@ time_t getNtpTime()
|
|||
// If we are here, we could not read the time from internet
|
||||
// So increase the counter
|
||||
gwayConfig.ntpErr++;
|
||||
gwayConfig.ntpErrTime = millis();
|
||||
if (debug>0) Serial.println(F("getNtpTime:: read failed"));
|
||||
gwayConfig.ntpErrTime = now();
|
||||
#if DUSB>=1
|
||||
if (debug>0) {
|
||||
Serial.println(F("getNtpTime:: read failed"));
|
||||
}
|
||||
#endif
|
||||
return(0); // return 0 if unable to get the time
|
||||
}
|
||||
|
||||
|
@ -477,7 +484,7 @@ int WlanWriteWpa( char* ssid, char *pass) {
|
|||
// It is a matter of returning to the main loop() asap and make sure in next loop
|
||||
// the reconnect is done first thing.
|
||||
// Parameters:
|
||||
// int maxTtry: Number of reties we do:
|
||||
// int maxTry: Number of reties we do:
|
||||
// 0: Try forever. Which is normally what we want except for Setup maybe
|
||||
// 1: Try once and if unsuccessful return(0);
|
||||
// x: Try x times
|
||||
|
@ -539,10 +546,17 @@ int WlanConnect(int maxTry) {
|
|||
}
|
||||
|
||||
#if DUSB>=1
|
||||
if (i>0) {
|
||||
Serial.print(F("WLAN reconnected"));
|
||||
if (i==0) {
|
||||
if (debug>=3) {
|
||||
Serial.print(F("WLAN connected"));
|
||||
Serial.println();
|
||||
}
|
||||
return(1);
|
||||
}
|
||||
else {
|
||||
Serial.print(F("WLAN retry="));
|
||||
Serial.println(i);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Still not connected?
|
||||
|
@ -643,7 +657,7 @@ int readUdp(int packetSize)
|
|||
}
|
||||
#endif
|
||||
gwayConfig.ntpErr++;
|
||||
gwayConfig.ntpErrTime = millis();
|
||||
gwayConfig.ntpErrTime = now();
|
||||
return(0);
|
||||
}
|
||||
|
||||
|
@ -1093,17 +1107,13 @@ void setup() {
|
|||
delay(100);
|
||||
Serial.flush();
|
||||
delay(500);
|
||||
#if MUTEX_SPI==1
|
||||
CreateMutux(&inSPI);
|
||||
#endif
|
||||
#if MUTEX_SPO==1
|
||||
CreateMutux(&inSPO);
|
||||
#endif
|
||||
#if MUTEX_INT==1
|
||||
CreateMutux(&inIntr);
|
||||
#endif
|
||||
|
||||
if (SPIFFS.begin()) Serial.println(F("SPIFFS loaded success"));
|
||||
|
||||
#if SPIFF_FORMAT>=1
|
||||
SPIFFS.format(); // Normally disabled. Enable only when SPIFFS corrupt
|
||||
#endif
|
||||
|
||||
Serial.print(F("Assert="));
|
||||
#if defined CFG_noassert
|
||||
Serial.println(F("No Asserts"));
|
||||
|
@ -1111,14 +1121,8 @@ void setup() {
|
|||
Serial.println(F("Do Asserts"));
|
||||
#endif
|
||||
|
||||
#if OLED==1
|
||||
// Initialising the UI will init the display too.
|
||||
display.init();
|
||||
display.flipScreenVertically();
|
||||
display.setFont(ArialMT_Plain_24);
|
||||
display.setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display.drawString(0, 24, "STARTING");
|
||||
display.display();
|
||||
#if OLED>=1
|
||||
init_oLED();
|
||||
#endif
|
||||
|
||||
delay(500);
|
||||
|
@ -1227,9 +1231,14 @@ void setup() {
|
|||
#endif
|
||||
|
||||
// Set the NTP Time
|
||||
// As long as the time has not been set we try to set the time.
|
||||
#if NTP_INTR==1
|
||||
setupTime(); // Set NTP time host and interval
|
||||
#else
|
||||
// If not using the standard libraries, do a manual setting
|
||||
// of the time. This meyhod works more reliable than the
|
||||
// interrupt driven method.
|
||||
|
||||
//setTime((time_t)getNtpTime());
|
||||
while (timeStatus() == timeNotSet) {
|
||||
Serial.println(F("setupTime:: Time not set (yet)"));
|
||||
|
@ -1238,12 +1247,14 @@ void setup() {
|
|||
newTime = (time_t)getNtpTime();
|
||||
if (newTime != 0) setTime(newTime);
|
||||
}
|
||||
// When we are here we succeeded in getting the time
|
||||
startTime = now(); // Time in seconds
|
||||
Serial.print("Time: "); printTime();
|
||||
Serial.println();
|
||||
|
||||
writeGwayCfg(CONFIGFILE );
|
||||
Serial.println(F("Gateway configuration saved"));
|
||||
#endif
|
||||
#endif //NTP_INTR
|
||||
|
||||
#if A_SERVER==1
|
||||
// Setup the webserver
|
||||
|
@ -1264,6 +1275,7 @@ void setup() {
|
|||
_state = S_RX;
|
||||
rxLoraModem();
|
||||
}
|
||||
LoraUp.payLoad[0]= 0;
|
||||
LoraUp.payLength = 0; // Init the length to 0
|
||||
|
||||
// init interrupt handlers, which are shared for GPIO15 / D8,
|
||||
|
@ -1283,12 +1295,9 @@ void setup() {
|
|||
writeConfig( CONFIGFILE, &gwayConfig); // Write config
|
||||
|
||||
// activate OLED display
|
||||
#if OLED==1
|
||||
// Initialising the UI will init the display too.
|
||||
display.clear();
|
||||
display.setFont(ArialMT_Plain_24);
|
||||
display.drawString(0, 24, "READY");
|
||||
display.display();
|
||||
#if OLED>=1
|
||||
acti_oLED();
|
||||
addr_oLED();
|
||||
#endif
|
||||
|
||||
Serial.println(F("--------------------------------------"));
|
||||
|
@ -1313,32 +1322,36 @@ void setup() {
|
|||
// ----------------------------------------------------------------------------
|
||||
void loop ()
|
||||
{
|
||||
uint32_t nowSeconds;
|
||||
uint32_t uSeconds; // micro seconds
|
||||
int packetSize;
|
||||
|
||||
nowTime = micros();
|
||||
nowSeconds = (uint32_t) millis() /1000;
|
||||
uint32_t nowSeconds = now();
|
||||
|
||||
// check for event value, which means that an interrupt has arrived.
|
||||
// In this case we handle the interrupt ( e.g. message received)
|
||||
// in userspace in loop().
|
||||
//
|
||||
if (_event != 0x00) {
|
||||
while (_event != 0x00) { //
|
||||
stateMachine(); // start the state machine
|
||||
_event = 0; // reset value
|
||||
return; // Restart loop
|
||||
}
|
||||
|
||||
// After a quiet period, make sure we reinit the modem.
|
||||
// XXX Still have to measure quiet period in stat[0];
|
||||
// For the moment we use msgTime
|
||||
if ( (((nowTime - statr[0].tmst) / 1000000) > _MSG_INTERVAL ) &&
|
||||
(msgTime < statr[0].tmst)) {
|
||||
// After a quiet period, make sure we reinit the modem and state machine.
|
||||
// The interval is in seconds (about 10 seconds) as this re-init
|
||||
// is a heavy operation.
|
||||
// SO it will kick in if there are not many messages for the gatway.
|
||||
// Note: Be carefull that it does not happen too often in normal operation.
|
||||
//
|
||||
if ( ((nowSeconds - statr[0].tmst) > _MSG_INTERVAL ) &&
|
||||
(msgTime < statr[0].tmst) )
|
||||
{
|
||||
#if DUSB>=1
|
||||
Serial.print('r');
|
||||
if (debug>=1) Serial.println("REINIT");
|
||||
#endif
|
||||
initLoraModem();
|
||||
if (_cad) {
|
||||
initLoraModem(); // XXX 26/12/2017
|
||||
if (_hop) {
|
||||
_state = S_SCAN;
|
||||
hop();
|
||||
}
|
||||
else if (_cad) {
|
||||
_state = S_SCAN;
|
||||
cadScanner();
|
||||
}
|
||||
|
@ -1348,7 +1361,7 @@ void loop ()
|
|||
}
|
||||
writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) 0x00);
|
||||
writeRegister(REG_IRQ_FLAGS, 0xFF); // Reset all interrupt flags
|
||||
msgTime = nowTime;
|
||||
msgTime = nowSeconds;
|
||||
}
|
||||
|
||||
#if A_OTA==1
|
||||
|
@ -1373,7 +1386,7 @@ void loop ()
|
|||
// We will not read Udp in this loop cycle then
|
||||
if (WlanConnect(1) < 0) {
|
||||
#if DUSB>=1
|
||||
Serial.print(F("loop: ERROR reconnect WLAN"));
|
||||
Serial.println(F("loop: ERROR reconnect WLAN"));
|
||||
#endif
|
||||
yield();
|
||||
return; // Exit loop if no WLAN connected
|
||||
|
@ -1382,11 +1395,11 @@ void loop ()
|
|||
// So if we are connected
|
||||
// Receive UDP PUSH_ACK messages from server. (*2, par. 3.3)
|
||||
// This is important since the TTN broker will return confirmation
|
||||
// messages on UDP for every message sent by the gateway. So we have to consume them..
|
||||
// messages on UDP for every message sent by the gateway. So we have to consume them.
|
||||
// As we do not know when the server will respond, we test in every loop.
|
||||
//
|
||||
else {
|
||||
while( (packetSize = Udp.parsePacket()) > 0) { // Length of UDP message waiting
|
||||
while( (packetSize = Udp.parsePacket()) > 0) {
|
||||
#if DUSB>=2
|
||||
Serial.println(F("loop:: readUdp calling"));
|
||||
#endif
|
||||
|
@ -1404,51 +1417,15 @@ void loop ()
|
|||
}
|
||||
//yield();
|
||||
}
|
||||
//yield(); // XXX 26/12/2017
|
||||
}
|
||||
|
||||
yield();
|
||||
|
||||
|
||||
// The next section is emergency only. If posible we hop() in the state machine.
|
||||
// If hopping is enabled, and by lack of timer, we hop()
|
||||
// XXX Experimental, 2.5 ms between hops max
|
||||
//
|
||||
|
||||
if ((_hop) && (((long)(nowTime - hopTime)) > 7500)) {
|
||||
|
||||
if ((_state == S_SCAN) && (sf==SF12)) {
|
||||
#if DUSB>=1
|
||||
if (debug>=1) Serial.println(F("loop:: hop"));
|
||||
#endif
|
||||
hop();
|
||||
}
|
||||
|
||||
// XXX section below does not work without further work. It is the section with the MOST
|
||||
// influence on the HOP mode of operation (which is somewhat unexpected)
|
||||
// If we keep staying in another state, reset
|
||||
else if (((long)(nowTime - hopTime)) > 100000) {
|
||||
|
||||
_state= S_RX;
|
||||
rxLoraModem();
|
||||
|
||||
hop();
|
||||
|
||||
if (_cad) {
|
||||
_state= S_SCAN;
|
||||
cadScanner();
|
||||
}
|
||||
}
|
||||
else if (debug>=3) { Serial.print(F(" state=")); Serial.println(_state); }
|
||||
inHop = false; // Reset re-entrane protection of HOP
|
||||
yield();
|
||||
}
|
||||
|
||||
|
||||
yield(); // XXX 26/12/2017
|
||||
|
||||
// stat PUSH_DATA message (*2, par. 4)
|
||||
//
|
||||
|
||||
if ((nowSeconds - stattime) >= _STAT_INTERVAL) { // Wake up every xx seconds
|
||||
if ((nowSeconds - statTime) >= _STAT_INTERVAL) { // Wake up every xx seconds
|
||||
#if DUSB>=1
|
||||
if (debug>=2) {
|
||||
Serial.print(F("STAT <"));
|
||||
|
@ -1481,7 +1458,7 @@ void loop ()
|
|||
}
|
||||
}
|
||||
#endif
|
||||
stattime = nowSeconds;
|
||||
statTime = nowSeconds;
|
||||
}
|
||||
|
||||
yield();
|
||||
|
@ -1489,7 +1466,7 @@ void loop ()
|
|||
|
||||
// send PULL_DATA message (*2, par. 4)
|
||||
//
|
||||
nowSeconds = (uint32_t) millis() /1000;
|
||||
nowSeconds = now();
|
||||
if ((nowSeconds - pulltime) >= _PULL_INTERVAL) { // Wake up every xx seconds
|
||||
#if DUSB>=1
|
||||
if (debug>=1) {
|
||||
|
@ -1525,8 +1502,8 @@ void loop ()
|
|||
#if NTP_INTR==0
|
||||
// Set the time in a manual way. Do not use setSyncProvider
|
||||
// as this function may collide with SPI and other interrupts
|
||||
yield();
|
||||
nowSeconds = (uint32_t) millis() /1000;
|
||||
yield(); // 26/12/2017
|
||||
nowSeconds = now();
|
||||
if (nowSeconds - ntptimer >= _NTP_INTERVAL) {
|
||||
yield();
|
||||
time_t newTime;
|
||||
|
@ -1535,4 +1512,6 @@ void loop ()
|
|||
ntptimer = nowSeconds;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
}//loop
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// 1-channel LoRa Gateway for ESP8266
|
||||
// Copyright (c) 2016, 2017 Maarten Westenberg
|
||||
// Version 5.0.1
|
||||
// Date: 2017-11-15
|
||||
// Version 5.0.6
|
||||
// Date: 2018-02-12
|
||||
//
|
||||
// Based on work done by Thomas Telkamp for Raspberry PI 1ch gateway
|
||||
// and many others.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// 1-channel LoRa Gateway for ESP8266
|
||||
// Copyright (c) 2016, 2017 Maarten Westenberg version for ESP8266
|
||||
// Version 5.0.1
|
||||
// Date: 2017-11-15
|
||||
// Version 5.0.6
|
||||
// Date: 2018-02-12
|
||||
//
|
||||
// based on work done by Thomas Telkamp for Raspberry PI 1ch gateway
|
||||
// and many others.
|
||||
|
@ -35,13 +35,14 @@
|
|||
// ----------------------------------------------------------------------------
|
||||
int readConfig(const char *fn, struct espGwayConfig *c) {
|
||||
|
||||
Serial.println(F("readConfig:: Starting"));
|
||||
Serial.print(F("readConfig:: Starting "));
|
||||
|
||||
if (!SPIFFS.exists(fn)) {
|
||||
Serial.print(F("ERROR:: readConfig, file does not exist "));
|
||||
Serial.println(fn);
|
||||
return(-1);
|
||||
}
|
||||
|
||||
File f = SPIFFS.open(fn, "r");
|
||||
if (!f) {
|
||||
Serial.println(F("ERROR:: SPIFFS open failed"));
|
||||
|
@ -50,6 +51,12 @@ int readConfig(const char *fn, struct espGwayConfig *c) {
|
|||
|
||||
while (f.available()) {
|
||||
|
||||
#if DUSB>=1
|
||||
if (debug>=0) {
|
||||
Serial.print('.');
|
||||
}
|
||||
#endif
|
||||
|
||||
String id =f.readStringUntil('=');
|
||||
String val=f.readStringUntil('\n');
|
||||
|
||||
|
@ -127,6 +134,12 @@ int readConfig(const char *fn, struct espGwayConfig *c) {
|
|||
}
|
||||
}
|
||||
f.close();
|
||||
#if DUSB>=1
|
||||
if (debug>=0) {
|
||||
Serial.println('#');
|
||||
}
|
||||
#endif
|
||||
Serial.println();
|
||||
return(1);
|
||||
}
|
||||
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,80 @@
|
|||
// 1-channel LoRa Gateway for ESP8266
|
||||
// Copyright (c) 2016, 2017 Maarten Westenberg version for ESP8266
|
||||
// Version 5.0.6
|
||||
// Date: 2018-02-12
|
||||
//
|
||||
// based on work done by Thomas Telkamp for Raspberry PI 1ch gateway
|
||||
// and many others.
|
||||
//
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the MIT License
|
||||
// which accompanies this distribution, and is available at
|
||||
// https://opensource.org/licenses/mit-license.php
|
||||
//
|
||||
// Author: Maarten Westenberg (mw12554@hotmail.com)
|
||||
//
|
||||
// This file contains the state machine code enabling to receive
|
||||
// and transmit packages/messages.
|
||||
// ========================================================================================
|
||||
//
|
||||
|
||||
#if OLED>=1
|
||||
|
||||
|
||||
|
||||
void init_oLED()
|
||||
{
|
||||
// Initialising the UI will init the display too.
|
||||
display.init();
|
||||
display.flipScreenVertically();
|
||||
display.setFont(ArialMT_Plain_24);
|
||||
display.setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display.drawString(0, 24, "STARTING");
|
||||
display.display();
|
||||
}
|
||||
|
||||
// Activate the OLED
|
||||
//
|
||||
void acti_oLED()
|
||||
{
|
||||
// Initialising the UI will init the display too.
|
||||
display.clear();
|
||||
#if OLED==1
|
||||
display.setFont(ArialMT_Plain_24);
|
||||
display.drawString(0, 24, "READY");
|
||||
#elif OLED==2
|
||||
display.setFont(ArialMT_Plain_16);
|
||||
display.drawString(0, 24, "READY");
|
||||
#endif
|
||||
|
||||
display.display();
|
||||
}
|
||||
|
||||
// Print a message on the OLED.
|
||||
// Note: The whole message must fit in the buffer
|
||||
//
|
||||
void msg_oLED(String tim, String sf) {
|
||||
display.clear();
|
||||
display.setFont(ArialMT_Plain_16);
|
||||
display.setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
display.drawString(0, 48, "LEN: " );
|
||||
// display.drawString(40, 48, String((int)messageLength) );
|
||||
display.display();
|
||||
yield();
|
||||
}
|
||||
|
||||
|
||||
// Print the OLED address in use
|
||||
//
|
||||
void addr_oLED()
|
||||
{
|
||||
|
||||
Serial.print(F("OLED_ADDR=0x"));
|
||||
Serial.println(OLED_ADDR, HEX);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif
|
|
@ -1,7 +1,7 @@
|
|||
// 1-channel LoRa Gateway for ESP8266
|
||||
// Copyright (c) 2016, 2017 Maarten Westenberg version for ESP8266
|
||||
// Version 5.0.1
|
||||
// Date: 2017-11-15
|
||||
// Version 5.0.6
|
||||
// Date: 2018-02-12
|
||||
//
|
||||
//
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// 1-channel LoRa Gateway for ESP8266
|
||||
// Copyright (c) 2016, 2017 Maarten Westenberg
|
||||
// Verison 5.0.1
|
||||
// Date: 2017-11-15
|
||||
// Verison 5.0.6
|
||||
// Date: 2018-02-12
|
||||
//
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the MIT License
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// 1-channel LoRa Gateway for ESP8266
|
||||
// Copyright (c) 2016, 2017 Maarten Westenberg
|
||||
// Verison 5.0.1
|
||||
// Date: 2017-11-15
|
||||
// Verison 5.0.6
|
||||
// Date: 2018-02-12
|
||||
//
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the MIT License
|
||||
|
@ -471,8 +471,6 @@ int sensorPacket() {
|
|||
//
|
||||
if (( frameCount % 10)==0) writeGwayCfg(CONFIGFILE);
|
||||
|
||||
//yield(); // XXX Can we remove this here?
|
||||
|
||||
if (buff_index > 512) {
|
||||
if (debug>0) Serial.println(F("sensorPacket:: ERROR buffer size too large"));
|
||||
return(-1);
|
||||
|
|
|
@ -0,0 +1,722 @@
|
|||
// 1-channel LoRa Gateway for ESP8266
|
||||
// Copyright (c) 2016, 2017 Maarten Westenberg version for ESP8266
|
||||
// Version 5.0.6
|
||||
// Date: 2018-02-12
|
||||
//
|
||||
// based on work done by Thomas Telkamp for Raspberry PI 1ch gateway
|
||||
// and many others.
|
||||
//
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the MIT License
|
||||
// which accompanies this distribution, and is available at
|
||||
// https://opensource.org/licenses/mit-license.php
|
||||
//
|
||||
// Author: Maarten Westenberg (mw12554@hotmail.com)
|
||||
//
|
||||
// This file contains the state machine code enabling to receive
|
||||
// and transmit packages/messages.
|
||||
// ========================================================================================
|
||||
//
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// stateMachine handler of the state machine.
|
||||
// We use ONE state machine for all kind of interrupts. This assures that we take
|
||||
// the correct action upon receiving an interrupt.
|
||||
//
|
||||
// _event is the software interrupt: If set this function is executed from loop(),
|
||||
// the function should itself take care of setting or resetting the variable.
|
||||
//
|
||||
// STATE MACHINE
|
||||
// The program uses the following state machine (in _state), all states
|
||||
// are done in interrupt routine, only the follow-up of S-RXDONE is done
|
||||
// in the main loop() program. This is because otherwise the interrupt processing
|
||||
// would take too long to finish
|
||||
//
|
||||
// So _state has one of the following state values:
|
||||
//
|
||||
// S-INIT=0, The commands in this state are executed only once
|
||||
// - Goto S_SCAN
|
||||
//
|
||||
// S-SCAN, CadScanner() part
|
||||
// - Upon CDDECT (int1) goto S_RX,
|
||||
// - upon CDDONE (int0) goto S_CAD, walk through all SF until CDDETD
|
||||
// - Else stay in SCAN state
|
||||
//
|
||||
// S-CAD,
|
||||
// - Upon CDDECT (int1) goto S_RX,
|
||||
// - Upon CDDONE (int0) goto S_SCAN, start with SF7 recognition again
|
||||
//
|
||||
// S-RX, Received CDDECT so message detected, RX cycle started.
|
||||
// - Upon RXDONE (int0) package read. If read ok continue to read message
|
||||
// - upon RXTOUT (int1) error, goto S_SCAN
|
||||
//
|
||||
// S-TX Transmitting a message
|
||||
// - Upon TXDONE goto S_SCAN
|
||||
//
|
||||
// S-TXDONE Transmission complete by loop() now again in interrupt
|
||||
// - Set the Mask
|
||||
// - reset the Flags
|
||||
// - Goto either SCAN or RX
|
||||
//
|
||||
// This interrupt routine has been kept as simple and short as possible.
|
||||
// If we receive an interrupt that does not below to a _state then print error.
|
||||
//
|
||||
// NOTE: We may clear the interrupt but leave the flag for the moment.
|
||||
// The eventHandler should take care of repairing flags between interrupts.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void stateMachine()
|
||||
{
|
||||
// Determine what interrupt flags are set
|
||||
//
|
||||
uint8_t flags = readRegister(REG_IRQ_FLAGS);
|
||||
uint8_t mask = readRegister(REG_IRQ_FLAGS_MASK);
|
||||
uint8_t intr = flags & ( ~ mask ); // Only react on non masked interrupts
|
||||
uint8_t rssi;
|
||||
|
||||
// If there is NO interrupt and if _hop we wait until this is one
|
||||
// or the wait time is over.
|
||||
// That means if hop we will ONLY execute the state machine below
|
||||
// when having an interrupt value and therefore a _state
|
||||
//
|
||||
if (intr == 0x00)
|
||||
{
|
||||
// If we hop we have to make sure that we allow enought time to detect
|
||||
// CDDONE or CDECT. But if we do not receive interrupts, we have to schedule
|
||||
// another hop after EVENT_WAIT microseconds.
|
||||
// The process is such that we scan on SF7 (the shortest) preamble and if
|
||||
// nothing detected within a scan, we switch to another frequency.
|
||||
//
|
||||
if (_hop) {
|
||||
|
||||
// Reset the IRQ registers. We clear the flag to accept all interrupts
|
||||
// and we clear all interrupts.
|
||||
writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) 0x00);
|
||||
writeRegister(REG_IRQ_FLAGS, (uint8_t) 0xFF);
|
||||
|
||||
while (_event==0)
|
||||
{
|
||||
hop(); // next frequency, set sf to SF7
|
||||
cadScanner(); // Reset to SF7
|
||||
|
||||
// Wait for first CDDONE or CDETD interrupt to come in
|
||||
// This is tricky for hopping as hopping is NOT interrupt driven.
|
||||
// XXX All such timers are in seconds, or micros is used for real-time
|
||||
|
||||
if ( (( micros() - hopTime ) > _STAT_INTERVAL ) ||
|
||||
(( micros() - hopTime ) > _PULL_INTERVAL ) )
|
||||
{
|
||||
_event=0;
|
||||
return;
|
||||
}
|
||||
yield(); // XXX 03/01/2018
|
||||
delayMicroseconds(300); // Allow CDDETD be noticed after CDDONE. XXX 150
|
||||
intr = readRegister(REG_IRQ_FLAGS) | intr;
|
||||
if (intr!=0) _event=1;
|
||||
}
|
||||
|
||||
// We received a real interrupt, so do nothing with either _event
|
||||
// or intr and let handle by state machine
|
||||
|
||||
_state=S_CAD;
|
||||
#if DUSB>=1
|
||||
if (debug>=1) {
|
||||
Serial.print("EVENT=0x");
|
||||
Serial.print(intr,HEX);
|
||||
Serial.print(F(", F="));
|
||||
Serial.print(ifreq);
|
||||
Serial.print(F(", SF="));
|
||||
Serial.print(sf);
|
||||
Serial.print(F(", E="));
|
||||
Serial.print(_event);
|
||||
Serial.print(F(", S="));
|
||||
Serial.print(_state);
|
||||
Serial.print(F(", t="));
|
||||
Serial.print( micros() - hopTime );
|
||||
Serial.println();
|
||||
}
|
||||
#endif
|
||||
_event=0; // If we received an interrupt, do the state machine below.
|
||||
|
||||
}// hop
|
||||
|
||||
// If not hopping make sure to return without doing anything
|
||||
// cause we only act on interrupts in this mode ((_event!=0) && (intr!=0))
|
||||
//
|
||||
else {
|
||||
_event=0;
|
||||
//return; // XXX Does this work as all are Freq 1 message when hopping
|
||||
}
|
||||
}// intr==0
|
||||
|
||||
// This is the actual state machine of the gateway
|
||||
// and its next actions are depending on the state we are in.
|
||||
// For hop situations we do not get interrupts, so we have to
|
||||
// simulate and generate events ourselves.
|
||||
//
|
||||
switch (_state)
|
||||
{
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// If the state is init, we are starting up.
|
||||
// The initLoraModem() function is already called in setup();
|
||||
//
|
||||
case S_INIT:
|
||||
#if DUSB>=2
|
||||
if (debug >= 1) {
|
||||
Serial.println(F("S_INIT"));
|
||||
}
|
||||
#endif
|
||||
// new state, needed to startup the radio (to S_SCAN)
|
||||
writeRegister(REG_IRQ_FLAGS, 0xFF ); // Clear ALL interrupts
|
||||
_event=0;
|
||||
break;
|
||||
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// In S_SCAN we measure a high RSSI this means that there (probably) is a message
|
||||
// coming in at that freq. But not necessarily on the current SF.
|
||||
// If so find the right SF with CDDETD.
|
||||
//
|
||||
case S_SCAN:
|
||||
//
|
||||
// Intr==IRQ_LORA_CDDETD_MASK
|
||||
// We detected a message on this frequency and SF when scanning
|
||||
// We clear both CDDETD and swich to reading state to read the message
|
||||
//
|
||||
if (intr & IRQ_LORA_CDDETD_MASK) {
|
||||
|
||||
_state = S_RX; // Set state to receiving
|
||||
opmode(OPMODE_RX_SINGLE); // set reg 0x01 to 0x06
|
||||
|
||||
// Set RXDONE interrupt to dio0, RXTOUT to dio1
|
||||
writeRegister(REG_DIO_MAPPING_1, (
|
||||
MAP_DIO0_LORA_RXDONE |
|
||||
MAP_DIO1_LORA_RXTOUT |
|
||||
MAP_DIO2_LORA_NOP |
|
||||
MAP_DIO3_LORA_CRC ));
|
||||
|
||||
// Since new state is S_RX, accept no interrupts except RXDONE or RXTOUT
|
||||
writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) ~(
|
||||
IRQ_LORA_RXDONE_MASK |
|
||||
IRQ_LORA_RXTOUT_MASK |
|
||||
IRQ_LORA_HEADER_MASK |
|
||||
IRQ_LORA_CRCERR_MASK));
|
||||
|
||||
delayMicroseconds( RSSI_WAIT ); // Wait some microseconds less
|
||||
// Starting with version 5.0.1 the waittime is dependent on the SF
|
||||
// So for SF12 we wait longer (2^7 == 128 uSec) and for SF7 4 uSec.
|
||||
//delayMicroseconds( (0x01 << ((uint8_t)sf - 5 )) );
|
||||
rssi = readRegister(REG_RSSI); // Read the RSSI
|
||||
_rssi = rssi; // Read the RSSI in the state variable
|
||||
|
||||
writeRegister(REG_IRQ_FLAGS, 0xFF ); // reset all interrupt flags
|
||||
_event = 0; // Make 0, as soon aswe have an interrupt
|
||||
#if DUSB>=1
|
||||
if (debug>=2) {
|
||||
Serial.println(F("SCAN:: CDDETD"));
|
||||
}
|
||||
#endif
|
||||
detTime = micros();
|
||||
}//if
|
||||
|
||||
// CDDONE
|
||||
// We received a CDDONE int telling us that we received a message on this
|
||||
// frequency and possibly on one of its SF.
|
||||
// If so, we switch to CAD state where we will wait for CDDETD event.
|
||||
//
|
||||
else if (intr & IRQ_LORA_CDDONE_MASK) {
|
||||
|
||||
opmode(OPMODE_CAD);
|
||||
rssi = readRegister(REG_RSSI); // Read the RSSI
|
||||
|
||||
// We choose the generic RSSI as a sorting mechanism for packages/messages
|
||||
// The pRSSI (package RSSI) is calculated upon successful reception of message
|
||||
// So we expect that this value makes little sense for the moment with CDDONE.
|
||||
// Set the rssi as low as the noise floor. Lower values are not recognized then.
|
||||
// Every cycle starts with ifreq==0 and sf=SF7
|
||||
//
|
||||
if ( rssi > RSSI_LIMIT ) // Is set to 35
|
||||
{
|
||||
#if DUSB>=1
|
||||
if (debug>=2) {
|
||||
Serial.println(F("S_SCAN:: -> CAD"));
|
||||
}
|
||||
#endif
|
||||
_state = S_CAD; // promote next level
|
||||
_event=0; // next CDDONE by interrupt XXXXX
|
||||
}
|
||||
|
||||
// If the RSSI is not big enough we skip the CDDONE
|
||||
// and go back to scanning
|
||||
else {
|
||||
#if DUSB>=1
|
||||
if (debug>=2) {
|
||||
Serial.print("S_SCAN:: rssi=");
|
||||
Serial.println(rssi);
|
||||
}
|
||||
#endif
|
||||
_state = S_SCAN;
|
||||
_event=1; // loop() scan until CDDONE
|
||||
}
|
||||
|
||||
// Clear the CADDONE flag
|
||||
writeRegister(REG_IRQ_FLAGS, 0xFF);
|
||||
|
||||
}//SCAN CDDONE
|
||||
|
||||
// So if we are here then we are in S_SCAN and the interrupt is not
|
||||
// CDDECT or CDDONE. it is probably soft interrupt _event==1
|
||||
// So if _hop we change the frequency and restart the
|
||||
// interrupt in order to check for CDONE on other frequencies
|
||||
// if _hop we start at the next frequency, hop () sets the sf to SF7.
|
||||
// If we are at the end of all frequencies, reset frequencies and sf
|
||||
// and go to S_SCAN state.
|
||||
//
|
||||
// Note:: We should make sure that all frequencies are scanned in a row
|
||||
// and when we switch to ifreq==0 we should stop for a while
|
||||
// to allow system processing.
|
||||
// We should make sure that we enable webserver etc every once in a while.
|
||||
// We do this by changing _event to 1 in loop() only for _hop and
|
||||
// use _event=0 for non hop.
|
||||
//
|
||||
else if (intr == 0x00)
|
||||
{
|
||||
//_state = S_SCAN; // Do this state again but now for other freq.
|
||||
if (! _hop) _event = 0; // XXX 26/12/2017 !!! NEED
|
||||
}
|
||||
|
||||
// Unkown Interrupt, so we have an error
|
||||
//
|
||||
else {
|
||||
#if DUSB>=1
|
||||
Serial.print(F("SCAN unknown intr="));
|
||||
Serial.println(intr,HEX);
|
||||
#endif
|
||||
_state=S_SCAN;
|
||||
writeRegister(REG_IRQ_FLAGS, 0xFF);
|
||||
}
|
||||
|
||||
break; // S_SCAN
|
||||
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// S_CAD: In CAD mode we scan every SF for high RSSI until we have a DETECT.
|
||||
// Reason is the we received a CADDONE interrupt so we know a message is received
|
||||
// on the frequency but may be on another SF.
|
||||
//
|
||||
// If message is of the right frequency and SF, IRQ_LORA_CDDETD_MSAK interrupt
|
||||
// is raised, indicating that we can start beging reading the message from SPI.
|
||||
//
|
||||
// DIO0 interrupt IRQ_LORA_CDDONE_MASK in state S_CAD==2 means that we might have
|
||||
// a lock on the Freq but not the right SF. So we increase the SF
|
||||
//
|
||||
case S_CAD:
|
||||
|
||||
// Intr=IRQ_LORA_CDDETD_MASK
|
||||
// We have to set the sf based on a strong RSSI for this channel
|
||||
//
|
||||
if (intr & IRQ_LORA_CDDETD_MASK) {
|
||||
|
||||
// Set RXDONE interrupt to dio0, RXTOUT to dio1
|
||||
writeRegister(REG_DIO_MAPPING_1, (
|
||||
MAP_DIO0_LORA_RXDONE |
|
||||
MAP_DIO1_LORA_RXTOUT |
|
||||
MAP_DIO2_LORA_NOP |
|
||||
MAP_DIO3_LORA_CRC ));
|
||||
|
||||
// Accept no interrupts except RXDONE or RXTOUT
|
||||
writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) ~(
|
||||
IRQ_LORA_RXDONE_MASK |
|
||||
IRQ_LORA_RXTOUT_MASK |
|
||||
IRQ_LORA_HEADER_MASK |
|
||||
IRQ_LORA_CRCERR_MASK ));
|
||||
|
||||
_state = S_RX; // Set state to start receiving
|
||||
opmode(OPMODE_RX_SINGLE); // set reg 0x01 to 0x06, initiate READ
|
||||
|
||||
delayMicroseconds( RSSI_WAIT ); // Wait some microseconds less
|
||||
//delayMicroseconds( (0x01 << ((uint8_t)sf - 5 )) );
|
||||
rssi = readRegister(REG_RSSI); // Read the RSSI
|
||||
_rssi = rssi; // Read the RSSI in the state variable
|
||||
|
||||
//writeRegister(REG_IRQ_FLAGS, IRQ_LORA_CDDETD_MASK | IRQ_LORA_RXDONE_MASK);
|
||||
writeRegister(REG_IRQ_FLAGS, 0xFF ); // reset all CAD Detect interrupt flags
|
||||
|
||||
if (_hop) {
|
||||
_event=0; // if CDECT, state=S_RX so we wait for intr
|
||||
#if DUSB>=1
|
||||
if (debug>=1) {
|
||||
Serial.print(F("S_CAD:: hop CDECT freq="));
|
||||
Serial.print(ifreq);
|
||||
Serial.print(F(", sf="));
|
||||
Serial.println(sf);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else{
|
||||
_event=1; // XXX was 0;
|
||||
}
|
||||
#if DUSB>=1
|
||||
if (debug>=2) {
|
||||
Serial.println(F("CAD:: CDDETD"));
|
||||
}
|
||||
#endif
|
||||
detTime=micros();
|
||||
}// CDDETD
|
||||
|
||||
// Intr == CADDONE
|
||||
// So we scan this SF and if not high enough ... next
|
||||
//
|
||||
else if (intr & IRQ_LORA_CDDONE_MASK) {
|
||||
// If this is not SF12, increment the SF and try again
|
||||
// We expect on other SF get CDDETD
|
||||
//
|
||||
if (((uint8_t)sf) < SF12) {
|
||||
sf = (sf_t)((uint8_t)sf+1); // Increment sf
|
||||
setRate(sf, 0x04); // Set SF with CRC==on
|
||||
|
||||
opmode(OPMODE_CAD); // Scanning mode
|
||||
|
||||
delayMicroseconds(RSSI_WAIT);
|
||||
rssi = readRegister(REG_RSSI); // Read the RSSI
|
||||
|
||||
// reset interrupt flags for CAD Done
|
||||
writeRegister(REG_IRQ_FLAGS, IRQ_LORA_CDDONE_MASK | IRQ_LORA_CDDETD_MASK);
|
||||
//writeRegister(REG_IRQ_FLAGS, 0xFF ); // This will prevent the CDDETD from being read
|
||||
_event=0; // XXXXX 171215
|
||||
#if DUSB>=1
|
||||
if (debug>=2) {
|
||||
Serial.print(F("S_CAD:: CDONE, SF="));
|
||||
Serial.println(sf);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// If we reach SF12, we should go back to SCAN state
|
||||
//
|
||||
else {
|
||||
_state = S_SCAN; // As soon as we reach SF12 do something
|
||||
cadScanner(); // Which will reset SF to SF7
|
||||
|
||||
//writeRegister(REG_IRQ_FLAGS, IRQ_LORA_CDDONE_MASK);
|
||||
writeRegister(REG_IRQ_FLAGS, 0xFF );
|
||||
_event=1; // reset soft intr, to state machine again
|
||||
#if DUSB>=1
|
||||
if (debug>=2) {
|
||||
Serial.print(F("CAD->SCAN:0x"));
|
||||
Serial.print(ifreq);
|
||||
Serial.print(F(":SF"));
|
||||
Serial.print(sf);
|
||||
Serial.println();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} //CADDONE
|
||||
|
||||
// if this interrupt is not CDECT or CDDONE then probably is 0x00
|
||||
// This means _event was set but there was no real interrupt (yet).
|
||||
// So we clear _event and wait for next (soft) interrupt.
|
||||
// We stay in the CAD state because CDDONE means something is
|
||||
// coming on this frequency so we wait on CDECT.
|
||||
//
|
||||
else if (intr == 0x00) {
|
||||
_event=0; // Stay in CAD _state until real interrupt
|
||||
}
|
||||
|
||||
// else we do not recognize the interrupt. We print an error
|
||||
// and restart scanning. If hop we even start at ifreq==1
|
||||
//
|
||||
else {
|
||||
#if DUSB>=1
|
||||
if (debug>=0) {
|
||||
Serial.print(F("CAD: Unknown interrupt="));
|
||||
Serial.println(intr);
|
||||
}
|
||||
#endif
|
||||
_state = S_SCAN;
|
||||
cadScanner(); // Scan and set SF7
|
||||
writeRegister(REG_IRQ_FLAGS, (uint8_t) 0xFF); // Reset all interrupts
|
||||
_event=0;
|
||||
}
|
||||
break; //S_CAD
|
||||
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// If we receive an interrupt on dio0 state==S_RX
|
||||
// it should be a RxDone interrupt
|
||||
// So we should handle the received message
|
||||
//
|
||||
case S_RX:
|
||||
|
||||
if (intr & IRQ_LORA_RXDONE_MASK) {
|
||||
|
||||
// We have to check for CRC error which will be visible AFTER RXDONE is set.
|
||||
// CRC errors might indicate tha the reception is not OK.
|
||||
// Could be CRC error or message too large.
|
||||
// CRC error checking requires DIO3
|
||||
//
|
||||
if (intr & IRQ_LORA_CRCERR_MASK) {
|
||||
#if DUSB>=1
|
||||
if ((debug>=1)&&(intr & IRQ_LORA_CRCERR_MASK)) Serial.println(F("CRC err"));
|
||||
#endif
|
||||
if (_cad) {
|
||||
_state = S_SCAN;
|
||||
cadScanner();
|
||||
}
|
||||
else {
|
||||
_state = S_RX;
|
||||
rxLoraModem();
|
||||
}
|
||||
|
||||
writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) 0x00); // Reset the interrupt mask
|
||||
// Reset interrupts
|
||||
writeRegister(REG_IRQ_FLAGS, (uint8_t)(
|
||||
IRQ_LORA_RXDONE_MASK |
|
||||
IRQ_LORA_RXTOUT_MASK |
|
||||
IRQ_LORA_HEADER_MASK |
|
||||
IRQ_LORA_CRCERR_MASK ));
|
||||
|
||||
_event=0;
|
||||
break;
|
||||
}
|
||||
unsigned long ffTime = micros();
|
||||
// There should not be an error in the message
|
||||
//
|
||||
LoraUp.payLoad[0]= 0x00;
|
||||
if((LoraUp.payLength = receivePkt(LoraUp.payLoad)) <= 0) {
|
||||
#if DUSB>=1
|
||||
if (debug>=0) {
|
||||
Serial.println(F("sMachine:: Error S-RX"));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#if DUSB>=1
|
||||
if (debug>=1) {
|
||||
Serial.print(F("RXDONE="));
|
||||
Serial.println(ffTime - detTime);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Do all register processing in this section
|
||||
uint8_t value = readRegister(REG_PKT_SNR_VALUE); // 0x19;
|
||||
if ( value & 0x80 ) { // The SNR sign bit is 1
|
||||
|
||||
value = ( ( ~value + 1 ) & 0xFF ) >> 2; // Invert and divide by 4
|
||||
LoraUp.snr = -value;
|
||||
}
|
||||
else {
|
||||
// Divide by 4
|
||||
LoraUp.snr = ( value & 0xFF ) >> 2;
|
||||
}
|
||||
|
||||
LoraUp.prssi = readRegister(REG_PKT_RSSI); // read register 0x1A, packet rssi
|
||||
|
||||
// Correction of RSSI value based on chip used.
|
||||
if (sx1272) { // Is it a sx1272 radio?
|
||||
LoraUp.rssicorr = 139;
|
||||
} else { // Probably SX1276 or RFM95
|
||||
LoraUp.rssicorr = 157;
|
||||
}
|
||||
|
||||
LoraUp.sf = readRegister(REG_MODEM_CONFIG2) >> 4;
|
||||
|
||||
// If read was successful, read the package from the LoRa bus
|
||||
//
|
||||
if (receivePacket() <= 0) { // read is not successful
|
||||
#if DUSB>=1
|
||||
Serial.println(F("sMach:: Error receivePacket"));
|
||||
#endif
|
||||
}
|
||||
|
||||
// Set the modem to receiving BEFORE going back to user space.
|
||||
//
|
||||
if (_cad) {
|
||||
_state = S_SCAN;
|
||||
cadScanner();
|
||||
}
|
||||
else {
|
||||
_state = S_RX;
|
||||
rxLoraModem();
|
||||
}
|
||||
|
||||
writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) 0x00);
|
||||
writeRegister(REG_IRQ_FLAGS, (uint8_t) 0xFF); // Reset the interrupt mask
|
||||
_event=0;
|
||||
}
|
||||
|
||||
// RX TIMEOUT: We did receive message receive timeout
|
||||
//
|
||||
else if (intr & IRQ_LORA_RXTOUT_MASK) {
|
||||
|
||||
// Make sure we reset all interrupts//
|
||||
writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) 0x00 );
|
||||
writeRegister(REG_IRQ_FLAGS, (uint8_t) 0xFF); // reset all interrupts
|
||||
|
||||
// For the modem in cad state we reset to SF7
|
||||
// If a timeout occurs here we reset the cadscanner
|
||||
//
|
||||
if (_cad) { // XXX 01/01/2018
|
||||
// Set the state to CAD scanning
|
||||
#if DUSB>=1
|
||||
if (debug>=2) {
|
||||
Serial.print(F("RXTOUT:: f="));
|
||||
Serial.print(ifreq);
|
||||
Serial.print(F(", sf="));
|
||||
Serial.print(sf);
|
||||
Serial.print(F(", tim="));
|
||||
Serial.println(micros() - detTime);
|
||||
}
|
||||
#endif
|
||||
_state = S_SCAN;
|
||||
cadScanner(); // Start the scanner after RXTOUT
|
||||
_event=0;
|
||||
}
|
||||
|
||||
// If not in cad mode we are in single channel single sf mode.
|
||||
//
|
||||
else {
|
||||
_state = S_RX; //
|
||||
rxLoraModem();
|
||||
_event=0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// The interrupt received is not RXDONE nor RXTOUT
|
||||
// therefore we restart the scanning sequence (catch all)
|
||||
// XXX This should not be possible, It is always one of the two...
|
||||
else {
|
||||
#if DUSB>=1
|
||||
if (debug>=1) {
|
||||
Serial.print(F("S_RX:: no RXDONE or RXTOUT but="));
|
||||
Serial.println(intr);
|
||||
}
|
||||
#endif
|
||||
//initLoraModem();
|
||||
//_event=0;
|
||||
}
|
||||
|
||||
break; // S_RX
|
||||
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// Start te transmissoion of a message in state S-TX
|
||||
// We use TXDONE as the state to read the message.
|
||||
//
|
||||
case S_TX:
|
||||
|
||||
if (intr == 0x00) {
|
||||
#if DUSB>=1
|
||||
Serial.println(F("TX:00"));
|
||||
writeRegister(REG_IRQ_FLAGS, (uint8_t) 0xFF); // reset interrupt flags
|
||||
_event=0;
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Initiate the transmission of the buffer (in Interrupt space)
|
||||
// We react on ALL interrupts if we are in TX state.
|
||||
txLoraModem(
|
||||
LoraDown.payLoad,
|
||||
LoraDown.payLength,
|
||||
LoraDown.tmst,
|
||||
LoraDown.sfTx,
|
||||
LoraDown.powe,
|
||||
LoraDown.fff,
|
||||
LoraDown.crc,
|
||||
LoraDown.iiq
|
||||
);
|
||||
|
||||
#if DUSB>=2
|
||||
if (debug>=0) {
|
||||
Serial.println(F("S_TX, "));
|
||||
}
|
||||
#endif
|
||||
_state = S_TXDONE;
|
||||
writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) 0x00);
|
||||
writeRegister(REG_IRQ_FLAGS, (uint8_t) 0xFF); // reset interrupt flags
|
||||
_event=1;
|
||||
break; // S_TX
|
||||
|
||||
|
||||
// ---------------------------------------------------
|
||||
// AFter the transmission is completed by the hardware,
|
||||
// the interrupt TXDONE is raised telling us that the tranmission
|
||||
// was successful.
|
||||
// If we receive an interrupt on dio0 _state==S_TX it is a TxDone interrupt
|
||||
// Do nothing with the interrupt, it is just an indication.
|
||||
// sendPacket switch back to scanner mode after transmission finished OK
|
||||
//
|
||||
case S_TXDONE:
|
||||
if (intr & IRQ_LORA_TXDONE_MASK) {
|
||||
#if DUSB>=1
|
||||
Serial.println(F("TXDONE interrupt"));
|
||||
#endif
|
||||
// After transmission reset to receiver
|
||||
if (_cad) {
|
||||
// Set the state to CAD scanning
|
||||
_state = S_SCAN;
|
||||
cadScanner(); // Start the scanner after TX cycle
|
||||
}
|
||||
else {
|
||||
_state = S_RX;
|
||||
rxLoraModem();
|
||||
}
|
||||
|
||||
writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) 0x00);
|
||||
writeRegister(REG_IRQ_FLAGS, (uint8_t) 0xFF); // reset interrupt flags
|
||||
#if DUSB>=1
|
||||
if (debug>=1) {
|
||||
Serial.println(F("TXDONE handled"));
|
||||
if (debug>=2) Serial.flush();
|
||||
}
|
||||
#endif
|
||||
_event=0;
|
||||
}
|
||||
|
||||
// If a soft _event==0 interrupt and no transmission finished:
|
||||
else {
|
||||
#if DUSB>=1
|
||||
if (debug>=0) {
|
||||
Serial.print(F("TXDONE unknown interrupt="));
|
||||
Serial.println(intr);
|
||||
if (debug>=2) Serial.flush();
|
||||
}
|
||||
#endif
|
||||
writeRegister(REG_IRQ_FLAGS, (uint8_t) 0xFF); // reset interrupt flags
|
||||
_event=0;
|
||||
}
|
||||
|
||||
break; // S_TXDONE
|
||||
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// If _STATE is in undefined state
|
||||
// If such a thing happens, we should re-init the interface and
|
||||
// make sure that we pick up next interrupt
|
||||
default:
|
||||
#if DUSB>=1
|
||||
if (debug >= 0) {
|
||||
Serial.print("E state=");
|
||||
Serial.println(_state);
|
||||
}
|
||||
#endif
|
||||
if (_cad) {
|
||||
_state = S_SCAN;
|
||||
cadScanner();
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = S_RX;
|
||||
rxLoraModem();
|
||||
}
|
||||
writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) 0x00);
|
||||
writeRegister(REG_IRQ_FLAGS, (uint8_t) 0xFF); // Reset all interrupts
|
||||
_event=0;
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// 1-channel LoRa Gateway for ESP8266
|
||||
// Copyright (c) 2016, 2017 Maarten Westenberg version for ESP8266
|
||||
// Version 5.0.1
|
||||
// Date: 2017-11-15
|
||||
// Version 5.0.6
|
||||
// Date: 2018-02-12
|
||||
//
|
||||
// based on work done by Thomas Telkamp for Raspberry PI 1ch gateway
|
||||
// and many others.
|
||||
|
@ -148,7 +148,7 @@ int sendPacket(uint8_t *buf, uint8_t length)
|
|||
|
||||
LoraDown.payLoad = payLoad;
|
||||
LoraDown.payLength = payLength;
|
||||
LoraDown.tmst = tmst;
|
||||
LoraDown.tmst = tmst; // Downstream in milis
|
||||
LoraDown.sfTx = sfTx;
|
||||
LoraDown.powe = powe;
|
||||
LoraDown.fff = fff;
|
||||
|
@ -254,13 +254,26 @@ int buildPacket(uint32_t tmst, uint8_t *buff_up, struct LoraUp LoraUp, bool inte
|
|||
#if STATISTICS >= 1
|
||||
// Receive statistics
|
||||
for (int m=( MAX_STAT -1); m>0; m--) statr[m]=statr[m-1];
|
||||
statr[0].tmst = millis();
|
||||
statr[0].tmst = now();
|
||||
statr[0].ch= ifreq;
|
||||
statr[0].prssi = prssi - rssicorr;
|
||||
#if RSSI==1
|
||||
statr[0].rssi = _rssi - rssicorr;
|
||||
#endif
|
||||
statr[0].sf = LoraUp.sf;
|
||||
#if DUSB>=2
|
||||
if (debug>=0) {
|
||||
if ((message[4] != 0x26) || (message[1]==0x99)) {
|
||||
Serial.print(F("addr="));
|
||||
for (int i=messageLength; i>0; i--) {
|
||||
if (message[i]<16) Serial.print('0');
|
||||
Serial.print(message[i],HEX);
|
||||
Serial.print(' ');
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
statr[0].node = ( message[1]<<24 | message[2]<<16 | message[3]<<8 | message[4] );
|
||||
|
||||
#if STATISTICS >= 2
|
||||
|
@ -277,7 +290,7 @@ int buildPacket(uint32_t tmst, uint8_t *buff_up, struct LoraUp LoraUp, bool inte
|
|||
|
||||
#if DUSB>=1
|
||||
if (debug>=1) {
|
||||
Serial.print(F("pRSSI: "));
|
||||
Serial.print(F("buildPacket:: pRSSI="));
|
||||
Serial.print(prssi-rssicorr);
|
||||
Serial.print(F(" RSSI: "));
|
||||
Serial.print(_rssi - rssicorr);
|
||||
|
@ -297,12 +310,23 @@ int buildPacket(uint32_t tmst, uint8_t *buff_up, struct LoraUp LoraUp, bool inte
|
|||
#endif
|
||||
|
||||
// Show received message status on OLED display
|
||||
#if OLED==1
|
||||
#if OLED>=1
|
||||
char timBuff[20];
|
||||
sprintf(timBuff, "%02i:%02i:%02i", hour(), minute(), second());
|
||||
|
||||
// char addrBuff[20;
|
||||
// if (message[4] < 0x10) display.drawString( 40, 32, "0"+String(message[4], HEX)); else display.drawString( 40, 32, String(message[4], HEX));
|
||||
// if (message[3] < 0x10) display.drawString( 61, 32, "0"+String(message[3], HEX)); else display.drawString( 61, 32, String(message[3], HEX));
|
||||
// if (message[2] < 0x10) display.drawString( 82, 32, "0"+String(message[2], HEX)); else display.drawString( 82, 32, String(message[2], HEX));
|
||||
// if (message[1] < 0x10) display.drawString(103, 32, "0"+String(message[1], HEX)); else display.drawString(103, 32, String(message[1], HEX));
|
||||
// sprintf(addrBuff, "%02X,:%02X:%02X:%02X", message[4], message[3], message[2], message[1]);
|
||||
|
||||
display.clear();
|
||||
display.setFont(ArialMT_Plain_16);
|
||||
display.setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
char timBuff[20];
|
||||
sprintf(timBuff, "%02i:%02i:%02i", hour(), minute(), second());
|
||||
|
||||
// msg_oLED(timBuff, prssi-rssicorr, SNR, message)
|
||||
|
||||
display.drawString(0, 0, "Time: " );
|
||||
display.drawString(40, 0, timBuff);
|
||||
display.drawString(0, 16, "RSSI: " );
|
||||
|
@ -321,7 +345,8 @@ int buildPacket(uint32_t tmst, uint8_t *buff_up, struct LoraUp LoraUp, bool inte
|
|||
display.drawString(40, 48, String((int)messageLength) );
|
||||
display.display();
|
||||
yield();
|
||||
#endif
|
||||
|
||||
#endif //OLED>=1
|
||||
|
||||
int j;
|
||||
|
||||
|
@ -336,9 +361,17 @@ int buildPacket(uint32_t tmst, uint8_t *buff_up, struct LoraUp LoraUp, bool inte
|
|||
}
|
||||
#endif
|
||||
base64_encode(b64, (char *) message, messageLength);// max 341
|
||||
// start composing datagram with the header
|
||||
uint8_t token_h = (uint8_t)rand(); // random token
|
||||
uint8_t token_l = (uint8_t)rand(); // random token
|
||||
|
||||
// pre-fill the data buffer with fixed fields
|
||||
buff_up[0] = PROTOCOL_VERSION; // 0x01 still
|
||||
|
||||
|
||||
buff_up[1] = token_h;
|
||||
buff_up[2] = token_l;
|
||||
|
||||
buff_up[3] = PKT_PUSH_DATA; // 0x00
|
||||
|
||||
// READ MAC ADDRESS OF ESP8266, and insert 0xFF 0xFF in the middle
|
||||
|
@ -351,11 +384,7 @@ int buildPacket(uint32_t tmst, uint8_t *buff_up, struct LoraUp LoraUp, bool inte
|
|||
buff_up[10] = MAC_array[4];
|
||||
buff_up[11] = MAC_array[5];
|
||||
|
||||
// start composing datagram with the header
|
||||
uint8_t token_h = (uint8_t)rand(); // random token
|
||||
uint8_t token_l = (uint8_t)rand(); // random token
|
||||
buff_up[1] = token_h;
|
||||
buff_up[2] = token_l;
|
||||
|
||||
buff_index = 12; // 12-byte binary (!) header
|
||||
|
||||
// start of JSON structure that will make payload
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// 1-channel LoRa Gateway for ESP8266
|
||||
// Copyright (c) 2016, 2017 Maarten Westenberg version for ESP8266
|
||||
// Version 5.0.1
|
||||
// Date: 2017-11-15
|
||||
// Version 5.0.6
|
||||
// Date: 2018-02-12
|
||||
//
|
||||
// based on work done by many people and making use of several libraries.
|
||||
//
|
||||
|
@ -69,16 +69,21 @@ static void printHEX(char * hexa, const char sep, String& response)
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
// stringTime
|
||||
// Print the time t into the String reponse. t is of type time_t in seconds.
|
||||
// Only when RTC is present we print real time values
|
||||
// t contains number of milli seconds since system started that the event happened.
|
||||
// t contains number of seconds since system started that the event happened.
|
||||
// So a value of 100 would mean that the event took place 1 minute and 40 seconds ago
|
||||
// ----------------------------------------------------------------------------
|
||||
static void stringTime(unsigned long t, String& response) {
|
||||
static void stringTime(time_t t, String& response) {
|
||||
|
||||
if (t==0) { response += "--"; return; }
|
||||
|
||||
// now() gives seconds since 1970
|
||||
time_t eventTime = now() - ((millis()-t)/1000);
|
||||
// as millis() does rotate every 50 days
|
||||
// So we need another timing parameter
|
||||
time_t eventTime = t;
|
||||
|
||||
// Rest is standard
|
||||
byte _hour = hour(eventTime);
|
||||
byte _minute = minute(eventTime);
|
||||
byte _second = second(eventTime);
|
||||
|
@ -140,6 +145,7 @@ static void setVariables(const char *cmd, const char *arg) {
|
|||
ifreq=0;
|
||||
freq=freqs[0];
|
||||
rxLoraModem();
|
||||
cadScanner();
|
||||
}
|
||||
writeGwayCfg(CONFIGFILE); // Save configuration to file
|
||||
}
|
||||
|
@ -272,7 +278,7 @@ static void openWebPage()
|
|||
|
||||
response +="Version: "; response+=VERSION;
|
||||
response +="<br>ESP alive since "; // STARTED ON
|
||||
stringTime(1, response);
|
||||
stringTime(startTime, response);
|
||||
|
||||
response +=", Uptime: "; // UPTIME
|
||||
uint32_t secs = millis()/1000;
|
||||
|
@ -289,7 +295,7 @@ static void openWebPage()
|
|||
response += String() + _second;
|
||||
|
||||
response +="<br>Current time "; // CURRENT TIME
|
||||
stringTime(millis(), response);
|
||||
stringTime(now(), response);
|
||||
response +="<br>";
|
||||
|
||||
server.sendContent(response);
|
||||
|
@ -529,9 +535,11 @@ static void statisticsData()
|
|||
|
||||
response +="<tr><td class=\"cell\">Packages Uplink Total</td>";
|
||||
response +="<td class=\"cell\">" + String(cp_nb_rx_rcv) + "</td>";
|
||||
response +="<td class=\"cell\">" + String((cp_nb_rx_rcv*3600)/(millis()/1000)) + "</td></tr>";
|
||||
response +="<td class=\"cell\">" + String((cp_nb_rx_rcv*3600)/(now() - startTime)) + "</td></tr>";
|
||||
|
||||
response +="<tr><td class=\"cell\">Packages Uplink OK </td><td class=\"cell\">";
|
||||
response +=cp_nb_rx_ok; response+="</tr>";
|
||||
|
||||
response +="<tr><td class=\"cell\">Packages Downlink</td><td class=\"cell\">";
|
||||
response +=cp_up_pkt_fwd; response+="</tr>";
|
||||
|
||||
|
@ -577,8 +585,9 @@ static void sensorData()
|
|||
response += "<tr>";
|
||||
response += "<th class=\"thead\">Time</th>";
|
||||
response += "<th class=\"thead\">Node</th>";
|
||||
response += "<th class=\"thead\" colspan=\"2\">Channel</th>";
|
||||
response += "<th class=\"thead\" style=\"width: 50px;\">SF</th>";
|
||||
response += "<th class=\"thead\" style=\"width: 20px;\">C</th>";
|
||||
response += "<th class=\"thead\">Freq</th>";
|
||||
response += "<th class=\"thead\" style=\"width: 40px;\">SF</th>";
|
||||
response += "<th class=\"thead\" style=\"width: 50px;\">pRSSI</th>";
|
||||
#if RSSI==1
|
||||
if (debug > 1) {
|
||||
|
@ -594,7 +603,7 @@ static void sensorData()
|
|||
response = "";
|
||||
|
||||
response += String() + "<tr><td class=\"cell\">";
|
||||
stringTime(statr[i].tmst, response);
|
||||
stringTime((statr[i].tmst), response); // XXX Change tmst not to be millis() dependent
|
||||
response += "</td>";
|
||||
response += String() + "<td class=\"cell\">";
|
||||
printHEX((char *)(& (statr[i].node)),' ',response);
|
||||
|
@ -785,7 +794,8 @@ void setupWWW()
|
|||
// Reset the statistics
|
||||
server.on("/RESET", []() {
|
||||
Serial.println(F("RESET"));
|
||||
cp_nb_rx_rcv = 0;
|
||||
startTime= now() - 1; // Reset all timers too
|
||||
cp_nb_rx_rcv = 0; // Reset package statistics
|
||||
cp_nb_rx_ok = 0;
|
||||
cp_up_pkt_fwd = 0;
|
||||
#if STATISTICS >= 1
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// 1-channel LoRa Gateway for ESP8266
|
||||
// Copyright (c) 2016, 2017 Maarten Westenberg version for ESP8266
|
||||
// Version 5.0.1
|
||||
// Date: 2017-11-15
|
||||
// Version 5.0.6
|
||||
// Date: 2018-02-12
|
||||
//
|
||||
// based on work done by Thomas Telkamp for Raspberry PI 1ch gateway
|
||||
// and many others.
|
||||
|
@ -39,7 +39,7 @@ struct espGwayConfig {
|
|||
uint8_t debug; // range 0 to 4
|
||||
|
||||
bool cad; // is CAD enabled?
|
||||
bool hop; // Is HOP enabled (Note: SHould be disabled)
|
||||
bool hop; // Is HOP enabled (Note: Should be disabled)
|
||||
bool isNode; // Is gateway node enabled
|
||||
bool refresh; // Is WWW browser refresh enabled
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// 1-channel LoRa Gateway for ESP8266
|
||||
// Copyright (c) 2016, 2017 Maarten Westenberg version for ESP8266
|
||||
// Version 5.0.1
|
||||
// Date: 2017-11-14
|
||||
// Version 5.0.6
|
||||
// Date: 2018-02-12
|
||||
//
|
||||
// based on work done by Thomas Telkamp for Raspberry PI 1ch gateway
|
||||
// and many other contributors.
|
||||
|
@ -19,10 +19,37 @@
|
|||
//
|
||||
// ------------------------------------------------------------------------------------
|
||||
|
||||
// Our code should correct the server timing
|
||||
long txDelay= 0x00; // delay time on top of server TMST
|
||||
|
||||
#define SPISPEED 8000000 // was 50000/50KHz < 10MHz
|
||||
// ----------------------------------------
|
||||
// Used by REG_PAYLOAD_LENGTH to set receive payload length
|
||||
#define PAYLOAD_LENGTH 0x40 // 64 bytes
|
||||
#define MAX_PAYLOAD_LENGTH 0x80 // 128 bytes
|
||||
|
||||
// In order to make the CAD behaviour dynamic we set a variable
|
||||
// when the CAD functions are defined. Value of 3 is minimum frequencies a
|
||||
// gateway should support to be fully LoRa compliant.
|
||||
#define NUM_HOPS 2 // 3 should be the minimum
|
||||
|
||||
// Do not change these setting for RSSI detection. They are used for CAD
|
||||
// Given the correction factor of 157, we can get to -122dB with this rating
|
||||
//
|
||||
#define RSSI_LIMIT 35 //
|
||||
|
||||
// How long to wait in LoRa mode before using the RSSI value.
|
||||
// This period should be as short as possible, yet sufficient
|
||||
//
|
||||
#define RSSI_WAIT 6 // was 25
|
||||
|
||||
// How long will it take when hopping before a CDONE or CDETD value
|
||||
// is measured.
|
||||
//
|
||||
#define EVENT_WAIT 600 // was 200
|
||||
|
||||
// Our code should correct the server Tramission delay settings
|
||||
long txDelay= 0x00; // tx delay time on top of server TMST
|
||||
|
||||
// SPI setting. 8MHz seems to be the max
|
||||
#define SPISPEED 8000000 // Set to 8 * 10E6
|
||||
|
||||
// Frequencies
|
||||
// Set center frequency. If in doubt, choose the first one, comment all others
|
||||
|
@ -32,7 +59,7 @@ int freqs [] = {
|
|||
868100000, // Channel 0, 868.1 MHz primary
|
||||
868300000, // Channel 1, 868.3 MHz mandatory
|
||||
868500000, // Channel 2, 868.5 MHz mandatory
|
||||
867100000, // Channel 3, 867.1 MHz
|
||||
867100000, // Channel 3, 867.1 MHz Optional
|
||||
867300000,
|
||||
867500000,
|
||||
867700000,
|
||||
|
@ -60,26 +87,23 @@ volatile uint8_t _event=0;
|
|||
// so we need to store the current value we like to work with
|
||||
uint8_t _rssi;
|
||||
|
||||
// In order to make the CAD behaviour dynamic we set a variable
|
||||
// when the CAD functions are defined. Value of 3 is minimum frequencies a
|
||||
// gateway should support to be fully LoRa compliant.
|
||||
#define NUM_HOPS 3
|
||||
|
||||
bool _cad= (bool) _CAD; // Set to true for Channel Activity Detection, only when dio 1 connected
|
||||
bool _hop=false; // experimental; frequency hopping. Only use when dio2 connected
|
||||
bool inHop=false;
|
||||
unsigned long nowTime=0;
|
||||
unsigned long hopTime=0;
|
||||
unsigned long msgTime=0;
|
||||
unsigned long hopTime=0;
|
||||
unsigned long detTime=0;
|
||||
|
||||
#if _PIN_OUT==1
|
||||
// Definition of the GPIO pins used by the Gateway for Hallard type boards
|
||||
//
|
||||
struct pins {
|
||||
uint8_t dio0=15; // GPIO15 / D8. For the Hallard board shared between DIO0/DIO1/DIO2
|
||||
uint8_t dio1=15; // GPIO15 / D8. Used for CAD, may or not be shared with DIO0
|
||||
uint8_t dio2=15; // GPIO15 / D8. Used for frequency hopping, don't care
|
||||
uint8_t ss=16; // GPIO16 / D0. Select pin connected to GPIO16 / D0
|
||||
uint8_t rst=0; // GPIO0 / D3. Reset pin not used
|
||||
uint8_t rst=0; // GPIO 0 / D3. Reset pin not used
|
||||
// MISO 12 / D6
|
||||
// MOSI 13 / D7
|
||||
// CLK 14 / D5
|
||||
|
@ -93,8 +117,22 @@ struct pins {
|
|||
uint8_t ss=15; // GPIO15 / D8. Select pin connected to GPIO15
|
||||
uint8_t rst=0; // GPIO0 / D3. Reset pin not used
|
||||
} pins;
|
||||
#elif _PIN_OUT==3
|
||||
// For ESP32 based board
|
||||
// SCK == GPIO5/ PIN5
|
||||
// SS == GPIO18/PIN18
|
||||
// MISO == GPIO19/ PIN19
|
||||
// MOSI == GPIO27/ PIN27
|
||||
// RST == GPIO14/ PIN14
|
||||
struct pins {
|
||||
uint8_t dio0=26; // GPIO26 / Dio0 used for one frequency and one SF
|
||||
uint8_t dio1=26; // GPIO26 / Used for CAD, may or not be shared with DIO0
|
||||
uint8_t dio2=26; // GPI26 / Used for frequency hopping, don't care
|
||||
uint8_t ss=18; // GPIO18 / Dx. Select pin connected to GPIO18
|
||||
uint8_t rst=14; // GPIO0 / D3. Reset pin not used
|
||||
} pins;
|
||||
#else
|
||||
// Use your own pin definitions, and uncomment #error line below
|
||||
// Use your own pin definitions, and comment #error line below
|
||||
// MISO 12 / D6
|
||||
// MOSI 13 / D7
|
||||
// CLK 14 / D5
|
||||
|
@ -106,7 +144,7 @@ struct pins {
|
|||
// Ech time a message is received or sent the statistics are updated.
|
||||
// In case STATISTICS==1 we define the last MAX_STAT messages as statistics
|
||||
struct stat_t {
|
||||
unsigned long tmst; // Time since 1970 in milli seconds
|
||||
unsigned long tmst; // Time since 1970 in seconds
|
||||
unsigned long node; // 4-byte DEVaddr (the only one known to gateway)
|
||||
uint8_t ch; // Channel index to freqs array
|
||||
uint8_t sf;
|
||||
|
@ -118,9 +156,6 @@ struct stat_t {
|
|||
|
||||
|
||||
#if STATISTICS >= 1
|
||||
// History of received uplink messages from nodes
|
||||
struct stat_t statr[MAX_STAT];
|
||||
|
||||
// STATC contains the statistic that are gateway related and not per
|
||||
// message. Example: Number of messages received on SF7 or number of (re) boots
|
||||
// So where statr contains the statistics gathered per packet the statc
|
||||
|
@ -139,6 +174,9 @@ struct stat_c {
|
|||
} stat_c;
|
||||
struct stat_c statc;
|
||||
#endif
|
||||
// History of received uplink messages from nodes
|
||||
struct stat_t statr[MAX_STAT];
|
||||
|
||||
#else // STATISTICS==0
|
||||
struct stat_t statr[1]; // Always have at least one element to store in
|
||||
#endif
|
||||
|
@ -149,7 +187,7 @@ uint8_t payLoad[128]; // Payload i
|
|||
struct LoraBuffer {
|
||||
uint8_t * payLoad;
|
||||
uint8_t payLength;
|
||||
uint32_t tmst;
|
||||
uint32_t tmst; // in millis()
|
||||
uint8_t sfTx;
|
||||
uint8_t powe;
|
||||
uint32_t fff;
|
||||
|
@ -157,7 +195,7 @@ struct LoraBuffer {
|
|||
uint8_t iiq;
|
||||
} LoraDown;
|
||||
|
||||
// Up buffer (from Lora to UDP)
|
||||
// Up buffer (from Lora sensor to UDP)
|
||||
//
|
||||
|
||||
struct LoraUp {
|
||||
|
@ -172,22 +210,6 @@ struct LoraUp {
|
|||
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// Used by REG_PAYLOAD_LENGTH to set receive payload length
|
||||
#define PAYLOAD_LENGTH 0x40 // 64 bytes
|
||||
#define MAX_PAYLOAD_LENGTH 0x80 // 128 bytes
|
||||
|
||||
// Do not change these setting for RSSI detection. They are used for CAD
|
||||
// Given the correction factor of 157, we can get to -120dB with this rating
|
||||
//
|
||||
#define RSSI_LIMIT 37 // was 39
|
||||
#define RSSI_LIMIT_DOWN 34 // Was 34
|
||||
|
||||
// How long to wait in LoRa mode before using the RSSI value.
|
||||
// This period should be as short as possible, yet sufficient
|
||||
//
|
||||
#define RSSI_WAIT 15 // was 100 works, 50 works, 40 works, 25 works
|
||||
#define RSSI_WAIT_DOWN 10 // was 75 works, 35 works, 30 works, 20 works
|
||||
|
||||
// ============================================================================
|
||||
// Set all definitions for Gateway
|
||||
|
@ -195,7 +217,7 @@ struct LoraUp {
|
|||
// Register definitions. These are the addresses of the TFM95, SX1276 that we
|
||||
// need to set in the program.
|
||||
|
||||
#define REG_FIFO 0x00
|
||||
#define REG_FIFO 0x00 // rw FIFO address
|
||||
#define REG_OPMODE 0x01
|
||||
// Register 2 to 5 are unused for LoRa
|
||||
#define REG_FRF_MSB 0x06
|
||||
|
@ -204,11 +226,11 @@ struct LoraUp {
|
|||
#define REG_PAC 0x09
|
||||
#define REG_PARAMP 0x0A
|
||||
#define REG_LNA 0x0C
|
||||
#define REG_FIFO_ADDR_PTR 0x0D
|
||||
#define REG_FIFO_TX_BASE_AD 0x0E
|
||||
#define REG_FIFO_RX_BASE_AD 0x0F
|
||||
#define REG_FIFO_ADDR_PTR 0x0D // rw SPI interface address pointer in FIFO data buffer
|
||||
#define REG_FIFO_TX_BASE_AD 0x0E // rw write base address in FIFO data buffer for TX modulator
|
||||
#define REG_FIFO_RX_BASE_AD 0x0F // rw read base address in FIFO data buffer for RX demodulator (0x00)
|
||||
|
||||
#define REG_FIFO_RX_CURRENT_ADDR 0x10
|
||||
#define REG_FIFO_RX_CURRENT_ADDR 0x10 // r Address of last packet received
|
||||
#define REG_IRQ_FLAGS_MASK 0x11
|
||||
#define REG_IRQ_FLAGS 0x12
|
||||
#define REG_RX_NB_BYTES 0x13
|
||||
|
@ -266,7 +288,7 @@ struct LoraUp {
|
|||
// ----------------------------------------
|
||||
// LOW NOISE AMPLIFIER
|
||||
|
||||
#define LNA_MAX_GAIN 0x23
|
||||
#define LNA_MAX_GAIN 0x23 // Max gain 0x20 | Boost 0x03
|
||||
#define LNA_OFF_GAIN 0x00
|
||||
#define LNA_LOW_GAIN 0x20
|
||||
|
||||
|
@ -326,9 +348,13 @@ struct LoraUp {
|
|||
#define MAP_DIO2_LORA_NOP 0x0C // ----11-- bit 3 and 2
|
||||
|
||||
#define MAP_DIO3_LORA_CADDONE 0x00 // ------00 bit 1 and 0
|
||||
#define MAP_DIO3_LORA_HEADER 0x01 // ------01
|
||||
#define MAP_DIO3_LORA_CRC 0x02 // ------10
|
||||
#define MAP_DIO3_LORA_NOP 0x03 // ------11
|
||||
|
||||
// FSK specific
|
||||
#define MAP_DIO0_FSK_READY 0x00 // 00------ (packet sent / payload ready)
|
||||
|
||||
#define MAP_DIO1_FSK_NOP 0x30 // --11----
|
||||
#define MAP_DIO2_FSK_TXNOP 0x04 // ----01--
|
||||
#define MAP_DIO2_FSK_TIMEOUT 0x08 // ----10--
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
// 1-channel LoRa Gateway for ESP8266
|
||||
// Copyright (c) 2016, 2017 Maarten Westenberg version for ESP8266
|
||||
// Version 5.0.6
|
||||
// Date: 2018-02-12
|
||||
//
|
||||
// based on work done by Thomas Telkamp for Raspberry PI 1ch gateway
|
||||
// and many others.
|
||||
//
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the MIT License
|
||||
// which accompanies this distribution, and is available at
|
||||
// https://opensource.org/licenses/mit-license.php
|
||||
//
|
||||
// Author: Maarten Westenberg (mw12554@hotmail.com)
|
||||
//
|
||||
// This file contains a number of compile-time settings and definitions for OLED support.
|
||||
//
|
||||
// ----------------------------------------------------------------------------------------
|
||||
|
||||
#if OLED>=1
|
||||
|
||||
#define OLED_SCL 5 // GPIO5 / D1
|
||||
#define OLED_SDA 4 // GPIO4 / D2
|
||||
|
||||
#if OLED==1
|
||||
#include "SSD1306.h"
|
||||
#define OLED_ADDR 0x3C // Default 0x3C for 0.9", for 1.3" it is 0x78
|
||||
SSD1306 display(OLED_ADDR, OLED_SDA, OLED_SCL);// i2c ADDR & SDA, SCL on wemos
|
||||
#endif
|
||||
|
||||
#if OLED==2
|
||||
#include "SH1106.h"
|
||||
//#define OLED_ADDR 0x78 // Default for 1.3" it is 0x78
|
||||
#define OLED_ADDR 0x3C // Default 0x3C for 0.9", for 1.3" it is 0x78
|
||||
SH1106 display(OLED_ADDR, OLED_SDA, OLED_SCL); // i2c ADDR & SDA, SCL on wemos
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
41
README.md
41
README.md
|
@ -1,6 +1,6 @@
|
|||
# Single Channel LoRaWAN Gateway
|
||||
|
||||
Version 5.0.1, November, 2017
|
||||
Version 5.0.3, December 09, 2017
|
||||
Author: M. Westenberg (mw12554@hotmail.com)
|
||||
Copyright: M. Westenberg (mw12554@hotmail.com)
|
||||
|
||||
|
@ -20,21 +20,25 @@ The software implements a standard LoRa gateway with the following exceptions on
|
|||
|
||||
- This LoRa gateway is not a full gateway but it implements just a one-channel/one frequency gateway.
|
||||
The minimum amount of frequencies supported by a full gateway is 3, most support 9 or more frequencies.
|
||||
This software started as a proof-of-concept to prove that a single low-cost RRFM95 chip which was present in almost every
|
||||
LoRa node in Europe could be used as a cheap alternative to the far more expensive full gateways that were
|
||||
making use of the SX1301 chip.
|
||||
This software started as a proof-of-concept to prove that a single low-cost RRFM95 chip which was present
|
||||
in almost every LoRa node in Europe could be used as a cheap alternative to the far more expensive full
|
||||
gateways that were making use of the SX1301 chip.
|
||||
|
||||
- As the software of this gateway will often be used during the development phase of a project or in demo situations,
|
||||
the software is flexible and can be easily configured according to environment or customer requirements.
|
||||
There are two ways of interacting with the software: 1. Modifying the ESP-sc-gway.h file at compile time allows the
|
||||
administrator to set almost all parameters. 2. Using the webinterface (http://<gateway_IP>) will allow
|
||||
administrators to set and reset several of the parameters at runtime.
|
||||
- As the software of this gateway will often be used during the development phase of a project or in
|
||||
demo situations, the software is flexible and can be easily configured according to environment or
|
||||
customer requirements. There are two ways of interacting with the software:
|
||||
1. Modifying the ESP-sc-gway.h file at compile time allows the administrator to set almost all parameters.
|
||||
2. Using the webinterface (http://<gateway_IP>) will allow administrators to set and reset several of the
|
||||
parameters at runtime.
|
||||
|
||||
Full documentation of the Single Channel Gateway is found at things4u.github.io, please look at the Hardware Guide
|
||||
under the Gateway chapter.
|
||||
|
||||
|
||||
## testing
|
||||
|
||||
The single channel gateway has been tested on a gateway with the Wemos D1 Mini, using a HopeRF RFM95W transceiver.
|
||||
The LoRa nodes tested are:
|
||||
The LoRa nodes tested againts this gateway are:
|
||||
|
||||
- TeensyLC with HopeRF RFM95 radio
|
||||
- Arduino Pro-Mini (default Armega328 model, 8MHz 3.3V and 16MHz 3.3V)
|
||||
|
@ -72,7 +76,7 @@ coming in on the Serial monitor.
|
|||
There are two ways of changing the configuration of the single channel gateway:
|
||||
|
||||
1. Changing the ESP-sc-gway.h file at compile-time
|
||||
2. Run the http://<gateway-IP> web interface to change setting at complie time.
|
||||
2. Run the http://<gateway-IP> web interface to change settings at run time.
|
||||
|
||||
|
||||
## Editing the ESP-sc-gway.h file
|
||||
|
@ -89,6 +93,14 @@ The user is advised to turn off functions not used in order to save on memory us
|
|||
If the heap drops below 18 KBytes some functions may not behave as expected (in extreme case the program may crash).
|
||||
|
||||
|
||||
### Setting USB
|
||||
|
||||
The user can determine whether or not the USB console is used for output messages.
|
||||
When setting DUSB to 0 all output by Serial is disabled
|
||||
(actually the Serial statements are not included in the code).
|
||||
|
||||
\#define DUSB 1
|
||||
|
||||
### Debug
|
||||
|
||||
The user can set the initial value of the DEBUG parameter.
|
||||
|
@ -301,7 +313,7 @@ Please note that they are NOT part of the ESP 1channel gateway and may have thei
|
|||
However, these libraries are not part of the single-channel Gateway software.
|
||||
|
||||
|
||||
# Connections
|
||||
# Pin Connections
|
||||
|
||||
See http://things4u.github.io in the hardware section for building and connection instructions.
|
||||
|
||||
|
@ -310,7 +322,7 @@ See http://things4u.github.io in the hardware section for building and connectio
|
|||
|
||||
The following things are still on my wish list to make to the single channel gateway:
|
||||
|
||||
- Receive downstream message with commands form the server. These can be used to configure
|
||||
- Receive downstream message with commands from the server. These can be used to configure
|
||||
the gateway through downlink messages (such as setting the SF)
|
||||
- Support for ESP32 and RFM95 on 915 MHz
|
||||
- Use the SPIFFS for storing .css files
|
||||
|
@ -321,4 +333,5 @@ The following things are still on my wish list to make to the single channel gat
|
|||
# License
|
||||
|
||||
The source files of the gateway sketch in this repository is made available under the MIT
|
||||
license. The libraries included in this repository are included for convenience only and all have their own license, and are not part of the ESP 1ch gateway code.
|
||||
license. The libraries included in this repository are included for convenience only and all have their own license,
|
||||
and are not part of the ESP 1ch gateway code.
|
||||
|
|
Ładowanie…
Reference in New Issue