# Tobias Schulz — Volltext aller Beiträge > Smart Home, Technik, Kram | Persönliche Webseite von Tobias Schulz Vollständiger Markdown-Inhalt aller Beiträge, neueste zuerst. Jeder Beitrag ist auch einzeln unter seiner `.md`-URL abrufbar; eine kuratierte Übersicht steht in https://tbsch.de/llms.txt. # Frisch gestrichen: mein eigenes Theme ersetzt Blowfish > Blowfish raus, Eigenbau rein: meine Seite läuft jetzt auf einem eigenen Hugo-Theme. _Quelle: https://tbsch.de/post/2026-06-23-frisch-gestrichen-mein-eigenes-theme-ersetzt-blowfish/ · Stand: 2026-06-23_ > Blowfish raus, Eigenbau rein: meine Seite läuft jetzt auf einem eigenen Hugo-Theme. Es gibt Projekte, die man sich selbst eine Weile verbietet, weil man genau weiß, wie tief das Kaninchenloch ist. Ein eigenes Blog-Theme war für mich so eines. Jahrelang lief meine Seite auf [Blowfish](https://blowfish.page), einem wirklich guten Theme für [Hugo](https://gohugo.io). Schlank, schnell, gepflegt - eigentlich alles, was man braucht.
Nur: Mit der Zeit hatte ich hier ein Layout überschrieben, dort eine Farbe zurechtgebogen, da noch ein Partial ersetzt. Irgendwann war meine Seite weniger "Blowfish" und mehr "Blowfish, den ich mit Klebeband umgebaut habe". Und jedes Theme-Update wurde zur kleinen Mutprobe: Bricht diesmal eine meiner Anpassungen? ## Forken oder selbst bauen? Damit lagen zwei Wege auf dem Tisch. Entweder *Blowfish* forken und meinen Umbau offiziell machen - oder das Theme von Grund auf selbst schreiben, genau nach meinen Vorstellungen. Ein Fork hätte bedeutet: Ich erbe eine fremde, sehr umfangreiche Codebasis, von der ich gerade einmal die Hälfte nutze, und pflege sie fortan allein weiter. Der ganze Ballast bliebe, das Update-Problem auch - nur ohne den Upstream, der es sonst für mich löst. Also habe ich mich für den anderen Weg entschieden: ein **eigenes Theme**, von der ersten Zeile an meins. Klein gehalten, nur das, was meine Seite wirklich braucht, und eine Optik, die eben nicht jeder Hugo-User hat. Es heißt schlicht **tbsch** und ist während der Entwicklung isoliert neben *Blowfish* gelaufen, sodass die Live-Seite die ganze Zeit unangetastet blieb. Erst zum Schluss habe ich umgeschaltet. ## Die neue Optik Das Herzstück ist eine eigene Farbidentität: ein Verlauf von Orange über Pink nach Violett, den ich *Solarflare* getauft habe. Den siehst du im Titelbild oben - er zieht sich als Akzent durch die ganze Seite. Dazu eine ruhige Sans-serif (*Inter*), ein editorial angelegtes Layout, ein schwebender Pill-Header und ein Lesefortschrittsbalken. Und ja: **Dark Mode**. Standardmäßig folgt die Seite jetzt einfach deinem System - steht der Rechner auf dunkel, ist die Seite dunkel. Den manuellen Schalter gibt es trotzdem, und das berüchtigte kurze Aufblitzen in Weiß beim Laden ist ebenfalls Geschichte. ## Was darunter besser wurde Ein Redesign nur fürs Auge wäre mir zu wenig gewesen. Der eigentliche Gewinn steckt unter der Haube: - **Datenschutz ohne Sternchen.** Alle Schriften und Bibliotheken liegen jetzt lokal, nichts wird mehr von fremden CDNs nachgeladen - keine Google Fonts, und *Google Analytics* ist gleich ganz rausgeflogen. *Inter* und *JetBrains Mono* kommen als lokale woff2-Dateien. - **Bilder, die sich selbst sortieren.** *Hugo* rechnet jedes Bild automatisch in die passenden Größen und nach *WebP* um. Kein manuelles Konvertieren mehr, und wo früher gern mal **20 MB** durch die Leitung gingen, wiegt ein Vorschaubild jetzt nur noch rund **20 KB**. - **IndieWeb statt Datenkrake.** Die Seite trägt jetzt microformats2-Markup und sammelt Reaktionen per Webmention ein - Likes, Reposts und Antworten aus dem Fediverse und von Threads landen direkt unter dem Beitrag, dazu Atom- und JSON-Feeds. Brauchen tue ich das alles ehrlich gesagt nicht - ich hatte schlicht Spaß daran, es einzubauen. > [!info] Externe Inhalte nur mit Ja > Die wenigen externen Einbettungen - etwa das Vorschaubild einer GitHub-Projektkarte - laufen über den Consent-Manager *Klaro* und werden erst nach deiner Zustimmung geladen. Bis dahin verlässt kein Byte deinen Browser in deren Richtung. Klingt nach viel Arbeit für ein Hobby-Blog? War es auch :grin:. Den Löwenanteil habe ich übrigens zusammen mit *Claude* gebaut, und ich muss zugeben: Ich arbeite mittlerweile richtig gern mit dem Ding. Und trotzdem ist es am Ende eben *meine* Arbeit, an *meinem* Code - genau das war der Punkt. > [!failure] Die Kehrseite > Ein eigenes Theme heißt auch: Ab jetzt ist jeder schiefe Pixel meiner. Es gibt keinen Upstream mehr, der meine Bugs fixt, während ich schlafe. Aber damit kann ich gut leben. Ein paar Ecken werde ich in den nächsten Wochen noch nachschleifen - so ein Relaunch ist nie wirklich "fertig", sondern bestenfalls "live". Anregungen? Wünsche? Fehler? Schreib mir :sunglasses:. --- Das Titel-/Hintergrundbild stammt von mir - der *Solarflare*-Verlauf des **tbsch**-Theme. --- # Zendure SolarFlow im Energie-Dashboard > SolarFlow und Shellys in Node-RED harmonisiert, damit das Energie-Dashboard mit dem Zähler aufgeht. _Quelle: https://tbsch.de/post/2026-06-19-zendure-solarflow-im-energie-dashboard/ · Stand: 2026-06-19_
flowchart TD
subgraph quellen["Quellen"]
sf["SolarFlow via MQTT
(sensor.solarflow_*)"]
p3em["Shelly Pro3EM
(3 Phasen)"]
pv["Shelly Outdoor
PV-Steckdose"]
keller["Shelly Plug S
Keller-Geräte"]
end
subgraph normalisierung["Node-RED Normalisierung"]
compute["compute-Function
7 Layer Mathematik"]
end
subgraph homeem["home_em-Schicht (Watt)"]
power["sensor.home_em_*_power
(via WebSocket-API)"]
end
subgraph energy["home_em-Schicht (kWh)"]
riemann["sensor.home_em_*_energy
(Riemann-Sum-Helfer)"]
end
subgraph dashboard["Home Assistant"]
ed["Energie-Dashboard"]
end
sf --> compute
p3em --> compute
pv --> compute
keller --> compute
compute --> power
power --> riemann
riemann --> ed
Vier Quellen oben, eine Berechnungs-Schicht in der Mitte, zwei abstrakte Schichten danach, das Energie-Dashboard am Ende. Genau diese **Trennung** ist der Schlüssel - und sie ist auch der Grund, warum ich überhaupt eine eigene `home_em`-Schicht eingeführt habe statt einfach die rohen Sensoren ins Dashboard zu hängen.
> [!info]
> `home_em` steht für *Home Energy Management*. Mein Namensschema ist `home_em_Dieses Objekt wird laufend mit den aktuellen Daten versorgt und bildet die Grundlage der mathematischen Berechnungen.
{
"sh_pv_socket": -141,
"sh_keller_server": 24.29,
"sh_keller_dryer": 0,
"sh_keller_wama": 0,
"sh_3em_a": 83.72,
"sh_3em_b": 0,
"sh_3em_c": -95.9,
"sf_solar_input": 408,
"sf_solar_panel_1": 150,
"sf_solar_panel_2": 258,
"sf_battery_input": 0,
"sf_battery_output": 239,
"sf_home_output": 145
}
Einmal definiert, welche Sensoren genau ich abbilden möchte: das Objekt enthält die Ausgabe der auf das Input-Objekt angewendeten Berechnungen.
{
"socket_import": 141,
"grid_import": 12.11,
"grid_export": 0,
"grid_consume": 153.11,
"battery_import": 239,
"battery_export": 0,
"battery_flow": -239,
"solar_production": 380,
"solar_consume": 141,
"solar_panel_1": 139.71,
"solar_panel_2": 240.29
}
flowchart TD
sensors["13 State-Change Nodes
(alle Quellen)"] --> state["state-Function
schreibt flow.sensor_raw"]
state --> trigger["Trigger-Node
(250 ms Debounce)"]
trigger --> compute["compute-Function
(7 Layer Mathematik)"]
compute --> tracking["change-tracking
(nur geänderte Werte raus)"]
tracking --> switchnode["Switch-Node
(verteilt nach Topic)"]
switchnode --> hasensors["11 HA-Sensor-Nodes
(home_em_*_power)"]
Drei Bausteine, die den Unterschied machen:
1. **State-Bus.** Statt 13 Funktionen parallel laufen zu lassen, schreibt jeder State-Change in ein **einziges Flow-Objekt** `sensor_raw`. Die `compute`-Function hat damit immer den vollständigen Snapshot.
2. **250-ms-Debounce.** Wenn fünf Sensoren in derselben Sekunde feuern, soll die Berechnung **einmal** laufen, nicht fünfmal. Der Trigger-Node sammelt die Tick-Events ein und setzt maximal alle 250 ms eine Nachricht ab.
3. **Change-Tracking.** Nur geänderte Werte werden an die HA-Sensoren weitergereicht. Das hält den Datenverkehr zur *Home Assistant* WebSocket-Verbindung schlank und die Historie in der HA-Datenbank sauber.
Die Ausgabe geht über **`ha-sensor`-Nodes** der [Node-RED Home Assistant WebSocket Integration](https://flows.nodered.org/node/node-red-contrib-home-assistant-websocket). Damit definiere ich pro Sensor genau einmal `device_class: power`, `unit_of_measurement: W` und `state_class: measurement` - und die Sensoren erscheinen automatisch in *Home Assistant*, sauber benannt, mit dem `home_em_*`-Präfix.
## Von Watt zu Kilowattstunden
Damit hat das Energie-Dashboard zwar saubere Power-Werte, aber noch keine Kilowattstunden. Die holen wir uns mit dem eingebauten **Riemann-Sum-Helfer** von *Home Assistant*, der aus jedem Power-Sensor einen Energie-Sensor macht.
Statt das händisch in YAML zu pflegen, lege ich die Helfer **bequem über die UI** an: Einstellungen → Geräte und Dienste → Helfer → Helfer erstellen → Integralsensor. Pro Power-Sensor einmal anlegen, als Methode **Linke Riemannsche Summe**, Präfix `k` für kWh und eine **Genauigkeit von 3** setzen. Die Details und Optionen sind sehr gut in der [offiziellen Doku](https://www.home-assistant.io/integrations/integration) beschrieben.
Das Ergebnis sind elf neue Sensoren mit `device_class: energy`, `unit_of_measurement: kWh` und `state_class: total` - genau das, was das Energie-Dashboard auswählbar macht.
## Im Energie-Dashboard einbinden
Letzter Schritt, erfreulich kurz: **Einstellungen → Energie**. Drei Kategorien anlegen:
| Kategorie | Sensoren |
| :------------------- | :--------------------------------------------------------------------------------------------------------- |
| **Stromnetz** | `sensor.home_em_p_grid_import_energy` (Verbrauch), `sensor.home_em_p_grid_export_energy` (Einspeisung) |
| **Solarmodule** | `sensor.home_em_s_solar_panel_1_import_energy` (SSW), `sensor.home_em_s_solar_panel_2_import_energy` (SSO) |
| **Batteriespeicher** | `sensor.home_em_s_battery_import_energy`, `sensor.home_em_s_battery_export_energy` |
Sobald du auf Speichern klickst, beginnt das Energie-Dashboard mit dem Sammeln. Die Sankey-Visualisierung wird in den ersten Stunden noch karg sein - das ist normal. Nach ein paar Tagen hast du ein vollständiges Bild deines Stromhaushalts, und vor allem: ein Bild, das **mit deinem Stromzähler übereinstimmt** :sunglasses:.

## Stolpersteine
**Reihenfolge der Layer ist nicht verhandelbar.** Wer in der `compute`-Function Layer 3 vor Layer 2 berechnet, bekommt überall Nullen. Die Abhängigkeitskette muss strikt eingehalten werden.
**Debounce-Wert mit Bedacht wählen.** Bei mir feuern die rohen Sensoren teils mehrfach pro Sekunde. Ohne Debounce würde die compute-Function entsprechend oft laufen, obwohl sich faktisch kaum etwas ändert. Mit 250 Millisekunden begrenze ich sie auf höchstens vier Durchläufe pro Sekunde, was im Alltag ein guter Kompromiss zwischen Reaktionsfreude und CPU-Schonung ist. Wer auf 1000 hochdreht, bekommt spürbare Latenz im Dashboard, weil Lastwechsel erst nach einer Sekunde durchgereicht werden.
**`ha-sensor`-Nodes kennen nur einen State.** Wenn dein Flow nach einem Neustart von *Node-RED* nicht sofort auslöst, bleibt der Sensor bei seinem letzten Wert. Mein Inject-Node mit `force: true` setzt nach jedem Deploy einmal alle Werte zwangsweise neu - kleine Geste, große Wirkung.
**Shelly Pro3EM versus Wechselrichter-Werte.** Die Differenz zwischen Pro3EM und Wechselrichter ist nicht null. Das ist normal und liegt an Verkabelungspfaden und Messpositionen. Bis zu 2 oder 3 Prozent sind unauffällig, alles darüber lohnt einen Blick auf den Anschluss.
## Unterm Strich
Das, was sich erst nach einer Riesen-Frickelei anhört, ist im Kern eine **einzige Idee**: die Shellys liefern den Bezug zum Hausanschluss, die SolarFlow liefert die Proportionen, und die `home_em`-Schicht in der Mitte harmonisiert beides zu einem stimmigen Bild, das am Ende mit dem Stromzähler aufgeht.
Der eigentliche Gewinn liegt für mich in der **Erweiterbarkeit**. Wenn ich morgen ein zweites Balkonkraftwerk dazustelle, einen weiteren Großverbraucher mit eigenem Shelly versehe oder den SolarFlow durch ein anderes Modell ersetze, hänge ich die neue Quelle einfach an die `compute`-Function unten dran.
Der Rest läuft :sunglasses:.
---
Das Titel-/Hintergrundbild stammt von [Benjamin Jopen](https://unsplash.com/de/@benjopen?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) auf [Unsplash](https://unsplash.com/de/fotos/nahaufnahme-eines-solarmoduls-auf-einem-gebaude-2SfssudtyIA?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash).
---
# Was würdest du mit 1 Billion Euro machen?
> Elon Musk ist der erste Billionär der Welt. Was eine Billion Euro wirklich ist - mit Taschenrechner und Sarkasmus.
_Quelle: https://tbsch.de/post/2026-06-12-was-wurdest-du-mit-1-billion-euro-machen/ · Stand: 2026-06-12_
flowchart TD
sf["Zendure
SolarFlow"]
broker["MQTT-Broker"]
parse["parse telemetry"]
settopic["set topic"]
rewrite["rewrite topic"]
ha["Home Assistant"]
sf -->|"/+/+/properties/report
/+/+/properties/write/reply"| parse
sf -->|"/+/+/log
/+/+/event"| settopic
parse -->|"zendure/__id__/..."| broker
settopic -->|"zendure/__id__/status"| broker
broker --> ha
ha -->|"zendure/cmd/..."| rewrite
rewrite -->|"iot/..."| sf
## Function Node
Die wichtigste Function. Sie zerlegt `properties` und `packData` und setzt nebenbei den Online-Status:
```javascript
const topic_parts = msg.topic.split("/");
const device_id = topic_parts[2];
const is_write_reply = ["write", "reply"]
.every(part => topic_parts.includes(part));
let has_properties = false;
let has_pack_data = false;
if ("properties" in msg.payload && !is_write_reply) {
has_properties = true;
for (let key in msg.payload.properties) {
node.send([{
topic: `zendure/${device_id}/telemetry/${key}`,
payload: msg.payload.properties[key],
retain: true,
}, null, null]);
}
}
if ("packData" in msg.payload && Array.isArray(msg.payload.packData)) {
has_pack_data = true;
msg.payload.packData.forEach(pack => {
const sn = pack.sn;
delete pack.sn;
for (let prop in pack) {
node.send([{
topic: `zendure/${device_id}/batteries/${sn}/${prop}`,
payload: pack[prop],
retain: true,
}, null, null]);
}
});
}
if (!has_properties && !has_pack_data) node.send([null, msg, null]);
node.send([null, null, {
topic: `zendure/${device_id}/status`,
payload: "online",
retain: true,
}]);
```
Drei Outputs: **telemetry** (verbunden mit MQTT-Out), **others** (für Debug-Zwecke offen) und **status**. Die `write/reply`-Antworten werden bewusst nicht als Telemetrie ausgewertet, weil ihr Payload nur das wiedergibt, was ich selbst gerade geschrieben habe. Sie zählen aber als Lebenszeichen. Das Topic-Ziel enthält absichtlich nur die `device_id`, nicht die `product_id`, was die Sensor-Konfiguration in *Home Assistant* einfacher hält.
Die beiden anderen Functions sind Einzeiler. `set topic` setzt für `log` und `event` nur den Status `"online"` auf `zendure/doc = await paperless.documents(4711)
doc.title = "Ein neuer Titel"
await doc.save()
doc = await paperless.documents(4711)
doc.title = "Ein neuer Titel"
await paperless.save(doc)
filters = {
"title__icontains": "invoice"
}
async with paperless.documents.reduce(**filters):
async for doc in paperless.documents:
print(doc.title)
filters = {
"title__icontains": "invoice"
}
async with paperless.documents.filter(**filters) as ctx:
async for doc in ctx:
print(doc.title)
graph LR A[/Trigger/] --> B(Actions ausführen) --> C[Sensoren aktualisieren]Während also der `scene_history_wohnzimmer` Sensor aktualisiert wird, steht die `history` Variable bereits mit allen Werten für das Wohnzimmer zur Verfügung. Diese Tatsache müssen wir im Kopf behalten, wenn wir solche Sensoren bauen. Das gilt natürlich auch für die Sensoren aller weiteren Räume, die wir an unseren Trigger anheften. Und hier liegt ein kleines Detail versteckt: sobald die `history` Variable nach Auslösen des Triggers für alle Räume befüllt wird, kann sie nämlich die Sensordaten aller Räume aktualisieren - quasi in einem Durchlauf. Ja, so werden auch die Sensoren der Räume aktualisiert, in denen gerade keine Szene aktiviert wurde. Das werden wir aber später noch brauchen :grin:. ## Herausforderung Wir haben es im [ersten Part](/post/2024-09-16-aktivierung-von-home-assistant-szenen-verfolgen-part-1/) erfolgreich geschafft, dass der Name der zuletzt aktivierten Szene im *Scene-History*-Sensor gespeichert wird. Konkret stand der Sensor auf `Essen` und verhinderte somit, dass während des Essens die Wohnzimmer-Beleuchtung gedimmt wird, wenn jemand den Fernseher einschaltet. Doch was passiert nun, wenn das Licht zwischenzeitlich ausgeschaltet wird? Du ahnst es vermutlich schon: Nichts. Die letzte Szene `Essen` wird so lange beibehalten, bis eine andere Szene im Wohnzimmer aktiviert wird. Und hier endet die Geschichte auch schon. Dies wird in anderen Situationen dafür sorgen, dass Automatisierungen eine Szene vermuten und entsprechend reagieren, obwohl sie mittlerweile gar nicht mehr aktiv sein könnte. Um dieses Problem zu lösen, erweitern wir nun unseren *Scene-History*-Sensor um weitere Trigger und Attribute. ## Erweiterung des Sensors Wir fügen einen weiteren **Trigger**, ein neues **Attribut** und ein **Makro**[^1] hinzu. [^1]: Seit der Version **2023.4** unterstützt *Home Assistant* die Erstellung und Wiederverwendung eigener Makros, [siehe hier](https://www.home-assistant.io/blog/2023/04/05/release-20234/). ### Trigger Ich teile den Triggern nun auch *Trigger-IDs* zu, um innerhalb des Trigger-basierten Template Sensors prüfen zu können, durch welches Ereignis er eigentlich aktualisiert wird. Entitäten wie `light.wohnzimmer_lights` gibt es bei mir für jeden Raum, sie gruppieren sämtliche Lampen innerhalb eines Raumes. Das ermöglicht mir die extrem bequeme Abfrage des Beleuchtungsstatus, oder eben alle Lichter gleichzeitig auszuschalten. ```yaml - trigger: - platform: homeassistant event: start - platform: event event_type: call_service event_data: domain: scene service: turn_on id: scene_activated # das ist neu # und ein neuer Trigger - platform: state entity_id: - light.wohnzimmer_lights from: "on" to: "off" id: off_again # ... ``` ### Attribut Statten wir den *Scene-History*-Sensor im Wohnzimmer nun mit dem Attribut aus, welches in Zukunft die Information vorhält, ob das Licht nach dem Aktivieren einer Szene wieder ausgeschaltet wurde. ```yaml # ... - name: scene_history_wohnzimmer unique_id: sensor_scene_history_wohnzimmer state: > {{ state_attr(history.wohnzimmer[0], "name") }} attributes: current: "{{ history.wohnzimmer[0] }}" history: "{{ history.wohnzimmer[1:] }}" # das kommt dazu off_again: > {% from "scenes.jinja" import get_off_again %} {{ get_off_again("wohnzimmer", trigger) }} ``` Ich habe mich für den Namen `off_again` entschieden. Das Template des Attributs importiert ein **Makro** und führt es dann aus. Damit ich das Template der Funktion `get_off_again` auch für andere Räume benutzen kann, ohne es immer wieder kopieren zu müssen, habe ich es in eben jenem Makro ausgelagert. Das ist übrigens der übliche Use-Case für Makros, falls du dich schon mal gefragt hast, wozu die eigentlich gut sein sollen :grin:. An das **Makro** übergebe ich den gewünschten Raum und das gesamte `trigger` Objekt, welches von *Home Assistant* zur Verfügung gestellt wird. In diesem Objekt enthalten ist auch die Information, welcher Trigger ausgelöst wurde. > [!tip] > An dieser Stelle machen wir uns die Tatsache zu Nutze, dass beim Auslösen eines Triggers immer **State** und **Attribute** des Trigger-basierten Template Sensors aktualisiert werden. Das musst du dir stets im Hinterkopf behalten, denn auch unser **Makro** ist darauf ausgelegt. ### Makro Um eigene **Makros** zu verwenden, erstelle im `config`-Ordner deiner *Home Assistant* Installation den Unterordner `custom_templates`. Hier legst du deine *.jinja*-Dateien ab. Ich habe mich für den Dateinamen `scenes.jinja` entschieden, einfach weil ich es vorteilhaft finde, Informationen thematisch zu strukturieren. **Die Datei hat folgenden Inhalt:** ```jinja {% macro get_off_again(area, trigger) %} {# eine Szene wurde aktiviert #} {% if trigger.id == "scene_activated" %} {% set areas = trigger.event.data.service_data.entity_id | map("area_id") | list %} {% if area in areas %} {% set return = "no" %} {% endif %} {# Lampen wurden ausgeschaltet #} {% elif trigger.id == "off_again" %} {% set areas = [area_id(trigger.entity_id)] %} {% if area in areas %} {% set return = "yes" %} {% endif %} {% endif %} {# Nichts von beidem #} {% if return is not defined %} {% set return = state_attr("sensor.scene_history_" + area, "off_again") %} {% endif %} {{ return }} {% endmacro %} ``` #### Erläuterungen Das **Makro** wird mit zwei Parametern aufgerufen. Dies ist einerseits `area`, die in meinem Beispiel ja auf `wohnzimmer` gesetzt ist. Andererseits das `trigger` Objekt von *Home Assistant*. Wird **eine Szene aktiviert**, soll überprüft werden, ob die ausgelöste Szene im `wohnzimmer` ist. Falls ja, wird `off_again` auf *no* gesetzt. Dieselbe Logik wende ich an, wenn **Lampen ausgeschaltet** werden. Es wird überprüft, ob es sich um den gewünschten Raum handelt und falls ja, `off_again` auf *yes* gesetzt. Du hast richtig gemerkt: wir haben einen Schalter im klassischen Sinne gebaut, der entweder *an* oder *aus* ist. Macht bis hierher Sinn, oder? Für den Fall, dass weder die aktivierte Szene, noch die ausgeschaltete Lampe aus dem `wohnzimmer` sind, wird einfach der aktuell eingestellte Wert übernommen. Warum das notwendig ist? Ich habe dir weiter oben einen brennenden Tipp dazu gegeben :sunglasses:. Im letzten Schritt wird der Wert der Variable `return` nun zurückgegeben und in der Folge als `off_again` Attribut festgelegt. ## Abschlussarbeiten Der *Scene-History*-Sensor kann nun nicht nur erfolgreich die zuletzt aktivierte Szene anzeigen, sondern stellt darüber hinaus auch ein Attribut bereit mit der Information, ob das Licht seit Aktivierung einer Szene wieder abgeschaltet wurde. Damit kann die in [Part 1](/post/2024-09-16-aktivierung-von-home-assistant-szenen-verfolgen-part-1/) aufgebaute Automatisierung nicht nur feststellen, ob die Szene `Essen` im Wohnzimmer zuletzt aktiviert wurde, sondern auch, ob das Licht seither wieder ausgeschaltet worden ist. Sobald der Fernseher im Wohnzimmer eingeschaltet wird, überprüft die für das Dimmen verantwortliche Automatisierung in Zukunft nicht nur, ob der *Scene-History*-Sensor auf `Essen` steht, sondern auch, ob das Licht seither wieder abgeschaltet wurde. ```yaml - condition: not conditions: - condition: state entity_id: sensor.scene_history_wohnzimmer state: Essen - condition: state entity_id: sensor.scene_history_wohnzimmer attribute: off_again state: "yes" ``` Jetzt wird die Automatisierung wirklich zuverlässig laufen und das Licht nur im vorgesehenen Moment dimmen :grin:. Wenn du bessere Ideen zu einer möglichen Umsetzung hast, schreib mir gern. Mein Ziel war die Vermeidung der Erstellung von unzähligen *Helfern*. > [!note] > Der Trigger-basierte Template Sensor ist von mir so designt, dass einfach weitere Räume hinzugefügt werden können, ohne den ganzen Code immer wieder schreiben zu müssen. Das war auch notwendig, da ich die *Scene-History* in sechs verschiedenen Räumen verwende. In einem der nächsten Beiträge zu *Home Assistant* werde ich darüber berichten, wie ich mit Hilfe der [Local calendar](https://www.home-assistant.io/integrations/local_calendar/), [To-do list](https://www.home-assistant.io/integrations/todo/) und [Roborock](https://www.home-assistant.io/integrations/roborock/) Integrations einen interaktiven Reinigungsplan erstellt habe. --- Das Titel-/Hintergrundbild stammt von [Bilal Mansuri](https://unsplash.com/de/@itsbilalmn?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) auf [Unsplash](https://unsplash.com/de/fotos/ein-zimmer-mit-einer-couch-und-einem-tv-EE0DOgXztcY?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash). --- # Aktivierung von Home Assistant Szenen verfolgen - Part 1 > Die Aktivierung von Szenen laufend mit einem Sensor verfolgen. _Quelle: https://tbsch.de/post/2024-09-16-aktivierung-von-home-assistant-szenen-verfolgen-part-1/ · Stand: 2024-09-16_ Es gibt zahlreiche Smart Home Apps, die für nahezu jedes Gerät eine eigene Steuerung bereitstellen. In den meisten dieser Apps lassen sich mittlerweile individuelle Szenen erstellen, mit denen du für eine Gruppe von Geräten einen bestimmten Zustand festlegen kannst, den sie bei Aktivierung automatisch einnehmen. Szenen sind für viele Menschen vermutlich unnötiger Ballast, weil sie damit nichts anfangen können oder wollen. Und dann gibt es da zum Beispiel mich, der sie insbesondere für die heimische Beleuchtung exzessiv einsetzt. Wenn es um Licht geht, gibt es aus meiner Sicht keinen Weg an [Philips Hue](https://www.philips-hue.com/de-de) vorbei: 1. Die Leuchtmittel sind hochwertig. Ich habe seit über 7 Jahren noch Glühbirnen aus der ersten Generation im Einsatz. 2. Die Steuerung in *Home Assistant*[^1] funktioniert perfekt. 3. Philips Hue funkt mit ZigBee. 4. Kein Cloudzwang[^2], alles wird lokal gesteuert. 5. Verwendet man die originale [Hue Bridge](https://amzlink.to/az0UY0AmCc2cn "!sponsored") vom Hersteller, kann man die Philips Hue App benutzen und hat hierüber Zugriff auf die Lichtszenen-Galerie. Diese Szenen sehen einfach nur klasse aus. [^1]: *Home Assistant* ist eine Open-Source-Plattform zur zentralen Steuerung und Automatisierung von Smart Home-Geräten. Sie ermöglicht benutzerdefinierte Automatisierungen und unterstützt zahlreiche Geräte, ohne auf Cloud-Dienste angewiesen zu sein. [^2]: Viele Hersteller von Smart Home Geräten setzen auf ihre eigenen Cloud Services. Man kann nur mutmaßen, warum eine Internetverbindung für ihren Betrieb benötigt wird; Fakt ist jedoch, dass einige Geräte ohne funktionierende Internetverbindung ihren Dienst verweigern (bspw. die Roborock Saugroboter von Xiaomi). Darum ist Cloudzwang keine gute Sache. > [!note] > Philips Hue erfüllt sämtliche Voraussetzungen für ein Markenprodukt, neben dem guten Namen haben die Leuchtmittel nämlich auch einen stolzen Preis. Im Ausgleich hierfür machen sie jedoch verglichen zu anderen Herstellern sehr viel richtig. ## Home Assistant Szenen Wenn du zur Steuerung deines smarten Zuhauses *Home Assistant* einsetzt, ist dir sicher bereits aufgefallen, dass du hier ebenfalls Szenen kreieren kannst. Falls du das noch nicht entdeckt hast, kannst du über den folgenden Link direkt zu den Einstellungen gelangen :sunglasses:. Das Tolle an Szenen ist, dass du dir beliebig viele Snapshots deiner Geräte erstellen und immer wieder aufrufen kannst. Der Nachteil von Szenen ist, dass sie stateless sind, also keinen eigenen Zustand besitzen. Es ist zum Beispiel nicht möglich zu überprüfen, welche Szene gerade in einem Moment ausgeführt wird, um beispielsweise in Abhängigkeit dessen in bestimmten Situationen anders reagieren zu können. Es wird Zeit für ein praxisnahes Beispiel. ### Ausflug ins Wohnzimmer In unserem Wohnzimmer wird zur Abenddämmerung das Licht automatisch eingeschaltet. In kalten Jahreszeiten nehmen die Lampen darüber hinaus warme Farbtöne an, um einen Ausgleich zum rauen Wetter zu schaffen. Lampen können so viel mehr als nur Licht erzeugen: sie nehmen positiv Einfluss auf unsere Stimmung. Schalten wir nun unseren Fernseher ein, wird das Licht gedimmt, damit eine noch gemütlichere Atmosphäre entsteht. Für all diese Umstände habe ich unterschiedliche Szenen gebaut, welche darüber hinaus täglich wechseln. Das klingt bisher recht unkompliziert, oder? Wer regelmäßig an Smart Home Automatisierungen bastelt, wird mir jedoch sofort zustimmen, wenn ich behaupte: alltägliche Abläufe sind alles andere als einfach. Sie können extrem komplex sein. Vor allem, wenn Automatisierungen den Bedürfnissen von mehreren Menschen gleichzeitig gerecht werden sollen (Stichwort: *Women Acceptance Factor*[^3]). Du wirst dieses Wort in meinen Smart Home Beiträgen noch öfter lesen. [^3]: *Women Acceptance Factor* (WAF) beschreibt scherzhaft den Grad, in dem technische Lösungen, insbesondere im Smart Home-Bereich, von Frauen akzeptiert werden. Er wird oft genutzt, um auszudrücken, wie gut eine technische Implementierung im Alltag ankommt und ob sie praktisch, einfach und ästhetisch ansprechend genug ist. Essen wir beispielsweise zu Abend, soll das Wohnzimmer (welches auch unseren Esstisch beherbergt) hell erleuchtet werden, also die Szene **Essen** aktiviert werden. Und nun kann es vorkommen, dass der Fernseher eingeschaltet wird. Du hast ja bereits gelesen, was in unserem Fall dann passiert: Das Licht wird gedimmt, und wir essen plötzlich im Dunkeln. Es erfordert im weiteren Verlauf eine manuelle Kurskorrektur durch uns, und das kann nerven: vor allem meine Frau. Und dann dauert es nicht mehr lange, bis ich genervt bin; du verstehst sicher. Mein Anspruch an Heimautomatisierung ist, dass sie intelligent genug agiert, um manuelle Interventionen überflüssig zu machen. Das steigert außerdem den WAF enorm - happy wife, happy life :smile:. ### Automatisierungen zur Intelligenz verhelfen Smart Home Automatisierungen machen dein Zuhause smarter, aber es ist wichtig, realistisch zu bleiben: sie sind nicht wirklich intelligent. Sie lösen unter bestimmten Umständen einfach nur eine Aktion aus, selbst wenn es lediglich bedeutet, jeden Tag um 18 Uhr das Licht einzuschalten – das macht häufig gar keinen Sinn. Und während der Essenszeit das Licht zu dimmen, nur weil der Fernseher eingeschaltet wird, ist eben weder smart noch intelligent. Das ist ehrlich gesagt ziemlich dumm. Also müssen wir hier ein bisschen nachhelfen. Wie eingangs erwähnt, sind Szenen stateless: Du weißt also weder, ob eine Szene gegenwärtig aktiv ist, noch um welche es sich dabei handelt. Für mich ist es auch keine Option, alle Lampen einzeln auf bestimmte Farben oder Helligkeit zu überprüfen und zu schauen, ob die Parameter zu irgendeiner Szene passen. Denn meine Szenen sind kunterbunt, verwenden alle möglichen Helligkeitsstufen und sind daher schlichtweg nicht oder eben nur mit höllischem Aufwand nachverfolgbar. Wie schön wäre es, wenn *Home Assistant* eine Funktion mitbringen würde, die uns hier unterstützen könnte? Normalerweise leite ich mit so einem Satz nun die zündende Lösung ein, in diesem Fall gibt es aber wirklich nichts. Zumindest fast nichts. Es gibt **Trigger-basierte Template Sensors**[^4]! Die lösen zwar das eigentliche Problem nicht, jedoch lässt sich mit ihnen etwas bauen, was grundsätzlich intelligentere Entscheidungen möglich machen kann. [^4]: Schau doch einmal in die *Home Assistant* [Dokumentation](https://www.home-assistant.io/integrations/template/) rein, denn die Template Sensors werden hier sehr gut erklärt. ## Mein Lösungsweg Nachdem wir nun die Herausforderung erörtert haben, möchte ich meine Lösung vorstellen. Hast du ein ähnliches Problem selbst lösen wollen und einen anderen Weg eingeschlagen? Lass es mich wissen, ich freue mich von dir zu hören! > [!warning] > Die folgenden Passagen setzen voraus, dass du dich zumindest etwas mit *Home Assistant* Automatisierungen und Templates auskennst. Ohne Basiswissen werden sie nur schwer zu greifen sein. Ich versuche natürlich trotzdem, alles etwas zu veranschaulichen und die Thematik verständlicher zu gestalten. Dennoch ist das eine komplexe Sache. ### Beobachtungen Um eine Herausforderung lösen zu können, müssen wir zunächst beobachten, wie das System in bestimmten Situationen reagiert. Fast alle Automatisierungen schalten bei mir keine Lampen direkt ein. Sie aktivieren Szenen, in denen die eigentlichen Beleuchtungseinstellungen definiert sind. Also musste ich herausfinden, was in *Home Assistant* passiert, wenn eine Szene aktiviert wird. Hierbei habe ich beobachtet, dass die Szenen-Entität selbst das Aktivierungsdatum als Zustand einnimmt. Das ist hilfreich, denn mit Zustandsänderungen können wiederum Aktionen ausgelöst werden :grin:. Ich habe allerdings sehr viele Szenen gebaut und müsste jede davon als Trigger-Bedingung verwenden. Würdest du nach dem Erstellen jeder neuen Szene daran denken, deinen Trigger anzupassen? Sehr gut! Ich jedoch leider nicht, daher ist das für mich kein gangbarer Weg. Und auf sämtliche Zustandsänderungen zu triggern, nur um dann überprüfen zu können, ob es sich dabei um eine Szene handelt, ist für mich ebenso keine gescheite Lösung. Zu viel Overhead. Statt aktiv auf die Aktivierung einer Szene zu warten, habe ich mir in der Folge angesehen, wie eine Szene denn eigentlich aktiviert wird: über die Aktion `scene.turn_on`. Und genau hier habe ich angesetzt, indem ich sämtliche Aufrufe dieser Aktion mitverfolge. ### Trigger-basierter Template Sensor Ausgestattet mit diesem Wissen habe ich einen Trigger implementiert, der je einen *Scene-History*[^5]-Sensor im Arbeits- und Wohnzimmer updaten soll. Das passiert im Detail, wenn eine Szene aktiviert wird: [^5]: Mir ist kein besserer Name als *Scene-History*-Sensor eingefallen. Schlimmer noch ist die deutsche Übersetzung im *Home Assistant* UI: Szenenhistorie :nauseated_face:. Wenn du wohlklingendere Ideen hast, freue ich mich über deinen Hinweis. 1. Die Aktion `scene.turn_on` wird verwendet 2. Die Variable `history` wird deklariert 3. Für Arbeits- und Wohnzimmer werden alle Szenen... 1. abgefragt 2. nach Last-Changed-Datum absteigend sortiert 4. Die jeweiligen Zustände der Sensoren auf den Namen der Szene gesetzt 5. Die Attribute `current` und `history` befüllt ```yaml - trigger: - platform: homeassistant event: start - platform: event event_type: call_service event_data: domain: scene service: turn_on action: - variables: history: > {% set areas = [ "arbeitszimmer", "wohnzimmer" ] %} {% set data = namespace(scenes=[]) %} {% for area in areas %} {% set scenes = area_entities(area) | select("search", "^scene.") | expand | sort(attribute="last_changed", reverse=true) | map(attribute="entity_id") | list %} {% set data.scenes = data.scenes + [scenes] %} {% endfor %} {{ dict(zip(areas, data.scenes)) }} sensor: - name: scene_history_arbeitszimmer unique_id: sensor_scene_history_arbeitszimmer state: > {{ state_attr(history.arbeitszimmer[0], "name") }} attributes: current: "{{ history.arbeitszimmer[0] }}" history: "{{ history.arbeitszimmer[1:] }}" - name: scene_history_wohnzimmer unique_id: sensor_scene_history_wohnzimmer state: > {{ state_attr(history.wohnzimmer[0], "name") }} attributes: current: "{{ history.wohnzimmer[0] }}" history: "{{ history.wohnzimmer[1:] }}" ``` Ist ganz schön viel Template-Code geworden. Der Vorteil hierbei ist jedoch, dass nun mit vergleichsweise wenigen Zeilen weitere Räume hinzugefügt werden können. Man muss sich einfach nur alles schönreden können :grin:. #### Erläuterungen Der Teil, der die `history` Variable mit Daten füllt, ist vermutlich am schwierigsten zu verstehen. Falls du nicht so tief in den technischen Details steckst, keine Sorge: wir gehen das jetzt Zeile für Zeile noch einmal durch. ```jinja {% set areas = [ "arbeitszimmer", "wohnzimmer" ] %} {% set data = namespace(scenes=[]) %} ``` An dieser Stelle werden die Räume (Areas) definiert, für die ich die Szenen abrufen möchte. Danach wird noch ein [Namespace](https://jinja.palletsprojects.com/en/stable/templates/#jinja-globals.namespace) für den darauffolgenden Code reserviert. ```jinja {% for area in areas %} ``` Für alle Räume wiederholen... ```jinja {% set scenes = area_entities(area) | select("search", "^scene.") | expand | sort(attribute="last_changed", reverse=true) | map(attribute="entity_id") | list %} ``` In diesem Befehl liegt die eigentliche Magie. Die Funktion `area_entities` gibt dir alle Entitäten zurück, die einem bestimmten Raum zugeordnet sind. Diese Liste kann sehr lang werden, weswegen wir mit `select` nur nach Entitäten suchen, die eine Szene verkörpern. Mit Hilfe von `expand` werden die Entitäten-Objekte quasi ausgepackt, wir erhalten dadurch Zugriff auf ihre einzelnen Attribute, um mit `sort` absteigend nach den letzten Änderungen zu sortieren. Da ich mich dazu entschieden habe, in den Sensoren nur die Entitäten-IDs zu speichern, verwerfen wir alle anderen Attribute mit dem `map` Befehl. Schlussendlich speichern wir mit `list` alle Ergebnisse als Liste. ```jinja {% set data.scenes = data.scenes + [scenes] %} ``` Speichert die lokale `scenes` Variable in unserem Namespace. ```jinja {% endfor %} {{ dict(zip(areas, data.scenes)) }} ``` Die Schleife ist an dieser Stelle durchgelaufen, wir verpacken die Ergebnisse nun in einem Dictionary, welches zugleich den Inhalt der `history` Variable bildet. #### Ergebnis Hier ist ein Beispiel, wie die fertig modellierten Daten meines Sensors für das Wohnzimmer am Ende aussehen: ```yaml state: Essen attributes: current: scene.wohnzimmer_essen history: - scene.wohnzimmer_tv_2 - scene.wohnzimmer_abend_4 - scene.wohnzimmer_hell - scene.wohnzimmer_abend_2 - scene.wohnzimmer_abend_3 - scene.wohnzimmer_gedimmt - scene.wohnzimmer_tv_1 - scene.wohnzimmer_konzentrieren - ... ``` > [!tip] > Diese Umsetzung muss man erst einmal sacken lassen. Darum möchte ich dir zum Verschnaufen kurz den Hinweis geben, dass *Home Assistant* mit einem tollen Tool ausgeliefert wird: den Entwicklerwerkzeugen. Gerade beim Bauen von Templates empfiehlt es sich, diese Werkzeuge rege zu nutzen; hier kann alles ausprobiert werden. Auch mein obiger Code :smile:. Natürlich musst du ihn an deine Bedingungen anpassen, wie Raumnamen. > > ## Abschlussarbeiten Nachdem wir die Template-Entitäten in den Einstellungen neu geladen haben, stehen die beiden *Scene-History*-Sensoren direkt bereit und werden getriggert, sobald Szenen in den jeweiligen Räumen aktiviert werden. Prima! Sobald der Fernseher im Wohnzimmer eingeschaltet wird, überprüft die für das Dimmen verantwortliche Automatisierung in Zukunft, ob die *Scene-History* auf `Essen` steht. Und wenn ja, wird das Licht einfach nicht gedimmt :sunglasses:. ```yaml - condition: not conditions: - condition: state entity_id: sensor.scene_history_wohnzimmer state: Essen ``` Dann kann ja in Zukunft der Haussegen nicht mehr schief hängen! Oder? Doch was, wenn das Licht ausgeschaltet wird? Bleibt die *Scene-History* dann auf `Essen` stehen und sorgt das nicht wiederum in der Folge für weiteren Ärger? Im nächsten Part zeige ich dir, wie du deinen neuen Trigger-basierten Template Sensor dazu bringst, auch auf ausgeschaltete Lampen zu reagieren. --- Das Titel-/Hintergrundbild stammt von [Bilal Mansuri](https://unsplash.com/de/@itsbilalmn?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) auf [Unsplash](https://unsplash.com/de/fotos/ein-zimmer-mit-einer-couch-und-einem-tv-EE0DOgXztcY?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash). --- # Paperless Guide: Customization > Das papierlose Büro mit Scripting monitoren und verbessern. _Quelle: https://tbsch.de/post/2024-08-28-paperless-guide-customization/ · Stand: 2024-08-28_ Nachdem ich meine grundlegende Arbeitsweise mit *Paperless-ngx* nun detailliert vorgestellt habe, möchte ich noch etwas Content für die richtigen Nerds unter uns abliefern. Auf der Seite über [Klassifizierungen](/post/2024-02-11-paperless-guide-classification/) habe ich ein paar Mal von einer **Middleware** gesprochen. Eigentlich trifft es das Wort gar nicht richtig, da es in *Paperless-ngx* derzeit keine Möglichkeiten gibt, eigene Programmabläufe während der Laufzeit des Systems einzubringen. Und komm mir jetzt nicht mit den neuen Workflows... das Feature hätte Potenzial, wenn... ach... lassen wir das fürs Erste. ## Dunkelverarbeitung Na, kennst du das Wort? Gerade im Büro einer größeren Firma werden wir häufig mit diesem Begriff konfrontiert. Es bezeichnet die automatisierte Erledigung wiederkehrender Aufgaben, ohne dass ein Mensch darauf Einfluss nehmen muss. In deinem papierlosen Büro wirst du dich sicher auch immer wieder selbst dabei ertappen, die immer und immer selben Tätigkeiten zu erledigen. Haha, habe ich dich ertappt :sunglasses:? Eigentlich bringt *Paperless-ngx* schon viele tolle Möglichkeiten mit, wie zum Beispiel die Auto-Klassifizierung. Wenn es jedoch an die Details geht, müssen wir immer wieder selbst nacharbeiten. ## Ein Beispiel Wenn ich meine allmonatlichen Kontoauszüge an *Paperless-ngx* übergebe, sollen sie immer eine Kombination aus Jahr und Monat im Titel tragen: `2024/03`. Und zwar nicht des Monats, in dem das Dokument erstellt oder vom *Document Consumer* konsumiert wurde, sondern für den Monat, für den der Kontoauszug schlichtweg ist. Ganz einfach. Das System generiert keine passenden Titel für meine Kontoauszüge, und manuelle Anpassungen sind oft umständlich, besonders wenn Dokumente über das Share Sheet vom iPhone hochgeladen werden. Und mal im Ernst: möchtest du bei jedem Dokument wirklich noch darüber nachdenken, den Titel anzupassen? Versuche es ruhig. Das klappt sicher anfangs noch gut :grin:. Auf der Zeitachse führt es zu Inkonsistenzen in der Benennung deiner Dokumente. ## Paperless Secretary Aus diesem und anderen Gründen erstellte ich für *Paperless-ngx* ein Python-Modul namens **pypaperless**, welches die API anspricht und einem Entwickler die Möglichkeit gibt, Einfluss auf sein DMS zu nehmen. Dieses Modul bildet die Grundlage für mein Projekt **Paperless Secretary**, also einen virtuellen Sekretär, der wiederkehrende Aufgaben für mich erledigt. Meine Aufgabe besteht darin, diese wiederkehrenden Aufgaben zu identifizieren und entsprechende Bearbeitungsregeln zu erstellen. > [!warning] > **Bitte beachte,** dass ich den Secretary zur Zeit nicht öffentlich zur Verfügung stellen kann. Es handelt sich hierbei um eine experimentelle Software in Python, die insbesondere meinen Bedarf abdeckt und bei dir möglicherweise gar nicht funktioniert, schlimmstenfalls deine Daten in *Paperless-ngx* durcheinanderbringen könnte. ### Regel für Kontoauszüge Kommen wir auf mein oben genanntes Beispiel zurück, welches ich wie folgt gelöst habe: ```python """N26; Kontoauszüge; Titel bearbeiten.""" import re from pypaperless.models import Document from .base import Rule from .const import CORRESPONDENT_N26, DOCUMENT_TYPE_KONTOAUSZUG # 1) _rule = Rule("N26; Kontoauszüge; Titel => yyyy/MM (Sparkonto)") # 2) @_rule.condition(name="correspondent == N26") async def check_correspondent(item: Document) -> bool: return item.correspondent == CORRESPONDENT_N26 # 3) @_rule.condition(name="document_type == Kontoauszug") async def check_document_type(item: Document) -> bool: return item.document_type == DOCUMENT_TYPE_KONTOAUSZUG # 4) @_rule.condition(name="title !~ yyyy/MM") async def check_document(item: Document) -> bool: content = item.content or "" if "Sparkonto" in content: pattern = r"^\d{4}/(0[1-9]|1[0-2])\sSparkonto*$" else: pattern = r"^\d{4}/(0[1-9]|1[0-2])*$" return not bool(re.match(pattern, item.title)) # 5) @_rule.action(name="Apply new document title.") async def apply_title(*, rule: Rule, item: Document) -> bool: content = item.content or "" match = re.search(r"\d{2}\.(\d{2})\.(\d{4}) bis \d{2}\.\1\.\2", content) if match: year = match.group(2) month = match.group(1) item.title = f"{year}/{month}" if "Sparkonto" in content: item.title += " Sparkonto" # 6) if not rule.dry_run: return await item.update() return False ``` Keine Sorge, sieht schlimmer aus, als es wirklich ist :smile:. Ich habe ein paar Stellen im Code nummeriert und gehe nun einmal kurz darauf ein. 1. Es wird eine neue `Rule` erstellt. Hierbei handelt es sich um eine Klasse, die zum Core des Secretary gehört. Der Secretary verarbeitet solche Regeln dann, indem er sie auf Dokumente in *Paperless-ngx* anwendet. 2. Der Regel wird eine `Condition` angeheftet. Das bedeutet, dass die Regel nur ausgeführt wird, wenn die Bedingung erfüllt wird. Konkret prüft sie also, ob das Dokument von meiner Bank ist. 3. Wieder eine `Condition`. Diesmal wird überprüft, ob das Dokument ein Kontoauszug ist. 4. Die letzte `Condition` überprüft, ob der Kontoauszug einen Titel in dem Format `yyyy/MM` oder `yyyy/MM Sparkonto` hat, so wie ich mir das wünsche. Besser gesagt: ob genau dieser Fall hier nicht zutrifft. 5. Jetzt wird die `Action` implementiert. Das ist die Aktion, die ausgeführt wird, wenn alle Bedingungen zutreffen. Im Dokument-Inhalt ist bei meiner Bank ein Hinweis enthalten, ob der Kontoauszug für mein Girokonto oder Tagesgeldkonto ist. Deswegen überprüft die Aktion das kurz und bildet dann aus dem Inhalt des Dokuments den korrekten Titel. 6. Wenn der Secretary gerade nicht im Testmodus ausgeführt wird, wird der neue Titel dann letztendlich in *Paperless-ngx* festgelegt. Bei der Klasse `Document` handelt es sich um ein Objekt von **pypaperless**. Dieses Objekt stellt nicht nur sämtliche Daten aus *Paperless-ngx* bereit, sondern auch Funktionen zum Ändern und Löschen von eben solchen Daten. ## Weitere Use-Cases Es gibt zahlreiche Anwendungsmöglichkeiten für diese Regeln. In meinem Fall haben sich folgende Use-Cases herauskristallisiert: 1. Titel von Dokumenten anpassen 2. Custom Fields von Dokumenten automatisch befüllen 3. Reminder für Wiedervorlagen/Todos 4. Verschiedene Validierungen Ich bin gespannt, welche Herausforderungen du mit diesen Regeln angehen würdest. Teile mir gerne deine Ideen mit! --- Das Titel-/Hintergrundbild stammt von [Chris Ried](https://unsplash.com/de/@cdr6934?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) auf [Unsplash](https://unsplash.com/de/fotos/ein-computerbildschirm-mit-einem-haufen-code-darauf-ieic5Tq8YMk?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash). --- # Eigener Strom mit Balkonkraftwerk > Selbst Strom erzeugen und dabei Geld sparen? Ein kleiner Einblick. _Quelle: https://tbsch.de/post/2024-08-20-eigener-strom-mit-balkonkraftwerk/ · Stand: 2024-08-20_
graph TD
A(Korrespondent) -->|Absender| B
B{{Sendung mit Dokumenten}} -->|Empfänger| A
In *Paperless-ngx* können wir uns Korrespondenten als virtuelle Schubladen vorstellen, in denen alle Dokumente einer Person oder Firma abgelegt sind. Mit nur einem Klick finden wir alle Dokumente, die einem bestimmten Korrespondenten zugeordnet sind.
### Dokumenttyp
Mit dem Dokumenttyp legst du fest, um was für eine Art Dokument es sich handelt. Hier kann man beliebig granular werden, doch Simplizität schlägt aus meiner Sicht Komplexität. Die Klassifizierung von Dokumenten zu einem Dokumenttyp erfolgt nach Regeln, die wir uns selbst ausdenken und auferlegen müssen. Es sollte uns also auf einen Blick klar sein, was für ein Dokument vor uns liegt und welcher Dokumenttyp das sein könnte.
> [!warning]
> Ertappst du dich selbst dabei, wie du immer wieder zwischen mehreren Dokumenttypen schwankst, gebe ich dir einen guten Rat: konsolidiere sie. Wenn selbst du dir als Mensch unsicher bist, wie soll dann *Paperless-ngx* den korrekten Dokumenttyp automatisiert für dich erkennen?
Sehen wir uns alle Dokumente an, die wir im Laufe der Zeit erhalten, kristallisieren sich ein paar Dokumenttypen deutlich heraus.
**In meinem Fall sind das diese hier:**
| Dokumenttyp | Beschreibung |
| :---------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Bescheid | Bescheide von Behörden, z.B. Steuerbescheide, Bewilligungen, etc. |
| Dokument | Abstrakter Typ für Dokumente, die keinem Typ eindeutig zuordenbar sind. |
| Kontoauszug | Der Name ist Programm, egal ob Girokonto, ETF-Sparplan, Depot. |
| Korrespondenz | Allgemeine Schriftwechsel, auf die eine Antwort folgen soll, z.B. Beschwerde beim Internetanbieter. |
| Kündigung | Kündigungen und Kündigungsbestätigungen von Verträgen aller Art. |
| Lohnabrechnung | Hiermit werden alle monatlichen Lohnzettel klassifiziert. |
| LSt-Bescheinigung | Lohnsteuerbescheinigungen kommen in der Regel 1x im Jahr, sind aber wichtig genug für einen eigenen Typ. |
| NK-Abrechnung | Lange habe ich Nebenkostenabrechnungen einfach als *Rechnung* klassifiziert, zur schnelleren Auffindbarkeit habe ich aber dann einen eigenen Dokumenttyp erstellt. |
| Rechnung | Enthält alle Rechnungen, z.B. vom Internetanbieter, Mobilfunk, Käufe mit Garantie, etc. |
| Spendennachweis | Ich spende jedes Jahr, die Quittungen erhalten diesen Dokumenttyp. |
| Spesenabrechnung | Beruflich bin ich viel unterwegs und dieser Dokumenttyp enthält die Abrechnungen gegenüber meiner Arbeitgeberin, wie auch ihre Antworten darauf. |
| SV-Mitteilung | Bestimmt für Sozialversicherungsmitteilungen, An- und Abmeldungen. |
| Vertrag | Komplexer Dokumenttyp, enthält Verträge aller Art. Auch Versicherungspolicen und Vertragsänderungsmitteilungen. |
| Zeitnachweis | Lohnabrechnungen und Zeitnachweise klassifiziere ich getrennt voneinander. Enthält auch Zeitnachweise für Freelancer-Tätigkeiten. |
| Zertifikat | Nachweise über Schulungen, Weiterbildungen und Seminare. |
| Zeugnis | Dokumenttyp für Schul-, Ausbildungs- und Arbeitszeugnisse. |
### Tagging
Die einen lieben sie, die anderen hassen sie, und die übrigen 80% nutzen sie nicht: Tags. Auch ich selbst habe mich lange vor dem Einsatz von Tags gesträubt, weil es Dingen eine Komplexität verleihen kann, die schnell unüberschaubar ist. Denn zu jedem Tag gibt es eine Story: wann nutze ich ihn? Wann muss er wieder entfernt werden? Was sagt er aus? Hier sind uns überhaupt keine Grenzen gesetzt.
Tagging ist prima: bei *Paperless-ngx* kann man einem Dokument damit einen Kontext verleihen, oder sogar mehrere. Ähnlich wie bei Korrespondenten kann man Tags wie virtuelle Schubladen verstehen, in denen Dokumente liegen. Sie eignen sich erstklassig, um Dokumenten eine Thematik zuzuweisen, die über viele Korrespondenten und Dokumenttypen verteilt ist (z.B. Kfz) - oder einfach nur als Statusindikator.

Damit es jedoch nicht zu komplex wird, beschränke ich mich auf einige wenige, dafür eindeutige Tags.
| Tag | Beschreibung |
| :------ | :------------------------------------------------------------------------------------------------------------------------------------------------ |
| Ausgang | Das Dokument ist ein Ausgangsdokument, wurde also von mir initiiert. |
| Kfz | Verträgen, Rechnungen, etc. verleihe ich mit diesem Tag einen Kfz-Kontext. So kann ich schnell sämtliche Unterlagen zu meinen Autos wiederfinden. |
| Neu | Hierbei handelt es sich um meinen Posteingangs-Tag. Alle Dokumente mit diesem Tag haben den Status **neu**. |
| Problem | Dieser Tag wird von meiner Middleware verwendet, wenn ich an einem Dokument noch etwas nacharbeiten muss. Darauf gehe ich später detailliert ein. |
| Steuer | Dokumente mit diesem Tag werden von mir für die nächste Steuererklärung benötigt. |
| Todo | Das sind Dokumente, mit denen ich zu einem späteren Zeitpunkt noch etwas vorhabe, oder bei denen ich noch auf eine Antwort warte. |
| Wichtig | Extrem wichtige Dokumente, die ich schnell wiederfinden muss, wenn ich sie brauche. |
### Speicherpfade
*Paperless-ngx* bietet noch eine weitere Möglichkeit, Schubladen zu erschaffen. Nicht dass wir davon nicht schon bereits einige hätten :sweat:. In diesem Fall handelt es sich um echte Ordner im Dateisystem, in denen die echten PDF-Dateien dann abgelegt werden, die du im DMS abspeicherst. Das ist natürlich ein reines Kosmetik-Feature und bietet Vorteile für Backups, oder wenn man all seine Dokumente bspw. zusätzlich bei einem Cloud-Anbieter speichern möchte.

Ich nutze zwar die physischen Dateiordner nicht, Speicherpfade stellen für mich allerdings ein zusätzliches Klassifizierungsmerkmal dar. So existiert nicht nur für jedes meiner Autos ein echter Dateiordner mit allen Dokumenten drin, meine Middleware weiß hierdurch sogar, um welches Auto es sich handelt. So spare ich mir für jedes Auto einen eigenen Tag anzulegen, denn viele Speicherpfade sind aus meiner Sicht weniger störend als viele Tags.
### Middleware
Ich habe nun mehrmals eine Middleware erwähnt. Hierbei handelt es sich um eine selbst entwickelte Python-App, die wiederkehrende Monitoring-Aufgaben wahrnimmt. Die App baut auf diesem Python Modul auf, welches ich auch entwickelt habe:
Wenn du ein bisschen mehr darüber erfahren möchtest, solltest du [hier](/post/2024-08-28-paperless-guide-customization/) weiterlesen :smile:.
## Erkennungs-Algorithmen
Die Klassifizierung ist der Prozess, bei dem man Dokumenten jeweils Korrespondenten, Dokumenttyp, Tags und ggf. einen Speicherpfad zuweist. Das muss man glücklicherweise nicht jedes Mal komplett per Hand machen, *Paperless-ngx* wird mit einigen Erkennungs-Algorithmen ausgeliefert. Darüber kannst du [hier](https://docs.paperless-ngx.com/advanced_usage/#matching) mehr lesen. Falls die automatische Erkennung nicht zum gewünschten Ergebnis führt, kann man natürlich selbst den korrekten Wert einstellen. Im Falle der Auto-Erkennung lernt *Paperless-ngx* dann von deiner Entscheidung und macht es mit ein bisschen Glück direkt beim nächsten Mal richtig.

### Keine
Die Verwendung von Erkennungs-Algorithmen wird deaktiviert.
### Irgendein Wort, Alle
Der Inhalt eines Dokuments wird nach den Wörtern im Zuweisungsmuster durchsucht. Legst du das Zuweisungsmuster auf `Rechnung` fest, werden alle Dokumente entsprechend klassifiziert, wenn sie das Wort *Rechnung* enthalten.
Beim Zuweisungsmuster `Rechnung Baumarkt Köln` trifft der Erkennungs-Algorithmus **Irgendein Wort**, sobald eines der drei Wörter im Dokument vorkommt. **Alle** trifft nur dann, wenn alle Wörter erkannt werden konnten. Du hast richtig erkannt: mehrere Wörter werden einfach durch ein Leerzeichen voneinander getrennt.
Es ist aber auch möglich, nach Wörtern mit enthaltenen Leerzeichen zu suchen. Das Prinzip ist fast dasselbe wie mit einzelnen Wörtern: `Rechnung "Baumarkt Köln"`. **Irgendein Wort** trifft nur, wenn die Wörter *Rechnung* oder (Achtung!) *Baumarkt Köln* im Dokument vorkommen. Du kennst das von gängigen Suchmaschinen, denn dort ist es genauso. **Alle** trifft wieder nur, wenn beide Wörter vorkommen.
Die Reihenfolge der Wörter im Dokument spielt übrigens keine Rolle, wichtig ist nur, *dass* sie vorkommen.
### Exakt
Dieser Erkennungs-Algorithmus verhält sich ähnlich wie **Irgendein Wort** und **Alle**, nur dass hier nicht im Kontext von Wörtern, sondern Zeichenketten gesucht wird.
Das Zuweisungsmuster `Rechnung Baumarkt Köln` setzt also dann voraus, dass im Dokument auch tatsächlich *Rechnung Baumarkt Köln* am Stück ausgeschrieben wurde, damit **Exakt** treffen kann.
### Regulärer Ausdruck (Regex)
Liebhaber von regulären Ausdrücken werden auf ihre Kosten kommen, auch ich bin ein großer Freund davon. Aufgrund der Komplexität von Regex empfehle ich die Verwendung allerdings nur, wenn du wirklich weißt, was du da machst. Es gibt leider extrem beschränkte *Debugging Funktionen* dafür in *Paperless-ngx*, neben der Logdatei gibt es nämlich keine. Im schlimmsten Fall bekommst du niemals mit, dass dein Zuweisungsmuster also gar nicht funktioniert.
Möchtest du Regex besser kennenlernen, empfiehlt sich die offizielle [Python Dokumentation](https://docs.python.org/3/library/re.html).
### Ungenau (Fuzzy)
Ich bin mir nicht sicher, was ich hierüber schreiben soll. *Fuzzy Match* war bislang niemals die Lösung einer meiner Use-Cases, aber vielleicht underrate ich den Erkennungs-Algorithmus ja auch einfach bodenlos. Ich werde mir vornehmen, mehr damit zu arbeiten und zu sehen, ob und wie er lebensverändernd sein könnte.
### Auto
*Paperless-ngx* erlernt die Zuweisung mit der Zeit automatisch. Ich nutze die **Auto-Erkennung**, wann immer ich bei einem Klassifizierungsmerkmal genügend Dokumente erwarte; ein Model muss schließlich mit genug Daten trainiert werden.
Die automatische Zuweisung funktionierte bereits mit der Vorgänger-Software recht zuverlässig und ich bin sehr zufrieden damit.
---
Das Titel-/Hintergrundbild stammt von [blocks](https://unsplash.com/de/@blocks?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) auf [Unsplash](https://unsplash.com/de/fotos/tilt-shift-objektivfotografie-eines-schwarzen-laptop-computers-TkEPQPWr2sY?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash).
---
# Paperless Guide: Setup
> Zeit für den digitalen Wandel: ein neues papierloses Büro aufsetzen.
_Quelle: https://tbsch.de/post/2024-02-04-paperless-guide-setup/ · Stand: 2024-02-04_
Viele Wege führen nach Rom, zumindest aber benötigst du eine Komponente: ein Gerät, auf dem Services betrieben werden können. Hier setzt *Paperless-ngx* nur wenige Grenzen. Ich habe mich für die virtualisierte Variante mit Docker[^1] entschieden, die Docker Bridge läuft auf meinem Synology NAS[^2]. Dieser Leitfaden beschränkt sich auf die Verwendung von **Docker**.
[^1]: Containermanager, [docker.com](https://www.docker.com)
[^2]: Network Attached Storage, [Synology Webseite](https://www.synology.com/de-de)
> [!info]
> Bitte beachte, dass ein paar Vorkenntnisse notwendig sind. Grundsätzlich ist aber jeder in der Lage, ein papierloses Büro zu betreiben, wenn er sich in die Materie einarbeitet und Englisch beherrscht.
## Dokumentation
Wichtigste Informationsquelle für die folgenden Inhalte ist die Webseite von [*Paperless-ngx*](https://docs.paperless-ngx.com).
## Setup
Ich empfehle stets die Verwendung von **docker compose**[^3], da es die Handhabung von Containern enorm erleichtert. Praktischerweise liefern die Macher von *Paperless-ngx* bereits diverse Beispiel-Setups mit, welche [hier](https://github.com/paperless-ngx/paperless-ngx/tree/dev/docker/compose) zur Verfügung stehen.
[^3]: Mehr Infos auf [docker-compose](https://github.com/docker/compose)
Meine Installation besteht aus den folgenden Komponenten:
```yaml
# ...
services:
broker:
image: redis:7
# ...
webserver:
image: paperlessngx/paperless-ngx:latest
# ...
gotenberg:
image: gotenberg/gotenberg:7.8
# ...
tika:
image: ghcr.io/paperless-ngx/tika:latest
# ...
# ...
```
Bewusst verzichte ich auf den Einsatz einer relationalen Datenbank, wie z.B. PostgreSQL. Ich möchte mein papierloses Büro einfach nicht von einer Datenbank abhängig machen, denn diese benötigt auch Wartung, Backups, etc. Ein weiterer Vorteil ist, dass sich eine *sqlite*-Datei sehr viel einfacher sichern lässt :sunglasses:. Solltest du eine *MySQL*-Datenbank verwenden wollen, gibt es noch weitere Dinge zu [beachten](https://docs.paperless-ngx.com/advanced_usage/#mysql-caveats).
> [!warning]
> Wenn du dich mit Datenbanken nicht gut auskennst, rate ich dringend dazu, ebenfalls auf deren Einsatz zu verzichten! Bei falscher Handhabung droht Datenverlust; und genau das muss vermieden werden. Es gibt absolut keinen großen Vorteil durch ihren Einsatz, da *Paperless-ngx* keine horrenden Datenmengen speichert. Meine *sqlite*-Datenbank ist gerade einmal 14 MB groß (über 3000 Dokumente, Stand 30.12.2023).
**Gotenberg** und **Tika** sind optionale Bestandteile der *Paperless-ngx*-Installation. Sie werden benötigt, um *Office*- und E-Mail-Dateien verarbeiten zu können. Ich nutze diese Services zwar extrem sporadisch, Haben ist aber besser als Brauchen. [Hier](https://docs.paperless-ngx.com/configuration/#optional-services) findest du weitergehende Erläuterungen dazu.
## Konfiguration
Meine *docker compose*-Konfiguration ist natürlich auf meine Bedürfnisse zugeschnitten. Hier ein kleiner Einblick in den *Paperless-ngx*-Container. Alle Ordner sind frei zugänglich auf dem Dateisystem meines Servers, *Docker Volumes* nutze ich nicht:
```yaml
# ...
volumes:
- ./data:/usr/src/paperless/data
- ./media:/usr/src/paperless/media
- ./export:/usr/src/paperless/export
- ./consume:/usr/src/paperless/consume
- ./hooks:/usr/src/paperless/scripts
- ./trash:/usr/src/paperless/trash
environment:
PAPERLESS_REDIS: redis://broker:6379
PAPERLESS_TIKA_ENABLED: 1
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
PAPERLESS_TASK_WORKERS: 2
PAPERLESS_THREADS_PER_WORKER: 2
PAPERLESS_PRE_CONSUME_SCRIPT: /usr/src/paperless/scripts/PRE_CONSUME.sh
PAPERLESS_POST_CONSUME_SCRIPT: /usr/src/paperless/scripts/POST_CONSUME.sh
PAPERLESS_TRASH_DIR: /usr/src/paperless/trash
PAPERLESS_URL: # vertraulich
PAPERLESS_TRUSTED_PROXIES: # vertraulich
PAPERLESS_USE_X_FORWARD_HOST: true
PAPERLESS_USE_X_FORWARD_PORT: true
PAPERLESS_PROXY_SSL_HEADER: ["HTTP_X_FORWARDED_PROTO", "https"]
PAPERLESS_SECRET_KEY: # vertraulich
PAPERLESS_ENABLE_UPDATE_CHECK: true
PAPERLESS_OCR_LANGUAGES: deu
PAPERLESS_OCR_LANGUAGE: deu
PAPERLESS_FILENAME_FORMAT: {owner_username}/{created}_{correspondent}_{document_type}_{title}
# QR Code Scanner
PAPERLESS_CONSUMER_ENABLE_BARCODES: true
PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE: true
PAPERLESS_CONSUMER_BARCODE_SCANNER: ZXING
# ...
```
Alle Optionen sind detailliert [hier](https://docs.paperless-ngx.com/configuration/#docker) dokumentiert, jedes Detail kann und sollte nachgelesen werden. Für Vorschläge zu meiner Konfiguration bin ich jederzeit zu haben, nimm einfach Kontakt mit mir auf!
**Erläuterungen:**
- Wie eingangs erwähnt, behalte ich mir den Zugriff auf alle Daten über das Dateisystem vor. Das erleichtert Backups ungemein.
- Ich verwende Custom Scripts, um Aktionen vor und nach dem Konsumieren von Dokumenten auszuführen.
- Der Papierkorb ist aktiviert. Sollte nicht passieren, aber falls ich mal etwas lösche, ist es nicht sofort verloren.
- *Paperless-ngx* läuft in meinem Home Lab innerhalb eines *Traefik*[^5]-Netzwerks, ist also hinter einem Proxy.
- Beim `PAPERLESS_FILENAME_FORMAT` wird für jeden Eigentümer ein eigener Ordner angelegt. *Paperless-ngx* ist mandantenfähig, ein bisschen :wink:. Für den Privathaushalt mit mehreren Personen reicht es vollkommen.
- Der Barcode-Scanner ist aktiviert. Für eine bessere Erkennung von kleinen QR Codes auf Dokumenten verwende ich `ZXING`. Ein anderer Paperless-User, *Marvin Gaube*, hat einen interessanten [Artikel](https://margau.net/posts/2023-04-16-paperless-ngx-asn/) darüber verfasst.
[^5]: Traefik ist ein Reverse Proxy, [traefik.io](https://doc.traefik.io/traefik/)
## Fazit
Es gibt viele Details, über die man seine *Paperless-ngx*-Installation für den persönlichen Gebrauch optimieren kann. Das hängt immer sehr vom eigenen Use-Case, aber auch der eingesetzten Technik ab.
Auf die oben angesprochenen Custom Scripts gehe ich im weiteren Verlauf detaillierter ein. Das ist ein extrem mächtiges Tool, womit sich allerhand Schandtaten realisieren lassen :grin:. Stichwort: eigene Regelwerke, Validierung und Nachhalten eben dieser.
---
Das Titel-/Hintergrundbild stammt von [Luca Bravo](https://unsplash.com/de/@lucabravo?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) auf [Unsplash](https://unsplash.com/de/fotos/apple-macbook-neben-computermaus-auf-tisch-9l_326FISzk?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash).
---
# Paperless Guide: Einführung
> Kurzer Abstecher zu den alltäglichen Themen mit der Papier-Ablage.
_Quelle: https://tbsch.de/post/2024-01-28-paperless-guide-introduction/ · Stand: 2024-01-28_
Papierkram. Jeder von uns hat ihn, jeder muss ihn bewältigen, rechtzeitig auf Ereignisse reagieren, Schriftgut ablegen - und irgendwann im Bedarfsfall wiederfinden. Einige haben sich hierfür ein straffes Ordnersystem ausgedacht, andere feuern ihre Dokumente in irgendeine Kiste. Die Ablageregeln, die man sich hierfür aufgestellt hat, geraten mit den Jahren möglicherweise in Vergessenheit und bestimmte Dokumenttypen lagern dann an mehreren Stellen - der Worst Case ist eingetreten.
## Lösungsansätze
Nacheinander probierte ich verschiedene Lösungsansätze aus und entwickelte so mit der Zeit meine heutige Lösung.
### Ablage nach Datum
Da ich mich 2016 noch nicht mit privaten DMS[^1] auskannte bzw. mir mein Use-Case noch nicht bewusst war, entwickelte ich ein einfacheres Ablagesystem in meinen Ordnern. Hierbei heftete ich jedes Dokument einfach nach Datum ab und beschriftete die Ordner mit Zeiträumen.
[^1]: Document Management System, [Wikipedia](https://de.wikipedia.org/wiki/Dokumentenmanagement)
Mann, kam ich mir clever vor :sunglasses:. Bis der Tag kam, an dem ich einen bestimmten Brief von der Krankenkasse gesucht habe. Natürlich wusste ich nicht mehr, wann genau ich ihn erhalten hatte: ich blätterte also jeden Ordner durch, bis ich den Brief schließlich gefunden hatte.
### Erste Gehversuche: ecoDMS
Über meinen Freund Ben habe ich von [ecoDMS](https://www.ecodms.de/de/) erfahren. Das DMS besteht aus einem **Server**, der die Dokumente erkennt, klassifiziert und speichert, sowie einem **Client**. Über diesen kann man dann Dokumente suchen, mit Metainformationen versehen und einfach damit arbeiten. Cool - da ich einen Server zuhause stehen hatte, passte das wie die Faust aufs Auge und meine Dokumente wären immer verfügbar für mich.
Man steckt bei *ecoDMS* sehr viel Zeit in die Konfiguration, überlegt sich Strukturen, Dokumenttypen, Datenfelder, etc. Außerdem musste man der Erkennungssoftware beibringen, *wie* Dokumente klassifiziert werden sollen. Über einen praktischen Editor konnte man Bereiche auf einem Dokument auswählen, in denen bspw. immer eine Kundennummer auftauchte. Wahnsinn :grin:!
Bis Absender von Dokumenten halt mal ihr Brieflayout ändern. Ich musste leider feststellen, dass das ziemlich oft passiert. Dann begann nämlich jedes Mal das große Herumgefummel und *ecoDMS* wurde für mich einfach nur noch lästig und fühlte sich so **gar nicht smart** an. Der lästige Windows-FAT-Client und die schlechten Mobile- und Web-Zugriffsmöglichkeiten rundeten das negative Erlebnis ab.
### Die Lösung: Paperless-ngx
Anfang 2021 dann der große, medienwirksame Newcomer: [paperless-ng](https://github.com/jonaswinkler/paperless-ng) - Open Source, ausführbar in vielen privaten Umgebungen, rein webbasierter Zugriff, alles über REST API.
Als im August 2021 das **Aus** von *paperless-ng* verkündet wurde, machte sich bei mir Enttäuschung breit. Ich hatte schließlich mein *ecoDMS* damit abgelöst. Im März 2022 dann die Kehrtwende: ich war offensichtlich nicht der einzige Fan von *paperless-ng*.
Das Projekt wurde geforkt und ging an den Start als: **[Paperless-ngx](https://github.com/paperless-ngx/paperless-ngx)**
Jeder hat seine Präferenzen, ich favorisiere ganz klar den digitalen Weg: wenig bis gar kein Papier im Aktenschrank. Das passt einfach zu meinem Lifestyle. Auf den folgenden Seiten gehe ich näher auf meine Vorgehensweise mit *Paperless-ngx* ein, die sich komplett mühelos in den Alltag integrieren lässt.
---
Das Titel-/Hintergrundbild stammt von [Carl Heyerdahl](https://unsplash.com/de/@carlheyerdahl?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) auf [Unsplash](https://unsplash.com/de/fotos/silberner-imac-mit-tastatur-und-trackpad-im-zimmer-KE0nC8-58MQ?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash).
---
# Meine neue Webseite
> Alles neu, außer der Inhalt. Auf Ruby folgt nun Go: statisch bleibt's.
_Quelle: https://tbsch.de/post/2024-01-01-meine-neue-webseite/ · Stand: 2024-01-01_
> Alles neu, außer der Inhalt. Auf Ruby folgt nun Go: statisch bleibt's.
Ich habe mich dazu entschlossen, meine Webseite neu aufzubauen und auf Cloudflare Pages zu hosten. Bislang habe ich das in meiner heimischen Infrastruktur selbst getan, aufgrund zunehmender Cyberangriffe kommt das aber nicht mehr für mich in Frage.
Zwischenzeitlich habe ich mit einem VPS experimentiert, aber auch hier muss man natürlich laufend dran bleiben, um Sicherheitsrisiken im Keim zu ersticken. Das ist für mich auf Dauer leider keine Option, da die Webseite einfach nur ein kleines Projekt für nebenbei ist und im Alltag vermutlich häufig viel zu kurz kommt.
## Komponenten
- Programmiersprache: Go
- Static Site Generator: [Hugo](https://gohugo.io)
- Theme: [Blowfish](https://blowfish.page)
- Keine Datenbank
- GitHub: [Mein Profil](https://github.com/tb1337/)
- Cloudflare Pages
## Jekyll? Hugo?
Ich habe mich vorher an einem Projekt mit [Jekyll](https://jekyllrb.com) versucht, soweit hat das auch wunderbar funktioniert. Das Geflecht aus Ruby-Abhängigkeiten hat mir jedoch einfach nicht so gut gefallen, es verursachte immer wieder Probleme beim Build der Webseite. Als Anwender möchte ich mich aber einfach nicht mit solchen elementaren Herausforderungen herumplagen müssen und erwarte, dass Dinge einfach funktionieren. Also habe ich mich in letzter Konsequenz von Jekyll und dem [Minimal Mistakes Theme](https://github.com/mmistakes/minimal-mistakes) wieder getrennt. So einfach ist das.
Die Tatsache, eine Webseite einfach mit Markdown runterschreiben zu können, hat mir bei Jekyll jedoch so gut gefallen, dass ich nun bei Hugo gelandet bin. Wollen wir hoffen, dass es nun dabei bleibt :grin:.
---
Das Titel-/Hintergrundbild stammt von [Max Langelott](https://unsplash.com/de/@freiburgermax?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) auf [Unsplash](https://unsplash.com/de/fotos/silhouette-von-kranwagen-d3_cFMe97Ec?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash).
---
# pypaperless v2 erschienen
> pypaperless v2: der Paperless-ngx-API-Client wurde vollständig auf async refactored - der Grundstein für die Home-Assistant-Integration.
_Quelle: https://tbsch.de/post/2023-12-19-pypaperless-v2-erschienen/ · Stand: 2023-12-19_
Seit April 2022 gibt es nun schon *pypaperless*, also den kleinen API Client für Paperless-ngx. Zugegeben, ich habe dem Projekt nie wirklich viel Liebe zukommen lassen, da ich mich zu der Zeit auch noch gar nicht viel mit Python beschäftigt habe. Das Ergebnis war ein kleines Python-Modul mit wahrlich keinem guten Code.
Ich habe mir jedoch in den Kopf gesetzt, [Paperless-ngx](https://docs.paperless-ngx.com) als Integration nach [Home Assistant](https://www.home-assistant.io) zu bringen. Um diesem Ziel einen Schritt näher zu kommen, war ein Refactoring des kompletten Codes von *pypaperless* notwendig: Es musste in einem `async` Umfeld funktionieren.
Heute war es nach langer Arbeit dann endlich so weit: Version 2 konnte an den Start gehen :grin:!
Viel Spaß mit *pypaperless* v2!
---
Das Titel-/Hintergrundbild stammt von [Chris Ried](https://unsplash.com/de/@cdr6934?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) auf [Unsplash](https://unsplash.com/de/fotos/ein-computerbildschirm-mit-einem-haufen-code-darauf-ieic5Tq8YMk?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash).