<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Sebastian Helms &#8211; SmartHomeNG | smarthome knx homematic mqtt hue 1wire home automation</title>
	<atom:link href="https://www.smarthomeng.de/author/morg/feed" rel="self" type="application/rss+xml" />
	<link>https://www.smarthomeng.de</link>
	<description>Die Device Integrations-Plattform für Dein Smart Home</description>
	<lastBuildDate>Thu, 10 Sep 2026 15:19:49 +0000</lastBuildDate>
	<language>de</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.5.10</generator>

<image>
	<url>https://www.smarthomeng.de/wp-content/uploads/global/logo_small_152x152-150x150.png</url>
	<title>Sebastian Helms &#8211; SmartHomeNG | smarthome knx homematic mqtt hue 1wire home automation</title>
	<link>https://www.smarthomeng.de</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Nutzung von Datenbanken in SmartHomeNG</title>
		<link>https://www.smarthomeng.de/nutzung-von-datenbanken-in-smarthomeng</link>
					<comments>https://www.smarthomeng.de/nutzung-von-datenbanken-in-smarthomeng#respond</comments>
		
		<dc:creator><![CDATA[Sebastian Helms]]></dc:creator>
		<pubDate>Thu, 10 Sep 2026 15:19:46 +0000</pubDate>
				<category><![CDATA[Core]]></category>
		<category><![CDATA[Releases und Updates]]></category>
		<category><![CDATA[Tipps & Tricks]]></category>
		<category><![CDATA[Datenbank]]></category>
		<guid isPermaLink="false">https://www.smarthomeng.de/?p=2847</guid>

					<description><![CDATA[Im develop Branch sind ein paar größere Neuerungen in lib.db und dem database Plugin gelandet. Der letzte Teil behandelt die aktuellen Updates, die ersten Teile gehen nochmal auf schon fertige Änderungen ein, die aber offensichtlich noch nicht alle erreicht haben 🙂 1. Datenlücken statt &#8222;Wert unverändert&#8220; Wenn eine Datenquelle zeitweise<a class="moretag" href="https://www.smarthomeng.de/nutzung-von-datenbanken-in-smarthomeng"> Weiterlesen&#8230;</a>]]></description>
										<content:encoded><![CDATA[<p>Im develop Branch sind ein paar größere Neuerungen in lib.db und dem database Plugin gelandet. Der letzte Teil behandelt die aktuellen Updates, die ersten Teile gehen nochmal auf schon fertige Änderungen ein, die aber offensichtlich noch nicht alle erreicht haben <img src="https://s.w.org/images/core/emoji/15.0.3/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<h2>1. Datenlücken statt &#8222;Wert unverändert&#8220;</h2>
<p>Wenn eine Datenquelle zeitweise ausfällt (offline, Verbindung nicht da) oder keine Daten liefert, behält das Item derzeit einfach seinen letzten Wert. Ohne Korrekturen wird dieser Wert für die gesamte Zeitspanne in der Datenbank gespeichert. Neben verfälschten Berechnungen haben auch Plots/Graphen dabei durchgehende Linien, obwohl ggf. keine Daten vorlagen.</p>
<p>Das ist ein altes Thema, über das in den Issues schon viel diskutiert wurde, und es gibt eine Lösung. Genaugenommen &#8211; zwei. <code>item.db_mark_invalid()</code> / <code>item.db_mark_valid()</code>.</p>
<pre><code class="language-python"># Verbindung verloren
sh.solar.leistung.db_mark_invalid(caller='solar_plugin', source='connection_lost')

# Verbindung wieder da
sh.solar.leistung.db_mark_valid(caller='solar_plugin', source='connection_restored')
sh.solar.leistung(neuer_wert, 'solar_plugin')
</code></pre>
<p>Wichtig dabei: das passiert nicht von allein. Das jeweilige Plugin muss den Ausfall selbst erkennen und <code>db_mark_invalid()</code> aktiv aufrufen. Unterstützt ein Plugin das (noch) nicht, bleibt der letzte Wert wie bisher einfach unverändert stehen. Aktuell macht das noch kein Plugin von Haus aus &#8211; Gateway-Plugins wie MQTT (Verbindung zum Broker verloren) oder KNX (Bus-Timeout, knxd nicht erreichbar) wären die ersten Kandidaten, die ich in absehbarer Zeit angehen möchte. Wer Lust hat, das für sein Lieblings-Plugin nachzurüsten: immer her damit.</p>
<p>Neu und ohne Plugin-Unterstützung nutzbar: das Item-Attribut <code>database_invalid_after</code>. Bekommt ein Item über die angegebene Zeitspanne keine Änderung/kein Update, wird automatisch eine Lücke geöffnet:</p>
<pre><code class="language-yaml">solar:
    leistung:
        type: num
        database: init
        enforce_updates: true
        database_invalid_after: 10m
</code></pre>
<p><code>enforce_updates</code> muss gesetzt sein, sonst zählt ein unverändertes Update nicht als Lebenszeichen. Und: das ist nur für Items sinnvoll, die ohnehin regelmäßig aktualisiert werden (cycle/crontab o.ä.) &#8211; bei unregelmäßig/ereignisgesteuert aktualisierten Items gibt&#8217;s sonst nur Fehlalarme.</p>
<p>Sobald wieder ein Wert gesendet wurde, wird automatisch <code>db_mark_valid()</code> aufgerufen und die Werte &#8222;zählen&#8220; wieder.</p>
<p>Lückeneinträge werden bei <code>avg</code>/<code>sum</code>/<code>integrate</code>/<code>on</code>/<code>min</code>/<code>max</code> automatisch ausgeschlossen, tauchen bei Rohwertabfragen als <code>NULL</code> (aus der Datenbank) auf (für Lücken in Plots), und lassen sich im Webinterface pro Datensatz auch manuell setzen/zurücknehmen.</p>
<h3>Beispiel: KNX-Item und MQTT-Item</h3>
<p>Zwei Items, zwei Transportwege, aber beide mit demselben eigentlichen Risiko: die Verbindung zur Außenwelt kann abreißen, ohne dass shng selbst oder das jeweilige Plugin davon groß Notiz nimmt &#8211; Telegramme bzw. Nachrichten bleiben einfach aus:</p>
<pre><code class="language-yaml">heizung:
    vorlauftemperatur:
        type: num
        database: init
        knx_dpt: 9
        knx_listen: 1/2/3
        enforce_updates: true
        database_invalid_after: 15m

garage:
    aussentemperatur:
        type: num
        database: init
        mqtt_topic_in: garage/aussentemperatur
        enforce_updates: true
        database_invalid_after: 3m
</code></pre>
<p><code>heizung.vorlauftemperatur</code> kommt vom Sensor selbst zyklisch (in ETS am Gerät eingestellt, z.B. alle 5 Minuten senden, unabhängig von einer Wertänderung) &#8211; komplett unabhängig von shngs eigenem Scheduler. Fällt <code>knxd</code> aus oder bricht die Verbindung zum Bus ab, bleiben die Telegramme einfach aus. Das KNX-Plugin selbst muss dabei nicht abstürzen oder für dieses eine Item einen auffälligen Fehler werfen &#8211; andere Items mit zwischenzeitlich gecachten Werten sehen auf den ersten Blick unauffällig aus. Genau diesen Fall fängt <code>database_invalid_after</code> ab, ohne dass jemand ins Log schauen muss.</p>
<p><code>garage.aussentemperatur</code> kommt per MQTT rein, mit demselben Prinzip: viele Sensoren (Zigbee2MQTT, Tasmota, &#8230;) senden zyklisch, unabhängig davon ob sich der Wert geändert hat &#8211; hier alle 60s angenommen, daher <code>3m</code> als Schwelle. Bleibt der Sensor tot oder der Broker weg, bleibt auch das MQTT-Update aus, ohne dass shng selbst etwas falsch macht.</p>
<p>Ein rein shng-internes <code>cycle</code>+<code>eval</code>-Item wäre dagegen kein gutes Beispiel: solange shng überhaupt läuft, feuert dessen Scheduler garantiert, und stünde der Scheduler wirklich still, würde auch die Prüfung selbst (<code>invalid_check_cycle</code>) nicht mehr laufen &#8211; es gäbe also niemanden mehr, der Alarm schlagen könnte.</p>
<p>Der Haken liegt nicht bei MQTT als Transportweg, sondern bei Quellen, die nur bei Zustandsänderung senden &#8211; klassisch Tür-/Fensterkontakte, viele Taster. Da ist &#8222;seit 30 Minuten kein Update&#8220; der absolute Normalfall, wenn eben nichts passiert ist, kein Ausfall. <code>database_invalid_after</code> ist nur sinnvoll, wenn die Quelle nachweislich regelmäßig sendet &#8211; egal ob per MQTT, KNX oder sonstwie -, und die Schwelle muss zu genau diesem Intervall passen, nicht zu einer geschätzten &#8222;sollte doch mal was kommen&#8220;-Zeit.</p>
<p>Das Risiko bei Fehlkonfiguration &#8211; falscher Itemtyp ohne regelmäßige Updates, oder zu kurz gewählte <code>database_invalid_after</code>-Zeit &#8211; sind Zeiträume, die in der Datenbank als &#8222;nicht vorhanden&#8220; eingetragen sind, obwohl eigentlich ein gültiger Wert vorlag. Das fällt seinerseits nur auf, wenn man es regelmäßig auf Plots überprüft.</p>
<h2>2. Aggregierung statt Löschen &#8211; die Optionen im Überblick</h2>
<p><code>database_maxage</code> ist ein bekanntes Item-Attribut und löscht alte Werte ersatzlos. Über <code>database_maxage_action</code> lässt sich jetzt stattdessen ein &#8222;Ersatzwert&#8220; zu einem Intervall verdichten:</p>
<table>
<thead>
<tr>
<th>Aktion</th>
<th>Bedeutung</th>
<th>Typ</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>delete</code></td>
<td>löschen (Standard)</td>
<td>beliebig</td>
</tr>
<tr>
<td><code>avg</code></td>
<td>zeitgewichteter Mittelwert</td>
<td>num, bool</td>
</tr>
<tr>
<td><code>sum</code></td>
<td>Summe</td>
<td>num, bool</td>
</tr>
<tr>
<td><code>min</code> / <code>max</code></td>
<td>Minimal-/Maximalwert</td>
<td>num, bool</td>
</tr>
<tr>
<td><code>integrate</code></td>
<td>diskretes Integral über der Zeit</td>
<td>num, bool</td>
</tr>
<tr>
<td><code>duty_cycle</code> (alt: <code>on</code>)</td>
<td>zeitgewichtete Einschaltdauer</td>
<td>bool</td>
</tr>
<tr>
<td><code>countall</code></td>
<td>Anzahl Rohwerte im Intervall</td>
<td>beliebig</td>
</tr>
<tr>
<td><code>first</code> / <code>last</code></td>
<td>ältester/neuester Rohwert, unverändert</td>
<td>beliebig, auch <code>str</code></td>
</tr>
</tbody>
</table>
<p>Das läuft komplett Python-seitig im Plugin, identisch auf SQLite3, MySQL/MariaDB und PostgreSQL. Wer zusätzlich zum Mittelwert auch Min/Max eines Intervalls dauerhaft behalten will, kann dafür die mitgelieferten Structs <code>database.min</code>/<code>database.max</code> nutzen (legen automatisch eigene Kind-Items mit den jeweiligen Werten an).</p>
<h3>Beispiel: vier Items, vier verschiedene Konfigurationen</h3>
<p>In den Konfigurationen fehlen die eigentlichen Datenquellen, die darf sich jeder dazu denken.</p>
<pre><code class="language-yaml"># plugin.yaml
database:
    plugin_name: database
    driver: sqlite3
    default_maxage_interval: 24h
</code></pre>
<pre><code class="language-yaml"># items/beispiel.yaml
wohnzimmer:
    temperatur:
        type: num
        database: init
        database_maxage: 90
        database_maxage_action: avg
        database_maxage_interval: 1h

pv:
    leistung:
        type: num
        database: init
        database_maxage: 365
        database_maxage_action: integrate
        database_maxage_interval: 1d

haustuer:
    kontakt:
        type: bool
        database: init
        database_maxage: 30
        database_maxage_action: countall

heizung:
    status:
        type: str
        database: init
        database_maxage: 14
        database_maxage_action: last
        database_maxage_interval: 6h
</code></pre>
<ul>
<li><code>wohnzimmer.temperatur</code>: nach 90 Tagen werden die Rohwerte je Stunde zu einem Mittelwert verdichtet &#8211; Langzeitverlauf bleibt Jahre erhalten, die minütlichen Rohdaten nicht.</li>
<li><code>pv.leistung</code>: nach einem Jahr wird aus der Momentanleistung je Tag die tatsächlich erzeugte Energie integriert &#8211; klassischer Fall für <code>integrate</code>.</li>
<li><code>haustuer.kontakt</code>: setzt kein eigenes <code>database_maxage_interval</code> &#8211; fällt auf den Plugin-Parameter <code>default_maxage_interval: 24h</code> zurück. Nach 30 Tagen bleibt pro Tag nur noch die Anzahl der Türöffnungen übrig.</li>
<li><code>heizung.status</code>: ein <code>str</code>-Item &#8211; hier funktionieren <code>avg</code>/<code>sum</code>/etc. nicht, aber <code>last</code> schon. Nach 14 Tagen bleibt je 6h-Intervall nur noch der letzte bekannte Status übrig.</li>
</ul>
<p>Effekt in der Praxis: Die Kompaktierung läuft im selben Zyklus wie das bisherige Löschen (<code>removeold_cycle</code>) und arbeitet sich &#8211; genau wie das &#8211; in begrenzten Schritten durch alte Daten (<code>max_aggregate_intervals</code>), pro Item. Bei vier Items mit doch recht unterschiedlichem Alter blockiert also keines der anderen den Durchlauf.</p>
<p>Ein Punkt, den man im Hinterkopf behalten sollte: Ändert man später bei einem Item mit <code>countall</code> (hier <code>haustuer.kontakt</code>) nachträglich das <code>database_maxage_interval</code>, werden schon kompaktierte alte Intervalle beim nächsten Durchlauf mit in ein neues, anders breites Intervall gezogen &#8211; für <code>avg</code>/<code>sum</code>/<code>min</code>/<code>max</code>/<code>integrate</code>/<code>duty_cycle</code> rechnerisch unproblematisch, bei <code>countall</code> zählt ein bereits kompaktiertes Intervall dann aber nur noch als &#8222;1&#8220; statt als die ursprüngliche Anzahl Rohwerte. Kein Bug, nur eine Ungenauigkeit, die man bei <code>countall</code> + nachträglicher Intervalländerung kennen sollte.</p>
<h2>3. PostgreSQL+TimescaleDB als neues Backend</h2>
<p>Neben SQLite3 und MySQL/MariaDB unterstützt das Plugin jetzt auch PostgreSQL, optional mit der <a href="https://www.timescale.com/">TimescaleDB</a>-Erweiterung. Die Treiber-Auswahl ist jetzt auch komfortabler: <code>driver</code> akzeptiert neben den echten Modulnamen (<code>psycopg2</code>/<code>psycopg</code>) auch sprechende Namen wie <code>postgres</code>, <code>postgresql</code>, <code>timescale</code>, <code>timescaledb</code> (und <code>mysql</code>/<code>mariadb</code> statt <code>pymysql</code>) &#8211; welches Python-Modul installiert ist, wird automatisch erkannt.</p>
<pre><code class="language-yaml">database_timescale:
    plugin_name: database
    driver: timescaledb
    connect:
    -   host:127.0.0.1
    -   port:5432
    -   user:shng
    -   password:shng_password
    -   database:shng
</code></pre>
<p><strong>Was TimescaleDB bringt:</strong></p>
<ul>
<li><strong>Hypertables</strong> &#8211; die <code>log</code>-Tabelle wird automatisch in Zeitpartitionen (Dateien für einzelne Zeiträume) aufgeteilt, macht zeitbereichsbasierte Abfragen auf großen Tabellen deutlich schneller.</li>
<li><strong>Native Kompression</strong> (<code>timescale_compress: true</code>) &#8211; TimescaleDBs eigene Doku nennt 10-20x (90-95% Speicherplatzersparnis) als üblich für Zeitreihendaten; im eigenen Test gegen einen echten 22-Millionen-Zeilen-Datensatz kamen 17,39x raus.</li>
<li><strong>Native Aggregation</strong> (<code>timescale_native_aggregation: true</code>) &#8211; statt Python-seitiger Aggregierung übernimmt TimescaleDB das über Continuous Aggregates, direkt auf dem Server. Dieselben Item-Attribute (<code>database_maxage_action</code> etc.) steuern das weiterhin.</li>
<li><strong>Native Retention</strong> (<code>timescale_native_retention: true</code>) &#8211; alte Rohdaten-Zeitpartitionen werden automatisch server-seitig fallengelassen statt Item für Item gelöscht.</li>
</ul>
<p><strong>Und die Nachteile, ehrlich gesagt:</strong></p>
<ul>
<li>Mehr Aufwand als SQLite3 (extra Server, extra Python-Paket <code>psycopg2-binary</code> oder <code>psycopg</code>).</li>
<li>Native Retention ist ein <strong>globaler</strong> Schwellwert für die ganze Tabelle, kein Wert pro Item mehr &#8211; auch Items mit <code>database_maxage_action: delete</code> warten dann auf denselben globalen Schwellwert.</li>
<li>Wichtig: Items <strong>ohne</strong> <code>database_maxage</code> sind davon nicht ausgenommen. ALLE Items werden nach der längsten eingestellten Zeit aggregiert &#8211; oder gelöscht. Wenn keine <code>default_maxage_action</code> eingestellt ist, werden alle nicht ausdrücklich anders konfigurierten Datenbankwerte der Items gelöscht. Wer native Retention nutzt, sollte <code>default_maxage</code> und <code>default_maxage_action</code> setzen.</li>
<li>Der Wechsel von nativer Aggregation zurück zu Plugin-Aggregierung ist nicht vorgesehen (nicht implementiert, da alles andere als trivial) und nicht verlustfrei möglich.</li>
<li>Auch die einmal aktivierte Kompression lässt sich über das Plugin nicht mehr rückgängig machen.</li>
</ul>
<h3>Warum gerade TimescaleDB</h3>
<p>Es gibt schon ein InfluxDB-Plugin, und Influx gilt seit Langem als die Referenz für Zeitreihen-Datenbanken. Das Problem mit InfluxDB ist dabei zweierlei: zum Einen hat sich die API mit jeder größeren Version komplett geändert, was ständigen Updateaufwand mit sich bringt und für jede Version ein eigenes Plugin erfordern würde. Zum Anderen ist die aktuelle Open-Source-Linie (InfluxDB 3 Core) explizit als Edge-/Echtzeit-Collector positioniert &#8211; Compactor und Optimierungen für Langzeit-Speicherung fehlen, und genau die wären für unseren Stats-/Grafik-Anwendungsfall am wichtigsten. Für brauchbare Abfrage-Performance über die Historie braucht es InfluxDB Enterprise. Es gibt zwar eine kostenlose Hobby-Stufe, aber damit hängt man an einer Hersteller-Lizenzstufe statt an einer eigenständigen Open-Source-Installation &#8211; für uns als reines Community-Projekt nicht ideal.</p>
<p>TimescaleDB basiert auf PostgreSQL, einer seit Langem gepflegten, gut getesteten und leistungsfähigen Datenbank, die auf den meisten System problemlos verfügbar ist. Die TimescaleDB-Erweiterung bringt mit der nativen Kompression einen großen Platzvorteil mit (siehe oben, 10-20x laut Timescale, 17,39x im eigenen Test), und die nativen Aggregations- und Retentionsfunktionen entlasten SmartHomeNG von Routineaufgaben, die sonst Python-seitig laufen müssten.</p>
<h3>Beispiel: dieselben vier Items, jetzt nativ auf TimescaleDB</h3>
<p>An den Items selbst ändert sich nichts &#8211; dieselbe Konfiguration wie oben. Nur <code>plugin.yaml</code> bekommt zwei zusätzliche Zeilen:</p>
<pre><code class="language-yaml">database_timescale:
    plugin_name: database
    driver: timescaledb
    connect:
    -   host:127.0.0.1
    -   port:5432
    -   user:shng
    -   password:shng_password
    -   database:shng
    default_maxage_interval: 24h
    timescale_hypertable: true
    timescale_native_aggregation: true
    timescale_native_retention: true
</code></pre>
<p>Was jetzt passiert: TimescaleDB legt <strong>ein</strong> Continuous Aggregate je tatsächlich genutzter Intervallbreite an, nicht eines je Item. Die Continuous Aggregates werden intern als virtuelle Tabelle über der log-Tabelle eingeblendet. Bei den vier Beispiel-Items sind das genau drei: 1h (<code>wohnzimmer.temperatur</code>), 6h (<code>heizung.status</code>) und 1d (<code>pv.leistung</code> und <code>haustuer.kontakt</code> über den <code>default_maxage_interval</code>-Fallback teilen sich hier eines, weil beide auf dieselbe Intervallbreite kommen). Die Aggregation selbst läuft weiter pro Item korrekt nach dessen eigener Konfiguration.</p>
<p>Der eigentliche Unterschied, konkret an diesem Beispiel: <code>pv.leistung</code> hat mit 365 Tagen das mit Abstand längste <code>database_maxage</code>. Unter nativer Retention wird genau dieser Wert (plus eine Zeitpartition Sicherheitsspanne) zum <strong>einzigen</strong> Schwellwert für die gesamte Tabelle. Die Rohdaten von <code>haustuer.kontakt</code> &#8211; eigentlich für 30 Tage konfiguriert &#8211; bleiben deswegen faktisch bis zu ein Jahr lang liegen, weil TimescaleDB pro Zeitpartition löscht und diese Item-übergreifend geteilt werden. Im Plugin-Modus (Abschnitt 2) würde <code>haustuer.kontakt</code> dagegen zuverlässig nach 30 Tagen kompaktiert. Das ist genau der &#8222;globaler statt Item-genauer Schwellwert&#8220;-Nachteil von oben, nur diesmal mit echten Zahlen dahinter. Durch die Kompression schrumpft der Platzbedarf für die eigentlich nicht erforderlichen Daten so weit, dass der zusätzliche Speicherplatz in der Praxis kein Problem darstellt. Trotzdem lässt sich dieses Feature optimal nutzen, wenn <code>database_maxage</code> bei allen Items in derselben Größenordnung liegt.</p>
<p>Für die meisten Home-Installationen reicht SQLite3 grundsätzlich aus. Da ich aber gehört habe, wie viele Millionen Zeilen viele Leute im Forum in ihren Datenbanken haben, wird TimescaleDB schon wieder interessant &#8211; und sei es nur aufgrund der Kompression, selbst ohne native Aggregation/Retention.</p>
<h3>Migration von Datenbanken</h3>
<p>Wer bestehende Daten zwischen Backends umziehen will (z.B. SQLite3 → TimescaleDB, oder umgekehrt): dafür gibt&#8217;s jetzt <code>tools/db_migrate.py</code> als eigenständiges Skript (läuft nur bei gestopptem SmartHomeNG). Migriert Item- und Log-Daten direkt zwischen zwei beliebigen unterstützten Backends, unterstützt Resume und <code>--dry-run</code>. Aber vorsicht &#8211; selbst auf einem schnellen Rechner sollte man für 22 Millionen Zeilen schon gute drei Stunden reine Transferzeit einplanen.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.smarthomeng.de/nutzung-von-datenbanken-in-smarthomeng/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
