Lora Smart Agriculture & Remote Monitoring System
In this project, we will learn how to build an IoT Lora Smart Agriculture & Remote Monitoring System, we will use the LoRa soil moisture sensor and Maduino Lora module.
1. Overview: IoT LoRa Based Smart Agriculture
In this project, we are going to learn about IoT LoRa Based Smart Agriculture & Remote Monitoring System. Smart Agriculture means monitoring environmental conditions that influence crop production & track livestock health indicators. LoRa based Internet of Things (IoT) technology for agriculture enables efficiencies that reduce environmental impact, maximize yield, and minimize expenses. Smart agriculture use cases based on LoRa devices and the LoRaWAN protocol have demonstrated significant improvements, such as a 50% water reduction for commercial farms.
In this project, we will use the LoRa soil moisture sensor based on the Atmega328P microcontroller, LoRa Module RFM95 & AHT10 Humidity/Temperature Sensor. The AHT10 Digital Humidity & Temperature Sensor measures the local air temperature & humidity. The Capacitive Soil Moisture Sensor will measure the Soil Humidity/Moisture. Hence all the measured data can be sent wirelessly up to 15km distance and can be read by the receiver. Both the transmitter and receiver are designed using the LoRa Module.
The transmitter operates on low power mode. Using a AAA Battery the device can be operated over a year. The device power can be controlled and data sending intervals can be increased as per requirement. Thus the advantage of this technology includes long-range, low-power wireless qualities that enable the use of low-cost sensors to send data from the farm to the Cloud where it can be analyzed to improve operations.
The transmitter operates on low power mode. Using a AAA Battery the device can be operated over a year. The device power can be controlled and data sending intervals can be increased as per requirement. Thus the advantage of this technology includes long-range, low-power wireless qualities that enable the use of low-cost sensors to send data from the farm to the Cloud where it can be analyzed to improve operations.
2. Bill of Materials
The following are the components required for making this IoT LoRa Network Based Smart Agriculture Project. We need a few LoRa Agriculture sensors like the Soil Moisture sensor to measure Soil Moisture. We can measure the environmental parameter like surrounding humidity & temperature. For this AHT10 is the best suitable Sensor.
Instead of buying separate sensors and assembling on a board is a tough and time-consuming task. So I purchased a combined sensor board assembled on a PCB Board. Both the transmitter & receiver part PCB Board with component assembled on it can be purchased from the below link.
No. | Material Name | Description | Quantity | Link |
---|---|---|---|---|
1 | Lora Soil Moisture Sensor | Transmitter | 1 | https://www.makerfabs.com/lora-soil-moisture-sensor.html |
2 | Maduino Lora Radio | Receiver | 1 | https://www.makerfabs.com/maduino-lora-radio-433m-868m-915m.html |
3 | CP2104 USB2UART Converter | FTDI Module | 1 | https://www.makerfabs.com/cp2104-usb-to-serial-converter.html |
3. LoRa Transmitter: Soil Moisture Sensor + AHT10 Humidity/Temperature Sensor
The transmitter part consists of a Lora soil moisture sensor and AHT10 Humidity/ Temperature Sensor. The microcontroller used in this board is Atmel’s Atmega328P which supports Arduino programming. The AHT10 sensor collects local air temperature & humidity. The Capacitive Soil Moisture Sensor detects the soil humidity. The sensor is based on 555-Timer IC. The Lora Radio and the Soil Moisture Sensor must have the same work frequency. Otherwise, it receives nothing from another. The transmitter transmits the local environment data to the gateway using the LoRa module RFM95.
The Lora transmitter is powered by a pair of AAA batteries. The device transmits the data regularly after the interval of a few minutes and then goes to sleep mode for saving the battery power. The sensor function could be shut down or they can be only powered ON for a short time based on code and hardware setup. Thus because of sleep mode and low power consumption mode, the battery life can be extended up to several months. The capacitive soil Moisture sensor is coated with waterproof paint, so it doesn’t have any corrosion effect even the sensor is dipped in the soil for a long time. The module suits for applications for smart-farm, irrigation, agriculture, etc.
The ATmega328 chip has default Arduino bootloader and thus can be easily programmed using Arduino IDE. We just need a USB-to-TTL converter module.
3.1 Power Consumption & Sleep Mode
MCU Power Consumption at 1MHz, 1.8V, 25°C.
- Active Mode: 0.2mA
- Power-down Mode: 0.1µA
- Power-save Mode: 0.75µA (Including 32kHz RTC)
- Active Mode: 0.2mA
- Power-down Mode: 0.1µA
- Power-save Mode: 0.75µA (Including 32kHz RTC)
4. Lora Receiver: Maduino Lora Radio (433M/868M/915M)
The Maduino Lora Radio Receiver is a mainboard based on the ATmega328 and 433MHZ/ 868MHz/ 915MHz RFM95 LoRa module. The Maduino LoRa Radio allows the user to send data and reach extremely long ranges at low data-rates. It provides ultra-long range spread spectrum communication and high interference immunity whilst minimizing current consumption. It has Arduino pro mini 3.3V 8MHz bootloader on this board and uses the CP2104 as USB to serial to upload the code using Arduino IDE. In this LoRa Smart Agriculture project, we will use it as a Receiver Gateway.
Note that the DTR in the USB-to-UART converter is needed to connect to the "Reset" pin for Arduino sketch uploading. If there is no DTR, you may need to press the "reset" button manually for uploading the code.
Install AAA battery on the backside of the module.
Features:
- BAT Input Voltage: 3.4-4.2V
- Integrated Power Control System
- 127 dB Dynamic Range RSSI
- Uses the License-free ISM band: "European ISM" @ 433MHz/868MHz/915MHz
- +5 to +20 dBm up to 100 mW Power Output Capability (power output selectable in software)
- ~100mA Peak during +20dBm transmit, ~30mA during active radio listening
- Range of approx.2KM, depending on obstructions, frequency, antenna, and power output
- Integrated Power Control System
- 127 dB Dynamic Range RSSI
- Uses the License-free ISM band: "European ISM" @ 433MHz/868MHz/915MHz
- +5 to +20 dBm up to 100 mW Power Output Capability (power output selectable in software)
- ~100mA Peak during +20dBm transmit, ~30mA during active radio listening
- Range of approx.2KM, depending on obstructions, frequency, antenna, and power output
5. Setting up Lora Soil Moisture Transmitter Part
In order to upload the code to the transmitter part, you need to solder a 5 pin male header pin. So first solder the male header pin here. Or you can use Makerfabs CP2104 USB to Serial Converter directly, which is specially designed for the Lora soil moisture sensor programming.
Now you need to connect USB-to-UART Module ;to upload the code the Atmega328 chip using Arduino IDE. Make a connection between the USB-to-UART module as follow. You can use a USB-to-Serial tool or directly use CP2014 USB2UART.
Now you need to connect USB-to-UART Module ;to upload the code the Atmega328 chip using Arduino IDE. Make a connection between the USB-to-UART module as follow. You can use a USB-to-Serial tool or directly use CP2014 USB2UART.
Lora Soil Moisture Sensor
|
USB-to-UART Tool
|
---|---|
3V3
|
3V3
|
GND
|
GND
|
RX
|
TXD
|
TX
|
RXD
|
Note that the DTR in the USB-to-UART converter is needed to connect to the "Reset" pin for Arduino sketch uploading. If there is no DTR, you may need to press the "reset" button manually for uploading the code.
Install AAA battery on the backside of the module.
Now in order to program the Board using Arduino IDE for Lora agriculture sensor Board, there is already a default package board installed on Arduino IDE.
From the top Arduino IDE menu, select Tools→ Board→ Arduino Pro or Pro Mini. Also select Tools→ Processor→ Atmega328P(3.3V,8Mhz).
6. Setting up Lora Receiver Part
The Lora Receiver doesn’t need any USB-to-UART module as it can be directly programmed using Micro-USB data cable.The board uses the same package for programming. So Select the Arduino Pro or Pro mini & ATmega328P (3.3V,8Mhz) Board.
If your Lora board is 868MHz:
If your Lora board is 915MHz:
Let us check now a simple demonstration of LoRa based Smart Agriculture. Now you can open the serial monitor for both the transmitter and receiver. The Lora transmitter and receiver will get started and communicate with each other.
The gateway data can be observed in the serial monitor.
Well, that’s all from IoT Lora Based Agriculture Network. If you have any further questions or some special customized requirement based on those Lora IoT boards, feel free to contact [email protected].
You can also check this review from Happy DIY Home when buying soil moisture sensors.
This article is originally posted on How2electronics.
You can also learn from this video tutorial made by How2electronics:
7. Transmitter Code
The transmitter code is divided into 3 files:
1. Main.ino File
2. I2C_AHT10.cpp File
3. I2C_AHT10.h File
1. Main.ino File
2. I2C_AHT10.cpp File
3. I2C_AHT10.h File
But before that, you need to add RFM95 library to the Arduino IDE. So download the RFM95 Library from the link below and add it to the library folder. Download LoRa RFM95 Library.
Modify the frequency macro according to your board is 433Mhz or 868Mhz or 915Mhz.
If your Lora board is 433MHz:
#define RF95_FREQ 433.0
If your Lora board is 868MHz:
#define RF95_FREQ 868.0
If your Lora board is 915MHz:
#define RF95_FREQ 915.0
7.1. Main.ino File
#include <SPI.h> #include "RH_RF95.h" #include "I2C_AHT10.h" #include <Wire.h> AHT10 humiditySensor; int sensorPin = A2; // select the input pin for the potentiometer int sensorValue = 0; // variable to store the value coming from the sensor int sensorPowerCtrlPin = 5; void sensorPowerOn(void) { digitalWrite(sensorPowerCtrlPin, HIGH);//Sensor power on } void sensorPowerOff(void) { digitalWrite(sensorPowerCtrlPin, LOW);//Sensor power on } #define RFM95_CS 10 #define RFM95_RST 4 #define RFM95_INT 2 // Change to 434.0 or other frequency, must match RX's freq! #define RF95_FREQ 433.0 // Singleton instance of the radio driver RH_RF95 rf95(RFM95_CS, RFM95_INT); void setup() { pinMode(RFM95_RST, OUTPUT); digitalWrite(RFM95_RST, LOW); delay(100); digitalWrite(RFM95_RST, HIGH); pinMode(sensorPowerCtrlPin, OUTPUT); //digitalWrite(sensorPowerCtrlPin, LOW);//Sensor power on sensorPowerOn(); //pinMode(sensorPin, INPUT); //while (!Serial); Serial.begin(115200); delay(100); Wire.begin(); //Join I2C bus //Check if the AHT10 will acknowledge if (humiditySensor.begin() == false) { Serial.println("AHT10 not detected. Please check wiring. Freezing."); //while (1); } else Serial.println("AHT10 acknowledged."); Serial.println("Marduino LoRa TX Test!"); // manual reset digitalWrite(RFM95_RST, LOW); delay(10); digitalWrite(RFM95_RST, HIGH); delay(10); while(!rf95.init()) { Serial.println("LoRa radio init failed"); while (1); } Serial.println("LoRa radio init OK!"); //rf95.setModemConfig(Bw125Cr48Sf4096); //Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM if (!rf95.setFrequency(RF95_FREQ)) { Serial.println("setFrequency failed"); while (1); } Serial.print("Set Freq to: "); Serial.println(RF95_FREQ); // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on // The default transmitter power is 13dBm, using PA_BOOST. // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then // you can set transmitter powers from 5 to 23 dBm: rf95.setTxPower(23, false); //dht.begin(); } int16_t packetnum = 0; // packet counter, we increment per xmission float temperature=0.0;// float humidity=0.0; void loop() { // Reading temperature or humidity takes about 250 milliseconds! // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor) //float humidity = 6.18;//dht.readHumidity(); // Read temperature as Celsius (the default) sensorPowerOn();// delay(100); sensorValue = analogRead(sensorPin); delay(200); if (humiditySensor.available() == true) { //Get the new temperature and humidity value temperature = humiditySensor.getTemperature(); humidity = humiditySensor.getHumidity(); //Print the results Serial.print("Temperature: "); Serial.print(temperature, 2); Serial.print(" C\t"); Serial.print("Humidity: "); Serial.print(humidity, 2); Serial.println("% RH"); } // Check if any reads failed and exit early (to try again). if (isnan(humidity) || isnan(temperature)) { Serial.println(F("Failed to read from AHT sensor!")); //return; } delay(100); //sensorPowerOff(); Serial.print(F("Moisture ADC : ")); Serial.println(sensorValue); //Serial.print(F("Humidity: ")); //Serial.print(humidity); //Serial.print(F("% Temperature: ")); //Serial.print(temperature); //Serial.println("Humidity is " + (String)humidity); //Serial.println("Temperature is " + (String)temperature); String message = "#"+(String)packetnum+" Humidity:"+(String)humidity+"% Temperature:"+(String)temperature+"C"+" ADC:"+(String)sensorValue; Serial.println(message); packetnum++; Serial.println("Transmit: Sending to rf95_server"); // Send a message to rf95_server uint8_t radioPacket[message.length()+1]; message.toCharArray(radioPacket, message.length()+1); radioPacket[message.length()+1]= '\0'; Serial.println("Sending..."); delay(10); rf95.send((uint8_t *)radioPacket, message.length()+1); Serial.println("Waiting for packet to complete..."); delay(10); rf95.waitPacketSent(); // Now wait for a reply uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; uint8_t len = sizeof(buf); Serial.println("Waiting for reply..."); delay(10); if(rf95.waitAvailableTimeout(8000)) { // Should be a reply message for us now if (rf95.recv(buf, &len)) { Serial.print("Got reply: "); Serial.println((char*)buf); Serial.print("RSSI: "); Serial.println(rf95.lastRssi(), DEC); } else { Serial.println("Receive failed"); } } else { Serial.println("No reply, is there a listener around?"); } delay(1000); }
7.2. I2C_AHT10.cpp File
#include "I2C_AHT10.h" /*--------------------------- Device Status ------------------------------*/ bool AHT10::begin(TwoWire &wirePort) { _i2cPort = &wirePort; //Grab the port the user wants to communicate on _deviceAddress = AHT10_DEFAULT_ADDRESS; //We had hoped the AHT10 would support two addresses but it doesn't seem to if (isConnected() == false) return false; //Wait 40 ms after power-on before reading temp or humidity. Datasheet pg 8 delay(40); //Check if the calibrated bit is set. If not, init the sensor. if (isCalibrated() == false) { //Send 0xBE0800 initialize(); //Immediately trigger a measurement. Send 0xAC3300 triggerMeasurement(); delay(75); //Wait for measurement to complete uint8_t counter = 0; while (isBusy()) { delay(1); if (counter++ > 100) return (false); //Give up after 100ms } //This calibration sequence is not completely proven. It's not clear how and when the cal bit clears //This seems to work but it's not easily testable if (isCalibrated() == false) { return (false); } } //Check that the cal bit has been set if (isCalibrated() == false) return false; //Mark all datums as fresh (not read before) sensorQueried.temperature = true; sensorQueried.humidity = true; return true; } //Ping the AHT10's I2C address //If we get a response, we are correctly communicating with the AHT10 bool AHT10::isConnected() { _i2cPort->beginTransmission(_deviceAddress); if (_i2cPort->endTransmission() == 0) return true; //If IC failed to respond, give it 20ms more for Power On Startup //Datasheet pg 7 delay(20); _i2cPort->beginTransmission(_deviceAddress); if (_i2cPort->endTransmission() == 0) return true; return false; } /*------------------------ Measurement Helpers ---------------------------*/ uint8_t AHT10::getStatus() { _i2cPort->requestFrom(_deviceAddress, (uint8_t)1); if (_i2cPort->available()) return (_i2cPort->read()); return (0); } //Returns the state of the cal bit in the status byte bool AHT10::isCalibrated() { return (getStatus() & (1 << 3)); } //Returns the state of the busy bit in the status byte bool AHT10::isBusy() { return (getStatus() & (1 << 7)); } bool AHT10::initialize() { _i2cPort->beginTransmission(_deviceAddress); _i2cPort->write(sfe_aht10_reg_initialize); _i2cPort->write(0x80); _i2cPort->write(0x00); if (_i2cPort->endTransmission() == 0) return true; return false; } bool AHT10::triggerMeasurement() { _i2cPort->beginTransmission(_deviceAddress); _i2cPort->write(sfe_aht10_reg_measure); _i2cPort->write(0x33); _i2cPort->write(0x00); if (_i2cPort->endTransmission() == 0) return true; return false; } //Loads the void AHT10::readData() { //Clear previous data sensorData.temperature = 0; sensorData.humidity = 0; if (_i2cPort->requestFrom(_deviceAddress, (uint8_t)6) > 0) { uint8_t state = _i2cPort->read(); uint32_t incoming = 0; incoming |= (uint32_t)_i2cPort->read() << (8 * 2); incoming |= (uint32_t)_i2cPort->read() << (8 * 1); uint8_t midByte = _i2cPort->read(); incoming |= midByte; sensorData.humidity = incoming >> 4; sensorData.temperature = (uint32_t)midByte << (8 * 2); sensorData.temperature |= (uint32_t)_i2cPort->read() << (8 * 1); sensorData.temperature |= (uint32_t)_i2cPort->read() << (8 * 0); //Need to get rid of data in bits > 20 sensorData.temperature = sensorData.temperature & ~(0xFFF00000); //Mark data as fresh sensorQueried.temperature = false; sensorQueried.humidity = false; } } //Triggers a measurement if one has not been previously started, then returns false //If measurement has been started, checks to see if complete. //If not complete, returns false //If complete, readData(), mark measurement as not started, return true bool AHT10::available() { if (measurementStarted == false) { triggerMeasurement(); measurementStarted = true; return (false); } if (isBusy() == true) { return (false); } readData(); measurementStarted = false; return (true); } bool AHT10::softReset() { _i2cPort->beginTransmission(_deviceAddress); _i2cPort->write(sfe_aht10_reg_reset); if (_i2cPort->endTransmission() == 0) return true; return false; } /*------------------------- Make Measurements ----------------------------*/ float AHT10::getTemperature() { if (sensorQueried.temperature == true) { //We've got old data so trigger new measurement triggerMeasurement(); delay(75); //Wait for measurement to complete uint8_t counter = 0; while (isBusy()) { delay(1); if (counter++ > 100) return (false); //Give up after 100ms } readData(); } //From datasheet pg 8 float tempCelsius = ((float)sensorData.temperature / 1048576) * 200 - 50; //Mark data as old sensorQueried.temperature = true; return tempCelsius; } float AHT10::getHumidity() { if (sensorQueried.humidity == true) { //We've got old data so trigger new measurement triggerMeasurement(); delay(75); //Wait for measurement to complete uint8_t counter = 0; while (isBusy()) { delay(1); if (counter++ > 100) return (false); //Give up after 100ms } readData(); } //From datasheet pg 8 float relHumidity = ((float)sensorData.humidity / 1048576) * 100; //Mark data as old sensorQueried.humidity = true; return relHumidity; }
7.3. I2C_AHT10.h File
#ifndef __I2C_AHT10_H__ #define __I2C_AHT10_H__ #include <Arduino.h> #include <Wire.h> #define AHT10_DEFAULT_ADDRESS 0x38 enum registers { sfe_aht10_reg_reset = 0xBA, sfe_aht10_reg_initialize = 0xBE, sfe_aht10_reg_measure = 0xAC, }; class AHT10 { private: TwoWire *_i2cPort; //The generic connection to user's chosen I2C hardware uint8_t _deviceAddress; bool measurementStarted = false; struct { uint32_t humidity; uint32_t temperature; } sensorData; struct { uint8_t temperature : 1; uint8_t humidity : 1; } sensorQueried; public: //Device status bool begin(TwoWire &wirePort = Wire); //Sets the address of the device and opens the I2C bus bool isConnected(); //Checks if the AHT10 is connected to the I2C bus bool available(); //Returns true if new data is available //Measurement helper functions uint8_t getStatus(); //Returns the status byte bool isCalibrated(); //Returns true if the cal bit is set, false otherwise bool isBusy(); //Returns true if the busy bit is set, false otherwise bool initialize(); //Initialize for taking measurement bool triggerMeasurement(); //Trigger the AHT10 to take a measurement void readData(); //Read and parse the 6 bytes of data into raw humidity and temp bool softReset(); //Restart the sensor system without turning power off and on //Make measurements float getTemperature(); //Goes through the measurement sequence and returns temperature in degrees celcius float getHumidity(); //Goes through the measurement sequence and returns humidity in % RH }; #endif
8. Receiver Code
#include <SPI.h> #include <RH_RF95.h> #define RFM95_CS 10 #define RFM95_RST 9 #define RFM95_INT 2 // Change to 434.0 or other frequency, must match RX's freq! #define RF95_FREQ 433.0 // Singleton instance of the radio driver RH_RF95 rf95(RFM95_CS, RFM95_INT); int count=0; void setup() { pinMode(RFM95_RST, OUTPUT); digitalWrite(RFM95_RST, HIGH); //while (!Serial); Serial.begin(115200); delay(100); Serial.println("Arduino LoRa RX Test!"); // manual reset digitalWrite(RFM95_RST, LOW); delay(10); digitalWrite(RFM95_RST, HIGH); delay(10); while (!rf95.init()) { Serial.println("LoRa radio init failed"); while (1); } Serial.println("LoRa radio init OK!"); // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM if (!rf95.setFrequency(RF95_FREQ)) { Serial.println("setFrequency failed"); while (1); } Serial.print("Set Freq to: "); Serial.println(RF95_FREQ); // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on // The default transmitter power is 13dBm, using PA_BOOST. // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then // you can set transmitter powers from 5 to 23 dBm: rf95.setTxPower(23, false); } void loop() { if (rf95.available()) { // Should be a message for us now uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; uint8_t len = sizeof(buf); if (rf95.recv(buf, &len)) { count++; RH_RF95::printBuffer("Received: ", buf, len); Serial.print("Got: "); Serial.println((char*)buf); Serial.print("RSSI: "); Serial.println(rf95.lastRssi(), DEC); // Send a reply uint8_t data[] = "And hello back to you"; rf95.send(data, sizeof(data)); rf95.waitPacketSent(); Serial.println("Sent a reply"); } else { Serial.println("Receive failed"); } } }
9. IoT LoRa Based Smart Agriculture & Remote Monitoring System
Let us check now a simple demonstration of LoRa based Smart Agriculture. Now you can open the serial monitor for both the transmitter and receiver. The Lora transmitter and receiver will get started and communicate with each other.
The transmitter end will read the soil moisture data in ADC which can be converted to a percentage value (Refer to this Article...). Similarly, the AHT10 will collect Air Humidity and Temperature Data. The data is transmitted to the gateway.
The gateway data can be observed in the serial monitor.
The transmitter will send the data and then goes to deep sleep mode or power saving mode. During the data transmission mode, it consumes around 0.2mA of power. While in the power-save mode the current get reduced to 0.75µA. Battery life can be increased to several months by increasing the interval of data transmission.
Well, that’s all from IoT Lora Based Agriculture Network. If you have any further questions or some special customized requirement based on those Lora IoT boards, feel free to contact [email protected].
You can also check this review from Happy DIY Home when buying soil moisture sensors.
This article is originally posted on How2electronics.
You can also learn from this video tutorial made by How2electronics:
You can also check this review from Happy DIY Home when buying soil moisture sensors.