LoRaWAN smart agriculture application using FRDM-K64F ARM mbed board along with SX1272MB2xAS LoRa shield as the LoRa Node.

Dependencies:   DHT11 LMiC SX1272Lib mbed

Fork of LoRaWAN-lmic-app by Semtech

main.cpp

Committer:
GTsapparellas
Date:
2018-04-04
Revision:
9:f3e9b3a6eded
Parent:
8:f307640ed331
Child:
10:0a4ad1388e05

File content as of revision 9:f3e9b3a6eded:

/*******************************************************************************
 * Internet of Things (IoT) smart monitoring
 * device for agriculture using LoRaWAN technology.
 * 
 * LoRa Gateway:           Single-channel Dragino LG01-P LoRa Gateway 
 *
 * Measurement parameters: Temperature (Celcius)
 *                         Humidity (Relative Humidity %)
 *                         Light Intenisty (Volts)
 *                         Soil Moisture (Volts)
 *
 * Evaluation board:       FRDM-K64F ARM mbed board
 *
 * LoRa shield:            Semtech SX1272MB2xAS
 * 
 * IoT Cloud Server:       The Things Network (Europe EU-868.1 frequency band)     
 * 
 * API Platform:           All Things Talk Maker
 *
 * - Time-triggered program periodically sends playload data (including 
 * temperature, humidity, light intensity and soil moisture sensor parameters)
 * by using FRDM-K64F ARM mbed board and Semtech SX1272MB2xAS as the LoRa Node.
 *
 * - DHT library and Digital Input pin used for the successful measurement 
 * of temperature and humidity sensor parameters.
 *
 * - Analog Input pins used for the successful employement of soil moisture and
 * light intensity sensor parameters.
 *
 * - Semtech's SX1272Lib used for the successful configuration and set up of 
 * of SX1272MB2xAS LoRa shield.  
 * 
 * - IBM's LMiC library used for the successful implementation of LoRa modulation.
 *
 * - LoRa Node transmitting playload data directly to the single-channel Dragino
 * LG01-P LoRa Gateway which is connected to The Things Network Cloud Server.
 * 
 * - ABP (Activation By Personalization) selected as the activation method over 
 * The Things Network Cloud Server.
 *
 * - The Things Network Cloud Server makes playload data available online.
 *
 * - Through required integration, The Things Network Cloud Server passes 
 * playload data to All Things Talk Maker API which visualizes the data in 
 * a meaningful way for end-user's reference.  
 *
 * - Fully reset device through RESET BUTTON pressed.       
 * 
 * @Author: Giorgos Tsapparellas
 * 
 * Code available at: 1) https://os.mbed.com/users/GTsapparellas/code/LoRaWAN_mbed_lmic_agriculture_app/
 *                    2) https://github.com/GTsapparellas/LoRaWAN_mbed_lmic_agriculture_app
 *
 * SEE readME.txt file for instructions of how to compile and run the program.
 *
 *******************************************************************************/

#include <mbed.h>
#include <lmic.h>
#include <hal.h>
#include <SPI.h>
#include <DHT.h>
#include <debug.h>

///////////////////////////////////////////////////
// DEFINITION DECLARATIONS                      //
/////////////////////////////////////////////////

#define MAX_EU_CHANNELS 16     // Frequency channels automatically initialized for EU reqion. 
#define SINGLE_CHANNEL_GATEWAY // Force it to use 868.1 MHz frequency band only due to Dragino LG01-P LoRa Gateway hardware limitation.   
#define TRANSMIT_INTERVAL 300 // Transmit interval in seconds, too often may get traffic ignored.
#define DEBUG_LEVEL 0          // Set debug level to 1 for outputting messages to the UART Terminal (e.g. Tera Term).
#define ACTIVATION_METHOD 0    // Set activation method to 0 for ABP (Activation By Personalization)
                               // Set activation method to 1 for OTAA (Over The Air Activation)

///////////////////////////////////////////////////
// GLOBAL VARIABLES DECLARATIONS                //
/////////////////////////////////////////////////

// Transmit interval in seconds.
uint16_t transmit_interval = TRANSMIT_INTERVAL;

// Static osjob_t sendjob variable used by loop function
static osjob_t sendjob;

// Unsigned integer packet counter used by transmit function.
unsigned int packetCounter = 1;

// Disable data rate adaption.
// Set to 1 in order to enable data rate adaption.
static const bit_t disableAdrMode = 0; 

