Cheap Bits, Solid Data: Building a Whole‑House Climate Monitor
share forum

Cheap Bits, Solid Data: Building a Whole‑House Climate Monitor


Technology • von Sven Reifschneider • 14. Mai 2025 • 0 Kommentare
info
Dieser Beitrag ist auch auf Deutsch verfügbar. Auf Deutsch lesen

Why I Wanted My Own Climate Feed

I like numbers more than guesses. More than five winters ago I put a pair of TFA Dostmann temperature‑humidity sensors—one in the living room, one in the basement—just to see how quickly the house cooled overnight. The €20 plastic pucks had LCDs, but walking around recording values with a notebook got old fast. I needed the data in one place, continuously, and preferably on a dashboard.

The obvious solution—Wi‑Fi IoT gadgets—felt wrong: higher cost, batteries that last months not years, and proprietary clouds. Then I found baycom/tfrec, a tiny open‑source program by Georg Acher & Deti Fliegl that decodes the 868 MHz packets those sensors already broadcast. All it needs is the same RTL2832U DVB‑T dongle many of us have in a parts drawer. This tool decodes data sent by the KlimaLogg Pro and similar temperature sensors made by TFA Dostmann or other Technoline/LaCrosse-compatible sensors. Have a look at their sensors.txt, a wide range is supported, including weather sensors like wind or rain.

That was the eureka moment: Cheap hardware I already trust, software I can compile, and battery‑friendly sensors I can buy in any electronics store.

Bill of Materials

Item Typical price Notes
RTL2832U DVB‑T USB stick (with R820T tuner) €20–30 Acts as a general‑purpose Software Defined Radio (SDR) receiver.
868 MHz temp/humidity sensor (TFA Dostmann / Technoline / La Crosse) €15–20 LCD read‑out, 2x AA batteries that last 4-5 years. Models for pools, wind, rain available.
Linux host (Raspberry Pi, old laptop, VM) from €15 (Pi Zero 2 W) Any system that can run rtl_sdr.
Optional: better 868 MHz antenna €5–10 The stock whip works across most houses; a bigger external antenna buys extra metres.

I started with two sensors. When that worked, I added some more units over the years to cover most rooms in my house—twelve total today. The RF range comfortably covers my two‑floor + basement brick house with the stock DVB‑T antenna on a shelf. A great addition to my weather station.

Since a wide range of sensors is supported, you'll always find a suiting one for your needs.

Installing tfrec

sudo apt install git build-essential rtl-sdr   # Debian/Ubuntu; Arch users know the drill
git clone https://github.com/baycom/tfrec
cd tfrec
make
sudo cp tfrec /usr/bin/   # or anywhere in $PATH

What tfrec does:

  • Listens to raw I/Q samples from rtl_sdr.
  • Detects the on‑off‑keying pattern used by KlimaLogg Pro‑compatible sensors.
  • Prints each packet’s sensor ID, temperature °C, humidity %, RSSI, battery flag, and timestamp.
  • Optionally executes a command, passing those values as arguments.

No kernel modules, no black magic—just libusb.

Running It Manually (First Sanity Check)

Run it with -d for debug mode:

sudo /opt/bin/tfrec -d

You should see lines like:

16b4  +23.8  41  27  0  -60
92af  +22.1  45  18  0  -63

ID + Temp + Humidity + Sequence + BatteryLow + SignalStrength(dB).

If nothing appears, move the antenna away from USB noise or check the sensor battery / error output.

Automating with systemd

I want:

  • 60 s “listening windows” every 30 min
  • automatic restart if rtl_sdr hangs (rare but happened a few times over the years, timeout is 180 s = 3 min)
  • everything logged via journalctl

Service Unit

We create a simple systemd service that runs our custom tfrec script at /etc/systemd/system/tfrec.service:

[Unit]
Description=Receive 868 MHz climate data and pass to handler

[Service]
Type=simple
User=root
ExecStart=/bin/bash /opt/tfrec.sh
TimeoutStartSec=180

Timer Unit

And we create a suiting systemd timer which calls our service every 30 minutes (minute 0 and 30 of every hour) at /etc/systemd/system/tfrec.timer:

[Unit]
Description=Run tfrec every 30 minutes

[Timer]
OnCalendar=*:0,30
AccuracySec=1min
Persistent=true

[Install]
WantedBy=timers.target

Enable both:

sudo systemctl daemon-reload
sudo systemctl enable --now tfrec.timer

The Wrapper Script

In our example, we put this script at /opt/tfrec.sh. Put it anywhere logical and accessible.

#!/usr/bin/env bash
/usr/bin/tfrec -q -w 60 -e "php /var/www/cli.php climate:add"
exit 0
  • -q quiet (no human output)
  • -w 60 listen for 60 s
  • -e execute the PHP handler for every packet

