Pablo Zuloaga Betancourt
Published © CC BY-NC-SA

POWAR v2.0 - WIO Terminal

The Plant Observatory of Weather Adaptability for Resilience is a DIY, Low-Cost, climate simulator, to grow food nowadays in future weather.

AdvancedWork in progress24 hours1,102

Things used in this project

Hardware components

POWAR IoT board
The board we designed in collaboration with Seeed Studio!
×1
Wio Terminal
Seeed Studio Wio Terminal
To control the main system.
×1
Grove - Capacitive Soil Moisture Sensor
Seeed Studio Grove - Capacitive Soil Moisture Sensor
×1
Grove - Temperature&Humidity Sensor (SHT40)
Seeed Studio Grove - Temperature&Humidity Sensor (SHT40)
To measure Temperature and Relative inside of the POWAR box.
×1
Grove - Air quality sensor v1.3
Seeed Studio Grove - Air quality sensor v1.3
To measure Air quality inside of the POWAR box.
×1
Grove - 4-Channel SPDT Relay
Seeed Studio Grove - 4-Channel SPDT Relay
To control the heating and cooling systems.
×1
Seeed Studio Grove - MOSFET for Arduino
To control the lights and water pump intensity inside of the box.
×2
Fan Kit, 120 mm Fan
Fan Kit, 120 mm Fan
×1
Grove - WS2813 RGB LED Strip Waterproof - 60 LED/m - 1m
Seeed Studio Grove - WS2813 RGB LED Strip Waterproof - 60 LED/m - 1m
I'm using a generic, non-RGB LED STRIP that comes with 4/1 Red/Blue LEDs.
×1
Lamp, Halogen
Lamp, Halogen
×2
Power Supply 12V / 5A / 60W
×1
1200mm x 600mm x 5mm - Plywood (water repellent)
×2
PLA 3D Printing Filament
×1

Software apps and online services

