Meng-Yun Tsai

From Medien Wiki

Project : The Air Between Us

TheAirBetweenUs Winterwerkschau2025 1.jpg

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.

Initial Idea

The Plant Plant Initial Idea.png

During the two-day workshop at the Bio Lab at the beginning of the semester, I learned how to create isolated plant environments using different cultivation, especially MS Media. This was my first time incorporating plants as part of an artistic creation. The conceptual foundation of the idea based on cybernetics, particularly its principles of feedback, adaptation, and self-regulation. Therefore, I was interested in experimenting with how autonomous systems—plants in this case—respond, adapt, and communicate within interconnected ecosystems, and implemented it to my project.

Project Concept

The Integration of Plants and Electronic Sensors — Exploring Interactions Between Plant Systems and the Concept of Cybernetics.

The Air Between Us is a new media exploration through a cybernetic approach, integrating electronic sensors with biological systems. By creating two isolated plant systems and analyzing their environmental data, the work attempts to simulate and observe potential interaction and "communication" between the living organisms through the shared mechanism.

A Helleborus in a sealed autoclave bag containing MS Media and several Sundew plants in a glass jar with the same cultivation medium served as the two independent systems. Due to the constraints of placing sensors directly inside the sealed environments, I decided to create a shared mechanism that using custom-made transparent plastic boxes. Inside each box, an MQ-2 gas sensor and a DHT22 temperature and humidity sensor were installed to measure emitted gases and environmental conditions, which were channeled through plastic tubes extended from the sealed plants environment. A third sensor box acts as a bridge between the two plant systems, capturing any atmospheric interactions that may arise when they are connected.

ESP32 microcontroller collected sensor data, sending real-time readings via Wi-Fi to the IoT platform Thingspeak, where the information was visualized through dynamic graphs on a vertical screen. Additionally, the data from the third group of MQ-2 sensor was processed through a programmed transformation: its analog read values were converted into ASCII characters, forming a continuous, scrolling string displayed on an LCD screen. This text-based representation used as an artistic metaphor for the hidden dialogue between plants, transforming their biochemical emissions into an abstract yet perceivable language.

By referring to cybernetic principles, this work experimented the feedback loops within living systems and their adaptive behaviors. The installation invited the audience to reconsider plants as active agents in communication, rather than passive organisms, offering a poetic interpretation of interspecies interaction beyond human perception.

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

I chose to use MS Media because its transparent texture allows the plant’s roots to be clearly visible. This might allow us to observe more potential changes throughout the flowering phase or even the entire life cycle of the plant.

The Plant Plant Preparation MS Medium.png

Recipe:

1 Liter Water:
Murashige 4.5 g
Gelrite 5 g
D(+) Saccharose

(sugar)

20 g


Sterilizing & Transplanting

ThePlantPlant Sterilizing&Transplanting.png

To maintain a clean system and prevent bacteria, the soil surrounding the roots must be removed, and the roots should be briefly sterilized with chlorine. Cut off extra leaves in order to allow the nutrients provided by the MS Media to be concentrated on the flowers or buds.

Note:

When selecting flowers to transplant in autoclave bags, choose plants with strong roots and straight stems. This makes it easier to move them into MS Media and less likely to collapse. Furthermore, it would be better to select flowers that are not fully bloomed, but still have some buds. This might extend the lifecycle of the system and allows for the observation of the blooming process. Plants also tend to release more gases during flowering.

The first plant, Helleborus, I purchased turned out to be the best choice unexpectedly. However, when I later attempted to use Primula, its thin and soft roots were unable to support within the MS Media, causing it to sink and ending the experiment.

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:

platformio.ini

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps = 
	arduino-libraries/ArduinoHttpClient@^0.6.1
	mathworks/ThingSpeak@^2.0.0
	adafruit/DHT sensor library@^1.4.6
    marcoschwartz/LiquidCrystal_I2C@^1.1.4

main.cpp

/*
  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 )

Exhibition

Technical Riders

General Set Up Installation Materials Technical Equipment
Space ~ 200 x 150 x 200cm ESP32 Mac mini
Table 120 x 80cm DHT22 Sensors *3 Monitor vertical
Stand with hooks

( Infusionsständer )

10k Ω Resistors *3 Extension Cord ~ 3 - 4 sockets
Notice:

The power and WiFi must remain on

for the sensor data to be continuously

uploaded and synchronized 24/7 via WiFi

to the ThingSpeak (IoT platform).

MQ-2 Sensors *3 HDMI cable
LCD 16x2 LED Lamp
Breadboard 5V External Power Supply
Jump Wires a lot ESP32 Type C Cable + Adapter
Autoclave Bags *3
Glass Jar + Cap

(Rundglas + Trinkhalmdeckel)

Transparent Plastic Boxes

(Klarsichtfaltschachtel)

*3
Transparent Plastic Bags

(Klarsichttüten klein)

*3
Cottons
Tubes
Ms Media
Plants Helleborus, Sundew

Winterwerkschau

I appreciate that this project was successfully exhibited at Winterwerkschau and received so much positive feedback. This was my first time incorporating plants into my artistic practice. At the beginning, I was unsure whether I could effectively apply the abstract concept and unfamiliar technics to the work. In the past, I faced many challenges and frustrations in the electronics and programming part, and the unpredictability of the unknown often made me doubt whether I could complete the project. However, throughout the process, I received a lot of support and assistance, encountered many surprises, and was able to integrate all the elements I wanted to experiment with. This resulted in a fully functioning prototype, a truly rewarding experience.

There are, of course, still many aspects that could be improved. Although I took advice to present the interconnected wiring, including electrical wires and plastic tubes, as a readable diagram, I was still frequently asked about it during the exhibition. That is to say, my design was not intuitive enough, and the structure was not immediately understandable to the audience, requiring further explanation. As for plants, the Helleborus was already approaching the end of its life cycle. Ideally, it would have been best to show it when it was fully blooming. Lastly, the MQ-2 gas sensor data conversion into ASCII codes was successfully executed in the program, aiming to represent the concept of a dialogue between two plants. However, I personally feel that the interpretation was not fully realized, and I would like to explore other expressive approaches.

As the first version of prototype, it is a fulfilled and successful experiment with many possibilities for further development. I’m looking forward to continuing this project in the future.

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