Mehmet Emin UğurHarun Yenisan
Published © MIT

Automated Greenhouse Climate Control System Optimized Growth

This system sends sensor data to ThingSpeak via LoRaWAN and optimizes greenhouse climate control based on incoming commands.

IntermediateWork in progress24 hours118
Automated Greenhouse Climate Control System Optimized Growth

Things used in this project

Hardware components

RAKwireless RAK7268V2
×1
RAKwireless RAK19003
×1
RAKwireless RAK1906
×1
RAKwireless RAK12004
×1
RAKwireless RAK1921
×1
RAKwireless RAK13009
×1
RAKwireless RAK13010
×1

Software apps and online services

Arduino IDE
Arduino IDE
ThingSpeak API
ThingSpeak API

Hand tools and fabrication machines

Digilent Screwdriver
Digilent Screwdriver
Wire Stripper & Cutter, 18-10 AWG / 0.75-4mm² Capacity Wires
Wire Stripper & Cutter, 18-10 AWG / 0.75-4mm² Capacity Wires

Story

Read more

Schematics

RAK19003

Code

Arduino IDE

Arduino
#include <LoRaWan-RAK4631.h> // LoRaWAN library for RAK4631
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME680.h> // For RAK1906 (uses BME680 sensor)
#include <RAK12004_Light.h> // For RAK12004 (uses BH1750 light sensor)

// LoRaWAN Join Settings
// You must obtain these values from your TheThingsNetwork (TTN) console.
// Each device should have a unique DevEUI, AppEUI, and AppKey.
String node_device_eui = "AC1F09FFFE0xxxx"; // <<< CHANGE THIS
String node_app_eui = "70B3D57ED000xxxx";   // <<< CHANGE THIS
String node_app_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; // <<< CHANGE THIS

// LoRaWAN Application Port
// This should match the port specified in your integration on TTN.
#define LORAWAN_APP_PORT 1       

// Data Transmission Intervals (in seconds)
#define SEND_INTERVAL_SEC 300    // Interval for sending sensor data (5 minutes)
#define REACTION_INTERVAL_SEC 60 // Interval for checking ThingSpeak Reaction commands (1 minute)

// RAK13009/RAK13010 Relay Pin Definitions
// These should be connected to the appropriate GPIO pins on the WisBlock base board.
// For example, WB_IO3 and WB_IO5 represent digital pins on the RAK19003.
#define RELAY_FAN_PIN WB_IO3     // Relay 1: Pin for fan control
#define RELAY_HEATER_PIN WB_IO5  // Relay 2: Pin for heater control

// Sensor Objects
Adafruit_BME680 bme;         // RAK1906 (BME680) environmental sensor object
RAK12004_Light lightSensor;  // RAK12004 (BH1750) light sensor object

// Timer Variables
time_t lastSendTime = 0;        // Last sensor data transmission time (millis())
time_t lastReactionCheckTime = 0; // Last ThingSpeak Reaction check time (millis())

void setup() {
  // Set internal LED as OUTPUT and turn it off initially
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW); 
  
  // Start serial communication (for debug messages)
  Serial.begin(115200);
  while (!Serial); // Wait for serial port to be ready
  Serial.println("Greenhouse Climate Control System Starting...");

  // Set relay pins as OUTPUT and keep them off initially
  pinMode(RELAY_FAN_PIN, OUTPUT);
  digitalWrite(RELAY_FAN_PIN, LOW);
  pinMode(RELAY_HEATER_PIN, OUTPUT);
  digitalWrite(RELAY_HEATER_PIN, LOW);

  // Start I2C communication (for BME680 and BH1750)
  Wire.begin();

  // Initialize RAK1906 (BME680) sensor
  // I2C address is typically 0x76 or 0x77.
  if (!bme.begin(0x76)) { 
    Serial.println("RAK1906 BME680 not found, check connection!");
    while (1); // Stay in infinite loop if initialization fails
  }
  // Sampling and filter settings for BME680
  bme.setTemperatureOversampling(BME680_OS_8X);
  bme.setHumidityOversampling(BME680_OS_2X);
  bme.setPressureOversampling(BME680_OS_4X);
  bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
  bme.setGasHeater(320, 150); // Heater settings for gas sensor (320°C, 150ms)
  Serial.println("RAK1906 BME680 successfully initialized.");

  // Initialize RAK12004 (BH1750) light sensor
  if (!lightSensor.begin()) {
    Serial.println("RAK12004 not found, check connection!");
    while (1); // Stay in infinite loop if initialization fails
  }
  Serial.println("RAK12004 successfully initialized.");

  // Initialize and configure LoRaWAN module
  api.lorawan.setMcuStatus(true);         // Set MCU status
  api.lorawan.setDeviceClass(CLASS_A);    // Set device class (Class A consumes least power)
  api.lorawan.setRegion(EU868);           // Set LoRaWAN region (e.g., EU868 for Europe)
                                          // Other options: US915, AS923, AU915, KR920, IN865, RU864
  api.lorawan.setDevEui(node_device_eui); // Set Device EUI
  api.lorawan.setAppEui(node_app_eui);   // Set Application EUI
  api.lorawan.setAppKey(node_app_key);   // Set Application Key
  api.lorawan.setConfirm(true);          // Use confirmed messages? (reduces data loss, but consumes more power)
  api.lorawan.setAdaptiveDr(true);       // Use Adaptive Data Rate (ADR)?

  Serial.println("Attempting to join LoRaWAN network...");
  api.lorawan.join(); // Start LoRaWAN network join process

  // Initialize timers
  lastSendTime = millis();
  lastReactionCheckTime = millis();
}

