Meng-Yun Tsai

From Medien Wiki

Project : The Air Between Us

This project integrates technology and nature, attempting to explore the invisible communication of plants. The use of sensors and IoT to uncover the hidden exchanges of gases and environmental data between isolated plant systems. By connecting their ecosystems through shared mechanisms, it transforms plant interactions into visible and tangible sensory experiences.

TheAirBetweenUs Winterwerkschau2025 1.jpg

Initial Idea

The Plant Plant Initial Idea.png

-

Project Concept

-

Brainstorming

https://www.figma.com/board/liuTtexNHdKw09Ce0443xg/The-Plant-Plant_Moodboard?node-id=0-1&t=8V8UNVewNwZUOFky-1

Sketches

Concept Sketches.png

Moodboard

The Plant Plant Moodboard.png

Preparation ( 2 Isolated Plant Systems)

MS Media

-

The Plant Plant Preparation MS Medium.png



Recipe:

1 Liter Water:

Murashige 4.5 g

Gelrite 5 g

D(+) Saccharose 20 g (sugar)


Sterilizing & Transplanting

ThePlantPlant Sterilizing&Transplanting.png

-

Plants Observation

Documented Period : 02.11.2024 - 08.02.2025

https://www.figma.com/board/SHD54HvGe71StBY7NUiXzr/The-Plant-Plant_Plants-Observation?node-id=0-1&t=hHtiHZMcxLQn19er-1

Prototype

Electronics / Circuits

ThePlantPlant CircuitsDesign Resize.png

Circuits Diagram Software: Cirkit Designer

Programming

Code:

/*
  Project: The Plant Plant - The Air Between Us
  Integration and Conversation between 2 isolated plant systems
  By using Microcontroller, sensors and IoT platform (cloud)
  --
  Board: ESP32 Development Board
  Component: MQ-2 Gas Sensor
             DHT22 Temperature & Humidity Sensor
             Alphanumerisches LCD 16x2, blau
  --
  Note: After the MQ-2 sensor is powered on, it needs to be preheated for 
  at least 3 minute before stable measurement readings can be obtained. 
  It is normal for the sensor to generate heat during operation due to 
  the presence of a heating wire inside.

*/

//add libraries
#include <Arduino.h>
#include <Adafruit_Sensor.h>     // for DHT22
#include <DHT.h>
#include <DHT_U.h>    
#include <WiFi.h>                // for connecting Wifi   
#include "esp_wpa2.h"            //wpa2 library for connections to Enterprise networks
#include <ThingSpeak.h>          // for publishing Sensor Readings to Thingspeak
#include <Wire.h>                // for getting the LCD Address
#include <LiquidCrystal_I2C.h>   // for LCD

/* Set up: DHT22 */
#define DHTTYPE DHT22   // sensor version, we are using DHT22
#define DHT22_PIN1  4 // Group01 pin GPIO4
#define DHT22_PIN2  15 // Group02 pin GPIO15 
#define DHT22_PIN3  0 // Group03 pin GPIO0
//Create sensors called "dht..."
DHT_Unified dht01(DHT22_PIN1, DHTTYPE); 
DHT_Unified dht02(DHT22_PIN2, DHTTYPE); 
DHT_Unified dht03(DHT22_PIN3, DHTTYPE); 
int delayMS = 1500; // add delay after each reading

/* Set up: MQ2 */
// Define the pin numbers for the Gas Sensor
#define MQ2_PIN1  34  // Group01 pin GPIO34
#define MQ2_PIN2  35  // Group02 pin GPIO35
#define MQ2_PIN3  32  // Group03 pin GPIO32

/* Set up: Thingspeak */
// due to the privacy of the account, won't show the real numbers
unsigned long ChannelNumber1 = XXXXXXX;     //Insert Thingspeak Channel's number 
unsigned long ChannelNumber2 = XXXXXXX;     //Insert 2nd Thingspeak Channel's number 
const char *WriteAPIkey1="XXXXXXXXXXXXXXXX";   // Thingspeak Official Channel API Key
const char *WriteAPIkey2="XXXXXXXXXXXXXXXX";   // 2nd Thingspeak Official Channel API Key

/* Set up: Eduroam Wifi */
WiFiClient client;

byte mac[6];
const char* host = "arduino.clanweb.eu";     //webserver test
String url = "/eduroam/data.php";            //URL to target PHP file test
#define EAP_ANONYMOUS_IDENTITY "XXXXXX@uni-weimar.de" //anonymous@example.com, or you can use also nickname@example.com
#define EAP_IDENTITY "XXXXXX" //nickname@example.com,at some organizations should work nickname only without realm, but it is not recommended
#define EAP_PASSWORD "XXXXXX" //password for eduroam account
//SSID NAME
const char* ssid = "eduroam"; // eduroam SSID