Don't forget to chmod +x tfrec.sh after saving it, so we can execute it.

Storing Readings with PHP

If you prefer Python or another language, skip to the next heading—the logic is the same: Have a script which accepts the arguments in order as tfrec adds them to your script, process them, ignore invalid entries and store / publish them.

My code is written for the Charm Framework but it's very close to Laravel and Symfony, you'll easily understand it.

CLI Command climate:add

I created a simple CLI command based on symfony/console. The full code is below. Highlights:

  1. Checks Climate::$sensors so unknown IDs are ignored.
  2. Drops duplicates within 15 minutes (tfrec can see the same packet twice in one window).
  3. Saves to MySQL (or SQLite/PostgreSQL—Laravel's Eloquent abstracts it).
  4. Publishes fresh readings on MQTT via php-mqtt/client.
use App\Models\Climate;
use Carbon\Carbon;
use Charm\Bob\Command;
use Charm\Vivid\C;
use PhpMqtt\Client\ConnectionSettings;
use PhpMqtt\Client\MqttClient;
use Symfony\Component\Console\Input\InputArgument;

class ClimateAdd extends Command
{
    protected function configure()
    {
        $this->setName("climate:add")
             ->setDescription("Store tfrec sensor reading")
             ->addArgument('id',       InputArgument::REQUIRED)
             ->addArgument('temp',     InputArgument::REQUIRED)
             ->addArgument('hum',      InputArgument::REQUIRED)
             ->addArgument('seq',      InputArgument::REQUIRED)
             ->addArgument('batfail',  InputArgument::REQUIRED)
             ->addArgument('rssi',     InputArgument::REQUIRED)
             ->addArgument('timestamp',InputArgument::OPTIONAL);
    }

    public function main(): bool
    {
        $id        = $this->io->getArgument('id');
        $temp      = (double)$this->io->getArgument('temp');
        $hum       = (int)$this->io->getArgument('hum');
        $batfail   = (bool)$this->io->getArgument('batfail');
        $rssi      = (int)$this->io->getArgument('rssi');
        $timestamp = $this->io->getArgument('timestamp');

        // Validate sensor
        if (!array_key_exists($id, Climate::$sensors)) {
            return true;
        }

        // 15‑minute duplicate check
        $recent = Climate::where('sensor_id', $id)
                         ->where('time', '>=', Carbon::now()->subMinutes(15))
                         ->first();
        if ($recent) {
            return true;
        }

        $c                = new Climate();
        $c->sensor_id     = $id;
        $c->temperature   = str_replace("+", "", $temp);
        $c->humidity      = $hum;
        $c->rssi          = $rssi;
        $c->low_battery   = $batfail;
        $c->time          = Carbon::createFromTimestamp($timestamp);
        $c->save();

        $this->publishViaMqtt($c);
        return true;
    }

    private function publishViaMqtt(Climate $c)
    {
        $client = new MqttClient(
            C::Config()->get('connections:mqtt.server'),
            C::Config()->get('connections:mqtt.port'),
            C::Config()->get('connections:mqtt.client_id')
        );
        $settings = (new ConnectionSettings())
            ->setUsername(C::Config()->get('connections:mqtt.username'))
            ->setPassword(C::Config()->get('connections:mqtt.password'));

        $client->connect($settings, true);
        $client->publish("/tfrec/{$c->sensor_id}/temp",  $c->temperature);
        $client->publish("/tfrec/{$c->sensor_id}/hygro", $c->humidity);
        $client->disconnect();
    }
}

Database Model

class Climate extends Model
{
    protected $table = 'tfrec';
    protected $casts = ['time' => 'datetime'];
    public $timestamps = false;

    public static $sensors = [
        '16b4' => 'Entrance',
        '92af' => 'Kitchen',
        '5d43' => 'Livingroom',
        '6ac9' => 'Bedroom',
    ];

    public static function getTableStructure(): \Closure
    {
        return function (Blueprint $table) {
            $table->increments('id');
            $table->string('sensor_id')->index();
            $table->decimal('temperature', 4, 1);
            $table->decimal('humidity',    4, 1);
            $table->tinyInteger('rssi')->nullable();
            $table->tinyInteger('low_battery')->nullable();
            $table->dateTime('time');
        };
    }
}

Feel free to swap in SQLite for a lightweight setup or to just only publish the data via MQTT.

MQTT Integration: Why It Makes Life Easy

No matter how your script looks like, it makes sense that it pushes the climate data to MQTT, so you can use it easily across your apps:

  • OpenHAB / Home Assistant subscribe to /tfrec/# and auto‑discover sensors.
  • Node‑RED can add thresholds, alerts, or InfluxDB logging with a drag‑and‑drop flow.
  • Grafana graphs look great on a wall‑mounted tablet.
  • Any language on any device can listen—MQTT is just lightweight TCP.

After import and a bit of configuration, my OpenHAB dashboard now shows the temperature and humidity on the room cards, provides graphs and more.

Reliability After 5+ Years

Aspect Observation
Sensor battery Batteries last 1–3 years, rarely need replacements (indoor, 20 °C).
Packet loss 1–2 drops per week on the farthest sensor; always reappears next interval.
tfrec stability One rtl_sdr hang every ~8-12 weeks—systemd watchdog handles it silently.
Range Stock antenna covers 15 m through two brick walls, more than enough for me.

When a sensor does disappear it’s usually a weak battery; swapping it fixes reception instantly.

Why RTL‑SDR + DVB‑T Sticks Shine for This Job

  • They’re everywhere. Any electronics hobby shop stocks them.
  • Flexible. The same dongle can read ADS‑B aircraft beacons, pager traffic, or weather satellites—just change software.
  • Cheap to replace. If it burns out, €20 buys a new one.
  • Driver support. rtl-sdr is in every mainstream Linux distro, maintained, and well‑documented.

Wrap‑Up

With a handful of parts and an afternoon of tinkering, you can:

  1. Capture room‑level temperature & humidity continuously.
  2. Store data in your own database—no vendor lock‑in.
  3. Feed dashboards or automations through MQTT.
  4. Expand at will: pool temperature, wind speed, even rain gauges—tfrec already supports them.

It started as a two‑sensor experiment and grew into a house‑wide climate feed I trust. If you give it a try, let me know how it works out—and what you end up building on top. Reliable data has a habit of inspiring new ideas.

Happy hacking!

This post was created by myself with support from AI (GPT o3). Illustrations were generated by myself with Sora. Explore how AI can inspire your content – Neoground GmbH.


Teile diesen Beitrag

Wenn dir dieser Artikel gefallen hat, teile ihn doch mit deinen Freunden und Bekannten! Das hilft mir dabei, noch mehr Leute zu erreichen und motiviert mich, weiterhin großartige Inhalte für euch zu erstellen. Nutze einfach die Sharing-Buttons hier unten, um den Beitrag auf deinen bevorzugten sozialen Medien zu teilen. Danke dir!

Please consider sharing this post
Please consider donating

Unterstütze den Blog

Falls du meine Arbeit und diesen Blog besonders schätzen solltest, würde ich mich riesig freuen, wenn du mich unterstützen möchtest! Du kannst mir zum Beispiel einen Kaffee spendieren, um mich bei der Arbeit an neuen Artikeln zu erfrischen, oder einfach so, um den Fortbestand des Blogs zu fördern. Jede noch so kleine Spende ist herzlich willkommen und wird sehr geschätzt!

currency_bitcoin Spende via Kryptowährungen
Bitcoin (BTC):1JZ4inmKVbM2aP5ujyvmYpzmJRCC6xS6Fu
Ethereum (ETH):0xC66B1D5ff486E7EbeEB698397F2a7b120e17A6bE
Litecoin (LTC):Laj2CkWBD1jt4ZP6g9ZQJu1GSnwEtsSGLf
Sven Reifschneider
Über den Autor

Sven Reifschneider

Herzliche Grüße! Ich bin Sven, ein technischer Innovator und begeisterter Fotograf aus der malerischen Wetterau, in der Nähe des lebendigen Frankfurt/Rhein-Main-Gebiets. In diesem Blog verbinde ich mein umfangreiches technisches Wissen mit meiner künstlerischen Leidenschaft, um Geschichten zu erschaffen, die fesseln und erleuchten. Als Leiter von Neoground spreng ich die Grenzen der KI-Beratung und digitalen Innovation und setze mich für Veränderungen ein, die durch Open Source Technologie Widerhall finden.

Die Fotografie ist mein Portal, um die flüchtige Schönheit des Lebens auszudrücken, die ich nahtlos mit technologischen Einsichten verbinde. Hier trifft Kunst auf Innovation, jeder Beitrag strebt nach Exzellenz und entfacht Gespräche, die inspirieren.

Neugierig, mehr zu erfahren? Folge mir in den sozialen Medien oder klicke auf "Mehr erfahren", um das Wesen meiner Vision zu erkunden.


Noch keine Kommentare

Kommentar hinzufügen

In deinem Kommentar kannst du **Markdown** nutzen. Deine E-Mail-Adresse wird nicht veröffentlicht. Mehr zum Datenschutz findest du in der Datenschutzerklärung.