-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New Module for Solax Gen5 #1972
Open
rgrae81
wants to merge
9
commits into
openWB:master
Choose a base branch
from
rgrae81:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 6 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
5d93ed6
New Module for Solax Gen5
rgrae81 c39d1da
Merge branch 'openWB:master' into master
rgrae81 2152ab2
Update inverter.py
rgrae81 def46f8
Update bat.py
rgrae81 bd7f2e1
Update counter.py
rgrae81 34d86db
Update device.py
rgrae81 caa28ea
Update inverter.py
rgrae81 57111dd
Update bat.py
rgrae81 d235e77
Merge branch 'openWB:master' into master
rgrae81 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
#!/usr/bin/env python3 | ||
from typing import Dict, Union | ||
|
||
from dataclass_utils import dataclass_from_dict | ||
from modules.common import modbus | ||
from modules.common.abstract_device import AbstractBat | ||
from modules.common.component_state import BatState | ||
from modules.common.component_type import ComponentDescriptor | ||
from modules.common.fault_state import ComponentInfo, FaultState | ||
from modules.common.modbus import ModbusDataType | ||
from modules.common.simcount import SimCounter | ||
from modules.common.store import get_bat_value_store | ||
from modules.devices.solax.solax_gen5.config import SolaxGen5BatSetup | ||
|
||
|
||
class SolaxGen5Bat(AbstractBat): | ||
def __init__(self, | ||
device_id: int, | ||
component_config: Union[Dict, SolaxGen5BatSetup], | ||
tcp_client: modbus.ModbusTcpClient_, | ||
modbus_id: int) -> None: | ||
self.__device_id = device_id | ||
self.__modbus_id = modbus_id | ||
self.component_config = dataclass_from_dict(SolaxGen5BatSetup, component_config) | ||
self.__tcp_client = tcp_client | ||
self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") | ||
self.store = get_bat_value_store(self.component_config.id) | ||
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) | ||
|
||
def update(self) -> None: | ||
with self.__tcp_client: | ||
power_bat1 = self.__tcp_client.read_input_registers(22, ModbusDataType.INT_16, unit=self.__modbus_id) | ||
power_bat2 = self.__tcp_client.read_input_registers(297, ModbusDataType.INT_16, unit=self.__modbus_id) | ||
power = power_bat1 + power_bat2 | ||
try: | ||
soc = self.__tcp_client.read_input_registers(302, ModbusDataType.UINT_16, unit=self.__modbus_id) | ||
except Exception: | ||
soc = self.__tcp_client.read_input_registers(28, ModbusDataType.UINT_16, unit=self.__modbus_id) | ||
|
||
try: | ||
imported = self.__tcp_client.read_input_registers(35, ModbusDataType.UINT_16, unit=self.__modbus_id) * 100 | ||
exported = self.__tcp_client.read_input_registers(32, ModbusDataType.UINT_16, unit=self.__modbus_id) * 100 | ||
except Exception: | ||
imported, exported = self.sim_counter.sim_count(power) | ||
|
||
bat_state = BatState( | ||
power=power, | ||
soc=soc, | ||
imported=imported, | ||
exported=exported | ||
) | ||
self.store.set(bat_state) | ||
|
||
component_descriptor = ComponentDescriptor(configuration_factory=SolaxGen5BatSetup) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
from typing import Optional | ||
|
||
from modules.common.component_setup import ComponentSetup | ||
from ..vendor import vendor_descriptor | ||
|
||
|
||
class SolaxGen5Configuration: | ||
def __init__(self, modbus_id: int = 1, ip_address: Optional[str] = None, port: int = 502): | ||
self.modbus_id = modbus_id | ||
self.ip_address = ip_address | ||
self.port = port | ||
|
||
|
||
class SolaxGen5: | ||
def __init__(self, | ||
name: str = "Solax Gen5", | ||
type: str = "solax_gen5", | ||
id: int = 0, | ||
configuration: SolaxGen5Configuration = None) -> None: | ||
self.name = name | ||
self.type = type | ||
self.vendor = vendor_descriptor.configuration_factory().type | ||
self.id = id | ||
self.configuration = configuration or SolaxGen5Configuration() | ||
|
||
|
||
class SolaxGen5BatConfiguration: | ||
def __init__(self): | ||
pass | ||
|
||
|
||
class SolaxGen5BatSetup(ComponentSetup[SolaxGen5BatConfiguration]): | ||
def __init__(self, | ||
name: str = "Solax Speicher", | ||
type: str = "bat", | ||
id: int = 0, | ||
configuration: SolaxGen5BatConfiguration = None) -> None: | ||
super().__init__(name, type, id, configuration or SolaxGen5BatConfiguration()) | ||
|
||
|
||
class SolaxGen5CounterConfiguration: | ||
def __init__(self): | ||
pass | ||
|
||
|
||
class SolaxGen5CounterSetup(ComponentSetup[SolaxGen5CounterConfiguration]): | ||
def __init__(self, | ||
name: str = "Solax Zähler", | ||
type: str = "counter", | ||
id: int = 0, | ||
configuration: SolaxGen5CounterConfiguration = None) -> None: | ||
super().__init__(name, type, id, configuration or SolaxGen5CounterConfiguration()) | ||
|
||
|
||
class SolaxGen5InverterConfiguration: | ||
def __init__(self): | ||
pass | ||
|
||
|
||
class SolaxGen5InverterSetup(ComponentSetup[SolaxGen5InverterConfiguration]): | ||
def __init__(self, | ||
name: str = "Solax Wechselrichter", | ||
type: str = "inverter", | ||
id: int = 0, | ||
configuration: SolaxGen5InverterConfiguration = None) -> None: | ||
super().__init__(name, type, id, configuration or SolaxGen5InverterConfiguration()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
#!/usr/bin/env python3 | ||
from typing import Dict, Union | ||
from pymodbus.constants import Endian | ||
|
||
from dataclass_utils import dataclass_from_dict | ||
from modules.common import modbus | ||
from modules.common.abstract_device import AbstractCounter | ||
from modules.common.component_state import CounterState | ||
from modules.common.component_type import ComponentDescriptor | ||
from modules.common.fault_state import ComponentInfo, FaultState | ||
from modules.common.modbus import ModbusDataType | ||
from modules.common.store import get_counter_value_store | ||
from modules.devices.solax.solax_gen5.config import SolaxGen5CounterSetup | ||
|
||
|
||
class SolaxGen5Counter(AbstractCounter): | ||
def __init__(self, | ||
device_id: int, | ||
component_config: Union[Dict, SolaxGen5CounterSetup], | ||
tcp_client: modbus.ModbusTcpClient_, | ||
modbus_id: int) -> None: | ||
|
||
self.component_config = dataclass_from_dict(SolaxGen5CounterSetup, component_config) | ||
self.__modbus_id = modbus_id | ||
self.__tcp_client = tcp_client | ||
self.store = get_counter_value_store(self.component_config.id) | ||
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) | ||
|
||
def update(self): | ||
with self.__tcp_client: | ||
power = self.__tcp_client.read_input_registers(70, ModbusDataType.INT_32, wordorder=Endian.Little, | ||
unit=self.__modbus_id) * -1 | ||
frequency = self.__tcp_client.read_input_registers(7, ModbusDataType.UINT_16, unit=self.__modbus_id) / 100 | ||
voltages = [value / 10 | ||
for value in self.__tcp_client.read_input_registers( | ||
202, [ModbusDataType.UINT_16] * 3, unit=self.__modbus_id | ||
)] | ||
|
||
currents = [(65535 - value) / 10 | ||
for value in self.__tcp_client.read_input_registers( | ||
206, [ModbusDataType.UINT_16] * 3, unit=self.__modbus_id | ||
)] | ||
|
||
power_factors = [value / 100 | ||
for value in self.__tcp_client.read_input_registers( | ||
197, [ModbusDataType.UINT_16] * 3, unit=self.__modbus_id | ||
)] | ||
|
||
powers = [-value for value in self.__tcp_client.read_input_registers( | ||
130, [ModbusDataType.INT_32] * 3, wordorder=Endian.Little, unit=self.__modbus_id | ||
)] | ||
|
||
exported, imported = [value * 10 | ||
for value in self.__tcp_client.read_input_registers( | ||
72, [ModbusDataType.UINT_32] * 2, wordorder=Endian.Little, unit=self.__modbus_id | ||
)] | ||
|
||
counter_state = CounterState( | ||
imported=imported, | ||
exported=exported, | ||
power=power, | ||
powers=powers, | ||
frequency=frequency, | ||
voltages=voltages, | ||
currents=currents, | ||
power_factors=power_factors | ||
) | ||
self.store.set(counter_state) | ||
|
||
|
||
component_descriptor = ComponentDescriptor(configuration_factory=SolaxGen5CounterSetup) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
#!/usr/bin/env python3 | ||
import logging | ||
from typing import Iterable, Union | ||
|
||
from modules.common.abstract_device import DeviceDescriptor | ||
from modules.common.component_context import SingleComponentUpdateContext | ||
from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater | ||
from modules.common.modbus import ModbusTcpClient_ | ||
from modules.devices.solax.solax_gen5.bat import SolaxGen5Bat | ||
from modules.devices.solax.solax_gen5.config import SolaxGen5, SolaxGen5BatSetup, SolaxGen5CounterSetup | ||
from modules.devices.solax.solax_gen5.config import SolaxGen5InverterSetup | ||
from modules.devices.solax.solax_gen5.counter import SolaxGen5Counter | ||
from modules.devices.solax.solax_gen5.inverter import SolaxGen5Inverter | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
def create_device(device_config: SolaxGen5): | ||
def create_bat_component(component_config: SolaxGen5BatSetup): | ||
return SolaxGen5Bat(device_config.id, component_config, client, device_config.configuration.modbus_id) | ||
|
||
def create_counter_component(component_config: SolaxGen5CounterSetup): | ||
return SolaxGen5Counter(device_config.id, component_config, client, device_config.configuration.modbus_id) | ||
|
||
def create_inverter_component(component_config: SolaxGen5InverterSetup): | ||
return SolaxGen5Inverter(device_config.id, component_config, client, device_config.configuration.modbus_id) | ||
|
||
def update_components(components: Iterable[Union[SolaxGen5Bat, SolaxGen5Counter, SolaxGen5Inverter]]): | ||
with client: | ||
for component in components: | ||
with SingleComponentUpdateContext(component.fault_state): | ||
component.update() | ||
|
||
try: | ||
client = ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) | ||
except Exception: | ||
log.exception("Fehler in create_device") | ||
return ConfigurableDevice( | ||
device_config=device_config, | ||
component_factory=ComponentFactoryByType( | ||
bat=create_bat_component, | ||
counter=create_counter_component, | ||
inverter=create_inverter_component, | ||
), | ||
component_updater=MultiComponentUpdater(update_components) | ||
) | ||
|
||
|
||
device_descriptor = DeviceDescriptor(configuration_factory=SolaxGen5) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
#!/usr/bin/env python3 | ||
from typing import Dict, Union | ||
|
||
from dataclass_utils import dataclass_from_dict | ||
from modules.common import modbus | ||
from modules.common.abstract_device import AbstractInverter | ||
from modules.common.component_state import InverterState | ||
from modules.common.component_type import ComponentDescriptor | ||
from modules.common.fault_state import ComponentInfo, FaultState | ||
from modules.common.modbus import ModbusDataType | ||
from modules.common.store import get_inverter_value_store | ||
from modules.devices.solax.solax_gen5.config import SolaxGen5InverterSetup | ||
|
||
|
||
class SolaxGen5Inverter(AbstractInverter): | ||
def __init__(self, | ||
device_id: int, | ||
component_config: Union[Dict, SolaxGen5InverterSetup], | ||
tcp_client: modbus.ModbusTcpClient_, | ||
modbus_id: int) -> None: | ||
self.component_config = dataclass_from_dict(SolaxGen5InverterSetup, component_config) | ||
self.__modbus_id = modbus_id | ||
self.__tcp_client = tcp_client | ||
self.store = get_inverter_value_store(self.component_config.id) | ||
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) | ||
|
||
def update(self) -> None: | ||
with self.__tcp_client: | ||
power_pv1 = self.__tcp_client.read_input_registers(10, ModbusDataType.UINT_16, unit=self.__modbus_id) * -1 | ||
power_pv2 = self.__tcp_client.read_input_registers(11, ModbusDataType.UINT_16, unit=self.__modbus_id) * -1 | ||
power_pv3 = self.__tcp_client.read_input_registers(292, ModbusDataType.UINT_16, unit=self.__modbus_id) * -1 | ||
power_temp = (power_pv1, power_pv2, power_pv3) | ||
power = sum(power_temp) | ||
exported = self.__tcp_client.read_input_registers(80, ModbusDataType.UINT_16, unit=self.__modbus_id) * 100 | ||
|
||
inverter_state = InverterState( | ||
power=power, | ||
exported=exported | ||
) | ||
self.store.set(inverter_state) | ||
|
||
|
||
component_descriptor = ComponentDescriptor(configuration_factory=SolaxGen5InverterSetup) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ist beim Solax Gen5 Register 80 der Gesamt-Ertrag oder der Tagesertrag?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Das ist Tagesertrag
Oder muss hier der Gesamt-Ertrag hinein?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Das ist der Gesamtertrag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, habe ich angepasst.
Dann hätte ich gleich noch eine Frage. Im counter lese ich auch die Ströme mit aus. Die Ströme passen aber nicht 100%ig zu den berechneten (P/U) Das dürfte so wie ich es am Zähler direkt gesehen habe mit der Blindleistung zusammenhängen). Ich habe die register für die Leistung aus dem original Solax-Modul übernommen, und da wird eben rein mit der Einspeiseleistung gearbeitet Wie ist es denn mehr oder weniger gewünscht bzw. Wie wird es bei den meisten Modulen im counter gemacht? Soll der Strom einfach berechnet werden, oder sollen wenn möglich die tatsächlichen Werte verwendet werden?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Von der Regelung wird aktuell bei den WR und Speichern nur die Leistung verwendet. Du kannst aber z. B. die Ströme gerne mit auslesen. Die müssen auch nicht 1:1 zur Wirkleistung passen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dann lass ich die Ströme mit drin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Viele Register stimmen mit dem Ursprungsmodul überein. Es liegen hier nur vereinzelte Registerabweichungen vor (bspw zusätzliches Erfassen von PV3 Power; SoC Register 302 statt 28; Auslesen der Gesamtzählerstände).
Es wäre besser das als zusätzliche Option zur Auswahl im bestehenden Modul anzubieten statt ein eigenes Modul zu erstellen (auch um redundanten Code zu vermeiden).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ndrsnhs Ist auch ein Ansatz. Wo kann man denn die UI für die Einstellungsseite in einem laufenden System zum testen anpassen?