void loop() {
  // Check LoRaWAN network connection status
  if (api.lorawan.queryNetworkStatus() == LORAWAN_JOINED) {
    // Check if it's time to send data
    if (millis() - lastSendTime > (SEND_INTERVAL_SEC * 1000)) {
      sendSensorData();       // Send sensor data
      lastSendTime = millis(); // Reset timer
    }
    // Check if it's time to check ThingSpeak Reactions
    // Note: RAK4631 cannot directly make HTTP requests. This part represents listening for downlink commands from TTN.
    if (millis() - lastReactionCheckTime > (REACTION_INTERVAL_SEC * 1000)) {
      Serial.println("Waiting for LoRaWAN downlink..."); // Listen for downlink messages
      lastReactionCheckTime = millis(); // Reset timer
    }
  } else {
    // If not connected to LoRaWAN network, try to join again
    Serial.println("Not connected to LoRaWAN network, trying to join again...");
    api.lorawan.join(); 
    delay(10000); // Wait 10 seconds before retrying
  }
}

// Function to read sensor data and send it via LoRaWAN
void sendSensorData() {
  Serial.println("Reading sensor data...");
  
  // Start BME680 (RAK1906) readings
  bme.performReading(); 
  float temperature = bme.temperature;
  float humidity = bme.humidity;
  float pressure = bme.pressure / 100.0; // Convert Pascal to hPa
  float gas_resistance = bme.gas_resistance / 1000.0; // Convert Ohm to KOhms

  // RAK12004 light sensor readings
  float lightLux = lightSensor.getLux();
  float lightIR = lightSensor.getIR();
  float lightVisible = lightSensor.getVisible();

  // Print read data to Serial Monitor
  Serial.printf("Temperature: %.2f C, Humidity: %.2f %%RH, Pressure: %.2f hPa, Gas Resistance: %.2f KOhms\n", temperature, humidity, pressure, gas_resistance);
  Serial.printf("Lux: %.2f, IR: %.2f, Visible: %.2f\n", lightLux, lightIR, lightVisible);

  // Package data into LoRaWAN payload
  // Sending 4 float values (4 bytes each), total 16 bytes.
  // Payload structure: | Temperature (float) | Humidity (float) | Gas Resistance (float) | Light (float) |
  uint8_t payload[16]; 
  memcpy(&payload[0], &temperature, sizeof(float)); // Temperature
  memcpy(&payload[4], &humidity, sizeof(float));    // Humidity
  memcpy(&payload[8], &gas_resistance, sizeof(float)); // Gas Resistance
  memcpy(&payload[12], &lightLux, sizeof(float));    // Light (Lux)

  Serial.println("Sending LoRaWAN data...");
  // Send payload to LoRaWAN network. Use LORAWAN_APP_PORT and send as confirmed message (true).
  api.lorawan.send(sizeof(payload), payload, LORAWAN_APP_PORT, true); 
}

// Callback function to be called when a LoRaWAN downlink message is received
// Processes commands from ThingSpeak Reaction or via TTN.
void OnRxData(lorawan_AppData_t* appData) {
  Serial.printf("LoRaWAN Downlink message received, Port: %d, Length: %d\n", appData->Port, appData->BuffSize);
  
  // Print received payload to serial monitor
  Serial.print("Payload (hex): ");
  for (int i = 0; i < appData->BuffSize; i++) {
    Serial.printf("%02X ", appData->Buffer[i]);
  }
  Serial.println();

  // Convert payload to String and interpret as command
  String command = "";
  for (int i = 0; i < appData->BuffSize; i++) {
    command += (char)appData->Buffer[i];
  }
  Serial.print("Received Command: ");
  Serial.println(command);

  // Control relays based on incoming commands
  if (command == "FAN_ON") {
    digitalWrite(RELAY_FAN_PIN, HIGH); // Turn on fan relay
    Serial.println("FAN TURNED ON!");
  } else if (command == "FAN_OFF") {
    digitalWrite(RELAY_FAN_PIN, LOW);  // Turn off fan relay
    Serial.println("FAN TURNED OFF!");
  } else if (command == "HEATER_ON") {
    digitalWrite(RELAY_HEATER_PIN, HIGH); // Turn on heater relay
    Serial.println("HEATER TURNED ON!");
  } else if (command == "HEATER_OFF") {
    digitalWrite(RELAY_HEATER_PIN, LOW);  // Turn off heater relay
    Serial.println("HEATER TURNED OFF!");
  } else {
    Serial.println("Unknown command.");
  }
}

// Mandatory callback function definitions for LoRaWAN library
void lorawan_rx_handler(lorawan_AppData_t* appData) {
  OnRxData(appData); // Call OnRxData function when data is received
}

void lorawan_has_joined_handler(void) {
  Serial.println("Successfully joined LoRaWAN network!");
  digitalWrite(LED_BUILTIN, HIGH); // Turn on internal LED after joining network
}

void lorawan_join_failed_handler(void) {
  Serial.println("LoRaWAN network join failed. Retrying...");
  digitalWrite(LED_BUILTIN, LOW); // Turn off internal LED if join fails
}

void lorawan_tx_handler(void) {
  Serial.println("LoRaWAN Tx Done!"); // Inform when data transmission is complete
}

Credits

Mehmet Emin Uğur
9 projects • 10 followers
Computer engineer, working as an IT teacher at the Ministry of National Education.
Harun Yenisan
8 projects • 8 followers
Electronic engineer, working as an IT teacher at the Ministry of National Education.

Comments