WIP project to build an open source cooling mattress. The goal of openbed is to provide a resource that allows you to affordably get a perfectly optimized sleep. To achieve this goal, all components should be sourced from common items without overuse of speciality vendors and custom parts. If a custom part is used, it should be simple to install and order a replacement from the design files. Soldering should be minimal and all parts should be simple to swap. Included software should be entirely open source and work without a network connection.
Starting ESP32 environment in platform.io navigate to project folder. pio run -t clean Remove-Item -Recurse -Force .pio pio run pio run -t compiledb pio run -t upload
for comm port: pio device monitor
Start to connecting... then hold the boot button. Then click EN once and let go of both buttons. It will appear to freeze for a moment. Then it will say: Writing at 0x000db0b4... (91 %) Writing at 0x000e0351... (94 %) Writing at 0x000e5dcf... (97 %) Writing at 0x000eb3b0... (100 %) Wrote 916464 bytes (586341 compressed) at 0x00010000 in 9.7 seconds (effective 752.6 kbit/s)... Hash of data verified.
Leaving... Hard resetting via RTS pin...
The firmware in src/main.cpp targets an esp32dev board. All grounds
must be common: tie the ESP32 GND, the 12 V PSU GND, and every sensor/module GND together.
| Module | Wire / signal | ESP32 GPIO | Notes |
|---|---|---|---|
| TEC 1 (Cytron MD13S) | PWM / DIR | 25 / 26 | |
| TEC 2 (Cytron MD13S) | PWM / DIR | 27 / 14 | |
| TEC 3 (Cytron MD13S) | PWM / DIR | 32 / 33 | |
| TEC 4 (Cytron MD13S) | PWM / DIR | 16 / 17 | |
| Heatsink temp ×2 (DS18B20) | DATA | 13 | both share the bus; one 4.7 kΩ pull-up DATA→3.3 V; VCC→3.3 V |
| Coolant probe (2-wire 10 kΩ NTC, on pump block) | divider node | 34 (ADC) | needs one 10 kΩ fixed resistor — see diagram |
| D5 pump | Red / Black | — | +12 V / GND (12 V PSU) |
| D5 pump | Blue (PWM) | 19 | 25 kHz control |
| D5 pump | Green (tach) | 22 | speed feedback (internal pull-up) |
| Water level (XKC-Y26, 5 V) | Brown / Blue | — | VCC 5 V / GND |
| Water level (XKC-Y26, 5 V) | Yellow (OUT) | 21 | 5 V output → needs divider — see diagram |
| Water level (XKC-Y26, 5 V) | Black (MODE) | — | leave floating = normally-open (HIGH when water present) |
| TFT ST7789 | SCK / MOSI(SDA) / DC | 18 / 23 / 4 | |
| TFT ST7789 | BL / BLK | 5 | backlight control (display off for dark room); avoid GPIO15 — strapping pin |
| TFT ST7789 | CS→GND, RST→3.3 V, VCC→3.3 V | — |
Software water-level override: ships ON by default so the rig can run before the 5 V sensor is wired. It bypasses the dry-run interlock — turn it OFF in the web menu once the sensor is connected. Don't leave the pump powered without coolant while override is ON.
A bare NTC only changes resistance, so it's paired with one fixed 10 kΩ resistor to make a voltage divider the ESP32 can read. Power it from 3.3 V (not 5 V):
3.3V ──[ 10kΩ NTC probe ]──┬──[ 10kΩ fixed resistor ]── GND
│
GPIO34 (ADC)
NTC leads are not polarised. If readings are inverted, the NTC and fixed resistor are
swapped. Constants (NTC_R_FIXED, NTC_R0, NTC_BETA) live near the top of main.cpp.
The OUT line swings to 5 V, which exceeds the ESP32's 3.3 V limit, so divide it down:
Brown ── 5V
Blue ── GND
Black ── (leave floating = normally open)
Yellow (OUT) ──[ 10kΩ ]──┬──[ 20kΩ ]── GND (≈3.3 V at the node)
│
GPIO21
If your unit reads inverted, short Black→Blue or flip WATER_LEVEL_ACTIVE_HIGH in the code.
water level OK (or override) ──┐
├─► pump PWM enabled + TECs allowed to run
system ON / no fault ──────────┘
otherwise → pump OFF and TECs OFF (prevents running the D5 dry)
- First boot / no saved WiFi: the device starts an open access point
TEC-CTRL-SETUPathttp://192.168.4.1. Join it and enter your 2.4 GHz WiFi credentials, then it reboots. - After setup: browse to
http://tec-ctrl.local/(or the IP shown on the TFT / serial log). Your device and the ESP32 must be on the same network. TheTEC-CTRL-SETUPhotspot only reappears if it has no creds or can't connect.
The controller is the API server — the built-in page is just a thin client. All endpoints
return the full state object as JSON, so anyone can build their own web or mobile client.
CORS is enabled (Access-Control-Allow-Origin: *), so a separately hosted app can call
these directly. Safety logic (dry-run interlock, heatsink over-temp, PID) always runs on the
device — external apps are clients only and are never in the control loop.
| Method | Path | Body (JSON) | Purpose |
|---|---|---|---|
| GET | /api/state |
— | Full live state (see fields below) |
| POST | /api/control |
any of on,mode,tconst,maxp,pumpen,pumpspd,dispon,wlovr |
Power, setpoints, pump, display, water-level override |
| POST | /api/invert |
{"inv":[bool,bool,bool,bool]} |
Per-TEC direction invert |
| POST | /api/profile |
{"hour":0-23,"temp":C} |
Set one hourly profile point |
| POST | /api/test |
{"duty":[0-1 ×4],"heat":[bool ×4],"dur":ms} (or single-channel {"chan":0-3,"heat":bool,"duty":0-1,"dur":ms}) |
Manual TEC test (needs water/override). Per-channel form runs any mix of TECs at once, each at its own duty (0 = off) and direction, for the shared duration. Every duty is clamped to maxp. |
| POST | /api/test_stop |
— | Stop manual test |
| POST | /api/fault_clear |
— | Clear a latched fault (only while OFF) |
Key /api/state fields: on, manual, mode (false=constant/true=profile), water (coolant
°C or null), level (sensor bool), level_ovr (override bool), target, tconst, maxp,
pumpen, pumpspd, pumprpm, dispon, fault, faultMsg, ip, ssid, hs_count,
hs[] (heatsink °C, null if absent), duty[], inv[].
Examples:
curl http://tec-ctrl.local/api/state
curl -X POST http://tec-ctrl.local/api/control -H "Content-Type: application/json" \
-d '{"on":true,"tconst":18.5}'
curl -X POST http://tec-ctrl.local/api/control -H "Content-Type: application/json" \
-d '{"pumpen":true,"pumpspd":40,"wlovr":false}'OpenBed/OpenCooler/Pod (haven't decided on a final name) will be hot swappable with several potential accessories:
- A pet cooler pad in large (dog) and small (cat/other) sizes to give your animal the best sleep possible
- A pillow cover for a standard sized pillow
- A single mattress cover, then mattress covers of other sizes.
- Chiller connect for bioreactors
- Medical hospital mat version for post surgery and long term care
- Wearable option for sports/vr training and recovery
- Test feasibility (concept works, simple to implement)
- Create a product design mockup
- Source components
- Build the thermal engine assembly for the TEC watercooling block
- Design and build the case for all components that is also protected against water spillage
- Design and implement an easily refillable 3D printed(?) water reservoir
- Build mounting box for TEC controllers
- Build mount for Arduino controller module
- Optimize quick connect system for modular accessories
- 5/100 Create a prototype cooling pillow
Proof of concept and testing components. The cooler prototype is meant to heat and cool a single mattress/pillow/blanket. It is not a split design for two people. Future iterations could do both, but for now I am focusing on raw cooling power and simplicity of design. No valves to exchange water flow, no separate reservoirs for hot and cool water.
Initial BOM was created after a teardown published on reddit from my reddit post:
| Component & key notes | Est. cost (USD) |
|---|---|
| 4× Peltier TEC 127106FX (water-sealed TEC12706 variant; slightly higher efficiency) • 2 Peltier coolers per bed side → TEC12706 draws 12 V × 6 A peak → per person: 12 V, ~12 A required • Two TEC12703 units acquired for testing (lower-power 3 A option) |
$20 |
| Generic 12 V water pump (run at low power for reduced noise; one per side) | $16 |
| No-contact water level meter (mounted outside reservoir) | $5 |
| Thermometer on heat sink (detect overheating; shut down on fault) | $3 |
| Meanwell power supply (≈300 W?) | $50 |
| Thermometer in water reservoir (confirm temp; adjust for drift) | $3 |
| Solenoid valve (likely for drainage or temp control) • Reviews mention Pod 2 clicking noise—probably this valve; consider replacing |
$5 |
| Custom mounting brackets for TECs (cold side to waterblock; hot side to radiator unless polarity is reversed) | $15 |
| 0.14 mm “no-mess” thermal pads for TEC mounts (instead of paste) | $3 |
| Black TPV tubing, secured with zip ties | $15 |
| Reservoir float valve (separate from magnetic water-level sensor? max-fill vs min-fill) | $5 |
| Waterblock housed in styrofoam insulation (isolate cold plates from hot radiator) | $3 |
| Custom ARM control board (cheap chipset) with polarity/current control for TECs; inputs for other modules (Pi/Arduino would also work) | $80 |
| Case: injection-molded plastic (unit cost after one-off mold) | $20 |
| Custom quick-connect fittings | ~$10 |
| Radiator (not custom; all parts except the ARM board available on Alibaba/AliExpress) • Bulk: $15 • Single sample: $80 |
$15–$80 |
| Total estimated pod cost (incl. shipping, taxes, extra BOM) | ~$300 |
The first cooler test consisted of two TEC12703 peltier coolers. They were mounted to 40x80mm watercooling block, which was then mounted to generic CPU air cooler. CPU thermal cooling paste was applied between the surface of the watercooling block and the CPU cooler copper contact plate.
- The thermometer needs to be inserted into the water tank. Idea presented: ip67 wire grommet for simple insertion and removal.
- Caps are difficult to fill, and it can be tough to see how much water is needed before it overflows. Solution: A jerry can fill/cap system. Use clear acrylic with acrylic cement for the water reservoir.