Regenbasierte automatische Gartenbewässerung + homeMesh-Anbindung für AutoAction-Regeleditor

- Neues stündliches Python-Script (restricted/gartenbewaesserung/auto_watering.py): holt Niederschlags-
  und Sonnenuntergangsdaten von Open-Meteo, wertet je Zone (Hochbeete/Tröge/Garten vorn) individuelle
  Regen-Schwellenwerte und Mindest-Bewässerungsdauern aus und startet bei Bedarf per MQTT den passenden
  Automatik-Modus der Ventilsteuerung im Zeitraum vor Sonnenuntergang; veröffentlicht den Regen-Status
  zusätzlich stündlich als retained MQTT-Nachricht je Zone.
- Web-UI: neue Bewässerungs-Steuerung/-Anzeige (ajax/watering.php, js/solar/solarMQTT.js,
  assets/img/realtime.svg) inkl. Live-Status-Icons (aus/geplant/pausiert/aktiv per Farbe und Symbol).
- AutoAction-Regeleditor (ajax/actorDetails.php, sensorDetails.php, fillActorDD.php, fillSensorDD.php,
  tahoma.php, AutoAction.php) liest Actor-/Sensor-Parameter jetzt live aus der homeMesh-Datenbank statt
  aus stark vereinfachten Altfeldern, inkl. zugehöriger Anpassungen am Device-Discovery-Tooling
  (restricted/deviceDiscovery/*, neues logic_module.py).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 20:19:27 +02:00
co-authored by Claude Sonnet 5
parent 4de95c1a29
commit 29f752eafe
22 changed files with 1693 additions and 234 deletions
+117 -28
View File
@@ -19,6 +19,7 @@ from modules.tahoma_module import TahomaModule
from modules.wled_module import WLEDModule
from modules.mqtt_module import MQTTModule
from modules.shelly_module import ShellyModule
from modules.logic_module import LogicModule #nicht Vergessen neue Module auch in Zeile 546 einzufügen!!
# Logging
logging.basicConfig(
@@ -142,6 +143,14 @@ class Config:
return 2
return self._get_int('wled', 'timeout', 2)
# LOGIC
@property
def logic_enable(self) -> bool:
if not self.config.has_section('logic'):
return False
return self._get_bool('logic', 'enable', False)
# MQTT
@property
def mqtt_enable(self) -> bool:
@@ -236,6 +245,33 @@ class Config:
class DatabaseManager:
"""Zentrale Datenbank-Verwaltung - ALLE DB-Operationen hier"""
PARAMETER_TYPES = {
0: "bool",
1: "integer", # Integer value
2: "float", # Floating point number
3: "string", # Text string
4: "undefined",
5: "time",
6: "array",
7: "deltatime",
8: "date",
9: "datetime",
}
PARAMETER_TYPES_MAPPING = {
"bool": 0,
"boolean" : 0,
"integer" : 1, # Integer value
"float" : 2, # Floating point number
"number" : 2, # Floating point number
"string" : 3, # Text string
"time" : 5,
"array": 6,
"deltatime" : 7,
"date" : 8,
"datetime" : 9,
}
def __init__(self, host: str, database: str, user: str, password: str, port: int = 3306):
self.host = host
self.database = database
@@ -272,13 +308,13 @@ class DatabaseManager:
try:
cursor = self.connection.cursor()
cursor.execute("SET FOREIGN_KEY_CHECKS=0")
cursor.execute("DELETE FROM command_parameters")
cursor.execute("DELETE FROM actor_commands")
cursor.execute("DELETE FROM actor_states")
cursor.execute("DELETE FROM actors")
cursor.execute("DELETE FROM sensor_states")
cursor.execute("DELETE FROM sensors")
cursor.execute("TRUNCATE state_types")
cursor.execute("TRUNCATE command_parameters")
cursor.execute("TRUNCATE actor_commands")
cursor.execute("TRUNCATE actor_states")
cursor.execute("TRUNCATE actors")
cursor.execute("TRUNCATE sensor_states")
cursor.execute("TRUNCATE sensors")
cursor.execute("SET FOREIGN_KEY_CHECKS=1")
@@ -289,6 +325,8 @@ class DatabaseManager:
logger.error(f"✗ Fehler beim Leeren der Tabellen: {e}")
self.connection.rollback()
def insert_actor(self, device_type: str, name: str, url: str,
commands: list, states: list) -> bool:
"""
@@ -306,22 +344,35 @@ class DatabaseManager:
# Actor einfügen
query = """
INSERT INTO actors (type, name, parameters, url)
VALUES (%s, %s, NULL, %s)
INSERT INTO actors (type, name, url)
VALUES (%s, %s, %s)
ON DUPLICATE KEY UPDATE
type = VALUES(type),
name = VALUES(name)
"""
cursor.execute(query, (device_type, name, url))
actor_id = cursor.lastrowid
query = """
SELECT ID FROM actors where url = %s
"""
cursor.execute(query, (url))
actor_id = cursor.fetchone()[0]
# Commands einfügen
for cmd in commands:
command_name = cmd.get('command', '')
command_url = cmd.get('url', '')
cmd_query = """
INSERT INTO actor_commands (actor_id, command_name)
VALUES (%s, %s)
INSERT INTO actor_commands (actor_id, command_name, command_url)
VALUES (%s, %s, %s)
ON DUPLICATE KEY UPDATE
command_name = VALUES(command_name)
"""
cursor.execute(cmd_query, (actor_id, command_name))
command_id = cursor.lastrowid
cursor.execute(cmd_query, (actor_id, command_name, command_url))
query = """
SELECT ID FROM actor_commands where actor_id = %s and command_url = %s
"""
cursor.execute(query, (actor_id, command_url))
command_id = cursor.fetchone()[0]
# Parameter einfügen
for param in cmd.get('parameters', []):
@@ -329,12 +380,18 @@ class DatabaseManager:
INSERT INTO command_parameters
(command_id, parameter_name, parameter_type, min_value, max_value, possible_values, url)
VALUES (%s, %s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
parameter_name = VALUES(parameter_name),
parameter_type = VALUES(parameter_type),
min_value = VALUES(min_value),
max_value = VALUES(max_value),
possible_values = VALUES(possible_values)
"""
param_name = param.get('name', '')
param_type = param.get('type', '')
min_val = param.get('min')
max_val = param.get('max')
possible_vals = json.dumps(param.get('values')) if 'values' in param else None
possible_vals = json.dumps(param.get('values')) if 'values' in param else ""
param_url = param.get('url')
cursor.execute(param_query,
@@ -344,16 +401,22 @@ class DatabaseManager:
for state in states:
state_query = """
INSERT INTO actor_states
(actor_id, state_name, state_type, current_value, unit, url)
VALUES (%s, %s, %s, %s, %s, %s)
(actor_id, state_name, state_type, current_value, unit, url, possible_values)
VALUES (%s, %s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
state_name = VALUES(state_name),
state_type = VALUES(state_type),
current_value = VALUES(current_value),
possible_values = VALUES(possible_values),
unit = VALUES(unit)
"""
state_name = state.get('name', '')
state_type = state.get('type', 0)
state_type = self.PARAMETER_TYPES_MAPPING.get(state.get('type', 0), 4)
current_value = str(state.get('current_value', '')) if 'current_value' in state else None
unit = state.get('unit')
state_url = state.get('url')
cursor.execute(state_query, (actor_id, state_name, state_type, current_value, unit, state_url))
possible_vals = json.dumps(state.get('values')) if 'values' in state else ""
cursor.execute(state_query, (actor_id, state_name, state_type, current_value, unit, state_url, possible_vals))
self.connection.commit()
cursor.close()
@@ -363,6 +426,17 @@ class DatabaseManager:
logger.error(f"✗ Fehler beim Einfügen des Aktors {name}: {e}")
self.connection.rollback()
return False
def fillVariableTypes(self):
cursor = self.connection.cursor()
query = """
INSERT INTO state_types (id, type)
VALUES (%s, %s)
ON DUPLICATE KEY UPDATE
type = VALUES(type)
"""
for id, parameter_type in self.PARAMETER_TYPES.items():
cursor.execute(query, (id, parameter_type))
def insert_sensor(self, device_type: str, name: str, url: str, states: list) -> bool:
"""
@@ -379,21 +453,34 @@ class DatabaseManager:
# Sensor einfügen
query = """
INSERT INTO sensors (type, name, parameters, url)
VALUES (%s, %s, NULL, %s)
INSERT INTO actors (type, name, url)
VALUES (%s, %s, %s)
ON DUPLICATE KEY UPDATE
type = VALUES(type),
name = VALUES(name)
"""
cursor.execute(query, (device_type, name, url))
sensor_id = cursor.lastrowid
query = """
SELECT ID FROM actors where url = %s
"""
cursor.execute(query, (url))
sensor_id = cursor.fetchone()[0]
# States einfügen
for state in states:
state_query = """
INSERT INTO sensor_states
(sensor_id, state_name, state_type, current_value, unit, url)
INSERT INTO actor_states
(actor_id, state_name, state_type, current_value, unit, url)
VALUES (%s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
state_name = VALUES(state_name),
state_type = VALUES(state_type),
current_value = VALUES(current_value),
unit = VALUES(unit)
"""
state_name = state.get('name', '')
state_type = state.get('type', 0)
state_type = self.PARAMETER_TYPES_MAPPING.get(state.get('type', 0), 4)
current_value = str(state.get('current_value', '')) if 'current_value' in state else None
unit = state.get('unit')
state_url = state.get('url')
@@ -460,13 +547,15 @@ def main():
total_actors = 0
total_sensors = 0
db.fillVariableTypes()
# Module initialisieren
modules = [
TahomaModule(config),
WLEDModule(config),
MQTTModule(config),
ShellyModule(config)
ShellyModule(config),
LogicModule(config)
]
# Jedes Modul durchlaufen