_udpSemtech.ino 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. // 1-channel LoRa Gateway for ESP8266
  2. // Copyright (c) 2016, 2017, 2018, 2019 Maarten Westenberg version for ESP8266
  3. // Version 6.1.5
  4. // Date: 2019-12-20
  5. //
  6. // based on work done by Thomas Telkamp for Raspberry PI 1ch gateway
  7. // and many others.
  8. //
  9. // All rights reserved. This program and the accompanying materials
  10. // are made available under the terms of the MIT License
  11. // which accompanies this distribution, and is available at
  12. // https://opensource.org/licenses/mit-license.php
  13. //
  14. // NO WARRANTY OF ANY KIND IS PROVIDED
  15. //
  16. // Author: Maarten Westenberg (mw12554@hotmail.com)
  17. //
  18. // _udpSemtech.ino: This file contains the UDP specific code enabling to receive
  19. // and transmit packages/messages to the server usig Semtech protocol.
  20. // ========================================================================================
  21. // Also referred to as Semtech code
  22. #if defined(_UDPROUTER)
  23. // If _UDPROUTER is defined, _TTNROUTER should NOT be defined. So...
  24. #if defined(_TTNROUTER)
  25. #error "Please make sure that either _UDPROUTER or _TTNROUTER are defined but not both"
  26. #endif
  27. // The following functions ae defined in this module:
  28. // int readUdp(int Packetsize)
  29. // int sendUdp(IPAddress server, int port, uint8_t *msg, int length)
  30. // bool connectUdp();
  31. // void pullData();
  32. // void sendstat();
  33. // ----------------------------------------------------------------------------
  34. // connectUdp()
  35. // connect to UDP (which is a local thing, after all UDP
  36. // connections do not exist.
  37. // Parameters:
  38. // <None>
  39. // Returns
  40. // Boollean indicating success or not
  41. // ----------------------------------------------------------------------------
  42. bool connectUdp()
  43. {
  44. bool ret = false;
  45. unsigned int localPort = _LOCUDPPORT; // To listen to return messages from WiFi
  46. # if _MONITOR>=1
  47. if (debug>=1) {
  48. mPrint("Local UDP port=" + String(localPort));
  49. }
  50. # endif //_MONITOR
  51. if (Udp.begin(localPort) == 1) {
  52. # if _MONITOR>=1
  53. if (debug>=1) {
  54. mPrint("UDP Connection successful");
  55. }
  56. # endif //_MONITOR
  57. ret = true;
  58. }
  59. else{
  60. # if _MONITOR>=1
  61. if (debug>=0) {
  62. mPrint("Connection failed");
  63. }
  64. # endif //_MONITOR
  65. }
  66. return(ret);
  67. }// connectUdp
  68. // ----------------------------------------------------------------------------
  69. // DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN
  70. // readUdp()
  71. // Read DOWN a package from UDP socket, can come from any server
  72. // Messages are received when server responds to gateway requests from LoRa nodes
  73. // (e.g. JOIN requests etc.) or when server has downstream data.
  74. // We respond only to the server that sent us a message!
  75. //
  76. // Note: So normally we can forget here about codes that do upstream
  77. //
  78. // Parameters:
  79. // Packetsize: size of the buffer to read, as read by loop() calling function
  80. //
  81. // Returns:
  82. // -1 or false if not read
  83. // Or number of characters read is success
  84. //
  85. // ----------------------------------------------------------------------------
  86. int readUdp(int packetSize)
  87. {
  88. uint8_t protocol;
  89. uint16_t token;
  90. uint8_t ident;
  91. uint8_t buff[32]; // General buffer to use for UDP, set to 64
  92. uint8_t buff_down[RX_BUFF_SIZE]; // Buffer for downstream
  93. if (WlanConnect(10) < 0) {
  94. # if _MONITOR>=1
  95. mPrint("readUdp: ERROR connecting to WLAN");
  96. # endif //_MONITOR
  97. Udp.flush();
  98. yield();
  99. return(-1);
  100. }
  101. yield();
  102. if (packetSize > RX_BUFF_SIZE) {
  103. # if _MONITOR>=1
  104. mPrint("readUdp:: ERROR package of size: " + String(packetSize));
  105. # endif //_MONITOR
  106. Udp.flush();
  107. return(-1);
  108. }
  109. // We assume here that we know the originator of the message.
  110. // In practice however this can be any sender!
  111. if (Udp.read(buff_down, packetSize) < packetSize) {
  112. # if _MONITOR>=1
  113. mPrint("A readUdp:: Reading less chars");
  114. # endif //_MONITOR
  115. return(-1);
  116. }
  117. // Remote Address should be known
  118. IPAddress remoteIpNo = Udp.remoteIP();
  119. // Remote port is either of the remote TTN server or from NTP server (=123)
  120. unsigned int remotePortNo = Udp.remotePort();
  121. if (remotePortNo == 123) {
  122. // This is an NTP message arriving
  123. # if _MONITOR>=1
  124. if ( debug>=0 ) {
  125. mPrint("A readUdp:: NTP msg rcvd");
  126. }
  127. # endif //_MONITOR
  128. gwayConfig.ntpErr++;
  129. gwayConfig.ntpErrTime = now();
  130. return(0);
  131. }
  132. // If it is not NTP it must be a LoRa message for gateway or node
  133. else {
  134. uint8_t *data = (uint8_t *) ((uint8_t *)buff_down + 4);
  135. protocol = buff_down[0];
  136. token = buff_down[2]*256 + buff_down[1];
  137. ident = buff_down[3];
  138. # if _MONITOR>=1
  139. if ((debug>1) && (pdebug & P_MAIN)) {
  140. mPrint("M readUdp:: message waiting="+String(ident));
  141. }
  142. # endif //_MONITOR
  143. // now parse the message type from the server (if any)
  144. switch (ident) {
  145. // This message is used by the gateway to send sensor data to the server.
  146. // As this function is used for downstream only, this option
  147. // will never be selected but is included as a reference only
  148. case PKT_PUSH_DATA: // 0x00 UP
  149. # if _MONITOR>=1
  150. if (debug >=1) {
  151. mPrint("PKT_PUSH_DATA:: size "+String(packetSize)+" From "+String(remoteIpNo.toString()));
  152. // Serial.print(F(", port ")); Serial.print(remotePortNo);
  153. // Serial.print(F(", data: "));
  154. // for (int i=0; i<packetSize; i++) {
  155. // Serial.print(buff_down[i],HEX);
  156. // Serial.print(':');
  157. // }
  158. // Serial.println();
  159. // if (debug>=2) Serial.flush();
  160. }
  161. # endif //_MONITOR
  162. break;
  163. // This message is sent by the server to acknowledge receipt of a
  164. // (sensor) message sent with the code above.
  165. case PKT_PUSH_ACK: // 0x01 DOWN
  166. #if _MONITOR>=1
  167. if (( debug>=2) && (pdebug & P_MAIN )) {
  168. mPrint("M PKT_PUSH_ACK:: size="+String(packetSize)+" From "+String(remoteIpNo.toString()));
  169. // Serial.print(F(", port "));
  170. // Serial.print(remotePortNo);
  171. // Serial.print(F(", token: "));
  172. // Serial.println(token, HEX);
  173. // Serial.println();
  174. }
  175. #endif //_MONITOR
  176. break;
  177. case PKT_PULL_DATA: // 0x02 UP
  178. #if _DUSB>=1
  179. Serial.print(F(" Pull Data"));
  180. Serial.println();
  181. #endif
  182. break;
  183. // This message type is used to confirm OTAA message to the node
  184. // XXX This message format may also be used for other downstream communication
  185. case PKT_PULL_RESP: // 0x03 DOWN
  186. # if _MONITOR>=1
  187. if (( debug>=0 ) && ( pdebug & P_MAIN )) {
  188. mPrint("readUdp:: PKT_PULL_RESP received");
  189. }
  190. # endif //_MONITOR
  191. // lastTmst = micros(); // Store the tmst this package was received
  192. // Send to the LoRa Node first (timing) and then do reporting to Serial
  193. _state=S_TX;
  194. sendTime = micros(); // record when we started sending the message
  195. if (sendPacket(data, packetSize-4) < 0) {
  196. # if _MONITOR>=1
  197. if ( debug>=0 ) {
  198. mPrint("A readUdp:: ERROR: PKT_PULL_RESP sendPacket failed");
  199. }
  200. # endif //_MONITOR
  201. return(-1);
  202. }
  203. // Now respond with an PKT_TX_ACK; 0x04 UP
  204. buff[0]=buff_down[0];
  205. buff[1]=buff_down[1];
  206. buff[2]=buff_down[2];
  207. //buff[3]=PKT_PULL_ACK; // Pull request/Change of Mogyi
  208. buff[3]=PKT_TX_ACK;
  209. buff[4]=MAC_array[0];
  210. buff[5]=MAC_array[1];
  211. buff[6]=MAC_array[2];
  212. buff[7]=0xFF;
  213. buff[8]=0xFF;
  214. buff[9]=MAC_array[3];
  215. buff[10]=MAC_array[4];
  216. buff[11]=MAC_array[5];
  217. buff[12]=0;
  218. # if _MONITOR>=1
  219. if (( debug >= 2 ) && ( pdebug & P_MAIN )) {
  220. mPrint("M readUdp:: TX buff filled");
  221. }
  222. # endif //_MONITOR
  223. // Only send the PKT_PULL_ACK to the UDP socket that just sent the data!!!
  224. Udp.beginPacket(remoteIpNo, remotePortNo);
  225. if (Udp.write((unsigned char *)buff, 12) != 12) {
  226. #if _DUSB>=1
  227. if (debug>=0)
  228. Serial.println("A readUdp:: Error: PKT_PULL_ACK UDP write");
  229. #endif
  230. }
  231. else {
  232. #if _DUSB>=1
  233. if (( debug>=0 ) && ( pdebug & P_TX )) {
  234. Serial.print(F("M PKT_TX_ACK:: micros="));
  235. Serial.println(micros());
  236. }
  237. #endif
  238. }
  239. if (!Udp.endPacket()) {
  240. #if _DUSB>=1
  241. if (( debug>=0 ) && ( pdebug & P_MAIN )) {
  242. Serial.println(F("M PKT_PULL_DATALL Error Udp.endpaket"));
  243. }
  244. #endif
  245. }
  246. yield();
  247. #if _DUSB>=1
  248. if (( debug >=1 ) && (pdebug & P_MAIN )) {
  249. Serial.print(F("M PKT_PULL_RESP:: size "));
  250. Serial.print(packetSize);
  251. Serial.print(F(" From "));
  252. Serial.print(remoteIpNo);
  253. Serial.print(F(", port "));
  254. Serial.print(remotePortNo);
  255. Serial.print(F(", data: "));
  256. data = buff_down + 4;
  257. data[packetSize] = 0;
  258. Serial.print((char *)data);
  259. Serial.println(F("..."));
  260. }
  261. #endif
  262. break;
  263. case PKT_PULL_ACK: // 0x04 DOWN; the server sends a PULL_ACK to confirm PULL_DATA receipt
  264. #if _DUSB>=1
  265. if (( debug >= 2 ) && (pdebug & P_MAIN )) {
  266. Serial.print(F("M PKT_PULL_ACK:: size ")); Serial.print(packetSize);
  267. Serial.print(F(" From ")); Serial.print(remoteIpNo);
  268. Serial.print(F(", port ")); Serial.print(remotePortNo);
  269. Serial.print(F(", data: "));
  270. for (int i=0; i<packetSize; i++) {
  271. Serial.print(buff_down[i],HEX);
  272. Serial.print(':');
  273. }
  274. Serial.println();
  275. }
  276. #endif
  277. break;
  278. default:
  279. #if GATEWAYMGT==1
  280. // For simplicity, we send the first 4 bytes too
  281. gateway_mgt(packetSize, buff_down);
  282. #else
  283. #endif
  284. # if _MONITOR>=1
  285. mPrint(", ERROR ident not recognized="+String(ident));
  286. # endif //_MONITOR
  287. break;
  288. }
  289. # if _MONITOR>=2
  290. if (debug>=2) {
  291. mPrint("readUdp:: returning=" + String(packetSize));
  292. }
  293. # endif //_MONITOR
  294. // For downstream messages
  295. return packetSize;
  296. }
  297. }//readUdp
  298. // ----------------------------------------------------------------------------
  299. // sendUdp()
  300. // Send UP an UDP/DGRAM message to the MQTT server
  301. // If we send to more than one host (not sure why) then we need to set sockaddr
  302. // before sending.
  303. // Parameters:
  304. // IPAddress
  305. // port
  306. // msg *
  307. // length (of msg)
  308. // return values:
  309. // 0: Error
  310. // 1: Success
  311. // ----------------------------------------------------------------------------
  312. int sendUdp(IPAddress server, int port, uint8_t *msg, int length) {
  313. // Check whether we are conected to Wifi and the internet
  314. if (WlanConnect(3) < 0) {
  315. # if _MONITOR>=1
  316. if (( debug>=0 ) && ( pdebug & P_MAIN )) {
  317. mPrint("sendUdp: ERROR connecting to WiFi");
  318. }
  319. # endif //_MONITOR
  320. Udp.flush();
  321. yield();
  322. return(0);
  323. }
  324. yield();
  325. //send the update
  326. # if _MONITOR>=1
  327. if (( debug>=3 ) && ( pdebug & P_MAIN )) {
  328. mPrint("M WiFi connected");
  329. }
  330. # endif //_MONITOR
  331. if (!Udp.beginPacket(server, (int) port)) {
  332. # if _MONITOR>=1
  333. if (( debug>=1 ) && ( pdebug & P_MAIN )) {
  334. mPrint("M sendUdp:: Error Udp.beginPacket");
  335. }
  336. # endif //_MONITOR
  337. return(0);
  338. }
  339. yield();
  340. if (Udp.write((unsigned char *)msg, length) != length) {
  341. # if _MONITOR>=1
  342. if (( debug<=1 ) && ( pdebug & P_MAIN )) {
  343. mPrint("M sendUdp:: Error write");
  344. }
  345. # endif //_MONITOR
  346. Udp.endPacket(); // Close UDP
  347. return(0); // Return error
  348. }
  349. yield();
  350. if (!Udp.endPacket()) {
  351. # if _MONITOR>=1
  352. if (debug>=1) {
  353. mPrint("sendUdp:: Error Udp.endPacket");
  354. }
  355. # endif //_MONITOR
  356. return(0);
  357. }
  358. return(1);
  359. }//sendUDP
  360. // ----------------------------------------------------------------------------
  361. // pullData()
  362. // Send UDP periodic Pull_DATA message to server to keepalive the connection
  363. // and to invite the server to send downstream messages when these are available
  364. // *2, par. 5.2
  365. // - Protocol Version (1 byte)
  366. // - Random Token (2 bytes)
  367. // - PULL_DATA identifier (1 byte) = 0x02
  368. // - Gateway unique identifier (8 bytes) = MAC address
  369. // ----------------------------------------------------------------------------
  370. void pullData() {
  371. uint8_t pullDataReq[12]; // status report as a JSON object
  372. int pullIndex=0;
  373. int i;
  374. uint8_t token_h = (uint8_t)rand(); // random token
  375. uint8_t token_l = (uint8_t)rand(); // random token
  376. // pre-fill the data buffer with fixed fields
  377. pullDataReq[0] = PROTOCOL_VERSION; // 0x01
  378. pullDataReq[1] = token_h;
  379. pullDataReq[2] = token_l;
  380. pullDataReq[3] = PKT_PULL_DATA; // 0x02
  381. // READ MAC ADDRESS OF ESP8266, and return unique Gateway ID consisting of MAC address and 2bytes 0xFF
  382. pullDataReq[4] = MAC_array[0];
  383. pullDataReq[5] = MAC_array[1];
  384. pullDataReq[6] = MAC_array[2];
  385. pullDataReq[7] = 0xFF;
  386. pullDataReq[8] = 0xFF;
  387. pullDataReq[9] = MAC_array[3];
  388. pullDataReq[10] = MAC_array[4];
  389. pullDataReq[11] = MAC_array[5];
  390. //pullDataReq[12] = 0/00; // add string terminator, for safety
  391. pullIndex = 12; // 12-byte header
  392. //send the update
  393. uint8_t *pullPtr;
  394. pullPtr = pullDataReq,
  395. #ifdef _TTNSERVER
  396. sendUdp(ttnServer, _TTNPORT, pullDataReq, pullIndex);
  397. yield();
  398. #endif
  399. # if _MONITOR>=1
  400. if (pullPtr != pullDataReq) {
  401. mPrint("pullPtr != pullDatReq");
  402. }
  403. # endif //_MONITOR
  404. #ifdef _THINGSERVER
  405. sendUdp(thingServer, _THINGPORT, pullDataReq, pullIndex);
  406. #endif
  407. #if _DUSB>=1
  408. if (( debug>=2 ) && ( pdebug & P_MAIN )) {
  409. yield();
  410. Serial.print(F("M PKT_PULL_DATA request, len=<"));
  411. Serial.print(pullIndex);
  412. Serial.print(F("> "));
  413. for (i=0; i<pullIndex; i++) {
  414. Serial.print(pullDataReq[i],HEX); // debug: display JSON stat
  415. Serial.print(':');
  416. }
  417. Serial.println();
  418. if (debug>=2) Serial.flush();
  419. }
  420. #endif
  421. return;
  422. }//pullData
  423. // ----------------------------------------------------------------------------
  424. // sendstat()
  425. // Send UP periodic status message to server even when we do not receive any
  426. // data.
  427. // Parameters:
  428. // - <none>
  429. // ----------------------------------------------------------------------------
  430. void sendstat() {
  431. uint8_t status_report[STATUS_SIZE]; // status report as a JSON object
  432. char stat_timestamp[32]; // XXX was 24
  433. time_t t;
  434. char clat[10]={0};
  435. char clon[10]={0};
  436. int stat_index=0;
  437. uint8_t token_h = (uint8_t)rand(); // random token
  438. uint8_t token_l = (uint8_t)rand(); // random token
  439. // pre-fill the data buffer with fixed fields
  440. status_report[0] = PROTOCOL_VERSION; // 0x01
  441. status_report[1] = token_h;
  442. status_report[2] = token_l;
  443. status_report[3] = PKT_PUSH_DATA; // 0x00
  444. // READ MAC ADDRESS OF ESP8266, and return unique Gateway ID consisting of MAC address and 2bytes 0xFF
  445. status_report[4] = MAC_array[0];
  446. status_report[5] = MAC_array[1];
  447. status_report[6] = MAC_array[2];
  448. status_report[7] = 0xFF;
  449. status_report[8] = 0xFF;
  450. status_report[9] = MAC_array[3];
  451. status_report[10] = MAC_array[4];
  452. status_report[11] = MAC_array[5];
  453. stat_index = 12; // 12-byte header
  454. t = now(); // get timestamp for statistics
  455. // XXX Using CET as the current timezone. Change to your timezone
  456. sprintf(stat_timestamp, "%04d-%02d-%02d %02d:%02d:%02d CET", year(),month(),day(),hour(),minute(),second());
  457. yield();
  458. ftoa(lat,clat,5); // Convert lat to char array with 5 decimals
  459. ftoa(lon,clon,5); // As IDE CANNOT prints floats
  460. // Build the Status message in JSON format, XXX Split this one up...
  461. delay(1);
  462. int j = snprintf((char *)(status_report + stat_index), STATUS_SIZE-stat_index,
  463. "{\"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\"}}",
  464. stat_timestamp, clat, clon, (int)alt, statc.msg_ttl, statc.msg_ok, statc.msg_down, 0, 0, 0, platform, email, description);
  465. yield(); // Give way to the internal housekeeping of the ESP8266
  466. stat_index += j;
  467. status_report[stat_index] = 0; // add string terminator, for safety
  468. #if _DUSB>=1
  469. if (( debug>=2 ) && ( pdebug & P_MAIN )) {
  470. Serial.print(F("M stat update: <"));
  471. Serial.print(stat_index);
  472. Serial.print(F("> "));
  473. Serial.println((char *)(status_report+12)); // DEBUG: display JSON stat
  474. }
  475. #endif
  476. if (stat_index > STATUS_SIZE) {
  477. # if _MONITOR>=1
  478. mPrint("A sendstat:: ERROR buffer too big");
  479. # endif //_MONITOR
  480. return;
  481. }
  482. //send the update
  483. #ifdef _TTNSERVER
  484. sendUdp(ttnServer, _TTNPORT, status_report, stat_index);
  485. yield();
  486. #endif
  487. #ifdef _THINGSERVER
  488. sendUdp(thingServer, _THINGPORT, status_report, stat_index);
  489. #endif
  490. return;
  491. }//sendstat
  492. #endif //_UDPROUTER