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:
@@ -11,6 +11,7 @@ KEINE Datenbank-Operationen!
|
||||
import json
|
||||
import requests
|
||||
import socket
|
||||
import concurrent.futures
|
||||
import logging
|
||||
from typing import List, Dict, Optional, Tuple
|
||||
from modules.base_module import BaseModule
|
||||
@@ -28,22 +29,47 @@ class ShellyDiscovery:
|
||||
SHELLY_MDNS_SERVICE = "_http._tcp.local."
|
||||
COMMON_PORTS = [80]
|
||||
|
||||
def __init__(self, network_range: str = "192.168.1"):
|
||||
self.network_range = network_range
|
||||
def __init__(self):
|
||||
self.devices = []
|
||||
|
||||
def scan_network(self, start_ip: int = 2, end_ip: int = 254, timeout: float = 0.3) -> List[str]:
|
||||
"""
|
||||
Scannt das Netzwerk nach aktiven Hosts
|
||||
|
||||
def scan_network(self, network: str = None, max_threads: int = 50) -> List[str]:
|
||||
"""Scannt das Netzwerk nach Shelly-Geräten"""
|
||||
if network is None:
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.connect(("8.8.8.8", 80))
|
||||
local_ip = s.getsockname()[0]
|
||||
s.close()
|
||||
network_prefix = '.'.join(local_ip.split('.')[:-1])
|
||||
except:
|
||||
logger.warning("Konnte lokale IP nicht ermitteln, verwende 192.168.1.x")
|
||||
network_prefix = "192.168.1"
|
||||
else:
|
||||
network_prefix = '.'.join(network.split('.')[:3])
|
||||
|
||||
Args:
|
||||
start_ip: Start IP (letztes Oktett)
|
||||
end_ip: End IP (letztes Oktett)
|
||||
timeout: Timeout für Socket-Verbindung
|
||||
logger.info(f"Scanne Netzwerk {network_prefix}.0/24 nach Shelly-Geräten...")
|
||||
|
||||
def check_ip(ip):
|
||||
device = ShellyDiscovery.is_shelly_device(ip)
|
||||
if device:
|
||||
return device
|
||||
return None
|
||||
|
||||
shelly_devices = []
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=max_threads) as executor:
|
||||
futures = [executor.submit(check_ip, f"{network_prefix}.{i}")
|
||||
for i in range(1, 255)]
|
||||
|
||||
Returns:
|
||||
Liste von erreichbaren IP-Adressen
|
||||
"""
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
result = future.result()
|
||||
if result:
|
||||
shelly_devices.append(result)
|
||||
#logger.info(f"Shelly-Gerät gefunden: {result}")
|
||||
|
||||
return shelly_devices
|
||||
"""def scan_network(self, start_ip: int = 2, end_ip: int = 254, timeout: float = 0.3) -> List[str]:
|
||||
|
||||
active_hosts = []
|
||||
logger.info(f"Scanne Netzwerk {self.network_range}.{start_ip}-{end_ip}...")
|
||||
|
||||
@@ -64,8 +90,8 @@ class ShellyDiscovery:
|
||||
|
||||
logger.info(f"{len(active_hosts)} aktive Hosts gefunden")
|
||||
return active_hosts
|
||||
|
||||
def is_shelly_device(self, ip: str) -> Optional[Dict]:
|
||||
"""
|
||||
def is_shelly_device(ip: str) -> Optional[Dict]:
|
||||
"""
|
||||
Prüft ob ein Host ein Shelly-Gerät ist
|
||||
|
||||
@@ -75,7 +101,7 @@ class ShellyDiscovery:
|
||||
Returns:
|
||||
Device Info Dict wenn Shelly, sonst None
|
||||
"""
|
||||
logger.info(f"Suche Shelly Getät unter: {ip}")
|
||||
#logger.info(f"Suche Shelly Getät unter: {ip}")
|
||||
try:
|
||||
# Versuche Gen2 API (neuere Shelly-Geräte)
|
||||
response = requests.get(
|
||||
@@ -96,13 +122,13 @@ class ShellyDiscovery:
|
||||
try:
|
||||
# Versuche Gen1 API (ältere Shelly-Geräte)
|
||||
response = requests.get(
|
||||
f"http://{ip}/shelly",
|
||||
f"http://{ip}/settings",
|
||||
timeout=2
|
||||
)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if 'type' in data and (data['type'].startswith('SHELLY') or data['type'].startswith('SHSW')):
|
||||
logger.info(f"Shelly Gen1 Gerät gefunden: {ip} - {data.get('type', 'Unknown')}")
|
||||
if 'device' in data and (data['device']['type'].startswith('SHELLY') or data['device']['type'].startswith('SHSW')):
|
||||
logger.info(f"Shelly Gen1 Gerät gefunden: {ip} - {data['device'].get('type', 'Unknown')}")
|
||||
return {
|
||||
'ip': ip,
|
||||
'generation': 1,
|
||||
@@ -146,22 +172,19 @@ class ShellyDiscovery:
|
||||
logger.error(f"Fehler beim Abrufen des Status von {ip}: {e}")
|
||||
|
||||
return None
|
||||
|
||||
def discover_devices(self, start_ip: int = 1, end_ip: int = 254) -> List[Dict]:
|
||||
|
||||
def discover_devices(self) -> List[Dict]:
|
||||
"""
|
||||
Entdeckt alle Shelly-Geräte im Netzwerk
|
||||
|
||||
Returns:
|
||||
Liste von Shelly-Geräten mit Status
|
||||
"""
|
||||
active_hosts = self.scan_network(start_ip, end_ip)
|
||||
self.devices = self.scan_network()
|
||||
|
||||
for ip in active_hosts:
|
||||
device = self.is_shelly_device(ip)
|
||||
if device:
|
||||
status = self.get_device_status(device)
|
||||
device['status'] = status
|
||||
self.devices.append(device)
|
||||
for idx, device in enumerate(self.devices):
|
||||
status = self.get_device_status(device)
|
||||
self.devices[idx]['status'] = status
|
||||
|
||||
logger.info(f"Insgesamt {len(self.devices)} Shelly-Geräte entdeckt")
|
||||
return self.devices
|
||||
@@ -197,11 +220,8 @@ class ShellyModule(BaseModule):
|
||||
sensors = []
|
||||
|
||||
try:
|
||||
discovery = ShellyDiscovery(network_range=self.config.shelly_network_range)
|
||||
devices = discovery.discover_devices(
|
||||
start_ip=self.config.shelly_start_ip,
|
||||
end_ip=self.config.shelly_end_ip
|
||||
)
|
||||
discovery = ShellyDiscovery()
|
||||
devices = discovery.discover_devices()
|
||||
|
||||
if not devices:
|
||||
logger.info("Keine Shelly-Geräte gefunden")
|
||||
@@ -266,7 +286,7 @@ class ShellyModule(BaseModule):
|
||||
temp_data = status.get(f'temperature:{i}', {})
|
||||
|
||||
sensors.append({
|
||||
'type': 'ShellyTemperatureSensor',
|
||||
'type': 'Temperatur',
|
||||
'name': f"{device_name}_Temp_{i}",
|
||||
'url': f"http://{ip}/rpc/Temperature.GetStatus?id={i}",
|
||||
'states': [
|
||||
@@ -274,6 +294,7 @@ class ShellyModule(BaseModule):
|
||||
'name': 'temperature',
|
||||
'type': 'number',
|
||||
'current_value': temp_data.get('tC'),
|
||||
'url': f"tC",
|
||||
'unit': '°C'
|
||||
}
|
||||
]
|
||||
@@ -283,7 +304,7 @@ class ShellyModule(BaseModule):
|
||||
for i in range(em_count):
|
||||
em_data = status.get(f'em:{i}', {})
|
||||
sensors.append({
|
||||
'type': 'ShellyEnergyMeter',
|
||||
'type': 'Stromzähler',
|
||||
'name': f"{device_name}_EM_{i}",
|
||||
'url': f"http://{ip}/rpc/em.GetStatus?id={i}",
|
||||
'states': []
|
||||
@@ -413,7 +434,7 @@ class ShellyModule(BaseModule):
|
||||
ip = device['ip']
|
||||
|
||||
device_name = info.get('name', f"Shelly_{info.get('type', ip)}")
|
||||
device_type = info.get('type', 'Unknown')
|
||||
device_type = info['device'].get('type', 'Unknown')
|
||||
|
||||
# Relays als Aktoren
|
||||
relays = status.get('relays', [])
|
||||
@@ -440,7 +461,7 @@ class ShellyModule(BaseModule):
|
||||
temp_data = status.get('tmp', {})
|
||||
if temp_data and 'tC' in temp_data:
|
||||
sensors.append({
|
||||
'type': 'ShellyTemperatureSensor',
|
||||
'type': 'Temperatur',
|
||||
'name': f"{device_name}_Temp",
|
||||
'url': f"http://{ip}/status",
|
||||
'states': [
|
||||
|
||||
Reference in New Issue
Block a user