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

ESP Gateway Config:

"; 997 | response +="Version: "; response+=VERSION; 998 | response +="
ESP is alive since "; response+=stringTime(1); 999 | response +="
Current time is "; response+=stringTime(millis()); 1000 | response +="
"; 1001 | 1002 | response +="

WiFi Config

"; 1003 | response +=""; 1004 | response +=""; 1005 | response +=""; 1006 | response +=""; 1007 | response +=""; 1008 | response +=""; 1009 | response +=""; 1010 | response +=""; 1011 | response +=""; 1012 | response +=""; 1013 | 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+="
"; 1014 | 1015 | response +="

System Status

"; 1016 | response +=""; 1017 | response +=""; 1018 | response +=""; 1019 | response +=""; 1020 | response +=""; 1021 | response +=""; 1022 | response +=""; 1023 | response +="
ParameterValue
Free heap"; response+=ESP.getFreeHeap(); response+="
ESP Chip ID"; response+=ESP.getChipId(); response+="
"; 1024 | 1025 | response +="

LoRa Status

"; 1026 | response +=""; 1027 | response +=""; 1028 | response +=""; 1029 | response +=""; 1030 | response +=""; 1031 | response +=""; 1032 | response +=""; 1033 | response +=""; 1042 | response +="
ParameterValue
Frequency"; response+=freq; response+="
Spreading Factor"; response+=sf; response+="
Gateway ID"; 1034 | response +=String(MAC_array[0],HEX); // The MAC array is always returned in lowercase 1035 | response +=String(MAC_array[1],HEX); 1036 | response +=String(MAC_array[2],HEX); 1037 | response +="ffff"; 1038 | response +=String(MAC_array[3],HEX); 1039 | response +=String(MAC_array[4],HEX); 1040 | response +=String(MAC_array[5],HEX); 1041 | response+="
"; 1043 | 1044 | response +="

Statistics

"; 1045 | delay(1); 1046 | response +=""; 1047 | response +=""; 1048 | response +=""; 1049 | response +=""; 1050 | response +=""; 1051 | response +=""; 1052 | response +=""; 1053 | response +=""; 1054 | response +=""; 1055 | 1056 | 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+="
 
"; 1057 | 1058 | response +="
"; 1059 | response +="

Settings

