diff --git a/README.md b/README.md
index ea51085..d9a8f30 100644
--- a/README.md
+++ b/README.md
@@ -85,10 +85,8 @@ flowchart TB
BROKER{{"MQTT-Broker"}}
SOLARLOG[("solarLog
Verlauf")]
HOMEMESH[("homeMesh
Geräte · Automatiken")]
- ALARM[("alarm
Weckzeiten")]
RUNNER["autoaction_runner.py
SolarManager"]
- WECKER["wecker.py
SolarManager"]
KALENDER["fetch_calendar.py
SolarManager · Cronjob, jährlich"]
DISCOVERY["device_discovery.py
Web · von Hand gestartet"]
AJAX["ajax/*.php
Web"]
@@ -103,10 +101,8 @@ flowchart TB
RUNNER -.->|"HTTP · WLED · Tahoma"| HAUS
BROKER -.->|"Gartenwasser/…/set"| VENTILE
- ALARM --> WECKER
FERIEN --> KALENDER
KALENDER -->|"calendar_days"| HOMEMESH
- WECKER -.->|HTTP| HAUS
BROKER -->|"homeassistant/#"| DISCOVERY
HAUS -->|"mDNS · Tahoma"| DISCOVERY
@@ -121,8 +117,8 @@ flowchart TB
classDef skript fill:#fff6e5,stroke:#d0a548
classDef speicher fill:#eaf5ee,stroke:#6fa981
classDef geraet fill:#eef4fb,stroke:#7f9dc0
- class RUNNER,WECKER,DISCOVERY,AJAX,BROWSER,KALENDER skript
- class BROKER,SOLARLOG,HOMEMESH,ALARM speicher
+ class RUNNER,DISCOVERY,AJAX,BROWSER,KALENDER skript
+ class BROKER,SOLARLOG,HOMEMESH speicher
class HAUS,VENTILE,FERIEN geraet
```
@@ -152,7 +148,6 @@ von Hand, wenn sich am Bestand etwas geändert hat.
| `homeassistant/#` | die Geräte selbst | `device_discovery.py` |
| **`solarLog`** | `solarManager.py` | `ajax/*.php`, `gatherWaterData` |
| **`homeMesh`** | `device_discovery.py`, Runner, Web-Editor | Runner, `ajax/*.php` |
-| **`alarm`** | Web-Oberfläche | `wecker.py` |
## Was wann startet
@@ -163,7 +158,6 @@ von Hand, wenn sich am Bestand etwas geändert hat.
| `gatherRainData.py` | dito |
| `wsMQTTbridge.py` | `startMQTTbridge.sh` |
| `wattpilot_bruecke.py` | `startWattpilotMQTT.sh` |
-| `wecker.py` | `startWecker.sh` |
| `autoActions/fetch_calendar.py` | Cronjob, einmal im Jahr |
| `device_discovery.py` (Web-Repo) | von Hand |
@@ -171,6 +165,42 @@ von Hand, wenn sich am Bestand etwas geändert hat.
startet — beim Runner ist das wichtig, zwei Instanzen würden jedes Kommando
doppelt schicken.
+## Der Wecker ist keiner mehr
+
+`wecker.py` gibt es nicht mehr. Es hat genau das getan, was der
+AutoAction-Runner ohnehin kann — nur mit allem doppelt: eigener Datenbank
+`alarm`, eigenem Feiertags- und Ferienkalender, eigenem UDP-Log auf Port
+13377, eigener Endlosschleife und einem eigenen Startskript samt nächtlichem
+Neustart um drei.
+
+Punkt für Punkt hatte der Runner die bessere Fassung schon:
+
+| `wecker.py` | Automatik |
+|---|---|
+| `alarmtime.time_1` | Bedingung „Uhrzeit um 05:50" |
+| Wochentagsspalten `mo` … `so` | `weekdays`, eine Bitmaske |
+| `is_holiday()` gegen `alarm.feiertage` | `on_holiday`, gegen `calendar_days` |
+| `is_school_holiday()` gegen `alarm.ferien` | `on_vacation`, ebenso |
+| `alarmtime.wecked = heute` | `cond_met`, die steigende Flanke |
+| fünf HTTP-Versuche im Abstand von vier Sekunden | der `Versand`, ein Faden je Gerät |
+| `send_log()` per UDP | `automation_log` und `autoActions.log` |
+
+Der Kalender war der auffälligste Teil davon: `alarm.feiertage` und
+`alarm.ferien` standen neben `calendar_days`, das `fetch_calendar.py` einmal
+im Jahr von openholidaysapi.org holt. Zwei Kalender, die dasselbe wissen
+müssen, gehen früher oder später auseinander.
+
+Umgezogen ist die eine Weckzeit, die scharf war: 05:50, Montag bis Freitag,
+nicht in den Ferien, nicht an Feiertagen, WLED-Preset 5 („Wakeup") auf
+`MenasHimmel`. Die drei anderen Zeilen in `alarmtime` standen auf
+`onoff = -1`. `wecker_zu_automatik.sql` legt die Automatik an und beschreibt
+im Kopf, wie dasselbe im Dashboard von Hand geht.
+
+Damit ist die Weckzeit dort einstellbar, wo alles andere auch eingestellt
+wird — unter „Automatismen", Etage EG. Die Datenbank `alarm` liest niemand
+mehr; sie steht noch da, mitsamt dem Abschnitt `[alarm]` in der `config.ini`,
+und kann weg, sobald der erste Morgen ohne `wecker.py` durch ist.
+
## Konfiguration
Zugangsdaten und Standort stehen in `config.ini` (Vorlage:
diff --git a/logrotate.conf b/logrotate.conf
index 8c9c947..ae67bf9 100644
--- a/logrotate.conf
+++ b/logrotate.conf
@@ -22,7 +22,6 @@
/volume1/homes/wagner/SolarManager/rainOutput.log
/volume1/homes/wagner/SolarManager/wattpilotshell.log
/volume1/homes/wagner/SolarManager/wsMQTTbridge.log
-/volume1/homes/wagner/SolarManager/wecker.log
{
monthly
# Zwei Wochen zurueck. Weiter zurueck hat noch nie jemand gesucht, und
@@ -34,7 +33,8 @@
maxsize 10M
compress
# Eine leere Datei zu rotieren bringt nichts ausser vierzehn leeren
- # Archiven - der Wecker meldet an den meisten Tagen gar nichts.
+ # Archiven, und mehrere dieser Prozesse melden an den meisten Tagen
+ # gar nichts.
notifempty
# Fehlt eine Datei, ist das kein Fehler: nicht jeder Prozess laeuft
# auf jedem System.
diff --git a/startWecker.sh b/startWecker.sh
deleted file mode 100755
index 94e11df..0000000
--- a/startWecker.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash
-echo "Starting..."
-# Change the name of the script here
-SCRIPT_NAME="wecker.py"
-LOG_FILE="wecker.log"
-
-# Find the process ID of any running instance of the script
-PID=$(ps aux | grep "$SCRIPT_NAME" | grep -v grep | awk '{print $2}')
-
-# If a running instance was found, kill it
-if [[ -n "$PID" ]]; then
- echo "Killing process $PID"
- kill "$PID"
-fi
-
-
-# Start the script
-cd "/volume1/homes/wagner/SolarManager/"
-# Angehaengt statt ueberschrieben. Dieses Skript laeuft jede Nacht um drei,
-# und mit "&>" war die Datei danach jedes Mal leer - was der Wecker am
-# Vortag gemeldet hat, war morgens nicht mehr nachzulesen. Ausserdem braucht
-# logrotate den Anhaengemodus: es leert die Datei mit copytruncate, waehrend
-# der Prozess sie offen haelt.
-/usr/bin/python3 "$SCRIPT_NAME" >> "$LOG_FILE" 2>&1 &
\ No newline at end of file
diff --git a/wecker.py b/wecker.py
deleted file mode 100644
index b350640..0000000
--- a/wecker.py
+++ /dev/null
@@ -1,129 +0,0 @@
-import sys
-import mysql.connector as pymysql
-from mysql.connector import connect, Error
-import requests
-import datetime
-import time
-import socket
-import traceback
-import konfig
-
-# MySQL-Datenbankverbindung. Zugangsdaten stehen in config.ini, siehe konfig.py.
-DB_CONFIG = konfig.datenbank("alarm")
-
-wled_url = "http://192.168.179.139/json" # URL zur WLED JSON API
-LOG_SERVER = ("localhost", 13377) # UDP Logserver
-
-# UDP Logging Funktion
-def send_log(message):
- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- log_message = f"{datetime.datetime.now().strftime('%b %d %H:%M:%S')} alarm_scheduler: {message}"
- sock.sendto(log_message.encode(), LOG_SERVER)
- sock.close()
-
-def is_holiday():
- today_str = datetime.datetime.today().strftime("%Y-%m-%d")
-
- conn = pymysql.connect(**DB_CONFIG)
- cursor = conn.cursor()
-
- query = "SELECT COUNT(*) FROM feiertage WHERE datetime = %s"
- cursor.execute(query, (today_str,))
- result = cursor.fetchone()
- conn.close()
-
- return result[0] > 0
-
-def is_school_holiday():
-
- today_str = datetime.datetime.today().strftime("%Y-%m-%d")
-
- conn = pymysql.connect(**DB_CONFIG)
- cursor = conn.cursor()
-
- query = "SELECT COUNT(*) FROM ferien WHERE date_from <= %s AND date_to >= %s"
- cursor.execute(query, (today_str, today_str))
- result = cursor.fetchone()
- conn.close()
-
- return result[0] > 0
-
-def get_active_alarms():
- today = datetime.datetime.today()
- today_str = today.strftime("%Y-%m-%d")
- weekday_map = {0: "mo", 1: "di", 2: "mi", 3: "do", 4: "fr", 5: "sa", 6: "so"}
- weekday_col = weekday_map[today.weekday()]
- holiday = is_holiday()
- school_holiday = is_school_holiday()
-
- conn = pymysql.connect(**DB_CONFIG)
- cursor = conn.cursor()
-
- query = f"""
- SELECT id, TIME_FORMAT(time_1, '%H:%i:%s'), mode FROM alarmtime
- WHERE onoff != -1 AND {weekday_col} != -1 AND (wecked IS NULL OR wecked != '{today_str}')
- """
-
- if holiday:
- query += " AND mode NOT IN ('arbeit', 'schule')"
- elif school_holiday:
- query += " AND mode != 'schule'"
-
- cursor.execute(query)
- alarms = cursor.fetchall()
- conn.close()
-
- return [(alarm[0], alarm[1]) for alarm in alarms]
-
-def trigger_wled_playlist():
- data = {"ps": 5} # Beispiel: Playlist 1 aktivieren
- headers = {"Content-Type": "application/json"}
-
- for attempt in range(5):
- try:
- response = requests.post(wled_url, json=data, headers=headers)
- send_log(f"WLED Response (Attempt {attempt + 1}): {response.text}")
- try:
- if response.json().get("success"):
- return
- except ValueError:
- send_log(f"Value error: {response.text}") # Falls die Antwort kein gültiges JSON ist, weitermachen
- except:
- send_log(f"No response (Attempt {attempt + 1}): {traceback.print_exc()}")
- time.sleep(4) # Kurze Wartezeit zwischen Versuchen
-
-def mark_alarm_as_executed(alarm_id):
- today_str = datetime.datetime.today().strftime("%Y-%m-%d")
- conn = pymysql.connect(**DB_CONFIG)
- cursor = conn.cursor()
-
- query = "UPDATE alarmtime SET wecked = %s WHERE id = %s"
- cursor.execute(query, (today_str, alarm_id))
- conn.commit()
- conn.close()
-
-def main():
- last_db_check = 0
- active_alarms = []
-
- while True:
- now = time.time()
-
- # Nur alle 5 Minuten die Datenbankabfrage ausführen
- if now - last_db_check >= 300:
- active_alarms = get_active_alarms()
- last_db_check = now
-
- current_time = datetime.datetime.now().strftime("%H:%M:%S")
-
- for alarm_id, alarm_time in active_alarms:
- if current_time >= alarm_time:
- send_log(f"Triggering WLED Playlist at {alarm_time}")
- trigger_wled_playlist()
- mark_alarm_as_executed(alarm_id)
- active_alarms = get_active_alarms()
-
- time.sleep(20) # Überprüfung alle 20 sec
-
-if __name__ == "__main__":
- main()
diff --git a/wecker_zu_automatik.sql b/wecker_zu_automatik.sql
new file mode 100644
index 0000000..d1268e0
--- /dev/null
+++ b/wecker_zu_automatik.sql
@@ -0,0 +1,76 @@
+-- ===========================================================================
+-- Der Wecker als Automatik
+--
+-- Ersetzt wecker.py. Das Skript hat dasselbe getan, was der AutoAction-Runner
+-- ohnehin kann, nur mit eigener Datenbank (`alarm`), eigenem Feiertags- und
+-- Ferienkalender, eigenem UDP-Log und einer eigenen Endlosschleife:
+--
+-- wecker.py Automatik
+-- --------------------------------- ----------------------------------
+-- alarmtime.time_1 = 05:50:00 Bedingung "Uhrzeit = 05:50"
+-- alarmtime.mo..fr != -1 weekdays = 31 (Bit 0..4, Mo bis Fr)
+-- mode = schule, is_holiday() on_holiday = 0
+-- mode = schule, is_school_holiday() on_vacation = 0
+-- alarmtime.wecked = heute cond_met, die steigende Flanke
+-- POST {"ps": 5} an 192.168.179.139 Kommando "Preset" des Aktors
+-- MenasHimmel, Parameter preset = 5
+--
+-- Preset 5 heisst am Geraet "Wakeup".
+--
+-- Von den vier Zeilen in `alarmtime` war nur eine scharf (id 3, 05:50, Modus
+-- schule); die drei anderen standen auf onoff = -1. Nur diese eine zieht hier
+-- also um.
+--
+-- Einspielen:
+-- mysql -h 127.0.0.1 -P 3310 -u homeMesh -p homeMesh < wecker_zu_automatik.sql
+--
+-- Dasselbe geht auch ohne SQL: im Dashboard unter "Automatismen", Etage EG,
+-- neue Automatik anlegen - Bedingung "Uhrzeit um 05:50", Aktion
+-- "MenasHimmel / Preset" mit Wakeup, Wochentage Mo-Fr, die beiden Haken
+-- "auch in den Ferien" und "auch an Feiertagen" heraus. Der Runner liest das
+-- Regelwerk alle 30 Sekunden neu, ein Neustart ist so oder so nicht noetig.
+-- ===========================================================================
+
+INSERT INTO automations
+ (name, floor, enabled, window_from, window_to, weekdays,
+ on_vacation, on_holiday, force_once, lockout_secs)
+SELECT Wecker Magdalena, EG, 1, 00:00:00, 23:59:00, 31, 0, 0, 0, 900
+ FROM DUAL
+ WHERE NOT EXISTS (SELECT 1 FROM automations WHERE name = Wecker Magdalena);
+
+SET @auto = (SELECT id FROM automations WHERE name = Wecker Magdalena);
+
+-- Bedingung: state_id 1 ist der gerechnete Messwert "Uhrzeit" des
+-- Logic-Aktors. Operator = heisst "um", also ab dieser Minute noch
+-- catchup_minutes lang wahr - ausgeloest wird trotzdem nur einmal, dafuer
+-- sorgt die Flanke.
+INSERT INTO automation_conditions
+ (automation_id, group_no, position, state_id, operator, value)
+SELECT @auto, 0, 0, 1, =, 05:50
+ FROM DUAL
+ WHERE NOT EXISTS (SELECT 1 FROM automation_conditions
+ WHERE automation_id = @auto AND state_id = 1);
+
+-- Aktion: command_id 20 ist "Preset" des WLED-Aktors MenasHimmel
+-- (wled://192.168.179.139), Vorlage {"ps":%preset%}.
+INSERT INTO automation_actions (automation_id, position, command_id)
+SELECT @auto, 0, 20
+ FROM DUAL
+ WHERE NOT EXISTS (SELECT 1 FROM automation_actions
+ WHERE automation_id = @auto AND command_id = 20);
+
+SET @aktion = (SELECT id FROM automation_actions
+ WHERE automation_id = @auto AND command_id = 20);
+
+-- parameter_id 16 ist "preset" zu diesem Kommando.
+INSERT INTO automation_action_params (action_id, parameter_id, value)
+VALUES (@aktion, 16, "5")
+ON DUPLICATE KEY UPDATE value = VALUES(value);
+
+SELECT a.id, a.name, a.weekdays, a.on_vacation, a.on_holiday,
+ c.operator, c.value AS uhrzeit, p.value AS preset
+ FROM automations a
+ JOIN automation_conditions c ON c.automation_id = a.id
+ JOIN automation_actions ak ON ak.automation_id = a.id
+ JOIN automation_action_params p ON p.action_id = ak.id
+ WHERE a.name = "Wecker Magdalena";