Zeilenenden vereinheitlichen: LF ueberall ausser im Fremdcode

Bisher entschied jede Arbeitsstation fuer sich, welche Zeilenenden ein
Commit bekommt: Git fuer Windows setzt core.autocrlf=input in seiner
System-Konfiguration, die NAS setzt nichts. Weil der Bestand ausserdem in
sich gemischt war - 34 PHP-Dateien mit LF, 30 mit CRLF -, gab es gar kein
richtiges Zeilenende, an das ein Werkzeug sich haette halten koennen. Jede
Ergaenzung mit LF in einer CRLF-Datei ergab eine gemischte Datei; helper.php
und js/solar/homeMQTT.js waren bereits so entstanden.

.gitattributes macht die Zeilenenden zur Eigenschaft des Repositorys. LF,
weil dieses Verzeichnis der Web-Ordner der NAS ist und von Linux
ausgeliefert wird - und weil Git fuer Windows mit "input" ohnehin schon
dorthin zeigt.

restricted/WebAuthn bleibt ausgenommen und damit byteweise so, wie die
Bibliothek geliefert wurde; darunter liegen 292 Zertifikate.

Von den 47 geaenderten Dateien wurde nachgerechnet keine einzige im Inhalt
angefasst - der Vergleich HEAD-ohne-CR gegen Index ist ueberall gleich.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 23:04:00 +02:00
co-authored by Claude Opus 5
parent abdf6880eb
commit 93fb054b66
48 changed files with 13209 additions and 13192 deletions
@@ -1,479 +1,479 @@
#!/usr/bin/env python3
"""
Shelly Module
Enthält NUR Shelly-spezifische Geräte-Discovery Logik
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
# Logging konfigurieren
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class ShellyDiscovery:
"""Klasse zum Entdecken von Shelly-Geräten im Netzwerk"""
SHELLY_MDNS_SERVICE = "_http._tcp.local."
COMMON_PORTS = [80]
def __init__(self):
self.devices = []
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])
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)]
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}...")
for i in range(start_ip, end_ip + 1):
ip = f"{self.network_range}.{i}"
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
try:
result = sock.connect_ex((ip, 80))
if result == 0:
active_hosts.append(ip)
logger.debug(f"Host gefunden: {ip}")
except:
pass
finally:
sock.close()
logger.info(f"{len(active_hosts)} aktive Hosts gefunden")
return active_hosts
"""
def is_shelly_device(ip: str) -> Optional[Dict]:
"""
Prüft ob ein Host ein Shelly-Gerät ist
Args:
ip: IP-Adresse des Hosts
Returns:
Device Info Dict wenn Shelly, sonst None
"""
#logger.info(f"Suche Shelly Getät unter: {ip}")
try:
# Versuche Gen2 API (neuere Shelly-Geräte)
response = requests.get(
f"http://{ip}/rpc/Shelly.GetDeviceInfo",
timeout=2
)
if response.status_code == 200:
data = response.json()
logger.info(f"Shelly Gen2 Gerät gefunden: {ip} - {data.get('name', 'Unknown')}")
return {
'ip': ip,
'generation': 2,
'info': data
}
except:
pass
try:
# Versuche Gen1 API (ältere Shelly-Geräte)
response = requests.get(
f"http://{ip}/settings",
timeout=2
)
if response.status_code == 200:
data = response.json()
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,
'info': data
}
except:
pass
return None
def get_device_status(self, device: Dict) -> Optional[Dict]:
"""
Holt den Status eines Shelly-Geräts
Args:
device: Device Info Dictionary
Returns:
Status Dictionary oder None
"""
ip = device['ip']
try:
if device['generation'] == 2:
# Gen2 Status
response = requests.get(
f"http://{ip}/rpc/Shelly.GetStatus",
timeout=2
)
if response.status_code == 200:
return response.json()
else:
# Gen1 Status
response = requests.get(
f"http://{ip}/status",
timeout=2
)
if response.status_code == 200:
return response.json()
except Exception as e:
logger.error(f"Fehler beim Abrufen des Status von {ip}: {e}")
return None
def discover_devices(self) -> List[Dict]:
"""
Entdeckt alle Shelly-Geräte im Netzwerk
Returns:
Liste von Shelly-Geräten mit Status
"""
self.devices = self.scan_network()
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
# ============================================================================
# MODULE WRAPPER
# ============================================================================
class ShellyModule(BaseModule):
"""
Shelly Modul - Implementiert BaseModule Interface
Gibt Actors/Sensors zurück, KEINE DB-Operationen
"""
def is_enabled(self) -> bool:
"""Prüft ob Shelly aktiviert ist"""
return self.config.shelly_enable
def discover(self) -> Tuple[List[Dict], List[Dict]]:
"""
Führt Shelly Discovery durch
Returns:
Tuple (actors, sensors)
"""
logger.info("\n" + "=" * 60)
logger.info("SHELLY-GERÄTE WERDEN GESUCHT")
logger.info("=" * 60)
actors = []
sensors = []
try:
discovery = ShellyDiscovery()
devices = discovery.discover_devices()
if not devices:
logger.info("Keine Shelly-Geräte gefunden")
return actors, sensors
logger.info(f"{len(devices)} Shelly-Geräte gefunden")
# Verarbeite jedes Gerät
for device in devices:
if device['generation'] == 2:
device_actors, device_sensors = self._parse_gen2_device(device)
else:
device_actors, device_sensors = self._parse_gen1_device(device)
actors.extend(device_actors)
sensors.extend(device_sensors)
except Exception as e:
logger.error(f"✗ Shelly Discovery Fehler: {e}")
logger.info(f"Shelly: {len(actors)} Aktoren, {len(sensors)} Sensoren gefunden")
return actors, sensors
def _parse_gen2_device(self, device: Dict) -> Tuple[List[Dict], List[Dict]]:
"""Parst Gen2 Shelly-Gerät"""
actors = []
sensors = []
info = device.get('info', {})
status = device.get('status', {})
ip = device['ip']
device_name = info.get('name', f"Shelly_{info.get('id', ip)}")
device_model = info.get('model', 'Unknown')
# Switches als Aktoren
switch_count = sum(1 for key in status.keys() if key.startswith('switch:'))
for i in range(switch_count):
switch_data = status.get(f'switch:{i}', {})
actors.append({
'type': f'ShellySwitch_{device_model}'.replace(' ', '_'),
'name': f"{device_name}_Switch_{i}",
'url': f"http://{ip}/rpc/Switch.Set?id={i}",
'commands': [
{'command': 'turn_on', 'url': 'on=true', 'parameters': []},
{'command': 'turn_off', 'url': 'on=false', 'parameters': []},
{'command': 'toggle', 'parameters': []}
],
'states': [
{
'name': 'output',
'type': 'boolean',
'url': 'output',
'current_value': switch_data.get('output', False)
}
]
})
# Temperatursensoren
temp_count = sum(1 for key in status.keys() if key.startswith('temperature:'))
for i in range(temp_count):
temp_data = status.get(f'temperature:{i}', {})
sensors.append({
'type': 'Temperatur',
'name': f"{device_name}_Temp_{i}",
'url': f"http://{ip}/rpc/Temperature.GetStatus?id={i}",
'states': [
{
'name': 'temperature',
'type': 'number',
'current_value': temp_data.get('tC'),
'url': f"tC",
'unit': '°C'
}
]
})
# Energy-Meter
em_count = sum(1 for key in status.keys() if key.startswith('em:'))
for i in range(em_count):
em_data = status.get(f'em:{i}', {})
sensors.append({
'type': 'Stromzähler',
'name': f"{device_name}_EM_{i}",
'url': f"http://{ip}/rpc/em.GetStatus?id={i}",
'states': []
})
if(em_data.get('a_voltage') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Spannung Phase A',
'type': 'number',
'url': 'a_voltage',
'current_value': em_data.get('a_voltage'),
'unit': 'V'})
if(em_data.get('b_voltage') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Spannung Phase B',
'type': 'number',
'url': 'b_voltage',
'current_value': em_data.get('b_voltage'),
'unit': 'V'})
if(em_data.get('c_voltage') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Spannung Phase C',
'type': 'number',
'url': 'c_voltage',
'current_value': em_data.get('c_voltage'),
'unit': 'V'})
if(em_data.get('a_current') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Strom Phase A',
'type': 'number',
'url': 'a_current',
'current_value': em_data.get('a_current'),
'unit': 'A'})
if(em_data.get('b_current') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Strom Phase B',
'type': 'number',
'url': 'b_current',
'current_value': em_data.get('b_current'),
'unit': 'A'})
if(em_data.get('c_current') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Strom Phase C',
'type': 'number',
'url': 'c_current',
'current_value': em_data.get('c_current'),
'unit': 'A'})
if(em_data.get('a_act_power') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Wirkleistung Phase A',
'type': 'number',
'url': 'a_act_power',
'current_value': em_data.get('a_act_power'),
'unit': 'W'})
if(em_data.get('b_act_power') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Wirkleistung Phase B',
'type': 'number',
'url': 'b_act_power',
'current_value': em_data.get('b_act_power'),
'unit': 'W'})
if(em_data.get('c_act_power') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Wirkleistung Phase C',
'type': 'number',
'url': 'c_act_power',
'current_value': em_data.get('c_act_power'),
'unit': 'W'})
if(em_data.get('a_aprt_power') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Scheinleistung Phase A',
'type': 'number',
'url': 'a_aprt_power',
'current_value': em_data.get('a_aprt_power'),
'unit': 'VA'})
if(em_data.get('b_aprt_power') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Scheinleistung Phase B',
'type': 'number',
'url': 'b_aprt_power',
'current_value': em_data.get('b_aprt_power'),
'unit': 'VA'})
if(em_data.get('c_aprt_power') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Scheinleistung Phase C',
'type': 'number',
'url': 'c_aprt_power',
'current_value': em_data.get('c_aprt_power'),
'unit': 'VA'})
if(em_data.get('a_freq') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Frequenz Phase A',
'type': 'number',
'url': 'a_freq',
'current_value': em_data.get('a_freq'),
'unit': 'Hz'})
if(em_data.get('b_freq') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Frequenz Phase B',
'type': 'number',
'url': 'b_freq',
'current_value': em_data.get('b_freq'),
'unit': 'Hz'})
if(em_data.get('c_freq') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Frequenz Phase C',
'type': 'number',
'url': 'c_freq',
'current_value': em_data.get('c_freq'),
'unit': 'Hz'})
if(em_data.get('total_act_power') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Wirkleistung gesamt',
'type': 'number',
'url': 'total_act_power',
'current_value': em_data.get('total_act_power'),
'unit': 'W'})
return actors, sensors
def _parse_gen1_device(self, device: Dict) -> Tuple[List[Dict], List[Dict]]:
"""Parst Gen1 Shelly-Gerät"""
actors = []
sensors = []
info = device.get('info', {})
status = device.get('status', {})
ip = device['ip']
device_name = info.get('name', f"Shelly_{info.get('type', ip)}")
device_type = info['device'].get('type', 'Unknown')
# Relays als Aktoren
relays = status.get('relays', [])
for i, relay in enumerate(relays):
actors.append({
'type': f'ShellyRelay_{device_type}'.replace(' ', '_'),
'name': f"{device_name}_Relay_{i}",
'url': f"http://{ip}/relay/{i}",
'commands': [
{'command': 'turn_on', 'url': 'turn=on', 'parameters': []},
{'command': 'turn_off', 'url': 'turn=off', 'parameters': []},
{'command': 'toggle', 'url': 'turn=toggle', 'parameters': []}
],
'states': [
{
'name': 'ison',
'type': 'boolean',
'url': 'ison',
'current_value': relay.get('ison', False)
}
]
})
# Temperatursensoren
temp_data = status.get('tmp', {})
if temp_data and 'tC' in temp_data:
sensors.append({
'type': 'Temperatur',
'name': f"{device_name}_Temp",
'url': f"http://{ip}/status",
'states': [
{
'name': 'temperature',
'type': 'number',
'current_value': temp_data.get('tC'),
'unit': '°C'
}
]
})
#!/usr/bin/env python3
"""
Shelly Module
Enthält NUR Shelly-spezifische Geräte-Discovery Logik
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
# Logging konfigurieren
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class ShellyDiscovery:
"""Klasse zum Entdecken von Shelly-Geräten im Netzwerk"""
SHELLY_MDNS_SERVICE = "_http._tcp.local."
COMMON_PORTS = [80]
def __init__(self):
self.devices = []
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])
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)]
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}...")
for i in range(start_ip, end_ip + 1):
ip = f"{self.network_range}.{i}"
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
try:
result = sock.connect_ex((ip, 80))
if result == 0:
active_hosts.append(ip)
logger.debug(f"Host gefunden: {ip}")
except:
pass
finally:
sock.close()
logger.info(f"{len(active_hosts)} aktive Hosts gefunden")
return active_hosts
"""
def is_shelly_device(ip: str) -> Optional[Dict]:
"""
Prüft ob ein Host ein Shelly-Gerät ist
Args:
ip: IP-Adresse des Hosts
Returns:
Device Info Dict wenn Shelly, sonst None
"""
#logger.info(f"Suche Shelly Getät unter: {ip}")
try:
# Versuche Gen2 API (neuere Shelly-Geräte)
response = requests.get(
f"http://{ip}/rpc/Shelly.GetDeviceInfo",
timeout=2
)
if response.status_code == 200:
data = response.json()
logger.info(f"Shelly Gen2 Gerät gefunden: {ip} - {data.get('name', 'Unknown')}")
return {
'ip': ip,
'generation': 2,
'info': data
}
except:
pass
try:
# Versuche Gen1 API (ältere Shelly-Geräte)
response = requests.get(
f"http://{ip}/settings",
timeout=2
)
if response.status_code == 200:
data = response.json()
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,
'info': data
}
except:
pass
return None
def get_device_status(self, device: Dict) -> Optional[Dict]:
"""
Holt den Status eines Shelly-Geräts
Args:
device: Device Info Dictionary
Returns:
Status Dictionary oder None
"""
ip = device['ip']
try:
if device['generation'] == 2:
# Gen2 Status
response = requests.get(
f"http://{ip}/rpc/Shelly.GetStatus",
timeout=2
)
if response.status_code == 200:
return response.json()
else:
# Gen1 Status
response = requests.get(
f"http://{ip}/status",
timeout=2
)
if response.status_code == 200:
return response.json()
except Exception as e:
logger.error(f"Fehler beim Abrufen des Status von {ip}: {e}")
return None
def discover_devices(self) -> List[Dict]:
"""
Entdeckt alle Shelly-Geräte im Netzwerk
Returns:
Liste von Shelly-Geräten mit Status
"""
self.devices = self.scan_network()
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
# ============================================================================
# MODULE WRAPPER
# ============================================================================
class ShellyModule(BaseModule):
"""
Shelly Modul - Implementiert BaseModule Interface
Gibt Actors/Sensors zurück, KEINE DB-Operationen
"""
def is_enabled(self) -> bool:
"""Prüft ob Shelly aktiviert ist"""
return self.config.shelly_enable
def discover(self) -> Tuple[List[Dict], List[Dict]]:
"""
Führt Shelly Discovery durch
Returns:
Tuple (actors, sensors)
"""
logger.info("\n" + "=" * 60)
logger.info("SHELLY-GERÄTE WERDEN GESUCHT")
logger.info("=" * 60)
actors = []
sensors = []
try:
discovery = ShellyDiscovery()
devices = discovery.discover_devices()
if not devices:
logger.info("Keine Shelly-Geräte gefunden")
return actors, sensors
logger.info(f"{len(devices)} Shelly-Geräte gefunden")
# Verarbeite jedes Gerät
for device in devices:
if device['generation'] == 2:
device_actors, device_sensors = self._parse_gen2_device(device)
else:
device_actors, device_sensors = self._parse_gen1_device(device)
actors.extend(device_actors)
sensors.extend(device_sensors)
except Exception as e:
logger.error(f"✗ Shelly Discovery Fehler: {e}")
logger.info(f"Shelly: {len(actors)} Aktoren, {len(sensors)} Sensoren gefunden")
return actors, sensors
def _parse_gen2_device(self, device: Dict) -> Tuple[List[Dict], List[Dict]]:
"""Parst Gen2 Shelly-Gerät"""
actors = []
sensors = []
info = device.get('info', {})
status = device.get('status', {})
ip = device['ip']
device_name = info.get('name', f"Shelly_{info.get('id', ip)}")
device_model = info.get('model', 'Unknown')
# Switches als Aktoren
switch_count = sum(1 for key in status.keys() if key.startswith('switch:'))
for i in range(switch_count):
switch_data = status.get(f'switch:{i}', {})
actors.append({
'type': f'ShellySwitch_{device_model}'.replace(' ', '_'),
'name': f"{device_name}_Switch_{i}",
'url': f"http://{ip}/rpc/Switch.Set?id={i}",
'commands': [
{'command': 'turn_on', 'url': 'on=true', 'parameters': []},
{'command': 'turn_off', 'url': 'on=false', 'parameters': []},
{'command': 'toggle', 'parameters': []}
],
'states': [
{
'name': 'output',
'type': 'boolean',
'url': 'output',
'current_value': switch_data.get('output', False)
}
]
})
# Temperatursensoren
temp_count = sum(1 for key in status.keys() if key.startswith('temperature:'))
for i in range(temp_count):
temp_data = status.get(f'temperature:{i}', {})
sensors.append({
'type': 'Temperatur',
'name': f"{device_name}_Temp_{i}",
'url': f"http://{ip}/rpc/Temperature.GetStatus?id={i}",
'states': [
{
'name': 'temperature',
'type': 'number',
'current_value': temp_data.get('tC'),
'url': f"tC",
'unit': '°C'
}
]
})
# Energy-Meter
em_count = sum(1 for key in status.keys() if key.startswith('em:'))
for i in range(em_count):
em_data = status.get(f'em:{i}', {})
sensors.append({
'type': 'Stromzähler',
'name': f"{device_name}_EM_{i}",
'url': f"http://{ip}/rpc/em.GetStatus?id={i}",
'states': []
})
if(em_data.get('a_voltage') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Spannung Phase A',
'type': 'number',
'url': 'a_voltage',
'current_value': em_data.get('a_voltage'),
'unit': 'V'})
if(em_data.get('b_voltage') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Spannung Phase B',
'type': 'number',
'url': 'b_voltage',
'current_value': em_data.get('b_voltage'),
'unit': 'V'})
if(em_data.get('c_voltage') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Spannung Phase C',
'type': 'number',
'url': 'c_voltage',
'current_value': em_data.get('c_voltage'),
'unit': 'V'})
if(em_data.get('a_current') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Strom Phase A',
'type': 'number',
'url': 'a_current',
'current_value': em_data.get('a_current'),
'unit': 'A'})
if(em_data.get('b_current') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Strom Phase B',
'type': 'number',
'url': 'b_current',
'current_value': em_data.get('b_current'),
'unit': 'A'})
if(em_data.get('c_current') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Strom Phase C',
'type': 'number',
'url': 'c_current',
'current_value': em_data.get('c_current'),
'unit': 'A'})
if(em_data.get('a_act_power') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Wirkleistung Phase A',
'type': 'number',
'url': 'a_act_power',
'current_value': em_data.get('a_act_power'),
'unit': 'W'})
if(em_data.get('b_act_power') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Wirkleistung Phase B',
'type': 'number',
'url': 'b_act_power',
'current_value': em_data.get('b_act_power'),
'unit': 'W'})
if(em_data.get('c_act_power') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Wirkleistung Phase C',
'type': 'number',
'url': 'c_act_power',
'current_value': em_data.get('c_act_power'),
'unit': 'W'})
if(em_data.get('a_aprt_power') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Scheinleistung Phase A',
'type': 'number',
'url': 'a_aprt_power',
'current_value': em_data.get('a_aprt_power'),
'unit': 'VA'})
if(em_data.get('b_aprt_power') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Scheinleistung Phase B',
'type': 'number',
'url': 'b_aprt_power',
'current_value': em_data.get('b_aprt_power'),
'unit': 'VA'})
if(em_data.get('c_aprt_power') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Scheinleistung Phase C',
'type': 'number',
'url': 'c_aprt_power',
'current_value': em_data.get('c_aprt_power'),
'unit': 'VA'})
if(em_data.get('a_freq') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Frequenz Phase A',
'type': 'number',
'url': 'a_freq',
'current_value': em_data.get('a_freq'),
'unit': 'Hz'})
if(em_data.get('b_freq') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Frequenz Phase B',
'type': 'number',
'url': 'b_freq',
'current_value': em_data.get('b_freq'),
'unit': 'Hz'})
if(em_data.get('c_freq') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Frequenz Phase C',
'type': 'number',
'url': 'c_freq',
'current_value': em_data.get('c_freq'),
'unit': 'Hz'})
if(em_data.get('total_act_power') is not None):
sensors[len(sensors)-1]['states'].append({
'name': 'Wirkleistung gesamt',
'type': 'number',
'url': 'total_act_power',
'current_value': em_data.get('total_act_power'),
'unit': 'W'})
return actors, sensors
def _parse_gen1_device(self, device: Dict) -> Tuple[List[Dict], List[Dict]]:
"""Parst Gen1 Shelly-Gerät"""
actors = []
sensors = []
info = device.get('info', {})
status = device.get('status', {})
ip = device['ip']
device_name = info.get('name', f"Shelly_{info.get('type', ip)}")
device_type = info['device'].get('type', 'Unknown')
# Relays als Aktoren
relays = status.get('relays', [])
for i, relay in enumerate(relays):
actors.append({
'type': f'ShellyRelay_{device_type}'.replace(' ', '_'),
'name': f"{device_name}_Relay_{i}",
'url': f"http://{ip}/relay/{i}",
'commands': [
{'command': 'turn_on', 'url': 'turn=on', 'parameters': []},
{'command': 'turn_off', 'url': 'turn=off', 'parameters': []},
{'command': 'toggle', 'url': 'turn=toggle', 'parameters': []}
],
'states': [
{
'name': 'ison',
'type': 'boolean',
'url': 'ison',
'current_value': relay.get('ison', False)
}
]
})
# Temperatursensoren
temp_data = status.get('tmp', {})
if temp_data and 'tC' in temp_data:
sensors.append({
'type': 'Temperatur',
'name': f"{device_name}_Temp",
'url': f"http://{ip}/status",
'states': [
{
'name': 'temperature',
'type': 'number',
'current_value': temp_data.get('tC'),
'unit': '°C'
}
]
})
return actors, sensors