Arduino IDE
Arduino IDE
Node-RED
Node-RED
Fusion 360
Autodesk Fusion 360
I used it to make all the designs and 3D-printed parts. (You won't need it to build it.)
KiCad
KiCad
This one I used only for writing the schematics, not building the board. (You won't need it to build it.)

Hand tools and fabrication machines

Laser cutter (generic)
Laser cutter (generic)
3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
Premium Female/Female Jumper Wires, 40 x 3" (75mm)
Premium Female/Female Jumper Wires, 40 x 3" (75mm)
Wire Stripper / Crimper, 10AWG - 20AWG Strip
Wire Stripper / Crimper, 10AWG - 20AWG Strip

Story

Read more

Custom parts and enclosures

POWAR FULL BODY PREVIEW

This is the full model for project visualization

Laser Cut - Board 2/2

Cut in water repellant plywood board of 1200mm x 600mm, and 5mm in thickness.

Laser Cut - Board 1/2

Cut in water repellant plywood board of 1200mm x 600mm, and 5mm in thickness.

3D PRINT - Shower

This is the shower piece. It looks better on green PLA, since that would make it glow with the grow lights.

3D PRINT - AIR tunnel

This creates an air tunnel between the fan and the top of the box.

3D PRINT - Grid

This grid is used inside the plant pot to filter the excedent water before draining it.

3D PRINT - Hose / Shower Connector

This part is used to connect the hose with the shower tube.

3D PRINT - Water Pot / Hose connector

This part goes connected to the water tank and plugged into a hose that takes the water back into the tank.

3D PRINT - Water Tank

This piece is the water tank, it has a filling tube that goes out of the box, so you can refill it without opening the box, and a whole in one of the walls, so you can connect that plant pot to reuse the excedent of water.

3D PRINT - Plan pot

This is the plant pot, which has a drain tube in the bottom part that helps to take the excedent of water back to the water tank. The bottom can be filled with small stones and covered with the 3D printed grid, so it filters the soil from the drained water.

Schematics

POWAR - Kicad Schematics

Here are only the WIO terminal connections, having into consideration that Relay and Mosfet modules could go separate. Also the Bucket module.

It would be better if everything goes in the same board.

POWAR SCHEMATICS PDF

Here are only the WIO terminal connections, having into consideration that Relay and Mosfet modules could go separate. Also the Bucket module.

It would be better if everything goes in the same board.

Connections Logic

This is a scheme of the logic of the connections.

Code

POWAR MAIN CODE

Arduino
This code is for use in ESP32 since I wasn't able to finish the code for the WIO terminal.
Subcodes go as separate files.
#include "DHT.h"
#include <PubSubClient.h>
#include <WiFiManager.h>
#include <LiquidCrystal_I2C.h>

// Sensors and Outputs
#define PIN_LED 14
#define LED_FREQ 5000
#define LED_CHANNEL 0
#define LED_RESOLUTION 8
#define PIN_DHT 33
#define DHT_TYPE DHT11
#define PIN_LDR 32
#define PIN_MOIST 34
#define PIN_TANK_LEVEL 35
#define PIN_PUMP 19
#define PIN_FAN 13
#define PIN_LAMPS 18

// MQTT
#define MQTT_SERVER "138.68.185.241"
const char* mqtt_username = "POWAR";
const char* mqtt_password = "matilda";

// Objects
DHT dht(PIN_DHT, DHT_TYPE);
WiFiClient espClient;
PubSubClient client(espClient);
LiquidCrystal_I2C lcd(0x27, 16, 2);

// Global variables sensors
struct POWAR {
  float temp, setpoint_temp; 
  float humidity, setpoint_humidity;
  float light, moisture, tank_level;
  int led_brightness, fan_speed;
  bool pump_status;
  String city, country, weather, description, curr_time, sunrise, sunset, clouds;
};
POWAR box_settings = { 0.0 , 0.0 , 0.0, 0.0, 0.0, 0.0, 0.0, 0, false, "", "", "", "", "", "", "", "" };

void setup() {
  ledcSetup(LED_CHANNEL, LED_FREQ, LED_RESOLUTION);
  ledcAttachPin(PIN_LED, LED_CHANNEL);
  pinMode(PIN_LDR, INPUT);
  pinMode(PIN_MOIST, INPUT);
  pinMode(PIN_TANK_LEVEL, INPUT);
  pinMode(PIN_PUMP, OUTPUT);
  pinMode(PIN_FAN, OUTPUT);
  pinMode(PIN_LAMPS, OUTPUT);
  Serial.begin(9600);
  delay(500);

  // LCD
  lcd.init();
  lcd.display();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("    Welcome to   ");
  lcd.setCursor(0, 1);
  lcd.print("   =  POWAR  =   ");
  
  //  Wifi
  WiFi.mode(WIFI_STA);
  WiFiManager wm;
  wm.setAPCallback(configModeCallback);
  wm.setSaveConfigCallback(saveConfigCallback);

  //wm.resetSettings();
  bool res;
  res = wm.autoConnect("---POWAR---", "12345678");
  if(!res) Serial.println("Failed to connect");
  else Serial.println("Connected!");

  delay(500);
  
  Serial.println("Connecting MQTT");
  client.setServer(MQTT_SERVER, 1883);
  client.setCallback(callback);
  
  dht.begin();
  delay(1000);
}

void loop(){
  if (!client.connected()) {
    reconnectmqttserver();
  }
  client.loop();
  getSensors();
  printDataLCD();
  runCycle();
}

powar_control.ino

Arduino
#define CYCLE_TIME_CYCLE 1000
#define TEMP_THRESHOLD 1

unsigned long lastMillis_cycle = 0;

void runCycle() {
  setPump(false);
  if (millis() - lastMillis_cycle >= CYCLE_TIME_CYCLE) {
    // Heating and cooling
    if(box_settings.temp >= (box_settings.setpoint_temp + TEMP_THRESHOLD)) {
      setLamps(false);
      setFan(true);
    }
    if(box_settings.temp <= (box_settings.setpoint_temp - TEMP_THRESHOLD)) {
      setLamps(true);
      setFan(false);
    }

    // Growing lamps
    setLEDs(box_settings.led_brightness);

    // 
    lastMillis_cycle = millis();
  }
}

powar_display.ino

Arduino
#define CYCLE_TIME_LCD 3000
unsigned long lastMillis_lcd = 0;
unsigned short page = 0;

void printDataLCD() {
  if (millis() - lastMillis_lcd >= CYCLE_TIME_LCD) {
    lastMillis_lcd = millis();
    switch (page) {
      // City + Country
      case 0:
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print(box_settings.city);
        lcd.setCursor(0, 1);
        lcd.print(box_settings.country);
        page = 1;
        break;
      // Weather + Descr
      case 1:
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print(box_settings.weather);
        lcd.setCursor(0, 1);
        lcd.print(box_settings.description);
        page = 2;
        break;
      case 2:
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Temperature");
        lcd.setCursor(0, 1);
        lcd.print(box_settings.temp);
        lcd.setCursor(5, 1);
        lcd.print("/");
        lcd.setCursor(6, 1);
        lcd.print(box_settings.setpoint_temp);
        page = 3;
        break;
      case 3:
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Clouds / light");
        lcd.setCursor(0, 1);
        lcd.print(box_settings.clouds);
        lcd.setCursor(5, 1);
        lcd.print("/");
        lcd.setCursor(6, 1);
        lcd.print(box_settings.light);
        page = 4;
        break;
      case 4:
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Humidity");
        lcd.setCursor(0, 1);
        lcd.print(box_settings.humidity);
        lcd.setCursor(5, 1);
        lcd.print("/");
        lcd.setCursor(6, 1);
        lcd.print(box_settings.setpoint_humidity);
        page = 0;
        break;
      default:
        break;
    }
  }
}

powar_mqtt.ino

Arduino
char msgmqtt[50];
void callback(char* topic, byte* payload, unsigned int len) {
  String MQTT_DATA = "";
  for (int i=0;i<len;i++) {
    MQTT_DATA += (char)payload[i];
  }
  String TOP = topic;
    if(TOP == "POWAR/City") box_settings.city = MQTT_DATA;
    else if(TOP == "POWAR/Country") box_settings.country = MQTT_DATA;
    else if(TOP == "POWAR/Weather") box_settings.weather = MQTT_DATA;
    else if(TOP == "POWAR/Description") box_settings.description = MQTT_DATA;
    else if(TOP == "POWAR/TempC") box_settings.setpoint_temp = MQTT_DATA.toInt();
    else if(TOP == "POWAR/MinTempC") return;
    else if(TOP == "POWAR/MaxTempC") return;
    else if(TOP == "POWAR/Time") box_settings.curr_time = MQTT_DATA;
    else if(TOP == "POWAR/Sunrise") box_settings.sunrise = MQTT_DATA;
    else if(TOP == "POWAR/Sunset") box_settings.sunset = MQTT_DATA;
    else if(TOP == "POWAR/Humidity") box_settings.setpoint_humidity = MQTT_DATA.toFloat();
    else if(TOP == "POWAR/Clouds") box_settings.clouds = MQTT_DATA;
    else if(TOP == "POWAR/SunLight") box_settings.led_brightness = MQTT_DATA.toInt();
    else Serial.println("Unknown Topic");
}

void reconnectmqttserver() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    Serial.println(MQTT_SERVER);
    String clientId = "POWARv3";
    if (client.connect((char*) clientId.c_str(), mqtt_username, mqtt_password)) {
      Serial.println("connected");
      client.subscribe("POWAR/City");
      client.subscribe("POWAR/Country");
      client.subscribe("POWAR/Weather");
      client.subscribe("POWAR/Description");
      client.subscribe("POWAR/TempC");
      client.subscribe("POWAR/MinTempC");
      client.subscribe("POWAR/MaxTempC");
      client.subscribe("POWAR/Time");
      client.subscribe("POWAR/Sunrise");
      client.subscribe("POWAR/Sunset");
      client.subscribe("POWAR/Humidity");
      client.subscribe("POWAR/Clouds");
      client.subscribe("POWAR/SunLight");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(2000);
    }
  }
}