"; 1060 | response +="Click here to reset statistics
"; 1061 | 1062 | response +="Debug level is: "; 1063 | response += debug; 1064 | response +=" set to: "; 1065 | response +=" 0"; 1066 | response +=" 1"; 1067 | response +=" 2
"; 1068 | 1069 | response +="Click here to explain Help and REST options
"; 1070 | response +=""; 1071 | 1072 | server.send(200, "text/html", response); 1073 | 1074 | delay(5); 1075 | free(dup); // free the memory used, before jumping to other page 1076 | } 1077 | 1078 | #endif 1079 | 1080 | 1081 | 1082 | // ======================================================================== 1083 | // MAIN PROGRAM (SETUP AND LOOP) 1084 | 1085 | // ---------------------------------------------------------------------------- 1086 | // Setup code (one time) 1087 | // ---------------------------------------------------------------------------- 1088 | void setup () { 1089 | 1090 | WiFiMode_t con_type ; 1091 | int ret = WiFi.status(); 1092 | 1093 | Serial.begin(_BAUDRATE); // As fast as possible for bus 1094 | 1095 | Serial.print(F("\r\nBooting ")); 1096 | Serial.println(ARDUINO_BOARD " " __DATE__ " " __TIME__); 1097 | 1098 | if (debug>=1) { 1099 | Serial.print(F("! debug: ")); 1100 | } 1101 | 1102 | #ifdef WEMOS_LORA_GW 1103 | rgb_led.Begin(); 1104 | LedRGBOFF(); 1105 | #endif 1106 | 1107 | // Setup WiFi UDP connection. Give it some time .. 1108 | if (WlanConnect( (char *) _SSID, (char *)_PASS) == WL_CONNECTED) { 1109 | // If we are here we are connected to WLAN 1110 | // So now test the UDP function 1111 | if (!UDPconnect()) { 1112 | Serial.println("Error UDPconnect"); 1113 | } 1114 | } 1115 | 1116 | WiFi.macAddress(MAC_array); 1117 | for (int i = 0; i < sizeof(MAC_array); ++i){ 1118 | sprintf(MAC_char,"%s%02x:",MAC_char,MAC_array[i]); 1119 | } 1120 | Serial.print("MAC: "); 1121 | Serial.println(MAC_char); 1122 | 1123 | // Configure IO Pin 1124 | pinMode(ssPin, OUTPUT); 1125 | pinMode(dio0, INPUT); 1126 | pinMode(RST, OUTPUT); 1127 | 1128 | SPI.begin(); 1129 | delay(100); 1130 | SetupLoRa(); 1131 | delay(100); 1132 | 1133 | // We choose the Gateway ID to be the Ethernet Address of our Gateway card 1134 | // display results of getting hardware address 1135 | // 1136 | Serial.print("Gateway ID: "); 1137 | Serial.print(MAC_array[0],HEX); 1138 | Serial.print(MAC_array[1],HEX); 1139 | Serial.print(MAC_array[2],HEX); 1140 | Serial.print(0xFF, HEX); 1141 | Serial.print(0xFF, HEX); 1142 | Serial.print(MAC_array[3],HEX); 1143 | Serial.print(MAC_array[4],HEX); 1144 | Serial.print(MAC_array[5],HEX); 1145 | 1146 | Serial.print(", Listening at SF"); 1147 | Serial.print(sf); 1148 | Serial.print(" on "); 1149 | Serial.print((double)freq/1000000); 1150 | Serial.println(" Mhz."); 1151 | 1152 | WiFi.hostByName(_TTNSERVER, ttnServer); // Use DNS to get server IP once 1153 | delay(100); 1154 | 1155 | setupTime(); // Set NTP time host and interval 1156 | setTime((time_t)getNtpTime()); 1157 | Serial.print("time "); printTime(); 1158 | Serial.println(); 1159 | 1160 | #if A_SERVER==1 1161 | server.on("/", []() { WifiServer("",""); }); 1162 | server.on("/HELP", []() { WifiServer("HELP",""); }); 1163 | server.on("/RESET", []() { WifiServer("RESET",""); }); 1164 | server.on("/DEBUG=0", []() { WifiServer("DEBUG","0"); }); 1165 | server.on("/DEBUG=1", []() { WifiServer("DEBUG","1"); }); 1166 | server.on("/DEBUG=2", []() { WifiServer("DEBUG","2"); }); 1167 | 1168 | server.begin(); // Start the webserver 1169 | Serial.print(F("Admin Server started on port ")); 1170 | Serial.println(SERVERPORT); 1171 | #endif 1172 | Serial.println("---------------------------------"); 1173 | 1174 | // Breathing now set to 1000ms 1175 | LedRGBSetAnimation(1000, RGB_WIFI); 1176 | LedRGBOFF(RGB_RF); 1177 | 1178 | } 1179 | 1180 | // ---------------------------------------------------------------------------- 1181 | // LOOP 1182 | // This is the main program that is executed time and time again. 1183 | // We need to geive way to the bacjend WiFi processing that 1184 | // takes place somewhere in the ESP8266 firmware and therefore 1185 | // we include yield() statements at important points. 1186 | // 1187 | // Note: If we spend too much time in user processing functions 1188 | // and the backend system cannot do its housekeeping, the watchdog 1189 | // function will be executed which means effectively that the 1190 | // program crashes. 1191 | // ---------------------------------------------------------------------------- 1192 | void loop () 1193 | { 1194 | static bool led_state ; 1195 | bool new_led_state ; 1196 | int buff_index; 1197 | char buff_up[TX_BUFF_SIZE]; // buffer to compose the upstream packet 1198 | 1199 | // Receive Lora messages 1200 | if ((buff_index = receivepacket(buff_up)) >= 0) { // read is successful 1201 | yield(); 1202 | LedRGBON(COLOR_MAGENTA, RGB_RF, true); 1203 | LedRGBSetAnimation(1000, RGB_RF, 1, RGB_ANIM_FADE_OUT); 1204 | sendUdp(buff_up, buff_index); // We can send to multiple sockets if necessary 1205 | } 1206 | else { 1207 | // No message received 1208 | } 1209 | 1210 | // Receive WiFi messages. This is important since the TTN broker will return confirmation 1211 | // messages on UDP for every message sent by the gateway. 1212 | int packetSize = Udp.parsePacket(); 1213 | if (packetSize >0) { 1214 | yield(); 1215 | if (readUdp(packetSize)>0) { 1216 | // 1 fade out animation green if okay else otherwhise 1217 | LedRGBON(COLOR_GREEN, RGB_WIFI, true); 1218 | } else { 1219 | LedRGBON(COLOR_ORANGE, RGB_WIFI, true); 1220 | } 1221 | LedRGBSetAnimation(1000, RGB_WIFI, 1, RGB_ANIM_FADE_OUT); 1222 | } 1223 | 1224 | uint32_t nowseconds = (uint32_t) millis() /1000; 1225 | if (nowseconds - lasttime >= 30) { // Send status every 30 seconds 1226 | sendstat(); 1227 | lasttime = nowseconds; 1228 | } 1229 | 1230 | // Handle the WiFi server part of this sketch. Mainly used for administration of the node 1231 | #if A_SERVER==1 1232 | server.handleClient(); 1233 | #endif 1234 | 1235 | // On board Led blink management 1236 | if (WiFi.status()==WL_CONNECTED) { 1237 | new_led_state = ((millis() % 1000) < 200) ? LOW:HIGH; // Connected long blink 200ms on each second 1238 | } else { 1239 | new_led_state = ((millis() % 333) < 111) ? LOW:HIGH;// AP Mode or client failed quick blink 111ms on each 1/3sec 1240 | } 1241 | // Led management 1242 | if (led_state != new_led_state) { 1243 | led_state = new_led_state; 1244 | digitalWrite(LED_BUILTIN, led_state); 1245 | } 1246 | 1247 | #ifdef WEMOS_LORA_GW 1248 | // RF RGB LED should be off if no animation is running place 1249 | if ( animationState[0].RgbEffectState == RGB_ANIM_NONE) { 1250 | LedRGBOFF(RGB_RF); 1251 | } 1252 | // WIFI RGB LED should breath if no animation is running place 1253 | if ( animationState[1].RgbEffectState == RGB_ANIM_NONE) { 1254 | LedRGBON(wifi_led_color, RGB_WIFI); 1255 | LedRGBSetAnimation(1000, RGB_WIFI); 1256 | } 1257 | 1258 | // Manage Animations 1259 | LedRGBAnimate(); 1260 | #endif 1261 | 1262 | // Handle OTA 1263 | ArduinoOTA.handle(); 1264 | } 1265 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------