/* Set up: LCD */
// set the LCD number of columns and rows
int lcdColumns = 16;
int lcdRows = 2;
// set LCD address, number of columns and rows
// if don't know the display address, run an I2C scanner sketch
LiquidCrystal_I2C lcd(0x27, lcdColumns, lcdRows); 
// set up display texts 
String messageStatic = "Our Dialogue";
String messageScrolling;
// A string variable used to store accumulated ASCII conversations
String dialogue = ""; 
// for counting characters from ASCII codes 
int charCount = 0;     

/* Function: Scrolling Texts */
// The function accepts the following arguments:
// row: row number where the text will be displayed
// message: message to scroll
// delayTime: delay between each character shifting
// lcdColumns: number of columns of your LCD
void scrollingText(int row, String message, int delayTime, int lcdColumns) {
  for (int i=0; i < lcdColumns; i++) {
    message = " " + message;  
  } 
  message = message + " "; 
  for (int pos = 0; pos < message.length(); pos++) {
    lcd.setCursor(0, row);
    lcd.print(message.substring(pos, pos + lcdColumns));
    delay(delayTime);
  }
}

/* Functions: Group01 Flowers */
void Group01(){
  // Delay between measurements
  delay(delayMS);   

  //Reading DHT22
  // Get access to sensor event through custom sensor event type
  sensors_event_t event;

  // get temperature event (by reference, not by copy) and print its value.
  // "event" is  - after this - filled with information from the sensor.
  dht01.temperature().getEvent(&event);
  if (isnan(event.temperature))
  {
    Serial.println(F("Error reading temperature!"));
  }
  else
  {
    Serial.print(F("G1 Temperature: "));
    Serial.print(event.temperature);
    Serial.println(F("°C"));
    ThingSpeak.setField(2, event.temperature); // write to Thingspeak Channel Field 2
  }

  // get humidity event (by reference, not by copy) and print its value.
  // "event" is  - after this - filled with information from the sensor.
  dht01.humidity().getEvent(&event);
  if (isnan(event.relative_humidity))
  {
    Serial.println(F("Error reading humidity!"));
  }
  else
  {
    Serial.print(F("G1 Humidity: "));
    Serial.print(event.relative_humidity);
    Serial.println(F("%"));
    ThingSpeak.setField(3, event.relative_humidity); // write to Thingspeak Channel Field 3
  }

  //Reading MQ2 
  int mq2g1V = analogRead(MQ2_PIN1);
  Serial.print("G1 MQ2 Analog output: ");
  Serial.println(mq2g1V);
  Serial.println(" ------------------ ");
  delay(1000);
  ThingSpeak.setField(1, mq2g1V); // write to Thingspeak Channel Field 1
}

/* Functions: Group02 Sundew */
void Group02(){
  delay(delayMS);   

  //Reading DHT22
  sensors_event_t event;

  dht02.temperature().getEvent(&event);
  if (isnan(event.temperature))
  {
    Serial.println(F("Error reading temperature!"));
  }
  else
  {
    Serial.print(F("G2 Temperature: "));
    Serial.print(event.temperature);
    Serial.println(F("°C"));
    ThingSpeak.setField(5, event.temperature); // write to Thingspeak Channel Field 5
  }

  dht02.humidity().getEvent(&event);
  if (isnan(event.relative_humidity))
  {
    Serial.println(F("Error reading humidity!"));
  }
  else
  {
    Serial.print(F("G2 Humidity: "));
    Serial.print(event.relative_humidity);
    Serial.println(F("%"));
    ThingSpeak.setField(6, event.relative_humidity); // write to Thingspeak Channel Field 6
  }

  //Reading MQ2 
  int mq2g2V = analogRead(MQ2_PIN2);
  Serial.print("G2 MQ2 Analog output: ");
  Serial.println(mq2g2V);
  Serial.println(" ------------------ ");
  delay(1000);
  ThingSpeak.setField(4, mq2g2V); // write to Thingspeak Channel Field 4
}

/* Functions: Group03 Integration */
void Group03(){
  delay(delayMS);   

  //Reading DHT22
  sensors_event_t event;

  dht03.temperature().getEvent(&event);
  if (isnan(event.temperature))
  {
    Serial.println(F("Error reading temperature!"));
  }
  else
  {
    Serial.print(F("G3 Temperature: "));
    Serial.print(event.temperature);
    Serial.println(F("°C"));
    ThingSpeak.setField(7, event.temperature); // write to Thingspeak Channel Field 7
    
  }

  dht03.humidity().getEvent(&event);
  if (isnan(event.relative_humidity))
  {
    Serial.println(F("Error reading humidity!"));
  }
  else
  {
    Serial.print(F("G3 Humidity: "));
    Serial.print(event.relative_humidity);
    Serial.println(F("%"));
    Serial.println(" ------------------ ");
    ThingSpeak.setField(8, event.relative_humidity); // write to Thingspeak Channel Field 8
    
  }

}