void publishValues() {
  snprintf (msgmqtt, 50, "%f",box_settings.temp);
  client.publish("BOX/TempC", msgmqtt);
  snprintf (msgmqtt, 50, "%f",box_settings.humidity);
  client.publish("BOX/Hum", msgmqtt);
  snprintf (msgmqtt, 50, "%f",box_settings.light);
  client.publish("BOX/LDR", msgmqtt);
  snprintf (msgmqtt, 50, "%f",box_settings.moisture);
  client.publish("BOX/Tank", msgmqtt);
  snprintf (msgmqtt, 50, "%f",box_settings.tank_level);
  client.publish("BOX/Moist", msgmqtt);
}

powar_sensors.ino

Arduino
#define CYCLE_TIME_SENSOR 3000
unsigned long lastMillis_sensor = 0;

void getSensors() {
  if (millis() - lastMillis_sensor >= CYCLE_TIME_SENSOR) {
    // Read temperature and hmidity from DHT11
    box_settings.humidity  = dht.readHumidity();
    box_settings.temp = dht.readTemperature();
  
    if (isnan(box_settings.humidity) || isnan(box_settings.temp)) {
      Serial.println(F("Failed to read from DHT sensor!"));
      box_settings.humidity  = 0.0f;
      box_settings.temp = 0.0f;
    }
  
    // Read LDR, Moisture and tank water level
    // And convert to percentage
    box_settings.light = analogRead(PIN_LDR);
    box_settings.light = map(box_settings.light, 0, 4095, 100, 0);
    box_settings.moisture = analogRead(PIN_MOIST);
    box_settings.moisture = map(box_settings.moisture, 0, 4095, 100, 0);
    box_settings.tank_level = analogRead(PIN_TANK_LEVEL);
    box_settings.tank_level = map(box_settings.tank_level, 0, 4095, 0, 100);

    // Send data to nodered
    publishValues();
    // ONLY DEBUG
    printSensors();
    // ONLY DEBUG
    
    lastMillis_sensor = millis();
  }
}

