├── LICENSE.txt ├── LoRaGoDOCK-Gateway ├── LoRaGoDOCK-Gateway.ino ├── _gatewayMgt.ino ├── _loraFiles.ino ├── _loraModem.ino ├── _otaServer.ino ├── _sensor.ino ├── _wwwServer.ino ├── config.h ├── loraFiles.h └── loraModem.h ├── README.md └── libraries_dependency.zip /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Maarten Westenberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /LoRaGoDOCK-Gateway/LoRaGoDOCK-Gateway.ino: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * 3 | * Description: Source code for single-channel LoRaWAN Gateway based on ESP8266 and SX1276 4 | * Version : 0.8.2 5 | * Date : 2018-11-26 6 | * Software : https://github.com/SandboxElectronics/LoRaGoDOCK-Gateway 7 | * Hardware : LoRaGo DOCK – http://sandboxelectronics.com/?product=lorago-dock-single-channel-lorawan-gateway 8 | * 9 | * Copyright (c) 2016, 2017 Maarten Westenberg 10 | * 11 | * All rights reserved. This program and the accompanying materials 12 | * are made available under the terms of the MIT License 13 | * which accompanies this distribution, and is available at 14 | * https://opensource.org/licenses/mit-license.php 15 | * 16 | *****************************************************************************************/ 17 | 18 | #include "config.h" // This file contains configuration of the gateway 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include // http://playground.arduino.cc/code/time 31 | #include 32 | #include // Local DNSserver 33 | #include "FS.h" 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include // https://github.com/adamvr/arduino-base64 (changed the name) 39 | #include 40 | 41 | #include "loraModem.h" 42 | #include "loraFiles.h" 43 | 44 | #if WIFIMANAGER>0 45 | #include // Library for ESP WiFi config through an AP 46 | #endif 47 | 48 | #if A_OTA==1 49 | #include 50 | #include 51 | #endif 52 | 53 | #if A_SERVER==1 54 | #include 55 | #endif 56 | 57 | #if GATEWAYNODE==1 58 | #include "AES-128_V10.h" 59 | #endif 60 | 61 | #if OLED==1 62 | #include "SSD1306.h" 63 | SSD1306 display(0x3c, OLED_SDA, OLED_SCL); // (i2c address of display(0x3c or 0x3d), SDA, SCL) on wemos 64 | #endif 65 | 66 | int debug=1; // Debug level! 0 is no msgs, 1 normal, 2 extensive 67 | 68 | // You can switch webserver off if not necessary but probably better to leave it in. 69 | #if A_SERVER==1 70 | #include // http://arduiniana.org/libraries/streaming/ 71 | ESP8266WebServer server(A_SERVERPORT); 72 | #endif 73 | using namespace std; 74 | 75 | byte currentMode = 0x81; 76 | 77 | char b64[256]; 78 | bool sx1272 = true; // Actually we use sx1276/RFM95 79 | 80 | uint32_t cp_nb_rx_rcv; 81 | uint32_t cp_nb_rx_ok; 82 | uint32_t cp_nb_rx_bad; 83 | uint32_t cp_nb_rx_nocrc; 84 | uint32_t cp_up_pkt_fwd; 85 | 86 | uint8_t MAC_array[6]; 87 | 88 | // ---------------------------------------------------------------------------- 89 | // 90 | // Configure these values only if necessary! 91 | // 92 | // ---------------------------------------------------------------------------- 93 | 94 | // Set spreading factor (SF7 - SF12) 95 | sf_t sf = _SPREADING; 96 | sf_t sfi = _SPREADING; // Initial value of SF 97 | 98 | // Set location, description and other configuration parameters 99 | // Defined in ESP-sc_gway.h 100 | // 101 | float lat = _LAT; // Configuration specific info... 102 | float lon = _LON; 103 | int alt = _ALT; 104 | char platform[24] = _PLATFORM; // platform definition 105 | char email[40] = _EMAIL; // used for contact email 106 | char description[64]= _DESCRIPTION; // used for free form description 107 | 108 | // define servers 109 | 110 | IPAddress ntpServer; // IP address of NTP_TIMESERVER 111 | IPAddress ttnServer; // IP Address of thethingsnetwork server 112 | IPAddress thingServer; 113 | 114 | WiFiUDP Udp; 115 | uint32_t stattime = 0; // last time we sent a stat message to server 116 | uint32_t pulltime = 0; // last time we sent a pull_data request to server 117 | uint32_t lastTmst = 0; 118 | #if A_SERVER==1 119 | uint32_t wwwtime = 0; 120 | #endif 121 | #if NTP_INTR==0 122 | uint32_t ntptimer = 0; 123 | #endif 124 | 125 | SimpleTimer timer; // Timer is needed for delayed sending 126 | 127 | #define TX_BUFF_SIZE 1024 // Upstream buffer to send to MQTT 128 | #define RX_BUFF_SIZE 1024 // Downstream received from MQTT 129 | #define STATUS_SIZE 512 // Should(!) be enough based on the static text .. was 1024 130 | 131 | uint8_t buff_down[RX_BUFF_SIZE]; // Buffer for downstream 132 | uint16_t lastToken = 0x00; 133 | 134 | #if GATEWAYNODE==1 135 | uint16_t frameCount=0; // We write this to SPIFF file 136 | #endif 137 | 138 | // ---------------------------------------------------------------------------- 139 | // FORWARD DECARATIONS 140 | // These forware declarations are done since _loraModem.ino is linked by the 141 | // compiler/linker AFTER the main LoRaGoDOCK-Gateway.ino file. 142 | // And espcesially when calling functions with ICACHE_RAM_ATTR the complier 143 | // does not want this. 144 | // ---------------------------------------------------------------------------- 145 | #if REENTRANT==2 146 | uint8_t ICACHE_RAM_ATTR readRegister(uint8_t addr); 147 | void ICACHE_RAM_ATTR writeRegister(uint8_t addr, uint8_t value); 148 | void ICACHE_RAM_ATTR setFreq(uint32_t freq); 149 | //void ICACHE_RAM_ATTR setRate(uint8_t sf, uint8_t crc); 150 | void ICACHE_RAM_ATTR setPow(uint8_t powe); 151 | void ICACHE_RAM_ATTR opmodeLora(); 152 | void ICACHE_RAM_ATTR opmode(uint8_t mode); 153 | void ICACHE_RAM_ATTR rxLoraModem(); 154 | void ICACHE_RAM_ATTR initLoraModem(); 155 | uint8_t ICACHE_RAM_ATTR receivePkt(uint8_t *payload); 156 | int ICACHE_RAM_ATTR receivePacket(); 157 | void ICACHE_RAM_ATTR cadScanner(); 158 | void ICACHE_RAM_ATTR Interrupt(); 159 | void ICACHE_RAM_ATTR Interrupt_0(); 160 | void ICACHE_RAM_ATTR Interrupt_1(); 161 | #endif 162 | 163 | // ---------------------------------------------------------------------------- 164 | // DIE is not use actively in the source code anymore. 165 | // It is replaced by a Serial.print command so we know that we have a problem 166 | // somewhere. 167 | // There are at least 3 other ways to restart the ESP. Pick one if you want. 168 | // ---------------------------------------------------------------------------- 169 | void die(const char *s) 170 | { 171 | if (debug>0) Serial.println(s); 172 | delay(50); 173 | // system_restart(); // SDK function 174 | // ESP.reset(); 175 | abort(); // Within a second 176 | } 177 | 178 | // ---------------------------------------------------------------------------- 179 | // gway_failed is a function called by ASSERT. 180 | // ---------------------------------------------------------------------------- 181 | void gway_failed(const char *file, uint16_t line) { 182 | Serial.print(F("Program failed in file: ")); 183 | Serial.print(file); 184 | Serial.print(F(", line: ")); 185 | Serial.print(line); 186 | } 187 | 188 | // ---------------------------------------------------------------------------- 189 | // Print leading '0' digits for hours(0) and second(0) when 190 | // printing values less than 10 191 | // ---------------------------------------------------------------------------- 192 | void printDigits(unsigned long digits) 193 | { 194 | // utility function for digital clock display: prints leading 0 195 | if(digits < 10) 196 | Serial.print(F("0")); 197 | Serial.print(digits); 198 | } 199 | 200 | // ---------------------------------------------------------------------------- 201 | // Print utin8_t values in HEX with leading 0 when necessary 202 | // ---------------------------------------------------------------------------- 203 | void printHexDigit(uint8_t digit) 204 | { 205 | // utility function for printing Hex Values with leading 0 206 | if(digit < 0x10) 207 | Serial.print('0'); 208 | Serial.print(digit,HEX); 209 | } 210 | 211 | 212 | // ---------------------------------------------------------------------------- 213 | // Print the current time 214 | // ---------------------------------------------------------------------------- 215 | static void printTime() { 216 | switch (weekday()) 217 | { 218 | case 1: Serial.print(F("Sunday")); break; 219 | case 2: Serial.print(F("Monday")); break; 220 | case 3: Serial.print(F("Tuesday")); break; 221 | case 4: Serial.print(F("Wednesday")); break; 222 | case 5: Serial.print(F("Thursday")); break; 223 | case 6: Serial.print(F("Friday")); break; 224 | case 7: Serial.print(F("Saturday")); break; 225 | default: Serial.print(F("ERROR")); break; 226 | } 227 | Serial.print(F(" ")); 228 | printDigits(hour()); 229 | Serial.print(F(":")); 230 | printDigits(minute()); 231 | Serial.print(F(":")); 232 | printDigits(second()); 233 | return; 234 | } 235 | 236 | 237 | // ---------------------------------------------------------------------------- 238 | // Convert a float to string for printing 239 | // f is value to convert 240 | // p is precision in decimal digits 241 | // val is character array for results 242 | // ---------------------------------------------------------------------------- 243 | void ftoa(float f, char *val, int p) { 244 | int j=1; 245 | int ival, fval; 246 | char b[6] = { 0x00 }; 247 | 248 | for (int i=0; i< p; i++) { j= j*10; } 249 | 250 | ival = (int) f; // Make integer part 251 | fval = (int) ((f- ival)*j); // Make fraction. Has same sign as integer part 252 | if (fval<0) fval = -fval; // So if it is negative make fraction positive again. 253 | // sprintf does NOT fit in memory 254 | strcat(val,itoa(ival,b,10)); // Copy integer part first, base 10, null terminated 255 | strcat(val,"."); // Copy decimal point 256 | 257 | itoa(fval,b,10); // Copy fraction part 258 | for (int i=0; i<(p-strlen(b)); i++) strcat(val,"0"); // first number of 0 of faction? 259 | 260 | // Fraction can be anything from 0 to 10^p , so can have less digits 261 | strcat(val,b); 262 | } 263 | 264 | // ============================================================================ 265 | // NTP TIME functions 266 | 267 | const int NTP_PACKET_SIZE = 48; // Fixed size of NTP record 268 | byte packetBuffer[NTP_PACKET_SIZE]; 269 | 270 | // ---------------------------------------------------------------------------- 271 | // Send the request packet to the NTP server. 272 | // 273 | // ---------------------------------------------------------------------------- 274 | void sendNTPpacket(IPAddress& timeServerIP) { 275 | // Zeroise the buffer. 276 | memset(packetBuffer, 0, NTP_PACKET_SIZE); 277 | packetBuffer[0] = 0b11100011; // LI, Version, Mode 278 | packetBuffer[1] = 0; // Stratum, or type of clock 279 | packetBuffer[2] = 6; // Polling Interval 280 | packetBuffer[3] = 0xEC; // Peer Clock Precision 281 | // 8 bytes of zero for Root Delay & Root Dispersion 282 | packetBuffer[12] = 49; 283 | packetBuffer[13] = 0x4E; 284 | packetBuffer[14] = 49; 285 | packetBuffer[15] = 52; 286 | 287 | Udp.beginPacket(timeServerIP, (int) 123); // NTP Server and Port 288 | 289 | if ((Udp.write((char *)packetBuffer, NTP_PACKET_SIZE)) != NTP_PACKET_SIZE) { 290 | die("sendNtpPacket:: Error write"); 291 | } 292 | else { 293 | // Success 294 | } 295 | Udp.endPacket(); 296 | } 297 | 298 | 299 | // ---------------------------------------------------------------------------- 300 | // Get the NTP time from one of the time servers 301 | // Note: As this function is called from SyncINterval in the background 302 | // make sure we have no blocking calls in this function 303 | // ---------------------------------------------------------------------------- 304 | time_t getNtpTime() 305 | { 306 | gwayConfig.ntps++; 307 | WiFi.hostByName(NTP_TIMESERVER, ntpServer); // Get IP address of Timeserver 308 | sendNTPpacket(ntpServer); // Send the request 309 | uint32_t beginWait = millis(); 310 | while (millis() - beginWait < 1000) 311 | { 312 | int size = Udp.parsePacket(); 313 | if ( size >= NTP_PACKET_SIZE ) { 314 | Udp.read(packetBuffer, NTP_PACKET_SIZE); 315 | // Extract seconds portion. 316 | unsigned long secs; 317 | secs = packetBuffer[40] << 24; 318 | secs |= packetBuffer[41] << 16; 319 | secs |= packetBuffer[42] << 8; 320 | secs |= packetBuffer[43]; 321 | Udp.flush(); 322 | return secs - 2208988800UL + NTP_TIMEZONES * SECS_PER_HOUR; 323 | // UTC is 1 TimeZone correction when no daylight saving time 324 | } 325 | delay(10); // Wait 10 millisecs, allow kernel to act when necessary 326 | } 327 | 328 | Udp.flush(); 329 | 330 | // If we are here, we could not read the time from internet 331 | // So increase the counter 332 | gwayConfig.ntpErr++; 333 | return 0; // return 0 if unable to get the time 334 | } 335 | 336 | // ---------------------------------------------------------------------------- 337 | // Set up regular synchronization of NTP server and the local time. 338 | // ---------------------------------------------------------------------------- 339 | #if NTP_INTR==1 340 | void setupTime() { 341 | setSyncProvider(getNtpTime); 342 | setSyncInterval(_NTP_INTERVAL); 343 | } 344 | #endif 345 | 346 | 347 | // ============================================================================ 348 | // UDP AND WLAN FUNCTIONS 349 | 350 | // ---------------------------------------------------------------------------- 351 | // Prepare the Config Parameters 352 | // ---------------------------------------------------------------------------- 353 | int WlanReadWpa() { 354 | readConfig( CONFIGFILE, &gwayConfig); 355 | 356 | if (gwayConfig.sf != (uint8_t)0) { 357 | sf = (sf_t) gwayConfig.sf; 358 | } 359 | 360 | ifreq = gwayConfig.ch; 361 | freq = freqs[ifreq]; 362 | debug = gwayConfig.debug; 363 | _cad = gwayConfig.cad; 364 | _hop = gwayConfig.hop; 365 | gwayConfig.boots++; // Every boot of the system we increase the reset 366 | 367 | #if GATEWAYNODE==1 368 | if (gwayConfig.fcnt != (uint8_t) 0) frameCount = gwayConfig.fcnt+10; 369 | #endif 370 | 371 | #if WIFIMANAGER > 0 372 | String ssid=gwayConfig.ssid; 373 | String pass=gwayConfig.pass; 374 | 375 | char ssidBuf[ssid.length()+1]; 376 | ssid.toCharArray(ssidBuf,ssid.length()+1); 377 | char passBuf[pass.length()+1]; 378 | pass.toCharArray(passBuf,pass.length()+1); 379 | Serial.print(F("WlanReadWpa: ")); Serial.print(ssidBuf); Serial.print(F(", ")); Serial.println(passBuf); 380 | 381 | strcpy(wpa[0].login, ssidBuf); // XXX changed from wpa[0][0] = ssidBuf 382 | strcpy(wpa[0].passw, passBuf); 383 | 384 | Serial.print(F("WlanReadWpa: <")); 385 | Serial.print(wpa[0].login); // XXX 386 | Serial.print(F(">, <")); 387 | Serial.print(wpa[0].passw); 388 | Serial.println(F(">")); 389 | #endif 390 | 391 | } 392 | 393 | // ---------------------------------------------------------------------------- 394 | // Print the WPA data of last WiFiManager to file 395 | // ---------------------------------------------------------------------------- 396 | int WlanWriteWpa( char* ssid, char *pass) { 397 | 398 | Serial.print(F("WlanWriteWpa:: ssid=")); Serial.print(ssid); 399 | Serial.print(F(", pass=")); Serial.print(pass); Serial.println(); 400 | 401 | // Version 3.3 use of config file 402 | String s((char *) ssid); 403 | gwayConfig.ssid = s; 404 | 405 | String p((char *) pass); 406 | gwayConfig.pass = p; 407 | 408 | #if GATEWAYNODE==1 409 | gwayConfig.fcnt = frameCount; 410 | #endif 411 | gwayConfig.ch = ifreq; 412 | gwayConfig.sf = sf; 413 | gwayConfig.cad = _cad; 414 | gwayConfig.hop = _hop; 415 | 416 | writeConfig( CONFIGFILE, &gwayConfig); 417 | } 418 | 419 | // ---------------------------------------------------------------------------- 420 | // Function to join the Wifi Network 421 | // It is a matter of returning to the main loop() asap and make sure in next loop 422 | // the reconnect is done first thing. 423 | // ---------------------------------------------------------------------------- 424 | int WlanConnect() { 425 | 426 | #if WIFIMANAGER==1 427 | WiFiManager wifiManager; 428 | #endif 429 | unsigned char agains = 0; 430 | unsigned char wpa_index = (WIFIMANAGER >0 ? 0 : 1); // Skip over first record for WiFiManager 431 | // Serial.print(F("WlanConnect:: wpa_index=")); Serial.println(wpa_index); 432 | 433 | while (WiFi.status() != WL_CONNECTED) 434 | { 435 | // Start with well-known access points in the list 436 | 437 | char *ssid = wpa[wpa_index].login; 438 | char *password = wpa[wpa_index].passw; 439 | 440 | // Serial.print(wpa_index); Serial.print(F(". WiFi connect to: ")); Serial.println(ssid); 441 | 442 | WiFi.begin(ssid, password); 443 | 444 | while (WiFi.status() != WL_CONNECTED) { 445 | delay(agains*400); 446 | agains++; 447 | if (debug>=2) Serial.print("."); 448 | yield(); 449 | 450 | // If after 10 times there is still no connection, we probably wait forever 451 | // So restart the WiFI.begin process!! 452 | if (agains == 10) { 453 | agains = 0; 454 | WiFi.disconnect(); 455 | //yield(); 456 | delay(500); 457 | break; 458 | } 459 | } 460 | wpa_index++; 461 | //if (wpa_index >= WPASIZE) { break; } 462 | if (wpa_index >= (sizeof(wpa)/sizeof(wpa[0]))) { 463 | wpa_index = (WIFIMANAGER >0 ? 0 : 1); 464 | break; 465 | } 466 | } 467 | 468 | // Still not connected? 469 | if (WiFi.status() != WL_CONNECTED) { 470 | #if WIFIMANAGER==1 471 | Serial.println(F("Starting Access Point Mode")); 472 | Serial.print(F("Connect Wifi to accesspoint: ")); 473 | Serial.print(AP_NAME); 474 | Serial.print(F(" and connect to IP: 192.168.4.1")); 475 | Serial.println(); 476 | wifiManager.autoConnect(AP_NAME, AP_PASSWD ); 477 | // Reset the board after 180s waiting for WIFI configuration 478 | wifiManager.setConfigPortalTimeout(180); 479 | //wifiManager.startConfigPortal(AP_NAME, AP_PASSWD ); 480 | // At this point, there IS a Wifi Access Point found and connected 481 | // We must connect to the local SPIFFS storage to store the access point 482 | //String s = WiFi.SSID(); 483 | //char ssidBuf[s.length()+1]; 484 | //s.toCharArray(ssidBuf,s.length()+1); 485 | // Now look for the password 486 | struct station_config sta_conf; 487 | wifi_station_get_config(&sta_conf); 488 | 489 | //WlanWriteWpa(ssidBuf, (char *)sta_conf.password); 490 | WlanWriteWpa((char *)sta_conf.ssid, (char *)sta_conf.password); 491 | #else 492 | return(-1); 493 | #endif 494 | } 495 | 496 | #if STATISTICS>=1 497 | gwayConfig.wifis++; 498 | #endif 499 | Serial.print(F("WiFi connected to ")); 500 | Serial.println(WiFi.SSID()); 501 | Serial.print(F("IP Addr: ")); 502 | Serial.println(WiFi.localIP()); 503 | yield(); 504 | return(0); 505 | } 506 | 507 | 508 | // ---------------------------------------------------------------------------- 509 | // Read DOWN a package from UDP socket, can come from any server 510 | // Messages are received when server responds to gateway requests from LoRa nodes 511 | // (e.g. JOIN requests etc.) or when server has downstream data. 512 | // We repond only to the server that sent us a message! 513 | // Note: So normally we can forget here about codes that do upstream 514 | // ---------------------------------------------------------------------------- 515 | int readUdp(int packetSize, uint8_t * buff_down) 516 | { 517 | uint8_t protocol; 518 | uint16_t token; 519 | uint8_t ident; 520 | uint8_t buff[64]; // General buffer to use for UDP 521 | 522 | if (WiFi.status() != WL_CONNECTED) { 523 | Serial.println(F("readUdp: ERROR not connected to WLAN")); 524 | Serial.flush(); 525 | Udp.flush(); 526 | 527 | if (WlanConnect() < 0) { 528 | Serial.print(F("readdUdp: ERROR connecting to WiFi")); 529 | yield(); 530 | return(-1); 531 | } 532 | if (debug>0) Serial.println(F("WiFi reconnected")); 533 | delay(10); 534 | } 535 | 536 | if (packetSize > RX_BUFF_SIZE) { 537 | Serial.print(F("readUDP:: ERROR package of size: ")); 538 | Serial.println(packetSize); 539 | Udp.flush(); 540 | return(-1); 541 | } 542 | 543 | 544 | Udp.read(buff_down, packetSize); 545 | IPAddress remoteIpNo = Udp.remoteIP(); 546 | unsigned int remotePortNo = Udp.remotePort(); 547 | 548 | uint8_t * data = buff_down + 4; 549 | protocol = buff_down[0]; 550 | token = buff_down[2]*256 + buff_down[1]; 551 | ident = buff_down[3]; 552 | 553 | // now parse the message type from the server (if any) 554 | switch (ident) { 555 | 556 | // This message is used by the gateway to send sensor data to the 557 | // server. As this function is used for downstream only, this option 558 | // will never be selected but is included as a reference only 559 | case PKT_PUSH_DATA: // 0x00 UP 560 | if (debug >=1) { 561 | Serial.print(F("PKT_PUSH_DATA:: size ")); Serial.print(packetSize); 562 | Serial.print(F(" From ")); Serial.print(remoteIpNo); 563 | Serial.print(F(", port ")); Serial.print(remotePortNo); 564 | Serial.print(F(", data: ")); 565 | for (int i=0; i= 2) { 577 | Serial.print(F("PKT_PUSH_ACK:: size ")); Serial.print(packetSize); 578 | Serial.print(F(" From ")); Serial.print(remoteIpNo); 579 | Serial.print(F(", port ")); Serial.print(remotePortNo); 580 | Serial.print(F(", token: ")); 581 | Serial.println(token, HEX); 582 | Serial.println(); 583 | } 584 | break; 585 | 586 | case PKT_PULL_DATA: // 0x02 UP 587 | Serial.print(F(" Pull Data")); 588 | Serial.println(); 589 | break; 590 | 591 | // This message type is used to confirm OTAA message to the node 592 | // XXX This message format may also be used for other downstream communucation 593 | case PKT_PULL_RESP: // 0x03 DOWN 594 | 595 | lastTmst = micros(); // Store the tmst this package was received 596 | // Send to the LoRa Node first (timing) and then do messaging 597 | if (sendPacket(data, packetSize-4) < 0) { 598 | return(-1); 599 | } 600 | 601 | // Now respond with an PKT_PULL_ACK; 0x04 UP 602 | buff[0]=buff_down[0]; 603 | buff[1]=buff_down[1]; 604 | buff[2]=buff_down[2]; 605 | //buff[3]=PKT_PULL_ACK; // Pull request/Change of Mogyi 606 | buff[3]=PKT_TX_ACK; 607 | buff[4]=MAC_array[0]; 608 | buff[5]=MAC_array[1]; 609 | buff[6]=MAC_array[2]; 610 | buff[7]=0xFF; 611 | buff[8]=0xFF; 612 | buff[9]=MAC_array[3]; 613 | buff[10]=MAC_array[4]; 614 | buff[11]=MAC_array[5]; 615 | buff[12]=0; 616 | 617 | // Only send the PKT_PULL_ACK to the UDP socket that just sent the data!!! 618 | Udp.beginPacket(remoteIpNo, remotePortNo); 619 | if (Udp.write((char *)buff, 12) != 12) { 620 | Serial.println("PKT_PULL_ACK:: Error writing Ack"); 621 | } 622 | else { 623 | if (debug>=1) { 624 | Serial.print(F("PKT_TX_ACK:: tmst=")); 625 | Serial.println(micros()); 626 | } 627 | } 628 | //yield(); 629 | Udp.endPacket(); 630 | 631 | if (debug >=1) { 632 | Serial.print(F("PKT_PULL_RESP:: size ")); Serial.print(packetSize); 633 | Serial.print(F(" From ")); Serial.print(remoteIpNo); 634 | Serial.print(F(", port ")); Serial.print(remotePortNo); 635 | Serial.print(F(", data: ")); 636 | data = buff_down + 4; 637 | data[packetSize] = 0; 638 | Serial.print((char *)data); 639 | Serial.println(F("...")); 640 | } 641 | 642 | break; 643 | 644 | case PKT_PULL_ACK: // 0x04 DOWN; the server sends a PULL_ACK to confirm PULL_DATA receipt 645 | if (debug >= 2) { 646 | Serial.print(F("PKT_PULL_ACK:: size ")); Serial.print(packetSize); 647 | Serial.print(F(" From ")); Serial.print(remoteIpNo); 648 | Serial.print(F(", port ")); Serial.print(remotePortNo); 649 | Serial.print(F(", data: ")); 650 | for (int i=0; i0) Serial.println(F("WiFi reconnected")); 696 | delay(10); 697 | } 698 | 699 | //send the update 700 | 701 | Udp.beginPacket(ttnServer, (int) _TTNPORT); 702 | if ((l = Udp.write((char *)msg, length)) != length) { 703 | Serial.println("sendUdp:: Error write"); 704 | } 705 | else { 706 | if (debug>=3) { 707 | Serial.print(F("sendUdp 1: sent ")); 708 | Serial.print(l); 709 | Serial.println(F(" bytes")); 710 | } 711 | } 712 | yield(); 713 | Udp.endPacket(); 714 | 715 | #ifdef _THINGSERVER 716 | delay(1); 717 | 718 | Udp.beginPacket(thingServer, (int) _THINGPORT); 719 | if ((l = Udp.write((char *)msg, length)) != length) { 720 | Serial.println("sendUdp:: Error write"); 721 | } 722 | else { 723 | if (debug>=3) { 724 | Serial.print(F("sendUdp 2: sent ")); 725 | Serial.print(l); 726 | Serial.println(F(" bytes")); 727 | } 728 | } 729 | yield(); 730 | Udp.endPacket(); 731 | #endif 732 | 733 | return; 734 | } 735 | 736 | 737 | // ---------------------------------------------------------------------------- 738 | // connect to UDP – returns true if successful or false if not 739 | // ---------------------------------------------------------------------------- 740 | bool UDPconnect() { 741 | 742 | bool ret = false; 743 | unsigned int localPort = _LOCUDPPORT; // To listen to return messages from WiFi 744 | if (debug>=1) { 745 | Serial.print(F("Local UDP port ")); 746 | Serial.println(localPort); 747 | } 748 | 749 | if (Udp.begin(localPort) == 1) { 750 | if (debug>=1) Serial.println(F("Connection successful")); 751 | ret = true; 752 | } 753 | else{ 754 | //Serial.println("Connection failed"); 755 | } 756 | return(ret); 757 | } 758 | 759 | 760 | 761 | 762 | // ---------------------------------------------------------------------------- 763 | // Send UP periodic Pull_DATA message to server to keepalive the connection 764 | // and to invite the server to send downstream messages when these are available 765 | // *2, par. 5.2 766 | // - Protocol Version (1 byte) 767 | // - Random Token (2 bytes) 768 | // - PULL_DATA identifier (1 byte) = 0x02 769 | // - Gateway unique identifier (8 bytes) = MAC address 770 | // ---------------------------------------------------------------------------- 771 | void pullData() { 772 | 773 | uint8_t pullDataReq[13]; // status report as a JSON object 774 | int pullIndex=0; 775 | int i; 776 | 777 | // pre-fill the data buffer with fixed fields 778 | pullDataReq[0] = PROTOCOL_VERSION; // 0x01 779 | uint8_t token_h = (uint8_t)rand(); // random token 780 | uint8_t token_l = (uint8_t)rand(); // random token 781 | pullDataReq[1] = token_h; 782 | pullDataReq[2] = token_l; 783 | pullDataReq[3] = PKT_PULL_DATA; // 0x02 784 | 785 | // READ MAC ADDRESS OF ESP8266, and return unique Gateway ID consisting of MAC address and 2bytes 0xFF 786 | pullDataReq[4] = MAC_array[0]; 787 | pullDataReq[5] = MAC_array[1]; 788 | pullDataReq[6] = MAC_array[2]; 789 | pullDataReq[7] = 0xFF; 790 | pullDataReq[8] = 0xFF; 791 | pullDataReq[9] = MAC_array[3]; 792 | pullDataReq[10] = MAC_array[4]; 793 | pullDataReq[11] = MAC_array[5]; 794 | 795 | pullIndex = 12; // 12-byte header 796 | 797 | pullDataReq[pullIndex] = 0; // add string terminator, for safety 798 | 799 | if (debug>= 2) { 800 | Serial.print(F("PKT_PULL_DATA request: <")); 801 | Serial.print(pullIndex); 802 | Serial.print(F("> ")); 803 | for (i=0; i 819 | // ---------------------------------------------------------------------------- 820 | void sendstat() { 821 | 822 | uint8_t status_report[STATUS_SIZE]; // status report as a JSON object 823 | char stat_timestamp[32]; // XXX was 24 824 | time_t t; 825 | char clat[10]={0}; 826 | char clon[10]={0}; 827 | 828 | int stat_index=0; 829 | uint8_t token_h = (uint8_t)rand(); // random token 830 | uint8_t token_l = (uint8_t)rand(); // random token 831 | 832 | // pre-fill the data buffer with fixed fields 833 | status_report[0] = PROTOCOL_VERSION; // 0x01 834 | status_report[1] = token_h; 835 | status_report[2] = token_l; 836 | status_report[3] = PKT_PUSH_DATA; // 0x00 837 | 838 | // READ MAC ADDRESS OF ESP8266, and return unique Gateway ID consisting of MAC address and 2bytes 0xFF 839 | status_report[4] = MAC_array[0]; 840 | status_report[5] = MAC_array[1]; 841 | status_report[6] = MAC_array[2]; 842 | status_report[7] = 0xFF; 843 | status_report[8] = 0xFF; 844 | status_report[9] = MAC_array[3]; 845 | status_report[10] = MAC_array[4]; 846 | status_report[11] = MAC_array[5]; 847 | 848 | stat_index = 12; // 12-byte header 849 | 850 | t = now(); // get timestamp for statistics 851 | 852 | // XXX Using CET as the current timezone. Change to your timezone 853 | sprintf(stat_timestamp, "%04d-%02d-%02d %02d:%02d:%02d CET", year(),month(),day(),hour(),minute(),second()); 854 | yield(); 855 | 856 | ftoa(lat,clat,5); // Convert lat to char array with 5 decimals 857 | ftoa(lon,clon,5); // As Arduino CANNOT prints floats 858 | 859 | // Build the Status message in JSON format, XXX Split this one up... 860 | delay(1); 861 | 862 | int j = snprintf((char *)(status_report + stat_index), STATUS_SIZE-stat_index, 863 | "{\"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\"}}", 864 | stat_timestamp, clat, clon, (int)alt, cp_nb_rx_rcv, cp_nb_rx_ok, cp_up_pkt_fwd, 0, 0, 0,platform,email,description); 865 | 866 | yield(); // Give way to the internal housekeeping of the ESP8266 867 | if (debug >=1) { delay(1); } 868 | stat_index += j; 869 | status_report[stat_index] = 0; // add string terminator, for safety 870 | 871 | if (debug>=2) { 872 | Serial.print(F("stat update: <")); 873 | Serial.print(stat_index); 874 | Serial.print(F("> ")); 875 | Serial.println((char *)(status_report+12)); // DEBUG: display JSON stat 876 | } 877 | 878 | if (stat_index > STATUS_SIZE) { 879 | Serial.println(F("sendstat:: ERROR buffer too big")); 880 | return; 881 | } 882 | 883 | //send the update 884 | sendUdp(status_report, stat_index); 885 | return; 886 | } 887 | 888 | 889 | void setup() { 890 | char buf[30]; 891 | 892 | Serial.begin(_BAUDRATE); 893 | Serial.flush(); 894 | Serial.println(); 895 | delay(100); 896 | 897 | if (!SPIFFS.begin()) { 898 | Serial.println(F("Failed to load SPIFFS")); 899 | } 900 | 901 | #if OLED==1 902 | // Initialising the UI will init the display too. 903 | display.init(); 904 | display.flipScreenVertically(); 905 | display.setFont(ArialMT_Plain_24); 906 | display.setTextAlignment(TEXT_ALIGN_LEFT); 907 | display.drawString(0, 24, "STARTING"); 908 | display.display(); 909 | #endif 910 | 911 | WiFi.mode(WIFI_STA); 912 | WlanReadWpa(); // Read the last Wifi settings from SPIFFS into memory 913 | 914 | WiFi.macAddress(MAC_array); 915 | Serial.print(F("MAC Addr: ")); 916 | 917 | for (int i=0; i<6; i++) { 918 | sprintf(buf, "%02X", MAC_array[i]); 919 | Serial.print(buf); 920 | 921 | if (i<5) { 922 | Serial.print("-"); 923 | } else { 924 | Serial.println(); 925 | } 926 | } 927 | 928 | // We start by connecting to a WiFi network, set hostname 929 | char hostname[12]; 930 | sprintf(hostname, "LoRaGo-%02X%02X%02X", MAC_array[3], MAC_array[4], MAC_array[5]); 931 | 932 | wifi_station_set_hostname(hostname); 933 | 934 | // Setup WiFi UDP connection. Give it some time .. 935 | while (WlanConnect() < 0) { 936 | Serial.println(F("[Error]: Failed to connect to Wifi network")); 937 | yield(); 938 | } 939 | 940 | // Test the UDP function 941 | if (!UDPconnect()) { 942 | Serial.println(F("Error UDPconnect")); 943 | } 944 | 945 | // Pins are defined and set in loraModem.h 946 | pinMode(pins.ss, OUTPUT); 947 | pinMode(pins.rst, OUTPUT); 948 | pinMode(pins.dio0, INPUT); // This pin is interrupt 949 | pinMode(pins.dio1, INPUT); // This pin is interrupt 950 | 951 | SPI.begin(); 952 | SPI.setFrequency( SPIFREQ ); // <=10 MHz 953 | delay(500); 954 | 955 | // We choose the Gateway ID to be the Ethernet Address of our Gateway card 956 | // display results of getting hardware address 957 | // 958 | Serial.print("Gateway ID: "); 959 | printHexDigit(MAC_array[0]); 960 | printHexDigit(MAC_array[1]); 961 | printHexDigit(MAC_array[2]); 962 | printHexDigit(0xFF); 963 | printHexDigit(0xFF); 964 | printHexDigit(MAC_array[3]); 965 | printHexDigit(MAC_array[4]); 966 | printHexDigit(MAC_array[5]); 967 | 968 | Serial.print(", Listening at SF"); 969 | Serial.print(sf); 970 | Serial.print(" on "); 971 | Serial.print((double)freq/1000000); 972 | Serial.println(" Mhz."); 973 | 974 | WiFi.hostByName(_TTNSERVER, ttnServer); // Use DNS to get server IP once 975 | delay(500); 976 | #ifdef _THINGSERVER 977 | WiFi.hostByName(_THINGSERVER, thingServer); 978 | delay(500); 979 | #endif 980 | 981 | #if NTP_INTR==1 982 | setupTime(); // Set NTP time host and interval 983 | #endif 984 | setTime((time_t)getNtpTime()); 985 | while (timeStatus() == timeNotSet) { 986 | Serial.println(F("setupTime:: Time not set (yet)")); 987 | delay(500); 988 | setTime((time_t)getNtpTime()); 989 | } 990 | Serial.print("Time: "); printTime(); 991 | Serial.println(); 992 | 993 | writeGwayCfg( CONFIGFILE ); 994 | // Serial.println(F("Gateway configuration saved")); 995 | 996 | #if A_SERVER==1 997 | // Setup the webserver 998 | setupWWW(); 999 | #endif 1000 | 1001 | #if A_OTA==1 1002 | setupOta(hostname); // Uses wwwServer 1003 | #endif 1004 | 1005 | delay(100); // Wait after setup 1006 | 1007 | // Setup ad initialise LoRa state machine of _loramModem.ino 1008 | _state = S_INIT; 1009 | initLoraModem(); 1010 | 1011 | _state = S_RX; 1012 | rxLoraModem(); 1013 | 1014 | if (_cad) { 1015 | _state = S_SCAN; 1016 | cadScanner(); // Always start at SF7 1017 | } 1018 | 1019 | // init interrupt handlers, which are shared for GPIO15 / D8, 1020 | // we switch on HIGH interrupts 1021 | if (pins.dio0 == pins.dio1) { 1022 | attachInterrupt(pins.dio0, Interrupt, RISING); // Share interrupts 1023 | } 1024 | // Or in the traditional Comresult case 1025 | else { 1026 | attachInterrupt(pins.dio0, Interrupt_0, RISING); // Separate interrupts 1027 | attachInterrupt(pins.dio1, Interrupt_1, RISING); // Separate interrupts 1028 | } 1029 | 1030 | writeConfig( CONFIGFILE, &gwayConfig); // Write config 1031 | 1032 | // activate OLED dieplay 1033 | #if OLED==1 1034 | // Initialising the UI will init the display too. 1035 | display.clear(); 1036 | display.setFont(ArialMT_Plain_24); 1037 | display.drawString(0, 24, "READY"); 1038 | display.display(); 1039 | #endif 1040 | 1041 | Serial.println(F("--------------------------------------")); 1042 | } 1043 | 1044 | 1045 | 1046 | // ---------------------------------------------------------------------------- 1047 | // LOOP 1048 | // This is the main program that is executed time and time again. 1049 | // We need to give way to the backend WiFi processing that 1050 | // takes place somewhere in the ESP8266 firmware and therefore 1051 | // we include yield() statements at important points. 1052 | // 1053 | // Note: If we spend too much time in user processing functions 1054 | // and the backend system cannot do its housekeeping, the watchdog 1055 | // function will be executed which means effectively that the 1056 | // program crashes. 1057 | // 1058 | // NOTE: For ESP make sure not to do lage array declarations in loop(); 1059 | // ---------------------------------------------------------------------------- 1060 | void loop () 1061 | { 1062 | uint32_t nowseconds; 1063 | int packetSize; 1064 | 1065 | // Receive Lora messages waiting, if there are any. 1066 | // Most important function in loop() 1067 | if (_state == S_RXDONE) { 1068 | eventHandler(); // Is S_RXDONE read a message 1069 | yield(); 1070 | } 1071 | 1072 | // The next section is emergency only. If posible we hop() in the state machine. 1073 | // If hopping is enabled, and by lack of timer, we hop() 1074 | // XXX Experimental, 2.5 ms between hops max 1075 | nowTime = micros(); 1076 | if ((_hop) && (((long)(nowTime - hopTime)) > 2500)) { 1077 | if ((_state == S_SCAN) && (sf==SF12)) { 1078 | if (debug>=1) Serial.println(F("loop:: hop")); 1079 | hop(); 1080 | } 1081 | // XXX section below does not work wthout further work. It is the section with the MOST 1082 | // influence on the HOP mode of operation (which is somewhat unexpected) 1083 | // If we keep staying in another state, reset 1084 | else if (((long)(nowTime - hopTime)) > 100000) { 1085 | if (debug>=2) { 1086 | Serial.print(F("loop:: reset, STATE=")); Serial.print(_state); 1087 | Serial.print(F(", F=")); Serial.print(ifreq); 1088 | } 1089 | _state= S_SCAN; 1090 | hop(); 1091 | if (debug>=2) { 1092 | Serial.print(F("->")); Serial.println(ifreq); 1093 | } 1094 | // rxLoraModem(); 1095 | if (_cad) { _state= S_SCAN; cadScanner(); } 1096 | } 1097 | else if (debug>=3) { Serial.print(F(" state=")); Serial.println(_state); } 1098 | inHop = false; // Reset re-entrane protection of HOP 1099 | yield(); 1100 | } 1101 | 1102 | 1103 | // Receive UDP PUSH_ACK messages from server. (*2, par. 3.3) 1104 | // This is important since the TTN broker will return confirmation 1105 | // messages on UDP for every message sent by the gateway. So we have to consume them.. 1106 | // As we do not know when the server will respond, we test in every loop. 1107 | while( (packetSize = Udp.parsePacket()) > 0) { // Length of UDP message waiting 1108 | yield(); 1109 | // Packet may be PKT_PUSH_ACK (0x01), PKT_PULL_ACK (0x03) or PKT_PULL_RESP (0x04) 1110 | // This command is found in byte 4 (buff_down[3]) 1111 | if (readUdp(packetSize, buff_down) < 0) { 1112 | if (debug>0) Serial.println(F("readUDP error")); 1113 | } 1114 | } 1115 | 1116 | yield(); 1117 | 1118 | // stat PUSH_DATA message (*2, par. 4) 1119 | nowseconds = (uint32_t) millis() /1000; 1120 | if (nowseconds - stattime >= _STAT_INTERVAL) { // Wake up every xx seconds 1121 | sendstat(); // Show the status message and send to server 1122 | 1123 | #if GATEWAYNODE==1 1124 | if (gwayConfig.node) { 1125 | // If the 1ch gateway is a sensor itself, send the sensor values 1126 | // could be battery but also other status info or sensor info 1127 | yield(); 1128 | 1129 | if (sensorPacket() < 0) { 1130 | Serial.println(F("sensorPacket: Error")); 1131 | } 1132 | } 1133 | #endif 1134 | stattime = nowseconds; 1135 | } 1136 | 1137 | yield(); 1138 | 1139 | // send PULL_DATA message (*2, par. 4) 1140 | nowseconds = (uint32_t) millis() /1000; 1141 | if (nowseconds - pulltime >= _PULL_INTERVAL) { // Wake up every xx seconds 1142 | pullData(); // Send PULL_DATA message to server 1143 | pulltime = nowseconds; 1144 | } 1145 | 1146 | #if A_OTA==1 1147 | // Perform Over the Air (OTA) update if enabled and requested by user. 1148 | yield(); 1149 | ArduinoOTA.handle(); 1150 | #endif 1151 | 1152 | #if A_SERVER==1 1153 | // Handle the WiFi server part of this sketch. Mainly used for administration 1154 | // and monitoring of the node 1155 | yield(); 1156 | server.handleClient(); 1157 | #endif 1158 | 1159 | #if NTP_INTR==0 1160 | // Set the time in a manual way. Do not use setSyncProvider 1161 | // as this function may collide with SPI and other interrupts 1162 | yield(); 1163 | nowseconds = (uint32_t) millis() /1000; 1164 | if (nowseconds - ntptimer >= _NTP_INTERVAL) { 1165 | yield(); 1166 | time_t newTime; 1167 | newTime = (time_t)getNtpTime(); 1168 | if (newTime != 0) setTime(newTime); 1169 | ntptimer = nowseconds; 1170 | } 1171 | #endif 1172 | } 1173 | -------------------------------------------------------------------------------- /LoRaGoDOCK-Gateway/_gatewayMgt.ino: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * 3 | * Description: Source code for single-channel LoRaWAN Gateway based on ESP8266 and SX1276 4 | * Version : 0.8.2 5 | * Date : 2018-11-26 6 | * Software : https://github.com/SandboxElectronics/LoRaGoDOCK-Gateway 7 | * Hardware : LoRaGo DOCK – http://sandboxelectronics.com/?product=lorago-dock-single-channel-lorawan-gateway 8 | * 9 | * Copyright (c) 2016, 2017 Maarten Westenberg 10 | * 11 | * All rights reserved. This program and the accompanying materials 12 | * are made available under the terms of the MIT License 13 | * which accompanies this distribution, and is available at 14 | * https://opensource.org/licenses/mit-license.php 15 | * 16 | *****************************************************************************************/ 17 | 18 | // This file contains the functions to do management over UDP 19 | // We could make use of the LoRa message function for the Gateway sensor 20 | // itself. However the functions defined in this file are not sensor 21 | // functions and activating them through the LoRa interface would add 22 | // no value and make the code more complex. 23 | // 24 | // So advantage: Simple, and does not mess with TTN setup. 25 | // 26 | // Disadvantage of course is that you need to setup you own small backend 27 | // function to exchange messages with the gateway, as TTN won't do this. 28 | // 29 | // XXX But, if necessary we can always add this later. 30 | 31 | #if GATEWAYMGT==1 32 | 33 | #if !defined _THINGPORT 34 | #error "The management functions only work over _THINGPORT and not over _TTNPORT" 35 | #endif 36 | 37 | 38 | 39 | // ---------------------------------------------------------------------------- 40 | // Ths function gateway_mgt is called in the UDP Receive function after 41 | // all well-known LoRa Gateway messages are scanned. 42 | // 43 | // As part of this function we will listen for another set of messages 44 | // that is defined in loraModem.h. 45 | // All opCodes start with 0x1y for at leaving opcodes 0x00 to 0x0F to the 46 | // pure Gateway protocol 47 | // 48 | // Incoming mesage format: 49 | // buf[0]-buf[2], These are 0x00 or dont care 50 | // buf[3], contains opcode 51 | // buf[4]-buf[7], contains parameter max. 4 bytes. 52 | // 53 | // Upstream Message format: 54 | // 55 | // ---------------------------------------------------------------------------- 56 | void gateway_mgt(uint8_t size, uint8_t *buff) { 57 | 58 | uint8_t opcode = buff[3]; 59 | 60 | switch (opcode) { 61 | case MGT_RESET: 62 | Serial.println(F("gateway_mgt:: RESET")); 63 | // No further parameters, just reset the GWay 64 | setup(); // Call the sketch setup function 65 | // Send Ack to server 66 | 67 | break; 68 | case MGT_SET_SF: 69 | Serial.println(F("gateway_mgt:: SET SF")); 70 | // byte [4] contains desired SF code (7 for SF7 and 12 for SF12) 71 | break; 72 | case MGT_SET_FREQ: 73 | Serial.println(F("gateway_mgt:: SET FREQ")); 74 | // Byte [4] contains index of Frequency 75 | break; 76 | default: 77 | Serial.print(F("gateway_mgt:: Unknown UDP code=")); 78 | Serial.println(opcode); 79 | return; 80 | break; 81 | } 82 | } 83 | 84 | #endif //GATEWAYMGT==1 85 | -------------------------------------------------------------------------------- /LoRaGoDOCK-Gateway/_loraFiles.ino: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * 3 | * Description: Source code for single-channel LoRaWAN Gateway based on ESP8266 and SX1276 4 | * Version : 0.8.2 5 | * Date : 2018-11-26 6 | * Software : https://github.com/SandboxElectronics/LoRaGoDOCK-Gateway 7 | * Hardware : LoRaGo DOCK – http://sandboxelectronics.com/?product=lorago-dock-single-channel-lorawan-gateway 8 | * 9 | * Copyright (c) 2016, 2017 Maarten Westenberg 10 | * 11 | * All rights reserved. This program and the accompanying materials 12 | * are made available under the terms of the MIT License 13 | * which accompanies this distribution, and is available at 14 | * https://opensource.org/licenses/mit-license.php 15 | * 16 | *****************************************************************************************/ 17 | 18 | // This file contains the LoRa filesystem specific code 19 | 20 | 21 | // ============================================================================ 22 | // LORA SPIFFS FILESYSTEM FUNCTIONS 23 | // 24 | // The LoRa supporting functions are in the section below 25 | 26 | // ---------------------------------------------------------------------------- 27 | // Directory listing. s is a string containing HTML/text code so far. 28 | // The resulting directory listing is appended to s and returned. 29 | // ---------------------------------------------------------------------------- 30 | String espDir(String s) { 31 | 32 | return s; 33 | } 34 | 35 | // ---------------------------------------------------------------------------- 36 | // Read the gateway configuration file 37 | // ---------------------------------------------------------------------------- 38 | int readConfig(const char *fn, struct espGwayConfig *c) { 39 | Serial.println(F("Load config...")); 40 | 41 | if (!SPIFFS.exists(fn)) { 42 | Serial.print("[WARNING]: Config file "); 43 | Serial.print(fn); 44 | Serial.println(" does not exist."); 45 | return -1; 46 | } 47 | 48 | File f = SPIFFS.open(fn, "r"); 49 | 50 | if (!f) { 51 | Serial.print("[ERROR]: Failed to open config file "); 52 | Serial.println(fn); 53 | return -1; 54 | } 55 | 56 | while (f.available()) { 57 | String id = f.readStringUntil('='); 58 | String val = f.readStringUntil('\n'); 59 | 60 | if (id == "SSID") { // WiFi SSID 61 | // Serial.print(F("SSID=")); Serial.println(val); 62 | (*c).ssid = val; 63 | } 64 | if (id == "PASS") { // WiFi Password 65 | // Serial.print(F("PASS=")); Serial.println(val); 66 | (*c).pass = val; 67 | } 68 | // if (id == "CH") { // Frequency Channel 69 | //// Serial.print(F("CH=")); Serial.println(val); 70 | // (*c).ch = (uint32_t) val.toInt(); 71 | // } 72 | // if (id == "SF") { // Spreading Factor 73 | //// Serial.print(F("SF=")); Serial.println(val); 74 | // (*c).sf = (uint32_t) val.toInt(); 75 | // } 76 | if (id == "FCNT") { // Frame Counter 77 | // Serial.print(F("FCNT=")); Serial.println(val); 78 | (*c).fcnt = (uint32_t) val.toInt(); 79 | } 80 | if (id == "DEBUG") { // Debug setting 81 | // Serial.print(F("DEBUG=")); Serial.println(val); 82 | (*c).debug = (uint8_t) val.toInt(); 83 | } 84 | // if (id == "CAD") { // CAD setting 85 | //// Serial.print(F("CAD=")); Serial.println(val); 86 | // (*c).cad = (uint8_t) val.toInt(); 87 | // } 88 | // if (id == "HOP") { // HOP setting 89 | //// Serial.print(F("HOP=")); Serial.println(val); 90 | // (*c).hop = (uint8_t) val.toInt(); 91 | // } 92 | if (id == "BOOTS") { // BOOTS setting 93 | // Serial.print(F("BOOTS=")); Serial.println(val); 94 | (*c).boots = (uint8_t) val.toInt(); 95 | } 96 | if (id == "RESETS") { // RESET setting 97 | // Serial.print(F("RESETS=")); Serial.println(val); 98 | (*c).resets = (uint8_t) val.toInt(); 99 | } 100 | if (id == "WIFIS") { // WIFIS setting 101 | // Serial.print(F("WIFIS=")); Serial.println(val); 102 | (*c).wifis = (uint8_t) val.toInt(); 103 | } 104 | if (id == "VIEWS") { // VIEWS setting 105 | // Serial.print(F("VIEWS=")); Serial.println(val); 106 | (*c).views = (uint8_t) val.toInt(); 107 | } 108 | if (id == "NODE") { // NODE setting 109 | // Serial.print(F("NODE=")); Serial.println(val); 110 | (*c).node = (uint8_t) val.toInt(); 111 | } 112 | if (id == "REFR") { // REFR setting 113 | // Serial.print(F("REFR=")); Serial.println(val); 114 | (*c).refresh = (uint8_t) val.toInt(); 115 | } 116 | if (id == "REENTS") { // REENTS setting 117 | // Serial.print(F("REENTS=")); Serial.println(val); 118 | (*c).reents = (uint8_t) val.toInt(); 119 | } 120 | if (id == "NTPERR") { // NTPERR setting 121 | // Serial.print(F("NTPERR=")); Serial.println(val); 122 | (*c).ntpErr = (uint8_t) val.toInt(); 123 | } 124 | if (id == "NTPS") { // NTPS setting 125 | // Serial.print(F("NTPS=")); Serial.println(val); 126 | (*c).ntps = (uint8_t) val.toInt(); 127 | } 128 | } 129 | 130 | c->cad = _CAD; 131 | c->hop = 0; 132 | c->ch = _CH; 133 | c->sf = _SPREADING; 134 | 135 | f.close(); 136 | return 1; 137 | } 138 | 139 | // ---------------------------------------------------------------------------- 140 | // Write the current gateway configuration to SPIFFS. First copy all the 141 | // separate data items to the gwayConfig structure 142 | // 143 | // ---------------------------------------------------------------------------- 144 | int writeGwayCfg(const char *fn) { 145 | 146 | gwayConfig.sf = (uint8_t) sf; 147 | gwayConfig.ssid = WiFi.SSID(); 148 | //gwayConfig.pass = WiFi.PASS(); // XXX We should find a way to store the password too 149 | gwayConfig.ch = ifreq; 150 | gwayConfig.debug = debug; 151 | gwayConfig.cad = _cad; 152 | gwayConfig.hop = _hop; 153 | #if GATEWAYNODE==1 154 | gwayConfig.fcnt = frameCount; 155 | #endif 156 | 157 | return(writeConfig(fn, &gwayConfig)); 158 | } 159 | 160 | // ---------------------------------------------------------------------------- 161 | // Write the configuration ad found in the espGwayConfig structure 162 | // to SPIFFS 163 | // ---------------------------------------------------------------------------- 164 | int writeConfig(const char *fn, struct espGwayConfig *c) { 165 | 166 | if (!SPIFFS.exists(fn)) { 167 | Serial.print("WARNING:: writeConfig, file does not exist, formatting "); 168 | SPIFFS.format(); 169 | // XXX make all initial declarations here if config vars need to have a value 170 | Serial.println(fn); 171 | } 172 | File f = SPIFFS.open(fn, "w"); 173 | if (!f) { 174 | Serial.print("ERROR:: writeConfig, file open failed for file="); 175 | Serial.print(fn); 176 | Serial.println(); 177 | return(-1); 178 | } 179 | 180 | f.print("SSID"); f.print('='); f.print((*c).ssid); f.print('\n'); 181 | f.print("PASS"); f.print('='); f.print((*c).pass); f.print('\n'); 182 | f.print("CH"); f.print('='); f.print((*c).ch); f.print('\n'); 183 | f.print("SF"); f.print('='); f.print((*c).sf); f.print('\n'); 184 | f.print("FCNT"); f.print('='); f.print((*c).fcnt); f.print('\n'); 185 | f.print("DEBUG"); f.print('='); f.print((*c).debug); f.print('\n'); 186 | f.print("CAD"); f.print('='); f.print((*c).cad); f.print('\n'); 187 | f.print("HOP"); f.print('='); f.print((*c).hop); f.print('\n'); 188 | f.print("NODE"); f.print('='); f.print((*c).node); f.print('\n'); 189 | f.print("BOOTS"); f.print('='); f.print((*c).boots); f.print('\n'); 190 | f.print("RESETS"); f.print('='); f.print((*c).resets); f.print('\n'); 191 | f.print("WIFIS"); f.print('='); f.print((*c).wifis); f.print('\n'); 192 | f.print("VIEWS"); f.print('='); f.print((*c).views); f.print('\n'); 193 | f.print("REFR"); f.print('='); f.print((*c).refresh); f.print('\n'); 194 | f.print("REENTS"); f.print('='); f.print((*c).reents); f.print('\n'); 195 | f.print("NTPERR"); f.print('='); f.print((*c).ntpErr); f.print('\n'); 196 | f.print("NTPS"); f.print('='); f.print((*c).ntps); f.print('\n'); 197 | 198 | f.close(); 199 | return(1); 200 | } 201 | -------------------------------------------------------------------------------- /LoRaGoDOCK-Gateway/_loraModem.ino: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * 3 | * Description: Source code for single-channel LoRaWAN Gateway based on ESP8266 and SX1276 4 | * Version : 0.8.2 5 | * Date : 2018-11-26 6 | * Software : https://github.com/SandboxElectronics/LoRaGoDOCK-Gateway 7 | * Hardware : LoRaGo DOCK – http://sandboxelectronics.com/?product=lorago-dock-single-channel-lorawan-gateway 8 | * 9 | * Copyright (c) 2016, 2017 Maarten Westenberg 10 | * 11 | * All rights reserved. This program and the accompanying materials 12 | * are made available under the terms of the MIT License 13 | * which accompanies this distribution, and is available at 14 | * https://opensource.org/licenses/mit-license.php 15 | * 16 | *****************************************************************************************/ 17 | 18 | // This file contains the LoRa modem specific code enabling to receive 19 | // and transmit packages/messages. 20 | 21 | // STATE MACHINE 22 | // The program uses the following state machine (in _state), all states 23 | // are done in interrupt routine, only the follow-up of S_RXDONE is done 24 | // in the main loop() program. This is because otherwise the interrupt processing 25 | // would take too long to finish 26 | // 27 | // S_INIT=0, The commands in this state are executed only once 28 | // - Goto S_SCAN 29 | // S_SCAN, CadScanner() part 30 | // - upon CDDONE (int0) got S_CAD 31 | // S_CAD, 32 | // - Upon CDDECT (int1) goto S_RX, 33 | // - Upon CDDONE (int0) goto S_SCAN 34 | // S_RX, Received CDDECT so message detected, RX cycle started. 35 | // - Upon RXDONE (int0) read ok goto S_RXDONE, 36 | // - upon RXTOUT (int1) goto S_SCAN 37 | // S_RXDONE, Read the buffer 38 | // - Wait for reading in loop() 39 | // - Upon message send to server goto S_SCAN 40 | // S_TX Transmitting a message 41 | // - Upon TX goto S_SCAN 42 | // 43 | 44 | 45 | 46 | void printState(uint8_t i) 47 | { 48 | uint8_t intr = flags & ( ~ mask ); // Only react on non masked interrupts 49 | if (i>= debug) { 50 | Serial.print(F(" state=")); Serial.print(_state); 51 | Serial.print(F(", sf=")); Serial.print(sf); 52 | Serial.print(F(", rssi=")); Serial.print(_rssi); 53 | Serial.print(F(", flags=0x")); if (flags<16) Serial.print('0'); Serial.print(flags,HEX); 54 | Serial.print(F(", mask=0x")); if (mask<16) Serial.print('0'); Serial.print(mask,HEX); 55 | Serial.print(F(", intr=0x")); if (intr<16) Serial.print('0'); Serial.print(intr,HEX); 56 | Serial.print(F(", ch=")); Serial.print(ifreq); 57 | Serial.println(); 58 | } 59 | } 60 | 61 | // ============================================================================ 62 | // LORA GATEWAY/MODEM FUNCTIONS 63 | // 64 | // The LoRa supporting functions are in the section below 65 | 66 | 67 | 68 | 69 | // ---------------------------------------------------------------------------- 70 | // Read one byte value, par addr is address 71 | // Returns the value of register(addr) 72 | // 73 | // The SS (Chip select) pin is used to make sure the RFM95 is selected 74 | // As we have no other SPI devices in the gateway, we could choose 75 | // to make this pin low (==selected) most of the time 76 | // ---------------------------------------------------------------------------- 77 | #if REENTRANT==2 78 | uint8_t ICACHE_RAM_ATTR readRegister(uint8_t addr) 79 | #else 80 | uint8_t readRegister(uint8_t addr) 81 | #endif 82 | { 83 | digitalWrite(pins.ss, LOW); // Select Receiver 84 | SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE0)); 85 | SPI.transfer(addr & 0x7F); 86 | uint8_t res = SPI.transfer(0x00); 87 | SPI.endTransaction(); 88 | digitalWrite(pins.ss, HIGH); // Unselect Receiver 89 | return res; 90 | } 91 | 92 | 93 | // ---------------------------------------------------------------------------- 94 | // Write value to a register with address addr. 95 | // Function writes one byte at a time. 96 | // ---------------------------------------------------------------------------- 97 | #if REENTRANT==2 98 | void ICACHE_RAM_ATTR writeRegister(uint8_t addr, uint8_t value) 99 | #else 100 | void writeRegister(uint8_t addr, uint8_t value) 101 | #endif 102 | { 103 | unsigned char spibuf[2]; 104 | 105 | spibuf[0] = addr | 0x80; 106 | spibuf[1] = value; 107 | digitalWrite(pins.ss, LOW); // Select Receiver 108 | SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE0)); 109 | SPI.transfer(spibuf[0]); 110 | SPI.transfer(spibuf[1]); 111 | SPI.endTransaction(); 112 | digitalWrite(pins.ss, HIGH); // Unselect Receiver 113 | } 114 | 115 | // ---------------------------------------------------------------------------- 116 | // setRate is setting rate and spreading factor and CRC etc. for transmission 117 | // Modem Config 1 (MC1) == 0x72 for sx1276 118 | // Modem Config 2 (MC2) == (CRC_ON) | (sf<<4) 119 | // Modem Config 3 (MC3) == 0x04 | (optional SF11/12 LOW DATA OPTIMIZE 0x08) 120 | // sf == SF7 default 0x07, (SF7<<4) == SX72_MC2_SF7 121 | // bw == 125 == 0x70 122 | // cr == CR4/5 == 0x02 123 | // CRC_ON == 0x04 124 | // ---------------------------------------------------------------------------- 125 | //#if REENTRANT==2 126 | //void ICACHE_RAM_ATTR setRate(uint8_t sf, uint8_t crc) 127 | //#else 128 | void setRate(uint8_t sf, uint8_t crc) 129 | //#endif 130 | { 131 | uint8_t mc1=0, mc2=0, mc3=0; 132 | // Set rate based on Spreading Factor etc 133 | if (sx1272) { 134 | mc1= 0x0A; // SX1276_MC1_BW_250 0x80 | SX1276_MC1_CR_4_5 0x02 135 | mc2= (sf<<4) | crc; 136 | // SX1276_MC1_BW_250 0x80 | SX1276_MC1_CR_4_5 0x02 | SX1276_MC1_IMPLICIT_HEADER_MODE_ON 0x01 137 | if (sf == SF11 || sf == SF12) { mc1= 0x0B; } 138 | } 139 | else { 140 | mc1= 0x72; // SX1276_MC1_BW_125==0x70 | SX1276_MC1_CR_4_5==0x02 141 | mc2= (sf<<4) | crc; // crc is 0x00 or 0x04==SX1276_MC2_RX_PAYLOAD_CRCON 142 | mc3= 0x04; // 0x04; SX1276_MC3_AGCAUTO 143 | if (sf == SF11 || sf == SF12) { mc3|= 0x08; } // 0x08 | 0x04 144 | } 145 | 146 | // Implicit Header (IH), for class b beacons 147 | //if (getIh(LMIC.rps)) { 148 | // mc1 |= SX1276_MC1_IMPLICIT_HEADER_MODE_ON; 149 | // writeRegister(REG_PAYLOAD_LENGTH, getIh(LMIC.rps)); // required length 150 | //} 151 | writeRegister(REG_MODEM_CONFIG1, mc1); 152 | writeRegister(REG_MODEM_CONFIG2, mc2); 153 | writeRegister(REG_MODEM_CONFIG3, mc3); 154 | 155 | // Symbol timeout settings 156 | if (sf == SF10 || sf == SF11 || sf == SF12) { 157 | writeRegister(REG_SYMB_TIMEOUT_LSB,0x05); 158 | } else { 159 | writeRegister(REG_SYMB_TIMEOUT_LSB,0x08); 160 | } 161 | return; 162 | } 163 | 164 | 165 | // ---------------------------------------------------------------------------- 166 | // Set the frequency for our gateway 167 | // The function has no parameter other than the freq setting used in init. 168 | // Since we are usin a 1ch gateway this value is set fixed. 169 | // ---------------------------------------------------------------------------- 170 | #if REENTRANT==2 171 | void ICACHE_RAM_ATTR setFreq(uint32_t freq) 172 | #else 173 | void setFreq(uint32_t freq) 174 | #endif 175 | { 176 | // set frequency 177 | uint64_t frf = ((uint64_t)freq << 19) / 32000000; 178 | writeRegister(REG_FRF_MSB, (uint8_t)(frf>>16) ); 179 | writeRegister(REG_FRF_MID, (uint8_t)(frf>> 8) ); 180 | writeRegister(REG_FRF_LSB, (uint8_t)(frf>> 0) ); 181 | 182 | return; 183 | } 184 | 185 | 186 | // ---------------------------------------------------------------------------- 187 | // Set Power for our gateway 188 | // ---------------------------------------------------------------------------- 189 | #if REENTRANT==2 190 | void ICACHE_RAM_ATTR setPow(uint8_t powe) 191 | #else 192 | void setPow(uint8_t powe) 193 | #endif 194 | { 195 | if (powe >= 16) powe = 15; 196 | //if (powe >= 15) powe = 14; 197 | else if (powe < 2) powe =2; 198 | 199 | uint8_t pac = 0x80 | (powe & 0xF); 200 | writeRegister(REG_PAC,pac); // set 0x09 to pac 201 | 202 | // XXX Power settings for CFG_sx1272 are different 203 | 204 | return; 205 | } 206 | 207 | 208 | // ---------------------------------------------------------------------------- 209 | // Used to set the radio to LoRa mode (transmitter) 210 | // ---------------------------------------------------------------------------- 211 | #if REENTRANT==2 212 | void ICACHE_RAM_ATTR opmodeLora() 213 | #else 214 | static void opmodeLora() 215 | #endif 216 | { 217 | uint8_t u = OPMODE_LORA; 218 | #ifdef CFG_sx1276_radio 219 | u |= 0x8; // TBD: sx1276 high freq 220 | #endif 221 | writeRegister(REG_OPMODE, u); 222 | } 223 | 224 | 225 | // ---------------------------------------------------------------------------- 226 | // Set the opmode to a value as defined on top 227 | // Values are 0x00 to 0x07 228 | // ---------------------------------------------------------------------------- 229 | #if REENTRANT==2 230 | void ICACHE_RAM_ATTR opmode(uint8_t mode) 231 | #else 232 | static void opmode(uint8_t mode) 233 | #endif 234 | { 235 | writeRegister(REG_OPMODE, (readRegister(REG_OPMODE) & ~OPMODE_MASK) | mode); 236 | } 237 | 238 | // ---------------------------------------------------------------------------- 239 | // Hop to next frequency as defined by NUM_HOPS 240 | // This function should only be used for receiver operation. The current 241 | // receiver frequency is determined by ifreq index like so: freqs[ifreq] 242 | // ---------------------------------------------------------------------------- 243 | void hop() { 244 | // If we are already in a hop function, do not proceed 245 | if (!inHop) { 246 | 247 | inHop=true; 248 | opmode(OPMODE_STANDBY); 249 | ifreq = (ifreq + 1)% NUM_HOPS ; 250 | setFreq(freqs[ifreq]); 251 | hopTime = micros(); // record HOP moment 252 | opmode(OPMODE_CAD); 253 | inHop=false; 254 | } 255 | else { 256 | if (debug >= 3) Serial.println(F("Hop:: Re-entrance try")); 257 | } 258 | } 259 | 260 | 261 | 262 | // ---------------------------------------------------------------------------- 263 | // This DOWN function sends a payload to the LoRa node over the air 264 | // Radio must go back in standby mode as soon as the transmission is finished 265 | // This is done outside the function but in main loop() 266 | // ---------------------------------------------------------------------------- 267 | bool sendPkt(uint8_t *payLoad, uint8_t payLength, uint32_t tmst) 268 | { 269 | writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_TX_BASE_AD)); // 0x0D, 0x0E 270 | writeRegister(REG_PAYLOAD_LENGTH, payLength); // 0x22 271 | for(int i = 0; i < payLength; i++) 272 | { 273 | writeRegister(REG_FIFO, payLoad[i]); // 0x00 274 | } 275 | return true; 276 | } 277 | 278 | 279 | // ---------------------------------------------------------------------------- 280 | // loraWait() 281 | // This function implements the wait protocol needed for downstream transmissions. 282 | // Note: Timing of downstream and JoinAccept messages is VERY critical. 283 | // 284 | // As the ESP8266 watchdog will not like us to wait more than a few hundred 285 | // milliseconds (or it will kick in) we have to implement a simple way to wait 286 | // time in case we have to wait seconds before sending messages (e.g. for OTAA 5 or 6 seconds) 287 | // Without it, the system is known to crash in half of the cases it has to wait for 288 | // JOIN-ACCEPT messages to send. 289 | // 290 | // This function uses a combination of delay() statements and delayMicroseconds(). 291 | // As we use delay() only when there is still enough time to wait and we use micros() 292 | // to make sure that delay() did not take too much time this works. 293 | // 294 | // Parameter: uint32-t tmst gives the micros() value when transmission should start. 295 | // ---------------------------------------------------------------------------- 296 | 297 | void loraWait(uint32_t tmst) 298 | { 299 | uint32_t startTime = micros(); // Start of the loraWait function 300 | tmst += txDelay; 301 | uint32_t waitTime = tmst - micros(); 302 | 303 | while (waitTime > 16000) { 304 | delay(15); // ms delay() including yield, slightly shorter 305 | waitTime= tmst - micros(); 306 | } 307 | if (waitTime>0) delayMicroseconds(waitTime); 308 | else if ((waitTime+20) < 0) Serial.println(F("loraWait TOO LATE")); 309 | #if DEBUG>=1 310 | if (debug >=1) { 311 | Serial.print(F("start: ")); 312 | Serial.print(startTime); 313 | Serial.print(F(", end: ")); 314 | Serial.print(tmst); 315 | Serial.print(F(", waited: ")); 316 | Serial.print(tmst - startTime); 317 | Serial.print(F(", delay=")); 318 | Serial.print(txDelay); 319 | Serial.println(); 320 | } 321 | #endif 322 | } 323 | 324 | 325 | // ---------------------------------------------------------------------------- 326 | // txLoraModem 327 | // Init the transmitter and transmit the buffer 328 | // After successful transmission (dio0==1) TxDone re-init the receiver 329 | // 330 | // crc is set to 0x00 for TX 331 | // iiq is set to 0x27 (or 0x40 based on ipol value in txpkt) 332 | // 333 | // 1. opmodeLora 334 | // 2. opmode StandBY 335 | // 3. Configure Modem 336 | // 4. Configure Channel 337 | // 5. write PA Ramp 338 | // 6. config Power 339 | // 7. RegLoRaSyncWord LORA_MAC_PREAMBLE 340 | // 8. write REG dio mapping (dio0) 341 | // 9. write REG IRQ flags 342 | // 10. write REG IRQ flags mask 343 | // 11. write REG LoRa Fifo Base Address 344 | // 12. write REG LoRa Fifo Addr Ptr 345 | // 13. write REG LoRa Payload Length 346 | // 14. Write buffer (byte by byte) 347 | // 15. Wait until the right time to transmit has arrived 348 | // 16. opmode TX 349 | // ---------------------------------------------------------------------------- 350 | 351 | static void txLoraModem(uint8_t *payLoad, uint8_t payLength, uint32_t tmst, uint8_t sfTx, 352 | uint8_t powe, uint32_t freq, uint8_t crc, uint8_t iiq) 353 | { 354 | 355 | if (debug>=1) { 356 | // Make sure that all serial stuff is done before continuing 357 | Serial.print(F("txLoraModem::")); 358 | Serial.print(F(" powe: ")); Serial.print(powe); 359 | Serial.print(F(", freq: ")); Serial.print(freq); 360 | Serial.print(F(", crc: ")); Serial.print(crc); 361 | Serial.print(F(", iiq: 0X")); Serial.print(iiq,HEX); 362 | Serial.println(); 363 | Serial.flush(); 364 | } 365 | _state = S_TX; 366 | 367 | // 1. Select LoRa modem from sleep mode 368 | opmodeLora(); // set register 0x01 to 0x80 369 | 370 | // Assert the value of the current mode 371 | ASSERT((readRegister(REG_OPMODE) & OPMODE_LORA) != 0); 372 | 373 | // 2. enter standby mode (required for FIFO loading)) 374 | opmode(OPMODE_STANDBY); // set 0x01 to 0x01 375 | 376 | // 3. Init spreading factor and other Modem setting 377 | setRate(sfTx, crc); 378 | 379 | // Frquency hopping 380 | //writeRegister(REG_HOP_PERIOD, 0x00); // set 0x24 to 0x00 only for receivers 381 | 382 | // 4. Init Frequency, config channel 383 | setFreq(freq); 384 | 385 | // 6. Set power level, REG_PAC 386 | setPow(powe); 387 | 388 | // 7. prevent node to node communication 389 | writeRegister(REG_INVERTIQ,iiq); // 0x33, (0x27 or 0x40) 390 | 391 | // 8. set the IRQ mapping DIO0=TxDone DIO1=NOP DIO2=NOP (or lesss for 1ch gateway) 392 | writeRegister(REG_DIO_MAPPING_1, (MAP_DIO0_LORA_TXDONE|MAP_DIO1_LORA_NOP|MAP_DIO2_LORA_NOP)); 393 | 394 | // 9. clear all radio IRQ flags 395 | writeRegister(REG_IRQ_FLAGS, 0xFF); 396 | 397 | // 10. mask all IRQs but TxDone 398 | writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) ~IRQ_LORA_TXDONE_MASK); 399 | 400 | // txLora 401 | opmode(OPMODE_FSTX); // set 0x01 to 0x02 (actual value becomes 0x82) 402 | 403 | // 11, 12, 13, 14. write the buffer to the FiFo 404 | sendPkt(payLoad, payLength, tmst); 405 | 406 | // 15. wait extra delay out. The delayMicroseconds timer is accurate until 16383 uSec. 407 | loraWait(tmst); 408 | 409 | // 16. Initiate actual transmission of FiFo 410 | opmode(OPMODE_TX); // set 0x01 to 0x03 (actual value becomes 0x83) 411 | 412 | // Reset the IRQ register 413 | //writeRegister(REG_IRQ_FLAGS, 0xFF); // set 0x12 to 0xFF 414 | writeRegister(REG_IRQ_FLAGS, IRQ_LORA_TXDONE_MASK); // set 0x12 to 0x08 415 | } 416 | 417 | 418 | // ---------------------------------------------------------------------------- 419 | // Setup the LoRa receiver on the connected transceiver. 420 | // - Determine the correct transceiver type (sx1272/RFM92 or sx1276/RFM95) 421 | // - Set the frequency to listen to (1-channel remember) 422 | // - Set Spreading Factor (standard SF7) 423 | // The reset RST pin might not be necessary for at least the RGM95 transceiver 424 | // 425 | // 1. Put the radio in LoRa mode 426 | // 2. Put modem in sleep or in standby 427 | // 3. Set Frequency 428 | // ---------------------------------------------------------------------------- 429 | #if REENTRANT==2 430 | void ICACHE_RAM_ATTR rxLoraModem() 431 | #else 432 | void rxLoraModem() 433 | #endif 434 | { 435 | // 1. Put system in LoRa mode 436 | opmodeLora(); 437 | 438 | // 2. Put the radio in sleep mode 439 | opmode(OPMODE_SLEEP); // set 0x01 to 0x00 440 | 441 | // 3. Set frequency based on value in freq 442 | setFreq(freqs[ifreq]); // set to 868.1MHz 443 | 444 | // 4. Set spreading Factor and CRC 445 | setRate(sf, 0x04); 446 | 447 | // prevent node to node communication 448 | writeRegister(REG_INVERTIQ,0x27); // 0x33, 0x27; to reset from TX 449 | 450 | // Max Payload length is dependent on 256 byte buffer. At startup TX starts at 451 | // 0x80 and RX at 0x00. RX therefore maximized at 128 Bytes 452 | writeRegister(REG_MAX_PAYLOAD_LENGTH,0x80); // set 0x23 to 0x80 453 | writeRegister(REG_PAYLOAD_LENGTH,PAYLOAD_LENGTH); // 0x22, 0x40; Payload is 64Byte long 454 | 455 | writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_BASE_AD)); // set 0x0D to 0x0F 456 | 457 | // Low Noise Amplifier used in receiver 458 | writeRegister(REG_LNA, LNA_MAX_GAIN); // 0x0C, 0x23 459 | 460 | // 9. clear all radio IRQ flags 461 | writeRegister(REG_IRQ_FLAGS, 0xFF); 462 | 463 | // Accept no interrupts except RXDONE 464 | writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) ~(IRQ_LORA_RXDONE_MASK | IRQ_LORA_RXTOUT_MASK)); 465 | 466 | // set frequency hopping 467 | if (_hop) { 468 | if (debug >= 3) { Serial.print(F("rxLoraModem:: Hop, channel=")); Serial.println(ifreq); } 469 | writeRegister(REG_HOP_PERIOD,0x01); // 0x24, 0x01 was 0xFF 470 | // Set RXDONE interrupt to dio0 471 | writeRegister(REG_DIO_MAPPING_1, (MAP_DIO0_LORA_RXDONE | MAP_DIO1_LORA_RXTOUT | MAP_DIO1_LORA_FCC)); 472 | } 473 | else { 474 | writeRegister(REG_HOP_PERIOD,0x00); // 0x24, 0x00 was 0xFF 475 | // Set RXDONE interrupt to dio0 476 | writeRegister(REG_DIO_MAPPING_1, (MAP_DIO0_LORA_RXDONE | MAP_DIO1_LORA_RXTOUT)); 477 | } 478 | 479 | if (_cad) { 480 | _state= S_RX; 481 | // Set Single Receive Mode, goes in STANDBY mode after receipt 482 | opmode(OPMODE_RX_SINGLE); // 0x80 | 0x06 (listen one message) 483 | } 484 | else { 485 | _state= S_RX; 486 | // Set Continous Receive Mode 487 | opmode(OPMODE_RX); // 0x80 | 0x05 (listen) 488 | } 489 | 490 | return; 491 | } 492 | 493 | 494 | // ---------------------------------------------------------------------------- 495 | // First time initialisation of the LoRa modem 496 | // Subsequent changes to the modem state etc. done by txLoraModem or rxLoraModem 497 | // After initialisation the modem is put in rx mode (listen) 498 | // ---------------------------------------------------------------------------- 499 | #if REENTRANT==2 500 | void ICACHE_RAM_ATTR initLoraModem() 501 | #else 502 | static void initLoraModem() 503 | #endif 504 | { 505 | _state = S_INIT; 506 | // Reset the transceiver chip with a pulse of 10 mSec 507 | digitalWrite(pins.rst, HIGH); 508 | delayMicroseconds(10000); 509 | digitalWrite(pins.rst, LOW); 510 | delayMicroseconds(10000); 511 | 512 | // 1 Set LoRa Mode 513 | opmodeLora(); // set register 0x01 to 0x80 514 | 515 | // 2. Set radio to sleep 516 | opmode(OPMODE_SLEEP); // set register 0x01 to 0x00 517 | 518 | // 3. Set frequency based on value in freq 519 | setFreq(freq); // set to 868.1MHz 520 | 521 | // 4. Set spreading Factor 522 | setRate(sf, 0x04); 523 | 524 | // Low Noise Amplifier used in receiver 525 | writeRegister(REG_LNA, LNA_MAX_GAIN); // 0x0C, 0x23 526 | 527 | uint8_t version = readRegister(REG_VERSION); // Read the LoRa chip version id 528 | if (version == 0x22) { 529 | // sx1272 530 | Serial.println(F("WARNING:: SX1272 detected")); 531 | sx1272 = true; 532 | } else { 533 | // sx1276? 534 | digitalWrite(pins.rst, LOW); 535 | delayMicroseconds(10000); 536 | digitalWrite(pins.rst, HIGH); 537 | delayMicroseconds(10000); 538 | 539 | version = readRegister(REG_VERSION); 540 | if (version == 0x12) { 541 | // sx1276 542 | if (debug >=1) Serial.println(F("SX1276 detected, starting.")); 543 | sx1272 = false; 544 | } else { 545 | Serial.print(F("Unrecognized transceiver, version: ")); 546 | Serial.println(version,HEX); 547 | die(""); 548 | } 549 | } 550 | // 7. set sync word 551 | writeRegister(REG_SYNC_WORD, 0x34); // set 0x39 to 0x34 LORA_MAC_PREAMBLE 552 | 553 | // prevent node to node communication 554 | writeRegister(REG_INVERTIQ,0x27); // 0x33, 0x27; to reset from TX 555 | 556 | // Max Payload length is dependent on 256 byte buffer. At startup TX starts at 557 | // 0x80 and RX at 0x00. RX therefore maximized at 128 Bytes 558 | writeRegister(REG_MAX_PAYLOAD_LENGTH,0x80); // set 0x23 to 0x80 559 | writeRegister(REG_PAYLOAD_LENGTH,PAYLOAD_LENGTH); // 0x22, 0x40; Payload is 64Byte long 560 | 561 | writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_BASE_AD)); // set reg 0x0D to 0x0F 562 | 563 | if (_hop) { 564 | writeRegister(REG_HOP_PERIOD,0x00); // reg 0x24, set to 0x00 was 0xFF 565 | writeRegister(REG_HOP_CHANNEL,0x00); // reg 0x1C, set to 0x00 566 | } 567 | else { 568 | writeRegister(REG_HOP_PERIOD,0x00); // reg 0x24, set to 0x00 was 0xFF 569 | } 570 | 571 | // 5. Config PA Ramp up time // set reg 0x0A 572 | writeRegister(REG_PARAMP, (readRegister(REG_PARAMP) & 0xF0) | 0x08); // set PA ramp-up time 50 uSec 573 | 574 | // Set 0x4D PADAC for SX1276 ; XXX register is 0x5a for sx1272 575 | writeRegister(REG_PADAC_SX1276, 0x84); // set 0x4D (PADAC) to 0x84 576 | //writeRegister(REG_PADAC, readRegister(REG_PADAC)|0x4); 577 | 578 | // 9. clear all radio IRQ flags 579 | writeRegister(REG_IRQ_FLAGS, 0xFF); 580 | } 581 | 582 | 583 | // ---------------------------------------------------------------------------- 584 | // Send DOWN a LoRa packet over the air to the node. This function does all the 585 | // decoding of the server message and prepares a Payload buffer. 586 | // The payload is actually transmitted by the sendPkt() function. 587 | // This function is used for regular downstream messages and for JOIN_ACCEPT 588 | // messages. 589 | // NOTE: This is not an interrupt function, but is started by loop(). 590 | // ---------------------------------------------------------------------------- 591 | int sendPacket(uint8_t *buff_down, uint8_t length) 592 | { 593 | // Received package with Meta Data: 594 | // codr : "4/5" 595 | // data : "Kuc5CSwJ7/a5JgPHrP29X9K6kf/Vs5kU6g==" // for example 596 | // freq : 868.1 // 868100000 597 | // ipol : true/false 598 | // modu : "LORA" 599 | // powe : 14 // Set by default 600 | // rfch : 0 // Set by default 601 | // size : 21 602 | // tmst : 1800642 // for example 603 | // datr : "SF7BW125" 604 | 605 | // 12-byte header; 606 | // HDR (1 byte) 607 | // 608 | // 609 | // Data Reply for JOIN_ACCEPT as sent by server: 610 | // AppNonce (3 byte) 611 | // NetID (3 byte) 612 | // DevAddr (4 byte) [ 31..25]:NwkID , [24..0]:NwkAddr 613 | // DLSettings (1 byte) 614 | // RxDelay (1 byte) 615 | // CFList (fill to 16 bytes) 616 | 617 | 618 | 619 | int i=0; 620 | StaticJsonBuffer<256> jsonBuffer; 621 | char * bufPtr = (char *) (buff_down); 622 | buff_down[length] = 0; 623 | 624 | if (debug >= 2) Serial.println((char *)buff_down); 625 | 626 | // Use JSON to decode the string after the first 4 bytes. 627 | // The data for the node is in the "data" field. This function destroys original buffer 628 | JsonObject& root = jsonBuffer.parseObject(bufPtr); 629 | 630 | if (!root.success()) { 631 | Serial.print (F("sendPacket:: ERROR Json Decode")); 632 | if (debug>=2) { 633 | Serial.print(':'); 634 | Serial.println(bufPtr); 635 | } 636 | return(-1); 637 | } 638 | delay(1); 639 | // Meta Data sent by server (example) 640 | // {"txpk":{"codr":"4/5","data":"YCkEAgIABQABGmIwYX/kSn4Y","freq":868.1,"ipol":true,"modu":"LORA","powe":14,"rfch":0,"size":18,"tmst":1890991792,"datr":"SF7BW125"}} 641 | 642 | // Used in the protocol: 643 | const char * data = root["txpk"]["data"]; 644 | uint8_t psize = root["txpk"]["size"]; 645 | bool ipol = root["txpk"]["ipol"]; 646 | uint8_t powe = root["txpk"]["powe"]; 647 | uint32_t tmst = (uint32_t) root["txpk"]["tmst"].as(); 648 | 649 | // Not used in the protocol: 650 | const char * datr = root["txpk"]["datr"]; // eg "SF7BW125" 651 | const float ff = root["txpk"]["freq"]; // eg 869.525 652 | const char * modu = root["txpk"]["modu"]; // =="LORA" 653 | const char * codr = root["txpk"]["codr"]; 654 | //if (root["txpk"].containsKey("imme") ) { 655 | // const bool imme = root["txpk"]["imme"]; // Immediate Transmit (tmst don't care) 656 | //} 657 | 658 | 659 | if (data != NULL) { 660 | if (debug>=2) { Serial.print(F("data: ")); Serial.println((char *) data); } 661 | } 662 | else { 663 | Serial.println(F("sendPacket:: ERROR: data is NULL")); 664 | return(-1); 665 | } 666 | 667 | uint8_t iiq = (ipol? 0x40: 0x27); // if ipol==true 0x40 else 0x27 668 | uint8_t crc = 0x00; // switch CRC off for TX 669 | uint8_t payLength = base64_dec_len((char *) data, strlen(data)); 670 | uint8_t payLoad[payLength]; // Declare buffer 671 | base64_decode((char *) payLoad, (char *) data, strlen(data)); 672 | 673 | // Compute wait time in microseconds 674 | uint32_t w = (uint32_t) (tmst - micros()); 675 | 676 | #if _STRICT_1CH == 1 677 | // Use RX1 timeslot as this is our frequency. 678 | // Do not use RX2 or JOIN2 as they contain other frequencies 679 | if ((w>1000000) && (w<3000000)) { tmst-=1000000; } 680 | else if ((w>6000000) && (w<7000000)) { tmst-=1000000; } 681 | 682 | const uint8_t sfTx = sfi; // Take care, TX sf not to be mixed with SCAN 683 | const uint32_t fff = freq; 684 | _state = S_TX; // _state set to transmit 685 | txLoraModem(payLoad, payLength, tmst, sfTx, powe, fff, crc, iiq); 686 | #else 687 | const uint8_t sfTx = atoi(datr+2); // Convert "SF9BW125" to 9 688 | // convert double frequency (MHz) into uint32_t frequency in Hz. 689 | const uint32_t fff = (uint32_t) ((uint32_t)((ff+0.000035)*1000)) * 1000; 690 | _state = S_TX; // _state set to transmit 691 | txLoraModem(payLoad, payLength, tmst, sfTx, powe, fff, crc, iiq); 692 | #endif 693 | 694 | // After transmitting make sure we reset the interrupt flags and 695 | // we set the _state of the program back to receiving/scanning 696 | // 697 | writeRegister(REG_IRQ_FLAGS, 0xFF ); // reset interrupt flags 698 | 699 | _state=S_RX; 700 | rxLoraModem(); 701 | 702 | if (_cad) { 703 | // Set the state to CAD scanning 704 | _state = S_SCAN; 705 | cadScanner(); // Start the scanner after TX cycle 706 | } 707 | #if DEBUG>=2 708 | if (debug>=2) { 709 | Serial.print(F("Request:: ")); 710 | Serial.print(F(" tmst=")); Serial.print(tmst); Serial.print(F(" wait=")); Serial.println(w); 711 | 712 | Serial.print(F(" strict=")); Serial.print(_STRICT_1CH); 713 | Serial.print(F(" datr=")); Serial.println(datr); 714 | Serial.print(F(" freq=")); Serial.print(freq); Serial.print(F(" ->")); Serial.println(fff); 715 | Serial.print(F(" sf =")); Serial.print(sf); Serial.print(F(" ->")); Serial.print(sfTx); 716 | 717 | Serial.print(F(" modu=")); Serial.print(modu); 718 | Serial.print(F(" powe=")); Serial.print(powe); 719 | Serial.print(F(" codr=")); Serial.println(codr); 720 | 721 | Serial.print(F(" ipol=")); Serial.println(ipol); 722 | Serial.println(); // empty line between messages 723 | } 724 | #endif 725 | 726 | if (payLength != psize) { 727 | Serial.print(F("sendPacket:: WARNING payLength: ")); 728 | Serial.print(payLength); 729 | Serial.print(F(", psize=")); 730 | Serial.println(psize); 731 | } 732 | else if (debug >= 2) { 733 | for (i=0; i> 2; 783 | SNR = -value; 784 | } 785 | else { 786 | // Divide by 4 787 | SNR = ( value & 0xFF ) >> 2; 788 | } 789 | 790 | prssi = readRegister(REG_PKT_RSSI); // read register 0x1A, packet rssi 791 | 792 | // Correction of RSSI value based on chip used. 793 | if (sx1272) { // Is it a sx1272 radio? 794 | rssicorr = 139; 795 | } else { // Probably SX1276 or RFM95 796 | rssicorr = 157; 797 | } 798 | } 799 | 800 | #if STATISTICS >= 1 801 | // Receive statistics 802 | for (int m=( MAX_STAT -1); m>0; m--) statr[m]=statr[m-1]; 803 | statr[0].tmst = millis(); 804 | statr[0].ch= ifreq; 805 | statr[0].prssi = prssi - rssicorr; 806 | statr[0].rssi = _rssi - rssicorr; 807 | statr[0].sf = readRegister(REG_MODEM_CONFIG2) >> 4; 808 | statr[0].node = ( message[1]<<24 | message[2]<<16 | message[3]<<8 | message[4] ); 809 | 810 | #if STATISTICS >= 2 811 | switch (statr[0].sf) { 812 | case SF7: statc.sf7++; break; 813 | case SF8: statc.sf8++; break; 814 | case SF9: statc.sf9++; break; 815 | case SF10: statc.sf10++; break; 816 | case SF11: statc.sf11++; break; 817 | case SF12: statc.sf12++; break; 818 | } 819 | #endif 820 | #endif 821 | 822 | #if DEBUG>=1 823 | if (debug>=1) { 824 | Serial.print(F("Packet RSSI: ")); 825 | Serial.print(prssi-rssicorr); 826 | Serial.print(F(" RSSI: ")); 827 | Serial.print(_rssi - rssicorr); 828 | Serial.print(F(" SNR: ")); 829 | Serial.print(SNR); 830 | Serial.print(F(" Length: ")); 831 | Serial.print((int)messageLength); 832 | Serial.print(F(" -> ")); 833 | int i; 834 | for (i=0; i< messageLength; i++) { 835 | Serial.print(message[i],HEX); 836 | Serial.print(' '); 837 | } 838 | Serial.println(); 839 | yield(); 840 | } 841 | #endif 842 | 843 | // Show received message status on OLED display 844 | #if OLED==1 845 | display.clear(); 846 | display.setFont(ArialMT_Plain_16); 847 | display.setTextAlignment(TEXT_ALIGN_LEFT); 848 | char timBuff[20]; 849 | sprintf(timBuff, "%02i:%02i:%02i", hour(), minute(), second()); 850 | display.drawString(0, 0, "Time: " ); 851 | display.drawString(40, 0, timBuff); 852 | display.drawString(0, 16, "RSSI: " ); 853 | display.drawString(40, 16, String(prssi-rssicorr)); 854 | display.drawString(0, 32, "SNR: " ); 855 | display.drawString(40, 32, String(SNR) ); 856 | display.drawString(0, 48, "LEN: " ); 857 | display.drawString(40, 48, String((int)messageLength) ); 858 | display.display(); 859 | #endif 860 | 861 | int j; 862 | // XXX Base64 library is nopad. So we may have to add padding characters until 863 | // length is multiple of 4! 864 | int encodedLen = base64_enc_len(messageLength); // max 341 865 | base64_encode(b64, (char *) message, messageLength);// max 341 866 | 867 | // pre-fill the data buffer with fixed fields 868 | buff_up[0] = PROTOCOL_VERSION; // 0x01 still 869 | buff_up[3] = PKT_PUSH_DATA; // 0x00 870 | 871 | // READ MAC ADDRESS OF ESP8266, and insert 0xFF 0xFF in the middle 872 | buff_up[4] = MAC_array[0]; 873 | buff_up[5] = MAC_array[1]; 874 | buff_up[6] = MAC_array[2]; 875 | buff_up[7] = 0xFF; 876 | buff_up[8] = 0xFF; 877 | buff_up[9] = MAC_array[3]; 878 | buff_up[10] = MAC_array[4]; 879 | buff_up[11] = MAC_array[5]; 880 | 881 | // start composing datagram with the header 882 | uint8_t token_h = (uint8_t)rand(); // random token 883 | uint8_t token_l = (uint8_t)rand(); // random token 884 | buff_up[1] = token_h; 885 | buff_up[2] = token_l; 886 | buff_index = 12; // 12-byte binary (!) header 887 | 888 | // start of JSON structure that will make payload 889 | memcpy((void *)(buff_up + buff_index), (void *)"{\"rxpk\":[", 9); 890 | buff_index += 9; 891 | buff_up[buff_index] = '{'; 892 | ++buff_index; 893 | j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, "\"tmst\":%u", tmst); 894 | buff_index += j; 895 | ftoa((double)freq/1000000,cfreq,6); // XXX This can be done better 896 | j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"chan\":%1u,\"rfch\":%1u,\"freq\":%s", 0, 0, cfreq); 897 | buff_index += j; 898 | memcpy((void *)(buff_up + buff_index), (void *)",\"stat\":1", 9); 899 | buff_index += 9; 900 | memcpy((void *)(buff_up + buff_index), (void *)",\"modu\":\"LORA\"", 14); 901 | buff_index += 14; 902 | /* Lora datarate & bandwidth, 16-19 useful chars */ 903 | switch (sf) { 904 | case SF6: 905 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF6", 12); 906 | buff_index += 12; 907 | break; 908 | case SF7: 909 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF7", 12); 910 | buff_index += 12; 911 | break; 912 | case SF8: 913 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF8", 12); 914 | buff_index += 12; 915 | break; 916 | case SF9: 917 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF9", 12); 918 | buff_index += 12; 919 | break; 920 | case SF10: 921 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF10", 13); 922 | buff_index += 13; 923 | break; 924 | case SF11: 925 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF11", 13); 926 | buff_index += 13; 927 | break; 928 | case SF12: 929 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF12", 13); 930 | buff_index += 13; 931 | break; 932 | default: 933 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF?", 12); 934 | buff_index += 12; 935 | } 936 | memcpy((void *)(buff_up + buff_index), (void *)"BW125\"", 6); 937 | buff_index += 6; 938 | memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"4/5\"", 13); 939 | buff_index += 13; 940 | j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"lsnr\":%li", SNR); 941 | buff_index += j; 942 | j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"rssi\":%d,\"size\":%u", prssi-rssicorr, messageLength); 943 | buff_index += j; 944 | memcpy((void *)(buff_up + buff_index), (void *)",\"data\":\"", 9); 945 | buff_index += 9; 946 | 947 | // Use gBase64 library to fill in the data string 948 | encodedLen = base64_enc_len(messageLength); // max 341 949 | j = base64_encode((char *)(buff_up + buff_index), (char *) message, messageLength); 950 | 951 | buff_index += j; 952 | buff_up[buff_index] = '"'; 953 | ++buff_index; 954 | 955 | // End of packet serialization 956 | buff_up[buff_index] = '}'; 957 | ++buff_index; 958 | buff_up[buff_index] = ']'; 959 | ++buff_index; 960 | 961 | // end of JSON datagram payload */ 962 | buff_up[buff_index] = '}'; 963 | ++buff_index; 964 | buff_up[buff_index] = 0; // add string terminator, for safety 965 | 966 | if (debug>=1) { 967 | Serial.print(F("RXPK:: ")); 968 | Serial.println((char *)(buff_up + 12)); // DEBUG: display JSON payload 969 | } 970 | if (debug>= 2) { 971 | Serial.print(F("RXPK:: package length=")); 972 | Serial.println(buff_index); 973 | } 974 | return(buff_index); 975 | } 976 | 977 | 978 | // ---------------------------------------------------------------------------- 979 | // This LoRa function reads a message from the LoRa transceiver 980 | // returns message length read when message correctly received or 981 | // it returns a negative value on error (CRC error for example). 982 | // UP function 983 | // This is the "lowlevel" receive function called by receivePacket() 984 | // dealing with the radio specific LoRa functions 985 | // ---------------------------------------------------------------------------- 986 | #if REENTRANT==2 987 | uint8_t ICACHE_RAM_ATTR receivePkt(uint8_t *payload) 988 | #else 989 | uint8_t receivePkt(uint8_t *payload) 990 | #endif 991 | { 992 | writeRegister(REG_IRQ_FLAGS, 0x40); // 0x12; Clear RxDone IRQ_LORA_RXDONE_MASK 993 | 994 | int irqflags = readRegister(REG_IRQ_FLAGS); // 0x12 995 | 996 | cp_nb_rx_rcv++; // Receive statistics counter 997 | 998 | // payload crc=0x20 set 999 | if((irqflags & 0x20) == 0x20) 1000 | { 1001 | Serial.println(F("CRC error")); 1002 | // Reset CRC flag 0x20 1003 | writeRegister(REG_IRQ_FLAGS, 0x20); // clear CRC 0x12 to 0x20 1004 | return -1; 1005 | } else { 1006 | 1007 | cp_nb_rx_ok++; // Receive OK statistics counter 1008 | 1009 | uint8_t currentAddr = readRegister(REG_FIFO_RX_CURRENT_ADDR); // 0x10 1010 | uint8_t receivedCount = readRegister(REG_RX_NB_BYTES); // 0x13; How many bytes were read 1011 | 1012 | //writeRegister(REG_FIFO_ADDR_PTR, currentAddr); // 0x0D XXX??? This sets the FiFo higher!!! 1013 | 1014 | for(int i = 0; i < receivedCount; i++) 1015 | { 1016 | payload[i] = readRegister(REG_FIFO); // 0x00 1017 | } 1018 | return(receivedCount); 1019 | } 1020 | return 0; 1021 | } 1022 | 1023 | 1024 | // ---------------------------------------------------------------------------- 1025 | // Receive a LoRa package over the air 1026 | // 1027 | // Receive a LoRa message and fill the buff_up char buffer. 1028 | // returns values: 1029 | // - returns the length of string returned in buff_up 1030 | // - returns -1 when no message arrived. 1031 | // 1032 | // This is the "highlevel" function called by loop() 1033 | // _state is S_RX when starting and 1034 | // _state is S_STANDBY when leaving function 1035 | // ---------------------------------------------------------------------------- 1036 | #if REENTRANT==2 1037 | int ICACHE_RAM_ATTR receivePacket() 1038 | #else 1039 | int receivePacket() 1040 | #endif 1041 | { 1042 | uint8_t buff_up[TX_BUFF_SIZE]; // buffer to compose the upstream packet to backend server 1043 | long SNR; 1044 | uint8_t message[128] = { 0 }; 1045 | uint8_t messageLength = 0; 1046 | 1047 | // Regular message received, see SX1276 spec table 18 1048 | // Next statement could also be a "while" to combine several messages received 1049 | // in one UDP message as the Semtech Gateway spec does allow this. 1050 | // XXX Not yet supported 1051 | 1052 | // Take the timestamp as soon as possible, to have accurate reception timestamp 1053 | // TODO: tmst can jump if micros() overflow. 1054 | uint32_t tmst = (uint32_t) micros(); // Only microseconds, rollover in 1055 | lastTmst = tmst; // Following/according to spec 1056 | 1057 | // Handle the physical data read from FiFo 1058 | if((messageLength = receivePkt(message)) > 0){ 1059 | 1060 | // external received packet, so last parameter is false 1061 | int buff_index = buildPacket(tmst, buff_up, message, messageLength, false); 1062 | 1063 | // debugging AFTER reading the message 1064 | if (debug >= 2) { 1065 | Serial.print(F("receivePacket:: ")); 1066 | printState(2); 1067 | } 1068 | //yield(); // Maybe we can NOT do this one 1069 | // rxpk PUSH_DATA received from node is rxpk (*2, par. 3.2) 1070 | sendUdp(buff_up, buff_index); // send to 1 or 2 sockets 1071 | 1072 | return(buff_index); // received a message 1073 | } 1074 | return(-1); // failure no message read 1075 | } 1076 | 1077 | 1078 | // ---------------------------------------------------------------------------- 1079 | // cadScanner() 1080 | // 1081 | // CAD Scanner will scan on the given channel for a valid Symbol/Preamble signal. 1082 | // So instead of receiving continuous on a given channel/sf combination 1083 | // we will wait on the given channel and scan for a preamble. Once received 1084 | // we will set the radio to the SF with best rssi (indicating reception on that sf). 1085 | // cadScanner() sets the _state to S_SCAN 1086 | // NOTE: DO not set the frequency here but use the frequency hopper 1087 | // ---------------------------------------------------------------------------- 1088 | #if REENTRANT==2 1089 | void ICACHE_RAM_ATTR cadScanner() 1090 | #else 1091 | void cadScanner() 1092 | #endif 1093 | { 1094 | 1095 | // 1. Put system in LoRa mode 1096 | opmodeLora(); 1097 | 1098 | // 2. Put the radio in sleep mode 1099 | //opmode(OPMODE_STANDBY); 1100 | opmode(OPMODE_SLEEP); // set 0x01 to 0x00 1101 | 1102 | // As we can come back from S_TX with other frequencies and SF 1103 | // reset both to good values for cadScanner 1104 | 1105 | // 3. Set frequency based on value in freq // XXX New, might be needed when receiving down 1106 | setFreq(freqs[ifreq]); 1107 | 1108 | // For every time we stat the scanner, we set the SF to the begin value 1109 | sf = SF7; // XXX MMM So we make SF one lower! 1110 | 1111 | // 4. Set spreading Factor and CRC 1112 | setRate(sf, 0x04); 1113 | 1114 | // listen to LORA_MAC_PREAMBLE 1115 | writeRegister(REG_SYNC_WORD, 0x34); // set reg 0x39 to 0x34 1116 | 1117 | // Clear all relevant interrupts 1118 | writeRegister(REG_IRQ_FLAGS, 0xFF ); // May work better, clear ALL interrupts 1119 | 1120 | // Set the interrupts we want top listen to 1121 | writeRegister(REG_DIO_MAPPING_1, 1122 | (MAP_DIO0_LORA_CADDONE | MAP_DIO1_LORA_CADDETECT | MAP_DIO2_LORA_NOP | MAP_DIO3_LORA_NOP)); 1123 | 1124 | // Set the mask for interrupts (we do not want to listen to) except for 1125 | writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) ~(IRQ_LORA_CDDONE_MASK | IRQ_LORA_CDDETD_MASK)); 1126 | 1127 | // Set the opMode to CAD 1128 | opmode(OPMODE_CAD); 1129 | 1130 | // If we are here. we either might have set the SF or we have a timeout in which 1131 | // case the receive is started just as normal. 1132 | return; 1133 | } 1134 | 1135 | // ---------------------------------------------------------------------------- 1136 | // eventHandler is the loop() function that handles the incoming events in the 1137 | // main loop() such as packet reading etc. 1138 | // This function is there to make sure we do not spend too much time in 1139 | // interrupt mode but do reading etc in loop(). 1140 | // So it is not activated by interrupts directly, but interrupts set _state so 1141 | // that the buffer can be read in the main loop. 1142 | // ---------------------------------------------------------------------------- 1143 | 1144 | void eventHandler() 1145 | { 1146 | int buff_index=0; 1147 | 1148 | // The type of interrupt is dependent on the state we are in 1149 | // at the moment of receiving. 1150 | switch (_state) { 1151 | 1152 | // state tells us that the system was receiving a LoRa message 1153 | // and that all data is now in the FIFO 1154 | case S_RXDONE: 1155 | if (debug >=4) { Serial.print(F("eH:: receive on SF=")); Serial.println(sf); } 1156 | 1157 | if ((buff_index = receivePacket() ) < 0) { // read is successful 1158 | 1159 | Serial.print(F("eH:: WARNING S_RX empty message, sf=")); 1160 | Serial.println(sf); 1161 | } 1162 | // Reset all RX lora stuff 1163 | _state = S_RX; 1164 | rxLoraModem(); 1165 | 1166 | // If we now switch to S_SCAN, we have to hop too 1167 | if (_hop) { hop(); } 1168 | 1169 | if (_cad) { 1170 | // Set the state to CAD scanning after receiving 1171 | _state = S_SCAN; // Inititialise scanner 1172 | cadScanner(); 1173 | } 1174 | 1175 | break; 1176 | 1177 | default: 1178 | if (debug >= 2) { 1179 | Serial.print(F("eH:: Unrecognized state, ")); 1180 | printState(2); 1181 | return; 1182 | } 1183 | break; 1184 | 1185 | }//switch 1186 | } 1187 | 1188 | // ---------------------------------------------------------------------------- 1189 | // Interrupt handler for DIO0 having High Value 1190 | // Handler for: 1191 | // - RxDone 1192 | // - TxDone 1193 | // - CadDone 1194 | // This interrupt routine has been kept as simple and short as possible. 1195 | // Thsi means that the handler will ONLY update the _state variable for 1196 | // reception (and do nothing for RXDONE events). 1197 | // Assuming tha the next sensible event after RXDONE is receive characters 1198 | // anyway. 1199 | // NOTE: We may clear the interrupt but leave the flag for the moment. 1200 | // The eventHandler // should take care of repairing flags between interrupts. 1201 | // ---------------------------------------------------------------------------- 1202 | #if REENTRANT==2 1203 | void ICACHE_RAM_ATTR Interrupt_0() 1204 | #else 1205 | void Interrupt_0() 1206 | #endif 1207 | { 1208 | // Determine what interrupt flags are set 1209 | flags = readRegister(REG_IRQ_FLAGS); 1210 | mask = readRegister(REG_IRQ_FLAGS_MASK); 1211 | uint8_t intr = flags & ( ~ mask ); // Only react on non masked interrupts 1212 | uint8_t rssi; 1213 | 1214 | if (intr == 0x00) { 1215 | if (debug>=4) Serial.println(F("DIO0:: NO intr")); 1216 | return; 1217 | } 1218 | 1219 | // Small state machine inside the interrupt handler 1220 | // as next actions are depending on the state we are in. 1221 | switch (_state) { 1222 | 1223 | // If the state is init, we are starting up. 1224 | // The initLoraModem() function is already called ini setup(); 1225 | case S_INIT: 1226 | if (debug >= 1) { Serial.println(F("DIO:: S_INIT")); } 1227 | // new state, needed to startup the radio (to S_SCAN) 1228 | break; 1229 | 1230 | // In S_SCAN we measure a high RSSI this means that there (probably) is a message 1231 | // coming in at that freq. But not necessarily on the current SF. 1232 | // So find the right SF with CDDECTD. 1233 | case S_SCAN: 1234 | // Clear the CADDONE flag 1235 | writeRegister(REG_IRQ_FLAGS, IRQ_LORA_CDDONE_MASK); 1236 | 1237 | opmode(OPMODE_CAD); 1238 | 1239 | rssi = readRegister(REG_RSSI); // Read the RSSI 1240 | if ( rssi > RSSI_LIMIT ) { 1241 | if (debug >= 4) { 1242 | Serial.print(F("DIO0:: CADDONE: ")); 1243 | printState(4); 1244 | } 1245 | _state = S_CAD; // XXX invoke the interrupt handler again? 1246 | } 1247 | 1248 | // If we do not switch to S_SCAN, we have to hop 1249 | // Instead of waiting for an interrupt we do this on timer bais (more regular). 1250 | else if (_hop) { hop(); } 1251 | 1252 | // else keep on scanning 1253 | break; 1254 | 1255 | // S_CAD: In CAD mode we scan every SF for high RSSI until we have a DETECT 1256 | // DIO0 interrupt IRQ_LORA_CDDONE_MASK in state S_CAD==2 means that we might have 1257 | // a lock on the Freq but not the right SF. So we increase the SF 1258 | case S_CAD: 1259 | if ((uint8_t)sf < 12) { 1260 | sf = (sf_t)((uint8_t)sf + 1); // XXX This would mean SF7 never used 1261 | setRate(sf, 0x04); // Set SF with CRC==on 1262 | 1263 | // reset interrupt flags for CAD Done 1264 | writeRegister(REG_IRQ_FLAGS, IRQ_LORA_CDDONE_MASK ); 1265 | opmode(OPMODE_CAD); 1266 | 1267 | delayMicroseconds( RSSI_WAIT ); 1268 | rssi = readRegister(REG_RSSI); // Read the RSSI 1269 | } 1270 | // if we are at SF12 level Restart the scanning process 1271 | else { 1272 | if (debug>=4) { Serial.println(F("DIO0:: SF Reset, restart scanner")); } 1273 | if (_hop) { hop(); } // XXX 1274 | _state = S_SCAN; 1275 | cadScanner(); 1276 | } 1277 | break; 1278 | 1279 | // If we receive an interrupt on dio0 state==S_RX 1280 | // it should be a RxDone interrupt 1281 | // So we should handle the received message 1282 | case S_RX: 1283 | if (intr & IRQ_LORA_RXDONE_MASK) { 1284 | _state=S_RXDONE; // Will be picked up by eventHandler() in loop() 1285 | writeRegister(REG_IRQ_FLAGS, 0xFF); // reset all interrupts (have to wait until received); 1286 | if (debug>=3) { 1287 | Serial.println(F("DIO0:: RXDONE, ")); 1288 | printState(3); 1289 | } 1290 | } 1291 | else if (debug >=3) { 1292 | Serial.print(F("DIO0:: S_RX but no RXDONE, ")); 1293 | printState(3); 1294 | } 1295 | break; 1296 | 1297 | // If we receive an interrupt on dio0 state==S_TX==5 it is a TxDone interrupt 1298 | // Do nothing with the interrupt, it is just an indication 1299 | // Switch back to scanner mode after transmission finished OK 1300 | case S_TX: 1301 | if (debug>=3) { Serial.print(F("DIO0:: S_TX, ")); printState(3); } 1302 | 1303 | _state=S_RX; 1304 | rxLoraModem(); 1305 | 1306 | writeRegister(REG_IRQ_FLAGS, 0xFF ); // reset interrupt flags 1307 | if (_cad) { 1308 | // Set the state to CAD scanning 1309 | _state = S_SCAN; 1310 | cadScanner(); // Start the scanner after TX cycle 1311 | } 1312 | break; 1313 | 1314 | default: 1315 | if (debug >= 2) { Serial.print("DIO0:: Unexpected state="); Serial.println(_state); } 1316 | break; 1317 | } 1318 | } 1319 | 1320 | // ---------------------------------------------------------------------------- 1321 | // Interrupt handler for DIO1 having High Value 1322 | // As DIO0 and DIO1 may be multiplexed on one GPIO interrupt handler 1323 | // (as we do) we have to be careful only to call the right Interrupt_x 1324 | // handler and clear the corresponding interrupts for that dio. 1325 | // NOTE: Make sure all Serial communication is only for debug level 3 and up. 1326 | // Handler for: 1327 | // - CDDETD 1328 | // - RXTIMEOUT 1329 | // - (RXDONE error only) 1330 | // ---------------------------------------------------------------------------- 1331 | #if REENTRANT==2 1332 | void ICACHE_RAM_ATTR Interrupt_1() 1333 | #else 1334 | void Interrupt_1() 1335 | #endif 1336 | { 1337 | flags = readRegister(REG_IRQ_FLAGS); 1338 | mask = readRegister(REG_IRQ_FLAGS_MASK); 1339 | uint8_t intr = flags & ( ~ mask ); // Only react on non masked interrupts 1340 | uint8_t rssi; 1341 | 1342 | if (intr == 0x00) { 1343 | if (debug>=4) Serial.println(F("DIO1:: NO intr")); 1344 | return; 1345 | } 1346 | 1347 | switch(_state) { 1348 | // If we receive an interrupt on dio1 and _state==S_CAD or S_SCAN 1349 | case S_CAD: 1350 | case S_SCAN: 1351 | 1352 | // Intr=IRQ_LORA_CDDETD_MASK 1353 | // We have to set the sf based on a strong RSSI for this channel 1354 | // So we scan this SF and if not high enough ... next 1355 | // 1356 | if (intr & IRQ_LORA_CDDETD_MASK) { 1357 | 1358 | if (debug >=3) { 1359 | Serial.print(F("DIO1:: CADDETD, ")); 1360 | printState(3); 1361 | } 1362 | 1363 | _state = S_RX; // Set state to receiving 1364 | opmode(OPMODE_RX_SINGLE); // set reg 0x01 to 0x06 1365 | 1366 | // Set RXDONE interrupt to dio0, RXTOUT to dio1 1367 | writeRegister(REG_DIO_MAPPING_1, (MAP_DIO0_LORA_RXDONE | MAP_DIO1_LORA_RXTOUT)); 1368 | 1369 | // Accept no interrupts except RXDONE or RXTOUT 1370 | writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) ~(IRQ_LORA_RXDONE_MASK | IRQ_LORA_RXTOUT_MASK)); 1371 | 1372 | //writeRegister(REG_IRQ_FLAGS, IRQ_LORA_CDDETD_MASK ); 1373 | writeRegister(REG_IRQ_FLAGS, 0xFF ); // reset CAD Detect interrupt flags 1374 | 1375 | delayMicroseconds( RSSI_WAIT_DOWN ); // Wait some microseconds less 1376 | rssi = readRegister(REG_RSSI); // Read the RSSI 1377 | _rssi = rssi; // Read the RSSI in the state variable 1378 | 1379 | //If we have low rssi value, go scan again 1380 | // if (rssi < ( RSSI_LIMIT_DOWN )) { // XXX RSSI might drop a little 1381 | // 1382 | // if (debug >= 3) { 1383 | // Serial.print(F("DIO1:: reset S_SCAN, rssi=")); Serial.print(rssi); Serial.print(F(", ")); 1384 | // printState(3); 1385 | // } 1386 | // if (_hop) { hop(); } // XXX 1387 | // _state = S_SCAN; 1388 | // cadScanner(); 1389 | // } 1390 | // // else we are going to read and get RXDONE of RXTOUT 1391 | // else if (debug >= 3 ) { 1392 | // Serial.print(F("DIO1:: Start S_RX, ")); 1393 | // printState(3); 1394 | // } 1395 | }//if 1396 | else { 1397 | if (debug >=3) { 1398 | Serial.print(F("DIO1:: ERROR Wrong flag ,")); 1399 | printState(3); 1400 | } 1401 | } 1402 | break; 1403 | 1404 | // If DIO1 is not a CADDETECT interrupt, it is an RXTIMEOUT interrupt 1405 | // So we restart the scanner 1406 | case S_RX: 1407 | if (intr & IRQ_LORA_RXDONE_MASK) { 1408 | if (debug >= 3) { Serial.print(F("DIO1:: Warning RXDONE received, ")); printState(3); } 1409 | } 1410 | if (intr & IRQ_LORA_RXTOUT_MASK) { 1411 | if (debug >= 3) { 1412 | Serial.println(F("DIO1:: RXTOUT, ")); 1413 | printState(3); 1414 | } 1415 | // reset RX Timeout interrupt flags 1416 | writeRegister(REG_IRQ_FLAGS_MASK, (uint8_t) ~(IRQ_LORA_CDDETD_MASK | IRQ_LORA_RXTOUT_MASK)); 1417 | 1418 | // Set the modem for next receive action. This must be done before 1419 | // scanning takes place as we cannot do it once RXDETTD is set. 1420 | _state=S_RX; 1421 | rxLoraModem(); 1422 | 1423 | if (_hop) { hop(); } 1424 | 1425 | if (_cad) { 1426 | // Set the state to CAD scanning 1427 | _state = S_SCAN; 1428 | cadScanner(); // Start the scanner after RXTOUT 1429 | } 1430 | } 1431 | else if (debug >=3) { 1432 | Serial.println(F("DIO1:: WARNING: _state==S_RX but event not IRQ_LORA_RXTOUT_MASK")); 1433 | } 1434 | break; 1435 | 1436 | default: 1437 | if (debug >= 1) { 1438 | Serial.print(F("DIO1:: ERROR Unrecognized state, ")); 1439 | printState(1); 1440 | } 1441 | break; 1442 | } 1443 | } 1444 | 1445 | // ---------------------------------------------------------------------------- 1446 | // Frequency Hopping Channel (FHSS) dio2 1447 | // ---------------------------------------------------------------------------- 1448 | //void Interrupt_2() 1449 | //{ 1450 | // uint8_t fhss = readRegister(REG_HOP_CHANNEL); 1451 | // Serial.print(F("DIO2:: Calling interrupt")); 1452 | //if (_hop) { // XXX HIGHLY experimental 1453 | // hop(); 1454 | //} 1455 | //} 1456 | 1457 | 1458 | // ---------------------------------------------------------------------------- 1459 | // Interrupt Handler. 1460 | // Both interrupts DIO0 and DIO1 are mapped on GPIO15. Se we have to look at 1461 | // the interrupt flags to see which interrupt(s) are called. 1462 | // 1463 | // NOTE:: This method may work not as good as just using more GPIO pins on 1464 | // the ESP8266 mcu. But in practice it works good enough 1465 | // ---------------------------------------------------------------------------- 1466 | #if REENTRANT==2 1467 | void ICACHE_RAM_ATTR Interrupt() 1468 | #else 1469 | void Interrupt() 1470 | #endif 1471 | { 1472 | uint8_t intr ; 1473 | #if REENTRANT==1 1474 | 1475 | // Make a sort of mutex by using a volatile variable 1476 | if (inIntr) { 1477 | gwayConfig.reents++; 1478 | return; 1479 | } 1480 | else inIntr=true; 1481 | #elif REENTRANT==2 1482 | if (inIntr) { gwayConfig.reents++; } 1483 | #endif 1484 | flags = readRegister(REG_IRQ_FLAGS); 1485 | mask = readRegister(REG_IRQ_FLAGS_MASK); 1486 | intr = flags & ( ~ mask ); // Only react on non masked interrupts 1487 | 1488 | // Check for dio1 interrupts and invoke corresponding interrupt routine 1489 | if (intr & (IRQ_LORA_RXTOUT_MASK | IRQ_LORA_CDDETD_MASK | IRQ_LORA_FHSSCH_MASK)) { 1490 | Interrupt_1(); 1491 | } 1492 | 1493 | flags = readRegister(REG_IRQ_FLAGS); 1494 | mask = readRegister(REG_IRQ_FLAGS_MASK); 1495 | intr = flags & ( ~ mask ); // Only react on non masked interrupts 1496 | 1497 | // Check for dio0 interrupts 1498 | if (intr & (IRQ_LORA_RXDONE_MASK | IRQ_LORA_TXDONE_MASK | IRQ_LORA_CDDONE_MASK)) { 1499 | Interrupt_0(); 1500 | } 1501 | 1502 | // Check for dio2 interrupts, Only for Frequency Hopping not used in this code 1503 | //if (intr & ( IRQ_LORA_FHSSCH_MASK )) { Interrupt_2(); } 1504 | #if REENTRANT>=1 1505 | inIntr=false; 1506 | #endif 1507 | } 1508 | -------------------------------------------------------------------------------- /LoRaGoDOCK-Gateway/_otaServer.ino: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * 3 | * Description: Source code for single-channel LoRaWAN Gateway based on ESP8266 and SX1276 4 | * Version : 0.8.2 5 | * Date : 2018-11-26 6 | * Software : https://github.com/SandboxElectronics/LoRaGoDOCK-Gateway 7 | * Hardware : LoRaGo DOCK – http://sandboxelectronics.com/?product=lorago-dock-single-channel-lorawan-gateway 8 | * 9 | * Copyright (c) 2016, 2017 Maarten Westenberg 10 | * 11 | * All rights reserved. This program and the accompanying materials 12 | * are made available under the terms of the MIT License 13 | * which accompanies this distribution, and is available at 14 | * https://opensource.org/licenses/mit-license.php 15 | * 16 | *****************************************************************************************/ 17 | 18 | // This file contains the otaa code for the ESP Single Channel Gateway. 19 | 20 | // Provide OTAA server funcionality so the 1ch gateway can be updated 21 | // over the air. 22 | // This code uses the ESPhttpServer functions to update the gateway. 23 | 24 | #if A_OTA==1 25 | 26 | //extern ArduinoOTAClass ArduinoOTA; 27 | 28 | // Make sure that webserver is running before continuing 29 | 30 | // ---------------------------------------------------------------------------- 31 | // setupOta 32 | // Function to run in the setup() function to initialise the update function 33 | // ---------------------------------------------------------------------------- 34 | void setupOta(char *hostname) { 35 | ArduinoOTA.begin(); 36 | Serial.println(F("OTA Started")); 37 | 38 | // Hostname defaults to esp8266-[ChipID] 39 | ArduinoOTA.setHostname(hostname); 40 | 41 | ArduinoOTA.onStart([]() { 42 | String type; 43 | // XXX version mispatch of platform.io and ArduinoOtaa 44 | // see https://github.com/esp8266/Arduino/issues/3020 45 | //if (ArduinoOTA.getCommand() == U_FLASH) 46 | type = "sketch"; 47 | //else // U_SPIFFS 48 | // type = "filesystem"; 49 | 50 | // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() 51 | Serial.println("Start updating " + type); 52 | }); 53 | 54 | ArduinoOTA.onEnd([]() { 55 | Serial.println("\nEnd"); 56 | }); 57 | 58 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 59 | Serial.printf("Progress: %u%%\r", (progress / (total / 100))); 60 | }); 61 | 62 | ArduinoOTA.onError([](ota_error_t error) { 63 | Serial.printf("Error[%u]: ", error); 64 | if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); 65 | else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); 66 | else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); 67 | else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); 68 | else if (error == OTA_END_ERROR) Serial.println("End Failed"); 69 | }); 70 | 71 | // Only if the Webserver is active also 72 | #if A_SERVER==2 // Displaed for the moment 73 | ESPhttpUpdate.rebootOnUpdate(false); 74 | 75 | server.on("/esp", HTTP_POST, [&](){ 76 | 77 | HTTPUpdateResult ret = ESPhttpUpdate.update(server.arg("firmware"), "1.0.0"); 78 | 79 | switch(ret) { 80 | case HTTP_UPDATE_FAILED: 81 | //PREi::sendJSON(500, "Update failed."); 82 | Serial.println(F("Update failed")); 83 | break; 84 | case HTTP_UPDATE_NO_UPDATES: 85 | //PREi::sendJSON(304, "Update not necessary."); 86 | Serial.println(F("Update not necessary")); 87 | break; 88 | case HTTP_UPDATE_OK: 89 | //PREi::sendJSON(200, "Update started."); 90 | Serial.println(F("Update started")); 91 | ESP.restart(); 92 | break; 93 | default: 94 | Serial.println(F("setupOta:: Unknown ret=")); 95 | } 96 | }); 97 | #endif 98 | } 99 | 100 | 101 | // ---------------------------------------------------------------------------- 102 | // 103 | // 104 | // ---------------------------------------------------------------------------- 105 | void updateOtaa() { 106 | 107 | String response=""; 108 | printIP((IPAddress)WiFi.localIP(),'.',response); 109 | 110 | ESPhttpUpdate.update(response, 80, "/arduino.bin"); 111 | 112 | } 113 | 114 | 115 | #endif 116 | -------------------------------------------------------------------------------- /LoRaGoDOCK-Gateway/_sensor.ino: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * 3 | * Description: Source code for single-channel LoRaWAN Gateway based on ESP8266 and SX1276 4 | * Version : 0.8.2 5 | * Date : 2018-11-26 6 | * Software : https://github.com/SandboxElectronics/LoRaGoDOCK-Gateway 7 | * Hardware : LoRaGo DOCK – http://sandboxelectronics.com/?product=lorago-dock-single-channel-lorawan-gateway 8 | * 9 | * Copyright (c) 2016, 2017 Maarten Westenberg 10 | * 11 | * All rights reserved. This program and the accompanying materials 12 | * are made available under the terms of the MIT License 13 | * which accompanies this distribution, and is available at 14 | * https://opensource.org/licenses/mit-license.php 15 | * 16 | *****************************************************************************************/ 17 | 18 | // This file contains code for using the single channel gateway also as a sensor node. 19 | // Please specify the DevAddr and the AppSKey below (and on your LoRa backend). 20 | // Also you will have to choose what sensors to forward to your application. 21 | 22 | #if GATEWAYNODE==1 23 | 24 | unsigned char DevAddr[4] = _DEVADDR ; // see config.h 25 | 26 | 27 | // ---------------------------------------------------------------------------- 28 | // XXX Experimental Read Internal Sensors 29 | // 30 | // You can monitor some settings of the RFM95/sx1276 chip. For example the temperature 31 | // which is set in REGTEMP in FSK mode (not in LORA). Or the battery value. 32 | // Find some sensible sensor values for LoRa radio and read them below in separate function 33 | // 34 | // ---------------------------------------------------------------------------- 35 | //uint8_t readInternal(uint8_t reg) { 36 | // 37 | // return 0; 38 | //} 39 | 40 | 41 | // ---------------------------------------------------------------------------- 42 | // LoRaSensors() is a function that puts sensor values in the MACPayload and 43 | // sends these values up to the server. For the server it is impossible to know 44 | // whther or not the message comes from a LoRa node or from the gateway. 45 | // 46 | // The example code below adds a battery value in lCode (encoding protocol) but 47 | // of-course you can add any byte string you wish 48 | // 49 | // Parameters: 50 | // - buf: contains the buffer to put the sensor values in 51 | // Returns: 52 | // - The amount of sensor characters put in the buffer 53 | // ---------------------------------------------------------------------------- 54 | static int LoRaSensors(uint8_t *buf) { 55 | 56 | uint8_t internalSersors; 57 | //internalSersors = readInternal(0x1A); 58 | //if (internalSersors > 0) { 59 | // return (internalSersors); 60 | //} 61 | 62 | 63 | buf[0] = 0x86; // 134; User code 65 | buf[2] = 0x3F; // 63; lCode code 66 | // Parity = buf[0]==1 buf[1]=1 buf[2]=0 ==> even, so last bit of first byte must be 0 67 | 68 | return(3); // return the number of bytes added to payload 69 | } 70 | 71 | 72 | // ---------------------------------------------------------------------------- 73 | // XOR() 74 | // perform x-or function for buffer and key 75 | // Since we do this ONLY for keys and X, Y we know that we need to XOR 16 bytes. 76 | // 77 | // ---------------------------------------------------------------------------- 78 | static void mXor(uint8_t *buf, uint8_t *key) { 79 | for (uint8_t i = 0; i < 16; ++i) buf[i] ^= key[i]; 80 | } 81 | 82 | 83 | // ---------------------------------------------------------------------------- 84 | // SHIFT-LEFT 85 | // Shift the buffer buf left one bit 86 | // Parameters: 87 | // - buf: An array of uint8_t bytes 88 | // - len: Length of the array in bytes 89 | // ---------------------------------------------------------------------------- 90 | static void shift_left(uint8_t * buf, uint8_t len) { 91 | while (len--) { 92 | uint8_t next = len ? buf[1] : 0; // len 0 to 15 93 | 94 | uint8_t val = (*buf << 1); 95 | if (next & 0x80) val |= 0x01; 96 | *buf++ = val; 97 | } 98 | } 99 | 100 | 101 | // ---------------------------------------------------------------------------- 102 | // generate_subkey 103 | // RFC 4493, para 2.3 104 | // ---------------------------------------------------------------------------- 105 | static void generate_subkey(uint8_t *key, uint8_t *k1, uint8_t *k2) { 106 | 107 | memset(k1, 0, 16); // Fill subkey1 with 0x00 108 | 109 | // Step 1: Assume k1 is an all zero block 110 | AES_Encrypt(k1,key); 111 | 112 | // Step 2: Analyse outcome of Encrypt operation (in k1), generate k1 113 | if (k1[0] & 0x80) { 114 | shift_left(k1,16); 115 | k1[15] ^= 0x87; 116 | } 117 | else { 118 | shift_left(k1,16); 119 | } 120 | 121 | // Step 3: Generate k2 122 | for (uint8_t i=0; i<16; i++) k2[i]=k1[i]; 123 | if (k1[0] & 0x80) { // use k1(==k2) according rfc 124 | shift_left(k2,16); 125 | k2[15] ^= 0x87; 126 | } 127 | else { 128 | shift_left(k2,16); 129 | } 130 | 131 | // step 4: Done, return k1 and k2 132 | return; 133 | } 134 | 135 | 136 | // ---------------------------------------------------------------------------- 137 | // ENCODEPACKET 138 | // In Sensor mode, we have to encode the user payload before sending. 139 | // The library files for AES are added to the library directory in AES. 140 | // For the moment we use the AES library made by ideetron as this library 141 | // is also used in the LMIC stack and is small in size. 142 | // 143 | // The function below follows the LoRa spec exactly. 144 | // 145 | // The resulting mumber of Bytes is returned by the functions. This means 146 | // 16 bytes per block, and as we add to the last block we also return 16 147 | // bytes for the last block. 148 | // 149 | // The LMIC code does not do this, so maybe we shorten the last block to only 150 | // the meaningful bytes in the last block. This means that encoded buffer 151 | // is exactly as big as the original message. 152 | // 153 | // NOTE:: Be aware that the LICENSE of the used AES library files 154 | // that we call with AES_Encrypt() is GPL3. It is used as-is, 155 | // but not part of this code. 156 | // 157 | // cmac = aes128_encrypt(K, Block_A[i]) 158 | // ---------------------------------------------------------------------------- 159 | uint8_t encodePacket(uint8_t *Data, uint8_t DataLength, uint16_t FrameCount, uint8_t Direction) { 160 | 161 | unsigned char AppSKey[16] = _APPSKEY ; // see config.h 162 | uint8_t i, j; 163 | uint8_t Block_A[16]; 164 | uint8_t bLen=16; // Block length is 16 except for last block in message 165 | 166 | uint8_t restLength = DataLength % 16; // We work in blocks of 16 bytes, this is the rest 167 | uint8_t numBlocks = DataLength / 16; // Number of whole blocks to encrypt 168 | if (restLength>0) numBlocks++; // And add block for the rest if any 169 | 170 | for(i = 1; i <= numBlocks; i++) { 171 | Block_A[0] = 0x01; 172 | 173 | Block_A[1] = 0x00; 174 | Block_A[2] = 0x00; 175 | Block_A[3] = 0x00; 176 | Block_A[4] = 0x00; 177 | 178 | Block_A[5] = Direction; // 0 is uplink 179 | 180 | Block_A[6] = DevAddr[3]; // Only works for and with ABP 181 | Block_A[7] = DevAddr[2]; 182 | Block_A[8] = DevAddr[1]; 183 | Block_A[9] = DevAddr[0]; 184 | 185 | Block_A[10] = (FrameCount & 0x00FF); 186 | Block_A[11] = ((FrameCount >> 8) & 0x00FF); 187 | Block_A[12] = 0x00; // Frame counter upper Bytes 188 | Block_A[13] = 0x00; // These are not used so are 0 189 | 190 | Block_A[14] = 0x00; 191 | 192 | Block_A[15] = i; 193 | 194 | // Encrypt and calculate the S 195 | AES_Encrypt(Block_A, AppSKey); 196 | 197 | // Last block? set bLen to rest 198 | if ((i == numBlocks) && (restLength>0)) bLen = restLength; 199 | 200 | for(j = 0; j < bLen; j++) { 201 | *Data = *Data ^ Block_A[j]; 202 | Data++; 203 | } 204 | } 205 | //return(numBlocks*16); // Do we really want to return all 16 bytes in lastblock 206 | return(DataLength); // or only 16*(numBlocks-1)+bLen; 207 | } 208 | 209 | 210 | // ---------------------------------------------------------------------------- 211 | // MICPACKET() 212 | // Provide a valid MIC 4-byte code (par 2.4 of spec, RFC4493) 213 | // see also https://tools.ietf.org/html/rfc4493 214 | // 215 | // Although our own handler may choose not to interpret the last 4 (MIC) bytes 216 | // of a PHYSPAYLOAD physical payload message of in internal sensor, 217 | // The official TTN (and other) backends will intrpret the complete message and 218 | // conclude that the generated message is bogus. 219 | // So we sill really simulate internal messages coming from the -1ch gateway 220 | // to come from a real sensor and append 4 MIC bytes to every message that are 221 | // perfectly legimate 222 | // Parameters: 223 | // - data: uint8_t array of bytes = ( MHDR | FHDR | FPort | FRMPayload ) 224 | // - len: 8=bit length of data, normally less than 64 bytes 225 | // - FrameCount: 16-bit framecounter 226 | // - dir: 0=up, 1=down 227 | // 228 | // B0 = ( 0x49 | 4 x 0x00 | Dir | 4 x DevAddr | 4 x FCnt | 0x00 | len ) 229 | // MIC is cmac [0:3] of ( aes128_cmac(NwkSKey, B0 | Data ) 230 | // 231 | // ---------------------------------------------------------------------------- 232 | uint8_t micPacket(uint8_t *data, uint8_t len, uint16_t FrameCount, uint8_t dir) { 233 | 234 | 235 | unsigned char NwkSKey[16] = _NWKSKEY ; 236 | uint8_t Block_B[16]; 237 | uint8_t X[16]; 238 | uint8_t Y[16]; 239 | 240 | // ------------------------------------ 241 | // build the B block used by the MIC process 242 | Block_B[0]= 0x49; // 1 byte MIC code 243 | 244 | Block_B[1]= 0x00; // 4 byte 0x00 245 | Block_B[2]= 0x00; 246 | Block_B[3]= 0x00; 247 | Block_B[4]= 0x00; 248 | 249 | Block_B[5]= dir; // 1 byte Direction 250 | 251 | Block_B[6]= DevAddr[3]; // 4 byte DevAddr 252 | Block_B[7]= DevAddr[2]; 253 | Block_B[8]= DevAddr[1]; 254 | Block_B[9]= DevAddr[0]; 255 | 256 | Block_B[10]= (FrameCount & 0x00FF); // 4 byte FCNT 257 | Block_B[11]= ((FrameCount >> 8) & 0x00FF); 258 | Block_B[12]= 0x00; // Frame counter upper Bytes 259 | Block_B[13]= 0x00; // These are not used so are 0 260 | 261 | Block_B[14]= 0x00; // 1 byte 0x00 262 | 263 | Block_B[15]= len; // 1 byte len 264 | 265 | // ------------------------------------ 266 | // Step 1: Generate the subkeys 267 | // 268 | uint8_t k1[16]; 269 | uint8_t k2[16]; 270 | generate_subkey(NwkSKey, k1, k2); 271 | 272 | // ------------------------------------ 273 | // Copy the data to a new buffer which is prepended with Block B0 274 | // 275 | uint8_t micBuf[len+16]; // B0 | data 276 | for (uint8_t i=0; i<16; i++) micBuf[i]=Block_B[i]; 277 | for (uint8_t i=0; i restBits) Y[i] = 0x00; 317 | } 318 | mXor(Y, k2); 319 | } 320 | else { 321 | for (uint8_t i=0; i<16; i++) { 322 | Y[i] = micBuf[((numBlocks-1)*16)+i]; 323 | } 324 | mXor(Y, k1); 325 | } 326 | mXor(Y, X); 327 | AES_Encrypt(Y,NwkSKey); 328 | 329 | // ------------------------------------ 330 | // Step 7: done, return the MIC size. 331 | // Only 4 bytes are returned (32 bits), which is less than the RFC recommends. 332 | // We return by appending 4 bytes to data, so there must be space in data array. 333 | // 334 | data[len+0]=Y[0]; 335 | data[len+1]=Y[1]; 336 | data[len+2]=Y[2]; 337 | data[len+3]=Y[3]; 338 | return 4; 339 | } 340 | 341 | 342 | #if _CHECK_MIC==1 343 | // ---------------------------------------------------------------------------- 344 | // CHECKMIC 345 | // Function to check the MIC computed for existing messages and for new messages 346 | // Parameters: 347 | // - buf: LoRa buffer to check in bytes, last 4 bytes contain the MIC 348 | // - len: Length of buffer in bytes 349 | // - key: Key to use for MIC. Normally this is the NwkSKey 350 | // 351 | // ---------------------------------------------------------------------------- 352 | static void checkMic(uint8_t *buf, uint8_t len, uint8_t *key) { 353 | uint8_t cBuf[len+1]; 354 | 355 | if (debug>=2) { 356 | Serial.print(F("old=")); 357 | for (uint8_t i=0; i=2) { 370 | Serial.print(F("new=")); 371 | for (uint8_t i=0; i 512) { 474 | if (debug>0) Serial.println(F("sensorPacket:: ERROR buffer size too large")); 475 | return(-1); 476 | } 477 | 478 | sendUdp(buff_up, buff_index); 479 | 480 | // Reset all RX lora stuff 481 | _state = S_RX; 482 | rxLoraModem(); 483 | 484 | // If we now switch to S_SCAN, we have to hop too 485 | if (_hop) { hop(); } 486 | 487 | if (_cad) { 488 | // Set the state to CAD scanning after receiving 489 | _state = S_SCAN; // Inititialise scanner 490 | cadScanner(); 491 | } 492 | 493 | return(buff_index); 494 | } 495 | 496 | #endif //GATEWAYNODE==1 497 | -------------------------------------------------------------------------------- /LoRaGoDOCK-Gateway/_wwwServer.ino: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * 3 | * Description: Source code for single-channel LoRaWAN Gateway based on ESP8266 and SX1276 4 | * Version : 0.8.2 5 | * Date : 2018-11-26 6 | * Software : https://github.com/SandboxElectronics/LoRaGoDOCK-Gateway 7 | * Hardware : LoRaGo DOCK – http://sandboxelectronics.com/?product=lorago-dock-single-channel-lorawan-gateway 8 | * 9 | * Copyright (c) 2016, 2017 Maarten Westenberg 10 | * 11 | * All rights reserved. This program and the accompanying materials 12 | * are made available under the terms of the MIT License 13 | * which accompanies this distribution, and is available at 14 | * https://opensource.org/licenses/mit-license.php 15 | * 16 | *****************************************************************************************/ 17 | 18 | // This file contains the webserver code for the ESP Single Channel Gateway. 19 | 20 | // Note: 21 | // The ESP Webserver works with Strings to display html content. 22 | // Care must be taken that not all data is output to the webserver in one string 23 | // as this will use a LOT of memory and possibly kill the heap (cause system 24 | // crash or other unreliable behaviour. 25 | // Instead, output of the various tables on the webpage should be displayed in 26 | // chucks so that strings are limited in size. 27 | // Be aware that using no strings but only sendContent() calls has its own 28 | // disadvantage that these calls take a lot of time and cause the page to be 29 | // displayed like an old typewriter. 30 | // So, the trick is to make chucks that are sent to the website by using 31 | // a response String but not make those Strings too big. 32 | 33 | // ---------------------------------------------------------------------------- 34 | // PRINT IP 35 | // Output the 4-byte IP address for easy printing. 36 | // As this function is also used by _otaServer.ino do not put in #define 37 | // ---------------------------------------------------------------------------- 38 | static void printIP(IPAddress ipa, const char sep, String& response) 39 | { 40 | response+=(IPAddress)ipa[0]; response+=sep; 41 | response+=(IPAddress)ipa[1]; response+=sep; 42 | response+=(IPAddress)ipa[2]; response+=sep; 43 | response+=(IPAddress)ipa[3]; 44 | } 45 | 46 | #if A_SERVER==1 47 | 48 | // ================================================================================ 49 | // WEBSERVER DECLARATIONS 50 | 51 | // None at the moment 52 | 53 | // ================================================================================ 54 | // WEBSERVER FUNCTIONS 55 | 56 | 57 | // ---------------------------------------------------------------------------- 58 | // Print a HEXadecimal string from a 4-byte char string 59 | // 60 | // ---------------------------------------------------------------------------- 61 | static void printHEX(char * hexa, const char sep, String& response) 62 | { 63 | char m; 64 | m = hexa[0]; if (m<016) response+='0'; response += String(m, HEX); response+=sep; 65 | m = hexa[1]; if (m<016) response+='0'; response += String(m, HEX); response+=sep; 66 | m = hexa[2]; if (m<016) response+='0'; response += String(m, HEX); response+=sep; 67 | m = hexa[3]; if (m<016) response+='0'; response += String(m, HEX); response+=sep; 68 | } 69 | 70 | // ---------------------------------------------------------------------------- 71 | // stringTime 72 | // Only when RTC is present we print real time values 73 | // t contains number of milli seconds since system started that the event happened. 74 | // So a value of 100 wold mean that the event took place 1 minute and 40 seconds ago 75 | // ---------------------------------------------------------------------------- 76 | static void stringTime(unsigned long t, String& response) { 77 | 78 | if (t==0) { response += "--"; return; } 79 | 80 | // now() gives seconds since 1970 81 | time_t eventTime = now() - ((millis()-t)/1000); 82 | byte _hour = hour(eventTime); 83 | byte _minute = minute(eventTime); 84 | byte _second = second(eventTime); 85 | 86 | switch(weekday(eventTime)) { 87 | case 1: response += "Sunday "; break; 88 | case 2: response += "Monday "; break; 89 | case 3: response += "Tuesday "; break; 90 | case 4: response += "Wednesday "; break; 91 | case 5: response += "Thursday "; break; 92 | case 6: response += "Friday "; break; 93 | case 7: response += "Saturday "; break; 94 | } 95 | response += String() + day(eventTime) + "-"; 96 | response += String() + month(eventTime) + "-"; 97 | response += String() + year(eventTime) + " "; 98 | 99 | if (_hour < 10) response += "0"; 100 | response += String() + _hour + ":"; 101 | if (_minute < 10) response += "0"; 102 | response += String() + _minute + ":"; 103 | if (_second < 10) response += "0"; 104 | response += String() + _second; 105 | } 106 | 107 | 108 | 109 | 110 | // ---------------------------------------------------------------------------- 111 | // SET ESP8266 WEB SERVER VARIABLES 112 | // 113 | // This funtion implements the WiFI Webserver (very simple one). The purpose 114 | // of this server is to receive simple admin commands, and execute these 115 | // results are sent back to the web client. 116 | // Commands: DEBUG, ADDRESS, IP, CONFIG, GETTIME, SETTIME 117 | // The webpage is completely built response and then printed on screen. 118 | // ---------------------------------------------------------------------------- 119 | static void setVariables(const char *cmd, const char *arg) { 120 | 121 | // DEBUG settings; These can be used as a single argument 122 | if (strcmp(cmd, "DEBUG")==0) { // Set debug level 0-2 123 | if (atoi(arg) == 1) { 124 | debug = (debug+1)%4; 125 | } 126 | else if (atoi(arg) == -1) { 127 | debug = (debug+3)%4; 128 | } 129 | writeGwayCfg(CONFIGFILE); // Save configuration to file 130 | } 131 | 132 | if (strcmp(cmd, "CAD")==0) { // Set -cad on=1 or off=0 133 | _cad=(bool)atoi(arg); 134 | writeGwayCfg(CONFIGFILE); // Save configuration to file 135 | } 136 | 137 | if (strcmp(cmd, "HOP")==0) { // Set -hop on=1 or off=0 138 | _hop=(bool)atoi(arg); 139 | if (! _hop) { ifreq=0; freq=freqs[0]; rxLoraModem(); } 140 | writeGwayCfg(CONFIGFILE); // Save configuration to file 141 | } 142 | 143 | if (strcmp(cmd, "DELAY")==0) { // Set delay usecs 144 | txDelay+=atoi(arg)*1000; 145 | } 146 | 147 | // SF; Handle Spreading Factor Settings 148 | if (strcmp(cmd, "SF")==0) { 149 | uint8_t sfi = sf; 150 | if (atoi(arg) == 1) { 151 | if (sf==SF12) sf=SF7; else sf= (sf_t)((int)sf+1); 152 | } 153 | else if (atoi(arg) == -1) { 154 | if (sf==SF7) sf=SF12; else sf= (sf_t)((int)sf-1); 155 | } 156 | rxLoraModem(); // Reset the radion with the new spreading factor 157 | writeGwayCfg(CONFIGFILE); // Save configuration to file 158 | } 159 | 160 | // FREQ; Handle Frequency Settings 161 | if (strcmp(cmd, "FREQ")==0) { 162 | uint8_t nf = sizeof(freqs)/sizeof(int); // Number of elements in array 163 | 164 | // Compute frequency index 165 | if (atoi(arg) == 1) { 166 | if (ifreq==(nf-1)) ifreq=0; else ifreq++; 167 | } 168 | else if (atoi(arg) == -1) { 169 | Serial.println("down"); 170 | if (ifreq==0) ifreq=(nf-1); else ifreq--; 171 | } 172 | 173 | freq = freqs[ifreq]; 174 | rxLoraModem(); // Reset the radion with the new frequency 175 | writeGwayCfg(CONFIGFILE); // Save configuration to file 176 | } 177 | 178 | //if (strcmp(cmd, "GETTIME")==0) { Serial.println(F("gettime tbd")); } // Get the local time 179 | 180 | //if (strcmp(cmd, "SETTIME")==0) { Serial.println(F("settime tbd")); } // Set the local time 181 | 182 | if (strcmp(cmd, "HELP")==0) { Serial.println(F("Display Help Topics")); } 183 | 184 | #if GATEWAYNODE==1 185 | if (strcmp(cmd, "NODE")==0) { // Set node on=1 or off=0 186 | gwayConfig.node =(bool)atoi(arg); 187 | writeGwayCfg(CONFIGFILE); // Save configuration to file 188 | } 189 | 190 | if (strcmp(cmd, "FCNT")==0) { 191 | frameCount=0; 192 | rxLoraModem(); // Reset the radion with the new frequency 193 | writeGwayCfg(CONFIGFILE); 194 | } 195 | #endif 196 | 197 | 198 | 199 | #if WIFIMANAGER==1 200 | if (strcmp(cmd, "NEWSSID")==0) { 201 | WiFiManager wifiManager; 202 | strcpy(wpa[0].login,""); 203 | strcpy(wpa[0].passw,""); 204 | WiFi.disconnect(); 205 | wifiManager.autoConnect(AP_NAME, AP_PASSWD ); 206 | } 207 | #endif 208 | 209 | #if A_OTAA==1 210 | if (strcmp(cmd, "UPDATE")==0) { 211 | if (atoi(arg) == 1) { 212 | updateOtaa(); 213 | } 214 | } 215 | #endif 216 | 217 | #if A_REFRESH==1 218 | if (strcmp(cmd, "REFR")==0) { // Set refresh on=1 or off=0 219 | gwayConfig.refresh =(bool)atoi(arg); 220 | writeGwayCfg(CONFIGFILE); // Save configuration to file 221 | } 222 | #endif 223 | 224 | } 225 | 226 | 227 | // ---------------------------------------------------------------------------- 228 | // OPEN WEB PAGE 229 | // This is the init function for opening the webpage 230 | // 231 | // ---------------------------------------------------------------------------- 232 | static void openWebPage() 233 | { 234 | ++gwayConfig.views; // increment number of views 235 | #if A_REFRESH==1 236 | //server.client().stop(); // Experimental, stop webserver in case something is still running! 237 | #endif 238 | String response=""; 239 | 240 | server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); 241 | server.sendHeader("Pragma", "no-cache"); 242 | server.sendHeader("Expires", "-1"); 243 | 244 | // init webserver, fill the webpage 245 | // NOTE: The page is renewed every _WWW_INTERVAL seconds, please adjust in config.h 246 | // 247 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 248 | server.send(200, "text/html", ""); 249 | #if A_REFRESH==1 250 | if (gwayConfig.refresh) { 251 | response += String() + "ESP8266 1ch Gateway"; 257 | } 258 | #else 259 | response += String() + "ESP8266 1ch Gateway"; 260 | #endif 261 | response += ""; 262 | response += ""; 263 | 264 | response += ""; 268 | 269 | response +="

