Foresta-Open is a networked environmental sensing project that uses MQTT (Message Queuing
Telemetry Transport) to transmit real-time sensor data from embedded microcontrollers to an
Internet of Things prototyping platform called shiftr.io, which can then be subscribed to from
anywhere in the world to drive art installations or other projects. It is part of an ecological and
artistic research initiative exploring how environmental data can be experienced and visualized
in inclusive and interactive formats.
MQTT is a lightweight publish/subscribe protocol designed for low-power devices. It’s ideal for transmitting sensor data from remote devices, managing multiple sensor nodes, and keeping communication efficient and scalable.
This project includes two key repositories:
Shiftr is a cloud-hosted MQTT broker that provides an interface for visualizing device connections and message pathways in real time. It supports MQTT standard functions, but keeps things lightweight for prototyping and education.
💡Note: shiftr.io has a basic free service, which works for 6 hrs a day only. For long term projects it is worth purchasing your own instance.
In shiftr, you connect to an MQTT broker instance. Access is controlled through a token, which includes the username, password (key), and instance URL. Tokens take the form:
| protocol://user:password@instance.cloud.shiftr.io |
For testing, shiftr provides a free public broker:
| mqtt://public:public@public.cloud.shiftr.io |
Here public is the user, password, and instance name. This is useful for examples, but not secure.
You can create your own broker instance in the shiftr dashboard. Each instance gets its own URL and supports multiple tokens with different permissions (read, write, or full access).
Example token:
| mqtt://tester123:DrB0qlfo4EHJGT7W@tester123.cloud.shiftr.io |
Where:
user/instance = tester123
password (key) = DrB0qlfo4EHJGT7W
URL = tester123.cloud.shiftr.io
Use custom instances for projects or class work. Public is fine for demos, but tokens tied to your instance give you control and security. For this setup we will use the public instance of shiftr.io to test our systems.
The ESP32 sketches read analog or digital sensor values and publish them using WiFi to shiftr.io. These values are formatted as MQTT messages under specific topics, which allows any client to subscribe and to receive real-time values. The Foresta-Open system publishes live sensor readings to customizable MQTT topics that can then be subscribed to for individualized projects.
At this point, you should have a clear sense of what MQTT is and how shiftr.io works as the broker that manages communication between your devices and clients. With this foundation in place, we can now turn to the practical steps of the Foresta-Open project. This guide will help you to:
The following hardware and software components are needed to replicate or build the Foresta-Open MQTT system:
Hardware
| Component | Description |
| ESP32 Firebeetle x 4 | Microcontroller with built-in WiFi, BLE support and an onboard battery charger. |
| Sensors | VOC, wind, lux, soil moisture, soil temperature, particulate, CO2, temperature and humidity |
| USB-C/USB-micro cable | For programming and power |
| Breadboard & jumpers | For prototyping sensor connections |
Software
| Tool | Purpose |
| Arduino IDE | Used to upload sketches to the ESP32 |
| ESP32 Board Package | Must be installed via the Board Manager |
| Arduino Sensor Libraries | Includes EspMQTTClient and sensor-specific libraries. |
| Shiftr.io Account | Acts as a cloud-based MQTT broker |
| Foresta-Open Repository | Visualizes incoming MQTT messages |
This section guides you through setting up the Arduino development environment and preparing your ESP32 boards for use with the Foresta system. By the end of this section, you’ll be ready to flash sketches to your ESP32 boards and begin testing communication between them.
| https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json |
New to Arduino? Follow this detailed walkthrough from Random Nerd Tutorials:
Installing the ESP32 Board in Arduino IDE (Windows instructions)
In the Arduino IDE, go to the Library Manager (Tools > Manage Libraries) and install the following:
Core Libraries
Sensor-Specific Libraries
💡Note: Each sketch includes its required libraries at the top. You only need to install the ones relevant to your use case.
All ESP32 sketches used in this project are located in a single GitHub repository.
| git clone https://github.com/JaneTingley/Foresta-Open.git |
| Foresta-Open/Generic-ESPNow |
This folder contains:
You will use one ESP32 for each of the sensor sketches, and one ESP32 for the gateway. For flashing instructions and communication workflow, continue to Section 4.
This section walks you through uploading the Generic-ESPNow sketches to ESP32 boards.
💡Note: there are two types of sensor sketches:
In the Foresta network, there are two types of ESP32 devices:
This approach enables remote sensing that is beyond wifi access. The gateway is connected to WiFi (it needs ongoing power and WiFi access) and creates its own mesh network that has a range of about 300-500 meters. (The ESP is capable of high power mode to increase this range, but this has not been deployed yet.) It is possible to use a MiFi device to convert SIM data to WiFi. This has been tested and works well. However MiFi requires a robust solar/battery combination to keep it charged.
| [Sensor ESP32] | ESP-NOW ↓ [Gateway ESP32 (e.g., WIND-Lux Rare)] | Wi‑Fi → shiftr.io (MQTT) |
All required sketches are found in the Foresta-Open/Generic-ESPNow folder and correspond to different environmental sensing modules:
Sensor Sketches
| Sketch Name | Purpose | Required Libraries |
|---|---|---|
| EspNowMQTT-Air-Generic | Detects airborne VOCs/ CO2, air temperature and humidity | Sensirion I2C SGP41, Adafruit_Sensor, CCS811 |
| EspNowMQTT-Atmosphere-Generic | Measures Particulate Matter and Rain | PMS3005 (modified version) |
| EspNowMQTT-Soil-Generic | Measures soil water content | Analog read only (no additional library) |
Gateway Sketch
The gateway connects to WiFi, receives ESP-NOW messages from any sensor node and publishes them to shiftr.io via MQTT.
| Gateway Sketch | Role | Required Libraries |
|---|---|---|
| MQTT-Gateway-WIND-Lux-Generic | Gateway + optional wind/lux sensor readings | EspMQTTClient (modified version) |
Responsibilities:
Before uploading any sketches, ensure the following:
| EspMQTTClient client(
“wifi-SSID”, //SSID “wifi-password”, //wifi password “public.cloud.shiftr.io”, //Instance location, “public”, // Username, “public”, // secret password, GATEWAY_NAME, // Client ID (do not change) 1883 // default MQTT port (do not change) ); |
| #define GATEWAY_NAME “00Name-Gateway” |
| lightReading = analogRead(luxPin); // This is the light sensor client.publish(“Topic-Name/Light-ESP”, String(lightReading)); // this publishes Light data windReading = analogRead(windPin); // This is the wind sensor client.publish(“Topic-Name/Wind-ESP”, String(windReading)); //this publishes Wind data |
| WiFi connected MQTT connected to shiftr.io as [client_id] Listening for ESP-NOW packets… |
Once the gateway is running, it will automatically forward any messages it receives from ESP-NOW nodes to shiftr.io using the topic and value string received.
💡A note on upload speed: the ESP32 firebeetle is not capable of managing uploads automatically set to 921600. In order to upload, the speed must be set to 115200.
💡A note on topic naming: The included gateway sketch already publishes sensor values to MQTT topics like Topic-Name/Wind-ESP and Topic-Name/Light-ESP. These do not need to be changed unless you are customizing your own MQTT structure later on.
Each sensor pod (e.g., Soil, Air, Atmosphere) is an ESP32 that collects environmental data and transmits it to the gateway via ESP-NOW. These steps walk you through preparing and flashing the sensor code.
| Found gateway on channel X Paired with gateway Publish: Soil/Moisture=512 Send Status: Success |
Before uploading and deploying any sensor sketches you must build the ESP sketches. This section shows how to physically build each sensor module, using the schematics provided in the Foresta repository.
💡Note: double check the pin definitions at the beginning of each sketch (they may vary slightly between sketches)
Sketch: MQTT-Gateway-WIND-Lux-Generic
Sensors:
Sketch: EspNowMQTT-Soil-Generic
Sensors:
Sketch: EspNowMQTT-Atmosphere-Rare.ino
Sensors:
Sketch: EspNowMQTT-Air-Rare.ino
Sensors:
Deep sleep mode allows your ESP32 sensor nodes to operate for months on a single battery charge. This section explains how to use deep sleep, how to adjust the sleep duration, and how to power the devices safely in remote environments.
Deep sleep is a special low-power mode where the ESP32 shuts down most internal functions, including the CPU, radio, and peripherals. Only the RTC (Real-Time Clock) and essential wake timers stay active. In Foresta-Open, the sensor nodes wake up briefly to power their sensors, collect data, and send data to a WiFi-connected gateway, before returning to deep sleep. This cycle can be tuned to optimize battery life, making it ideal for remote environmental sensing.
Deep sleep versions of each sensor sketch are located in:
| Foresta-Open/Generic-DeepSleep |
These sketches are modified to include:
At the end of loop(), the following can be found:
| #define DEEP_SLEEP_SECONDS 59*60
#define AWAKE_SECONDS 60 |
This code:
💡Note: Adjust the number 60000000 to control how often your sensor wakes up
For example:
In a field deployment, sensor nodes must operate independently from wall power. To enable long-term, low-power functionality, the hardware supports deep sleep with the following features:
| Method | Description |
| USB power | Used for testing, debugging, or indoor installations |
| 3.7v Lithium Ion 18650 Button-Top Battery | Primary rechargeable option for remote deployments; built-in protection circuit prevents dangerous overcharging, over-discharging, and short circuits that can cause damage or fire |
| Solar + Battery | Can be used in combination with a charge controller for long-term outdoor setups |
💡Note: Deep sleep only works effectively if your sensor is not draining power while sleeping. That’s why the hardware uses a MOSFET switch to cut power to the sensors while the ESP32 sleeps.
About MOSFETs
The sensor ground is routed through a MOSFET. The ESP32 Pin D2 controls the gate of the MOSFET, which switches the sensor power on and off.
When working with deep sleep sketches during development:
| #define DEEP_SLEEP_SECONDS 10 // 10 seconds for test purposes |
| Serial.println(“Boot #” + String(bootCount)); |
Use deep sleep if:
Avoid or disable deep sleep if:
| Stage | What Happens |
| Wake | ESP32 powers up, starts in setup() |
| Sensor On | MOSFET pin (e.g., D2) set to HIGH |
| Sensor Warmup | Delay ensures sensor stabilizes |
| Read/Send | Data is collected and sent via ESP-NOW |
| Sensor Off | MOSFET pin set to LOW |
| Sleep | esp_deep_sleep_start() called |
| Repeat | Wake on timer, process restarts |
Now that your sensors are sending data to shiftr.io via the gateway, you’ll want to receive and use that data in your own creative or analytical applications. This section covers how to subscribe to your MQTT topics using popular platforms.
All sensor nodes send data using this format:
| topic=value |
The gateway receives this and publishes it to the MQTT broker using:
These topic names are customizable in your sketches but should remain consistent across the network for easier mapping.
In this step, you’ll learn how to receive real-time sensor data from your Foresta MQTT network directly into Processing, using the MQTT Client for Processing library by 256dpi.
Before you begin:
To install the MQTT library:
To connect to the shiftr.io broker:
Here’s a minimal working example that connects to the public instance of shiftr.io, publishes a simple counter to the topic ForestaOpen/debug/counter, and listens for messages on the same topic. This example tests basic send-receive functionality, and can be modified later for your own sensor type and topic.
| import mqtt.*; MQTTClient client; String latestMessage = “”; int crashCounter = 0; // Keeps a running count that increases every few seconds; reconnect cycles int lastPublishTime = 0; boolean connected = false; // Track connection state manually int lastReconnectAttempt = 0; // Track last reconnect attempt time void setup() { size(400, 200); client = new MQTTClient(this); println(“Connecting to MQTT broker…”); client.connect(“mqtt://public:public@public.cloud.shiftr.io”); client.subscribe(“ForestaOpen/debug/counter”); } void draw() { background(255); textAlign(CENTER, CENTER); textSize(20); fill(0); text(“Counter: “ + crashCounter, width/2, height/2 – 30); text(“Last message: “ + latestMessage, width/2, height/2 + 20); // Every 3 seconds, publish the counter (only if connected) if (connected && millis() – lastPublishTime > 3000) { lastPublishTime = millis(); crashCounter++; client.publish(“ForestaOpen/debug/counter”, str(crashCounter)); println(“Published counter: “ + crashCounter); } // Try reconnecting if disconnected (every 5 seconds) if (!connected && millis() – lastReconnectAttempt > 5000) { lastReconnectAttempt = millis(); println(“Connection lost — attempting to reconnect…”); try { client.connect(“mqtt://public:public@public.cloud.shiftr.io”); client.subscribe(“ForestaOpen/debug/counter”); } catch (Exception e) { println(“Reconnect failed: “ + e.getMessage()); } } } // Called automatically when a message arrives void messageReceived(String topic, byte[] payload) { latestMessage = new String(payload); println(topic + ” → “ + latestMessage); } // Called automatically when connected void clientConnected() { println(“Connected to broker!”); connected = true; } // Called automatically when connection lost void connectionLost() { println(“Connection lost!”); connected = false; } |
💡Note: If you have a personal shiftr.io account and don’t have your ESP node running yet, you can test this sketch by creating a private namespace and manually publishing values to your subscribed topic from the dashboard using the “Send Message” panel.