Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
Empty file.
33 changes: 33 additions & 0 deletions packages/modules/consumers/daikin/daikin_homehub_air2air/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from typing import Optional, Tuple

from control.consumer.consumer_data import ConsumerUsage
from helpermodules.auto_str import auto_str
from modules.common.consumer_setup import ConsumerSetup
from ..vendor import vendor_descriptor


@auto_str
class DaikinConfiguration:
def __init__(self,
ip_address: Optional[str] = None,
port: int = 502,
modbus_id: int = 1):
self.ip_address = ip_address
self.port = port
self.modbus_id = modbus_id


@auto_str
class Daikin(ConsumerSetup[DaikinConfiguration]):
def __init__(self,
name: str = "Daikin Air2Air (HomeHub EKRHH)",
type: str = "daikin_homehub_air2air",
id: int = 0,
configuration: Optional[DaikinConfiguration] = None,
# Register gegen das offizielle EKRHH Installer Reference Guide (4PDE744838,
# Kap. 10 "Modbus TCP/IP oder RTU für Luft-zu-Luft-Wärmepumpe") geprüft.
usage: Tuple[ConsumerUsage, ...] = (ConsumerUsage.SUSPENDABLE_ONOFF,
ConsumerUsage.SUSPENDABLE_TUNABLE),
**kwargs) -> None:
super().__init__(name, type, id, vendor=vendor_descriptor.configuration_factory(
).type, configuration=configuration or DaikinConfiguration(), usage=usage, **kwargs)
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
import logging
from typing import Optional

from modules.common.abstract_device import DeviceDescriptor
from modules.common.configurable_consumer import ConfigurableConsumer, SetLimitData
from modules.common.modbus import ModbusDataType, ModbusTcpClient_
from modules.consumers.daikin.daikin_homehub_air2air.config import Daikin

log = logging.getLogger(__name__)


def create_consumer(config: Daikin):
client: Optional[ModbusTcpClient_] = None

def initializer():
nonlocal client
client = ModbusTcpClient_(config.configuration.ip_address, config.configuration.port)

def switch_on() -> None:
# Reg 1000: Smart-Grid-Modus, 2 = Empfehlung ein (Boost)
client.write_register(1000, 2, data_type=ModbusDataType.INT_16, unit=config.configuration.modbus_id)

def switch_off() -> None:
# Reg 1000: Smart-Grid-Modus, 0 = freier Betrieb (Normal)
client.write_register(1000, 0, data_type=ModbusDataType.INT_16, unit=config.configuration.modbus_id)

def set_power_limit(power_limit: Optional[float], data: SetLimitData) -> None:
# Reg 1001: Leistungsgrenze für Bedarfssteuerung, Pow16 (raw = kW * 100), lt. offiziellem
# Daikin-Referenzhandbuch (EKRHH Installer Reference Guide, Kap. 10.2.1/10.2). Bereich 0..20kW.
# Die Einheit setzt intern trotzdem eine Untergrenze von 40% der Nennleistung der
# Außeneinheit um - Werte darunter reduzieren die Leistung nicht weiter; echtes Abschalten
# läuft über switch_off() (Reg 1000, Zwangsabschaltung).
value = 0 if power_limit is None else round(max(power_limit, 0) / 10)
client.write_register(1001, value, data_type=ModbusDataType.INT_16, unit=config.configuration.modbus_id)

# Keine update()-Funktion: die HomeHub-Anbindung stellt für Air2Air-Geräte keine
# Leistungsmessung zur Verfügung. Wer den Verbrauch erfassen will, braucht eine separate
# Leistungsmessung (zB Zwischenzähler/Smart-Plug), die diesem Verbraucher zugeordnet wird.
return ConfigurableConsumer(consumer_config=config,
initializer=initializer,
switch_on=switch_on,
switch_off=switch_off,
set_power_limit=set_power_limit)


device_descriptor = DeviceDescriptor(configuration_factory=Daikin)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from typing import Optional, Tuple

from control.consumer.consumer_data import ConsumerUsage
from helpermodules.auto_str import auto_str
from modules.common.consumer_setup import ConsumerSetup
from ..vendor import vendor_descriptor


@auto_str
class DaikinAlthermaConfiguration:
def __init__(self,
ip_address: Optional[str] = None,
port: int = 502,
modbus_id: int = 1):
self.ip_address = ip_address
self.port = port
self.modbus_id = modbus_id


@auto_str
class DaikinAltherma(ConsumerSetup[DaikinAlthermaConfiguration]):
def __init__(self,
name: str = "Daikin Altherma (HomeHub)",
type: str = "daikin_homehub_altherma",
id: int = 0,
configuration: Optional[DaikinAlthermaConfiguration] = None,
# Register gegen das offizielle EKRHH Installer Reference Guide (4PDE744838,
# Kap. 9 "Modbus TCP/IP oder RTU für Daikin Altherma") geprüft.
usage: Tuple[ConsumerUsage, ...] = (ConsumerUsage.SUSPENDABLE_ONOFF,
ConsumerUsage.SUSPENDABLE_TUNABLE,
ConsumerUsage.METER_ONLY),
**kwargs) -> None:
super().__init__(name, type, id, vendor=vendor_descriptor.configuration_factory(
).type, configuration=configuration or DaikinAlthermaConfiguration(), usage=usage, **kwargs)
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
import logging
from typing import Optional

