_udpSemtech.ino 16 KB

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