void Group03_MQ2(){
  //Reading MQ2 
  int mq2g3V = analogRead(MQ2_PIN3);
  Serial.print("G3 MQ2 Analog output: ");
  Serial.println(mq2g3V);
  delay(500);
  ThingSpeak.writeField(ChannelNumber2, 1, mq2g3V, WriteAPIkey2);

  // LCD messages  
  // convert MQ-2's value to ASCII code
  int ascii_value = map(mq2g3V, 200, 800, 65, 122);
  char ascii_char = (char)ascii_value;

  // a new character is added to the string (the previous one is kept)
  dialogue += ascii_char ;
  charCount++; // accumulated character count +1

  // Try using spaces to create a variation in sentence length for a more natural dialogue effect.
  // when the accumulated character count exceeds 7, insert a space randomly
  if (charCount > 7) {
    // if the count exceeds 15, force to insert a space 
    if (random(0, 2) == 1 || charCount >= 15) { 
      dialogue += " ";
      charCount = 0; // after inserting a space, reset the count
    }
  }
  // if the string length exceeds the LCD limit (32 characters)
  if (dialogue.length() > 32) { 
    dialogue = dialogue.substring(1); // remove the oldest character
  } 
  // print scrolling message
  lcd.setCursor(0, 1);
  messageScrolling = dialogue.substring(0, 16);
  scrollingText(1, messageScrolling, 350, lcdColumns);
}


void setup()
{
  // initialize serial communication
  Serial.begin(9600); 

  // connect or reconnect to wifi
  WiFi.disconnect(true); //disconnect from WiFi to set new WiFi connection
  WiFi.mode(WIFI_STA); //init wifi mode
  
  // start wifi connection with eduroam
  // WITHOUT CERTIFICATE - WORKING WITH EXCEPTION ON RADIUS SERVER
  WiFi.begin(ssid, WPA2_AUTH_PEAP, EAP_ANONYMOUS_IDENTITY, EAP_IDENTITY, EAP_PASSWORD); 
  
  // check if wifi connected
  // continue after while loop finished
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(F("."));
    }
    Serial.println("");
    Serial.println(F("WiFi is connected!"));
    Serial.println(" ");
  
  // initialize DHT22
  dht01.begin(); 
  dht02.begin(); 
  dht03.begin(); 
  
  // initialize ThingSpeak
  ThingSpeak.begin(client);   

  // initialize LCD
  lcd.init();
  // turn on LCD backlight                      
  lcd.backlight();            

}

void loop()
{
  yield(); // buffer ESP - stuff

// show LCD
  lcd.clear();
  // set cursor to position (column and row)
  lcd.setCursor(2, 0);
  // print static message
  lcd.print(messageStatic);

// Execute functions of 3 groups
  Group03_MQ2();
  Group03();
  delay(delayMS);

  Group01();
  delay(delayMS);

  Group02();
  delay(delayMS);


  // Write to ThingSpeak.
  int x = ThingSpeak.writeFields(ChannelNumber1, WriteAPIkey1);

  if(x == 200){
     Serial.println("Channel update successful.");
   }else{
     Serial.println("Problem updating channel. HTTP error code " + String(x));
   }
   delay(15000);
}

Data Visualization

ThingSpeak Screenshots ( sensors data real-time uploaded )

Winterwerkschau (Exhibition)

-photos-

Technical Riders

-

References

ESP32 with DHT22 Temperature and Humidity Sensor

The Plant Plant - GitHub Repo

https://randomnerdtutorials.com/esp32-dht11-dht22-temperature-humidity-sensor-arduino-ide/

ESP32 with MQ-2 Gas Sensor

https://docs.sunfounder.com/projects/umsk/en/latest/03_esp32/esp32_lesson04_mq2.html

ESP32 Publish Sensor Readings to ThingSpeak (Wi-Fi)

https://randomnerdtutorials.com/esp32-thingspeak-publish-arduino/#multiple

ESP32 with I2C LCD (Liquid Crystal Display)

https://randomnerdtutorials.com/esp32-esp8266-i2c-lcd-arduino-ide/

https://zhillan-arf.medium.com/getting-lcd-displays-to-work-with-esp32-e7fe8016bffd