_udpSemtech.ino 15 KB

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