├── ESP-sc-gway ├── ESP-sc-gway.h ├── ESP-sc-gway.ino ├── LICENSE ├── README.md ├── RGBLed.cpp ├── RGBLed.h ├── gBase64.cpp └── gBase64.h └── README.md /ESP-sc-gway/ESP-sc-gway.h: -------------------------------------------------------------------------------- 1 | // 2 | // Author: Maarten Westenberg 3 | // Version: 1.0.0 4 | // Date: 2016-03-25 5 | // 6 | // This file contains a number of compile-time settings that can be set on (=1) or off (=0) 7 | // The disadvantage of compile time is minor compared to the memory gain of not having 8 | // too much code compiled and loaded on your ESP8266. 9 | // 10 | // See http://pubsubclient.knolleary.net/api.html for API description of MQTT 11 | // 12 | // 13 | // 2016-06-12 - Charles-Henri Hallard (http://hallard.me and http://github.com/hallard) 14 | // added support for WeMos Lora Gateway 15 | // added AP mode (for OTA) 16 | // added Over The Air (OTA) feature 17 | // added support for onboard WS2812 RGB Led 18 | // refactored include source file 19 | // see https://github.com/hallard/WeMos-Lora 20 | // 21 | // ---------------------------------------------------------------------------------------- 22 | 23 | #include 24 | 25 | #define VERSION " ! V. 1.1.3, 110616" 26 | 27 | // Enable WeMos Lora Shield Gateway 28 | // see https://github.com/hallard/WeMos-Lora 29 | // Uncomment this line if you're using this shield as gateway 30 | #define WEMOS_LORA_GW 31 | 32 | /******************************************************************************* 33 | 34 | Configure these values if necessary! 35 | 36 | *******************************************************************************/ 37 | 38 | // WiFi definitions 39 | // Setup your Wifi SSID and password 40 | // If your device already connected to your Wifi, then 41 | // let as is (stars), it will connect using 42 | // your previous saved SDK credentials 43 | 44 | #ifdef CREDENTIALS // if sensorsiot credentials.h file is present 45 | #define _SSID mySSID 46 | #define _PASS myPASSWORD 47 | #define _EMAIL myEmail 48 | #else 49 | #define _SSID "..." 50 | #define _PASS "..." 51 | #define _EMAIL "..." 52 | #endif 53 | 54 | // Access point Password 55 | #define _AP_PASS "1Ch@n3l-Gateway" 56 | 57 | // MQTT definitions 58 | #define _TTNSERVER "router.eu.thethings.network" 59 | #define _MQTTSERVER "your.server.com" 60 | 61 | // TTN related 62 | #define SERVER1 _TTNSERVER // The Things Network: croft.thethings.girovito.nl "54.72.145.119" 63 | #define PORT1 1700 // The port on which to send data 64 | 65 | //#define SERVER2 _MQTTSERVER // 2nd server to send to, e.g. private server 66 | //#define PORT2 "1700" 67 | 68 | // Gateway Ident definitions 69 | #define _DESCRIPTION "ESP Gateway" 70 | 71 | #define _PLATFORM "ESP8266" 72 | #define _LAT 47.474683 73 | #define _LON 7.767343 74 | #define _ALT 350 75 | 76 | // SX1276 - ESP8266 connections 77 | #ifdef WEMOS_LORA_GW 78 | #define DEFAULT_PIN_SS 16 // GPIO16, D0 79 | #define DEFAULT_PIN_DIO0 15 // GPIO15, D8 80 | #define DEFAULT_PIN_RST NOT_A_PIN // Unused 81 | #else 82 | #define DEFAULT_PIN_SS 15 // GPIO15, D8 83 | #define DEFAULT_PIN_DIO0 5 // GPIO5, D1 84 | #define DEFAULT_PIN_RST NOT_A_PIN // Unused 85 | #endif 86 | 87 | #define STATISTICS 1 // Gather statistics on sensor and Wifi status 88 | #define DEBUG 1 // Initial value of debug var. Can be changed using the admin webserver 89 | // For operational use, set initial DEBUG vaulue 0 90 | 91 | // Definitions for the admin webserver 92 | #define A_SERVER 1 // Define local WebServer only if this define is set 93 | #define SERVERPORT 8080 // local webserver port 94 | 95 | #define A_MAXBUFSIZE 192 // Must be larger than 128, but small enough to work 96 | #define _BAUDRATE 115200 // Works for debug messages to serial momitor (if attached). 97 | 98 | // ntp 99 | #define NTP_TIMESERVER "ch.pool.ntp.org" // Country and region specific 100 | #define NTP_INTERVAL 3600 // How often doe we want time NTP synchronization 101 | #define NTP_TIMEZONES 2 // How far is our Timezone from UTC (excl daylight saving/summer time) 102 | 103 | // ============================================================================ 104 | // Set all definitions for Gateway 105 | // ============================================================================ 106 | 107 | #define REG_FIFO 0x00 108 | #define REG_FIFO_ADDR_PTR 0x0D 109 | #define REG_FIFO_TX_BASE_AD 0x0E 110 | #define REG_FIFO_RX_BASE_AD 0x0F 111 | #define REG_RX_NB_BYTES 0x13 112 | #define REG_OPMODE 0x01 113 | #define REG_FIFO_RX_CURRENT_ADDR 0x10 114 | #define REG_IRQ_FLAGS 0x12 115 | #define REG_DIO_MAPPING_1 0x40 116 | #define REG_DIO_MAPPING_2 0x41 117 | #define REG_MODEM_CONFIG 0x1D 118 | #define REG_MODEM_CONFIG2 0x1E 119 | #define REG_MODEM_CONFIG3 0x26 120 | #define REG_SYMB_TIMEOUT_LSB 0x1F 121 | #define REG_PKT_SNR_VALUE 0x19 122 | #define REG_PAYLOAD_LENGTH 0x22 123 | #define REG_IRQ_FLAGS_MASK 0x11 124 | #define REG_MAX_PAYLOAD_LENGTH 0x23 125 | #define REG_HOP_PERIOD 0x24 126 | #define REG_SYNC_WORD 0x39 127 | #define REG_VERSION 0x42 128 | 129 | #define SX72_MODE_RX_CONTINUOS 0x85 130 | #define SX72_MODE_TX 0x83 131 | #define SX72_MODE_SLEEP 0x80 132 | #define SX72_MODE_STANDBY 0x81 133 | 134 | #define PAYLOAD_LENGTH 0x40 135 | 136 | // LOW NOISE AMPLIFIER 137 | #define REG_LNA 0x0C 138 | #define LNA_MAX_GAIN 0x23 139 | #define LNA_OFF_GAIN 0x00 140 | #define LNA_LOW_GAIN 0x20 141 | 142 | // CONF REG 143 | #define REG1 0x0A 144 | #define REG2 0x84 145 | 146 | #define SX72_MC2_FSK 0x00 147 | #define SX72_MC2_SF7 0x70 148 | #define SX72_MC2_SF8 0x80 149 | #define SX72_MC2_SF9 0x90 150 | #define SX72_MC2_SF10 0xA0 151 | #define SX72_MC2_SF11 0xB0 152 | #define SX72_MC2_SF12 0xC0 153 | 154 | #define SX72_MC1_LOW_DATA_RATE_OPTIMIZE 0x01 // mandated for SF11 and SF12 155 | 156 | // FRF 157 | #define REG_FRF_MSB 0x06 158 | #define REG_FRF_MID 0x07 159 | #define REG_FRF_LSB 0x08 160 | 161 | 162 | #define FRF_MSB 0xD9 // 868.1 Mhz 163 | #define FRF_MID 0x06 164 | #define FRF_LSB 0x66 165 | #define BUFLEN 2048 //Max length of buffer 166 | 167 | #define PROTOCOL_VERSION 1 168 | #define PKT_PUSH_DATA 0 169 | #define PKT_PUSH_ACK 1 170 | #define PKT_PULL_DATA 2 171 | #define PKT_PULL_RESP 3 172 | #define PKT_PULL_ACK 4 173 | 174 | #define TX_BUFF_SIZE 2048 175 | #define STATUS_SIZE 512 // This should(!) be enough based on the static text part.. was 1024 176 | 177 | -------------------------------------------------------------------------------- /ESP-sc-gway/ESP-sc-gway.ino: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | Copyright (c) 2016 Maarten Westenberg version for ESP8266 3 | Copyright (c) 2015 Thomas Telkamp for initial Raspberry Version 4 | 5 | All rights reserved. This program and the accompanying materials 6 | are made available under the terms of the Eclipse Public License v1.0 7 | which accompanies this distribution, and is available at 8 | http://www.eclipse.org/legal/epl-v10.html 9 | 10 | Notes: 11 | - Once call gethostbyname() to get IP for services, after that only use IP 12 | addresses (too many gethost name makes ESP unstable) 13 | - Only call yield() in main stream (not for background NTP sync). 14 | 15 | 2016-06-12 - Charles-Henri Hallard (http://hallard.me and http://github.com/hallard) 16 | added support for WeMos Lora Gateway 17 | added AP mode (for OTA) 18 | added Over The Air (OTA) feature 19 | added support for onboard WS2812 RGB Led 20 | see https://github.com/hallard/WeMos-Lora 21 | 22 | 2017-04-20 - Added OLED support (Andreas Spiess) 23 | 24 | *******************************************************************************/ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include // https://github.com/PaulStoffregen/Time) 32 | #include "SSD1306.h" // alias for `#include "SSD1306Wire.h"` 33 | //#include 34 | 35 | extern "C" { 36 | #include "user_interface.h" 37 | #include "lwip/err.h" 38 | #include "lwip/dns.h" 39 | } 40 | 41 | #include "gBase64.h" // https://github.com/adamvr/arduino-base64 (I changed the name) 42 | #include "ESP-sc-gway.h" // This file contains configuration of GWay 43 | #include "RGBLed.h" // Thid file is for onboard RGBLED of WeMos Lora Shield 44 | 45 | int debug = 1; // Debug level! 0 is no msgs, 1 normal, 2 is extensive 46 | 47 | using namespace std; 48 | 49 | byte currentMode = 0x81; 50 | char message[256]; 51 | char b64[256]; 52 | bool sx1272 = true; // Actually we use sx1276/RFM95 53 | byte receivedbytes; 54 | 55 | uint32_t cp_nb_rx_rcv; 56 | uint32_t cp_nb_rx_ok; 57 | uint32_t cp_nb_rx_bad; 58 | uint32_t cp_nb_rx_nocrc; 59 | uint32_t cp_up_pkt_fwd; 60 | 61 | enum sf_t { SF7 = 7, SF8, SF9, SF10, SF11, SF12 }; 62 | 63 | uint8_t MAC_array[6]; 64 | char MAC_char[18]; 65 | 66 | #define OLED 67 | 68 | #ifdef OLED 69 | SSD1306 display(0x3d, D2, D1); 70 | #endif 71 | 72 | /******************************************************************************* 73 | 74 | Configure these values if necessary! 75 | 76 | *******************************************************************************/ 77 | 78 | // SX1276 - ESP8266 connections 79 | int ssPin = DEFAULT_PIN_SS; 80 | int dio0 = DEFAULT_PIN_DIO0; 81 | int RST = DEFAULT_PIN_RST; 82 | 83 | // Set spreading factor (SF7 - SF12) 84 | sf_t sf = SF7; 85 | 86 | // Set center frequency. If in doubt, choose the first one, comment all others 87 | // Each "real" gateway should support the first 3 frequencies according to LoRa spec. 88 | uint32_t freq = 868100000; // Channel 0, 868.1 MHz 89 | //uint32_t freq = 868300000; // Channel 1, 868.3 MHz 90 | //uint32_t freq = 868500000; // in Mhz! (868.5) 91 | //uint32_t freq = 867100000; // in Mhz! (867.1) 92 | //uint32_t freq = 867300000; // in Mhz! (867.3) 93 | //uint32_t freq = 867500000; // in Mhz! (867.5) 94 | //uint32_t freq = 867700000; // in Mhz! (867.7) 95 | //uint32_t freq = 867900000; // in Mhz! (867.9) 96 | //uint32_t freq = 868800000; // in Mhz! (868.8) 97 | //uint32_t freq = 869525000; // in Mhz! (869.525) 98 | // TTN defines an additional channel at 869.525Mhz using SF9 for class B. Not used 99 | 100 | // Set location, description and other configuration parameters 101 | // Defined in ESP-sc_gway.h 102 | // 103 | float lat = _LAT; // Configuration specific info... 104 | float lon = _LON; 105 | int alt = _ALT; 106 | char platform[24] = _PLATFORM; // platform definition 107 | char email[40] = _EMAIL; // used for contact email 108 | char description[64] = _DESCRIPTION; // used for free form description 109 | 110 | IPAddress ntpServer; // IP address of NTP_TIMESERVER 111 | IPAddress ttnServer; // IP Address of thethingsnetwork server 112 | 113 | WiFiUDP Udp; 114 | uint32_t lasttime; 115 | 116 | // You can switch webserver off if not necessary 117 | // Probably better to leave it in though. 118 | #if A_SERVER==1 119 | //#include // http://arduiniana.org/libraries/streaming/ 120 | ESP8266WebServer server(SERVERPORT); 121 | #endif 122 | 123 | 124 | // ---------------------------------------------------------------------------- 125 | // DIE is not use actively in the source code anymore. 126 | // It is replaced by a Serial.print command so we know that we have a problem 127 | // somewhere. 128 | // There are at least 3 other ways to restart the ESP. Pick one if you want. 129 | // ---------------------------------------------------------------------------- 130 | void die(const char *s) 131 | { 132 | Serial.println(s); 133 | delay(50); 134 | // system_restart(); // SDK function 135 | // ESP.reset(); 136 | abort(); // Within a second 137 | } 138 | 139 | // ---------------------------------------------------------------------------- 140 | // Print the current time 141 | // ---------------------------------------------------------------------------- 142 | void printTime() { 143 | const char * Days [] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; 144 | Serial.printf("%s %02d:%02d:%02d", Days[weekday() - 1], hour(), minute(), second() ); 145 | } 146 | 147 | 148 | // ---------------------------------------------------------------------------- 149 | // Convert a float to string for printing 150 | // f is value to convert 151 | // p is precision in decimal digits 152 | // val is character array for results 153 | // ---------------------------------------------------------------------------- 154 | void ftoa(float f, char *val, int p) { 155 | int j = 1; 156 | int ival, fval; 157 | char b[6]; 158 | 159 | for (int i = 0; i < p; i++) { 160 | j = j * 10; 161 | } 162 | 163 | ival = (int) f; // Make integer part 164 | fval = (int) ((f - ival) * j); // Make fraction. Has same sign as integer part 165 | if (fval < 0) fval = -fval; // So if it is negative make fraction positive again. 166 | // sprintf does NOT fit in memory 167 | strcat(val, itoa(ival, b, 10)); 168 | strcat(val, "."); // decimal point 169 | 170 | itoa(fval, b, 10); 171 | for (int i = 0; i < (p - strlen(b)); i++) strcat(val, "0"); 172 | // Fraction can be anything from 0 to 10^p , so can have less digits 173 | strcat(val, b); 174 | } 175 | 176 | // ============================================================================= 177 | // NTP TIME functions 178 | 179 | const int NTP_PACKET_SIZE = 48; // Fixed size of NTP record 180 | byte packetBuffer[NTP_PACKET_SIZE]; 181 | 182 | // ---------------------------------------------------------------------------- 183 | // Send the request packet to the NTP server. 184 | // 185 | // ---------------------------------------------------------------------------- 186 | void sendNTPpacket(IPAddress& timeServerIP) { 187 | // Zeroise the buffer. 188 | memset(packetBuffer, 0, NTP_PACKET_SIZE); 189 | packetBuffer[0] = 0b11100011; // LI, Version, Mode 190 | packetBuffer[1] = 0; // Stratum, or type of clock 191 | packetBuffer[2] = 6; // Polling Interval 192 | packetBuffer[3] = 0xEC; // Peer Clock Precision 193 | // 8 bytes of zero for Root Delay & Root Dispersion 194 | packetBuffer[12] = 49; 195 | packetBuffer[13] = 0x4E; 196 | packetBuffer[14] = 49; 197 | packetBuffer[15] = 52; 198 | 199 | Udp.beginPacket(timeServerIP, (int) 123); // NTP Server and Port 200 | 201 | if ((Udp.write((char *)packetBuffer, NTP_PACKET_SIZE)) != NTP_PACKET_SIZE) { 202 | die("sendNtpPacket:: Error write"); 203 | } 204 | else { 205 | // Success 206 | } 207 | Udp.endPacket(); 208 | } 209 | 210 | 211 | // ---------------------------------------------------------------------------- 212 | // Get the NTP time from one of the time servers 213 | // Note: As this function is called from SyncINterval in the background 214 | // make sure we have no blocking calls in this function 215 | // ---------------------------------------------------------------------------- 216 | time_t getNtpTime() 217 | { 218 | WiFi.hostByName(NTP_TIMESERVER, ntpServer); 219 | //while (Udp.parsePacket() > 0) ; // discard any previously received packets 220 | for (int i = 0 ; i < 4 ; i++) { // 5 retries. 221 | sendNTPpacket(ntpServer); 222 | uint32_t beginWait = millis(); 223 | while (millis() - beginWait < 5000) 224 | { 225 | if (Udp.parsePacket()) { 226 | Udp.read(packetBuffer, NTP_PACKET_SIZE); 227 | // Extract seconds portion. 228 | unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); 229 | unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); 230 | unsigned long secSince1900 = highWord << 16 | lowWord; 231 | Udp.flush(); 232 | return secSince1900 - 2208988800UL + NTP_TIMEZONES * SECS_PER_HOUR; 233 | // UTC is 1 TimeZone correction when no daylight saving time 234 | } 235 | //delay(10); 236 | } 237 | } 238 | return 0; // return 0 if unable to get the time 239 | } 240 | 241 | // ---------------------------------------------------------------------------- 242 | // Set up regular synchronization of NTP server and the local time. 243 | // ---------------------------------------------------------------------------- 244 | void setupTime() { 245 | setSyncProvider(getNtpTime); 246 | setSyncInterval(NTP_INTERVAL); 247 | } 248 | 249 | 250 | 251 | // ============================================================================ 252 | // UDP AND WLAN FUNCTIONS 253 | 254 | // ---------------------------------------------------------------------------- 255 | // GET THE DNS SERVER IP address 256 | // ---------------------------------------------------------------------------- 257 | IPAddress getDnsIP() { 258 | ip_addr_t dns_ip = dns_getserver(0); 259 | IPAddress dns = IPAddress(dns_ip.addr); 260 | return ((IPAddress) dns); 261 | } 262 | 263 | 264 | // ---------------------------------------------------------------------------- 265 | // Read a package 266 | // 267 | // ---------------------------------------------------------------------------- 268 | int readUdp(int packetSize) 269 | { 270 | char receiveBuffer[64]; //buffer to hold incoming packet 271 | Udp.read(receiveBuffer, packetSize); 272 | receiveBuffer[packetSize] = 0; 273 | IPAddress remoteIpNo = Udp.remoteIP(); 274 | unsigned int remotePortNo = Udp.remotePort(); 275 | if (debug >= 1) { 276 | Serial.print(F(" Received packet of size ")); 277 | Serial.print(packetSize); 278 | Serial.print(F(" From ")); 279 | Serial.print(remoteIpNo); 280 | Serial.print(F(", port ")); 281 | Serial.print(remotePortNo); 282 | Serial.print(F(", Contents: 0x")); 283 | for (int i = 0; i < packetSize; i++) { 284 | Serial.printf("%02X:", receiveBuffer[i]); 285 | } 286 | Serial.println(); 287 | } 288 | return packetSize; 289 | } 290 | 291 | // ---------------------------------------------------------------------------- 292 | // Function to join the Wifi Network 293 | // XXX Maybe we should make the reconnect shorter in order to avoid watchdog resets. 294 | // It is a matter of returning to the main loop() asap and make sure in next loop 295 | // the reconnect is done first thing. 296 | // ---------------------------------------------------------------------------- 297 | int WlanConnect(char * wifi_ssid, char * wifi_pass) { 298 | char thishost[17]; 299 | WiFiMode_t con_type ; 300 | int ret = WiFi.status(); 301 | 302 | WiFi.mode(WIFI_AP_STA); 303 | 304 | // Set Hostname for OTA and network (add only 2 last bytes of last MAC Address) 305 | sprintf_P(thishost, PSTR("ESP-TTN-GW-%04X"), ESP.getChipId() & 0xFFFF); 306 | 307 | if (debug >= 1) { 308 | Serial.print(F("========== SDK Saved parameters Start")); 309 | WiFi.printDiag(Serial); 310 | Serial.println(F("========== SDK Saved parameters End")); 311 | } 312 | 313 | if ( strncmp(wifi_ssid, "**", 2) && strncmp(wifi_pass, "**", 2) ) { 314 | Serial.println(F("Sketch contain SSID/PSK will set them")); 315 | } 316 | 317 | // No empty sketch SSID, try connect 318 | if (*wifi_ssid != '*' && *wifi_pass != '*' ) { 319 | Serial.printf("connecting to %s with psk %s\r\n", wifi_ssid, wifi_pass ); 320 | WiFi.begin(wifi_ssid, wifi_pass); 321 | } else { 322 | // empty sketch SSID, try autoconnect with SDK saved credentials 323 | Serial.println(F("No SSID/PSK defined in sketch\r\nConnecting with SDK ones if any")); 324 | } 325 | 326 | // Loop until connected or 20 sec time out 327 | unsigned long this_start = millis(); 328 | LedRGBON( COLOR_ORANGE_YELLOW, RGB_WIFI); 329 | LedRGBSetAnimation(333, RGB_WIFI, 0, RGB_ANIM_FADE_IN); 330 | while ( WiFi.status() != WL_CONNECTED && millis() - this_start < 20000 ) { 331 | LedRGBAnimate(); 332 | delay(1); 333 | } 334 | 335 | // Get latest WifI Status 336 | ret = WiFi.status(); 337 | 338 | // connected remove AP 339 | if ( ret == WL_CONNECTED ) { 340 | WiFi.mode(WIFI_STA); 341 | } else { 342 | // Need Access point configuration 343 | // SSID = hostname 344 | Serial.printf("Starting AP : %s", thishost); 345 | 346 | // STA+AP Mode without connected to STA, autoconnec will search 347 | // other frequencies while trying to connect, this is causing issue 348 | // to AP mode, so disconnect will avoid this 349 | if (ret != WL_CONNECTED) { 350 | // Disable auto retry search channel 351 | WiFi.disconnect(); 352 | } 353 | 354 | // protected network 355 | Serial.printf(" with key %s\r\n", _AP_PASS); 356 | WiFi.softAP(thishost, _AP_PASS); 357 | 358 | Serial.print(F("IP address : ")); Serial.println(WiFi.softAPIP()); 359 | Serial.print(F("MAC address : ")); Serial.println(WiFi.softAPmacAddress()); 360 | } 361 | 362 | #ifdef WEMOS_LORA_GW 363 | con_type = WiFi.getMode(); 364 | Serial.print(F("WIFI=")); 365 | if (con_type == WIFI_STA) { 366 | // OK breathing cyan 367 | wifi_led_color = COLOR_CYAN; 368 | Serial.print(F("STA")); 369 | } else if ( con_type == WIFI_AP || con_type == WIFI_AP_STA ) { 370 | // Owe're also in AP ? breathing Yellow 371 | wifi_led_color = COLOR_ORANGE; 372 | if (con_type == WIFI_AP) { 373 | Serial.print(F("AP")); 374 | } else { 375 | Serial.print(F("AP_STA")); 376 | } 377 | } else { 378 | // Error breathing red 379 | wifi_led_color = COLOR_RED; 380 | Serial.print(F("???")); 381 | } 382 | 383 | // Set Breathing color to indicate connexion type on LED 2 384 | LedRGBON(wifi_led_color, RGB_WIFI); 385 | #endif 386 | 387 | ArduinoOTA.setHostname(thishost); 388 | ArduinoOTA.begin(); 389 | 390 | // OTA callbacks 391 | ArduinoOTA.onStart([]() { 392 | // Light of the LED, stop animation 393 | LedRGBOFF(); 394 | Serial.println(F("\r\nOTA Starting")); 395 | }); 396 | 397 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 398 | uint8_t percent = progress / (total / 100); 399 | 400 | #ifdef RGB_LED_PIN 401 | // hue from 0.0 to 1.0 (rainbow) with 33% (of 0.5f) luminosity 402 | if (percent % 4 >= 2) { 403 | rgb_led.SetPixelColor(0, HslColor( (float) percent * 0.01f , 1.0f, 0.3f )); 404 | rgb_led.SetPixelColor(1, RgbColor(0)); 405 | } else { 406 | rgb_led.SetPixelColor(0, RgbColor(0)); 407 | rgb_led.SetPixelColor(1, HslColor( (float) percent * 0.01f , 1.0f, 0.3f )); 408 | } 409 | rgb_led.Show(); 410 | #endif 411 | 412 | if (percent % 10 == 0) { 413 | Serial.print('.'); 414 | } 415 | }); 416 | 417 | ArduinoOTA.onEnd([]() { 418 | #ifdef RGB_LED_PIN 419 | rgb_led.SetPixelColor(0, HslColor(COLOR_ORANGE / 360.0f, 1.0f, 0.3f)); 420 | rgb_led.SetPixelColor(1, HslColor(COLOR_ORANGE / 360.0f, 1.0f, 0.3f)); 421 | rgb_led.Show(); 422 | #endif 423 | Serial.println(F("Done Rebooting")); 424 | }); 425 | 426 | ArduinoOTA.onError([](ota_error_t error) { 427 | #ifdef RGB_LED_PIN 428 | rgb_led.SetPixelColor(0, HslColor(COLOR_RED / 360.0f, 1.0f, 0.3f)); 429 | rgb_led.SetPixelColor(1, HslColor(COLOR_RED / 360.0f, 1.0f, 0.3f)); 430 | rgb_led.Show(); 431 | #endif 432 | Serial.println(F("Error")); 433 | ESP.restart(); 434 | }); 435 | 436 | return ret; 437 | } 438 | 439 | // ---------------------------------------------------------------------------- 440 | // Send an UDP/DGRAM message to the MQTT server 441 | // If we send to more than one host (not sure why) then we need to set sockaddr 442 | // before sending. 443 | // ---------------------------------------------------------------------------- 444 | void sendUdp(char *msg, int length) { 445 | int l; 446 | bool err = true ; 447 | if (WiFi.status() != WL_CONNECTED) { 448 | Serial.println(F("sendUdp: ERROR not connected to WLAN")); 449 | Udp.flush(); 450 | } else { 451 | //send the update 452 | Udp.beginPacket(ttnServer, (int) PORT1); 453 | 454 | #ifdef SERVER2 455 | delay(1); 456 | Udp.beginPacket((char *)SERVER2, (int) PORT2); 457 | #endif 458 | 459 | if ((l = Udp.write((char *)msg, length)) != length) { 460 | Serial.println(F("sendUdp:: Error write")); 461 | } else { 462 | err = false; 463 | if (debug >= 2) { 464 | Serial.printf("sendUdp: sent %d bytes", l); 465 | } 466 | } 467 | 468 | yield(); 469 | Udp.endPacket(); 470 | } 471 | 472 | // 1 fade out animation green if okay else otherwhise 473 | LedRGBON(err ? COLOR_RED : COLOR_GREEN, RGB_WIFI, true); 474 | LedRGBSetAnimation(1000, RGB_WIFI, 1, RGB_ANIM_FADE_OUT); 475 | } 476 | 477 | 478 | // ---------------------------------------------------------------------------- 479 | // connect to UDP – returns true if successful or false if not 480 | // ---------------------------------------------------------------------------- 481 | bool UDPconnect() { 482 | 483 | bool ret = false; 484 | if (debug >= 1) Serial.println(F("Connecting to UDP")); 485 | unsigned int localPort = 1701; // XXX Do not listen to return messages from WiFi 486 | if (Udp.begin(localPort) == 1) { 487 | if (debug >= 1) Serial.println(F("Connection successful")); 488 | ret = true; 489 | } 490 | else { 491 | //Serial.println("Connection failed"); 492 | } 493 | return (ret); 494 | } 495 | 496 | // ================================================================================= 497 | // LORA GATEWAY FUNCTIONS 498 | // The LoRa supporting functions are in the section below 499 | 500 | // ---------------------------------------------------------------------------- 501 | // The SS (Chip select) pin is used to make sure the RFM95 is selected 502 | // ---------------------------------------------------------------------------- 503 | inline void selectreceiver() 504 | { 505 | digitalWrite(ssPin, LOW); 506 | } 507 | 508 | // ---------------------------------------------------------------------------- 509 | // ... or unselected 510 | // ---------------------------------------------------------------------------- 511 | inline void unselectreceiver() 512 | { 513 | digitalWrite(ssPin, HIGH); 514 | } 515 | 516 | // ---------------------------------------------------------------------------- 517 | // Read one byte value, par addr is address 518 | // Returns the value of register(addr) 519 | // ---------------------------------------------------------------------------- 520 | byte readRegister(byte addr) 521 | { 522 | selectreceiver(); 523 | SPI.beginTransaction(SPISettings(50000, MSBFIRST, SPI_MODE0)); 524 | SPI.transfer(addr & 0x7F); 525 | uint8_t res = SPI.transfer(0x00); 526 | SPI.endTransaction(); 527 | unselectreceiver(); 528 | return res; 529 | } 530 | 531 | // ---------------------------------------------------------------------------- 532 | // Write value to a register with address addr. 533 | // Function writes one byte at a time. 534 | // ---------------------------------------------------------------------------- 535 | void writeRegister(byte addr, byte value) 536 | { 537 | unsigned char spibuf[2]; 538 | 539 | spibuf[0] = addr | 0x80; 540 | spibuf[1] = value; 541 | selectreceiver(); 542 | SPI.beginTransaction(SPISettings(50000, MSBFIRST, SPI_MODE0)); 543 | SPI.transfer(spibuf[0]); 544 | SPI.transfer(spibuf[1]); 545 | SPI.endTransaction(); 546 | unselectreceiver(); 547 | } 548 | 549 | // ---------------------------------------------------------------------------- 550 | // This LoRa function reads a message from the LoRa transceiver 551 | // returns true when message correctly received or fails on error 552 | // (CRC error for example) 553 | // ---------------------------------------------------------------------------- 554 | bool receivePkt(char *payload) 555 | { 556 | // clear rxDone 557 | writeRegister(REG_IRQ_FLAGS, 0x40); 558 | 559 | int irqflags = readRegister(REG_IRQ_FLAGS); 560 | 561 | cp_nb_rx_rcv++; // Receive statistics counter 562 | 563 | // payload crc: 0x20 564 | if ((irqflags & 0x20) == 0x20) 565 | { 566 | Serial.println(F("CRC error")); 567 | writeRegister(REG_IRQ_FLAGS, 0x20); 568 | return false; 569 | } else { 570 | 571 | cp_nb_rx_ok++; // Receive OK statistics counter 572 | 573 | byte currentAddr = readRegister(REG_FIFO_RX_CURRENT_ADDR); 574 | byte receivedCount = readRegister(REG_RX_NB_BYTES); 575 | receivedbytes = receivedCount; 576 | 577 | writeRegister(REG_FIFO_ADDR_PTR, currentAddr); 578 | 579 | for (int i = 0; i < receivedCount; i++) 580 | { 581 | payload[i] = (char)readRegister(REG_FIFO); 582 | } 583 | } 584 | return true; 585 | } 586 | 587 | // ---------------------------------------------------------------------------- 588 | // Setup the LoRa environment on the connected transceiver. 589 | // - Determine the correct transceiver type (sx1272/RFM92 or sx1276/RFM95) 590 | // - Set the frequency to listen to (1-channel remember) 591 | // - Set Spreading Factor (standard SF7) 592 | // The reset RST pin might not be necessary for at least the RGM95 transceiver 593 | // ---------------------------------------------------------------------------- 594 | void SetupLoRa() 595 | { 596 | Serial.println(F("Trying to Setup LoRa Module with")); 597 | Serial.printf("CS=GPIO%d DIO0=GPIO%d Reset=", ssPin, dio0 ); 598 | 599 | if (RST == NOT_A_PIN ) { 600 | Serial.println(F("Unused")); 601 | } else { 602 | Serial.printf("GPIO%d\r\n", RST); 603 | digitalWrite(RST, HIGH); 604 | delay(100); 605 | digitalWrite(RST, LOW); 606 | delay(100); 607 | } 608 | 609 | byte version = readRegister(REG_VERSION); // Read the LoRa chip version id 610 | if (version == 0x22) { 611 | // sx1272 612 | Serial.println(F("SX1272 detected, starting.")); 613 | sx1272 = true; 614 | } else { 615 | // sx1276? 616 | if (RST != NOT_A_PIN ) { 617 | digitalWrite(RST, LOW); 618 | delay(100); 619 | digitalWrite(RST, HIGH); 620 | delay(100); 621 | } 622 | 623 | version = readRegister(REG_VERSION); 624 | if (version == 0x12) { 625 | // sx1276 626 | Serial.println(F("SX1276 detected, starting.")); 627 | sx1272 = false; 628 | } else { 629 | Serial.print(F("Unrecognized transceiver, version: 0x")); 630 | Serial.printf("%02X", version); 631 | die(""); 632 | } 633 | } 634 | 635 | writeRegister(REG_OPMODE, SX72_MODE_SLEEP); 636 | 637 | // set frequency 638 | uint64_t frf = ((uint64_t)freq << 19) / 32000000; 639 | writeRegister(REG_FRF_MSB, (uint8_t)(frf >> 16) ); 640 | writeRegister(REG_FRF_MID, (uint8_t)(frf >> 8) ); 641 | writeRegister(REG_FRF_LSB, (uint8_t)(frf >> 0) ); 642 | 643 | writeRegister(REG_SYNC_WORD, 0x34); // LoRaWAN public sync word 644 | 645 | // Set spreading Factor 646 | if (sx1272) { 647 | if (sf == SF11 || sf == SF12) { 648 | writeRegister(REG_MODEM_CONFIG, 0x0B); 649 | } else { 650 | writeRegister(REG_MODEM_CONFIG, 0x0A); 651 | } 652 | writeRegister(REG_MODEM_CONFIG2, (sf << 4) | 0x04); 653 | } else { 654 | if (sf == SF11 || sf == SF12) { 655 | writeRegister(REG_MODEM_CONFIG3, 0x0C); 656 | } else { 657 | writeRegister(REG_MODEM_CONFIG3, 0x04); 658 | } 659 | writeRegister(REG_MODEM_CONFIG, 0x72); 660 | writeRegister(REG_MODEM_CONFIG2, (sf << 4) | 0x04); 661 | } 662 | 663 | if (sf == SF10 || sf == SF11 || sf == SF12) { 664 | writeRegister(REG_SYMB_TIMEOUT_LSB, 0x05); 665 | } else { 666 | writeRegister(REG_SYMB_TIMEOUT_LSB, 0x08); 667 | } 668 | writeRegister(REG_MAX_PAYLOAD_LENGTH, 0x80); 669 | writeRegister(REG_PAYLOAD_LENGTH, PAYLOAD_LENGTH); 670 | writeRegister(REG_HOP_PERIOD, 0xFF); 671 | writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_BASE_AD)); 672 | 673 | // Set Continous Receive Mode 674 | writeRegister(REG_LNA, LNA_MAX_GAIN); // max lna gain 675 | writeRegister(REG_OPMODE, SX72_MODE_RX_CONTINUOS); 676 | } 677 | 678 | 679 | // ---------------------------------------------------------------------------- 680 | // Send periodic status message to server even when we do not receive any 681 | // data. 682 | // Parameter is socketr to TX to 683 | // ---------------------------------------------------------------------------- 684 | void sendstat() { 685 | // XXX removed static 686 | char status_report[STATUS_SIZE]; // status report as a JSON object 687 | char stat_timestamp[32]; // XXX was 24 688 | time_t t; 689 | char clat[8] = {0}; 690 | char clon[8] = {0}; 691 | 692 | int stat_index = 0; 693 | 694 | // pre-fill the data buffer with fixed fields 695 | status_report[0] = PROTOCOL_VERSION; 696 | status_report[3] = PKT_PUSH_DATA; 697 | 698 | // READ MAC ADDRESS OF ESP8266 699 | status_report[4] = MAC_array[0]; 700 | status_report[5] = MAC_array[1]; 701 | status_report[6] = MAC_array[2]; 702 | status_report[7] = 0xFF; 703 | status_report[8] = 0xFF; 704 | status_report[9] = MAC_array[3]; 705 | status_report[10] = MAC_array[4]; 706 | status_report[11] = MAC_array[5]; 707 | 708 | uint8_t token_h = (uint8_t)rand(); // random token 709 | uint8_t token_l = (uint8_t)rand(); // random token 710 | status_report[1] = token_h; 711 | status_report[2] = token_l; 712 | stat_index = 12; // 12-byte header 713 | 714 | t = now(); // get timestamp for statistics 715 | // %F Short YYYY-MM-DD date, %T %H:%M:%S, %Z CET 716 | //strftime(stat_timestamp, sizeof stat_timestamp, "%F %T %Z", gmtime(&t)); 717 | 718 | sprintf(stat_timestamp, "%d-%d-%d %d:%d:%d CET", year(), month(), day(), hour(), minute(), second()); 719 | 720 | ftoa(lat, clat, 4); // Convert lat to char array with 4 decimals 721 | ftoa(lon, clon, 4); // As Arduino CANNOT prints floats 722 | 723 | // Build the Status message in JSON format 724 | // XXX Split this one up... 725 | int j = snprintf((char *)(status_report + stat_index), STATUS_SIZE - stat_index, 726 | "{\"stat\":{\"time\":\"%s\",\"lati\":%s,\"long\":%s,\"alti\":%i,\"rxnb\":%u,\"rxok\":%u,\"rxfw\":%u,\"ackr\":%u.0,\"dwnb\":%u,\"txnb\":%u,\"pfrm\":\"%s\",\"mail\":\"%s\",\"desc\":\"%s\"}}", 727 | stat_timestamp, clat, clon, (int)alt, cp_nb_rx_rcv, cp_nb_rx_ok, cp_up_pkt_fwd, 0, 0, 0, platform, email, description); 728 | stat_index += j; 729 | status_report[stat_index] = 0; // add string terminator, for safety 730 | 731 | if (debug >= 1) { 732 | Serial.print(F("stat update: <")); 733 | Serial.print(stat_index); 734 | Serial.print(F("> ")); 735 | Serial.println((char *)(status_report + 12)); // DEBUG: display JSON stat 736 | } 737 | 738 | //send the update 739 | sendUdp(status_report, stat_index); 740 | 741 | } 742 | 743 | // ---------------------------------------------------------------------------- 744 | // Receive a LoRa package 745 | // 746 | // Receive a LoRa message and fill the buff_up char buffer. 747 | // returns values: 748 | // - returns the length of string returned in buff_up 749 | // - returns -1 when no message arrived. 750 | // ---------------------------------------------------------------------------- 751 | int receivepacket(char *buff_up) { 752 | 753 | long int SNR; 754 | int rssicorr; 755 | char cfreq[12] = {0}; // Character array to hold freq in MHz 756 | if (digitalRead(dio0) == 1) // READY? 757 | { 758 | if (receivePkt(message)) { 759 | byte value = readRegister(REG_PKT_SNR_VALUE); 760 | 761 | if ( value & 0x80 ) { // The SNR sign bit is 1 762 | // Invert and divide by 4 763 | value = ( ( ~value + 1 ) & 0xFF ) >> 2; 764 | SNR = -value; 765 | } else { 766 | // Divide by 4 767 | SNR = ( value & 0xFF ) >> 2; 768 | } 769 | 770 | if (sx1272) { 771 | rssicorr = 139; 772 | } else { // Probably SX1276 or RFM95 773 | rssicorr = 157; 774 | } 775 | 776 | if (debug >= 1) { 777 | Serial.print(F("Packet RSSI: ")); 778 | Serial.print(readRegister(0x1A) - rssicorr); 779 | Serial.print(F(" RSSI: ")); 780 | Serial.print(readRegister(0x1B) - rssicorr); 781 | Serial.print(F(" SNR: ")); 782 | Serial.print(SNR); 783 | Serial.print(F(" Length: ")); 784 | Serial.print((int)receivedbytes); 785 | Serial.println(); 786 | yield(); 787 | } 788 | 789 | int j; 790 | 791 | // XXX Base64 library is nopad. So we may have to add padding characters until 792 | // length is multiple of 4! 793 | int encodedLen = base64_enc_len(receivedbytes); // max 341 794 | base64_encode(b64, message, receivedbytes); // max 341 795 | 796 | //j = bin_to_b64((uint8_t *)message, receivedbytes, (char *)(b64), 341); 797 | //fwrite(b64, sizeof(char), j, stdout); 798 | 799 | int buff_index = 0; 800 | 801 | // pre-fill the data buffer with fixed fields 802 | buff_up[0] = PROTOCOL_VERSION; 803 | buff_up[3] = PKT_PUSH_DATA; 804 | 805 | // XXX READ MAC ADDRESS OF ESP8266 806 | buff_up[4] = MAC_array[0]; 807 | buff_up[5] = MAC_array[1]; 808 | buff_up[6] = MAC_array[2]; 809 | buff_up[7] = 0xFF; 810 | buff_up[8] = 0xFF; 811 | buff_up[9] = MAC_array[3]; 812 | buff_up[10] = MAC_array[4]; 813 | buff_up[11] = MAC_array[5]; 814 | 815 | // start composing datagram with the header 816 | uint8_t token_h = (uint8_t)rand(); // random token 817 | uint8_t token_l = (uint8_t)rand(); // random token 818 | buff_up[1] = token_h; 819 | buff_up[2] = token_l; 820 | buff_index = 12; /* 12-byte header */ 821 | 822 | // TODO: tmst can jump if time is (re)set, not good. 823 | struct timeval now; 824 | gettimeofday(&now, NULL); 825 | uint32_t tmst = (uint32_t)(now.tv_sec * 1000000 + now.tv_usec); 826 | 827 | // start of JSON structure that will make payload 828 | memcpy((void *)(buff_up + buff_index), (void *)"{\"rxpk\":[", 9); 829 | buff_index += 9; 830 | buff_up[buff_index] = '{'; 831 | ++buff_index; 832 | j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE - buff_index, "\"tmst\":%u", tmst); 833 | buff_index += j; 834 | ftoa((double)freq / 1000000, cfreq, 6); // XXX This can be done better 835 | j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE - buff_index, ",\"chan\":%1u,\"rfch\":%1u,\"freq\":%s", 0, 0, cfreq); 836 | buff_index += j; 837 | memcpy((void *)(buff_up + buff_index), (void *)",\"stat\":1", 9); 838 | buff_index += 9; 839 | memcpy((void *)(buff_up + buff_index), (void *)",\"modu\":\"LORA\"", 14); 840 | buff_index += 14; 841 | /* Lora datarate & bandwidth, 16-19 useful chars */ 842 | switch (sf) { 843 | case SF7: 844 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF7", 12); 845 | buff_index += 12; 846 | break; 847 | case SF8: 848 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF8", 12); 849 | buff_index += 12; 850 | break; 851 | case SF9: 852 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF9", 12); 853 | buff_index += 12; 854 | break; 855 | case SF10: 856 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF10", 13); 857 | buff_index += 13; 858 | break; 859 | case SF11: 860 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF11", 13); 861 | buff_index += 13; 862 | break; 863 | case SF12: 864 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF12", 13); 865 | buff_index += 13; 866 | break; 867 | default: 868 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF?", 12); 869 | buff_index += 12; 870 | } 871 | memcpy((void *)(buff_up + buff_index), (void *)"BW125\"", 6); 872 | buff_index += 6; 873 | memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"4/5\"", 13); 874 | buff_index += 13; 875 | j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE - buff_index, ",\"lsnr\":%li", SNR); 876 | buff_index += j; 877 | j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE - buff_index, ",\"rssi\":%d,\"size\":%u", readRegister(0x1A) - rssicorr, receivedbytes); 878 | buff_index += j; 879 | memcpy((void *)(buff_up + buff_index), (void *)",\"data\":\"", 9); 880 | buff_index += 9; 881 | 882 | // Use gBase64 library 883 | encodedLen = base64_enc_len(receivedbytes); // max 341 884 | j = base64_encode((char *)(buff_up + buff_index), message, receivedbytes); 885 | 886 | buff_index += j; 887 | buff_up[buff_index++] = '"'; 888 | 889 | // End of packet serialization 890 | buff_up[buff_index++] = '}'; 891 | buff_up[buff_index++] = ']'; 892 | // end of JSON datagram payload */ 893 | buff_up[buff_index++] = '}'; 894 | buff_up[buff_index] = 0; // add string terminator, for safety 895 | 896 | if (debug >= 1) { 897 | Serial.print(F("rxpk update: ")); 898 | Serial.println((char *)(buff_up + 12)); // DEBUG: display JSON payload 899 | } 900 | // OLED display 901 | #ifdef OLED 902 | display.clear(); 903 | display.setFont(Open_Sans_13); 904 | display.setTextAlignment(TEXT_ALIGN_LEFT); 905 | char timBuff[20]; 906 | sprintf(timBuff, "%02i:%02i:%02i", hour(), minute(), second()); 907 | display.drawString(0, 0, "Time: " ); 908 | display.drawString(40, 0, timBuff); 909 | display.drawString(0, 15, "RSSI: " ); 910 | display.drawString(40, 15, String(readRegister(0x1A) - rssicorr) ); 911 | display.drawString(0, 30, "SNR: " ); 912 | display.drawString(40, 30, String(SNR) ); 913 | display.drawString(0, 45, "LEN: " ); 914 | display.drawString(40, 45, String(receivedbytes) ); 915 | display.display(); 916 | #endif 917 | 918 | 919 | 920 | return (buff_index); 921 | 922 | } // received a message 923 | } // dio0=1 924 | return (-1); 925 | } 926 | 927 | 928 | // ================================================================================ 929 | // WEBSERVER FUNCTIONS (PORT 8080) 930 | 931 | #if A_SERVER==1 932 | 933 | 934 | // ---------------------------------------------------------------------------- 935 | // Output the 4-byte IP address for easy printing 936 | // ---------------------------------------------------------------------------- 937 | String printIP(IPAddress ipa) { 938 | String response; 939 | response += (IPAddress)ipa[0]; response += "."; 940 | response += (IPAddress)ipa[1]; response += "."; 941 | response += (IPAddress)ipa[2]; response += "."; 942 | response += (IPAddress)ipa[3]; 943 | return (response); 944 | } 945 | 946 | // ---------------------------------------------------------------------------- 947 | // stringTime 948 | // Only when RTC is present we print real time values 949 | // t contains number of milli seconds since system started that the event happened. 950 | // So a value of 100 wold mean that the event took place 1 minute and 40 seconds ago 951 | // ---------------------------------------------------------------------------- 952 | String stringTime(unsigned long t) { 953 | String response; 954 | String Days[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; 955 | 956 | if (t == 0) { 957 | response = " -none- "; 958 | return (response); 959 | } 960 | 961 | // now() gives seconds since 1970 962 | time_t eventTime = now() - ((millis() - t) / 1000); 963 | byte _hour = hour(eventTime); 964 | byte _minute = minute(eventTime); 965 | byte _second = second(eventTime); 966 | 967 | response += Days[weekday(eventTime) - 1]; response += " "; 968 | response += day(eventTime); response += "-"; 969 | response += month(eventTime); response += "-"; 970 | response += year(eventTime); response += " "; 971 | if (_hour < 10) response += "0"; 972 | response += _hour; response += ":"; 973 | if (_minute < 10) response += "0"; 974 | response += _minute; response += ":"; 975 | if (_second < 10) response += "0"; 976 | response += _second; 977 | return (response); 978 | } 979 | 980 | 981 | // ---------------------------------------------------------------------------- 982 | // WIFI SERVER 983 | // 984 | // This funtion implements the WiFI Webserver (very simple one). The purpose 985 | // of this server is to receive simple admin commands, and execute these 986 | // results are sent back to the web client. 987 | // Commands: DEBUG, ADDRESS, IP, CONFIG, GETTIME, SETTIME 988 | // The webpage is completely built response and then printed on screen. 989 | // ---------------------------------------------------------------------------- 990 | void WifiServer(const char *cmd, const char *arg) { 991 | 992 | String response; 993 | char *dup, *pch; 994 | 995 | yield(); 996 | if (debug >= 2) { 997 | Serial.print(F("WifiServer new client")); 998 | } 999 | 1000 | // These can be used as a single argument 1001 | if (strcmp(cmd, "DEBUG") == 0) { // Set debug level 0-2 1002 | debug = atoi(arg); response += " debug="; response += arg; 1003 | } 1004 | if (strcmp(cmd, "IP") == 0) { // List local IP address 1005 | response += " local IP="; 1006 | response += (IPAddress) WiFi.localIP()[0]; response += "."; 1007 | response += (IPAddress) WiFi.localIP()[1]; response += "."; 1008 | response += (IPAddress) WiFi.localIP()[2]; response += "."; 1009 | response += (IPAddress) WiFi.localIP()[3]; 1010 | } 1011 | 1012 | if (strcmp(cmd, "GETTIME") == 0) { 1013 | response += "gettime tbd"; // Get the local time 1014 | } 1015 | if (strcmp(cmd, "SETTIME") == 0) { 1016 | response += "settime tbd"; // Set the local time 1017 | } 1018 | if (strcmp(cmd, "HELP") == 0) { 1019 | response += "Display Help Topics"; 1020 | } 1021 | if (strcmp(cmd, "RESET") == 0) { 1022 | response += "Resetting Statistics"; 1023 | cp_nb_rx_rcv = 0; 1024 | cp_nb_rx_ok = 0; 1025 | cp_up_pkt_fwd = 0; 1026 | } 1027 | 1028 | // Do work, fill the webpage 1029 | delay(15); 1030 | response += ""; 1031 | response += ""; 1032 | response += "ESP8266 1ch Gateway"; 1033 | response += ""; 1034 | response += ""; 1035 | 1036 | response += "