ESP Gateway Config

"; 270 | 271 | response +="Version: "; response+=VERSION; 272 | response +="
ESP alive since "; 273 | stringTime(1, response); 274 | 275 | response +=", Uptime: "; 276 | uint32_t secs = millis()/1000; 277 | uint16_t days = secs / 86400; // Determine number of days 278 | uint8_t _hour = hour(secs); 279 | uint8_t _minute = minute(secs); 280 | uint8_t _second = second(secs); 281 | response += String() + days + "-"; 282 | if (_hour < 10) response += "0"; 283 | response += String() + _hour + ":"; 284 | if (_minute < 10) response += "0"; 285 | response += String() + _minute + ":"; 286 | if (_second < 10) response += "0"; 287 | response += String() + _second; 288 | 289 | response +="
Current time "; 290 | stringTime(millis(), response); 291 | response +="
"; 292 | 293 | server.sendContent(response); 294 | } 295 | 296 | 297 | // ---------------------------------------------------------------------------- 298 | // CONFIG DATA 299 | // 300 | // 301 | // ---------------------------------------------------------------------------- 302 | static void configData() 303 | { 304 | String response=""; 305 | String bg=""; 306 | 307 | response +="

Gateway Settings

"; 308 | 309 | response +=""; 310 | response +=""; 311 | response +=""; 312 | response +=""; 313 | response +=""; 314 | response +=""; 315 | 316 | #if GATEWAYMGT==1 317 | bg = " background-color: "; 318 | bg += ( (_cad == 1) ? "LightGreen" : "orange" ); 319 | response +=""; 320 | response +=""; 323 | response +=""; 324 | response +=""; 325 | 326 | bg = " background-color: "; 327 | bg += ( (_hop == 1) ? "LightGreen" : "orange" ); 328 | response +=""; 329 | response +=""; 332 | response +=""; 333 | response +=""; 334 | 335 | response +=""; 338 | } 339 | else { 340 | response += sf; 341 | response +=""; 342 | response +=""; 343 | } 344 | response +=""; 345 | 346 | // Channel 347 | response +=""; 348 | response +=""; 351 | } 352 | else { 353 | response += String() + ifreq; 354 | response +=""; 355 | response +=""; 356 | response +=""; 357 | } 358 | response +=""; 359 | #endif 360 | 361 | // Debugging options 362 | response +=""; 366 | response +=""; 367 | response +=""; 368 | response +=""; 369 | 370 | #if GATEWAYNODE==1 371 | response +=""; 372 | response +=""; 376 | response +=""; 377 | 378 | bg = " background-color: "; 379 | bg += ( (gwayConfig.node == 1) ? "LightGreen" : "orange" ); 380 | response +=""; 381 | response +=""; 384 | response +=""; 385 | response +=""; 386 | #endif 387 | 388 | #if A_REFRESH==1 389 | bg = " background-color: "; 390 | bg += ( (gwayConfig.refresh == 1) ? "LightGreen" : "orange" ); 391 | response +=""; 392 | response +=""; 395 | response +=""; 396 | response +=""; 397 | #endif 398 | 399 | #if WIFIMANAGER==1 400 | response +=""; 403 | #endif 404 | 405 | // Reset all statistics 406 | #if STATISTICS >= 1 407 | response +=""; 408 | response +=String() + ""; 409 | response +=""; 410 | 411 | response +=""; 412 | response +=String() + ""; 413 | response +=""; 414 | #endif 415 | response +="
SettingValueSet
CAD"; 321 | response += ( (_cad == 1) ? "ON" : "OFF" ); 322 | response +="
HOP"; 330 | response += ( (_hop == 1) ? "ON" : "OFF" ); 331 | response +="
SF Setting"; 336 | if (_cad == 1) { 337 | response += "AUTO
Channel"; 349 | if (_hop == 1) { 350 | response += "AUTO
"; 363 | response +="Debug level"; 364 | response +=debug; 365 | response +="
Framecounter Internal Sensor"; 373 | response +=frameCount; 374 | response +=""; 375 | response +="
Gateway Node"; 382 | response += ( (gwayConfig.node == true) ? "ON" : "OFF" ); 383 | response +="
WWW Refresh"; 393 | response += ( (gwayConfig.refresh == 1) ? "ON" : "OFF" ); 394 | response +="
"; 401 | response +="Click here to reset accesspoint
"; 402 | response +="
Statistics"+statc.resets+"
Boots and Resets"+gwayConfig.boots+"
"; 416 | 417 | // Update Firmware all statistics 418 | response +="Update Firmware"; 419 | response +=""; 420 | response +=""; 421 | 422 | server.sendContent(response); 423 | } 424 | 425 | 426 | 427 | // ---------------------------------------------------------------------------- 428 | // INTERRUPT DATA 429 | // Display interrupt data, but only for debug >= 2 430 | // 431 | // ---------------------------------------------------------------------------- 432 | static void interruptData() 433 | { 434 | if (debug >= 2) { 435 | String response=""; 436 | 437 | response +="

