_udpSemtech.ino 16 KB

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