ESP Gateway Config:

"; 1037 | response += "Version: "; response += VERSION; 1038 | response += "
ESP is alive since "; response += stringTime(1); 1039 | response += "
Current time is "; response += stringTime(millis()); 1040 | response += "
"; 1041 | 1042 | response += "

WiFi Config

"; 1043 | response += ""; 1044 | response += ""; 1045 | response += ""; 1046 | response += ""; 1047 | response += ""; 1048 | response += ""; 1049 | response += ""; 1050 | response += ""; 1051 | response += ""; 1052 | response += ""; 1053 | response += "
ParameterValue
IP Address"; response += printIP((IPAddress)WiFi.localIP()); response += "
IP Gateway"; response += printIP((IPAddress)WiFi.gatewayIP()); response += "
NTP Server"; response += NTP_TIMESERVER; response += "
LoRa Router"; response += _TTNSERVER; response += "
LoRa Router IP"; response += printIP((IPAddress)ttnServer); response += "
"; 1054 | 1055 | response += "

System Status

"; 1056 | response += ""; 1057 | response += ""; 1058 | response += ""; 1059 | response += ""; 1060 | response += ""; 1061 | response += ""; 1062 | response += ""; 1063 | response += "
ParameterValue
Free heap"; response += ESP.getFreeHeap(); response += "
ESP Chip ID"; response += ESP.getChipId(); response += "
"; 1064 | 1065 | response += "