System State and Interrupt

"; 438 | 439 | response +=""; 440 | response +=""; 441 | response +=""; 442 | response +=""; 443 | response +=""; 444 | response +=""; 445 | 446 | response +=""; 447 | response +=""; 458 | 459 | response +=""; 460 | response +=""; 463 | 464 | 465 | response +=""; 466 | response +=""; 469 | 470 | response +=""; 471 | response +=""; 474 | 475 | response +=""; 476 | response +=""; 479 | 480 | response +=""; 481 | response +=""; 484 | 485 | response +=""; 486 | response +=""; 489 | 490 | response +=""; 493 | response +=""; 494 | response +=""; 495 | response +=""; 496 | 497 | response +="
ParameterValueSet
_state"; 448 | switch (_state) { // See loraModem.h 449 | case 0: response +="INIT"; break; 450 | case 1: response +="SCAN"; break; 451 | case 2: response +="CAD"; break; 452 | case 3: response +="RX"; break; 453 | case 4: response +="RXDONE"; break; 454 | case 5: response +="TX"; break; 455 | default: response +="unknown"; break; 456 | } 457 | response +="
flags (8 bits)0x"; 461 | if (flags <16) response += "0"; 462 | response+=String(flags,HEX); response+="
mask (8 bits)0x"; 467 | if (mask <16) response += "0"; 468 | response+=String(mask,HEX); response+="
REENTRANT"; 472 | response += (REENTRANT==1 ? "SAFE" : (REENTRANT==2 ? "ON" : "OFF" )); 473 | response+="
Re-entrant cntr"; 477 | response += String() + gwayConfig.reents; 478 | response+="
ntp call cntr"; 482 | response += String() + gwayConfig.ntps; 483 | response+="
ntpErr cntr"; 487 | response += String() + gwayConfig.ntpErr; 488 | response+="
Timing Correction (uSec)"; 491 | response += txDelay; 492 | response +="
"; 498 | 499 | server.sendContent(response); 500 | }// if debug>=2 501 | } 502 | 503 | 504 | // ---------------------------------------------------------------------------- 505 | // STATISTICS DATA 506 | // 507 | // ---------------------------------------------------------------------------- 508 | static void statisticsData() 509 | { 510 | String response=""; 511 | 512 | response +="

