Air Mattress Controller V1
Open-Source Adjustable Firmness System

5.0 5. Build Guide

Step 1 — Set up the ESP32 and read one pressure sensor

Goal: Get the ESP32 connected to your computer, upload code successfully, and watch live sensor values in the Serial Monitor.

What you need for this step

  • 1 × ESP32 dev board
  • 1 × pressure sensor
  • jumper wires
  • USB cable
  • computer with Arduino IDE installed

Before you start

For this step, do not connect mains power. Only power the ESP32 through USB.

Step 1A — Install Arduino IDE and ESP32 support

  1. Install Arduino IDE
  2. Open Arduino IDE
  3. Go to File → Preferences
  4. Add this URL to Additional Boards Manager URLs:
https://dl.espressif.com/dl/package_esp32_index.json
  1. Go to Tools → Board → Boards Manager
  2. Search for ESP32
  3. Install the ESP32 board package

Step 1B — Connect the ESP32

  1. Plug the ESP32 into your computer with USB
  2. In Arduino IDE, go to Tools → Board and select the ESP32 board that matches your dev board
  3. Go to Tools → Port and choose the correct COM port

If no port appears:

  • try another USB cable
  • unplug and reconnect the board
  • install the proper USB driver for your board, usually CP2102 or CH340

Step 1C — Wire the pressure sensor

Pressure Sensor PinConnect To
VCC3.3V or 5V, depending on the sensor board requirements
GNDGND
OUTGPIO34

Step 1D — Upload the test code

const int pressurePin = 34;

void setup() {
  Serial.begin(115200);
}

void loop() {
  int raw = analogRead(pressurePin);
  Serial.println(raw);
  delay(500);
}

Step 1E — View the sensor values

  1. Open Serial Monitor
  2. Set the baud rate to 115200
  3. Watch the values scroll
What success looks like: values update every half second, and values change when you apply pressure to the sensor line.

If it does not work

  • check board and COM port selection
  • check sensor ground
  • check the output pin wiring
  • check that the sensor is powered correctly

Step 2 — Wire and test one valve with one MOSFET

Goal: Use the ESP32 to switch one 12V valve on and off safely.

What you need for this step

  • ESP32 from Step 1
  • 1 × 12V normally closed valve
  • 1 × IRLZ44N MOSFET
  • 1 × 220Ω resistor
  • 1 × 10kΩ resistor
  • 1 × 1N4007 diode
  • 12V power supply for the valve
  • jumper wires / perfboard

For this step, still do not connect mains power. This step uses only the low-voltage DC side.

What each part does

  • Valve: opens or closes airflow
  • MOSFET: lets the ESP32 control a 12V load
  • 220Ω resistor: protects and stabilizes the gate drive
  • 10kΩ resistor: keeps the MOSFET off when the ESP32 pin is floating
  • 1N4007 diode: suppresses the voltage spike from the valve coil when it turns off

Wire the MOSFET and valve

ESP32 GPIO18 -> 220Ω resistor -> MOSFET Gate
MOSFET Gate -> 10kΩ resistor -> GND

Valve + -> +12V
Valve - -> MOSFET Drain
MOSFET Source -> GND

Flyback diode across valve:
Diode stripe (cathode) -> +12V
Other side (anode) -> Valve - / MOSFET Drain

Make sure grounds are shared

The ESP32 ground and the 12V supply ground must be connected together. If they are not shared, the ESP32 signal will not control the MOSFET correctly.

Upload the valve test code

const int valvePin = 18;