LoRa Status

"; 1066 | response += ""; 1067 | response += ""; 1068 | response += ""; 1069 | response += ""; 1070 | response += ""; 1071 | response += ""; 1072 | response += ""; 1073 | response += ""; 1082 | response += "
ParameterValue
Frequency"; response += freq; response += "
Spreading Factor"; response += sf; response += "
Gateway ID"; 1074 | response += String(MAC_array[0], HEX); // The MAC array is always returned in lowercase 1075 | response += String(MAC_array[1], HEX); 1076 | response += String(MAC_array[2], HEX); 1077 | response += "ffff"; 1078 | response += String(MAC_array[3], HEX); 1079 | response += String(MAC_array[4], HEX); 1080 | response += String(MAC_array[5], HEX); 1081 | response += "
"; 1083 | 1084 | response += "

Statistics

"; 1085 | delay(1); 1086 | response += ""; 1087 | response += ""; 1088 | response += ""; 1089 | response += ""; 1090 | response += ""; 1091 | response += ""; 1092 | response += ""; 1093 | response += ""; 1094 | response += ""; 1095 | 1096 | response += "
CounterValue
Packages Received"; response += cp_nb_rx_rcv; response += "
Packages OK "; response += cp_nb_rx_ok; response += "
Packages Forwarded"; response += cp_up_pkt_fwd; response += "
 