void setLEDs(int value) {
  ledcWrite(LED_CHANNEL, value);
}

void setPump(boolean value) {
  digitalWrite(PIN_PUMP, value ? HIGH : LOW);
}

void setFan(boolean value) {
  digitalWrite(PIN_FAN, value ? HIGH : LOW);
}

void setLamps(boolean value) {
  digitalWrite(PIN_LAMPS, value ? HIGH : LOW);
}

boolean getPump() {
  return digitalRead(PIN_PUMP);
}

boolean getFan() {
  return digitalRead(PIN_FAN);
}

boolean getLamps() {
  return digitalRead(PIN_LAMPS);
}

void printSensors() {
  Serial.println("#######################################");
  Serial.print("City: ");
  Serial.print(box_settings.city);
  Serial.print("/");
  Serial.print(box_settings.country);
  Serial.print(", ");
  Serial.print(box_settings.weather);
  Serial.print(", ");
  Serial.println(box_settings.description);
  
  Serial.print("Time: ");
  Serial.print(box_settings.curr_time);
  Serial.print(" - Sunrise: ");
  Serial.print(box_settings.sunrise);
  Serial.print(" - Sunset: ");
  Serial.print(box_settings.sunset);
  Serial.print(" - Clouds: ");
  Serial.println(box_settings.clouds);

  Serial.println("---------------------------------------------");
  
  Serial.print("Humidity: ");
  Serial.print(box_settings.humidity);
  Serial.print("/");
  Serial.print(box_settings.setpoint_humidity);
  Serial.println("\t [%]");

  Serial.print("Temperature: ");
  Serial.print(box_settings.temp);
  Serial.print("/");
  Serial.print(box_settings.setpoint_temp);
  Serial.println("\t [ºC]");

  Serial.print("Light: ");
  Serial.print(box_settings.light);
  Serial.println("\t [%]");

  Serial.print("Moisture: ");
  Serial.print(box_settings.moisture);
  Serial.println("\t [%]");

  Serial.print("Tank Level: ");
  Serial.print(box_settings.tank_level);
  Serial.println("\t [%]");

  Serial.print("Pump status: "); Serial.print(getPump());
  Serial.print("\t Lamps status: "); Serial.print(getLamps());
  Serial.print("\t Fan status: "); Serial.println(getFan());
}

powar_wifi.ino

Arduino
void configModeCallback (WiFiManager *myWiFiManager) {
  // Write to LCD To connect to the AccessPoint
  Serial.println("Entered config mode");
  Serial.println(WiFi.softAPIP());
  Serial.println(myWiFiManager->getConfigPortalSSID());
  lcd.setCursor(0, 0);
  lcd.print("WiFi:---POWAR---");
  lcd.setCursor(0, 1);
  lcd.print("Psw: 12345678");
}

void saveConfigCallback () {
  // Write to LCD that the wifi is correcty set
  lcd.setCursor(0, 0);
  lcd.print("WiFi configured ");
  lcd.setCursor(0, 1);
  lcd.print("Connecting ...  ");
  Serial.println("Should save config");
}

Credits

Pablo Zuloaga Betancourt

Pablo Zuloaga Betancourt

3 projects • 8 followers
Colombian Maker based in Barcelona, working on projects related to education, the decentralization of technology and climate change.
Thanks to SEED STUDIO.

Comments