thermo

Open source · Linux · Rust

Make every Xiaomi thermometer in the house agree.

thermo is a terminal dashboard for LYWSD03MMC and other thermometers running the pvvx ATC_MiThermometer firmware. It charts their live BTHome v2 readings and fills gaps from each device's own log. Put them side by side, press c, and it calibrates them all at once, writing the corrections into the thermometers. Home Assistant sees the fixed values too.

Three thermometers showing 21.4, 21.9 and 21.1 °C send Bluetooth adverts to a terminal chart where their lines merge after calibration 21.4 °C 58% 21.9 °C 63% 21.1 °C 55% thermo calibrated ● 3 devices after: one line

What it looks like

Real screenshots from eight LYWSD03MMC thermometers. They sat side by side indoors overnight, went to a cooler room in the morning, and were calibrated. Afterwards some of them moved to other rooms. In Ghostty or kitty the chart is a crisp image; other terminals and SSH get braille.

thermo terminal dashboard showing a temperature chart of eight Xiaomi LYWSD03MMC thermometers over 24 hours above a table of live readings
Temperature, last 24 h: the side-by-side group is calibrated, then three devices go to other rooms. The table shows battery, latest and 5-minute median signal strength, and each device's clock offset.
0.078 → 0.012 °C

Disagreement in temperature (rms against the group) over the fitted window, before and after calibrating 8 devices.

1.76 → 0.40 %RH

The same for humidity. Out of the box, readings spread over 4–5 %RH.

~200 ppm

Clock drift measured on the thermometers. s syncs them and trims each clock's rate.

Everything for a houseful of cheap BLE thermometers

Every action works on one device or on all marked devices at once, so eight thermometers are as easy as one.

Live dashboard

One line per thermometer, a chart above a table: temperature, humidity, battery, voltage and signal strength, updated as BTHome v2 adverts arrive. History is saved as plain CSV.

Consensus calibration

No reference instrument needed. A robust fit pulls every device onto the group average, rejects outliers, and writes a gain and offset to each thermometer's flash.

Log download, honest timestamps

Missed the last 24 hours? thermo downloads each device's own log and corrects for its wrong clock. Device clocks are usually off, and nothing assumes they aren't.

Clock sync and drift trim

Sets every clock to the computer's time. After a day it measures how fast each one runs and trims the oscillator, which here ran about 20 s per day fast.

Rename and settings, in bulk

Rename devices (stored in flash, broadcast to Home Assistant), and change advertising interval, measurement interval, TX power and log interval on all of them in one go.

Which one is which?

i shows the device's row number on its own LCD for ten minutes, 333 with a smiley for #3, so you can label them.

Calibrating takes an afternoon and one keypress

  1. Side by side, two hours.

    Put all thermometers a few centimetres apart, away from sun, radiators and laptop exhaust. That is enough for offsets.

  2. Somewhere 5 °C different, three hours.

    Move them all at once: outside in the shade, a cold room, a garage. The second temperature makes the gain measurable.

  3. Press c.

    Pick the window on the chart with Tab and ← →, check the bars, press Enter. Each device gets its own correction in flash. x brings back the factory values.

Read the full calibration guide → It covers what range to calibrate in, humidity, salt tests, and how the fit works.

Temperature over time: a steady indoor phase, a move, a settling hour that is ignored, and a steady cooler phase; both steady phases feed the fit 22° 16° settling (ignored) indoors ≥ 2 h cooler ≥ 3 h both steady phases → one gain + offset per device

Install

You need Linux with BlueZ (any desktop distro, or a Raspberry Pi), Rust 1.85 or newer, and the D-Bus development headers.

# Debian / Ubuntu / Raspberry Pi OS
sudo apt install libdbus-1-dev pkg-config
# Fedora
sudo dnf install dbus-devel pkgconf-pkg-config
# Arch
sudo pacman -S --needed dbus pkgconf

cargo install --git https://github.com/arturzielazny/atc-thermo thermo
thermo

Readings are saved to ~/.local/share/thermo/ as CSV, one file per day, and reloaded on start. thermo calibrate 6 prints a calibration proposal for the last six hours without touching any device.

First, flash the thermometers

thermo talks to the open pvvx ATC_MiThermometer firmware. Stock Xiaomi firmware encrypts its adverts and has no calibration interface. Flashing is wireless, from a browser, with the TelinkMiFlasher page:

  1. Chrome on Linux keeps Web Bluetooth behind a flag. Enable chrome://flags/#enable-experimental-web-platform-features and restart, or use Chrome on Android.
  2. Connect, activate, and flash the pvvx firmware for your model (version 5.9 or newer was used here).
  3. Set the advertising type to BTHome v2. Each thermometer then shows up in thermo, Home Assistant and ESPHome.

Keys

↑ ↓select a device · Esc clears marks, then the selection
space amark one / all devices: actions apply to marked devices, or the selected one
ccalibration screen
ssync clocks and trim their drift
ddownload the device log for the last 24 h
n erename · settings (advertising, measurement and log interval, TX power)
iidentify: show the device's number on its screen
t h r w ptemperature / humidity / signal chart · time span · lines or points
qquit
The thermo code base, shown as a terminal, branching through an AI agent to macOS, Windows, a Raspberry Pi exporter and a web dashboard $ claude > port thermo to macOS… macOS · CoreBluetooth Windows · WinRT Pi → MQTT / Influx web dashboard

Not quite what you need? Have an agent rewrite it.

thermo is written to be changed by AI coding agents such as Claude Code, Codex or Cursor. The repository has an AGENTS.md that explains the architecture and what is platform-specific. The BTHome decoder and the calibration maths are dependency-free and fully unit-tested, so an agent can check its own work without hardware.

Port it to macOS or Windows, turn it into a headless MQTT exporter on a Raspberry Pi, or add your own sensor model.

Guide and ready-made prompts →

Questions

Can I calibrate LYWSD03MMC thermometers without a reference thermometer?

Yes, relative to each other: thermo makes all devices agree with their group average, which is usually closer to the truth than any single sensor. Agreement isn't absolute accuracy, though. For absolute humidity, a sealed box over saturated table salt holds 75 %RH and makes a cheap reference. The guide explains the details.

Will Home Assistant see the calibrated values?

Yes. The gain and offset are written into each thermometer, so every receiver of its BTHome adverts gets corrected values: Home Assistant's BTHome integration, ESPHome Bluetooth proxies and anything else that decodes BTHome. thermo only listens, so it runs fine next to them. Names set with n appear in Home Assistant when it scans actively.

Which thermometers work?

Tested with eight Xiaomi LYWSD03MMC (Mi Temperature and Humidity Monitor 2) on pvvx firmware 5.9, including both sensor variants (SHT4x-type and SHT3x-type coefficients). Other models running the pvvx firmware with BTHome v2, such as MHO-C401 or CGG1, should show up and be calibratable but are untested. Reports are welcome.

macOS, Windows, Raspberry Pi?

Any Linux with BlueZ works, a Raspberry Pi included. macOS and Windows need the Bluetooth layer replaced. See porting with an AI agent.

Can I undo a calibration?

Yes. x on the calibration screen restores the factory coefficients, and every change is logged with old and new values in calibrations.csv. Corrections compose, so you can calibrate again later on top of the last one.

Does it drain the batteries?

Listening is passive and costs the thermometers nothing. thermo only connects to a device when you ask for something: log download, clock sync, settings, rename, identify or calibration.