// Disable link check validadtion.
// Set to 1 in order to enable link check validation.
static const bit_t disableLinkCheck = 0; 

// Set LoRa Node's transmission power to 14 dBm.
static const s1_t txPower = 14;

// Set LoRa Node's network id to 0x1.
static const u4_t NETID = 0x1;

/* LMiC frame initializations. */

// Playload frame length of size 8. 
static const u1_t LMIC_FRAME_LENGTH = 8;

// Set listening port to 1.
static const u1_t LMIC_PORT = 1;

// Disable confirmation of transmitted LMiC data.
// Set to 1 in order to enable confirmation of transmitted LMiC data.
static const u1_t LMIC_CONFIRMED = 0;

#if ACTIVATION_METHOD == 1 // if OTAA (Over The Air Activation) is applied.

// LoRaWAN Application identifier (AppEUI) associated with The Things Network Cloud Server.
static const u1_t APPEUI[8]  = { 0x70, 0xB3, 0xD5, 0x7E, 0xD0, 0x00, 0xA4, 0x54 };

// LoRaWAN unique device ID (DevEUI) associated with The Things Network Cloud Server.
static const u1_t DEVEUI[8]  = { 0x00, 0x1D, 0x45, 0x32, 0xEC, 0xA8, 0x01, 0x59 };

#endif

// Acquired activation method
#if ACTIVATION_METHOD == 0 // if ABP (Activation By Personalization) is applied.

// LoRaWAN network session key (NwkSKey) associated with The Things Network Cloud Server.
static const u1_t NWKSKEY[16] = { 0xDF, 0x9B, 0xB1, 0x30, 0xE8, 0x33, 0x42, 0x76, 0x33, 0x0C, 0x88, 0xBB, 0x30, 0xE2, 0xC2, 0xE9 };

// LoRaWAN application session key (AppSKey) associated with The Things Network Cloud Server.    
static const u1_t APPSKEY[16] = { 0xE0, 0x52, 0x18, 0x15, 0x0B, 0xE1, 0xEF, 0x1F, 0xAF, 0x8C, 0x8A, 0x31, 0x09, 0xB9, 0xAB, 0x9C };

// LoRaWAN end-device address (DevAddr) associated with The Things Network Cloud Server.
static const u4_t DEVADDR = 0x26011B39 ;

#endif

/* Sensor declarations. */

// Digital Input pin of temperature and humidity sensor set to D6.
DHT sensorTempHum(D6, DHT11);

// Analog Input pin of light intensity sensor set to A1.
AnalogIn sensorLight(A1);

// Analog Input pin of soil moisture sensor set to A3.
AnalogIn sensorSoilMoisture(A3);

///////////////////////////////////////////////////
// LMiC APPLICATION CALLBACKS                   //
/////////////////////////////////////////////////

/* 
 * os_getArtEui callback of type void.
 *
 * Copies application ID (8 bytes) of The Things Network Cloud Server into
 * memory if OTAA(Over The Air Activation) is applied.
 *
 * Input parameters: unsigned char buf of APPEUI. 
 *
 */ 
void os_getArtEui (u1_t* buf) {
    #if ACTIVATION_METHOD == 1 // if OTAA (Over The Air Activation) is applied.
        memcpy(buf, APPEUI, 8);
    #endif
}// end of os_getArtEui callback.

/* 
 * os_getDevEui callback of type void.
 *
 * Copies device ID (8 bytes) of The Things Network Cloud Server into
 * memory if OTAA(Over The Air Activation) is applied.
 *
 * Input parameters: unsigned char buf of DEVEUI. 
 *
 */ 
void os_getDevEui (u1_t* buf) {
    #if ACTIVATION_METHOD == 1 // if OTAA (Over The Air Activation) is applied.
        memcpy(buf, DEVEUI, 8);
    #endif
}// end of os_getDevEui callback.

/* 
 * os_getDevKey callback of type void.
 *
 * Copies device network session ID (16 bytes) of The Things Network Cloud Server
 * into memory.
 *
 * Input parameters: unsigned char buf of NWKSKEY. 
 *
 */ 
void os_getDevKey (u1_t* buf) {
    memcpy(buf, NWKSKEY, 16);
}// end of os_getDevKey callback.

/* 
 * onEvent callback of type void.
 *
 * Outputs UART message depending on
 * related event.
 *
 * NOTICE THAT: Not all events are being
 * used due to significant set up 
 * of LMiC environment.
 *
 * Input parameters: ev_t event. 
 *
 */ 