"; 1097 | 1098 | response += "
"; 1099 | response += "

Settings

"; 1100 | response += "Click here to reset statistics
"; 1101 | 1102 | response += "Debug level is: "; 1103 | response += debug; 1104 | response += " set to: "; 1105 | response += " 0"; 1106 | response += " 1"; 1107 | response += " 2
"; 1108 | 1109 | response += "Click here to explain Help and REST options
"; 1110 | response += ""; 1111 | 1112 | server.send(200, "text/html", response); 1113 | 1114 | delay(5); 1115 | free(dup); // free the memory used, before jumping to other page 1116 | } 1117 | 1118 | #endif 1119 | 1120 | 1121 | 1122 | // ======================================================================== 1123 | // MAIN PROGRAM (SETUP AND LOOP) 1124 | 1125 | // ---------------------------------------------------------------------------- 1126 | // Setup code (one time) 1127 | // ---------------------------------------------------------------------------- 1128 | void setup () { 1129 | 1130 | WiFiMode_t con_type ; 1131 | int ret = WiFi.status(); 1132 | 1133 | Serial.begin(_BAUDRATE); // As fast as possible for bus 1134 | 1135 | Serial.print(F("\r\nBooting ")); 1136 | Serial.println(ARDUINO_BOARD " " __DATE__ " " __TIME__); 1137 | 1138 | if (debug >= 1) { 1139 | Serial.print(F("! debug: ")); 1140 | } 1141 | 1142 | #ifdef WEMOS_LORA_GW 1143 | rgb_led.Begin(); 1144 | LedRGBOFF(); 1145 | #endif 1146 | 1147 | // OLED display 1148 | #ifdef OLED 1149 | // Initialising the UI will init the display too. 1150 | display.init(); 1151 | display.flipScreenVertically(); 1152 | display.setFont(ArialMT_Plain_24); 1153 | display.setTextAlignment(TEXT_ALIGN_LEFT); 1154 | display.drawString(0, 24, "STARTING"); 1155 | display.display(); 1156 | #endif 1157 | 1158 | 1159 | 1160 | // Setup WiFi UDP connection. Give it some time .. 1161 | if (WlanConnect( (char *) _SSID, (char *)_PASS) == WL_CONNECTED) { 1162 | // If we are here we are connected to WLAN 1163 | // So now test the UDP function 1164 | if (!UDPconnect()) { 1165 | Serial.println("Error UDPconnect"); 1166 | } 1167 | } 1168 | 1169 | WiFi.macAddress(MAC_array); 1170 | for (int i = 0; i < sizeof(MAC_array); ++i) { 1171 | sprintf(MAC_char, "%s%02x:", MAC_char, MAC_array[i]); 1172 | } 1173 | Serial.print("MAC: "); 1174 | Serial.println(MAC_char); 1175 | 1176 | // Configure IO Pin 1177 | pinMode(ssPin, OUTPUT); 1178 | pinMode(dio0, INPUT); 1179 | pinMode(RST, OUTPUT); 1180 | 1181 | SPI.begin(); 1182 | delay(100); 1183 | SetupLoRa(); 1184 | delay(100); 1185 | 1186 | // We choose the Gateway ID to be the Ethernet Address of our Gateway card 1187 | // display results of getting hardware address 1188 | // 1189 | Serial.print("Gateway ID: "); 1190 | Serial.print(MAC_array[0], HEX); 1191 | Serial.print(MAC_array[1], HEX); 1192 | Serial.print(MAC_array[2], HEX); 1193 | Serial.print(0xFF, HEX); 1194 | Serial.print(0xFF, HEX); 1195 | Serial.print(MAC_array[3], HEX); 1196 | Serial.print(MAC_array[4], HEX); 1197 | Serial.print(MAC_array[5], HEX); 1198 | Serial.print(",Listening at SF"); 1199 | Serial.print(sf); 1200 | Serial.print(" on "); 1201 | Serial.print((double)freq / 1000000); 1202 | Serial.println(" Mhz."); 1203 | Serial.println(WiFi.localIP()); 1204 | 1205 | WiFi.hostByName(_TTNSERVER, ttnServer); // Use DNS to get server IP once 1206 | delay(100); 1207 | 1208 | setupTime(); // Set NTP time host and interval 1209 | setTime((time_t)getNtpTime()); 1210 | Serial.print("time "); printTime(); 1211 | Serial.println(); 1212 | 1213 | #if A_SERVER==1 1214 | server.on("/", []() { 1215 | WifiServer("", ""); 1216 | }); 1217 | server.on("/HELP", []() { 1218 | WifiServer("HELP", ""); 1219 | }); 1220 | server.on("/RESET", []() { 1221 | WifiServer("RESET", ""); 1222 | }); 1223 | server.on("/DEBUG=0", []() { 1224 | WifiServer("DEBUG", "0"); 1225 | }); 1226 | server.on("/DEBUG=1", []() { 1227 | WifiServer("DEBUG", "1"); 1228 | }); 1229 | server.on("/DEBUG=2", []() { 1230 | WifiServer("DEBUG", "2"); 1231 | }); 1232 | 1233 | server.begin(); // Start the webserver 1234 | Serial.print(F("Admin Server started on port ")); 1235 | Serial.println(SERVERPORT); 1236 | #endif 1237 | Serial.println("---------------------------------"); 1238 | 1239 | // Breathing now set to 1000ms 1240 | LedRGBSetAnimation(1000, RGB_WIFI); 1241 | LedRGBOFF(RGB_RF); 1242 | 1243 | // OLED dieplay 1244 | #ifdef OLED 1245 | // Initialising the UI will init the display too. 1246 | display.clear(); 1247 | display.setFont(ArialMT_Plain_24); 1248 | display.drawString(0, 24, "READY"); 1249 | display.display(); 1250 | #endif 1251 | 1252 | } 1253 | 1254 | // ---------------------------------------------------------------------------- 1255 | // LOOP 1256 | // This is the main program that is executed time and time again. 1257 | // We need to geive way to the bacjend WiFi processing that 1258 | // takes place somewhere in the ESP8266 firmware and therefore 1259 | // we include yield() statements at important points. 1260 | // 1261 | // Note: If we spend too much time in user processing functions 1262 | // and the backend system cannot do its housekeeping, the watchdog 1263 | // function will be executed which means effectively that the 1264 | // program crashes. 1265 | // ---------------------------------------------------------------------------- 1266 | void loop () 1267 | { 1268 | static bool led_state ; 1269 | bool new_led_state ; 1270 | int buff_index; 1271 | char buff_up[TX_BUFF_SIZE]; // buffer to compose the upstream packet 1272 | 1273 | // Receive Lora messages 1274 | if ((buff_index = receivepacket(buff_up)) >= 0) { // read is successful 1275 | yield(); 1276 | LedRGBON(COLOR_MAGENTA, RGB_RF, true); 1277 | LedRGBSetAnimation(1000, RGB_RF, 1, RGB_ANIM_FADE_OUT); 1278 | sendUdp(buff_up, buff_index); // We can send to multiple sockets if necessary 1279 | } 1280 | else { 1281 | // No message received 1282 | } 1283 | 1284 | // Receive WiFi messages. This is important since the TTN broker will return confirmation 1285 | // messages on UDP for every message sent by the gateway. 1286 | int packetSize = Udp.parsePacket(); 1287 | if (packetSize > 0) { 1288 | yield(); 1289 | if (readUdp(packetSize) > 0) { 1290 | // 1 fade out animation green if okay else otherwhise 1291 | LedRGBON(COLOR_GREEN, RGB_WIFI, true); 1292 | } else { 1293 | LedRGBON(COLOR_ORANGE, RGB_WIFI, true); 1294 | } 1295 | LedRGBSetAnimation(1000, RGB_WIFI, 1, RGB_ANIM_FADE_OUT); 1296 | } 1297 | 1298 | uint32_t nowseconds = (uint32_t) millis() / 1000; 1299 | if (nowseconds - lasttime >= 30) { // Send status every 30 seconds 1300 | sendstat(); 1301 | lasttime = nowseconds; 1302 | } 1303 | 1304 | // Handle the WiFi server part of this sketch. Mainly used for administration of the node 1305 | #if A_SERVER==1 1306 | server.handleClient(); 1307 | #endif 1308 | 1309 | // On board Led blink management 1310 | if (WiFi.status() == WL_CONNECTED) { 1311 | new_led_state = ((millis() % 1000) < 200) ? LOW : HIGH; // Connected long blink 200ms on each second 1312 | } else { 1313 | new_led_state = ((millis() % 333) < 111) ? LOW : HIGH; // AP Mode or client failed quick blink 111ms on each 1/3sec 1314 | } 1315 | // Led management 1316 | if (led_state != new_led_state) { 1317 | led_state = new_led_state; 1318 | digitalWrite(LED_BUILTIN, led_state); 1319 | } 1320 | 1321 | #ifdef WEMOS_LORA_GW 1322 | // RF RGB LED should be off if no animation is running place 1323 | if ( animationState[0].RgbEffectState == RGB_ANIM_NONE) { 1324 | LedRGBOFF(RGB_RF); 1325 | } 1326 | // WIFI RGB LED should breath if no animation is running place 1327 | if ( animationState[1].RgbEffectState == RGB_ANIM_NONE) { 1328 | LedRGBON(wifi_led_color, RGB_WIFI); 1329 | LedRGBSetAnimation(1000, RGB_WIFI); 1330 | } 1331 | 1332 | // Manage Animations 1333 | LedRGBAnimate(); 1334 | #endif 1335 | 1336 | // Handle OTA 1337 | ArduinoOTA.handle(); 1338 | } 1339 | -------------------------------------------------------------------------------- /ESP-sc-gway/LICENSE: -------------------------------------------------------------------------------- 1 | Applicable to gBase64.h and gBase64.c: 2 | 3 | Copyright (C) 2013, SEMTECH S.A. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | * Neither the name of the Semtech corporation nor the 14 | names of its contributors may be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY 21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /ESP-sc-gway/README.md: -------------------------------------------------------------------------------- 1 | Single Channel LoRaWAN Gateway 2 | ============================== 3 | This repository contains a proof-of-concept implementation of a single 4 | channel LoRaWAN gateway. It has been tested on the Wemos D1 Mini, using a 5 | Semtech SX1276 transceiver (HopeRF RFM95W). 6 | 7 | The code is for testing and development purposes only, and is not meant 8 | for production usage. 9 | 10 | Engine is based on code base of Single Channel gateway for RaspberryPI 11 | which is developed by Thomas Telkamp. Code was ported and extended to run 12 | on ESP 8266 mcu and provide RTC, Webserver and DNS services. 13 | 14 | Maintained by Maarten Westenberg (mw12554@hotmail.com) 15 | 16 | Features 17 | -------- 18 | - listen on configurable frequency and spreading factor 19 | - SF7 to SF12 20 | - status updates 21 | - can forward to two servers 22 | - DNS support for server lookup 23 | - NTP Support for time sync with internet time servers 24 | - Webserver support (default port 8080) 25 | - OTA Updates 26 | - Access Point Mode (for OTA) 27 | 28 | Not (yet) supported: 29 | - PACKET_PUSH_ACK processing 30 | - SF7BW250 modulation 31 | - FSK modulation 32 | - downstream messages (tx) 33 | 34 | Added features if you're using [WeMos-Lora][3] Shield as gateway 35 | - 2 On board RGB LED for visual 36 | - 1 SSD1306 I2C OLED connector 37 | - 1 On board push button 38 | 39 | Assembled WeMos-Lora Shield 40 | 41 | Top 42 | Bottom 43 | 44 | 45 | Dependencies 46 | ------------ 47 | 48 | - [gBase64][7] library by Adam Rudd is now integrated in the project, no need to install 49 | - [Time][5] library Arduino [documentation][6] 50 | - [NeoPixelBus][4] library is you're using [WeMos Lora][3] Shield as gateway 51 | 52 | Connections 53 | ----------- 54 | See [things4u][8] in the [hardware][9] section for building and connection instructions 55 | See [WeMos-Lora][3] github if you're using WeMos Lora Shield as gateway 56 | 57 | 58 | Configuration 59 | ------------- 60 | 61 | Defaults: 62 | 63 | - LoRa: SF7 at 868.1 Mhz 64 | - Server: 54.229.214.112, port 1700 (The Things Network: croft.thethings.girovito.nl) 65 | or directly croft.thethings.girovito.nl 66 | 67 | Edit .h file (ESP-sc-gway.h) to change configuration (look for: "Configure these values!"). 68 | 69 | Please set location, email and description. 70 | 71 | License 72 | ------- 73 | The source files in this repository are made available under the Eclipse 74 | Public License v1.0, except for the base64 implementation, that has been 75 | copied from the Semtech Packet Forwader. 76 | 77 | 78 | [2]: https://hallard.me 79 | [3]: https://github.com/hallard/WeMos-Lora 80 | [4]: https://github.com/Makuna/NeoPixelBus 81 | [5]: https://github.com/PaulStoffregen/Time 82 | [6]: http://playground.arduino.cc/code/time 83 | [7]: https://github.com/adamvr/arduino-base64 84 | [8]: http://things4u.github.io 85 | [9]: http://things4u.github.io/HardwareGuide/hardware_guide.html 86 | -------------------------------------------------------------------------------- /ESP-sc-gway/RGBLed.cpp: -------------------------------------------------------------------------------- 1 | // ********************************************************************************** 2 | // RGB Led source file for ESP-1 Channel Gateway 3 | // ********************************************************************************** 4 | // Creative Commons Attrib Share-Alike License 5 | // You are free to use/extend this library but please abide with the CC-BY-SA license: 6 | // http://creativecommons.org/licenses/by-sa/4.0/ 7 | // 8 | // Written by Charles-Henri Hallard (http://hallard.me) 9 | // 10 | // History : V1.20 2016-06-11 - Creation 11 | // 12 | // All text above must be included in any redistribution. 13 | // 14 | // ********************************************************************************** 15 | 16 | #include "RGBLed.h" 17 | 18 | #ifdef WEMOS_LORA_GW 19 | 20 | MyPixelBus rgb_led(RGB_LED_COUNT, RGB_LED_PIN); 21 | uint8_t rgb_luminosity = 20 ; // Luminosity from 0 to 100% 22 | uint16_t wifi_led_color ; // Wifi Led Color dependinf on connexion type 23 | 24 | // one entry per pixel to match the animation timing manager 25 | NeoPixelAnimator animations(RGB_LED_COUNT); 26 | MyAnimationState animationState[RGB_LED_COUNT]; 27 | 28 | /* ====================================================================== 29 | Function: LedRGBBlinkAnimUpdate 30 | Purpose : Blink Anim update for RGB Led 31 | Input : - 32 | Output : - 33 | Comments: grabbed from NeoPixelBus library examples 34 | ====================================================================== */ 35 | void LedRGBBlinkAnimUpdate(const AnimationParam& param) 36 | { 37 | // this gets called for each animation on every time step 38 | // progress will start at 0.0 and end at 1.0 39 | rgb_led.SetPixelColor(param.index, RgbColor(0)); 40 | 41 | // 25% on so 75% off 42 | if (param.progress < 0.25f) { 43 | rgb_led.SetPixelColor(param.index, animationState[param.index].RgbNoEffectColor); 44 | } 45 | 46 | // done, time to restart this position tracking animation/timer 47 | if (param.state == AnimationState_Completed) 48 | animations.RestartAnimation(param.index); 49 | } 50 | 51 | /* ====================================================================== 52 | Function: LedRGBFadeAnimUpdate 53 | Purpose : Fade in and out effect for RGB Led 54 | Input : - 55 | Output : - 56 | Comments: grabbed from NeoPixelBus library examples 57 | ====================================================================== */ 58 | void LedRGBFadeAnimUpdate(const AnimationParam& param) 59 | { 60 | // this gets called for each animation on every time step 61 | // progress will start at 0.0 and end at 1.0 62 | // apply a exponential curve to both front and back 63 | float progress = param.progress; 64 | 65 | if (animationState[param.index].RgbEffectState==RGB_ANIM_FADE_IN) 66 | progress = NeoEase::QuadraticOut(param.progress) ; 67 | 68 | if (animationState[param.index].RgbEffectState==RGB_ANIM_FADE_OUT) 69 | progress = NeoEase::QuadraticIn(param.progress) ; 70 | 71 | // we use the blend function on the RgbColor to mix 72 | // color based on the progress given to us in the animation 73 | #ifdef RGBW_LED 74 | RgbwColor updatedColor = RgbwColor::LinearBlend( 75 | animationState[param.index].RgbStartingColor, 76 | animationState[param.index].RgbEndingColor, 77 | progress); 78 | rgb_led.SetPixelColor(param.index, updatedColor); 79 | #else 80 | RgbColor updatedColor = RgbColor::LinearBlend( 81 | animationState[param.index].RgbStartingColor, 82 | animationState[param.index].RgbEndingColor, 83 | progress); 84 | rgb_led.SetPixelColor(param.index, updatedColor); 85 | #endif 86 | } 87 | 88 | /* ====================================================================== 89 | Function: LedRGBAnimate 90 | Purpose : Manage RGBLed Animations 91 | Input : true if forcing setup 92 | Output : - 93 | Comments: 94 | ====================================================================== */ 95 | void LedRGBAnimate(bool force) 96 | { 97 | static unsigned long ctx; 98 | 99 | if ( animations.IsAnimating() && !force ) { 100 | // the normal loop just needs these two to run the active animations 101 | animations.UpdateAnimations(); 102 | rgb_led.Show(); 103 | 104 | } else { 105 | 106 | ctx++; 107 | 108 | // Loop trough animations 109 | for (uint8_t i=0 ; i1) { 118 | animationState[i].AnimCount--; 119 | //Debugf("%d Animation(%d) Count=%d", ctx, i, animationState[i].AnimCount); 120 | 121 | // Last one ? 122 | if (animationState[i].AnimCount==1) { 123 | // Stop this animation 124 | animationState[i].RgbEffectState=RGB_ANIM_NONE; 125 | animationState[i].AnimTime=0; 126 | animations.StopAnimation(i); 127 | restart = false; 128 | //Debugf(" Stopping effect=%d", animationState[i].RgbEffectState ); 129 | } 130 | 131 | //Debugln(""); 132 | } 133 | 134 | effect = animationState[i].RgbEffectState; 135 | 136 | // And a time value 137 | if (animationState[i].AnimTime == 0 ) 138 | restart = false; 139 | 140 | // Fade in 141 | if ( effect==RGB_ANIM_FADE_IN ) { 142 | animationState[i].RgbEndingColor = animationState[i].RgbNoEffectColor; // selected color 143 | animationState[i].RgbEffectState=RGB_ANIM_FADE_OUT; // Next 144 | if (restart) 145 | animations.StartAnimation(i, animationState[i].AnimTime, LedRGBFadeAnimUpdate); 146 | 147 | // Fade out 148 | } else if ( effect==RGB_ANIM_FADE_OUT ) { 149 | animationState[i].RgbEndingColor = RgbColor(0); // off 150 | animationState[i].RgbEffectState=RGB_ANIM_FADE_IN; // Next 151 | if (restart) 152 | animations.StartAnimation(i, animationState[i].AnimTime, LedRGBFadeAnimUpdate); 153 | 154 | // Blink ON 155 | } else if ( effect==RGB_ANIM_BLINK_ON ) { 156 | animationState[i].RgbEffectState=RGB_ANIM_BLINK_OFF; // Next 157 | if (restart) 158 | animations.StartAnimation(i, animationState[i].AnimTime, LedRGBBlinkAnimUpdate); 159 | 160 | // Blink OFF 161 | } else if ( effect==RGB_ANIM_BLINK_OFF ) { 162 | animationState[i].RgbEffectState=RGB_ANIM_BLINK_ON; // Next 163 | if (restart) 164 | animations.StartAnimation(i, animationState[i].AnimTime, LedRGBBlinkAnimUpdate); 165 | } 166 | //Debugf("%d Animation(%d) restart=%d, duration=%ld, count=%d, effect=%d\r\n", ctx, i, restart, animationState[i].AnimTime, animationState[i].AnimCount, animationState[i].RgbEffectState ); 167 | } 168 | } 169 | } 170 | 171 | /* ====================================================================== 172 | Function: LedRGBSetAnimation 173 | Purpose : Manage RGBLed Animations 174 | Input : animation duration (in ms) 175 | led number (from 1 to ...), if 0 then all leds 176 | number of animation count (0 = infinite) 177 | animation type 178 | Input : - 179 | Output : - 180 | Comments: 181 | ====================================================================== */ 182 | void LedRGBSetAnimation(uint16_t duration, uint16_t led, uint8_t count, RgbEffectState_e effect) 183 | { 184 | uint8_t start = 0; 185 | uint8_t end = RGB_LED_COUNT-1; // Start at 0 186 | 187 | // just one LED ? 188 | // Strip start 0 not 1 189 | if (led) { 190 | led--; 191 | start = led ; 192 | end = start ; 193 | } 194 | 195 | // Specific counter for animation ? 196 | if (count) { 197 | // Counting stopping to 1 (0 used for infinite) so add 1 198 | // Counting is decremented before doing, so add 1 again 199 | count+=2; 200 | } 201 | 202 | for (uint8_t i=start ; i<=end; i++) { 203 | animationState[i].RgbEffectState = effect; 204 | animationState[i].AnimCount = count ; 205 | animationState[i].AnimTime = duration; 206 | //Debugf("SetAnimation(%d) duration=%ld, count=%d, effect=%d\r\n", i, duration, count, effect ); 207 | } 208 | } 209 | 210 | /* ====================================================================== 211 | Function: LedRGBON 212 | Purpose : Set RGB LED strip color, but does not lit it 213 | Input : Hue of LED (0..360) 214 | led number (from 1 to ...), if 0 then all leds 215 | if led should be lit immediatly 216 | Output : - 217 | Comments: 218 | ====================================================================== */ 219 | void LedRGBON (uint16_t hue, uint16_t led, bool doitnow) 220 | { 221 | uint8_t start = 0; 222 | uint8_t end = RGB_LED_COUNT-1; // Start at 0 223 | 224 | // Convert to neoPixel API values 225 | // H (is color from 0..360) should be between 0.0 and 1.0 226 | // S is saturation keep it to 1 227 | // L is brightness should be between 0.0 and 0.5 228 | // rgb_luminosity is between 0 and 100 (percent) 229 | RgbColor target = HslColor( hue / 360.0f, 1.0f, 0.005f * rgb_luminosity); 230 | 231 | // just one LED ? 232 | // Strip start 0 not 1 233 | if (led) { 234 | led--; 235 | start = led ; 236 | end = start ; 237 | } 238 | 239 | for (uint8_t i=start ; i<=end; i++) { 240 | animationState[i].RgbNoEffectColor = target; 241 | // do it now ? 242 | if (doitnow) { 243 | // Stop animation 244 | animations.StopAnimation(i); 245 | animationState[i].RgbEndingColor = RgbColor(0); 246 | rgb_led.SetPixelColor(i, target); 247 | rgb_led.Show(); 248 | } 249 | } 250 | } 251 | 252 | /* ====================================================================== 253 | Function: LedRGBOFF 254 | Purpose : light off the RGB LED strip 255 | Input : Led number starting at 1, if 0=>all leds 256 | Output : - 257 | Comments: - 258 | ====================================================================== */ 259 | void LedRGBOFF(uint16_t led) 260 | { 261 | uint8_t start = 0; 262 | uint8_t end = RGB_LED_COUNT-1; // Start at 0 263 | 264 | // just one LED ? 265 | if (led) { 266 | led--; 267 | start = led ; 268 | end = start ; 269 | } 270 | 271 | // stop animation, reset params 272 | for (uint8_t i=start ; i<=end; i++) { 273 | animations.StopAnimation(i); 274 | animationState[i].RgbStartingColor = RgbColor(0); 275 | animationState[i].RgbEndingColor = RgbColor(0); 276 | animationState[i].RgbNoEffectColor = RgbColor(0); 277 | animationState[i].RgbEffectState = RGB_ANIM_NONE; 278 | 279 | // clear the led strip 280 | rgb_led.SetPixelColor(i, RgbColor(0)); 281 | rgb_led.Show(); 282 | } 283 | } 284 | 285 | #endif 286 | -------------------------------------------------------------------------------- /ESP-sc-gway/RGBLed.h: -------------------------------------------------------------------------------- 1 | // ********************************************************************************** 2 | // RGB Led header file for ESP-1 Channel Gateway 3 | // ********************************************************************************** 4 | // Creative Commons Attrib Share-Alike License 5 | // You are free to use/extend this library but please abide with the CC-BY-SA license: 6 | // http://creativecommons.org/licenses/by-sa/4.0/ 7 | // 8 | // Written by Charles-Henri Hallard (http://hallard.me) 9 | // 10 | // History : V1.20 2016-06-11 - Creation 11 | // 12 | // All text above must be included in any redistribution. 13 | // 14 | // ********************************************************************************** 15 | 16 | // Master Project File 17 | #include "ESP-sc-gway.h" 18 | 19 | #ifndef RGBLed_H 20 | #define RGBLed_H 21 | 22 | // value for HSL color 23 | // see http://www.workwithcolor.com/blue-color-hue-range-01.htm 24 | #define COLOR_RED 0 25 | #define COLOR_ORANGE 30 26 | #define COLOR_ORANGE_YELLOW 45 27 | #define COLOR_YELLOW 60 28 | #define COLOR_YELLOW_GREEN 90 29 | #define COLOR_GREEN 120 30 | #define COLOR_GREEN_CYAN 165 31 | #define COLOR_CYAN 180 32 | #define COLOR_CYAN_BLUE 210 33 | #define COLOR_BLUE 240 34 | #define COLOR_BLUE_MAGENTA 275 35 | #define COLOR_MAGENTA 300 36 | #define COLOR_PINK 350 37 | 38 | #define RGB_WIFI 2 /* RGB Led for Wifi is #2 */ 39 | #define RGB_RF 1 /* RGB Led for RF module is #1 */ 40 | 41 | // The RGB animation state machine 42 | typedef enum { 43 | RGB_ANIM_NONE, 44 | RGB_ANIM_FADE_IN, 45 | RGB_ANIM_FADE_OUT, 46 | RGB_ANIM_BLINK_ON, 47 | RGB_ANIM_BLINK_OFF, 48 | } 49 | RgbEffectState_e; 50 | 51 | #ifdef WEMOS_LORA_GW 52 | #include 53 | #include 54 | 55 | // RGB Led on GPIO0 56 | #define RGB_LED_PIN 0 57 | #define RGB_LED_COUNT 2 58 | #define RGBW_LED /* I'm using a RGBW WS2812 led */ 59 | 60 | #ifdef RGBW_LED 61 | typedef NeoPixelBus MyPixelBus; 62 | 63 | // what is stored for state is specific to the need, in this case, the colors. 64 | // basically what ever you need inside the animation update function 65 | struct MyAnimationState { 66 | RgbwColor RgbStartingColor; 67 | RgbwColor RgbEndingColor; 68 | RgbwColor RgbNoEffectColor; 69 | RgbEffectState_e RgbEffectState; // current effect of RGB LED 70 | uint16_t AnimTime; 71 | uint8_t AnimCount; // Animation counter 72 | //uint8_t IndexPixel; // general purpose variable used to store pixel index 73 | }; 74 | #else 75 | typedef NeoPixelBus MyPixelBus; 76 | 77 | // what is stored for state is specific to the need, in this case, the colors. 78 | // basically what ever you need inside the animation update function 79 | struct MyAnimationState { 80 | RgbColor RgbStartingColor; 81 | RgbColor RgbEndingColor; 82 | RgbColor RgbNoEffectColor; 83 | RgbEffectState_e RgbEffectState; // current effect of RGB LED 84 | uint16_t AnimTime; 85 | uint8_t AnimCount; // Animation counter 86 | //uint8_t IndexPixel; // general purpose variable used to store pixel index 87 | }; 88 | #endif 89 | 90 | void LedRGBFadeAnimUpdate(const AnimationParam& param); 91 | void LedRGBAnimate(bool force=false); 92 | void LedRGBSetAnimation(uint16_t duration, uint16_t led=0, uint8_t count=0, RgbEffectState_e effect=RGB_ANIM_FADE_IN); 93 | void LedRGBOFF(uint16_t led=0); 94 | void LedRGBON (uint16_t hue, uint16_t led=0, bool doitnow=false); 95 | 96 | extern uint16_t wifi_led_color ; 97 | extern MyPixelBus rgb_led; 98 | extern MyAnimationState animationState[]; 99 | #else 100 | inline void LedRGBFadeAnimUpdate(void * p) {}; 101 | inline void LedRGBAnimate(bool f=false) {}; 102 | inline void LedRGBSetAnimation(uint16_t d, uint16_t l=0, uint8_t c=0, RgbEffectState_e e=0) {}; 103 | inline void LedRGBOFF(uint16_t l=0) {}; 104 | inline void LedRGBON(uint16_t h, uint16_t l=0, bool n=false) {}; 105 | #endif 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /ESP-sc-gway/gBase64.cpp: -------------------------------------------------------------------------------- 1 | #include "gBase64.h" 2 | //#include 3 | 4 | const char b64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 5 | "abcdefghijklmnopqrstuvwxyz" 6 | "0123456789+/"; 7 | 8 | /* 'Private' declarations */ 9 | inline void a3_to_a4(unsigned char * a4, unsigned char * a3); 10 | inline void a4_to_a3(unsigned char * a3, unsigned char * a4); 11 | inline unsigned char b64_lookup(char c); 12 | 13 | int base64_encode(char *output, char *input, int inputLen) { 14 | int i = 0, j = 0; 15 | int encLen = 0; 16 | unsigned char a3[3]; 17 | unsigned char a4[4]; 18 | 19 | while(inputLen--) { 20 | a3[i++] = *(input++); 21 | if(i == 3) { 22 | a3_to_a4(a4, a3); 23 | 24 | for(i = 0; i < 4; i++) { 25 | output[encLen++] = b64_alphabet[a4[i]]; 26 | } 27 | 28 | i = 0; 29 | } 30 | } 31 | 32 | if(i) { 33 | for(j = i; j < 3; j++) { 34 | a3[j] = '\0'; 35 | } 36 | 37 | a3_to_a4(a4, a3); 38 | 39 | for(j = 0; j < i + 1; j++) { 40 | output[encLen++] = b64_alphabet[a4[j]]; 41 | } 42 | 43 | while((i++ < 3)) { 44 | output[encLen++] = '='; 45 | } 46 | } 47 | output[encLen] = '\0'; 48 | return encLen; 49 | } 50 | 51 | int base64_decode(char * output, char * input, int inputLen) { 52 | int i = 0, j = 0; 53 | int decLen = 0; 54 | unsigned char a3[3]; 55 | unsigned char a4[4]; 56 | 57 | 58 | while (inputLen--) { 59 | if(*input == '=') { 60 | break; 61 | } 62 | 63 | a4[i++] = *(input++); 64 | if (i == 4) { 65 | for (i = 0; i <4; i++) { 66 | a4[i] = b64_lookup(a4[i]); 67 | } 68 | 69 | a4_to_a3(a3,a4); 70 | 71 | for (i = 0; i < 3; i++) { 72 | output[decLen++] = a3[i]; 73 | } 74 | i = 0; 75 | } 76 | } 77 | 78 | if (i) { 79 | for (j = i; j < 4; j++) { 80 | a4[j] = '\0'; 81 | } 82 | 83 | for (j = 0; j <4; j++) { 84 | a4[j] = b64_lookup(a4[j]); 85 | } 86 | 87 | a4_to_a3(a3,a4); 88 | 89 | for (j = 0; j < i - 1; j++) { 90 | output[decLen++] = a3[j]; 91 | } 92 | } 93 | output[decLen] = '\0'; 94 | return decLen; 95 | } 96 | 97 | int base64_enc_len(int plainLen) { 98 | int n = plainLen; 99 | return (n + 2 - ((n + 2) % 3)) / 3 * 4; 100 | } 101 | 102 | int base64_dec_len(char * input, int inputLen) { 103 | int i = 0; 104 | int numEq = 0; 105 | for(i = inputLen - 1; input[i] == '='; i--) { 106 | numEq++; 107 | } 108 | 109 | return ((6 * inputLen) / 8) - numEq; 110 | } 111 | 112 | inline void a3_to_a4(unsigned char * a4, unsigned char * a3) { 113 | a4[0] = (a3[0] & 0xfc) >> 2; 114 | a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4); 115 | a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6); 116 | a4[3] = (a3[2] & 0x3f); 117 | } 118 | 119 | inline void a4_to_a3(unsigned char * a3, unsigned char * a4) { 120 | a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4); 121 | a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2); 122 | a3[2] = ((a4[2] & 0x3) << 6) + a4[3]; 123 | } 124 | 125 | inline unsigned char b64_lookup(char c) { 126 | if(c >='A' && c <='Z') return c - 'A'; 127 | if(c >='a' && c <='z') return c - 71; 128 | if(c >='0' && c <='9') return c + 4; 129 | if(c == '+') return 62; 130 | if(c == '/') return 63; 131 | return -1; 132 | } 133 | -------------------------------------------------------------------------------- /ESP-sc-gway/gBase64.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Adam Rudd. 3 | * See LICENSE for more information 4 | */ 5 | #ifndef _BASE64_H 6 | #define _BASE64_H 7 | 8 | /* b64_alphabet: 9 | * Description: Base64 alphabet table, a mapping between integers 10 | * and base64 digits 11 | * Notes: This is an extern here but is defined in Base64.c 12 | */ 13 | extern const char b64_alphabet[]; 14 | 15 | /* base64_encode: 16 | * Description: 17 | * Encode a string of characters as base64 18 | * Parameters: 19 | * output: the output buffer for the encoding, stores the encoded string 20 | * input: the input buffer for the encoding, stores the binary to be encoded 21 | * inputLen: the length of the input buffer, in bytes 22 | * Return value: 23 | * Returns the length of the encoded string 24 | * Requirements: 25 | * 1. output must not be null or empty 26 | * 2. input must not be null 27 | * 3. inputLen must be greater than or equal to 0 28 | */ 29 | int base64_encode(char *output, char *input, int inputLen); 30 | 31 | /* base64_decode: 32 | * Description: 33 | * Decode a base64 encoded string into bytes 34 | * Parameters: 35 | * output: the output buffer for the decoding, 36 | * stores the decoded binary 37 | * input: the input buffer for the decoding, 38 | * stores the base64 string to be decoded 39 | * inputLen: the length of the input buffer, in bytes 40 | * Return value: 41 | * Returns the length of the decoded string 42 | * Requirements: 43 | * 1. output must not be null or empty 44 | * 2. input must not be null 45 | * 3. inputLen must be greater than or equal to 0 46 | */ 47 | int base64_decode(char *output, char *input, int inputLen); 48 | 49 | /* base64_enc_len: 50 | * Description: 51 | * Returns the length of a base64 encoded string whose decoded 52 | * form is inputLen bytes long 53 | * Parameters: 54 | * inputLen: the length of the decoded string 55 | * Return value: 56 | * The length of a base64 encoded string whose decoded form 57 | * is inputLen bytes long 58 | * Requirements: 59 | * None 60 | */ 61 | int base64_enc_len(int inputLen); 62 | 63 | /* base64_dec_len: 64 | * Description: 65 | * Returns the length of the decoded form of a 66 | * base64 encoded string 67 | * Parameters: 68 | * input: the base64 encoded string to be measured 69 | * inputLen: the length of the base64 encoded string 70 | * Return value: 71 | * Returns the length of the decoded form of a 72 | * base64 encoded string 73 | * Requirements: 74 | * 1. input must not be null 75 | * 2. input must be greater than or equal to zero 76 | */ 77 | int base64_dec_len(char *input, int inputLen); 78 | 79 | #endif // _BASE64_H 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Single Channel LoRaWAN Gateway 2 | ============================== 3 | This repository contains a proof-of-concept implementation of a single 4 | channel LoRaWAN gateway. It has been tested on the Wemos D1 Mini, using a 5 | Semtech SX1276 transceiver (HopeRF RFM95W). 6 | 7 | The code is for testing and development purposes only, and is not meant 8 | for production usage. 9 | 10 | Engine is based on code base of Single Channel gateway for RaspberryPI 11 | which is developed by Thomas Telkamp. Code was ported and extended to run 12 | on ESP 8266 mcu and provide RTC, Webserver and DNS services. 13 | 14 | Maintained by Maarten Westenberg (mw12554@hotmail.com) 15 | 16 | Features 17 | -------- 18 | - listen on configurable frequency and spreading factor 19 | - SF7 to SF12 20 | - status updates 21 | - can forward to two servers 22 | - DNS support for server lookup 23 | - NTP Support for time sync with internet time servers 24 | - Webserver support (default port 8080) 25 | - OTA Updates 26 | - Access Point Mode (for OTA) 27 | 28 | Not (yet) supported: 29 | - PACKET_PUSH_ACK processing 30 | - SF7BW250 modulation 31 | - FSK modulation 32 | - downstream messages (tx) 33 | 34 | Added features if you're using [WeMos-Lora][3] Shield as gateway 35 | - 2 On board RGB LED for visual 36 | - 1 SSD1306 I2C OLED connector 37 | - 1 On board push button 38 | 39 | Assembled WeMos-Lora Shield 40 | 41 | Top 42 | Bottom 43 | 44 | Dependencies 45 | ------------ 46 | 47 | - [gBase64][7] library by Adam Rudd is now integrated in the project, no need to install 48 | - [Time][5] library Arduino [documentation][6] 49 | - [NeoPixelBus][4] library is you're using [WeMos Lora][3] Shield as gateway 50 | 51 | Connections 52 | ----------- 53 | See [things4u][8] in the [hardware][9] section for building and connection instructions 54 | See [WeMos-Lora][3] github if you're using WeMos Lora Shield as gateway 55 | 56 | 57 | Configuration 58 | ------------- 59 | 60 | Defaults: 61 | 62 | - LoRa: SF7 at 868.1 Mhz 63 | - Server: 54.229.214.112, port 1700 (The Things Network: croft.thethings.girovito.nl) 64 | or directly croft.thethings.girovito.nl 65 | 66 | Edit .h file (ESP-sc-gway.h) to change configuration (look for: "Configure these values!"). 67 | 68 | Please set location, email and description. 69 | 70 | License 71 | ------- 72 | The source files in this repository are made available under the Eclipse 73 | Public License v1.0, except for the base64 implementation, that has been 74 | copied from the Semtech Packet Forwader. 75 | 76 | 77 | [2]: https://hallard.me 78 | [3]: https://github.com/hallard/WeMos-Lora 79 | [4]: https://github.com/Makuna/NeoPixelBus 80 | [5]: https://github.com/PaulStoffregen/Time 81 | [6]: http://playground.arduino.cc/code/time 82 | [7]: https://github.com/adamvr/arduino-base64 83 | [8]: http://things4u.github.io 84 | [9]: http://things4u.github.io/HardwareGuide/hardware_guide.html 85 | --------------------------------------------------------------------------------