This is a replacement firmware for Eight Sleep pods. It's local, cloud-less and communicates directly with the serial devices.
Home Assistant first approach. It supports MQTT discovery so no need for manual configuration.
This is an AI-fork of opensleep.
This project is for personal, educational, and research use. It is not affiliated with, endorsed by, or sponsored by Eight Sleep. βEight Sleepβ and related names are trademarks of Eight Sleep, Inc.
Using custom firmware or low-level hardware control may void warranties, break the vendor app, or in rare cases damage hardware. Use at your own risk!
- β Pod 1
- β Pod 2
β οΈ Pod 3 (should work, untested)- β Pod 4
β οΈ Pod 5 (should work, untested)
- β Full Home Assistant integration
- β Single all-in-one replacement
- β Cover buttons
- β Vibration
- β Temperature control
- β LED control
- β Self update
- β Presence detection
- β Biometrics (work in progress)
How Lunaris differs from other local-control projects (free-sleep, opensleep, ninesleep):
| Topic | Lunaris | free-sleep | opensleep | ninesleep |
|---|---|---|---|---|
| Goal | MQTT + Home Assistant for Pod hardware over USART | Jailbreak Pod + LAN web app and REST API | Replace vendor SOM apps; full Rust firmware on Pod | Replace dac only; HTTP API to frankenfirmware |
| Stack | Rust | Node/Express + React (+ optional Python biometrics) | Rust | Rust |
| License | GPL-3.0 | MIT | GPL-3.0 | MIT |
| Automation / HA | Native MQTT discovery, no extra config | REST / custom integration | MQTT + RON config | REST / HTTP |
| Built-in web UI | No (HA is the UI) | Yes | No | No |
| Configuration | CLI flags (ports, broker, pod model) | App + JSON storage | .ron files |
Driven by HTTP clients |
| Pod generations | Pod 4 focus; Pod 3/5 best-effort | Pod 3β5 documented | Pod 3 tested; Pod 4/5 untested | Pod 3 documented |
| Name | Kind | Hidden by default | What it does |
|---|---|---|---|
| Cover Left / Right | climate | No | Set how warm or cool each side of the bed should feel. |
| LED | light | No | Colour and brightness for the indicator LED on the bed. |
| Prime | button | No | Tell the pod to prepare / prime its water system before use. |
| Vibrate Cover Left / Right | button | No | Run a vibration on that side. |
| Name | Kind | Hidden by default | What it does |
|---|---|---|---|
| Ambient Humidity | sensor | No | Relative ambien humidity send by the cover sensor. |
| Ambient Temperature | sensor | No | Ambient temperature send by the cover sensor. |
| Cover Button Left / Right | sensor | No | Tap count 1-5, 0 when idle. |
| Current Cover Temperature Left / Right | sensor | No | How warm each side of the mattress is right now. |
| Heatsink Temperature | sensor | No | Temperature of the cooling hardware. |
| Presence Any | binary_sensor | No | Someone is detected on either side of the bed. |
| Presence Calibration | binary_sensor | No | On while lunaris is learning an βempty bedβ baseline. |
| Presence Left / Right | binary_sensor | No | Someone is detected on that side of the mattress. |
| Target Temperature Left / Right | sensor | No | Same target you set on the climate control, shown as plain numbers for dashboards or cards. |
| Water Tank | binary_sensor | No | Shows whether the water tank is plugged in. |
| Name | Kind | Hidden by default | What it does |
|---|---|---|---|
| Firmware | update | No | Updates Lunaris itself by pulling the latest version from GitHub releases. |
| Calibrate Presence | button | No | Start learning what an empty mattress looks like (keep the bed clear for ~10s afterwards). |
| Presence Baseline Delta | number | Yes | Fine-tune sensitivity after calibration. |
| Presence Cap Threshold | number | Yes | Fine-tune rough βcapacitanceβ sensitivity before calibration. |
| LED Behavior | select | No | Chooses LED startup/runtime behavior: - Manual (off at boot, only light entity controls it)- Status (solid green while lunaris runs)- Startup (green for 5s on boot, then off and manual control). |
| Vibration Cancel Preamble | switch | Yes | Tweaks vibration behaviour β leave as-is unless troubleshooting. |
| Vibration Duration | number | No | How long vibrations last when you press a vibrate button. |
| Vibration Intensity | number | No | How strong vibrations feel (percentage). |
| Vibration Pattern | select | No | Simple vs double vibration pattern. |
| Name | Kind | Hidden by default | What it does |
|---|---|---|---|
| Device ID | sensor | Yes | Short internal id taken from pod files when available; mostly for support. |
| Device Label | sensor | Yes | Human-readable pod name from disk when available; mostly for support. |
| Frozen Message | sensor | Yes | Text, send by the Frozen MCU. |
| Sensor Message | sensor | Yes | Text, send by the Sensor MCU. |
| Presence Baseline Zones | sensor | Yes | Raw saved calibration snapshot for nerds/support; not usually needed daily. |
| System Uptime | sensor | No | Shows the local date/time since when the pod OS has been running (last reboot timestamp). |
| Frozen Link | binary_sensor | No | ON when valid Frozen MCU traffic was decoded. |
| Sensor Link | binary_sensor | No | ON when valid Sensor MCU traffic was decoded. |
| Internet Access | binary_sensor | No | ON when the configured URL (default: lunaris GitHub project page) is reachable (checked every 60s). Change the URL with --internet-access-url. |
| Reboot Pod | button | No | Restarts the pod. |
| Restart Lunaris | button | No | Restarts the lunaris process. |
| Shutdown Pod | button | No | Powers off the pod. |
| Request Temperatures | button | No | Ask the pod to refresh its temperature readings (troubleshooting / curiosity). |
There is an excellent guide from free-sleep. We only need steps 1-12.
Because free-sleep uses a different approach to communicate to the pod, we have to disable an additional service:
systemctl disable --now frank
systemctl mask frankAlso make sure you blocked the Eight Sleep servers in your /etc/hosts:
127.0.0.1 raw-api-upload.8slp.net update-api.8slp.net device-api.8slp.net 8slp.net
Connect to your pod and run these commands in a root shell:
# Download lunaris
wget https://github.com/Schluggi/lunaris/releases/latest/download/lunaris -O /usr/bin/lunaris
# Make the binary executable
chmod +x /usr/bin/lunaris
# Download unit file
wget https://github.com/Schluggi/lunaris/raw/refs/heads/main/systemd/lunaris.service -O /etc/systemd/system/lunaris.service
# Edit ExecStart β at minimum set broker + pod (see README [Quick start](#cli-quick-start), full tables [below](#cli-arguments))
vi /etc/systemd/system/lunaris.service
# Activate service
systemctl daemon-reload
systemctl enable --now lunarisThere is no periodic priming built-in. You have to manage it yourself.
Lunaris provides a blueprint for this.
All settings are CLI-only (there is no config file or ENVs). lunaris --help lists the same flags.
lunaris \
--mqtt-host 192.168.1.40 \
--mqtt-username user123 \
--mqtt-password password123 \
--pod 4 # 3/4/5| Option | Default | Required | Description |
|---|---|---|---|
--pod |
β | Yes | Your Pod generation: 3, 4, or 5. Chooses sensible default serial speeds. |
--self-update-poll-secs |
43200 |
No | How often lunaris checks GitHub for a newer release (12h by default). Set to 0 to disable self-update polling and hide the Firmware update entity. |
--internet-access-url |
https://github.com/Schluggi/lunaris |
No | URL the Internet Access sensor probes (every 60s). |
| Option | Default | Required | Description |
|---|---|---|---|
--device-identifier |
lunaris_pod |
No | Stable id so Home Assistant recognizes the same device across restarts. |
--device-name |
Eight Sleep |
No | Name shown for the device in Home Assistant. |
--discovery-prefix |
homeassistant |
No | Where Home Assistant expects MQTT discovery messages (change only if your setup uses a different prefix). |
--mqtt-client-id |
lunaris |
No | Name of this program on the MQTT broker. |
--mqtt-host |
β | Yes | Your MQTT broker (hostname or IP). |
--mqtt-password |
β | No | Broker password, if required. |
--mqtt-port |
1883 |
No | Broker port. |
--mqtt-username |
β | No | Broker username, if required. |
--payload-press |
PRESS |
No | Text Home Assistant sends when you βpressβ an MQTT button; must match what discovery advertises. |
--topic-prefix |
lunaris/pod4 |
No | Leading part of all topics for this bed (availability, controls, sensors, etc.). |
| Option | Default | Required | Description |
|---|---|---|---|
--climate-max-temp |
47 |
No | Highest setpoint (Β°C) allowed in the climate controls. |
--climate-min-temp |
13 |
No | Lowest setpoint (Β°C) allowed. |
--climate-temp-step |
0.5 |
No | Step size (Β°C) for the temperature slider. |
| Option | Default | Required | Description |
|---|---|---|---|
--i2c-device |
/dev/i2c-1 |
No | IΒ²C bus used for the status LED. If it is missing, everything else still works except the light in Home Assistant. |
--log-level |
info |
No | How chatty the logs are when RUST_LOG is not set. |
--sensor-baud |
(from --pod; Pod 4 / 5 β 921600) |
No | Speed for the sensor/vibration serial line; change if your hardware needs it. |
--sensor-device |
/dev/ttyS2 |
No | Serial port for vibration and presence. If it cannot be opened, heating and MQTT still work, but not vibration or presence. |
--sensor-vibrate-no-ack-wait |
(off) | No | Try this if vibration never starts but you suspect the sensor port does not report replies. |
--serial-baud |
(from --pod; Pod 4 / 5 β 38400) |
No | Speed for the main (temperature) serial line. |
--serial-device |
/dev/ttyS1 |
No | Serial port for temperature and related control. Required: the program exits if this port cannot be opened. |
- Host: Ubuntu 24.04
- Rust: Same version as rust-toolchain.toml
# Download & install zig (https://ziglang.org/download/)
wget https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz -O /opt/zig-x86_64-linux-0.16.0.tar.xz
cd /opt/
tar xfv zig-x86_64-linux-0.16.0.tar.xz
export PATH=${PATH}:/opt/zig-x86_64-linux-0.16.0/
cd -
# Setup Zigbuild
rustup target add aarch64-unknown-linux-musl
cargo install cargo-zigbuild
# Build
cargo zigbuild --release --target aarch64-unknown-linux-muslOutput: ./target/aarch64-unknown-linux-musl/release/lunaris.
When lunaris starts and the MQTT topic does not exist yet (this is normally the first start ever), it will automatically calibrate the presence sensors.
You can also calibrate manually via the Calibrate Presence button entity.
For fine-tuning, use Presence Baseline Delta and Presence Cap Threshold on the device.
Hint: Increase the
Presence Baseline Deltaby 150 after pressingCalibrate Presence. This fixes it for me.
The Water Tank binary sensor often shows unknown briefly at startup until the bridge has parsed reservoir state. You can usually refresh it by pulling the tank out and plugging it back in.
- LiamSnow for the great research and groundwork on the protocol (opensleep).
- throwaway31265 for free-sleep and the instructions on how to root.
- OsirisSpectrum for the naming idea of this project.
SPDX: GPL-3.0-only. See LICENSE. CRC/framing for Frozen frames derives from opensleep (same license).