void onEvent (ev_t ev) {

    switch(ev) { // Switch events.
        case EV_SCAN_TIMEOUT:
            printf("EV_SCAN_TIMEOUT\n"); // Scan timeout.
            break;
        case EV_BEACON_FOUND:
            printf("EV_BEACON_FOUND\n"); // Beacon found.
            break;
        case EV_BEACON_MISSED:
            printf("EV_BEACON_MISSED\n"); // Beacon missed.
            break;
        case EV_BEACON_TRACKED:
            printf("EV_BEACON_TRACKED\n"); // Beacon tracked.
            break;
        case EV_JOINING:
            printf("EV_JOINING\n"); // Joining the network.
            break;
        case EV_JOINED:
            printf("EV_JOINED\n"); // Network joined.
            break;
        case EV_RFU1:
            printf("EV_RFU1\n"); // RFU1 event.
            break;
        case EV_JOIN_FAILED:
            printf("EV_JOIN_FAILED\n"); // Joining failed.
            break;
        case EV_REJOIN_FAILED:
            printf("EV_REJOIN_FAILED\n"); // Re-joining failed.
            break;
        case EV_TXCOMPLETE:
            printf("EV_TXCOMPLETE\n"); // Transmission complete.
            if (LMIC.txrxFlags & TXRX_ACK) // Check if acknowledgment received.
            {
                printf("Received ack\n");
            }
            if(LMIC.dataLen) // Output playload's data length.
            {
                printf("Received ");
                printf("%u", LMIC.dataLen);
                printf(" bytes of payload\n");
            }
            break;
        case EV_LOST_TSYNC:
            printf("EV_LOST_TSYNC\n"); // Lost transmision sync.
            break;
        case EV_RESET:
            printf("EV_RESET\n"); // Reset.
            break;
        case EV_RXCOMPLETE:
            printf("EV_RXCOMPLETE\n"); // Reception complete.
            break;
        case EV_LINK_DEAD:
            printf("EV_LINK_DEAD\n"); // Link dead.
            break;
        case EV_LINK_ALIVE:
            printf("EV_LINK_ALIVE\n"); // Link alive.
            break;
       default:
            printf("Unknown event\n"); // Default unknown event.
            break;
    }
    printf("\n"); // New line.
}// end of onEvent callback.

///////////////////////////////////////////////////
// LOCAL FUNCTIONS DECLARATIONS                 //
/////////////////////////////////////////////////

/* 
 * setUp function of type void.
 *
 * Initializes mbed OS, LMiC OS as well as
 * disabling all channels but 0 (868.1 MHz) for single-channel 
 * gateway compatibility.  
 *
 * Input parameters: None.
 *
 */ 
void setUp() {
    
    #if DEBUG_LEVEL == 1
        printf("IoT smart monitoring device for agriculture using LoRaWAN technology\n\n");
    #endif
    // Initializes OS.
    os_init(); 
    
    #if DEBUG_LEVEL == 1
        printf("OS_INIT\n\n");
    #endif
    
    // Reset the MAC state. Session and pending data transfers are being discarded.
    LMIC_reset();
  
    // Set static session parameters. Instead of dynamically establishing a session
    // by joining the network, precomputed session parameters are be provided.
    LMIC_setSession (NETID, DEVADDR, (uint8_t*)NWKSKEY, (uint8_t*)APPSKEY);
  
    // Disable data rate adaptation.
    LMIC_setAdrMode(disableAdrMode);
  
    // Disable link check validation.
    LMIC_setLinkCheckMode(disableLinkCheck);
  
    // Disable beacon tracking.
    LMIC_disableTracking();
  
    // Stop listening for downstream data (periodical reception) as LoRa Node
    // is only transmitting data to the Gateway.
    LMIC_stopPingable();
     
    // Set data rate and transmit power.
    LMIC_setDrTxpow(DR_SF7,txPower);

    //fflush(stdout);
    
    // If single-channel gateway is being used disable 
    // all the other channels except channel 0. 
    #ifdef SINGLE_CHANNEL_GATEWAY
        #if DEBUG_LEVEL == 1
            printf("      ----->Disabling all channels but 0 (868.1 MHz) for single-channel gateway compatibility\n\n\n");
        #endif
    
        for (int i=1; i < MAX_EU_CHANNELS; i++)
        {
            LMIC_disableChannel(i);
        }
    #endif
  
    #if DEBUG_LEVEL == 1
        printf("//////////Entering into TIME-TRIGGERED packet sending through LoRaWAN//////////\n");
        printf("---------------------Packets to be sent every %u seconds----------------------\n\n", transmit_interval);
    #endif
}// end of setUp function.

