2022-07-02 11:01:49 +00:00
// FARM DATA RELAY SYSTEM
//
// "fdrs_sensor.h"
//
// Developed by Timm Bogner (timmbogner@gmail.com) for Sola Gratia Farm in Urbana, Illinois, USA.
//
2022-07-05 14:02:01 +00:00
# include <fdrs_datatypes.h>
2022-07-02 11:01:49 +00:00
# if defined(ESP8266)
# include <ESP8266WiFi.h>
# include <espnow.h>
# elif defined(ESP32)
# include <esp_now.h>
# include <WiFi.h>
# include <esp_wifi.h>
# endif
# ifdef USE_LORA
# include <LoRa.h>
# endif
2022-07-21 12:01:51 +00:00
// enable to get detailed info from where single configuration macros have been taken
2022-07-22 21:41:52 +00:00
# define DEBUG_NODE_CONFIG
2022-07-21 12:01:51 +00:00
# ifdef USE_LORA
// select LoRa band configuration
# if defined(LORA_BAND)
2022-07-07 10:37:04 +00:00
# define FDRS_BAND LORA_BAND
2022-07-21 12:01:51 +00:00
# elif defined (GLOBAL_LORA_BAND)
# define FDRS_BAND GLOBAL_LORA_BAND
# else
// ASSERT("NO LORA-BAND defined! Please define in fdrs_globals.h (recommended) or in fdrs_sensor_config.h");
# endif //LORA_BAND
// select LoRa SF configuration
# if defined(LORA_SF)
2022-07-07 10:37:04 +00:00
# define FDRS_SF LORA_SF
2022-07-21 12:01:51 +00:00
# elif defined (GLOBAL_LORA_SF)
# define FDRS_SF GLOBAL_LORA_SF
# else
// ASSERT("NO LORA-SF defined! Please define in fdrs_globals.h (recommended) or in fdrs_sensor_config.h");
# endif //LORA_SF
2022-07-28 00:46:41 +00:00
// select LoRa TXPWR configuration
# if defined(LORA_TXPWR)
# define FDRS_TXPWR LORA_TXPWR
# elif defined (GLOBAL_LORA_TXPWR)
# define FDRS_TXPWR GLOBAL_LORA_TXPWR
# else
// ASSERT("NO LORA-TXPWR defined! Please define in fdrs_globals.h (recommended) or in fdrs_sensor_config.h");
# endif //LORA_TXPWR
2022-07-21 12:01:51 +00:00
# endif //USE_LORA
2022-07-02 11:01:49 +00:00
2022-07-08 06:41:58 +00:00
# ifdef FDRS_DEBUG
2022-07-02 11:01:49 +00:00
# define DBG(a) (Serial.println(a))
# else
# define DBG(a)
# endif
# define MAC_PREFIX 0xAA, 0xBB, 0xCC, 0xDD, 0xEE // Should only be changed if implementing multiple FDRS systems.
2022-07-22 21:41:52 +00:00
# ifdef DEBUG_NODE_CONFIG
# include "fdrs_checkConfig.h"
# endif
2022-07-02 11:01:49 +00:00
typedef struct __attribute__ ( ( packed ) ) DataReading {
float d ;
uint16_t id ;
uint8_t t ;
} DataReading ;
2022-07-25 18:28:21 +00:00
enum crcResult {
CRC_NULL ,
CRC_OK ,
CRC_BAD ,
} returnCRC ;
enum {
cmd_clear ,
cmd_ping ,
cmd_add ,
cmd_ack ,
} ;
2022-07-02 11:01:49 +00:00
const uint16_t espnow_size = 250 / sizeof ( DataReading ) ;
2022-07-26 18:24:46 +00:00
const uint8_t gatewayAddress [ ] = { MAC_PREFIX , GTWY_MAC } ;
const 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 ) ;
2022-07-25 18:28:21 +00:00
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
2022-07-02 11:01:49 +00:00
uint32_t wait_time = 0 ;
DataReading fdrsData [ espnow_size ] ;
uint8_t data_count = 0 ;
2022-07-21 12:01:51 +00:00
2022-07-02 11:01:49 +00:00
void beginFDRS ( ) {
2022-07-08 06:41:58 +00:00
# ifdef FDRS_DEBUG
2022-07-02 11:01:49 +00:00
Serial . begin ( 115200 ) ;
2022-07-22 21:41:52 +00:00
// find out the reset reason
esp_reset_reason_t resetReason ;
resetReason = esp_reset_reason ( ) ;
2022-07-02 11:01:49 +00:00
# endif
2022-07-26 18:24:46 +00:00
DBG ( " FDRS Sensor ID " + String ( READING_ID , 16 ) + " initializing... " ) ;
2022-07-02 11:01:49 +00:00
DBG ( " Gateway: " + String ( GTWY_MAC , HEX ) ) ;
# ifdef POWER_CTRL
DBG ( " Powering up the sensor array! " ) ;
pinMode ( POWER_CTRL , OUTPUT ) ;
digitalWrite ( POWER_CTRL , 1 ) ;
# endif
// Init ESP-NOW for either ESP8266 or ESP32 and set MAC address
# ifdef USE_ESPNOW
DBG ( " Initializing ESP-NOW! " ) ;
WiFi . mode ( WIFI_STA ) ;
WiFi . disconnect ( ) ;
# if defined(ESP8266)
if ( esp_now_init ( ) ! = 0 ) {
return ;
}
esp_now_set_self_role ( ESP_NOW_ROLE_COMBO ) ;
// Register peers
esp_now_add_peer ( gatewayAddress , ESP_NOW_ROLE_COMBO , 0 , NULL , 0 ) ;
# elif defined(ESP32)
if ( esp_now_init ( ) ! = ESP_OK ) {
DBG ( " Error initializing ESP-NOW " ) ;
return ;
}
esp_now_peer_info_t peerInfo ;
peerInfo . ifidx = WIFI_IF_STA ;
peerInfo . channel = 0 ;
peerInfo . encrypt = false ;
// Register first peer
memcpy ( peerInfo . peer_addr , gatewayAddress , 6 ) ;
if ( esp_now_add_peer ( & peerInfo ) ! = ESP_OK ) {
DBG ( " Failed to add peer " ) ;
return ;
}
# endif
DBG ( " ESP-NOW Initialized. " ) ;
2022-07-22 21:41:52 +00:00
# endif //USE_ESPNOW
2022-07-02 11:01:49 +00:00
# ifdef USE_LORA
DBG ( " Initializing LoRa! " ) ;
# ifdef ESP32
2022-07-07 10:38:57 +00:00
SPI . begin ( SPI_SCK , SPI_MISO , SPI_MOSI ) ;
2022-07-02 11:01:49 +00:00
# endif
2022-07-07 10:37:04 +00:00
LoRa . setPins ( LORA_SS , LORA_RST , LORA_DIO0 ) ;
2022-07-02 11:01:49 +00:00
if ( ! LoRa . begin ( FDRS_BAND ) ) {
DBG ( " Unable to initialize LoRa! " ) ;
while ( 1 ) ;
}
LoRa . setSpreadingFactor ( FDRS_SF ) ;
2022-07-30 19:48:47 +00:00
LoRa . setTxPower ( FDRS_TXPWR ) ;
DBG ( " LoRa Initialized. Band: " + String ( FDRS_BAND ) + " SF: " + String ( FDRS_SF ) + " Tx Power: " + String ( FDRS_TXPWR ) + " dBm " ) ;
2022-07-21 12:01:51 +00:00
# endif // USE_LORA
2022-07-22 21:41:52 +00:00
# ifdef DEBUG_NODE_CONFIG
if ( resetReason ! = ESP_RST_DEEPSLEEP ) {
checkConfig ( ) ;
}
# endif //DEBUG_NODE_CONFIG
2022-07-02 11:01:49 +00:00
}
2022-07-21 09:46:16 +00:00
2022-07-25 18:28:21 +00:00
// USED to get ACKs 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
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
uint ln = ( packetSize - 6 ) / sizeof ( SystemPacket ) ;
SystemPacket receiveData [ ln ] ;
LoRa . readBytes ( ( uint8_t * ) & packet , packetSize ) ;
destMAC = ( packet [ 0 ] < < 8 ) | packet [ 1 ] ;
sourceMAC = ( packet [ 2 ] < < 8 ) | packet [ 3 ] ;
packetCRC = ( ( packet [ packetSize - 2 ] < < 8 ) | packet [ packetSize - 1 ] ) ;
2022-07-28 00:46:41 +00:00
DBG ( " Incoming LoRa. Size: " + String ( packetSize ) + " Bytes, RSSI: " + String ( LoRa . packetRssi ( ) ) + " dBm, SNR: " + String ( LoRa . packetSnr ( ) ) + " dB, PacketCRC: 0x " + String ( packetCRC , HEX ) ) ;
2022-07-25 18:28:21 +00:00
if ( destMAC = = LoRaAddress ) {
//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 ) {
2022-07-28 00:46:41 +00:00
DBG ( " ACK Received - address 0x " + String ( sourceMAC , HEX ) + " (hex) does not want ACKs " ) ;
2022-07-25 18:28:21 +00:00
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 {
2022-07-28 00:46:41 +00:00
DBG ( " ACK Received CRC Mismatch! Packet CRC is 0x " + String ( packetCRC , HEX ) + " , Calculated CRC is 0x " + String ( calcCRC , HEX ) ) ;
2022-07-25 18:28:21 +00:00
return CRC_BAD ;
}
}
}
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 ;
}
}
else if ( ( packetSize - 6 ) % sizeof ( DataReading ) = = 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 ;
}
else {
DBG ( " Incoming LoRa packet of " + String ( packetSize ) + " bytes received, not destined to our address. " ) ;
return CRC_NULL ;
}
}
else {
if ( packetSize ! = 0 ) {
DBG ( " Incoming LoRa packet of " + String ( packetSize ) + " bytes received " ) ;
}
}
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 ) {
2022-07-02 11:01:49 +00:00
# ifdef USE_LORA
2022-07-25 18:28:21 +00:00
uint8_t pkt [ 6 + ( len * sizeof ( DataReading ) ) ] ;
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 ( DataReading ) ) ;
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 ] ) ;
}
2022-07-26 11:40:50 +00:00
# ifndef LORA_ACK
calcCRC = crc16_update ( calcCRC , 0xA1 ) ; // Recalculate CRC for No ACK
2022-07-25 18:28:21 +00:00
# endif // LORA_ACK
pkt [ len * sizeof ( DataReading ) + 4 ] = ( calcCRC > > 8 ) ;
pkt [ len * sizeof ( DataReading ) + 5 ] = ( calcCRC & 0x00FF ) ;
# ifdef LORA_ACK // Wait for ACK
int retries = LORA_RETRIES + 1 ;
while ( retries ! = 0 ) {
if ( transmitLoRaMsg ! = 0 )
2022-07-28 00:46:41 +00:00
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 ) + " % " ) ;
2022-07-25 18:28:21 +00:00
else
2022-07-28 00:46:41 +00:00
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 ) ) ;
2022-07-25 18:28:21 +00:00
//printLoraPacket(pkt,sizeof(pkt));
LoRa . beginPacket ( ) ;
LoRa . write ( ( uint8_t * ) & pkt , sizeof ( pkt ) ) ;
LoRa . endPacket ( ) ;
transmitLoRaMsg + + ;
unsigned long loraAckTimeout = millis ( ) + LORA_ACK_TIMEOUT ;
retries - - ;
delay ( 10 ) ;
while ( returnCRC = = CRC_NULL & & ( millis ( ) < loraAckTimeout ) ) {
returnCRC = getLoRa ( ) ;
}
if ( returnCRC = = CRC_OK ) {
//DBG("LoRa ACK Received! CRC OK");
msgOkLoRa + + ;
return ; // we're done
}
else if ( returnCRC = = CRC_BAD ) {
//DBG("LoRa ACK Received! CRC BAD");
// Resend original packet again if retries are available
}
else {
DBG ( " LoRa Timeout waiting for ACK! " ) ;
// resend original packet again if retries are available
}
}
# else // Send and do not wait for ACK reply
2022-07-28 00:46:41 +00:00
DBG ( " Transmitting LoRa message of size " + String ( sizeof ( pkt ) ) + " bytes with CRC 0x " + String ( calcCRC , HEX ) + " to gateway 0x " + String ( * destMAC , HEX ) ) ;
2022-07-25 18:28:21 +00:00
//printLoraPacket(pkt,sizeof(pkt));
2022-07-02 11:01:49 +00:00
LoRa . beginPacket ( ) ;
LoRa . write ( ( uint8_t * ) & pkt , sizeof ( pkt ) ) ;
LoRa . endPacket ( ) ;
2022-07-25 18:28:21 +00:00
transmitLoRaMsg + + ;
# endif // LORA_ACK
# endif // USE_LORA
2022-07-02 11:01:49 +00:00
}
2022-07-21 09:46:16 +00:00
2022-07-02 11:01:49 +00:00
void sendFDRS ( ) {
DBG ( " Sending FDRS Packet! " ) ;
# ifdef USE_ESPNOW
esp_now_send ( gatewayAddress , ( uint8_t * ) & fdrsData , data_count * sizeof ( DataReading ) ) ;
delay ( 5 ) ;
DBG ( " ESP-NOW sent. " ) ;
# endif
# ifdef USE_LORA
transmitLoRa ( gtwyAddress , fdrsData , data_count ) ;
DBG ( " LoRa sent. " ) ;
# endif
data_count = 0 ;
2022-07-25 18:28:21 +00:00
returnCRC = CRC_NULL ;
2022-07-02 11:01:49 +00:00
}
2022-07-21 09:46:16 +00:00
2022-07-02 11:01:49 +00:00
void loadFDRS ( float d , uint8_t t ) {
2022-07-26 18:24:46 +00:00
DBG ( " Id: " + String ( READING_ID , 16 ) + " - Type: " + String ( t ) + " - Data loaded: " + String ( d ) ) ;
2022-07-02 11:01:49 +00:00
if ( data_count > espnow_size ) sendFDRS ( ) ;
DataReading dr ;
dr . id = READING_ID ;
dr . t = t ;
dr . d = d ;
fdrsData [ data_count ] = dr ;
data_count + + ;
}
2022-07-21 09:46:16 +00:00
2022-07-02 11:01:49 +00:00
void sleepFDRS ( int sleep_time ) {
DBG ( " Sleepytime! " ) ;
# ifdef DEEP_SLEEP
DBG ( " Deep sleeping. " ) ;
# ifdef ESP32
esp_sleep_enable_timer_wakeup ( sleep_time * 1000000 ) ;
esp_deep_sleep_start ( ) ;
# endif
# ifdef ESP8266
ESP . deepSleep ( sleep_time * 1000000 ) ;
# endif
# endif
DBG ( " Delaying. " ) ;
delay ( sleep_time * 1000 ) ;
}
2022-07-25 18:28:21 +00:00
// 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 ) < br >
Initial value : 0xFFFF
This CRC is normally used in disk - drive controllers .
@ param uint16_t crc ( 0x0000 . .0 xFFFF )
@ param uint8_t a ( 0x00 . .0 xFF )
@ return calculated CRC ( 0x0000 . .0 xFFFF )
*/
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 ;
}