void setup() {
  pinMode(valvePin, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  Serial.println("Valve ON");
  digitalWrite(valvePin, HIGH);
  delay(3000);

  Serial.println("Valve OFF");
  digitalWrite(valvePin, LOW);
  delay(3000);
}

Test the valve

  1. Apply 12V power
  2. Open Serial Monitor
  3. Listen for the valve clicking every 3 seconds
  4. Feel for airflow if tubing is attached
What success looks like: the serial monitor alternates between “Valve ON” and “Valve OFF”, the valve clicks in sync with the messages, and airflow starts and stops in sync with the messages.

Common mistakes

  • diode reversed
  • MOSFET source and drain swapped
  • no shared ground
  • missing 10k pulldown, causing the valve to stay on or behave randomly

Step 3 — Wire and test the relay and pump

Goal: Use the ESP32 to switch the AC pump on and off through a relay.

What you need for this step

  • ESP32
  • relay module or relay + driver stage
  • pump
  • fused AC input
  • insulated terminals
  • power supply
  • enclosure or safe temporary test arrangement
This step uses mains voltage.
Before doing anything:
  • unplug AC power
  • use insulated connectors
  • do not leave exposed AC terminals where they can be touched
  • if you are not confident with mains wiring, have someone experienced inspect your work before powering it

Wire the relay control side

Relay Control PinConnect To
INGPIO23
VCCappropriate relay control voltage
GNDGND

If using a bare relay instead of a module, use a transistor or MOSFET driver and a flyback diode on the coil.

Wire the AC side

AC LIVE -> Fuse -> Relay COM
Relay NO -> Pump LIVE

AC NEUTRAL -> Pump NEUTRAL
AC GROUND -> Pump chassis

If the same AC input also powers the Mean Well PSU, wire it in parallel after the fused input:

AC LIVE -> Fuse -> Split
                 -> Relay COM -> Relay NO -> Pump LIVE
                 -> PSU L

AC NEUTRAL -> Split
              -> Pump NEUTRAL
              -> PSU N

AC GROUND -> Split
             -> Pump chassis
             -> PSU ground

Upload the relay test code

const int relayPin = 23;

void setup() {
  pinMode(relayPin, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  Serial.println("Pump ON");
  digitalWrite(relayPin, HIGH);
  delay(5000);

  Serial.println("Pump OFF");
  digitalWrite(relayPin, LOW);
  delay(5000);
}

Test carefully

  1. Power the low-voltage side first
  2. Verify the relay clicks
  3. Then power the AC side
  4. Confirm the pump turns on and off with the relay
What success looks like: relay click matches Serial Monitor messages, and the pump turns on and off in sync with those messages.

Step 4 — Build single-zone closed-loop pressure control

Goal: Make one zone hold a target pressure automatically.

What you need for this step

  • 1 pressure sensor
  • 1 fill valve
  • 1 vent valve
  • 1 relay-controlled pump
  • tubing connected to a test air chamber or mattress zone

Step 4A — Define the first variables

You need:

  • pressure
  • target
  • deadband
  • state
float pressure = 0.0;
float target = 500.0;
float deadband = 20.0;

Step 4B — Read and filter the pressure

float filteredPressure = 0.0;

float readPressure() {
  int raw = analogRead(34);
  filteredPressure = 0.9 * filteredPressure + 0.1 * raw;
  return filteredPressure;
}

This reduces sensor noise and small fluctuations.

Step 4C — Add fill and vent control functions

const int fillValvePin = 18;
const int ventValvePin = 19;
const int relayPin = 23;

void startFill() {
  digitalWrite(relayPin, HIGH);
  digitalWrite(fillValvePin, HIGH);
  digitalWrite(ventValvePin, LOW);
}

void startVent() {
  digitalWrite(relayPin, LOW);
  digitalWrite(fillValvePin, LOW);
  digitalWrite(ventValvePin, HIGH);
}

void stopAll() {
  digitalWrite(relayPin, LOW);
  digitalWrite(fillValvePin, LOW);
  digitalWrite(ventValvePin, LOW);
}

Step 4D — Add basic control logic

void loop() {
  float pressure = readPressure();
  Serial.println(pressure);

  if (pressure < target - deadband) {
    startFill();
  }
  else if (pressure > target + deadband) {
    startVent();
  }
  else {
    stopAll();
  }

  delay(100);
}

Deadband creates a no-action zone around the target pressure so the system does not constantly toggle valves when it is already close enough.

Step 4E — Test the behavior

  1. Run the code
  2. Watch pressure in Serial Monitor
  3. Confirm:
    • below target → fill starts
    • above target → vent starts
    • within deadband → everything stops
What success looks like: the system changes pressure in the correct direction, valves do not chatter constantly, and pressure moves toward the target and stops near it.

Step 5 — Add predictive control and tune it

Goal: Improve control accuracy so the system does not overshoot or undershoot badly.

Why this is needed

When a valve closes, airflow does not stop instantly. Pressure may keep changing briefly, so stopping exactly at the target can still overshoot.

Step 5A — Track rate of change

float previousPressure = 0.0;
unsigned long previousTime = 0;

float computeDpDt(float pressure) {
  unsigned long now = millis();
  float dt = (now - previousTime) / 1000.0;
  float dpdt = 0.0;

  if (dt > 0) {
    dpdt = (pressure - previousPressure) / dt;
  }

  previousPressure = pressure;
  previousTime = now;
  return dpdt;
}

Step 5B — Use the trend to stop a little early

Start simple:

  • if filling fast, stop slightly before the target
  • if venting fast, stop slightly before the target in the opposite direction

This can begin as a fixed margin before becoming adaptive.

Step 5C — Add auto-tuning concept

Over repeated fills and vents, record:

  • pressure at valve close
  • final settled pressure
  • overshoot or undershoot amount

Then slowly adjust the stopping margin based on real behavior.

What to tune

  • deadband
  • sample interval
  • stop margin
  • settle time
  • transient thresholds
What success looks like: smaller overshoot, fewer corrective cycles, smoother behavior, and less noise and valve activity.

Step 6 — Expand to dual-zone operation

Goal: Make the second side work independently while sharing the same pump.

What you add

  • second pressure sensor
  • second fill valve
  • second vent valve
  • second tee node
  • second zone logic

Example GPIO expansion

FunctionGPIO
Left Fill18
Left Vent19
Right Fill21
Right Vent22
Relay23
Sensor Left34
Sensor Right35

How to proceed

  1. Keep the left-zone code working
  2. Duplicate the sensor read path for the right zone
  3. Duplicate the fill/vent logic for the right zone
  4. Keep targets, deadbands, and state variables separate per zone
  5. Coordinate the shared pump so only the required fill action is active

Important rule: Do not share pressure readings between zones. Each zone must keep its own independent control state.

DIY Air Mattress Controller V1 • Open Source Project