/* 
 * getTemperatureHumidity function of type void.
 *
 * Gets temperature (celcius) and humidity (relative humidity %)
 * measurements using DHT library. Otherwise, print an error.
 *
 * Input parameters: float temperature
 *                   float humidity
 *
 */ 
void getTemperatureHumidity(float& temperature, float& humidity) {

    // Set err variable to 0 (none).
    uint8_t err = ERROR_NONE;
    // Set humidity variable to 0.
    humidity = 0.0f; 
    // Set temperature variable to 0.
    temperature = 0.0f;
    
    // Store sensor data (40 bits(16-bit temperature, 16-bit humidity and 8-bit
    // CRC checksum)) into err variable.
    err = sensorTempHum.readData();
    
    if (err == ERROR_NONE) // if err equals to 0.
    { 
        // Store float temperature value in celcius.
        temperature = sensorTempHum.ReadTemperature(CELCIUS);
        // Store float humidity value. 
        humidity = sensorTempHum.ReadHumidity();
        
        // Output temperature and humidity values on UART Terminal
        #if DEBUG_LEVEL == 1
            printf("Temperature:   %4.2f Celsius \r\n", temperature);
            printf("Humidity:      %4.2f Relative Humidity \r\n", humidity);
        #endif
    }
    else // if err occurs.
    {
        // Output error message on UART Terminal and flash the RED LED.
        #if DEBUG_LEVEL == 1
            printf("Error: %d\r\n", err);
        #endif    
    }
}// end of getTemperatureHumidity function.

/* 
 * getLightIntensity function of type void.
 *
 * Gets the light's intensity analogue value at first instance
 * and then converts it using 16-bit ADC converter into voltage
 * counting from 0.0 to 5.0. 
 *
 * Input parameters: float lightIntensityVoltage
 *
 */ 
void getLightIntensity(float& lightIntensityVoltage) {
    
    // Set light intensity voltage variable to 0.
    lightIntensityVoltage = 0.0f;
    // Set light intensity analogue value to 0.
    uint16_t lightIntensityAnalogue = 0;
   
    // Read light intensity 16-bit analogue value.
    lightIntensityAnalogue = sensorLight.read_u16();
    //Convert the light intensity analog reading (which goes from 0 - 65536) to a voltage (0 - 5V).
    lightIntensityVoltage = (float) lightIntensityAnalogue*(5.0/65536.0);
    
    // Output light intensity voltage as well as resistance value on UART Terminal.
    #if DEBUG_LEVEL == 1
        float resistance = 0.0f;
        // Groove's calculation for resistance value.
        resistance = (float)(65536-lightIntensityAnalogue)*10/lightIntensityAnalogue;
        printf("Light Intensity:  %2.2f Volts -- ", lightIntensityVoltage);
        printf("Resistance: %2.2f Kiloohm \r\n", resistance);
    #endif
}// end of getLightIntensity function.

/* 
 * getSoilMoisture function of type void.
 *
 * Gets the soil's moisture analogue value at first instance
 * and then converts it using 16-bit ADC converter into voltage
 * counting from 0.0 to 5.0. 
 *
 * Input parameters: float lightOutputVoltage
 *
 */ 
void getSoilMoisture(float& soilMoistureVoltage) {
    
    // Set soil moisture voltage variable to 0.
    soilMoistureVoltage = 0.0f;
    // Set soil moisture analogue value to 0.
    uint16_t soilMoistureAnalogue = 0;
    
    // Read soil moisture 16-bit analogue value.
    soilMoistureAnalogue = sensorSoilMoisture.read_u16();
    // Convert the soil moisture analog reading (which goes from 0 - 65536) to a voltage (0 - 5V).
    soilMoistureVoltage = (float) soilMoistureAnalogue*(5.0/65536.0);
    
    // Output soil moisture voltage as well as soil moisture analogue value on UART Terminal.
    #if DEBUG_LEVEL == 1
        printf("Soil Moisture: %2.2f Volts -- ", soilMoistureVoltage);
        printf("Analogue Value: %d \r\n", soilMoistureAnalogue);
    #endif
}// end of getSoilMoisture function.

