diff --git a/FDRS_Gateway/fdrs_functions.h b/FDRS_Gateway/fdrs_functions.h index 8c1e3e1..455b3c6 100644 --- a/FDRS_Gateway/fdrs_functions.h +++ b/FDRS_Gateway/fdrs_functions.h @@ -20,14 +20,12 @@ enum { event_lora2 }; - enum crcResult{ CRC_NULL, CRC_OK, CRC_BAD, } returnCRC = CRC_NULL; - enum { cmd_clear, cmd_ping, @@ -39,13 +37,13 @@ enum { #define DBG(a) (Serial.println(a)) #else #define DBG(a) -#endif // FDRS_DEBUG +#endif #if defined (ESP32) #define UART_IF Serial1 #else #define UART_IF Serial -#endif // ESP32 +#endif // enable to get detailed info from where single configuration macros have been taken #define DEBUG_NODE_CONFIG @@ -197,6 +195,8 @@ unsigned long ackOkLoRaMsg = 0; // Number of total LoRa packets with valid C char logBuffer[512]; uint16_t logBufferPos = 0; // datatype depends on size of sdBuffer uint32_t timeLOGBUF = 0; +time_t last_mqtt_success = 0; +time_t last_log_write = 0; #endif SystemPacket theCmd; @@ -204,6 +204,7 @@ DataReading theData[256]; uint8_t ln; uint8_t newData = event_clear; uint8_t newCmd = cmd_clear; +bool is_ping = false; #ifdef USE_ESPNOW DataReading ESPNOW1buffer[256]; @@ -258,6 +259,43 @@ const char* mqtt_pass = NULL; #endif //USE_WIFI + +// Function prototypes +void transmitLoRa(uint16_t*, DataReading*, uint8_t); +void transmitLoRa(uint16_t*, SystemPacket*, uint8_t); +static uint16_t crc16_update(uint16_t, uint8_t); + + +// CRC16 from https://github.com/4-20ma/ModbusMaster/blob/3a05ff87677a9bdd8e027d6906dc05ca15ca8ade/src/util/crc16.h#L71 + +/** @ingroup util_crc16 + Processor-independent CRC-16 calculation. + Polynomial: x^16 + x^15 + x^2 + 1 (0xA001)
+ Initial value: 0xFFFF + This CRC is normally used in disk-drive controllers. + @param uint16_t crc (0x0000..0xFFFF) + @param uint8_t a (0x00..0xFF) + @return calculated CRC (0x0000..0xFFFF) +*/ + +static uint16_t crc16_update(uint16_t crc, uint8_t a) +{ + int i; + + crc ^= a; + for (i = 0; i < 8; ++i) + { + if (crc & 1) + crc = (crc >> 1) ^ 0xA001; + else + crc = (crc >> 1); + } + + return crc; +} + + + #ifdef USE_ESPNOW // Set ESP-NOW send and receive callbacks for either ESP8266 or ESP32 #if defined(ESP8266) @@ -320,13 +358,17 @@ void releaseLogBuffer() #ifdef USE_SD_LOG DBG("Releasing Log buffer to SD"); File logfile = SD.open(SD_FILENAME, FILE_WRITE); - logfile.print(logBuffer); + if((logfile.size()/1024.0) < SD_MAX_FILESIZE){ + logfile.print(logBuffer); + } logfile.close(); #endif #ifdef USE_FS_LOG DBG("Releasing Log buffer to internal flash."); File logfile = LittleFS.open(FS_FILENAME, "a"); - logfile.print(logBuffer); + if((logfile.size()/1024.0) < FS_MAX_FILESIZE){ + logfile.print(logBuffer); + } logfile.close(); #endif memset(&(logBuffer[0]), 0, sizeof(logBuffer) / sizeof(char)); @@ -334,24 +376,39 @@ void releaseLogBuffer() } #endif // USE_XX_LOG +uint16_t stringCrc(const char input[]){ + uint16_t calcCRC = 0x0000; + + for(unsigned int i = 0; i < strlen(input); i++) { + calcCRC = crc16_update(calcCRC,input[i]); + } + return calcCRC; +} + void sendLog() { #if defined (USE_SD_LOG) || defined (USE_FS_LOG) DBG("Logging to buffer"); for (int i = 0; i < ln; i++) { - char linebuf[34]; // size depends on resulting length of the formatting string - sprintf(linebuf, "%lld,%d,%d,%g\r\n", time(nullptr), theData[i].id, theData[i].t, theData[i].d); - - if (logBufferPos + strlen(linebuf) >= (sizeof(logBuffer) / sizeof(char))) // if buffer would overflow, release first + StaticJsonDocument<96> doc; + JsonObject doc_0 = doc.createNestedObject(); + doc_0["id"] = theData[i].id; + doc_0["type"] = theData[i].t; + doc_0["data"] = theData[i].d; + doc_0["time"] = time(nullptr); + String outgoingString; + serializeJson(doc, outgoingString); + outgoingString = outgoingString + " " + stringCrc(outgoingString.c_str()) + "\r\n"; + if (logBufferPos+outgoingString.length() >= (sizeof(logBuffer)/sizeof(char))) // if buffer would overflow, release first { releaseLogBuffer(); } - memcpy(&logBuffer[logBufferPos], linebuf, strlen(linebuf)); //append line to buffer - logBufferPos += strlen(linebuf); + memcpy(&logBuffer[logBufferPos], outgoingString.c_str(), outgoingString.length()); //append line to buffer + logBufferPos+=outgoingString.length(); } + time(&last_log_write); #endif //USE_xx_LOG - } void reconnect(short int attempts, bool silent) { @@ -413,28 +470,73 @@ void mqtt_callback(char* topic, byte * message, unsigned int length) { } } +void resendLog(){ + #ifdef USE_SD_LOG + DBG("Resending logged values from SD card."); + File logfile = SD.open(SD_FILENAME, FILE_READ); + while(1){ + String line = logfile.readStringUntil('\n'); + if (line.length() > 0){ // if line contains something + if (!client.publish(TOPIC_DATA_BACKLOG, line.c_str())) { + break; + }else{ + time(&last_mqtt_success); + } + }else{ + logfile.close(); + SD.remove(SD_FILENAME); // if all values are sent + break; + } + } + DBG(" Done"); + #endif + #ifdef USE_FS_LOG + DBG("Resending logged values from internal flash."); + File logfile = LittleFS.open(FS_FILENAME, "r"); + while(1){ + String line = logfile.readStringUntil('\n'); + if (line.length() > 0){ // if line contains something + uint16_t readCrc; + char data[line.length()]; + sscanf(line.c_str(),"%s %hd",data,&readCrc); + if(stringCrc(data)!=readCrc){continue;} // if CRCs don't match, skip the line + if (!client.publish(TOPIC_DATA_BACKLOG, line.c_str())) { + break; + }else{ + time(&last_mqtt_success); + } + }else{ + logfile.close(); + LittleFS.remove(FS_FILENAME); // if all values are sent + break; + } + } + DBG(" Done"); + #endif + +} + void mqtt_publish(const char* payload) { #ifdef USE_WIFI if (!client.publish(TOPIC_DATA, payload)) { DBG(" Error on sending MQTT"); sendLog(); + }else{ + #if defined (USE_SD_LOG) || defined (USE_FS_LOG) + if (last_log_write >= last_mqtt_success){ + releaseLogBuffer(); + resendLog(); + } + time(&last_mqtt_success); + #endif } #endif //USE_WIFI } -void printLoraPacket(uint8_t* p,int size) { - printf("Printing packet of size %d.",size); - for(int i = 0; i < size; i++ ) { - if(i % 2 == 0) printf("\n%02d: ", i); - printf("%02X ", p[i]); - } - printf("\n"); -} - -void getLoRa() { +crcResult getLoRa() { #ifdef USE_LORA int packetSize = LoRa.parsePacket(); - if((packetSize - 6) % sizeof(DataReading) == 0 && packetSize > 0) { // packet size should be 6 bytes plus multiple of size of DataReading + if((((packetSize - 6) % sizeof(DataReading) == 0) || ((packetSize - 6) % sizeof(SystemPacket) == 0)) && packetSize > 0) { // packet size should be 6 bytes plus multiple of size of DataReading uint8_t packet[packetSize]; uint16_t packetCRC = 0x0000; // CRC Extracted from received LoRa packet uint16_t calcCRC = 0x0000; // CRC calculated from received LoRa packet @@ -442,7 +544,6 @@ void getLoRa() { uint16_t destMAC = 0x0000; LoRa.readBytes((uint8_t *)&packet, packetSize); - ln = (packetSize - 6) / sizeof(DataReading); destMAC = (packet[0] << 8) | packet[1]; sourceMAC = (packet[2] << 8) | packet[3]; @@ -450,7 +551,6 @@ void getLoRa() { //DBG("Packet Address: 0x" + String(packet[0], HEX) + String(packet[1], HEX) + " Self Address: 0x" + String(selfAddress[4], HEX) + String(selfAddress[5], HEX)); if (destMAC == (selfAddress[4] << 8 | selfAddress[5])) { //Check if addressed to this device (2 bytes, bytes 1 and 2) //printLoraPacket(packet,sizeof(packet)); - memcpy(&theData, &packet[4], packetSize - 6); //Split off data portion of packet (N - 6 bytes (6 bytes for headers and CRC)) if(receivedLoRaMsg != 0){ // Avoid divide by 0 DBG("Incoming LoRa. Size: " + String(packetSize) + " Bytes, RSSI: " + String(LoRa.packetRssi()) + "dBm, SNR: " + String(LoRa.packetSnr()) + "dB, PacketCRC: 0x" + String(packetCRC, HEX) + ", Total LoRa received: " + String(receivedLoRaMsg) + ", CRC Ok Pct " + String((float)ackOkLoRaMsg/receivedLoRaMsg*100) + "%"); } @@ -463,34 +563,92 @@ void getLoRa() { //printf("CRC: %02X : %d\n",calcCRC, i); calcCRC = crc16_update(calcCRC, packet[i]); } - if(calcCRC == packetCRC) { - SystemPacket ACK = { .cmd = cmd_ack, .param = CRC_OK }; - DBG("CRC Match, sending ACK packet to sensor 0x" + String(sourceMAC, HEX) + "(hex)"); - transmitLoRa(&sourceMAC, &ACK, 1); // Send ACK back to source - ackOkLoRaMsg++; - } - else if(packetCRC == crc16_update(calcCRC,0xA1)) { // Sender does not want ACK and CRC is valid - DBG("Sensor address 0x" + String(sourceMAC, HEX) + "(hex) does not want ACK"); - ackOkLoRaMsg++; - } - else { - SystemPacket NAK = { .cmd = cmd_ack, .param = CRC_BAD }; - // Send NAK packet to sensor - DBG("CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX) + " Sending NAK packet to sensor 0x" + String(sourceMAC, HEX) + "(hex)"); - transmitLoRa(&sourceMAC, &NAK, 1); // CRC did not match so send NAK to source - newData = event_clear; // do not process data as data may be corrupt - return; // Exit function and do not update newData to send invalid data further on + if((packetSize - 6) % sizeof(DataReading) == 0) { // DataReading type packet + if(calcCRC == packetCRC) { + SystemPacket ACK = { .cmd = cmd_ack, .param = CRC_OK }; + DBG("CRC Match, sending ACK packet to sensor 0x" + String(sourceMAC, HEX) + "(hex)"); + transmitLoRa(&sourceMAC, &ACK, 1); // Send ACK back to source + } + else if(packetCRC == crc16_update(calcCRC,0xA1)) { // Sender does not want ACK and CRC is valid + DBG("Sensor address 0x" + String(sourceMAC,16) + "(hex) does not want ACK"); + } + else { + SystemPacket NAK = { .cmd = cmd_ack, .param = CRC_BAD }; + // Send NAK packet to sensor + DBG("CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX) + " Sending NAK packet to sensor 0x" + String(sourceMAC, HEX) + "(hex)"); + transmitLoRa(&sourceMAC, &NAK, 1); // CRC did not match so send NAK to source + newData = event_clear; // do not process data as data may be corrupt + return CRC_BAD; // Exit function and do not update newData to send invalid data further on + } + memcpy(&theData, &packet[4], packetSize - 6); //Split off data portion of packet (N - 6 bytes (6 bytes for headers and CRC)) + ln = (packetSize - 6) / sizeof(DataReading); + ackOkLoRaMsg++; + if (memcmp(&sourceMAC, &LoRa1, 2) == 0) { //Check if it is from a registered sender + newData = event_lora1; + return CRC_OK; + } + if (memcmp(&sourceMAC, &LoRa2, 2) == 0) { + newData = event_lora2; + return CRC_OK; + } + newData = event_lorag; + return CRC_OK; } + else if((packetSize - 6) % sizeof(SystemPacket) == 0) { + uint ln = (packetSize - 6) / sizeof(SystemPacket); + SystemPacket receiveData[ln]; - if (memcmp(&sourceMAC, &LoRa1, 2) == 0) { //Check if it is from a registered sender - newData = event_lora1; - return; + if(calcCRC == packetCRC) { + memcpy(receiveData, &packet[4], packetSize - 6); //Split off data portion of packet (N bytes) + if(ln == 1 && receiveData[0].cmd == cmd_ack) { + DBG("ACK Received - CRC Match"); + } + else if(ln == 1 && receiveData[0].cmd == cmd_ping) { // We have received a ping request or reply?? + if(receiveData[0].param == 1) { // This is a reply to our ping request + is_ping = true; + DBG("We have received a ping reply via LoRa from address " + String(sourceMAC, HEX)); + } + else if(receiveData[0].param == 0) { + DBG("We have received a ping request from 0x" + String(sourceMAC, HEX) + ", Replying."); + SystemPacket pingReply = { .cmd = cmd_ping, .param = 1 }; + transmitLoRa(&sourceMAC, &pingReply, 1); + } + } + else { // data we have received is not yet programmed. How we handle is future enhancement. + DBG("Received some LoRa SystemPacket data that is not yet handled. To be handled in future enhancement."); + DBG("ln: " + String(ln) + "data type: " + String(receiveData[0].cmd)); + } + ackOkLoRaMsg++; + return CRC_OK; + } + else if(packetCRC == crc16_update(calcCRC,0xA1)) { // Sender does not want ACK and CRC is valid + memcpy(receiveData, &packet[4], packetSize - 6); //Split off data portion of packet (N bytes) + if(ln == 1 && receiveData[0].cmd == cmd_ack) { + DBG("ACK Received - CRC Match"); + } + else if(ln == 1 && receiveData[0].cmd == cmd_ping) { // We have received a ping request or reply?? + if(receiveData[0].param == 1) { // This is a reply to our ping request + is_ping = true; + DBG("We have received a ping reply via LoRa from address " + String(sourceMAC, HEX)); + } + else if(receiveData[0].param == 0) { + DBG("We have received a ping request from 0x" + String(sourceMAC, HEX) + ", Replying."); + SystemPacket pingReply = { .cmd = cmd_ping, .param = 1 }; + transmitLoRa(&sourceMAC, &pingReply, 1); + } + } + else { // data we have received is not yet programmed. How we handle is future enhancement. + DBG("Received some LoRa SystemPacket data that is not yet handled. To be handled in future enhancement."); + DBG("ln: " + String(ln) + "data type: " + String(receiveData[0].cmd)); + } + ackOkLoRaMsg++; + return CRC_OK; + } + else { + DBG("ACK Received CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX)); + return CRC_BAD; + } } - if (memcmp(&sourceMAC, &LoRa2, 2) == 0) { - newData = event_lora2; - return; - } - newData = event_lorag; } else { DBG("Incoming LoRa packet of " + String(packetSize) + " bytes received from address 0x" + String(sourceMAC, HEX) + " destined for node address 0x" + String(destMAC, HEX)); @@ -501,6 +659,7 @@ void getLoRa() { DBG("Incoming LoRa packet of " + String(packetSize) + "bytes not processed."); } } + return CRC_NULL; #endif //USE_LORA } @@ -527,8 +686,9 @@ void transmitLoRa(uint16_t* destMac, DataReading * packet, uint8_t len) { LoRa.write((uint8_t*)&pkt, sizeof(pkt)); LoRa.endPacket(); } -} -#endif // USE_LORA + +#endif //USE_LORA + void sendESPNOW(uint8_t address) { #ifdef USE_ESPNOW @@ -560,7 +720,9 @@ void sendESPNOW(uint8_t address) { esp_now_send(temp_peer, (uint8_t *) &thePacket, j * sizeof(DataReading)); esp_now_del_peer(temp_peer); + #endif // USE_ESPNOW + } void sendSerial() { @@ -588,6 +750,7 @@ void sendMQTT() { doc[i]["id"] = theData[i].id; doc[i]["type"] = theData[i].t; doc[i]["data"] = theData[i].d; + doc[i]["time"] = time(nullptr); } String outgoingString; serializeJson(doc, outgoingString); @@ -619,7 +782,9 @@ void bufferESPNOW(uint8_t interface) { lenESPNOW2 += ln; break; } + #endif // USE_ESPNOW + } void bufferSerial() { @@ -725,6 +890,7 @@ void releaseESPNOW(uint8_t interface) { break; } } + #endif // USE_ESPNOW } @@ -947,11 +1113,13 @@ void handleCommands() { DBG("Add sender to peer list (not completed)"); break; } + is_ping = false; theCmd.cmd = cmd_clear; theCmd.param = 0; } + // CRC16 from https://github.com/4-20ma/ModbusMaster/blob/3a05ff87677a9bdd8e027d6906dc05ca15ca8ade/src/util/crc16.h#L71 /** @ingroup util_crc16 @@ -964,8 +1132,7 @@ void handleCommands() { @return calculated CRC (0x0000..0xFFFF) */ -static uint16_t crc16_update(uint16_t crc, uint8_t a) -{ +static uint16_t crc16_update(uint16_t crc, uint8_t a) { int i; crc ^= a; @@ -980,4 +1147,14 @@ static uint16_t crc16_update(uint16_t crc, uint8_t a) return crc; } -#endif //__FDRS_FUNCTIONS_H__ \ No newline at end of file +void printLoraPacket(uint8_t* p,int size) { + printf("Printing packet of size %d.",size); + for(int i = 0; i < size; i++ ) { + if(i % 2 == 0) printf("\n%02d: ", i); + printf("%02X ", p[i]); + } + printf("\n"); +} + +#endif //__FDRS_FUNCTIONS_H__ + diff --git a/FDRS_Gateway/fdrs_gateway_config.h b/FDRS_Gateway/fdrs_gateway_config.h index 9e114c6..16f9241 100644 --- a/FDRS_Gateway/fdrs_gateway_config.h +++ b/FDRS_Gateway/fdrs_gateway_config.h @@ -43,8 +43,10 @@ //#define USE_FS_LOG //Enable filesystem (flash) logging #define LOGBUF_DELAY 10000 // Log Buffer Delay - in milliseconds #define SD_SS 0 //SD card CS pin (Use different pins for LoRa and SD) -#define SD_FILENAME "fdrs_log.csv" -#define FS_FILENAME "fdrs_log.csv" +#define SD_FILENAME "fdrs_log.txt" +#define SD_MAX_FILESIZE 1024 // maximum size of logfile in KiB +#define FS_FILENAME "fdrs_log.txt" +#define FS_MAX_FILESIZE 1024 // maximum size of logfile in KiB // SPI Configuration -- Needed only on Boards with multiple SPI interfaces like the ESP32 @@ -92,5 +94,6 @@ // MQTT Topics #define TOPIC_DATA "fdrs/data" +#define TOPIC_DATA_BACKLOG "fdrs/data" // used for resending data logged when MQTT was disconnected #define TOPIC_STATUS "fdrs/status" #define TOPIC_COMMAND "fdrs/command" diff --git a/FDRS_Sensor/fdrs_sensor.h b/FDRS_Sensor/fdrs_sensor.h index b670d41..97cc3ef 100644 --- a/FDRS_Sensor/fdrs_sensor.h +++ b/FDRS_Sensor/fdrs_sensor.h @@ -75,7 +75,7 @@ enum crcResult{ CRC_NULL, CRC_OK, CRC_BAD, -} returnCRC; +} returnCRC = CRC_NULL; enum { cmd_clear, @@ -90,12 +90,12 @@ typedef struct __attribute__((packed)) SystemPacket { } SystemPacket; const uint16_t espnow_size = 250 / sizeof(DataReading); -uint8_t gatewayAddress[] = {MAC_PREFIX, GTWY_MAC}; +const uint8_t gatewayAddress[] = {MAC_PREFIX, GTWY_MAC}; uint16_t gtwyAddress = ((gatewayAddress[4] << 8) | GTWY_MAC); -uint16_t LoRaAddress = ((gatewayAddress[4] << 8) | READING_ID); -uint16_t sensorAddress = ((gatewayAddress[4] << 8) | READING_ID); -unsigned long transmitLoRaMsg = 0; // Number of total LoRa packets destined for us and of valid size -unsigned long msgOkLoRa = 0; // Number of total LoRa packets with valid CRC +const uint16_t LoRaAddress = ((gatewayAddress[4] << 8) | READING_ID); +const uint16_t sensorAddress = ((gatewayAddress[4] << 8) | READING_ID); +unsigned long transmitLoRaMsgwAck = 0; // Number of total LoRa packets transmitted and we expect ACK in return +unsigned long msgOkLoRa = 0; // Number of total LoRa packets ACKed with valid CRC uint32_t wait_time = 0; @@ -196,17 +196,19 @@ void beginFDRS() { } -// USED to get ACKs from LoRa gateway at this point. May be used in the future to get other data +// getLoRa for Sensors +// USED to get ACKs (SystemPacket type) from LoRa gateway at this point. May be used in the future to get other data // Return type is crcResult struct - CRC_OK, CRC_BAD, CRC_NULL. CRC_NULL used for non-ack data crcResult getLoRa() { #ifdef USE_LORA int packetSize = LoRa.parsePacket(); - if ((packetSize - 6) % sizeof(SystemPacket) == 0 && packetSize > 0) { // packet size should be 6 bytes plus multiple of size of SystemPacket + if((packetSize - 6) % sizeof(SystemPacket) == 0 && packetSize > 0) { // packet size should be 6 bytes plus multiple of size of SystemPacket uint8_t packet[packetSize]; - uint16_t sourceMAC = 0x0000; - uint16_t destMAC = 0x0000; uint16_t packetCRC = 0x0000; // CRC Extracted from received LoRa packet uint16_t calcCRC = 0x0000; // CRC calculated from received LoRa packet + uint16_t sourceMAC = 0x0000; + uint16_t destMAC = 0x0000; + uint ln = (packetSize - 6) / sizeof(SystemPacket); SystemPacket receiveData[ln]; @@ -216,36 +218,62 @@ crcResult getLoRa() { sourceMAC = (packet[2] << 8) | packet[3]; packetCRC = ((packet[packetSize - 2] << 8) | packet[packetSize - 1]); DBG("Incoming LoRa. Size: " + String(packetSize) + " Bytes, RSSI: " + String(LoRa.packetRssi()) + "dBm, SNR: " + String(LoRa.packetSnr()) + "dB, PacketCRC: 0x" + String(packetCRC, HEX)); - if (destMAC == LoRaAddress) { + if (destMAC == LoRaAddress) { // The packet is for us so let's process it //printLoraPacket(packet,sizeof(packet)); - memcpy(receiveData, &packet[4], packetSize - 6); //Split off data portion of packet (N bytes) - if(ln == 1 && receiveData[0].cmd == cmd_ack) { // We have received an ACK packet - if(packetCRC == 0xFFFF) { - DBG("ACK Received - address 0x" + String(sourceMAC, HEX) + "(hex) does not want ACKs"); - return CRC_OK; - } - else { - for(int i = 0; i < (packetSize - 2); i++) { // Last 2 bytes of packet are the CRC so do not include them in calculation - //printf("CRC: %02X : %d\n",calcCRC, i); - calcCRC = crc16_update(calcCRC, packet[i]); - } - if(calcCRC == packetCRC) { - DBG("ACK Received - CRC Match"); - return CRC_OK; - } - else { - DBG("ACK Received CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX)); - return CRC_BAD; - } - } + for(int i = 0; i < (packetSize - 2); i++) { // Last 2 bytes of packet are the CRC so do not include them in calculation + //printf("CRC: %02X : %d\n",calcCRC, i); + calcCRC = crc16_update(calcCRC, packet[i]); } - else{ // data we have received is not of type ACK_T. How we handle is future enhancement. - DBG("Received some LoRa SystemPacket data that is not of type ACK. To be handled in future enhancement."); - DBG("ln: " + String(ln) + "data type: " + String(receiveData[0].cmd)); - return CRC_NULL; + if(calcCRC == packetCRC) { + memcpy(receiveData, &packet[4], packetSize - 6); //Split off data portion of packet (N bytes) + if(ln == 1 && receiveData[0].cmd == cmd_ack) { + DBG("ACK Received - CRC Match"); + } + else if(ln == 1 && receiveData[0].cmd == cmd_ping) { // We have received a ping request or reply?? + if(receiveData[0].param == 1) { // This is a reply to our ping request + is_ping = true; + DBG("We have received a ping reply via LoRa from address 0x" + String(sourceMAC, HEX)); + } + else if(receiveData[0].param == 0) { + DBG("We have received a ping request from 0x" + String(sourceMAC, HEX) + ", Replying."); + SystemPacket pingReply = { .cmd = cmd_ping, .param = 1 }; + transmitLoRa(&sourceMAC, &pingReply, 1); + } + } + else { // data we have received is not yet programmed. How we handle is future enhancement. + DBG("Received some LoRa SystemPacket data that is not yet handled. To be handled in future enhancement."); + DBG("ln: " + String(ln) + "data type: " + String(receiveData[0].cmd)); + } + return CRC_OK; + } + else if(packetCRC == crc16_update(calcCRC,0xA1)) { // Sender does not want ACK and CRC is valid + memcpy(receiveData, &packet[4], packetSize - 6); //Split off data portion of packet (N bytes) + if(ln == 1 && receiveData[0].cmd == cmd_ack) { + DBG("ACK Received - CRC Match"); + } + else if(ln == 1 && receiveData[0].cmd == cmd_ping) { // We have received a ping request or reply?? + if(receiveData[0].param == 1) { // This is a reply to our ping request + is_ping = true; + DBG("We have received a ping reply via LoRa from address 0x" + String(sourceMAC, HEX)); + } + else if(receiveData[0].param == 0) { + DBG("We have received a ping request from 0x" + String(sourceMAC, HEX) + ", Replying."); + SystemPacket pingReply = { .cmd = cmd_ping, .param = 1 }; + transmitLoRa(&sourceMAC, &pingReply, 1); + } + } + else { // data we have received is not yet programmed. How we handle is future enhancement. + DBG("Received some LoRa SystemPacket data that is not yet handled. To be handled in future enhancement."); + DBG("ln: " + String(ln) + "data type: " + String(receiveData[0].cmd)); + } + return CRC_OK; + } + else { + DBG("ACK Received CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX)); + return CRC_BAD; } } - else if((packetSize - 6) % sizeof(DataReading) == 0) { // packet size should be 6 bytes plus multiple of size of DataReading) + else if((packetSize - 6) % sizeof(DataReading) == 0 && packetSize > 0) { // packet size should be 6 bytes plus multiple of size of DataReading) DBG("Incoming LoRa packet of " + String(packetSize) + " bytes received, with DataReading data to be processed."); return CRC_NULL; } @@ -257,21 +285,13 @@ crcResult getLoRa() { else { if(packetSize != 0) { DBG("Incoming LoRa packet of " + String(packetSize) + " bytes received"); + return CRC_NULL; } } return CRC_NULL; #endif } -void printLoraPacket(uint8_t* p,int size) { - printf("Printing packet of size %d.",size); - for(int i = 0; i < size; i++ ) { - if(i % 2 == 0) printf("\n%02d: ", i); - printf("%02X ", p[i]); - } - printf("\n"); -} - void transmitLoRa(uint16_t* destMAC, DataReading * packet, uint8_t len) { #ifdef USE_LORA uint8_t pkt[6 + (len * sizeof(DataReading))]; @@ -294,15 +314,15 @@ void transmitLoRa(uint16_t* destMAC, DataReading * packet, uint8_t len) { #ifdef LORA_ACK // Wait for ACK int retries = LORA_RETRIES + 1; while(retries != 0) { - if(transmitLoRaMsg != 0) - DBG("Transmitting LoRa message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to gateway 0x" + String(*destMAC, HEX) + ". Retries remaining: " + String(retries - 1) + ", CRC OK " + String((float)msgOkLoRa/transmitLoRaMsg*100) + "%"); + if(transmitLoRaMsgwAck != 0) + DBG("Transmitting LoRa message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to gateway 0x" + String(*destMAC, HEX) + ". Retries remaining: " + String(retries - 1) + ", Ack Ok " + String((float)msgOkLoRa/transmitLoRaMsgwAck*100) + "%"); else DBG("Transmitting LoRa message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to gateway 0x" + String(*destMAC, HEX) + ". Retries remaining: " + String(retries - 1)); //printLoraPacket(pkt,sizeof(pkt)); LoRa.beginPacket(); LoRa.write((uint8_t*)&pkt, sizeof(pkt)); LoRa.endPacket(); - transmitLoRaMsg++; + transmitLoRaMsgwAck++; unsigned long loraAckTimeout = millis() + LORA_ACK_TIMEOUT; retries--; delay(10); @@ -329,11 +349,37 @@ void transmitLoRa(uint16_t* destMAC, DataReading * packet, uint8_t len) { LoRa.beginPacket(); LoRa.write((uint8_t*)&pkt, sizeof(pkt)); LoRa.endPacket(); - transmitLoRaMsg++; + transmitLoRaMsgwAck++; #endif // LORA_ACK #endif // USE_LORA } +// For now SystemPackets will not use ACK but will calculate CRC +void transmitLoRa(uint16_t* destMAC, SystemPacket* packet, uint8_t len) { +#ifdef USE_LORA + uint8_t pkt[6 + (len * sizeof(SystemPacket))]; + uint16_t calcCRC = 0x0000; + + pkt[0] = (*destMAC >> 8); + pkt[1] = (*destMAC & 0x00FF); + pkt[2] = (LoRaAddress >> 8); + pkt[3] = (LoRaAddress & 0x00FF); + memcpy(&pkt[4], packet, len * sizeof(SystemPacket)); + for(int i = 0; i < (sizeof(pkt) - 2); i++) { // Last 2 bytes are CRC so do not include them in the calculation itself + //printf("CRC: %02X : %d\n",calcCRC, i); + calcCRC = crc16_update(calcCRC, pkt[i]); + } + calcCRC = crc16_update(calcCRC, 0xA1); // Recalculate CRC for No ACK + pkt[len * sizeof(SystemPacket) + 4] = (calcCRC >> 8); + pkt[len * sizeof(SystemPacket) + 5] = (calcCRC & 0x00FF); + DBG("Transmitting LoRa message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to destination 0x" + String(*destMAC, HEX)); + //printLoraPacket(pkt,sizeof(pkt)); + LoRa.beginPacket(); + LoRa.write((uint8_t*)&pkt, sizeof(pkt)); + LoRa.endPacket(); +#endif // USE_LORA +} + void sendFDRS() { DBG("Sending FDRS Packet!"); #ifdef USE_ESPNOW @@ -343,6 +389,7 @@ void sendFDRS() { #endif #ifdef USE_LORA transmitLoRa(>wyAddress, fdrsData, data_count); + DBG(" LoRa sent."); #endif data_count = 0; returnCRC = CRC_NULL; @@ -378,6 +425,7 @@ void sleepFDRS(int sleep_time) { void pingFDRS(int timeout) { SystemPacket sys_packet; sys_packet.cmd = cmd_ping; + sys_packet.param = 0; // 0 for Ping Request and 1 for Ping Reply??? #ifdef USE_ESPNOW esp_now_send(gatewayAddress, (uint8_t *) &sys_packet, sizeof(SystemPacket)); DBG(" ESP-NOW ping sent."); @@ -387,15 +435,29 @@ void pingFDRS(int timeout) { yield(); //do I need to yield or does it automatically? if (is_ping) { DBG("Ping Returned:" + String(millis() - ping_start)); + is_ping = false; break; } } - -#endif +#endif // USE_ESPNOW #ifdef USE_LORA - //transmitLoRa(gtwyAddress, sys_packet, data_count); // TODO: Make this congruent to esp_now_send() - DBG(" LoRa ping not sent because it isn't implemented."); -#endif + transmitLoRa(>wyAddress, &sys_packet, 1); // TODO: Make this congruent to esp_now_send() + DBG("LoRa ping sent to gateway address: 0x" + String(gtwyAddress, HEX)); + uint32_t ping_start = millis(); + is_ping = false; + while ((millis() - ping_start) <= timeout) { + getLoRa(); + yield(); //do I need to yield or does it automatically? + if(is_ping) { + DBG("LoRa Ping Returned: " + String(millis() - ping_start) + "ms."); + break; + } + } + if(!is_ping) { + DBG("No LoRa ping returned within " + String(timeout) + "ms."); + } + is_ping = false; +#endif // USE_LORA } // CRC16 from https://github.com/4-20ma/ModbusMaster/blob/3a05ff87677a9bdd8e027d6906dc05ca15ca8ade/src/util/crc16.h#L71 @@ -424,4 +486,15 @@ static uint16_t crc16_update(uint16_t crc, uint8_t a) } return crc; -} \ No newline at end of file +} + +#ifdef USE_LORA +void printLoraPacket(uint8_t* p,int size) { + printf("Printing packet of size %d.",size); + for(int i = 0; i < size; i++ ) { + if(i % 2 == 0) printf("\n%02d: ", i); + printf("%02X ", p[i]); + } + printf("\n"); +} +#endif // USE_LORA \ No newline at end of file diff --git a/fdrs_checkConfig.h b/fdrs_checkConfig.h index ae874f2..f2433bb 100644 --- a/fdrs_checkConfig.h +++ b/fdrs_checkConfig.h @@ -2,11 +2,18 @@ #ifndef __FDRS_CHECKCONFIG_h__ #define __FDRS_CHECKCONFIG_h__ -char* separatorLine2 = "----------------------------------------------------"; +const char* separatorLine2 = "----------------------------------------------------"; + +// helper function for obfuscating passwords +String obfuscatePassword(String password) { + char obfuscatedPass[password.length()]; + std::fill(obfuscatedPass, obfuscatedPass + password.length(), '*'); + return String(obfuscatedPass); +} // helper function for a nice little header above each section -void printSmallSectionHeader(char* headerText) { - char * separatorLine = "----------------------------------------------------"; +void printSmallSectionHeader(const char* headerText) { + const char * separatorLine = "----------------------------------------------------"; DBG(separatorLine); DBG(headerText); @@ -14,8 +21,8 @@ void printSmallSectionHeader(char* headerText) { } // helper function for a nice little header above each section -void printSectionHeader(char* headerText) { - char * separatorLine = "----------------------------------------------------"; +void printSectionHeader(const char* headerText) { + const char * separatorLine = "----------------------------------------------------"; DBG(separatorLine); DBG(headerText); @@ -23,8 +30,8 @@ void printSectionHeader(char* headerText) { } // helper function for a nice little header above each section -void printConfigHeader(char* headerText) { - char * headerAndFooter = "===================================================="; +void printConfigHeader(const char* headerText) { + const char * headerAndFooter = "===================================================="; DBG(headerAndFooter); DBG(headerText); @@ -83,9 +90,9 @@ void printWifiDetails() { #endif //WIFI_SSID #if defined(WIFI_PASS) - DBG("WiFi password used from WIFI_PASS : " + String(FDRS_WIFI_PASS)); + DBG("WiFi password used from WIFI_PASS : " + obfuscatePassword(FDRS_WIFI_PASS)); #elif defined (GLOBAL_SSID) - DBG("WiFi password used from GLOBAL_PASS : " + String(FDRS_WIFI_PASS)); + DBG("WiFi password used from GLOBAL_PASS : " + obfuscatePassword(FDRS_WIFI_PASS)); #else DBG("NO WiFi password defined! Please define in fdrs_globals.h (recommended) or in fdrs_sensor_config.h"); //exit(0); @@ -123,9 +130,9 @@ void printWifiDetails() { #endif //MQTT_USER #if defined(MQTT_PASS) - DBG("MQTT password used from MQTT_PASS : " + String(FDRS_MQTT_PASS)); + DBG("MQTT password used from MQTT_PASS : " + obfuscatePassword(FDRS_MQTT_PASS)); #elif defined (GLOBAL_MQTT_PASS) - DBG("MQTT password used from GLOBAL_MQTT_PASS : " + String(FDRS_MQTT_PASS)); + DBG("MQTT password used from GLOBAL_MQTT_PASS : " + obfuscatePassword(FDRS_MQTT_PASS)); #else DBG("NO MQTT password defined! Please define in fdrs_globals.h (recommended) or in fdrs_sensor_config.h"); //exit(0); diff --git a/fdrs_functions.h b/fdrs_functions.h index dda06e6..a1f7c56 100644 --- a/fdrs_functions.h +++ b/fdrs_functions.h @@ -202,6 +202,7 @@ uint32_t timeLOGBUF = 0; DataReading theData[256]; uint8_t ln; uint8_t newData = event_clear; +bool is_ping = false; #ifdef USE_ESPNOW DataReading ESPNOW1buffer[256]; @@ -256,6 +257,11 @@ const char* mqtt_pass = NULL; #endif //USE_WIFI +// Function prototypes +void transmitLoRa(uint16_t*, DataReading*, uint8_t); +void transmitLoRa(uint16_t*, SystemPacket*, uint8_t); +static uint16_t crc16_update(uint16_t, uint8_t); + #ifdef USE_ESPNOW // Set ESP-NOW send and receive callbacks for either ESP8266 or ESP32 #if defined(ESP8266) @@ -413,19 +419,10 @@ void mqtt_publish(const char* payload) { #endif //USE_WIFI } -void printLoraPacket(uint8_t* p,int size) { - printf("Printing packet of size %d.",size); - for(int i = 0; i < size; i++ ) { - if(i % 2 == 0) printf("\n%02d: ", i); - printf("%02X ", p[i]); - } - printf("\n"); -} - -void getLoRa() { +crcResult getLoRa() { #ifdef USE_LORA int packetSize = LoRa.parsePacket(); - if((packetSize - 6) % sizeof(DataReading) == 0 && packetSize > 0) { // packet size should be 6 bytes plus multiple of size of DataReading + if((((packetSize - 6) % sizeof(DataReading) == 0) || ((packetSize - 6) % sizeof(SystemPacket) == 0)) && packetSize > 0) { // packet size should be 6 bytes plus multiple of size of DataReading uint8_t packet[packetSize]; uint16_t packetCRC = 0x0000; // CRC Extracted from received LoRa packet uint16_t calcCRC = 0x0000; // CRC calculated from received LoRa packet @@ -433,7 +430,6 @@ void getLoRa() { uint16_t destMAC = 0x0000; LoRa.readBytes((uint8_t *)&packet, packetSize); - ln = (packetSize - 6) / sizeof(DataReading); destMAC = (packet[0] << 8) | packet[1]; sourceMAC = (packet[2] << 8) | packet[3]; @@ -441,7 +437,6 @@ void getLoRa() { //DBG("Packet Address: 0x" + String(packet[0], HEX) + String(packet[1], HEX) + " Self Address: 0x" + String(selfAddress[4], HEX) + String(selfAddress[5], HEX)); if (destMAC == (selfAddress[4] << 8 | selfAddress[5])) { //Check if addressed to this device (2 bytes, bytes 1 and 2) //printLoraPacket(packet,sizeof(packet)); - memcpy(&theData, &packet[4], packetSize - 6); //Split off data portion of packet (N - 6 bytes (6 bytes for headers and CRC)) if(receivedLoRaMsg != 0){ // Avoid divide by 0 DBG("Incoming LoRa. Size: " + String(packetSize) + " Bytes, RSSI: " + String(LoRa.packetRssi()) + "dBm, SNR: " + String(LoRa.packetSnr()) + "dB, PacketCRC: 0x" + String(packetCRC, HEX) + ", Total LoRa received: " + String(receivedLoRaMsg) + ", CRC Ok Pct " + String((float)ackOkLoRaMsg/receivedLoRaMsg*100) + "%"); } @@ -454,34 +449,92 @@ void getLoRa() { //printf("CRC: %02X : %d\n",calcCRC, i); calcCRC = crc16_update(calcCRC, packet[i]); } - if(calcCRC == packetCRC) { - SystemPacket ACK = { .cmd = cmd_ack, .param = CRC_OK }; - DBG("CRC Match, sending ACK packet to sensor 0x" + String(sourceMAC, HEX) + "(hex)"); - transmitLoRa(&sourceMAC, &ACK, 1); // Send ACK back to source + if((packetSize - 6) % sizeof(DataReading) == 0) { // DataReading type packet + if(calcCRC == packetCRC) { + SystemPacket ACK = { .cmd = cmd_ack, .param = CRC_OK }; + DBG("CRC Match, sending ACK packet to sensor 0x" + String(sourceMAC, HEX) + "(hex)"); + transmitLoRa(&sourceMAC, &ACK, 1); // Send ACK back to source + } + else if(packetCRC == crc16_update(calcCRC,0xA1)) { // Sender does not want ACK and CRC is valid + DBG("Sensor address 0x" + String(sourceMAC,16) + "(hex) does not want ACK"); + } + else { + SystemPacket NAK = { .cmd = cmd_ack, .param = CRC_BAD }; + // Send NAK packet to sensor + DBG("CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX) + " Sending NAK packet to sensor 0x" + String(sourceMAC, HEX) + "(hex)"); + transmitLoRa(&sourceMAC, &NAK, 1); // CRC did not match so send NAK to source + newData = event_clear; // do not process data as data may be corrupt + return CRC_BAD; // Exit function and do not update newData to send invalid data further on + } + memcpy(&theData, &packet[4], packetSize - 6); //Split off data portion of packet (N - 6 bytes (6 bytes for headers and CRC)) + ln = (packetSize - 6) / sizeof(DataReading); ackOkLoRaMsg++; + if (memcmp(&sourceMAC, &LoRa1, 2) == 0) { //Check if it is from a registered sender + newData = event_lora1; + return CRC_OK; + } + if (memcmp(&sourceMAC, &LoRa2, 2) == 0) { + newData = event_lora2; + return CRC_OK; + } + newData = event_lorag; + return CRC_OK; } - else if(packetCRC == crc16_update(calcCRC,0xA1)) { // Sender does not want ACK and CRC is valid - DBG("Sensor address 0x" + String(sourceMAC, HEX) + "(hex) does not want ACK"); - ackOkLoRaMsg++; - } - else { - SystemPacket NAK = { .cmd = cmd_ack, .param = CRC_BAD }; - // Send NAK packet to sensor - DBG("CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX) + " Sending NAK packet to sensor 0x" + String(sourceMAC, HEX) + "(hex)"); - transmitLoRa(&sourceMAC, &NAK, 1); // CRC did not match so send NAK to source - newData = event_clear; // do not process data as data may be corrupt - return; // Exit function and do not update newData to send invalid data further on - } + else if((packetSize - 6) % sizeof(SystemPacket) == 0) { + uint ln = (packetSize - 6) / sizeof(SystemPacket); + SystemPacket receiveData[ln]; - if (memcmp(&sourceMAC, &LoRa1, 2) == 0) { //Check if it is from a registered sender - newData = event_lora1; - return; + if(calcCRC == packetCRC) { + memcpy(receiveData, &packet[4], packetSize - 6); //Split off data portion of packet (N bytes) + if(ln == 1 && receiveData[0].cmd == cmd_ack) { + DBG("ACK Received - CRC Match"); + } + else if(ln == 1 && receiveData[0].cmd == cmd_ping) { // We have received a ping request or reply?? + if(receiveData[0].param == 1) { // This is a reply to our ping request + is_ping = true; + DBG("We have received a ping reply via LoRa from address " + String(sourceMAC, HEX)); + } + else if(receiveData[0].param == 0) { + DBG("We have received a ping request from 0x" + String(sourceMAC, HEX) + ", Replying."); + SystemPacket pingReply = { .cmd = cmd_ping, .param = 1 }; + transmitLoRa(&sourceMAC, &pingReply, 1); + } + } + else { // data we have received is not yet programmed. How we handle is future enhancement. + DBG("Received some LoRa SystemPacket data that is not yet handled. To be handled in future enhancement."); + DBG("ln: " + String(ln) + "data type: " + String(receiveData[0].cmd)); + } + ackOkLoRaMsg++; + return CRC_OK; + } + else if(packetCRC == crc16_update(calcCRC,0xA1)) { // Sender does not want ACK and CRC is valid + memcpy(receiveData, &packet[4], packetSize - 6); //Split off data portion of packet (N bytes) + if(ln == 1 && receiveData[0].cmd == cmd_ack) { + DBG("ACK Received - CRC Match"); + } + else if(ln == 1 && receiveData[0].cmd == cmd_ping) { // We have received a ping request or reply?? + if(receiveData[0].param == 1) { // This is a reply to our ping request + is_ping = true; + DBG("We have received a ping reply via LoRa from address " + String(sourceMAC, HEX)); + } + else if(receiveData[0].param == 0) { + DBG("We have received a ping request from 0x" + String(sourceMAC, HEX) + ", Replying."); + SystemPacket pingReply = { .cmd = cmd_ping, .param = 1 }; + transmitLoRa(&sourceMAC, &pingReply, 1); + } + } + else { // data we have received is not yet programmed. How we handle is future enhancement. + DBG("Received some LoRa SystemPacket data that is not yet handled. To be handled in future enhancement."); + DBG("ln: " + String(ln) + "data type: " + String(receiveData[0].cmd)); + } + ackOkLoRaMsg++; + return CRC_OK; + } + else { + DBG("ACK Received CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX)); + return CRC_BAD; + } } - if (memcmp(&sourceMAC, &LoRa2, 2) == 0) { - newData = event_lora2; - return; - } - newData = event_lorag; } else { DBG("Incoming LoRa packet of " + String(packetSize) + " bytes received from address 0x" + String(sourceMAC, HEX) + " destined for node address 0x" + String(destMAC, HEX)); @@ -492,6 +545,7 @@ void getLoRa() { DBG("Incoming LoRa packet of " + String(packetSize) + "bytes not processed."); } } + return CRC_NULL; #endif //USE_LORA } @@ -535,6 +589,7 @@ void transmitLoRa(uint16_t* destMac, SystemPacket * packet, uint8_t len) { //printf("CRC: %02X : %d\n",calcCRC, i); calcCRC = crc16_update(calcCRC, pkt[i]); } + calcCRC = crc16_update(calcCRC, 0xA1); // No ACK for SystemPacket messages so generate new CRC with 0xA1 pkt[(len * sizeof(SystemPacket) + 4)] = (calcCRC >> 8); // Append calculated CRC to the last 2 bytes of the packet pkt[(len * sizeof(SystemPacket) + 5)] = (calcCRC & 0x00FF); DBG("Transmitting LoRa message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to LoRa MAC 0x" + String(*destMac, HEX)); @@ -632,7 +687,7 @@ void bufferESPNOW(uint8_t interface) { lenESPNOW2 += ln; break; } -#endif USE_ESPNOW +#endif // USE_ESPNOW } void bufferSerial() { @@ -738,9 +793,10 @@ void releaseESPNOW(uint8_t interface) { break; } } -#endif USE_ESPNOW +#endif // USE_ESPNOW } + void releaseLoRa(uint8_t interface) { #ifdef USE_LORA DBG("Releasing LoRa."); @@ -898,8 +954,11 @@ void begin_lora() { DBG(" Initialization failed!"); while (1); } + + LoRa.setSpreadingFactor(FDRS_SF); LoRa.setTxPower(FDRS_TXPWR); - DBG("LoRa Initialized. Band: " + String(FDRS_BAND) + " SF: " + String(FDRS_SF) + " Tx Power: " + String(FDRS_TXPWR) + " dBm"); + DBG("LoRa Initialized. Band: " + String(FDRS_BAND) + " SF: " + String(FDRS_SF) + " Tx Power: " + String(LORA_TXPWR) + " dBm"); + #endif // USE_LORA } @@ -934,6 +993,36 @@ void begin_FS() { #endif // USE_FS_LOG } +void handleCommands() { + switch (theCmd.cmd) { + case cmd_ping: + DBG("Ping back to sender"); + SystemPacket sys_packet; + sys_packet.cmd = cmd_ping; +#if defined(ESP32) + esp_now_peer_info_t peerInfo; + peerInfo.ifidx = WIFI_IF_STA; + peerInfo.channel = 0; + peerInfo.encrypt = false; + memcpy(peerInfo.peer_addr, incMAC, 6); + if (esp_now_add_peer(&peerInfo) != ESP_OK) { + DBG("Failed to add peer"); + return; + } +#endif + esp_now_send(incMAC, (uint8_t *) &sys_packet, sizeof(SystemPacket)); + esp_now_del_peer(incMAC); + break; + case cmd_add: + DBG("Add sender to peer list (not completed)"); + break; + } + is_ping = false; + theCmd.cmd = cmd_clear; + theCmd.param = 0; +} + + // CRC16 from https://github.com/4-20ma/ModbusMaster/blob/3a05ff87677a9bdd8e027d6906dc05ca15ca8ade/src/util/crc16.h#L71 /** @ingroup util_crc16 @@ -946,8 +1035,7 @@ void begin_FS() { @return calculated CRC (0x0000..0xFFFF) */ -static uint16_t crc16_update(uint16_t crc, uint8_t a) -{ +static uint16_t crc16_update(uint16_t crc, uint8_t a) { int i; crc ^= a; @@ -962,4 +1050,13 @@ static uint16_t crc16_update(uint16_t crc, uint8_t a) return crc; } +void printLoraPacket(uint8_t* p,int size) { + printf("Printing packet of size %d.",size); + for(int i = 0; i < size; i++ ) { + if(i % 2 == 0) printf("\n%02d: ", i); + printf("%02X ", p[i]); + } + printf("\n"); +} + #endif //__FDRS_FUNCTIONS_H__ \ No newline at end of file diff --git a/fdrs_sensor.h b/fdrs_sensor.h index f2bd51a..4b7f917 100644 --- a/fdrs_sensor.h +++ b/fdrs_sensor.h @@ -75,7 +75,7 @@ enum crcResult{ CRC_NULL, CRC_OK, CRC_BAD, -} returnCRC; +} returnCRC = CRC_NULL; enum { cmd_clear, @@ -94,13 +94,34 @@ const uint8_t gatewayAddress[] = {MAC_PREFIX, GTWY_MAC}; uint16_t gtwyAddress = ((gatewayAddress[4] << 8) | GTWY_MAC); const uint16_t LoRaAddress = ((gatewayAddress[4] << 8) | READING_ID); const uint16_t sensorAddress = ((gatewayAddress[4] << 8) | READING_ID); -unsigned long transmitLoRaMsg = 0; // Number of total LoRa packets destined for us and of valid size -unsigned long msgOkLoRa = 0; // Number of total LoRa packets with valid CRC +unsigned long transmitLoRaMsgwAck = 0; // Number of total LoRa packets transmitted and we expect ACK in return +unsigned long msgOkLoRa = 0; // Number of total LoRa packets ACKed with valid CRC uint32_t wait_time = 0; DataReading fdrsData[espnow_size]; uint8_t data_count = 0; +bool is_ping = false; + +// Set ESP-NOW send and receive callbacks for either ESP8266 or ESP32 +#if defined(ESP8266) +void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) { +} +void OnDataRecv(uint8_t* mac, uint8_t *incomingData, uint8_t len) { +#elif defined(ESP32) +void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { +} +void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { +#endif + if (len < sizeof(DataReading)) { + SystemPacket command; + memcpy(&command, incomingData, sizeof(command)); + if (command.cmd == cmd_ping) { + is_ping = true; + return; + } + } +} static uint16_t crc16_update(uint16_t, uint8_t); // function prototype for Arduino compilation purposes @@ -170,17 +191,19 @@ void beginFDRS() { } -// USED to get ACKs from LoRa gateway at this point. May be used in the future to get other data +// getLoRa for Sensors +// USED to get ACKs (SystemPacket type) from LoRa gateway at this point. May be used in the future to get other data // Return type is crcResult struct - CRC_OK, CRC_BAD, CRC_NULL. CRC_NULL used for non-ack data crcResult getLoRa() { #ifdef USE_LORA int packetSize = LoRa.parsePacket(); - if ((packetSize - 6) % sizeof(SystemPacket) == 0 && packetSize > 0) { // packet size should be 6 bytes plus multiple of size of SystemPacket + if((packetSize - 6) % sizeof(SystemPacket) == 0 && packetSize > 0) { // packet size should be 6 bytes plus multiple of size of SystemPacket uint8_t packet[packetSize]; - uint16_t sourceMAC = 0x0000; - uint16_t destMAC = 0x0000; uint16_t packetCRC = 0x0000; // CRC Extracted from received LoRa packet uint16_t calcCRC = 0x0000; // CRC calculated from received LoRa packet + uint16_t sourceMAC = 0x0000; + uint16_t destMAC = 0x0000; + uint ln = (packetSize - 6) / sizeof(SystemPacket); SystemPacket receiveData[ln]; @@ -190,36 +213,62 @@ crcResult getLoRa() { sourceMAC = (packet[2] << 8) | packet[3]; packetCRC = ((packet[packetSize - 2] << 8) | packet[packetSize - 1]); DBG("Incoming LoRa. Size: " + String(packetSize) + " Bytes, RSSI: " + String(LoRa.packetRssi()) + "dBm, SNR: " + String(LoRa.packetSnr()) + "dB, PacketCRC: 0x" + String(packetCRC, HEX)); - if (destMAC == LoRaAddress) { + if (destMAC == LoRaAddress) { // The packet is for us so let's process it //printLoraPacket(packet,sizeof(packet)); - memcpy(receiveData, &packet[4], packetSize - 6); //Split off data portion of packet (N bytes) - if(ln == 1 && receiveData[0].cmd == cmd_ack) { // We have received an ACK packet - if(packetCRC == 0xFFFF) { - DBG("ACK Received - address 0x" + String(sourceMAC, HEX) + "(hex) does not want ACKs"); - return CRC_OK; - } - else { - for(int i = 0; i < (packetSize - 2); i++) { // Last 2 bytes of packet are the CRC so do not include them in calculation - //printf("CRC: %02X : %d\n",calcCRC, i); - calcCRC = crc16_update(calcCRC, packet[i]); - } - if(calcCRC == packetCRC) { - DBG("ACK Received - CRC Match"); - return CRC_OK; - } - else { - DBG("ACK Received CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX)); - return CRC_BAD; - } - } + for(int i = 0; i < (packetSize - 2); i++) { // Last 2 bytes of packet are the CRC so do not include them in calculation + //printf("CRC: %02X : %d\n",calcCRC, i); + calcCRC = crc16_update(calcCRC, packet[i]); } - else{ // data we have received is not of type ACK_T. How we handle is future enhancement. - DBG("Received some LoRa SystemPacket data that is not of type ACK. To be handled in future enhancement."); - DBG("ln: " + String(ln) + "data type: " + String(receiveData[0].cmd)); - return CRC_NULL; + if(calcCRC == packetCRC) { + memcpy(receiveData, &packet[4], packetSize - 6); //Split off data portion of packet (N bytes) + if(ln == 1 && receiveData[0].cmd == cmd_ack) { + DBG("ACK Received - CRC Match"); + } + else if(ln == 1 && receiveData[0].cmd == cmd_ping) { // We have received a ping request or reply?? + if(receiveData[0].param == 1) { // This is a reply to our ping request + is_ping = true; + DBG("We have received a ping reply via LoRa from address 0x" + String(sourceMAC, HEX)); + } + else if(receiveData[0].param == 0) { + DBG("We have received a ping request from 0x" + String(sourceMAC, HEX) + ", Replying."); + SystemPacket pingReply = { .cmd = cmd_ping, .param = 1 }; + transmitLoRa(&sourceMAC, &pingReply, 1); + } + } + else { // data we have received is not yet programmed. How we handle is future enhancement. + DBG("Received some LoRa SystemPacket data that is not yet handled. To be handled in future enhancement."); + DBG("ln: " + String(ln) + "data type: " + String(receiveData[0].cmd)); + } + return CRC_OK; + } + else if(packetCRC == crc16_update(calcCRC,0xA1)) { // Sender does not want ACK and CRC is valid + memcpy(receiveData, &packet[4], packetSize - 6); //Split off data portion of packet (N bytes) + if(ln == 1 && receiveData[0].cmd == cmd_ack) { + DBG("ACK Received - CRC Match"); + } + else if(ln == 1 && receiveData[0].cmd == cmd_ping) { // We have received a ping request or reply?? + if(receiveData[0].param == 1) { // This is a reply to our ping request + is_ping = true; + DBG("We have received a ping reply via LoRa from address 0x" + String(sourceMAC, HEX)); + } + else if(receiveData[0].param == 0) { + DBG("We have received a ping request from 0x" + String(sourceMAC, HEX) + ", Replying."); + SystemPacket pingReply = { .cmd = cmd_ping, .param = 1 }; + transmitLoRa(&sourceMAC, &pingReply, 1); + } + } + else { // data we have received is not yet programmed. How we handle is future enhancement. + DBG("Received some LoRa SystemPacket data that is not yet handled. To be handled in future enhancement."); + DBG("ln: " + String(ln) + "data type: " + String(receiveData[0].cmd)); + } + return CRC_OK; + } + else { + DBG("ACK Received CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX)); + return CRC_BAD; } } - else if((packetSize - 6) % sizeof(DataReading) == 0) { // packet size should be 6 bytes plus multiple of size of DataReading) + else if((packetSize - 6) % sizeof(DataReading) == 0 && packetSize > 0) { // packet size should be 6 bytes plus multiple of size of DataReading) DBG("Incoming LoRa packet of " + String(packetSize) + " bytes received, with DataReading data to be processed."); return CRC_NULL; } @@ -231,21 +280,13 @@ crcResult getLoRa() { else { if(packetSize != 0) { DBG("Incoming LoRa packet of " + String(packetSize) + " bytes received"); + return CRC_NULL; } } return CRC_NULL; #endif } -void printLoraPacket(uint8_t* p,int size) { - printf("Printing packet of size %d.",size); - for(int i = 0; i < size; i++ ) { - if(i % 2 == 0) printf("\n%02d: ", i); - printf("%02X ", p[i]); - } - printf("\n"); -} - void transmitLoRa(uint16_t* destMAC, DataReading * packet, uint8_t len) { #ifdef USE_LORA uint8_t pkt[6 + (len * sizeof(DataReading))]; @@ -268,15 +309,15 @@ void transmitLoRa(uint16_t* destMAC, DataReading * packet, uint8_t len) { #ifdef LORA_ACK // Wait for ACK int retries = LORA_RETRIES + 1; while(retries != 0) { - if(transmitLoRaMsg != 0) - DBG("Transmitting LoRa message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to gateway 0x" + String(*destMAC, HEX) + ". Retries remaining: " + String(retries - 1) + ", CRC OK " + String((float)msgOkLoRa/transmitLoRaMsg*100) + "%"); + if(transmitLoRaMsgwAck != 0) + DBG("Transmitting LoRa message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to gateway 0x" + String(*destMAC, HEX) + ". Retries remaining: " + String(retries - 1) + ", Ack Ok " + String((float)msgOkLoRa/transmitLoRaMsgwAck*100) + "%"); else DBG("Transmitting LoRa message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to gateway 0x" + String(*destMAC, HEX) + ". Retries remaining: " + String(retries - 1)); //printLoraPacket(pkt,sizeof(pkt)); LoRa.beginPacket(); LoRa.write((uint8_t*)&pkt, sizeof(pkt)); LoRa.endPacket(); - transmitLoRaMsg++; + transmitLoRaMsgwAck++; unsigned long loraAckTimeout = millis() + LORA_ACK_TIMEOUT; retries--; delay(10); @@ -303,11 +344,37 @@ void transmitLoRa(uint16_t* destMAC, DataReading * packet, uint8_t len) { LoRa.beginPacket(); LoRa.write((uint8_t*)&pkt, sizeof(pkt)); LoRa.endPacket(); - transmitLoRaMsg++; + transmitLoRaMsgwAck++; #endif // LORA_ACK #endif // USE_LORA } +// For now SystemPackets will not use ACK but will calculate CRC +void transmitLoRa(uint16_t* destMAC, SystemPacket* packet, uint8_t len) { +#ifdef USE_LORA + uint8_t pkt[6 + (len * sizeof(SystemPacket))]; + uint16_t calcCRC = 0x0000; + + pkt[0] = (*destMAC >> 8); + pkt[1] = (*destMAC & 0x00FF); + pkt[2] = (LoRaAddress >> 8); + pkt[3] = (LoRaAddress & 0x00FF); + memcpy(&pkt[4], packet, len * sizeof(SystemPacket)); + for(int i = 0; i < (sizeof(pkt) - 2); i++) { // Last 2 bytes are CRC so do not include them in the calculation itself + //printf("CRC: %02X : %d\n",calcCRC, i); + calcCRC = crc16_update(calcCRC, pkt[i]); + } + calcCRC = crc16_update(calcCRC, 0xA1); // Recalculate CRC for No ACK + pkt[len * sizeof(SystemPacket) + 4] = (calcCRC >> 8); + pkt[len * sizeof(SystemPacket) + 5] = (calcCRC & 0x00FF); + DBG("Transmitting LoRa message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to destination 0x" + String(*destMAC, HEX)); + //printLoraPacket(pkt,sizeof(pkt)); + LoRa.beginPacket(); + LoRa.write((uint8_t*)&pkt, sizeof(pkt)); + LoRa.endPacket(); +#endif // USE_LORA +} + void sendFDRS() { DBG("Sending FDRS Packet!"); #ifdef USE_ESPNOW @@ -317,6 +384,10 @@ void sendFDRS() { #endif #ifdef USE_LORA transmitLoRa(>wyAddress, fdrsData, data_count); + + + DBG(" LoRa sent."); + #endif data_count = 0; returnCRC = CRC_NULL; @@ -349,6 +420,45 @@ void sleepFDRS(int sleep_time) { delay(sleep_time * 1000); } + +void pingFDRS(int timeout) { + SystemPacket sys_packet; + sys_packet.cmd = cmd_ping; + sys_packet.param = 0; // 0 for Ping Request and 1 for Ping Reply??? +#ifdef USE_ESPNOW + esp_now_send(gatewayAddress, (uint8_t *) &sys_packet, sizeof(SystemPacket)); + DBG(" ESP-NOW ping sent."); + uint32_t ping_start = millis(); + is_ping = false; + while ((millis() - ping_start) <= timeout) { + yield(); //do I need to yield or does it automatically? + if (is_ping) { + DBG("Ping Returned:" + String(millis() - ping_start)); + is_ping = false; + break; + } + } +#endif // USE_ESPNOW +#ifdef USE_LORA + transmitLoRa(>wyAddress, &sys_packet, 1); // TODO: Make this congruent to esp_now_send() + DBG("LoRa ping sent to gateway address: 0x" + String(gtwyAddress, HEX)); + uint32_t ping_start = millis(); + is_ping = false; + while ((millis() - ping_start) <= timeout) { + getLoRa(); + yield(); //do I need to yield or does it automatically? + if(is_ping) { + DBG("LoRa Ping Returned: " + String(millis() - ping_start) + "ms."); + break; + } + } + if(!is_ping) { + DBG("No LoRa ping returned within " + String(timeout) + "ms."); + } + is_ping = false; +#endif // USE_LORA +} + // CRC16 from https://github.com/4-20ma/ModbusMaster/blob/3a05ff87677a9bdd8e027d6906dc05ca15ca8ade/src/util/crc16.h#L71 /** @ingroup util_crc16 @@ -375,4 +485,15 @@ static uint16_t crc16_update(uint16_t crc, uint8_t a) } return crc; -} \ No newline at end of file +} + +#ifdef USE_LORA +void printLoraPacket(uint8_t* p,int size) { + printf("Printing packet of size %d.",size); + for(int i = 0; i < size; i++ ) { + if(i % 2 == 0) printf("\n%02d: ", i); + printf("%02X ", p[i]); + } + printf("\n"); +} +#endif // USE_LORA \ No newline at end of file