_udpSemtech.ino 16 KB

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