/* 
 * transmit function of type void.
 *
 * Checking if channel is ready.
 * If no, waiting until channel becomes free.
 * If yes, calling getTemperatureHumidity,
 * getLightIntensity and getSoilMoisture functions 
 * to gather measuring parameters. Then, prepares 
 * the LoRa packet and finally sending the packet
 * using os_setTimedCallback LMiC callback.
 * 
 * Input parameters: osjob_t* j
 *
 */ 
void transmit(osjob_t* j)
{
    // Define sensor measuring parameters.
    float temperature, humidity, lightIntensity, soilMoisture;
    
    #if DEBUG_LEVEL == 1
        printf("txChannel: %u , Channel Ready? ", LMIC.txChnl);
    #endif
    
    if (LMIC.opmode & (1 << 7)) // Is channel ready for transmission?
    {
        printf("NO, waiting...\n\n");
    } 
    else 
    {
        #if DEBUG_LEVEL == 1
            printf("YES, sensor readings...\n\n");
        #endif
        
        // Gather sensor readings.
        getTemperatureHumidity(temperature, humidity);
        getLightIntensity(lightIntensity);
        getSoilMoisture(soilMoisture); 
      
        #if DEBUG_LEVEL == 1
            printf("      ----->Preparing LoRa packet...\n");
        #endif
        
        // Prepare upstream data transmission at the next possible time.
        // Multiply all sensor readings by 100.
        int16_t temp = temperature*100;
        int16_t hum = humidity*100;
        int16_t light = lightIntensity*100;
        int16_t soil = soilMoisture*100;
      
        // Allocate measurements into LMIC frame array ready for transmission.
        // Each sensor measurement allocates 2 positions in frame array.
        // First, value is right shifted for 8 bits, while on the next 
        // array's position least significant bits are taken as the desired 
        // value for each sensor measurement.
        // This procedure is required in order to send playload data
        // to The Things Network Cloud Server.
        // Data will then be converted in a meaningful way through 
        // All Things Talk ABCL custom JSON binary conversion script.  
        LMIC.frame[0] = temp >> 8;
        LMIC.frame[1] = temp & 0xFF;
        LMIC.frame[2] = hum >> 8;
        LMIC.frame[3] = hum & 0xFF; 
        LMIC.frame[4] = light >> 8;
        LMIC.frame[5] = light & 0xFF;
        LMIC.frame[6] = soil >> 8;
        LMIC.frame[7] = soil & 0xFF;
      
        // Set the transmission data.
        LMIC_setTxData2(LMIC_PORT, LMIC.frame, LMIC_FRAME_LENGTH, LMIC_CONFIRMED);
        
        #if DEBUG_LEVEL == 1
            printf("      ----->LoRa Packet READY\n\n");
            printf("      ----->Sending LoRa packet %u of byte size %u\n\n", packetCounter++, LMIC_FRAME_LENGTH);
        #endif
    }
    // Schedule a time-triggered job to run based on TRANSMIT_INTERVAL time value.
    os_setTimedCallback(j, os_getTime()+sec2osticks(TRANSMIT_INTERVAL), transmit);
    
}// end of transmit function.

/* 
 * loop function of type void.
 *
 * Calling transmit as well as os_runloop_once functions
 * for a repeatedly time-triggered behaviour 
 * program execution.
 *
 * Input parameters: None.
 *
 */ 
void loop()
{
    // Calling transmit local function for acquiring next transmission
    // job of LoRa Node.
    transmit(&sendjob);

    // Super loop running os_runloop_once LMiC callback in a time-triggered behaviour. 
    while(1)
    {
        // Calling LMiC os_runloop_once callback.
        os_runloop_once();
        // Delay of 20 ms.
        wait_ms(20);
    }
    // Never arives here!
    
}// end of loop function.

/* 
 * main function of type integer.
 *
 * Calling setUp as well as loop functions
 * for a repeatedly time-triggered behaviour 
 * program execution.
 *
 * Input parameters: integer argc.
 *                   char **argv. 
 *
 */ 
int main(int argc, char **argv) 
{
    // Calling setUp local function for OS initialization.
    setUp();
    
    // Super loop running loop local funcion in a time-triggered behaviour.
    while(1)
    { 
        loop();
    }    
    // Never arrives here!
    
} // end of main function.