Package Statistics

"; 513 | 514 | response +=""; 515 | response +=""; 516 | response +=""; 517 | response +=""; 518 | response +=""; 519 | response +=""; 521 | response +=""; 523 | response +=""; 525 | 526 | #if STATISTICS >= 2 527 | response +=""; response +=""; 528 | response +=""; response +=""; 529 | response +=""; response +=""; 530 | response +=""; response +=""; 531 | response +=""; response +=""; 532 | response +=""; response +=""; 533 | #endif 534 | 535 | response +="
CounterValue
Packages Uplink Total"; 520 | response +=cp_nb_rx_rcv; response+="
Packages Uplink OK "; 522 | response +=cp_nb_rx_ok; response+="
Packages Downlink"; 524 | response +=cp_up_pkt_fwd; response+="
SF7 rcvd"; response +=statc.sf7; response +="
SF8 rcvd"; response +=statc.sf8; response +="
SF9 rcvd"; response +=statc.sf9; response +="
SF10 rcvd"; response +=statc.sf10; response +="
SF11 rcvd"; response +=statc.sf11; response +="
SF12 rcvd"; response +=statc.sf12; response +="
"; 536 | 537 | server.sendContent(response); 538 | } 539 | 540 | 541 | 542 | 543 | // ---------------------------------------------------------------------------- 544 | // SENSORDATA 545 | // If enabled, display the sensorHistory on the current webserver Page. 546 | // Parameters: 547 | // - 548 | // Returns: 549 | // - 550 | // ---------------------------------------------------------------------------- 551 | static void sensorData() 552 | { 553 | #if STATISTICS >= 1 554 | String response=""; 555 | 556 | response += "