from modules.common.abstract_device import DeviceDescriptor
from modules.common.component_state import ConsumerState
from modules.common.component_type import ComponentType
from modules.common.configurable_consumer import ConfigurableConsumer, SetLimitData
from modules.common.modbus import ModbusDataType, ModbusTcpClient_
from modules.common.simcount._simcounter import SimCounterConsumer
from modules.consumers.daikin.daikin_homehub_altherma.config import DaikinAltherma

log = logging.getLogger(__name__)


def create_consumer(config: DaikinAltherma):
client: Optional[ModbusTcpClient_] = None
sim_counter: Optional[SimCounterConsumer] = None

def initializer():
nonlocal client, sim_counter
client = ModbusTcpClient_(config.configuration.ip_address, config.configuration.port)
sim_counter = SimCounterConsumer(config.id, ComponentType.CONSUMER)

def update() -> ConsumerState:
# Reg 51: Stromverbrauch Wärmepumpe, Pow16 (raw = kW * 100)
power = client.read_input_registers(
50, ModbusDataType.INT_16, unit=config.configuration.modbus_id) * 10
imported, exported = sim_counter.sim_count(power)
return ConsumerState(power=power, imported=imported, exported=exported)

def switch_on() -> None:
# Reg 56: Smart-Grid-Modus, 2 = Empfehlung ein (Boost)
client.write_register(55, 2, data_type=ModbusDataType.INT_16, unit=config.configuration.modbus_id)

def switch_off() -> None:
# Reg 56: Smart-Grid-Modus, 0 = freier Betrieb (Normal)
client.write_register(55, 0, data_type=ModbusDataType.INT_16, unit=config.configuration.modbus_id)

def set_power_limit(power_limit: Optional[float], data: SetLimitData) -> None:
# Reg 58: Allgemeine Leistungsbegrenzung, Pow16 (raw = kW * 100), lt. offiziellem
# Daikin-Referenzhandbuch (EKRHH Installer Reference Guide, Kap. 9.2.1/9.3). Bereich 0..20kW,
# gilt laut Referenzhandbuch unabhängig vom Smart-Grid-Modus (anders als Reg 57, das nur
# während der Pufferung im Modus "Empfehlung ein" greift).
# Die Einheit setzt intern trotzdem eine Untergrenze von 4,5kW für 15 Minuten nach
# Verdichterstart durch - Werte darunter reduzieren die Leistung in dieser Zeit nicht weiter;
# echtes Abschalten läuft über switch_off() (Reg 56, Zwangsabschaltung).
value = 0 if power_limit is None else round(max(power_limit, 0) / 10)
client.write_register(57, value, data_type=ModbusDataType.INT_16, unit=config.configuration.modbus_id)

return ConfigurableConsumer(consumer_config=config,
initializer=initializer,
update=update,
switch_on=switch_on,
switch_off=switch_off,
set_power_limit=set_power_limit)


device_descriptor = DeviceDescriptor(configuration_factory=DaikinAltherma)
14 changes: 14 additions & 0 deletions packages/modules/consumers/daikin/vendor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from pathlib import Path

from modules.common.abstract_device import DeviceDescriptor
from modules.devices.vendors import VendorGroup


class Vendor:
def __init__(self):
self.type = Path(__file__).parent.name
self.vendor = "Daikin"
self.group = VendorGroup.VENDORS.value


vendor_descriptor = DeviceDescriptor(configuration_factory=Vendor)
Empty file.
Empty file.
32 changes: 32 additions & 0 deletions packages/modules/consumers/ego/ego/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from typing import Optional, Tuple

from control.consumer.consumer_data import ConsumerUsage
from helpermodules.auto_str import auto_str
from modules.common.consumer_setup import ConsumerSetup
from ..vendor import vendor_descriptor


@auto_str
class EgoConfiguration:
def __init__(self,
ip_address: Optional[str] = None,
port: int = 502,
modbus_id: int = 247):
self.ip_address = ip_address
self.port = port
self.modbus_id = modbus_id


@auto_str
class Ego(ConsumerSetup[EgoConfiguration]):
def __init__(self,
name: str = "E.G.O. Smart Heater",
type: str = "ego",
id: int = 0,
configuration: Optional[EgoConfiguration] = None,
usage: Tuple[ConsumerUsage, ...] = (ConsumerUsage.SUSPENDABLE_TUNABLE,
ConsumerUsage.SELF_CONTROLLED,
ConsumerUsage.METER_ONLY),
**kwargs) -> None:
super().__init__(name, type, id, vendor=vendor_descriptor.configuration_factory(
).type, configuration=configuration or EgoConfiguration(), usage=usage, **kwargs)
63 changes: 63 additions & 0 deletions packages/modules/consumers/ego/ego/consumer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
import logging
from typing import Optional

