// 1-channel LoRa Gateway for ESP8266 // Copyright (c) 2016-2020 Maarten Westenberg version for ESP8266 // // based on work done by Thomas Telkamp for Raspberry PI 1ch gateway // and many others. // // All rights reserved. This program and the accompanying materials // are made available under the terms of the MIT License // which accompanies this distribution, and is available at // https://opensource.org/licenses/mit-license.php // // NO WARRANTY OF ANY KIND IS PROVIDED // // Author: Maarten Westenberg (mw12554@hotmail.com) // // This file contains the utilities for time and other functions // ======================================================================================== // ==================== STRING STRING STRING ============================================== // -------------------------------------------------------------------------------- // PRINT IP // Output the 4-byte IP address for easy printing. // As this function is also used by _otaServer.ino do not put in #define // Parameters: // ipa: The Ip Address (input) // sep: Separator character (input) // response: The response string (output) // Return: // // -------------------------------------------------------------------------------- void printIP(IPAddress ipa, const char sep, String & response) { response+=(String)ipa[0]; response+=sep; response+=(String)ipa[1]; response+=sep; response+=(String)ipa[2]; response+=sep; response+=(String)ipa[3]; } // ---------------------------------------------------------------------------------------- // Fill a HEXadecimal String from a 4-byte char array (uint32_t) // // ---------------------------------------------------------------------------------------- void printHex(uint32_t hexa, const char sep, String & response) { # if _MONITOR>=1 if ((debug>=0) && (hexa==0)) { mPrint("printHex:: hexa amount to convert is 0"); } # endif uint8_t * h = (uint8_t *)(& hexa); if (h[0]<016) response+='0'; response += String(h[0], HEX); response+=sep; if (h[1]<016) response+='0'; response += String(h[1], HEX); response+=sep; if (h[2]<016) response+='0'; response += String(h[2], HEX); response+=sep; if (h[3]<016) response+='0'; response += String(h[3], HEX); response+=sep; } // ---------------------------------------------------------------------------- // Print uint8_t values in HEX with leading 0 when necessary // ---------------------------------------------------------------------------- void printHexDigit(uint8_t digit, String & response) { // utility function for printing Hex Values with leading 0 if(digit < 0x10) response += '0'; response += String(digit,HEX); } // ---------------------------------------------------------------------------------------- // Print to the monitor console. // This function is used all over the gateway code as a substite for USB debug code. // It allows webserver users to view printed/debugging code. // With initMonitor() we init the index iMoni=0; // // Parameters: // txt: The text to be printed. // return: // // ---------------------------------------------------------------------------------------- void mPrint(String txt) { # if _DUSB>=1 Serial.println(txt); if (debug>=2) Serial.flush(); # endif //_DUSB # if _MONITOR>=1 time_t tt = now(); monitor[iMoni].txt = String(day(tt)) + "-"; monitor[iMoni].txt += String(month(tt)) + "-"; monitor[iMoni].txt += String(year(tt)) + " "; uint8_t _hour = hour(tt); uint8_t _minute = minute(tt); uint8_t _second = second(tt); if (_hour < 10) monitor[iMoni].txt += "0"; monitor[iMoni].txt += String( _hour )+ ":"; if (_minute < 10) monitor[iMoni].txt += "0"; monitor[iMoni].txt += String(_minute) + ":"; if (_second < 10) monitor[iMoni].txt += "0"; monitor[iMoni].txt += String(_second) + "- "; monitor[iMoni].txt += String(txt); // Use the circular buffer to increment the index iMoni = (iMoni+1) % _MAXMONITOR ; // And goto 0 when skipping over _MAXMONITOR # endif //_MONITOR return; } // ---------------------------------------------------------------------------- // mStat // Print the statistics on Serial (USB) port and/or Monitor // Depending on setting of _DUSB and _MONITOR. // Note: This function does not initialise the response var, will only append. // Parameters: // Interrupt: 8-bit // Response: String // Return: // 1: If successful // 0: No Success // ---------------------------------------------------------------------------- int mStat(uint8_t intr, String & response) { #if _MONITOR>=1 if (debug>=0) { response += "I="; if (intr & IRQ_LORA_RXTOUT_MASK) response += String("RXTOUT "); // 0x80 if (intr & IRQ_LORA_RXDONE_MASK) response += String("RXDONE "); // 0x40 if (intr & IRQ_LORA_CRCERR_MASK) response += "CRCERR "; // 0x20 if (intr & IRQ_LORA_HEADER_MASK) response += "HEADER "; // 0x10 if (intr & IRQ_LORA_TXDONE_MASK) response += "TXDONE "; // 0x08 if (intr & IRQ_LORA_CDDONE_MASK) response += String("CDDONE "); // 0x04 if (intr & IRQ_LORA_FHSSCH_MASK) response += "FHSSCH "; // 0x02 if (intr & IRQ_LORA_CDDETD_MASK) response += "CDDETD "; // 0x01 if (intr == 0x00) response += " -- "; response += ", F=" + String(gwayConfig.ch); response += ", SF=" + String(sf); response += ", E=" + String(_event); response += ", S="; switch (_state) { case S_INIT: response += "INIT "; break; case S_SCAN: response += "SCAN "; break; case S_CAD: response += "CAD "; break; case S_RX: response += String("RX "); break; case S_TX: response += "TX "; break; case S_TXDONE: response += "TXDONE"; break; default: response += " -- "; } response += ", eT="; response += String( micros() - eventTime ); response += ", dT="; response += String( micros() - doneTime ); //mPrint(response); // Do actual printing } #endif //_MONITOR return(1); } // ============== NUMBER FUNCTIONS ============================================ // ---------------------------------------------------------------------------- // Convert a float to string for printing // Parameters: // f is float value to convert // p is precision in decimal digits // val is character array for results // ---------------------------------------------------------------------------- void ftoa(float f, char *val, int p) { int j=1; int ival, fval; char b[7] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; for (int i=0; i< p; i++) { j= j*10; } ival = (int) f; // Make integer part fval = (int) ((f- ival)*j); // Make fraction. Has same sign as integer part if (fval<0) fval = -fval; // So if it is negative make fraction positive again. // sprintf does NOT fit in memory if ((f<0) && (ival == 0)) strcat(val, "-"); strcat(val,itoa(ival,b,10)); // Copy integer part first, base 10, null terminated strcat(val,"."); // Copy decimal point itoa(fval,b,10); // Copy fraction part base 10 for (int i=0; i<(p-strlen(b)); i++) { strcat(val,"0"); // first number of 0 of faction? } // Fraction can be anything from 0 to 10^p , so can have less digits strcat(val,b); } // ============== SERIAL SERIAL SERIAL ======================================== // ---------------------------------------------------------------------------- // Print leading '0' digits for hours(0) and second(0) when // printing values less than 10 // ---------------------------------------------------------------------------- void printDigits(uint32_t digits) { // utility function for digital clock display: prints leading 0 if(digits < 10) Serial.print(F("0")); Serial.print(digits); } // ============================================================================ // NTP TIME functions // These helper function deal with the Network Time Protool(NTP) functions. // ============================================================================ // ---------------------------------------------------------------------------------------- // stringTime // Print the time t into the String reponse. t is of type time_t in seconds. // Only when RTC is present we print real time values // t contains number of seconds since system started that the event happened. // So a value of 100 would mean that the event took place 1 minute and 40 seconds ago // ---------------------------------------------------------------------------------------- static void stringTime(time_t t, String & response) { if (t==0) { response += "--"; return; } // now() gives seconds since 1970 // as millis() does rotate every 50 days // So we need another timing parameter time_t eTime = t; // Rest is standard byte _hour = hour(eTime); byte _minute = minute(eTime); byte _second = second(eTime); switch(weekday(eTime)) { case 1: response += "Sunday "; break; case 2: response += "Monday "; break; case 3: response += "Tuesday "; break; case 4: response += "Wednesday "; break; case 5: response += "Thursday "; break; case 6: response += "Friday "; break; case 7: response += "Saturday "; break; } response += String(day(eTime)) + "-"; response += String(month(eTime)) + "-"; response += String(year(eTime)) + " "; if (_hour < 10) response += "0"; response += String(_hour) + ":"; if (_minute < 10) response += "0"; response += String(_minute) + ":"; if (_second < 10) response += "0"; response += String(_second); } // ---------------------------------------------------------------------------- // Send the time request packet to the NTP server. // // ---------------------------------------------------------------------------- int sendNtpRequest(IPAddress timeServerIP) { const int NTP_PACKET_SIZE = 48; // Fixed size of NTP record byte packetBuffer[NTP_PACKET_SIZE]; memset(packetBuffer, 0, NTP_PACKET_SIZE); // Zero the buffer. packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock packetBuffer[2] = 6; // Polling Interval packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; if (!sendUdp( (IPAddress) timeServerIP, (int) 123, packetBuffer, NTP_PACKET_SIZE)) { gwayConfig.ntpErr++; gwayConfig.ntpErrTime = now(); return(0); } return(1); } // ---------------------------------------------------------------------------- // Get the NTP time from one of the time servers // Note: As this function is called from SyncInterval in the background // make sure we have no blocking calls in this function // parameters: // t: the resulting time_t // return: // 0: when fail // >=1: when success // ---------------------------------------------------------------------------- int getNtpTime(time_t *t) { gwayConfig.ntps++; if (!sendNtpRequest(ntpServer)) // Send the request for new time { # if _MONITOR>=1 if (debug>=0) { mPrint("utils:: ERROR getNtpTime: sendNtpRequest failed"); } # endif //_MONITOR return(0); } const int NTP_PACKET_SIZE = 48; // Fixed size of NTP record byte packetBuffer[NTP_PACKET_SIZE]; memset(packetBuffer, 0, NTP_PACKET_SIZE); // Set buffer contents to zero uint32_t beginWait = millis(); delay(10); while (millis() - beginWait < 1500) // Wait for 1500 millisecs { int size = Udp.parsePacket(); if ( size >= NTP_PACKET_SIZE ) { if (Udp.read(packetBuffer, NTP_PACKET_SIZE) < NTP_PACKET_SIZE) { # if _MONITOR>=1 if (debug>=0) { mPrint("getNtpTime:: ERROR packetsize too low"); } # endif //_MONITOR break; // Error, or should we use continue } else { // Extract seconds portion. uint32_t secs; secs = packetBuffer[40] << 24; secs |= packetBuffer[41] << 16; secs |= packetBuffer[42] << 8; secs |= packetBuffer[43]; // in NL UTC is 1 TimeZone correction when no daylight saving time *t = (time_t)(secs - 2208988800UL + NTP_TIMEZONES * SECS_IN_HOUR); Udp.flush(); return(1); } Udp.flush(); } delay(100); // Wait 100 millisecs, allow kernel to act when necessary } Udp.flush(); // If we are here, we could not read the time from internet // So increase the counter gwayConfig.ntpErr++; gwayConfig.ntpErrTime = now(); # if _MONITOR>=1 if ((debug>=3) && (pdebug & P_MAIN)) { mPrint("getNtpTime:: WARNING read time failed"); // but we return 0 to indicate this to caller } # endif //_MONITOR return(0); // return 0 if unable to get the time } //getNtpTime // ---------------------------------------------------------------------------- // Set up regular synchronization of NTP server and the local time. // ---------------------------------------------------------------------------- #if NTP_INTR==1 void setupTime() { time_t t; getNtpTime(&t); setSyncProvider(t); setSyncInterval(_NTP_INTERVAL); } #endif //NTP_INTR // ---------------------------------------------------------------------------- // SerialName(id, response) // Check whether for address a (4 bytes in uint32_t) there is a // Trusted Node name. It will return the index of that name in nodex struct. // Otherwise it returns -1. // This function only works if _TRUSTED_NODES is set. // ---------------------------------------------------------------------------- int SerialName(uint32_t a, String & response) { #if _TRUSTED_NODES>=1 uint8_t * in = (uint8_t *)(& a); uint32_t id = ((in[0]<<24) | (in[1]<<16) | (in[2]<<8) | in[3]); for (int i=0; i< (sizeof(nodes)/sizeof(nodex)); i++) { if (id == nodes[i].id) { # if _MONITOR>=1 if ((debug>=3) && (pdebug & P_MAIN )) { mPrint("SerialName:: i="+String(i)+", Name="+String(nodes[i].nm)+". for node=0x"+String(nodes[i].id,HEX)); } # endif //_MONITOR response += nodes[i].nm; return(i); } } #endif // _TRUSTED_NODES return(-1); // If no success OR is TRUSTED NODES not defined } //SerialName #if _LOCALSERVER==1 // ---------------------------------------------------------------------------- // inDecodes(id) // Find the id in Decodes array, and return the index of the item // Parameters: // id: The first field in the array (normally DevAddr id). Must be char[4] // Returns: // The index of the ID in the Array. Returns -1 if not found // ---------------------------------------------------------------------------- int inDecodes(char * id) { uint32_t ident = ((id[3]<<24) | (id[2]<<16) | (id[1]<<8) | id[0]); for (int i=0; i< (sizeof(decodes)/sizeof(codex)); i++) { if (ident == decodes[i].id) { return(i); } } return(-1); } #endif // ============================= GENERAL SKETCH =============================== // ---------------------------------------------------------------------------- // DIE is not used actively in the source code apart from resolveHost(). // It is replaced by a Serial.print command so we know that we have a problem // somewhere. // There are at least 3 other ways to restart the ESP. Pick one if you want. // ---------------------------------------------------------------------------- void die(String s) { # if _MONITOR>=1 mPrint(s); # endif //_MONITOR # if _DUSB>=1 Serial.println(s); if (debug>=2) Serial.flush(); # endif //_DUSB delay(50); abort(); // Within a second } // ---------------------------------------------------------------------------- // gway_failed is a function called by ASSERT in configGway.h // // ---------------------------------------------------------------------------- void gway_failed(const char *file, uint16_t line) { #if _MONITOR>=1 String response = "Program failed in file: "; response += String(file); response += ", line: "; response += String(line); mPrint(response); #endif //_MONITOR }