_utils.ino 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. // 1-channel LoRa Gateway for ESP8266 and ESP32
  2. // Copyright (c) 2016-2020 Maarten Westenberg version for ESP8266
  3. //
  4. // based on work done by Thomas Telkamp for Raspberry PI 1ch gateway
  5. // and many others.
  6. //
  7. // All rights reserved. This program and the accompanying materials
  8. // are made available under the terms of the MIT License
  9. // which accompanies this distribution, and is available at
  10. // https://opensource.org/licenses/mit-license.php
  11. //
  12. // NO WARRANTY OF ANY KIND IS PROVIDED
  13. //
  14. // Author: Maarten Westenberg (mw12554@hotmail.com)
  15. //
  16. // This file contains the utilities for time and other functions
  17. // ========================================================================================
  18. // ==================== STRING STRING STRING ==============================================
  19. // --------------------------------------------------------------------------------
  20. // PRINT INT
  21. // The function printInt prints a number with Thousands seperator
  22. // Paraneters:
  23. // i: Integer containing Microseconds
  24. // response: String & value containing the converted number
  25. // Return:
  26. // <none>
  27. // --------------------------------------------------------------------------------
  28. void printInt (uint32_t i, String & response)
  29. {
  30. response+=(String(i/1000000) + "." + String(i%1000000));
  31. }
  32. // --------------------------------------------------------------------------------
  33. // PRINT Down
  34. // In a uniform way, this function prints the timstamp, the current time and the
  35. // time the function must wait to execute. It will print all Downstream data
  36. // Parameters:
  37. //
  38. // Returns:
  39. // <none>
  40. // --------------------------------------------------------------------------------
  41. void printDwn(struct LoraDown *LoraDown, String & response)
  42. {
  43. uint32_t i= LoraDown->tmst;
  44. uint32_t m= micros();
  45. response += "micr="; printInt(m, response);
  46. response += ", tmst="; printInt(i, response);
  47. response += ", wait=";
  48. if (i>m) {
  49. response += String(i-m);
  50. }
  51. else {
  52. response += "(";
  53. response += String(m-i);
  54. response += ")";
  55. }
  56. //inval =int(LoraDown->freq);
  57. //response += ", freq=" +String(intval) +".";
  58. //fraqval =int((LoraDown->freq-intval)*10000);
  59. //response += String(fraqval);
  60. char cfreq[12] = {0};
  61. ftoa(LoraDown->freq, cfreq, 3);
  62. response += ", freq=" +String(cfreq);
  63. response += ", sf=" +String(LoraDown->sf);
  64. response += ", bw=" +String(LoraDown->bw);
  65. response += ", powe=" +String(LoraDown->powe);
  66. response += ", crc=" +String(LoraDown->crc);
  67. response += ", imme=" +String(LoraDown->imme);
  68. response += ", iiq=" +String(LoraDown->iiq, HEX);
  69. response += ", prea=" +String(LoraDown->prea);
  70. response += ", rfch=" +String(LoraDown->rfch);
  71. response += ", ncrc=" +String(LoraDown->ncrc);
  72. response += ", size=" +String(LoraDown->size);
  73. response += ", strict=" +String(_STRICT_1CH);
  74. response += ", a=";
  75. uint8_t DevAddr [4];
  76. DevAddr[0] = LoraDown->payLoad[4];
  77. DevAddr[1] = LoraDown->payLoad[3];
  78. DevAddr[2] = LoraDown->payLoad[2];
  79. DevAddr[3] = LoraDown->payLoad[1];
  80. printHex((IPAddress)DevAddr, ':', response);
  81. yield();
  82. return;
  83. }
  84. // --------------------------------------------------------------------------------
  85. // PRINT IP
  86. // Output the 4-byte IP address for easy printing.
  87. // As this function is also used by _otaServer.ino do not put in #define
  88. // Parameters:
  89. // ipa: The Ip Address (input)
  90. // sep: Separator character (input)
  91. // response: The response string (output)
  92. // Return:
  93. // <none>
  94. // --------------------------------------------------------------------------------
  95. void printIP(IPAddress ipa, const char sep, String & response)
  96. {
  97. response+=(String)ipa[0]; response+=sep;
  98. response+=(String)ipa[1]; response+=sep;
  99. response+=(String)ipa[2]; response+=sep;
  100. response+=(String)ipa[3];
  101. }
  102. // ----------------------------------------------------------------------------------------
  103. // Fill a HEXadecimal String from a 4-byte char array (uint32_t)
  104. //
  105. // ----------------------------------------------------------------------------------------
  106. void printHex(uint32_t hexa, const char sep, String & response)
  107. {
  108. # if _MONITOR>=1
  109. if ((debug>=0) && (hexa==0)) {
  110. mPrint("printHex:: hexa amount to convert is 0");
  111. }
  112. # endif
  113. uint8_t * h = (uint8_t *)(& hexa);
  114. if (h[0]<016) response+='0'; response += String(h[0], HEX); response+=sep;
  115. if (h[1]<016) response+='0'; response += String(h[1], HEX); response+=sep;
  116. if (h[2]<016) response+='0'; response += String(h[2], HEX); response+=sep;
  117. if (h[3]<016) response+='0'; response += String(h[3], HEX); response+=sep;
  118. }
  119. // ----------------------------------------------------------------------------
  120. // Print uint8_t values in HEX with leading 0 when necessary
  121. // ----------------------------------------------------------------------------
  122. void printHexDigit(uint8_t digit, String & response)
  123. {
  124. // utility function for printing Hex Values with leading 0
  125. if(digit < 0x10)
  126. response += '0';
  127. response += String(digit,HEX);
  128. }
  129. // ========================= MONITOR FUNCTIONS ============================================
  130. // Monitor functions
  131. // These functions write to the Monitor and print the monitor.
  132. // ----------------------------------------------------------------------------------------
  133. // Print one line to the monitor console array.
  134. // This function is used all over the gateway code as a substitute for USB debug code.
  135. // It allows webserver users to view printed/debugging code.
  136. // With initMonitor() we init the index iMoni=0;
  137. //
  138. // Parameters:
  139. // txt: The text to be printed.
  140. // return:
  141. // <None>
  142. // ----------------------------------------------------------------------------------------
  143. void mPrint(String txt)
  144. {
  145. # if _DUSB>=1
  146. if (gwayConfig.dusbStat>=1) {
  147. Serial.println(txt); // Copy to serial when configured
  148. }
  149. # endif //_DUSB
  150. #if _MONITOR>=1
  151. time_t tt = now();
  152. monitor[iMoni].txt = "";
  153. stringTime(tt, monitor[iMoni].txt);
  154. monitor[iMoni].txt += "- " + String(txt);
  155. // Use the circular buffer to increment the index
  156. iMoni = (iMoni+1) % gwayConfig.maxMoni ; // And goto 0 when skipping over _MAXMONITOR
  157. #endif //_MONITOR
  158. return;
  159. } //mPrint
  160. // ----------------------------------------------------------------------------
  161. // mStat (Monitor-Statistics)
  162. // Print the statistics on Serial (USB) port and/or Monitor
  163. // Depending on setting of _DUSB and _MONITOR.
  164. // Note: This function does not initialise the response var, will only append.
  165. // Parameters:
  166. // Interrupt: 8-bit
  167. // Response: String
  168. // Return:
  169. // 1: If successful
  170. // 0: No Success
  171. // ----------------------------------------------------------------------------
  172. int mStat(uint8_t intr, String & response)
  173. {
  174. #if _MONITOR>=1
  175. if (debug>=0) {
  176. response += "I=";
  177. if (intr & IRQ_LORA_RXTOUT_MASK) response += "RXTOUT "; // 0x80
  178. if (intr & IRQ_LORA_RXDONE_MASK) response += "RXDONE "; // 0x40
  179. if (intr & IRQ_LORA_CRCERR_MASK) response += "CRCERR "; // 0x20
  180. if (intr & IRQ_LORA_HEADER_MASK) response += "HEADER "; // 0x10
  181. if (intr & IRQ_LORA_TXDONE_MASK) response += "TXDONE "; // 0x08
  182. if (intr & IRQ_LORA_CDDONE_MASK) response += "CDDONE "; // 0x04
  183. if (intr & IRQ_LORA_FHSSCH_MASK) response += "FHSSCH "; // 0x02
  184. if (intr & IRQ_LORA_CDDETD_MASK) response += "CDDETD "; // 0x01
  185. if (intr == 0x00) response += " -- ";
  186. response += ", F=" + String(gwayConfig.ch);
  187. response += ", SF=" + String(sf);
  188. response += ", E=" + String(_event);
  189. response += ", S=";
  190. switch (_state) {
  191. case S_INIT:
  192. response += "INIT ";
  193. break;
  194. case S_SCAN:
  195. response += "SCAN ";
  196. break;
  197. case S_CAD:
  198. response += "CAD ";
  199. break;
  200. case S_RX:
  201. response += "RX ";
  202. break;
  203. case S_TX:
  204. response += "TX ";
  205. break;
  206. case S_TXDONE:
  207. response += "TXDONE";
  208. break;
  209. default:
  210. response += " -- ";
  211. }
  212. response += ", eT=";
  213. response += String( micros() - eventTime );
  214. response += ", dT=";
  215. response += String( micros() - doneTime );
  216. }
  217. #endif //_MONITOR
  218. return(1);
  219. }
  220. // ============== NUMBER FUNCTIONS ============================================
  221. // ----------------------------------------------------------------------------
  222. // Convert a float to string for printing
  223. // Parameters:
  224. // f is float value to convert
  225. // p is precision in decimal digits
  226. // val is character array for results
  227. // ----------------------------------------------------------------------------
  228. void ftoa(float f, char *val, int p)
  229. {
  230. int j=1;
  231. int ival, fval;
  232. char b[7] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
  233. for (int i=0; i< p; i++) { j= j*10; }
  234. ival = (int) f; // Make integer part
  235. fval = (int) ((f- ival)*j); // Make fraction. Has same sign as integer part
  236. if (fval<0) fval = -fval; // So if it is negative make fraction positive again.
  237. // sprintf does NOT fit in memory
  238. if ((f<0) && (ival == 0)) strcat(val, "-");
  239. strcat(val,itoa(ival,b,10)); // Copy integer part first, base 10, null terminated
  240. strcat(val,"."); // Copy decimal point
  241. itoa(fval,b,10); // Copy fraction part base 10
  242. for (unsigned int i=0; i<(p-strlen(b)); i++) {
  243. strcat(val,"0"); // first number of 0 of faction?
  244. }
  245. // Fraction can be anything from 0 to 10^p , so can have less digits
  246. strcat(val,b);
  247. }
  248. // ============== SERIAL SERIAL SERIAL ========================================
  249. // ----------------------------------------------------------------------------
  250. // Print leading '0' digits for hours(0) and second(0) when
  251. // printing values less than 10
  252. // ----------------------------------------------------------------------------
  253. void printDigits(uint32_t digits)
  254. {
  255. // utility function for digital clock display: prints leading 0
  256. if(digits < 10)
  257. Serial.print(F("0"));
  258. Serial.print(digits);
  259. }
  260. // ============================================================================
  261. // NTP TIME functions
  262. // These helper function deal with the Network Time Protool(NTP) functions.
  263. // ============================================================================
  264. // ----------------------------------------------------------------------------------------
  265. // stringTime
  266. // Print the time t into the String reponse. t is of type time_t in seconds.
  267. // Only when RTC is present we print real time values
  268. // t contains number of seconds since system started that the event happened.
  269. // So a value of 100 would mean that the event took place 1 minute and 40 seconds ago
  270. // ----------------------------------------------------------------------------------------
  271. static void stringTime(time_t t, String & response)
  272. {
  273. if (t==0) { response += "--"; return; }
  274. // now() gives seconds since 1970
  275. // as millis() does rotate every 50 days
  276. // So we need another timing parameter
  277. time_t eTime = t;
  278. // Rest is standard
  279. byte _hour = hour(eTime);
  280. byte _minute = minute(eTime);
  281. byte _second = second(eTime);
  282. byte _month = month(eTime);
  283. byte _day = day(eTime);
  284. switch(weekday(eTime)) {
  285. case 1: response += "Sun "; break;
  286. case 2: response += "Mon "; break;
  287. case 3: response += "Tue "; break;
  288. case 4: response += "Wed "; break;
  289. case 5: response += "Thu "; break;
  290. case 6: response += "Fri "; break;
  291. case 7: response += "Sat "; break;
  292. }
  293. if (_day < 10) response += "0"; response += String(_day) + "-";
  294. if (_month < 10) response += "0"; response += String(_month) + "-";
  295. response += String(year(eTime)) + " ";
  296. if (_hour < 10) response += "0"; response += String(_hour) + ":";
  297. if (_minute < 10) response += "0"; response += String(_minute) + ":";
  298. if (_second < 10) response += "0"; response += String(_second);
  299. }
  300. // ----------------------------------------------------------------------------
  301. // Send the time request packet to the NTP server.
  302. //
  303. // ----------------------------------------------------------------------------
  304. int sendNtpRequest(IPAddress timeServerIP)
  305. {
  306. const int NTP_PACKET_SIZE = 48; // Fixed size of NTP record
  307. byte packetBuffer[NTP_PACKET_SIZE];
  308. memset(packetBuffer, 0, NTP_PACKET_SIZE); // Zero the buffer.
  309. packetBuffer[0] = 0b11100011; // LI, Version, Mode
  310. packetBuffer[1] = 0; // Stratum, or type of clock
  311. packetBuffer[2] = 6; // Polling Interval
  312. packetBuffer[3] = 0xEC; // Peer Clock Precision
  313. // 8 bytes of zero for Root Delay & Root Dispersion
  314. packetBuffer[12] = 49;
  315. packetBuffer[13] = 0x4E;
  316. packetBuffer[14] = 49;
  317. packetBuffer[15] = 52;
  318. if (!sendUdp( (IPAddress) timeServerIP, (int) 123, packetBuffer, NTP_PACKET_SIZE)) {
  319. gwayConfig.ntpErr++;
  320. gwayConfig.ntpErrTime = now();
  321. return(0);
  322. }
  323. return(1);
  324. } // sendNtpRequest()
  325. // ----------------------------------------------------------------------------
  326. // Get the NTP time from one of the time servers
  327. // Note: As this function is called from SyncInterval in the background
  328. // make sure we have no blocking calls in this function
  329. // parameters:
  330. // t: the resulting time_t
  331. // return:
  332. // 0: when fail
  333. // >=1: when success
  334. // ----------------------------------------------------------------------------
  335. int getNtpTime(time_t *t)
  336. {
  337. gwayConfig.ntps++;
  338. if (!sendNtpRequest(ntpServer)) // Send the request for new time
  339. {
  340. # if _MONITOR>=1
  341. if (debug>=0) {
  342. mPrint("utils:: ERROR getNtpTime: sendNtpRequest failed");
  343. }
  344. # endif //_MONITOR
  345. return(0);
  346. }
  347. const int NTP_PACKET_SIZE = 48; // Fixed size of NTP record
  348. byte packetBuffer[NTP_PACKET_SIZE];
  349. memset(packetBuffer, 0, NTP_PACKET_SIZE); // Set buffer contents to zero
  350. uint32_t beginWait = millis();
  351. delay(10);
  352. while (millis() - beginWait < 1500) // Wait for 1500 millisecs
  353. {
  354. int size = Udp.parsePacket();
  355. if ( size >= NTP_PACKET_SIZE ) {
  356. if (Udp.read(packetBuffer, NTP_PACKET_SIZE) < NTP_PACKET_SIZE) {
  357. # if _MONITOR>=1
  358. if (debug>=0) {
  359. mPrint("getNtpTime:: ERROR packetsize too low");
  360. }
  361. # endif //_MONITOR
  362. break; // Error, or should we use continue
  363. }
  364. else {
  365. // Extract seconds portion.
  366. uint32_t secs;
  367. secs = packetBuffer[40] << 24;
  368. secs |= packetBuffer[41] << 16;
  369. secs |= packetBuffer[42] << 8;
  370. secs |= packetBuffer[43];
  371. // in NL UTC is 1 TimeZone correction when no daylight saving time
  372. *t = (time_t)(secs - 2208988800UL + NTP_TIMEZONES * SECS_IN_HOUR);
  373. Udp.flush();
  374. return(1);
  375. }
  376. Udp.flush();
  377. }
  378. delay(100); // Wait 100 millisecs, allow kernel to act when necessary
  379. }
  380. Udp.flush();
  381. // If we are here, we could not read the time from internet
  382. // So increase the counter
  383. gwayConfig.ntpErr++;
  384. gwayConfig.ntpErrTime = now();
  385. # if _MONITOR>=1
  386. if ((debug>=3) && (pdebug & P_MAIN)) {
  387. mPrint("getNtpTime:: WARNING read time failed"); // but we return 0 to indicate this to caller
  388. }
  389. # endif //_MONITOR
  390. return(0); // return 0 if unable to get the time
  391. } //getNtpTime
  392. // ----------------------------------------------------------------------------
  393. // Set up regular synchronization of NTP server and the local time.
  394. // ----------------------------------------------------------------------------
  395. #if NTP_INTR==1
  396. void setupTime()
  397. {
  398. time_t t;
  399. getNtpTime(&t);
  400. setSyncProvider(t);
  401. setSyncInterval(_NTP_INTERVAL);
  402. }
  403. #endif //NTP_INTR
  404. // ----------------------------------------------------------------------------
  405. // SerialName(id, response)
  406. // Check whether for address a (4 bytes in uint32_t) there is a
  407. // Trusted Node name. It will return the index of that name in nodex struct.
  408. // Otherwise it returns -1.
  409. // This function only works if _TRUSTED_NODES is set.
  410. // ----------------------------------------------------------------------------
  411. int SerialName(uint32_t a, String & response)
  412. {
  413. #if _TRUSTED_NODES>=1
  414. uint8_t * in = (uint8_t *)(& a);
  415. uint32_t id = ((in[0]<<24) | (in[1]<<16) | (in[2]<<8) | in[3]);
  416. for (unsigned int i=0; i< (sizeof(nodes)/sizeof(nodex)); i++) {
  417. if (id == nodes[i].id) {
  418. # if _MONITOR>=1
  419. if ((debug>=3) && (pdebug & P_MAIN )) {
  420. mPrint("SerialName:: i="+String(i)+", Name="+String(nodes[i].nm)+". for node=0x"+String(nodes[i].id,HEX));
  421. }
  422. # endif //_MONITOR
  423. response += nodes[i].nm;
  424. return(i);
  425. }
  426. }
  427. #endif //_TRUSTED_NODES
  428. return(-1); // If no success OR is TRUSTED NODES not defined
  429. } //SerialName
  430. #if _LOCALSERVER==1
  431. // ----------------------------------------------------------------------------
  432. // inDecodes(id)
  433. // Find the id in Decodes array, and return the index of the item
  434. // Parameters:
  435. // id: The first field in the array (normally DevAddr id). Must be char[4]
  436. // Returns:
  437. // The index of the ID in the Array. Returns -1 if not found
  438. // ----------------------------------------------------------------------------
  439. int inDecodes(char * id) {
  440. uint32_t ident = ((id[3]<<24) | (id[2]<<16) | (id[1]<<8) | id[0]);
  441. for (unsigned int i=0; i< (sizeof(decodes)/sizeof(codex)); i++) {
  442. if (ident == decodes[i].id) {
  443. return(i);
  444. }
  445. }
  446. return(-1);
  447. }
  448. #endif
  449. // ============================= GENERAL SKETCH ===============================
  450. // ----------------------------------------------------------------------------
  451. // DIE is not used actively in the source code apart from resolveHost().
  452. // It is replaced by a Serial.print command so we know that we have a problem
  453. // somewhere.
  454. // There are at least 3 other ways to restart the ESP. Pick one if you want.
  455. // ----------------------------------------------------------------------------
  456. void die(String s)
  457. {
  458. # if _MONITOR>=1
  459. mPrint(s);
  460. # endif //_MONITOR
  461. # if _DUSB>=1
  462. Serial.println(s);
  463. if (debug>=2) Serial.flush();
  464. # endif //_DUSB
  465. delay(50);
  466. abort(); // Within a second
  467. }
  468. // ----------------------------------------------------------------------------
  469. // gway_failed is a function called by ASSERT in configGway.h
  470. //
  471. // ----------------------------------------------------------------------------
  472. void gway_failed(const char *file, uint16_t line) {
  473. #if _MONITOR>=1
  474. String response = "Program failed in file: ";
  475. response += String(file);
  476. response += ", line: ";
  477. response += String(line);
  478. mPrint(response);
  479. #endif //_MONITOR
  480. }