from modules.common.abstract_consumer import CurrentValues
from modules.common.abstract_device import DeviceDescriptor
from modules.common.component_state import ConsumerState
from modules.common.component_type import ComponentType
from modules.common.configurable_consumer import ConfigurableConsumer, SetLimitData
from modules.common.modbus import ModbusDataType, ModbusTcpClient_
from modules.common.simcount._simcounter import SimCounterConsumer
from modules.consumers.ego.ego.config import Ego

log = logging.getLogger(__name__)


def create_consumer(config: Ego):
client: Optional[ModbusTcpClient_] = None
sim_counter: Optional[SimCounterConsumer] = None

def initializer():
nonlocal client, sim_counter
client = ModbusTcpClient_(config.configuration.ip_address, config.configuration.port)
sim_counter = SimCounterConsumer(config.id, ComponentType.CONSUMER)

def send_values(values: CurrentValues) -> None:
unit = config.configuration.modbus_id
# Reg 4864 = -1: Heizstab nutzt selbst Reg 4865 (HomeTotalPower) zur Regelung.
client.write_register(4864, -1, data_type=ModbusDataType.INT_16, unit=unit)
# Reg 4865/4866: Netzsaldo, negativ = Überschuss (gleiche Konvention wie evu_power).
client.write_register(4865, round(values.evu_power), data_type=ModbusDataType.INT_32, unit=unit)

def update() -> ConsumerState:
unit = config.configuration.modbus_id
status = client.read_holding_registers(5128, ModbusDataType.UINT_16, unit=unit)
power = 0
if status & 0x01:
power += client.read_holding_registers(4096, ModbusDataType.UINT_16, unit=unit)
if status & 0x02:
power += client.read_holding_registers(4128, ModbusDataType.UINT_16, unit=unit)
if status & 0x04:
power += client.read_holding_registers(4160, ModbusDataType.UINT_16, unit=unit)
imported, exported = sim_counter.sim_count(power)

return ConsumerState(
power=power,
imported=imported,
exported=exported
)

def set_power_limit(power_limit: Optional[float], data: SetLimitData) -> None:
# Reg 4864: gewünschte Leistung [W], bestmöglich auf die 500/1000/2000W-Relais verteilt.
value = 0 if power_limit is None else max(round(power_limit), 0)
client.write_register(4864, value, data_type=ModbusDataType.INT_16, unit=config.configuration.modbus_id)

return ConfigurableConsumer(consumer_config=config,
initializer=initializer,
update=update,
send_values=send_values,
set_power_limit=set_power_limit)


device_descriptor = DeviceDescriptor(configuration_factory=Ego)
14 changes: 14 additions & 0 deletions packages/modules/consumers/ego/vendor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from pathlib import Path

from modules.common.abstract_device import DeviceDescriptor
from modules.devices.vendors import VendorGroup


class Vendor:
def __init__(self):
self.type = Path(__file__).parent.name
self.vendor = "E.G.O."
self.group = VendorGroup.VENDORS.value


vendor_descriptor = DeviceDescriptor(configuration_factory=Vendor)
Empty file.
Empty file.
33 changes: 33 additions & 0 deletions packages/modules/consumers/ems_esp/ems_esp/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from typing import Optional, Tuple

from control.consumer.consumer_data import ConsumerUsage
from helpermodules.auto_str import auto_str
from modules.common.consumer_setup import ConsumerSetup
from ..vendor import vendor_descriptor


@auto_str
class EmsEspConfiguration:
def __init__(self,
ip_address: Optional[str] = None,
token: Optional[str] = None):
self.ip_address = ip_address
self.token = token


@auto_str
class EmsEsp(ConsumerSetup[EmsEspConfiguration]):
def __init__(self,
name: str = "EMS-ESP Wärmepumpe (Bosch/Buderus/Junkers)",
type: str = "ems_esp",
id: int = 0,
configuration: Optional[EmsEspConfiguration] = None,
# kein SELF_CONTROLLED: die energyPriceEl/-PV-Entities steuern laut
# EMS-ESP32-Quellcode die Wärmequellen-Auswahl bei Hybridanlagen, nicht die
# Kompressorleistung einer reinen Wärmepumpe.
usage: Tuple[ConsumerUsage, ...] = (ConsumerUsage.SUSPENDABLE_ONOFF,
ConsumerUsage.SUSPENDABLE_TUNABLE,
ConsumerUsage.METER_ONLY),
**kwargs) -> None:
super().__init__(name, type, id, vendor=vendor_descriptor.configuration_factory(
).type, configuration=configuration or EmsEspConfiguration(), usage=usage, **kwargs)
Loading
Loading