Message History

"; 557 | response += ""; 558 | response += ""; 559 | response += ""; 560 | response += ""; 561 | response += ""; 562 | response += ""; 563 | response += ""; 564 | response += ""; 565 | response += ""; 566 | server.sendContent(response); 567 | 568 | for (int i=0; i"; 576 | response += String() + ""; 579 | response += String() + ""; 580 | response += String() + ""; 581 | response += String() + ""; 582 | response += String() + ""; 583 | response += String() + ""; 584 | 585 | server.sendContent(response); 586 | } 587 | 588 | server.sendContent("
TimeNodeChannelSFRSSIpRSSI
"; 574 | stringTime(statr[i].tmst, response); 575 | response += ""; 577 | printHEX((char *)(& (statr[i].node)),' ',response); 578 | response += "" + statr[i].ch + "" + freqs[statr[i].ch] + "" + statr[i].sf + "" + statr[i].rssi + "" + statr[i].prssi + "
"); 589 | 590 | #endif 591 | } 592 | 593 | // ---------------------------------------------------------------------------- 594 | // SYSTEMDATA 595 | // 596 | // ---------------------------------------------------------------------------- 597 | static void systemData() 598 | { 599 | String response=""; 600 | response +="

System Status

"; 601 | 602 | response +=""; 603 | response +=""; 604 | response +=""; 605 | response +=""; 606 | response +=""; 607 | 608 | response +=""; 609 | response +=""; 618 | 619 | response +=""; 620 | 621 | response +=""; 622 | 623 | #if STATISTICS>=1 624 | response +=""; 625 | response +=""; 626 | #endif 627 | 628 | response +="
ParameterValue
Gateway ID"; 610 | if (MAC_array[0]< 0x10) response +='0'; response +=String(MAC_array[0],HEX); // The MAC array is always returned in lowercase 611 | if (MAC_array[1]< 0x10) response +='0'; response +=String(MAC_array[1],HEX); 612 | if (MAC_array[2]< 0x10) response +='0'; response +=String(MAC_array[2],HEX); 613 | response +="FFFF"; 614 | if (MAC_array[3]< 0x10) response +='0'; response +=String(MAC_array[3],HEX); 615 | if (MAC_array[4]< 0x10) response +='0'; response +=String(MAC_array[4],HEX); 616 | if (MAC_array[5]< 0x10) response +='0'; response +=String(MAC_array[5],HEX); 617 | response+="
Free heap"; response+=ESP.getFreeHeap(); response+="
ESP Chip ID"; response+=ESP.getChipId(); response+="
WiFi Setups"; response+=gwayConfig.wifis; response+="
WWW Views"; response+=gwayConfig.views; response+="
"; 629 | server.sendContent(response); 630 | } 631 | 632 | 633 | // ---------------------------------------------------------------------------- 634 | // WIFIDATA 635 | // Display the most important Wifi parameters gathered 636 | // 637 | // ---------------------------------------------------------------------------- 638 | static void wifiData() 639 | { 640 | String response=""; 641 | response +="

WiFi Config

"; 642 | 643 | response +=""; 644 | 645 | response +=""; 646 | 647 | response +=""; 649 | 650 | response +=""; 652 | 653 | response +=""; 656 | response +=""; 659 | response +=""; 660 | response +=""; 661 | response +=""; 664 | #ifdef _THINGSERVER 665 | response +=""; 667 | response +=""; 670 | #endif 671 | response +="
ParameterValue
WiFi host"; 648 | response +=wifi_station_get_hostname(); response+="
WiFi SSID"; 651 | response +=WiFi.SSID(); response+="
IP Address"; 654 | printIP((IPAddress)WiFi.localIP(),'.',response); 655 | response +="
IP Gateway"; 657 | printIP((IPAddress)WiFi.gatewayIP(),'.',response); 658 | response +="
NTP Server"; response+=NTP_TIMESERVER; response+="
LoRa Router"; response+=_TTNSERVER; response+="
LoRa Router IP"; 662 | printIP((IPAddress)ttnServer,'.',response); 663 | response +="
LoRa Router 2"; response+=_THINGSERVER; 666 | response += String() + ":" + _THINGPORT + "
LoRa Router 2 IP"; 668 | printIP((IPAddress)thingServer,'.',response); 669 | response +="
"; 672 | 673 | server.sendContent(response); 674 | } 675 | 676 | 677 | // ---------------------------------------------------------------------------- 678 | // SEND WEB PAGE() 679 | // Call the webserver and send the standard content and the content that is 680 | // passed by the parameter. 681 | // 682 | // NOTE: This is the only place where yield() or delay() calls are used. 683 | // 684 | // ---------------------------------------------------------------------------- 685 | void sendWebPage(const char *cmd, const char *arg) 686 | { 687 | openWebPage(); yield(); 688 | 689 | setVariables(cmd,arg); yield(); 690 | 691 | statisticsData(); yield(); // Node statistics 692 | sensorData(); yield(); // Display the sensor history, message statistics 693 | systemData(); yield(); // System statistics such as heap etc. 694 | wifiData(); yield(); // WiFI specific parameters 695 | 696 | configData(); yield(); // Display web configuration 697 | 698 | interruptData(); yield(); // Display interrupts only when debug >= 2 699 | 700 | // Close the client connection to server 701 | server.sendContent(String() + "

Click here to explain Help and REST options
"); 702 | server.sendContent(String() + ""); 703 | 704 | server.sendContent(""); yield(); 705 | 706 | server.client().stop(); 707 | } 708 | 709 | // ---------------------------------------------------------------------------- 710 | // Used by timeout functions 711 | // This function only displays the standard homepage 712 | // Note: This function is not actively used, as the page is renewed 713 | // by using a HTML meta setting of 60 seconds 714 | // ---------------------------------------------------------------------------- 715 | static void renewWebPage() 716 | { 717 | //Serial.println(F("Renew Web Page")); 718 | //sendWebPage("",""); 719 | //return; 720 | } 721 | 722 | // ---------------------------------------------------------------------------- 723 | // SetupWWW function called by main setup() program to setup webserver 724 | // It does actually not much more than installing the callback handlers 725 | // for messages sent to the webserver 726 | // 727 | // ---------------------------------------------------------------------------- 728 | void setupWWW() 729 | { 730 | server.begin(); // Start the webserver 731 | 732 | // ----------------- 733 | // BUTTONS, define what should happen with the buttons we press on the homepage 734 | 735 | server.on("/", []() { 736 | sendWebPage("",""); // Send the webPage string 737 | server.sendHeader("Location", String("/"), true); 738 | server.send ( 302, "text/plain", ""); 739 | }); 740 | 741 | 742 | server.on("/HELP", []() { 743 | sendWebPage("HELP",""); // Send the webPage string 744 | server.sendHeader("Location", String("/"), true); 745 | server.send ( 302, "text/plain", ""); 746 | }); 747 | 748 | 749 | // Reset the statistics 750 | server.on("/RESET", []() { 751 | Serial.println(F("RESET")); 752 | cp_nb_rx_rcv = 0; 753 | cp_nb_rx_ok = 0; 754 | cp_up_pkt_fwd = 0; 755 | #if STATISTICS >= 1 756 | for (int i=0; i= 2 758 | statc.sf7 = 0; 759 | statc.sf8 = 0; 760 | statc.sf9 = 0; 761 | statc.sf10= 0; 762 | statc.sf11= 0; 763 | statc.sf12= 0; 764 | 765 | statc.resets= 0; 766 | writeGwayCfg(CONFIGFILE); 767 | #endif 768 | #endif 769 | server.sendHeader("Location", String("/"), true); 770 | server.send ( 302, "text/plain", ""); 771 | }); 772 | 773 | 774 | // Reset the boot counter 775 | server.on("/BOOT", []() { 776 | Serial.println(F("BOOT")); 777 | #if STATISTICS >= 2 778 | gwayConfig.boots = 0; 779 | gwayConfig.wifis = 0; 780 | gwayConfig.views = 0; 781 | gwayConfig.ntpErr = 0; // NTP errors 782 | gwayConfig.ntps = 0; // Number of NTP calls 783 | #endif 784 | gwayConfig.reents = 0; // Re-entrance 785 | 786 | writeGwayCfg(CONFIGFILE); 787 | server.sendHeader("Location", String("/"), true); 788 | server.send ( 302, "text/plain", ""); 789 | }); 790 | 791 | 792 | server.on("/NEWSSID", []() { 793 | sendWebPage("NEWSSID",""); // Send the webPage string 794 | server.sendHeader("Location", String("/"), true); 795 | server.send ( 302, "text/plain", ""); 796 | }); 797 | 798 | 799 | // Set debug parameter 800 | server.on("/DEBUG=-1", []() { // Set debug level 0-2 801 | debug = (debug+3)%4; 802 | writeGwayCfg(CONFIGFILE); // Save configuration to file 803 | server.sendHeader("Location", String("/"), true); 804 | server.send ( 302, "text/plain", ""); 805 | }); 806 | server.on("/DEBUG=1", []() { 807 | debug = (debug+1)%4; 808 | writeGwayCfg(CONFIGFILE); // Save configuration to file 809 | server.sendHeader("Location", String("/"), true); 810 | server.send ( 302, "text/plain", ""); 811 | }); 812 | 813 | 814 | // Set delay in microseconds 815 | server.on("/DELAY=1", []() { 816 | txDelay+=1000; 817 | server.sendHeader("Location", String("/"), true); 818 | server.send ( 302, "text/plain", ""); 819 | }); 820 | server.on("/DELAY=-1", []() { 821 | txDelay-=1000; 822 | server.sendHeader("Location", String("/"), true); 823 | server.send ( 302, "text/plain", ""); 824 | }); 825 | 826 | 827 | // Spreading Factor setting 828 | server.on("/SF=1", []() { 829 | if (sf==SF12) sf=SF7; else sf= (sf_t)((int)sf+1); 830 | server.sendHeader("Location", String("/"), true); 831 | server.send ( 302, "text/plain", ""); 832 | }); 833 | server.on("/SF=-1", []() { 834 | if (sf==SF7) sf=SF12; else sf= (sf_t)((int)sf-1); 835 | server.sendHeader("Location", String("/"), true); 836 | server.send ( 302, "text/plain", ""); 837 | }); 838 | 839 | 840 | // Frequency of the GateWay node 841 | server.on("/FREQ=1", []() { 842 | uint8_t nf = sizeof(freqs)/sizeof(int); // Number of elements in array 843 | if (ifreq==(nf-1)) ifreq=0; else ifreq++; 844 | server.sendHeader("Location", String("/"), true); 845 | server.send ( 302, "text/plain", ""); 846 | }); 847 | server.on("/FREQ=-1", []() { 848 | uint8_t nf = sizeof(freqs)/sizeof(int); // Number of elements in array 849 | if (ifreq==0) ifreq=(nf-1); else ifreq--; 850 | server.sendHeader("Location", String("/"), true); 851 | server.send ( 302, "text/plain", ""); 852 | }); 853 | 854 | 855 | // Set CAD function off/on 856 | server.on("/CAD=1", []() { 857 | _cad=(bool)1; 858 | writeGwayCfg(CONFIGFILE); // Save configuration to file 859 | server.sendHeader("Location", String("/"), true); 860 | server.send ( 302, "text/plain", ""); 861 | }); 862 | server.on("/CAD=0", []() { 863 | _cad=(bool)0; 864 | writeGwayCfg(CONFIGFILE); // Save configuration to file 865 | server.sendHeader("Location", String("/"), true); 866 | server.send ( 302, "text/plain", ""); 867 | }); 868 | 869 | 870 | // GatewayNode 871 | server.on("/NODE=1", []() { 872 | #if GATEWAYNODE==1 873 | gwayConfig.node =(bool)1; 874 | writeGwayCfg(CONFIGFILE); // Save configuration to file 875 | #endif 876 | server.sendHeader("Location", String("/"), true); 877 | server.send ( 302, "text/plain", ""); 878 | }); 879 | server.on("/NODE=0", []() { 880 | #if GATEWAYNODE==1 881 | gwayConfig.node =(bool)0; 882 | writeGwayCfg(CONFIGFILE); // Save configuration to file 883 | #endif 884 | server.sendHeader("Location", String("/"), true); 885 | server.send ( 302, "text/plain", ""); 886 | }); 887 | 888 | 889 | // Framecounter of the Gateway node 890 | server.on("/FCNT", []() { 891 | #if GATEWAYNODE==1 892 | frameCount=0; 893 | rxLoraModem(); // Reset the radion with the new frequency 894 | writeGwayCfg(CONFIGFILE); 895 | #endif 896 | //sendWebPage("",""); // Send the webPage string 897 | server.sendHeader("Location", String("/"), true); 898 | server.send ( 302, "text/plain", ""); 899 | }); 900 | 901 | 902 | // WWW Page refresh function 903 | server.on("/REFR=1", []() { // WWW page auto refresh ON 904 | #if A_REFRESH==1 905 | gwayConfig.refresh =1; 906 | writeGwayCfg(CONFIGFILE); // Save configuration to file 907 | #endif 908 | server.sendHeader("Location", String("/"), true); 909 | server.send ( 302, "text/plain", ""); 910 | }); 911 | server.on("/REFR=0", []() { // WWW page auto refresh OFF 912 | #if A_REFRESH==1 913 | gwayConfig.refresh =0; 914 | writeGwayCfg(CONFIGFILE); // Save configuration to file 915 | #endif 916 | server.sendHeader("Location", String("/"), true); 917 | server.send ( 302, "text/plain", ""); 918 | }); 919 | 920 | 921 | // Switch off/on the HOP functions 922 | server.on("/HOP=1", []() { 923 | _hop=(bool)1; 924 | server.sendHeader("Location", String("/"), true); 925 | server.send ( 302, "text/plain", ""); 926 | }); 927 | server.on("/HOP=0", []() { 928 | _hop=(bool)0; 929 | if (! _hop) { ifreq=0; freq=freqs[0]; rxLoraModem(); } 930 | server.sendHeader("Location", String("/"), true); 931 | server.send ( 302, "text/plain", ""); 932 | }); 933 | 934 | 935 | // Update the sketch. Not yet implemented 936 | server.on("/UPDATE=1", []() { 937 | #if A_OTAA==1 938 | updateOtaa(); 939 | #endif 940 | server.sendHeader("Location", String("/"), true); 941 | server.send ( 302, "text/plain", ""); 942 | }); 943 | 944 | 945 | // ----------- 946 | // This section from version 4.0.7 defines what PART of the 947 | // webpage is shown based on the buttons pressed by the user 948 | // Maybe not all information wshould be put on the screen since it 949 | // may take too much time to serve all information before a next 950 | // package interrupt arrives at tyhe gateway 951 | 952 | 953 | 954 | Serial.print(F("Web server started on port ")); 955 | Serial.println(A_SERVERPORT); 956 | return; 957 | } 958 | 959 | #endif 960 | -------------------------------------------------------------------------------- /LoRaGoDOCK-Gateway/config.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * 3 | * Description: Source code for single-channel LoRaWAN Gateway based on ESP8266 and SX1276 4 | * Version : 0.8.2 5 | * Date : 2018-11-26 6 | * Software : https://github.com/SandboxElectronics/LoRaGoDOCK-Gateway 7 | * Hardware : LoRaGo DOCK – http://sandboxelectronics.com/?product=lorago-dock-single-channel-lorawan-gateway 8 | * 9 | * Copyright (c) 2016, 2017 Maarten Westenberg 10 | * 11 | * All rights reserved. This program and the accompanying materials 12 | * are made available under the terms of the MIT License 13 | * which accompanies this distribution, and is available at 14 | * https://opensource.org/licenses/mit-license.php 15 | * 16 | *****************************************************************************************/ 17 | 18 | // This file contains a number of compile-time settings that can be set on (=1) or off (=0) 19 | // The disadvantage of compile time is minor compared to the memory gain of not having 20 | // too much code compiled and loaded on your ESP8266. 21 | // 22 | // ---------------------------------------------------------------------------------------- 23 | 24 | #define VERSION "0.8.2" 25 | 26 | // This value of DEBUG determines whether some parts of code get compiled. 27 | // Also this is the initial value of debug parameter. 28 | // The value can be changed using the admin webserver 29 | // For operational use, set initial DEBUG vaulue 0 30 | #define DEBUG 1 31 | 32 | // BAND can be defined as 868/915 in config.h file 33 | #define BAND 868 34 | 35 | // _CH is the index in the frequency array freqs[] defined in loraModem.h 36 | #define _CH 0 37 | 38 | // The spreading factor is the most important parameter to set for a single channel 39 | // gateway. It specifies the speed/datarate in which the gateway and node communicate. 40 | // As the name says, in principle the single channel gateway listens to one channel/frequency 41 | // and to one spreading factor only. 42 | // This parameter is hard-coded in the source code and is not stored in SPIFFS 43 | // NOTE: The frequency is set in the loraModem.h file and is default 868.100000 MHz. 44 | #define _SPREADING SF7 45 | 46 | // Channel Activity Detection 47 | // This function will scan for valid LoRa headers and determine the Spreading 48 | // factor accordingly. If set to 1 we will use this function which means the 49 | // 1-channel gateway will become even more versatile. If set to 0 we will use the 50 | // continuous listen mode. 51 | // Using this function means that we HAVE to use more dio pins on the RFM95/sx1276 52 | // device and also connect enable dio1 to detect this state. 53 | #define _CAD 1 54 | 55 | // Definitions for the admin webserver. 56 | // A_SERVER determines whether or not the admin webpage is included in the sketch. 57 | // Normally, leave it in! 58 | #define A_SERVER 1 // Define local WebServer only if this define is set 59 | #define A_REFRESH 1 // Will the webserver refresh or not? 60 | #define A_SERVERPORT 80 // local webserver port 61 | #define A_MAXBUFSIZE 192 // Must be larger than 128, but small enough to work 62 | 63 | // Definitions for over the air updates. At the moment we support OTA with IDE 64 | // Make sure that you have installed Python version 2.7 and have Bonjour in your network. 65 | // Bonjour is included in iTunes (which is free) and OTA is recommended to install 66 | // the firmware on your router witout having to be really close to the gateway and 67 | // connect with USB. 68 | #define A_OTA 0 69 | 70 | // Gather statistics on sensor and Wifi status 71 | // 0= No statistics 72 | // 1= Keep track of messages statistics, number determined by MAX_STAT 73 | // 2= See 1 + Keep track of messages received PER SF 74 | #define STATISTICS 2 75 | // Maximum number of statistics records gathered. 20 is a good maximum (memory intensive) 76 | #define MAX_STAT 20 77 | 78 | // Single channel gateways if they behave strict should only use one frequency 79 | // channel and one spreading factor. However, the TTN backend replies on RX2 80 | // timeslot for spreading factors SF9-SF12. 81 | // Also, the server will respond with SF12 in the RX2 timeslot. 82 | // If the 1ch gateway is working in and for nodes that ONLY transmit and receive on the set 83 | // and agreed frequency and spreading factor. make sure to set STRICT to 1. 84 | // In this case, the frequency and spreading factor for downlink messages is adapted by this 85 | // gateway 86 | // NOTE: If your node has only one frequency enabled and one SF, you must set this to 1 87 | // in order to receive downlink messages 88 | #define _STRICT_1CH 0 89 | 90 | // Allows configuration through WifiManager AP setup. Must be 0 or 1 91 | #define WIFIMANAGER 0 92 | 93 | // Define the name of the accesspoint if the gateway is in accesspoint mode (is 94 | // getting WiFi SSID and password using WiFiManager) 95 | #define AP_NAME "LoRaGo" 96 | #define AP_PASSWD "DOCK" 97 | 98 | 99 | // Defines whether the gateway will also report sensor/status value on MQTT 100 | // after all, a gateway can be a node to the system as well 101 | // Set its LoRa address and key below in this file 102 | // See spec. para 4.3.2 103 | #define GATEWAYNODE 1 104 | #define _CHECK_MIC 0 105 | 106 | 107 | // Define whether we want to manage the gateway over UDP (next to management 108 | // thru webinterface). 109 | // This will allow us to send messages over the UDP connection to manage the gateway 110 | // and its parameters. Sometimes the gateway is not accesible from remote, 111 | // in this case we would allow it to use the SERVER UDP connection to receive 112 | // messages as well. 113 | // NOTE: Be aware that these messages are NOT LoRa and NOT LoRa Gateway spec compliant. 114 | // However that should not interfere with regular gateway operation but instead offer 115 | // functions to set/reset certain parameters from remote. 116 | #define GATEWAYMGT 0 117 | 118 | // Name of he configfile in SPIFFs filesystem 119 | // In this file we store the configuration and other relevant info that should 120 | // survive a reboot of the gateway 121 | #define CONFIGFILE "/gwayConfig.txt" 122 | 123 | // Set the Server Settings (IMPORTANT) 124 | #define _LOCUDPPORT 1700 // UDP port of gateway! Often 1700 or 1701 is used for upstream comms 125 | 126 | // Timing 127 | #define _PULL_INTERVAL 30 // PULL_DATA messages to server to get downstream 128 | #define _STAT_INTERVAL 120 // Send a 'stat' message to server 129 | #define _NTP_INTERVAL 3600 // How often doe we want time NTP synchronization 130 | #define _WWW_INTERVAL 60 // Number of seconds before we refresh the web page 131 | 132 | // MQTT definitions, these settings should be standard for TTN 133 | // and need not changing 134 | #define _TTNPORT 1700 // Standard port for TTN 135 | #define _TTNSERVER "router.eu.thethings.network" 136 | 137 | // If you have a second back-end server defined such as Semtech or loriot.io 138 | // your can define _THINGPORT and _THINGSERVER with your own value. 139 | // If not, make sure that you do not defined these. 140 | // Port is UDP port in this program 141 | 142 | //#define _THINGPORT 1700 143 | //#define _THINGSERVER "yourserver.com" // Server URL of the LoRa-udp.js handler 144 | 145 | // Gateway Ident definitions 146 | #define _DESCRIPTION "LoRaGo DOCK – Single-Channel LoRaWAN Gateway" 147 | #define _EMAIL "info@sandboxelectronics.com" 148 | #define _PLATFORM "LoRaGo DOCK" 149 | #define _LAT 52.00 150 | #define _LON 5.800 151 | #define _ALT 14 152 | 153 | // ntp 154 | #define NTP_TIMESERVER "nl.pool.ntp.org" // Country and region specific 155 | #define NTP_TIMEZONES 1 // How far is our Timezone from UTC (excl daylight saving/summer time) 156 | #define SECS_PER_HOUR 3600 157 | #define NTP_INTR 0 // Do NTP processing with interrupts or in loop(); 158 | 159 | #if GATEWAYNODE==1 160 | #define _DEVADDR { 0x26, 0x01, 0x15, 0x3D } 161 | #define _APPSKEY { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } 162 | #define _NWKSKEY { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } 163 | #define _SENSOR_INTERVAL 300 164 | #endif 165 | 166 | // Define the correct radio type that you are using 167 | #define CFG_sx1276_radio 168 | //#define CFG_sx1272_radio 169 | 170 | // Serial Port speed 171 | #define _BAUDRATE 115200 // Works for debug messages to serial momitor 172 | 173 | // if OLED Display is connected to i2c 174 | #define OLED 0 // Make define 1 on line if you have an OLED display connected 175 | #if OLED==1 176 | #define OLED_SCL 5 // GPIO5 / D1 177 | #define OLED_SDA 4 // GPIO4 / D2 178 | #endif 179 | 180 | 181 | // Wifi definitions 182 | // WPA is an array with SSID and password records. Set WPA size to number of entries in array 183 | // When using the WiFiManager, we will overwrite the first entry with the 184 | // accesspoint we last connected to with WifiManager 185 | // NOTE: Structure needs at least one (empty) entry. 186 | // So WPASIZE must be >= 1 187 | struct wpas { 188 | char login[32]; // Maximum Buffer Size (and allocated memory) 189 | char passw[64]; 190 | }; 191 | 192 | // Please fill in at least ONE SSID and password from your own WiFI network 193 | // below. This is needed to get the gateway working 194 | // Note: DO NOT use the first and the last line of the stucture, these should be empty strings and 195 | // the first line in te struct is reserved for WifiManager. 196 | // 197 | wpas wpa[] = { 198 | { "", "" }, // Reserved for WiFi Manager 199 | { "your_router_1", "1st_password" }, 200 | { "", ""} // spare line 201 | }; 202 | 203 | // For asserting and testing the following defines are used. 204 | // 205 | #if !defined(CFG_noassert) 206 | #define ASSERT(cond) if(!(cond)) gway_failed(__FILE__, __LINE__) 207 | #else 208 | #define ASSERT(cond) /**/ 209 | #endif 210 | -------------------------------------------------------------------------------- /LoRaGoDOCK-Gateway/loraFiles.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * 3 | * Description: Source code for single-channel LoRaWAN Gateway based on ESP8266 and SX1276 4 | * Version : 0.8.2 5 | * Date : 2018-11-26 6 | * Software : https://github.com/SandboxElectronics/LoRaGoDOCK-Gateway 7 | * Hardware : LoRaGo DOCK – http://sandboxelectronics.com/?product=lorago-dock-single-channel-lorawan-gateway 8 | * 9 | * Copyright (c) 2016, 2017 Maarten Westenberg 10 | * 11 | * All rights reserved. This program and the accompanying materials 12 | * are made available under the terms of the MIT License 13 | * which accompanies this distribution, and is available at 14 | * https://opensource.org/licenses/mit-license.php 15 | * 16 | *****************************************************************************************/ 17 | 18 | // This file contains a number of compile-time settings that can be set on (=1) or off (=0) 19 | // The disadvantage of compile time is minor compared to the memory gain of not having 20 | // too much code compiled and loaded on your ESP8266. 21 | // 22 | // ---------------------------------------------------------------------------------------- 23 | 24 | 25 | 26 | // Definition of the configuration record that is read at startup and written 27 | // when settings are changed. 28 | struct espGwayConfig { 29 | uint16_t fcnt; // =0 as init value XXX Could be 32 bit in size 30 | uint16_t boots; // Number of restarts made by the gateway after reset 31 | uint16_t resets; // Number of statistics resets 32 | uint16_t views; // Number of sendWebPage() calls 33 | uint16_t wifis; // Number of WiFi Setups 34 | uint16_t reents; // Number of re-entrant interrupt handler calls 35 | uint16_t ntpErr; // Number of UTP requests that failed 36 | uint16_t ntps; 37 | 38 | uint8_t ch; // index to freqs array, freqs[ifreq]=868100000 default 39 | uint8_t sf; // range from SF7 to SF12 40 | uint8_t debug; // range 0 to 4 41 | 42 | bool cad; // is CAD enabled? 43 | bool hop; // Is HOP enabled (Note: SHould be disabled) 44 | bool node; // Is gateway node enabled 45 | bool refresh; // Is WWW browser refresh enabled 46 | 47 | String ssid; // SSID of the last connected WiFi Network 48 | String pass; // Password 49 | } gwayConfig; 50 | 51 | 52 | -------------------------------------------------------------------------------- /LoRaGoDOCK-Gateway/loraModem.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************************** 2 | * 3 | * Description: Source code for single-channel LoRaWAN Gateway based on ESP8266 and SX1276 4 | * Version : 0.8.2 5 | * Date : 2018-11-26 6 | * Software : https://github.com/SandboxElectronics/LoRaGoDOCK-Gateway 7 | * Hardware : LoRaGo DOCK – http://sandboxelectronics.com/?product=lorago-dock-single-channel-lorawan-gateway 8 | * 9 | * Copyright (c) 2016, 2017 Maarten Westenberg 10 | * 11 | * All rights reserved. This program and the accompanying materials 12 | * are made available under the terms of the MIT License 13 | * which accompanies this distribution, and is available at 14 | * https://opensource.org/licenses/mit-license.php 15 | * 16 | *****************************************************************************************/ 17 | 18 | // This file contains a number of compile-time settings and declaations that are 19 | // specific to the LoRa rfm95, sx1276, sx1272 radio of the gateway. 20 | // 21 | // 22 | // ------------------------------------------------------------------------------------ 23 | 24 | // Our code should correct the server timing 25 | long txDelay= 0x00; // delay time on top of server TMST 26 | #define SPIFREQ 10000000 27 | #define SPISPEED 10000000 // was 50000/50KHz < 10MHz 28 | 29 | // Frequencies 30 | // Set center frequency. If in doubt, choose the first one, comment all others 31 | // Each "real" gateway should support the first 3 frequencies according to LoRaWAN spec. 32 | 33 | #if BAND == 868 34 | int freqs[] = { 35 | 868100000, // Channel 0, 868.1 MHz primary 36 | 868300000, // Channel 1, 868.3 MHz mandatory 37 | 868500000, // Channel 2, 868.5 MHz mandatory 38 | 867100000, // Channel 3, 867.1 MHz optional 39 | 867300000, // Channel 4, 867.3 MHz optional 40 | 867500000, // Channel 5, 867.5 MHz optional 41 | 867700000, // Channel 6, 867.7 MHz optional 42 | 867900000, // Channel 7, 867.9 MHz optional 43 | 868800000, 44 | 869525000 // Channel, for responses gateway (10%) 45 | // TTN defines an additional channel at 869.525Mhz using SF9 for class B. Not used 46 | }; 47 | #elif BAND == 915 48 | int freqs[] = { 49 | 903900000, // Channel 0, 903.9 MHz 50 | 904100000, // Channel 1, 904.1 MHz 51 | 904300000, // Channel 2, 904.3 MHz 52 | 904500000, // Channel 3, 904.5 MHz 53 | 904700000, // Channel 4, 904.7 MHz 54 | 904900000, // Channel 5, 904.9 MHz 55 | 905100000, // Channel 6, 905.1 MHz 56 | 905300000 // Channel 7, 905.3 MHz 57 | }; 58 | #else 59 | #error "BAND has to be defined as 868/915 in config.h file" 60 | #endif 61 | 62 | uint8_t ifreq = 0; // Channel Index 63 | uint32_t freq = freqs[ifreq]; 64 | 65 | // Set the structure for spreading factor 66 | enum sf_t { SF6=6, SF7, SF8, SF9, SF10, SF11, SF12 }; 67 | 68 | // The state of the receiver. See Semtech Datasheet (rev 4, March 2015) page 43 69 | // The _state is of the enum type (and should be cast when used as a number) 70 | enum state_t { S_INIT=0, S_SCAN, S_CAD, S_RX, S_RXDONE, S_TX }; 71 | state_t _state; 72 | 73 | // rssi is measured at specific moments and reported on others 74 | // so we need to store the current value we like to work with 75 | uint8_t _rssi; 76 | 77 | // In order to make the CAD behaviour dynamic we set a variable 78 | // when the CAD functions are defined. Value of 3 is minimum frequencies a 79 | // gateway should support to be fully LoRa compliant. 80 | #define NUM_HOPS 3 81 | 82 | bool _cad= (bool) _CAD; // Set to true for Channel Activity Detection, only when dio1 connected 83 | bool _hop=false; // experimental; frequency hopping. Only use when dio2 connected 84 | bool inHop=false; 85 | unsigned long nowTime=0; 86 | unsigned long hopTime=0; 87 | 88 | // Define whether we shoudl include protection for the interrupt() 89 | // routine to make it re-entrant (a second interrupt while still in first 90 | // invocation of the interrupt routine 91 | // Assuming we need to protect against re entrance of interrupt handler 92 | // bute we need not necessarily enable full re-entrance, 1 is a good value! 93 | #define REENTRANT 2 94 | #if REENTRANT>=1 95 | // Declare a variable that should keep track of us handling an interrupt. 96 | volatile bool inIntr = 0; 97 | #endif 98 | 99 | // Definition of the GPIO pins used by the Gateway 100 | struct pins { 101 | uint8_t dio0 = 15; // GPIO15 - For the Hallard board shared between DIO0/DIO1/DIO2 102 | uint8_t dio1 = 15; // GPIO15 - Used for CAD, may or not be shared with DIO0 103 | uint8_t dio2 = 15; // GPIO15 - Used for frequency hopping, don't care 104 | uint8_t ss = 16; // GPIO16 - Slave Select pin 105 | uint8_t rst = 0; // GPIO0 - Reset pin not used 106 | } pins; 107 | 108 | // STATR contains the statictis that are kept by message. 109 | // Ech time a message is received or sent the statistics are updated. 110 | // In case STATISTICS==1 we define the last MAX_STAT messages as statistics 111 | #if STATISTICS >= 1 112 | struct stat_t { 113 | unsigned long tmst; // Time since 1970 114 | unsigned long node; // 4-byte DEVaddr (the only one known to gateway) 115 | uint8_t ch; // Channel index to freqs array 116 | uint8_t sf; 117 | int8_t rssi; // XXX Can be < -128 118 | int8_t prssi; // XXX Can be < -128 119 | } stat_t; 120 | // History of received uplink messages from nodes 121 | struct stat_t statr[MAX_STAT]; 122 | 123 | // STATC contains the statistic that are gateway related and not per 124 | // message. Example: Number of messages received on SF7 or number of (re) boots 125 | // So where statr contains the statistics gathered per packet the statc 126 | // contains general statics of the node 127 | #if STATISTICS >= 2 // Only if we explicitely set it higher 128 | struct stat_c { 129 | unsigned long sf7; // Spreading factor 7 130 | unsigned long sf8; // Spreading factor 8 131 | unsigned long sf9; // Spreading factor 9 132 | unsigned long sf10; // Spreading factor 10 133 | unsigned long sf11; // Spreading factor 11 134 | unsigned long sf12; // Spreading factor 12 135 | 136 | uint16_t boots; // Number of boots 137 | uint16_t resets; 138 | } stat_c; 139 | struct stat_c statc; 140 | #endif 141 | #endif 142 | 143 | // Interrupt variables 144 | volatile uint8_t flags; 145 | volatile uint8_t mask; 146 | 147 | // Do not change these setting for RSSI detection. They are used for CAD 148 | // Given the correction factor of 157, we can get to -118dB with this rating 149 | // XXX 40 works 150 | #define RSSI_LIMIT 39 151 | #define RSSI_LIMIT_DOWN 34 // Was 34 152 | // How long to wait in LoRa mode before using the RSSSI value. 153 | // XXX 275 works well in old CAD mode 154 | #define RSSI_WAIT 250 155 | #define RSSI_WAIT_DOWN 225 156 | 157 | // ============================================================================ 158 | // Set all definitions for Gateway 159 | // ============================================================================ 160 | // Register definitions. These are the addresses of the TFM95, SX1276 that we 161 | // need to set in the program. 162 | 163 | #define REG_FIFO 0x00 164 | #define REG_OPMODE 0x01 165 | // Register 2 to 5 are unused for LoRa 166 | #define REG_FRF_MSB 0x06 167 | #define REG_FRF_MID 0x07 168 | #define REG_FRF_LSB 0x08 169 | #define REG_PAC 0x09 170 | #define REG_PARAMP 0x0A 171 | #define REG_LNA 0x0C 172 | #define REG_FIFO_ADDR_PTR 0x0D 173 | #define REG_FIFO_TX_BASE_AD 0x0E 174 | #define REG_FIFO_RX_BASE_AD 0x0F 175 | 176 | #define REG_FIFO_RX_CURRENT_ADDR 0x10 177 | #define REG_IRQ_FLAGS_MASK 0x11 178 | #define REG_IRQ_FLAGS 0x12 179 | #define REG_RX_NB_BYTES 0x13 180 | #define REG_PKT_SNR_VALUE 0x19 181 | #define REG_PKT_RSSI 0x1A // latest package 182 | #define REG_RSSI 0x1B // Current RSSI, section 6.4, or 5.5.5 183 | #define REG_HOP_CHANNEL 0x1C 184 | #define REG_MODEM_CONFIG1 0x1D 185 | #define REG_MODEM_CONFIG2 0x1E 186 | #define REG_SYMB_TIMEOUT_LSB 0x1F 187 | 188 | #define REG_PAYLOAD_LENGTH 0x22 189 | #define REG_MAX_PAYLOAD_LENGTH 0x23 190 | #define REG_HOP_PERIOD 0x24 191 | #define REG_MODEM_CONFIG3 0x26 192 | #define REG_RSSI_WIDEBAND 0x2C 193 | 194 | #define REG_INVERTIQ 0x33 195 | #define REG_DET_TRESH 0x37 // SF6 196 | #define REG_SYNC_WORD 0x39 197 | #define REG_TEMP 0x3C 198 | 199 | #define REG_DIO_MAPPING_1 0x40 200 | #define REG_DIO_MAPPING_2 0x41 201 | #define REG_VERSION 0x42 202 | 203 | #define REG_PADAC 0x5A 204 | #define REG_PADAC_SX1272 0x5A 205 | #define REG_PADAC_SX1276 0x4D 206 | 207 | // ---------------------------------------- 208 | // Used by REG_PAYLOAD_LENGTH to set receive patyload length 209 | #define PAYLOAD_LENGTH 0x40 210 | 211 | // ---------------------------------------- 212 | // opModes 213 | #define SX72_MODE_SLEEP 0x80 214 | #define SX72_MODE_STANDBY 0x81 215 | #define SX72_MODE_FSTX 0x82 216 | #define SX72_MODE_TX 0x83 // 0x80 | 0x03 217 | #define SX72_MODE_RX_CONTINUOS 0x85 218 | 219 | // ---------------------------------------- 220 | // LMIC Constants for radio registers 221 | #define OPMODE_LORA 0x80 222 | #define OPMODE_MASK 0x07 223 | #define OPMODE_SLEEP 0x00 224 | #define OPMODE_STANDBY 0x01 225 | #define OPMODE_FSTX 0x02 226 | #define OPMODE_TX 0x03 227 | #define OPMODE_FSRX 0x04 228 | #define OPMODE_RX 0x05 229 | #define OPMODE_RX_SINGLE 0x06 230 | #define OPMODE_CAD 0x07 231 | 232 | 233 | 234 | // ---------------------------------------- 235 | // LOW NOISE AMPLIFIER 236 | 237 | #define LNA_MAX_GAIN 0x23 238 | #define LNA_OFF_GAIN 0x00 239 | #define LNA_LOW_GAIN 0x20 240 | 241 | // CONF REG 242 | #define REG1 0x0A 243 | #define REG2 0x84 244 | 245 | // ---------------------------------------- 246 | // MC1 sx1276 RegModemConfig1 247 | #define SX1276_MC1_BW_125 0x70 248 | #define SX1276_MC1_BW_250 0x80 249 | #define SX1276_MC1_BW_500 0x90 250 | #define SX1276_MC1_CR_4_5 0x02 251 | #define SX1276_MC1_CR_4_6 0x04 252 | #define SX1276_MC1_CR_4_7 0x06 253 | #define SX1276_MC1_CR_4_8 0x08 254 | #define SX1276_MC1_IMPLICIT_HEADER_MODE_ON 0x01 255 | 256 | #define SX72_MC1_LOW_DATA_RATE_OPTIMIZE 0x01 // mandated for SF11 and SF12 257 | 258 | // ---------------------------------------- 259 | // MC2 definitions 260 | #define SX72_MC2_FSK 0x00 261 | #define SX72_MC2_SF7 0x70 // SF7 == 0x07, so (SF7<<4) == SX7_MC2_SF7 262 | #define SX72_MC2_SF8 0x80 263 | #define SX72_MC2_SF9 0x90 264 | #define SX72_MC2_SF10 0xA0 265 | #define SX72_MC2_SF11 0xB0 266 | #define SX72_MC2_SF12 0xC0 267 | 268 | // ---------------------------------------- 269 | // MC3 270 | #define SX1276_MC3_LOW_DATA_RATE_OPTIMIZE 0x08 271 | #define SX1276_MC3_AGCAUTO 0x04 272 | 273 | // ---------------------------------------- 274 | // FRF 275 | #define FRF_MSB 0xD9 // 868.1 Mhz 276 | #define FRF_MID 0x06 277 | #define FRF_LSB 0x66 278 | 279 | // ---------------------------------------- 280 | // DIO function mappings D0D1D2D3 281 | #define MAP_DIO0_LORA_RXDONE 0x00 // 00------ bit 7 and 6 282 | #define MAP_DIO0_LORA_TXDONE 0x40 // 01------ 283 | #define MAP_DIO0_LORA_CADDONE 0x80 // 10------ 284 | #define MAP_DIO0_LORA_NOP 0xC0 // 11------ 285 | 286 | #define MAP_DIO1_LORA_RXTOUT 0x00 // --00---- bit 5 and 4 287 | #define MAP_DIO1_LORA_FCC 0x10 // --01---- 288 | #define MAP_DIO1_LORA_CADDETECT 0x20 // --10---- 289 | #define MAP_DIO1_LORA_NOP 0x30 // --11---- 290 | 291 | #define MAP_DIO2_LORA_FCC0 0x00 // ----00-- bit 3 and 2 292 | #define MAP_DIO2_LORA_FCC1 0x04 // ----01-- bit 3 and 2 293 | #define MAP_DIO2_LORA_FCC2 0x08 // ----10-- bit 3 and 2 294 | #define MAP_DIO2_LORA_NOP 0x0C // ----11-- bit 3 and 2 295 | 296 | #define MAP_DIO3_LORA_CADDONE 0x00 // ------00 bit 1 and 0 297 | #define MAP_DIO3_LORA_NOP 0x03 // ------11 298 | 299 | #define MAP_DIO0_FSK_READY 0x00 // 00------ (packet sent / payload ready) 300 | #define MAP_DIO1_FSK_NOP 0x30 // --11---- 301 | #define MAP_DIO2_FSK_TXNOP 0x04 // ----01-- 302 | #define MAP_DIO2_FSK_TIMEOUT 0x08 // ----10-- 303 | 304 | // ---------------------------------------- 305 | // Bits masking the corresponding IRQs from the radio 306 | #define IRQ_LORA_RXTOUT_MASK 0x80 307 | #define IRQ_LORA_RXDONE_MASK 0x40 308 | #define IRQ_LORA_CRCERR_MASK 0x20 309 | #define IRQ_LORA_HEADER_MASK 0x10 310 | #define IRQ_LORA_TXDONE_MASK 0x08 311 | #define IRQ_LORA_CDDONE_MASK 0x04 312 | #define IRQ_LORA_FHSSCH_MASK 0x02 313 | #define IRQ_LORA_CDDETD_MASK 0x01 314 | 315 | 316 | // ---------------------------------------- 317 | // Definitions for UDP message arriving from server 318 | #define PROTOCOL_VERSION 0x01 319 | #define PKT_PUSH_DATA 0x00 320 | #define PKT_PUSH_ACK 0x01 321 | #define PKT_PULL_DATA 0x02 322 | #define PKT_PULL_RESP 0x03 323 | #define PKT_PULL_ACK 0x04 324 | #define PKT_TX_ACK 0x05 325 | 326 | #define MGT_RESET 0x15 // Not a LoRa Gateway Spec message 327 | #define MGT_SET_SF 0x16 328 | #define MGT_SET_FREQ 0x17 329 | 330 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LoRaGo DOCK - Single Channel LoRaWAN Gateway based on ESP8266 and SX1276 2 | 3 | Version : 0.8.2 4 | Date : 2018-11-26 5 | Software: https://github.com/SandboxElectronics/LoRaGoDOCK-Gateway 6 | Hardware: LoRaGo DOCK – http://sandboxelectronics.com/?product=lorago-dock-single-channel-lorawan-gateway 7 | 8 | Originally designed by Maarten Westenberg (mw12554@hotmail.com) 9 | Adopted by Sandbox Electronics (http://sandboxelectronics.com) 10 | 11 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | 13 | # Description 14 | 15 | This repository contains a proof-of-concept implementation of a single channel LoRaWAN gateway for the ESP8266. 16 | The software implements a standard LoRa gateway with the following exceptions on changes: 17 | 18 | - This LoRa gateway is not a full gateway but it implements just a one-channel/one frequency gateway. 19 | The minimum amount of frequencies supported by a full gateway is 3, most support 9 or more frequencies. 20 | This software started as a proof-of-concept to prove that a single low-cost RRFM95 chip which was present in almost every 21 | LoRa node in Europe could be used as a cheap alternative to the far more expensive full gateways that were 22 | making use of the SX1301 chip. 23 | 24 | - As the software of this gateway will often be used during the development phase of a project or in demo situations, 25 | the software is flexible and can be easily configured according to environment or customer requirements. 26 | There are two ways of interacting with the software: 1. Modifying the config.h file at compile time allows the 27 | administrator to set almost all parameters. 2. Using the web interface (http://) will allow 28 | administrators to set and reset several of the parameters at runtime. 29 | 30 | 31 | # Getting Started 32 | 33 | It is recommended to compile and start the single channel gateway with as little modificatons as possible. This means that you should use the default settings in the configuration files as much as possible and only change the SSID/Password for your WiFi setup and the pins of your ESP8266 that you use in loraModem.h. This section describes the minimum of configuration necessary to get a working gateway which than can be configured further using the webpage. 34 | 35 | 1. Unpack the source code including the libraries in a separate folder. 36 | 1. Install depending libraries. Check "Dependencies" section below. 37 | 1. Install ESP8266 board from with Boards Manager if necessary. 38 | 1. Select Board and related settings: 39 | - Board : "NodeMCU 1.0 (ESP-12E Module)" 40 | - CPU Frequency: "80 MHz" 41 | 1. Connect the gateway to a serial port of your computer, and configure that port in the IDE. 42 | 1. Edit the config.h file and adapt the "wpas" structure. Make sure that the first line of this structure remains empty and put the SSID and Password of your router on the second line of the array. 43 | 1. Compile the code and download the executable over USB to the gateway. If all is right, you should see the gateway starting up on the Serial Monitor (115200). 44 | 1. Note the IP address that the device receives from your router. Use that IP address in a browser on your computer to connect to the gateway with the browser. 45 | 46 | Now your gateway should be running. Use the webpage to set "debug" to 1 and you should be able to see packets coming in on the Serial monitor. 47 | 48 | 49 | # Configuration 50 | 51 | There are two ways of changing the configuration of the single channel gateway: 52 | 53 | 1. Changing the config.h file at compile-time 54 | 2. Run the http:// web interface to change settings (limited). 55 | 56 | There are some parameters that are hard-coded in the source code. In other words, these settings cannot be changed from the web interface: 57 | 58 | 1. BAND - The band the gateway is operating in. Currently Band 868 and 915 are supported. 59 | 2. _SPREADING - The spreading factor the gateway is receiving. 60 | 3. _CH - The channel number (which determines the frequency) the gateway is listening on. 61 | 4. _CAD - Whether Channel Activity Detection is enabled in order to receive multiple spreading factor at the same time. 62 | 63 | 64 | ## Editing the config.h file 65 | 66 | The config.h file contains all the user configurable settings. All have their definitions set through #define statements. In general, setting a #define to 1 will enable the function and setting it to 0 will disable it. 67 | 68 | Also, some settings can be initialized by setting their value with a #define but can be changed at runtime in the web interface. For some settings, disabling the function with a #define will remove the function from the webserver as well. 69 | 70 | NOTE regarding memory usage: The ESP8266 has an enormous amount of memory available for program space and SPIFFS file system. However the memory available for heap and variables is limited to about 80K bytes (For the ESP-32 this is higher). The user is advised to turn off functions not used in order to save on memory usage. If the heap drops below 18 KBytes some functions may not behave as expected (in extreme case the program may crash). 71 | 72 | 73 | ### Debug 74 | 75 | The user can set the initial value of the DEBUG parameter. 76 | Setting this parameter will also determine some settings of the webserver. 77 | 78 | `#define DEBUG 1` 79 | 80 | 81 | ### Setting Spreading Factor 82 | 83 | Set the `_SPREADING` factor to the desired SF7, SF8 - SF12 value. 84 | Please note that this value is closely related to teh value used for `_CAD`. 85 | If `_CAD` is enabled, the value of `_SPREADING` is not used by the gateway as it has all spreading factors enabled. 86 | 87 | `#define _SPREADING SF7` 88 | 89 | Please note that the default frequency used is 868.1 MHz which can be changed in the loraModem.h file. 90 | The user is advised NOT to change this setting and only use the default 868.1 MHz frequency. 91 | 92 | 93 | ### Channel Activity Detection 94 | 95 | Channel Activity Detection (CAD) is a function of the LoRa RFM95 chip to detect incoming messages (activity). 96 | These incoming messages might arrive on any of the well know spreading factors SF7-SF12. 97 | By enabling CAD, the gateway can receive messages of any of the spreading factors. 98 | 99 | Actually it is used in normal operation to tell the receiver that another signal is using the 100 | channel already. 101 | 102 | The CAD functionality comes at a (little) price: The chip will not be able to receive very weak signals as 103 | the CAD function will use the RSSI register setting of the chip to determine whether or not it received a 104 | signal (or just noise). As a result, very weak signals are not received which means that the range of the 105 | gateway will be reduced in CAD mode. 106 | 107 | `#define _CAD 1` 108 | 109 | 110 | ### Over the Air Updates (OTA) 111 | 112 | As from version 4.0.6 the gateway allows over the air updated if the setting A_OTA is on. 113 | The over the air software requires once setting of the 4.0.6 version over USB to the gateway, 114 | after which the software is (default) enabled for use. 115 | 116 | The first release only supports OTA function using the IDE which in practice means the IDE has to 117 | be on the same network segment as the gateway. 118 | 119 | Note: You have to use Bonjour software (Apple) on your network somewhere. A version is available 120 | for most platforms (shipped with iTunes for windows for example). The Bonjour software enables the 121 | gateway to use mDNS to resolve the gateway ID set by OTA after which download ports show up in the IDE. 122 | 123 | Todo: The OTA software has not (yet) been tested in conjunction with the WiFiManager software. 124 | 125 | `#define A_OTA 1` 126 | 127 | 128 | ### Enable Webserver 129 | 130 | This setting enables the webserver. Although the webserver itself takes a lot of memory, it greatly helps 131 | to configure the gateway at run-time and inspects its behavior. It also provides statistics of last messages received. 132 | The A_REFRESH parameter defines whether the webserver should renew every X seconds. 133 | 134 | `#define A_SERVER 1 // Define local WebServer only if this define is set` 135 | `#define A_REFRESH 0 // Will the webserver refresh or not?` 136 | `#define A_SERVERPORT 80 // local webserver port` 137 | `#define A_MAXBUFSIZE 192 // Must be larger than 128, but small enough to work` 138 | 139 | The `A_REFRESH` parameter determines the refresh frequency of the webserver. 140 | 141 | ### Strict LoRa behaviour 142 | 143 | In order to have the gateway send downlink messages on the pre-set spreading factor and on the default frequency, 144 | you have to set the `_STRICT_1Ch` parameter to 1. Note that when it is not set to 1, the gateway will respond to 145 | downlink requests with the frequency and spreading factor set by the backend server. And at the moment TTN 146 | responds to downlink messages for SF9-SF12 in the RX2 timeslot and with frequency 869.525MHz and on SF12 147 | (according to the LoRa standard when sending in the RX2 timeslot). 148 | 149 | `#define _STRICT_1CH 0` 150 | 151 | You are advised not to change the default setting of this parameter. 152 | 153 | ### Enable OLED panel 154 | 155 | By setting the OLED you configure the system to work with OLED panels over I2C. 156 | Some panels work by both SPI and I2C where I2c is slower. However, since SPI is use for RFM95 transceiver 157 | communication you are strongly discouraged to use one of these as they will not work with this software. 158 | Instead choose a OLED solution that works over I2C. 159 | 160 | `#define OLED 1` 161 | 162 | Setting the I2C SDA/SCL pins is done in the config.h file right after the #define of OLED. 163 | 164 | ### Setting TTN server 165 | 166 | The gateway allows to connect to 2 servers at the same time (as most LoRa gateways do BTW). 167 | You have to connect to at least one standard LoRa router, in case you use The Things Network (TTN) 168 | than make sure that you set: 169 | 170 | `#define _TTNSERVER "router.eu.thethings.network"` 171 | `#define _TTNPORT 1700` 172 | 173 | In case you setup your own server, you can specify as follows using your own router URL and your own port: 174 | 175 | `#define _THINGSERVER "your_server.com" // Server URL of the LoRa udp.js server program` 176 | `#define _THINGPORT 1701 // Your UDP server should listen to this port` 177 | 178 | 179 | ### Gateway Identity 180 | Set the identity parameters for your gateway: 181 | 182 | `#define _DESCRIPTION "LoRaGo DOCK – Single-Channel LoRaWAN Gateway"` 183 | `#define _EMAIL "info@sandboxelectronics.com"` 184 | `#define _PLATFORM "LoRaGo DOCK"` 185 | `#define _LAT 52.00` 186 | `#define _LON 5.800` 187 | `#define _ALT 14` 188 | 189 | 190 | ### Using the gateway as a sensor node 191 | 192 | It is possible to use the gateway as a node. This way, local/internal sensor values are reported. 193 | This is a cpu and memory intensive function as making a sensor message involves EAS and CMAC functions. 194 | 195 | `#define GATEWAYNODE 0` 196 | 197 | Further below in the configuration file, it is possible to set the address and other LoRa information of the gateway node. 198 | 199 | `#if GATEWAYNODE==1` 200 | `#define _DEVADDR { 0x26, 0x01, 0x15, 0x3D }` 201 | `#define _APPSKEY { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }` 202 | `#define _NWKSKEY { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }` 203 | `#define _SENSOR_INTERVAL 300` 204 | `#endif` 205 | 206 | 207 | ### Connect to WiFi with WiFiManager 208 | 209 | The easiest way to configure the Gateway on WiFi is by using the WiFimanager function. This function works out of the box. 210 | WiFiManager will put the gateway in accesspoint mode so that you can connect to it as a WiFi accesspoint. 211 | 212 | `#define WIFIMANAGER 0` 213 | 214 | If Wifi Manager is enabled, make sure to define the name of the accesspoint if the gateway is in accesspoint 215 | mode and the password. 216 | 217 | `#define AP_NAME "LoRaGo"` 218 | `#define AP_PASSWD "DOCK"` 219 | 220 | The standard access point name used by the gateway is "LoRaGo" and its password is "DOCK". 221 | After binding to the access point with your mobile phone or computer, go to http://192.168.4.1 in a browser and tell the gateway to which WiFi network you want it to connect, and specify the password. 222 | 223 | The gateway will then reset and bind to the given network. If all goes well you are now set and the ESP8266 will remember the network that it must connect to. NOTE: As long as the accesspoint that the gateway is bound to is present, the gateway will not any longer work with the wpa list of known access points. 224 | If necessary, you can delete the current access point in the webserver and power cycle the gateway to force it to read the wpa array again. 225 | 226 | 227 | ### Other Settings 228 | 229 | - `static char *wpa[WPASIZE][2]` contains the array of known WiFi access points the Gateway will connect to. Make sure that the dimensions of the array are correctly defined in the WPASIZE settings. 230 | Note: When the WiFiManager software is enabled (it is by default) there must at least be one entry in the wpa file, wpa[0] is used for storing WiFiManager information. 231 | 232 | 233 | ## Webserver 234 | 235 | The built-in webserver can be used to display status and debugging information. Also the webserver allows the user to change certain settings at run-time such as the debug level. It can be accessed with the following URL: http://:80 where is the IP given by the router to the ESP8266 at startup. It is probably something like 192.168.1.XX. The webserver shows various configuration settings as well as providing functions to set parameters. 236 | 237 | 238 | # Dependencies 239 | 240 | The software is dependent on several pieces of software, the Arduino IDE for ESP8266 being the most important. Several other libraries are also used by this program, make sure you install those libraries with the IDE: 241 | 242 | - gBase64 library, The gBase library is actually a base64 library made by Adam Rudd (url=https://github.com/adamvr/arduino-base64). I changed the name because I had another base64 library installed on my system and they did not coexist well. 243 | - Time library (http://playground.arduino.cc/code/time) 244 | - Arduino JSON; Needed to decode downstream messages 245 | - SimpleTimer; Not yet used, but reserved for interrupt and timing 246 | - WiFiManager 247 | - ESP8266 Web Server 248 | - Streaming library, used in the wwwServer part 249 | - AES library (taken from ideetron.nl) for downstream messages 250 | - Time 251 | 252 | For convenience, the libraries are also found in this github repository in the libraries directory. Please note that they are NOT part of the ESP 1channel gateway and may have their own licensing. However, these libraries are not part of the single-channel Gateway software. 253 | 254 | # License 255 | 256 | The source files of the gateway sketch in this repository is made available under the MIT license. The libraries included in this repository are included for convenience only and all have their own license, and are not part of the gateway source code. 257 | -------------------------------------------------------------------------------- /libraries_dependency.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandboxElectronics/LoRaGoDOCK-Gateway/37988a56bbf545be4257b3819392fede79b0d3ee/libraries_dependency.zip --------------------------------------------------------------------------------