Update custom_components/aurora_solar/sensor.py
This commit is contained in:
@@ -1,56 +1,87 @@
|
|||||||
"""Support for ABB Aurora Solar Inverters via Waveshare RS485-to-Ethernet adapter."""
|
"""Support for ABB Aurora Solar Inverters via Waveshare RS485-to-Ethernet adapter."""
|
||||||
from homeassistant.components.sensor import SensorEntity
|
from homeassistant.components.sensor import SensorEntity
|
||||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||||
import socket
|
|
||||||
import logging
|
import logging
|
||||||
|
from aurorapy.client import AuroraTCPClient, AuroraError
|
||||||
|
|
||||||
from .const import DOMAIN, CONF_SLAVE_ID
|
from .const import DOMAIN, CONF_SLAVE_ID
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Vollständige ABB Aurora Befehlsliste (inkl. Diagnose, Seriennummern, Events)
|
class AuroraSensorBase(SensorEntity):
|
||||||
COMMANDS = {
|
"""Basis-Klasse für alle ABB Aurora Sensoren."""
|
||||||
# Standard-Sensoren
|
|
||||||
"DSP_GRID_POWER": b"\x30\x33\x0D", # Netzleistung (W)
|
|
||||||
"DSP_DAILY_ENERGY": b"\x31\x33\x0D", # Tagesenergie (Wh)
|
|
||||||
"DSP_TOTAL_ENERGY": b"\x31\x34\x0D", # Gesamtenergie (kWh)
|
|
||||||
"DSP_GRID_VOLTAGE": b"\x32\x33\x0D", # Netzspannung (V)
|
|
||||||
"DSP_GRID_CURRENT": b"\x33\x33\x0D", # Netzstrom (A)
|
|
||||||
"DSP_GRID_FREQUENCY": b"\x34\x33\x0D", # Netzfrequenz (Hz)
|
|
||||||
"DSP_TEMPERATURE": b"\x35\x33\x0D", # Temperatur (°C)
|
|
||||||
"DSP_DC_VOLTAGE": b"\x36\x33\x0D", # Gleichspannung (V)
|
|
||||||
"DSP_DC_CURRENT": b"\x37\x33\x0D", # Gleichstrom (A)
|
|
||||||
"DSP_DC_POWER": b"\x38\x33\x0D", # Gleichleistung (W)
|
|
||||||
"DSP_EFFICIENCY": b"\x39\x33\x0D", # Wirkungsgrad (%)
|
|
||||||
"DSP_PF": b"\x3A\x33\x0D", # Leistungsfaktor
|
|
||||||
"DSP_AC_VOLTAGE_PHASE": b"\x3B\x33\x0D", # Phasenspannung (V)
|
|
||||||
"DSP_DC_VOLTAGE2": b"\x3C\x33\x0D", # Gleichspannung 2 (falls vorhanden)
|
|
||||||
"DSP_DC_CURRENT2": b"\x3D\x33\x0D", # Gleichstrom 2 (falls vorhanden)
|
|
||||||
"DSP_RADIATOR_TEMP": b"\x3E\x33\x0D", # Kühlkörpertemperatur (°C)
|
|
||||||
|
|
||||||
# Diagnose und Status
|
def __init__(self, host, port, slave_id, name, sensor_type, unit, factor=1, is_string=False, text_mapping=None):
|
||||||
"DSP_ALARMS": b"\x50\x33\x0D", # Alarme (Bitmaske)
|
"""Initialisiere den Sensor."""
|
||||||
"DSP_STATUS": b"\x51\x33\x0D", # Betriebsstatus
|
self._host = host
|
||||||
"DSP_EVENTS": b"\x52\x33\x0D", # Ereignisse (z. B. Start/Stop)
|
self._port = port
|
||||||
"DSP_FAULT_CODE": b"\x53\x33\x0D", # Fehlercode
|
self._slave_id = slave_id
|
||||||
"DSP_SERIAL_NUMBER": b"\x54\x33\x0D", # Seriennummer (String)
|
self._name = name
|
||||||
"DSP_VERSION": b"\x55\x33\x0D", # Software-Version
|
self._sensor_type = sensor_type
|
||||||
"DSP_MODEL": b"\x56\x33\x0D", # Modellbezeichnung
|
self._unit = unit
|
||||||
"DSP_DC_INPUT_VOLTAGE": b"\x57\x33\x0D", # PV-Eingangsspannung (V)
|
self._factor = factor
|
||||||
"DSP_MPPT_POWER": b"\x58\x33\x0D", # MPPT-Leistung (W)
|
self._is_string = is_string
|
||||||
"DSP_ISOLATION": b"\x59\x33\x0D", # Isolationswiderstand (kΩ)
|
self._text_mapping = text_mapping
|
||||||
"DSP_AMBIENT_TEMP": b"\x5A\x33\x0D", # Umgebungs-Temperatur (°C)
|
self._state = None
|
||||||
|
self._attr_native_unit_of_measurement = unit if not is_string else None
|
||||||
|
self._attr_unique_id = f"aurora_{slave_id}_{sensor_type.lower()}"
|
||||||
|
|
||||||
# Erweitere Diagnose (falls unterstützt)
|
@property
|
||||||
"DSP_DC_POWER2": b"\x5B\x33\x0D", # MPPT2-Leistung (W)
|
def name(self):
|
||||||
"DSP_DC_VOLTAGE3": b"\x5C\x33\x0D", # MPPT3-Spannung (V)
|
"""Return the name of the sensor."""
|
||||||
"DSP_LAST_ERROR": b"\x5D\x33\x0D", # Letzter Fehler (Code)
|
return self._name
|
||||||
"DSP_OPERATING_HOURS": b"\x5E\x33\x0D", # Betriebsstunden (h)
|
|
||||||
"DSP_GRID_POWER_LIMIT": b"\x5F\x33\x0D", # Netzleistungsbegrenzung (%)
|
|
||||||
|
|
||||||
# Spezifische Events (z. B. Relaiszustände)
|
@property
|
||||||
"DSP_RELAY_STATUS": b"\x60\x33\x0D", # Relais-Status
|
def state(self):
|
||||||
}
|
"""Return the state of the sensor."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Fetch new state data for the sensor."""
|
||||||
|
try:
|
||||||
|
client = AuroraTCPClient(ip=self._host, port=self._port, address=self._slave_id, timeout=10)
|
||||||
|
client.connect()
|
||||||
|
|
||||||
|
if self._sensor_type == "DSP_GRID_POWER":
|
||||||
|
self._state = client.measure(3) * self._factor
|
||||||
|
elif self._sensor_type == "DSP_DAILY_ENERGY":
|
||||||
|
self._state = client.cumulated_energy(0)
|
||||||
|
elif self._sensor_type == "DSP_TOTAL_ENERGY":
|
||||||
|
self._state = client.cumulated_energy(1) * 0.1
|
||||||
|
elif self._sensor_type == "DSP_GRID_VOLTAGE":
|
||||||
|
self._state = client.measure(6)
|
||||||
|
elif self._sensor_type == "DSP_GRID_CURRENT":
|
||||||
|
self._state = client.measure(7)
|
||||||
|
elif self._sensor_type == "DSP_GRID_FREQUENCY":
|
||||||
|
self._state = client.measure(8)
|
||||||
|
elif self._sensor_type == "DSP_PF":
|
||||||
|
self._state = client.measure(9) * 0.01
|
||||||
|
elif self._sensor_type == "DSP_DC_VOLTAGE":
|
||||||
|
self._state = client.measure(10)
|
||||||
|
elif self._sensor_type == "DSP_DC_CURRENT":
|
||||||
|
self._state = client.measure(11)
|
||||||
|
elif self._sensor_type == "DSP_DC_POWER":
|
||||||
|
self._state = client.measure(12)
|
||||||
|
elif self._sensor_type == "DSP_TEMPERATURE":
|
||||||
|
self._state = client.measure(13)
|
||||||
|
elif self._sensor_type == "DSP_SERIAL_NUMBER":
|
||||||
|
self._state = client.serial_number()
|
||||||
|
elif self._sensor_type == "DSP_VERSION":
|
||||||
|
self._state = client.version()
|
||||||
|
elif self._sensor_type == "DSP_ALARMS":
|
||||||
|
alarms = client.alarms()
|
||||||
|
self._state = self._text_mapping.get(alarms, f"Unbekannt (0x{alarms:04X})")
|
||||||
|
elif self._sensor_type == "DSP_STATUS":
|
||||||
|
status = client.status()
|
||||||
|
self._state = self._text_mapping.get(status, f"Unbekannt (0x{status:04X})")
|
||||||
|
elif self._sensor_type == "DSP_FAULT_CODE":
|
||||||
|
fault = client.fault_code()
|
||||||
|
self._state = self._text_mapping.get(fault, f"Unbekannt (0x{fault:04X})")
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
except AuroraError as e:
|
||||||
|
self._state = None
|
||||||
|
_LOGGER.error("Fehler bei %s: %s", self._name, e)
|
||||||
|
|
||||||
# Mapping für lesbare Texte
|
# Mapping für lesbare Texte
|
||||||
ALARM_MESSAGES = {
|
ALARM_MESSAGES = {
|
||||||
@@ -76,112 +107,39 @@ FAULT_MESSAGES = {
|
|||||||
0x0002: "Kommunikationsfehler",
|
0x0002: "Kommunikationsfehler",
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuroraSensorBase(SensorEntity):
|
|
||||||
"""Basis-Klasse für alle ABB Aurora Sensoren."""
|
|
||||||
|
|
||||||
def __init__(self, host, port, slave_id, name, command_key, unit, data_index=0, factor=1, is_string=False, text_mapping=None):
|
|
||||||
"""Initialisiere den Sensor."""
|
|
||||||
self._host = host
|
|
||||||
self._port = port
|
|
||||||
self._slave_id = slave_id
|
|
||||||
self._name = f"{name} {command_key.split('_')[-1].title()}"
|
|
||||||
self._command = bytes([slave_id]) + COMMANDS[command_key]
|
|
||||||
self._unit = unit
|
|
||||||
self._data_index = data_index
|
|
||||||
self._factor = factor
|
|
||||||
self._is_string = is_string
|
|
||||||
self._text_mapping = text_mapping # Für lesbare Texte
|
|
||||||
self._state = None
|
|
||||||
self._attr_native_unit_of_measurement = unit if not is_string else None
|
|
||||||
self._attr_unique_id = f"aurora_{slave_id}_{command_key.lower()}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def state(self):
|
|
||||||
"""Aktueller Zustand des Sensors."""
|
|
||||||
return self._state
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""Aktualisiere die Sensordaten."""
|
|
||||||
try:
|
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
||||||
s.settimeout(3)
|
|
||||||
s.connect((self._host, self._port))
|
|
||||||
s.send(self._command)
|
|
||||||
response = s.recv(1024)
|
|
||||||
if response:
|
|
||||||
if self._is_string:
|
|
||||||
# Seriennummer/Modell als String
|
|
||||||
self._state = response[self._data_index:].decode('ascii').strip()
|
|
||||||
elif self._text_mapping:
|
|
||||||
# Alarme/Status/Fehler als lesbarer Text
|
|
||||||
raw_value = int.from_bytes(response[:2], byteorder='little', signed=False)
|
|
||||||
self._state = self._text_mapping.get(raw_value, f"Unbekannt (0x{raw_value:04X})")
|
|
||||||
else:
|
|
||||||
# Standardwerte (Integer mit Skalierung)
|
|
||||||
self._state = int.from_bytes(
|
|
||||||
response[self._data_index:self._data_index + 2],
|
|
||||||
byteorder='little',
|
|
||||||
signed=True
|
|
||||||
) * self._factor
|
|
||||||
else:
|
|
||||||
self._state = None
|
|
||||||
_LOGGER.warning("Keine Antwort für %s", self._name)
|
|
||||||
except Exception as e:
|
|
||||||
self._state = None
|
|
||||||
_LOGGER.error("Fehler bei %s: %s", self._name, e)
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Richte ALLE ABB Aurora Sensoren ein."""
|
"""Set up the ABB Aurora sensors."""
|
||||||
host = config[CONF_HOST]
|
host = config[CONF_HOST]
|
||||||
port = config[CONF_PORT]
|
port = config[CONF_PORT]
|
||||||
slave_id = config.get(CONF_SLAVE_ID, 2)
|
slave_id = config.get(CONF_SLAVE_ID, 2)
|
||||||
name = config.get("name", f"Aurora WR {slave_id}")
|
name = config.get("name", f"Aurora WR {slave_id}")
|
||||||
|
|
||||||
# Erstelle alle Sensoren für diesen Wechselrichter
|
# Create all sensors for this inverter
|
||||||
sensors = [
|
sensors = [
|
||||||
# Leistung und Energie
|
# Leistung und Energie
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_GRID_POWER", "W"),
|
AuroraSensorBase(host, port, slave_id, f"{name} Power", "DSP_GRID_POWER", "W"),
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_DAILY_ENERGY", "Wh", data_index=2),
|
AuroraSensorBase(host, port, slave_id, f"{name} Daily Energy", "DSP_DAILY_ENERGY", "Wh"),
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_TOTAL_ENERGY", "kWh", factor=0.1),
|
AuroraSensorBase(host, port, slave_id, f"{name} Total Energy", "DSP_TOTAL_ENERGY", "kWh"),
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_GRID_VOLTAGE", "V"),
|
AuroraSensorBase(host, port, slave_id, f"{name} Grid Voltage", "DSP_GRID_VOLTAGE", "V"),
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_GRID_CURRENT", "A"),
|
AuroraSensorBase(host, port, slave_id, f"{name} Grid Current", "DSP_GRID_CURRENT", "A"),
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_GRID_FREQUENCY", "Hz"),
|
AuroraSensorBase(host, port, slave_id, f"{name} Grid Frequency", "DSP_GRID_FREQUENCY", "Hz"),
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_PF", "", 0, 0.01), # Skaliert mit 0.01
|
AuroraSensorBase(host, port, slave_id, f"{name} PF", "DSP_PF", "", 0.01),
|
||||||
|
|
||||||
# Gleichstromkreis
|
# Gleichstromkreis
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_DC_VOLTAGE", "V"),
|
AuroraSensorBase(host, port, slave_id, f"{name} DC Voltage", "DSP_DC_VOLTAGE", "V"),
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_DC_CURRENT", "A"),
|
AuroraSensorBase(host, port, slave_id, f"{name} DC Current", "DSP_DC_CURRENT", "A"),
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_DC_POWER", "W"),
|
AuroraSensorBase(host, port, slave_id, f"{name} DC Power", "DSP_DC_POWER", "W"),
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_MPPT_POWER", "W"),
|
|
||||||
|
|
||||||
# Temperatur und Umwelt
|
# Temperatur und Umwelt
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_TEMPERATURE", "°C"),
|
AuroraSensorBase(host, port, slave_id, f"{name} Temperature", "DSP_TEMPERATURE", "°C"),
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_RADIATOR_TEMP", "°C"),
|
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_AMBIENT_TEMP", "°C"),
|
|
||||||
|
|
||||||
# Diagnose (wichtig!)
|
# Diagnose (wichtig!)
|
||||||
AuroraSensorBase(
|
AuroraSensorBase(host, port, slave_id, f"{name} Alarms", "DSP_ALARMS", "", text_mapping=ALARM_MESSAGES),
|
||||||
host, port, slave_id, name, "DSP_ALARMS", "",
|
AuroraSensorBase(host, port, slave_id, f"{name} Status", "DSP_STATUS", "", text_mapping=STATUS_MESSAGES),
|
||||||
text_mapping=ALARM_MESSAGES
|
AuroraSensorBase(host, port, slave_id, f"{name} Fault Code", "DSP_FAULT_CODE", "", text_mapping=FAULT_MESSAGES),
|
||||||
),
|
|
||||||
AuroraSensorBase(
|
|
||||||
host, port, slave_id, name, "DSP_FAULT_CODE", "",
|
|
||||||
text_mapping=FAULT_MESSAGES
|
|
||||||
),
|
|
||||||
AuroraSensorBase(
|
|
||||||
host, port, slave_id, name, "DSP_STATUS", "",
|
|
||||||
text_mapping=STATUS_MESSAGES
|
|
||||||
),
|
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_EVENTS", "", is_string=False),
|
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_LAST_ERROR", "", is_string=False),
|
|
||||||
|
|
||||||
# Seriennummern und Modell (als String)
|
# Seriennummern und Modell (als String)
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_SERIAL_NUMBER", "", is_string=True),
|
AuroraSensorBase(host, port, slave_id, f"{name} Serial Number", "DSP_SERIAL_NUMBER", "", is_string=True),
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_MODEL", "", is_string=True),
|
AuroraSensorBase(host, port, slave_id, f"{name} Version", "DSP_VERSION", "", is_string=True),
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_VERSION", "", is_string=True),
|
|
||||||
|
|
||||||
# Erweiterte Diagnose
|
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_ISOLATION", "kΩ"),
|
|
||||||
AuroraSensorBase(host, port, slave_id, name, "DSP_OPERATING_HOURS", "h"),
|
|
||||||
]
|
]
|
||||||
add_entities(sensors, True)
|
add_entities(sensors, True)
|
||||||
|
|||||||
Reference in New Issue
Block a user