147
edits
(57 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
== Project : The Air Between Us == | == Project : The Air Between Us == | ||
[[File:TheAirBetweenUs Winterwerkschau2025 1.jpg|frameless|1000x1000px]] | |||
''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 == | |||
[[File:The_Plant_Plant_Initial_Idea.png|frameless|700x700px]] | |||
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 === | === Brainstorming === | ||
Line 21: | Line 24: | ||
=== Sketches === | === Sketches === | ||
[[File:Concept Sketches.png|800x800px]] | |||
=== Moodboard === | === Moodboard === | ||
[[File:The Plant Plant Moodboard.png|frameless|850x850px]] | |||
== Preparation ( 2 Isolated Plant Systems) == | == Preparation ( 2 Isolated Plant Systems) == | ||
=== MS Media === | === 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.[[File:The_Plant_Plant_Preparation_MS_Medium.png|right|frameless|564x564px]] | |||
'''Recipe:''' | |||
{| class="wikitable" | |||
|+ | |||
!1 Liter Water: | |||
| | |||
|- | |||
|Murashige | |||
|4.5 g | |||
|- | |||
|Gelrite | |||
|5 g | |||
|- | |||
|D(+) Saccharose | |||
(sugar) | |||
|20 g | |||
|} | |||
=== Sterilizing & Transplanting === | === Sterilizing & Transplanting === | ||
[[File:ThePlantPlant Sterilizing&Transplanting.png|frameless|800x800px]] | |||
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.<blockquote>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. | |||
</blockquote> | |||
=== Plants Observation === | === 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 == | == Prototype == | ||
=== | === Electronics / Circuits === | ||
[[File:ThePlantPlant CircuitsDesign Resize.png|frameless|800x800px]] | |||
<small>Circuits Diagram Software: Cirkit Designer</small> | |||
=== Programming === | === Programming === | ||
Code | Code: | ||
'''platformio.ini'''<syntaxhighlight lang="arduino"> | |||
[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 | |||
</syntaxhighlight> | |||
'''main.cpp'''<syntaxhighlight lang="arduino"> | |||
/* | |||
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); | |||
} | |||
</syntaxhighlight> | |||
== | === Data Visualization === | ||
- | ThingSpeak Screenshots ( sensors data real-time uploaded )<gallery mode="nolines" widths="300" heights="300"> | ||
File:Screenshot Thingspeak 040225-050225.png|Feb 04.- 05. | |||
File:Screenshot Thingspeak 060225-050225.png|Feb 05.- 06. | |||
File:Screenshot Thingspeak 070225-060225.png|Feb 06.- 07. | |||
File:Screenshot Thingspeak 090225-080225.png|Feb 08.- 09. | |||
File:Screenshot Thingspeak 090225 Final.png|Feb 09. Final | |||
</gallery> | |||
== Exhibition == | |||
=== Technical Riders === | === Technical Riders === | ||
- | {| class="wikitable" | ||
! colspan="2" |'''General Set Up''' | |||
! colspan="2" |'''Installation Materials''' | |||
! colspan="2" |'''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 | |||
|- | |||
| colspan="2" rowspan="12" |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 | |||
| colspan="2" rowspan="8" | | |||
|- | |||
|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 === | |||
<gallery mode="nolines" widths="250" heights="200"> | |||
File:Winterwerkschau01.jpg | |||
File:Winterwerkschau06.jpg | |||
File:Winterwerkschau07.jpg | |||
File:Winterwerkschau04.jpg | |||
File:Winterwerkschau02.jpg | |||
File:Winterwerkschau03.jpg | |||
File:Winterwerkschau05.jpg | |||
File:Winterwerkschau08.jpg | |||
File:Winterwerkschau Dialogue01.jpg | |||
File:Winterwerkschau Dialogue02.jpg | |||
File:The Plant Plant The Air Between Us DialogueScrolling.mp4 | |||
</gallery>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 == | == References == |
edits