-
Notifications
You must be signed in to change notification settings - Fork 107
Initial support for Algodue UEM meters on CP0 and CP1 and as EVU-Kit #1960
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
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
42d95a6
Initial support for Algodue UEM meters on CP
dj3mu 72359f8
log serial and model even if only warnintgs and errors are enabled
dj3mu 079ade4
change to model string
dj3mu 981bf7a
Reduce number of returns
dj3mu 8bb87ad
Reduce number of returns
dj3mu 6da192f
Revert logging to error because level is reduce here
dj3mu 6dacea1
Import to adjust log level
dj3mu 1c96a4d
Merge remote-tracking branch 'openwb/master' into feature/algodueMeter
dj3mu d54c21a
Add Algodue as EVU-Kit
dj3mu fa84984
de-flaked
dj3mu 9bcad16
Removed detection of Algodue in clients.py as requested
dj3mu 2dda224
Merge branch 'feature/algodueMeter' of https://github.com/YourCharge/…
dj3mu 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
This file contains hidden or 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,98 @@ | ||
| #!/usr/bin/env python3 | ||
| import logging | ||
| from typing import List, Tuple, Optional | ||
|
|
||
| from helpermodules.logger import ModifyLoglevelContext | ||
|
|
||
| from modules.common import modbus | ||
| from modules.common.abstract_counter import AbstractCounter | ||
| from modules.common.modbus import ModbusDataType | ||
|
|
||
| log = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class Algodue(AbstractCounter): | ||
| serial_cached: Optional[str] = None | ||
| model_cached: Optional[str] = None | ||
|
|
||
| def __init__(self, modbus_id: int, client: modbus.ModbusTcpClient_) -> None: | ||
| self.client = client | ||
| self.id = modbus_id | ||
|
|
||
| def get_imported(self) -> float: | ||
| return self.client.read_input_registers(0x1106, ModbusDataType.FLOAT_32, unit=self.id) | ||
|
|
||
| def get_exported(self) -> float: | ||
| return self.client.read_input_registers(0x110e, ModbusDataType.FLOAT_32, unit=self.id) | ||
|
|
||
| def get_frequency(self) -> float: | ||
| return self.client.read_input_registers(0x1038, ModbusDataType.FLOAT_32, unit=self.id) | ||
|
|
||
| def get_serial_number(self) -> Optional[str]: | ||
| # serial will never change - at least until power cycle | ||
| if self.serial_cached is None: | ||
| serial_chars = self.client.read_holding_registers(0x500, [ModbusDataType.UINT_8]*10, unit=self.id) | ||
| serial_string = "" | ||
| for x in serial_chars: | ||
| serial_string += chr(x) | ||
| # due to caching this appears rarely - but it's nice to have always have it in main log | ||
| with ModifyLoglevelContext(log, logging.DEBUG): | ||
| log.debug("Algodue meter serial " + serial_string) | ||
| self.serial_cached = serial_string | ||
| return self.serial_cached | ||
|
|
||
| def get_currents(self) -> List[float]: | ||
| return self.client.read_input_registers(0x100E, [ModbusDataType.FLOAT_32]*3, unit=self.id) | ||
|
|
||
| def get_power_factors(self) -> List[float]: | ||
| return self.client.read_input_registers(0x1018, [ModbusDataType.FLOAT_32]*3, unit=self.id) | ||
|
|
||
| def get_power(self) -> Tuple[List[float], float]: | ||
| powers = self.client.read_input_registers(0x1020, [ModbusDataType.FLOAT_32]*3, unit=self.id) | ||
| power = sum(powers) | ||
| return powers, power | ||
|
|
||
| def get_voltages(self) -> List[float]: | ||
| return self.client.read_input_registers(0x1000, [ModbusDataType.FLOAT_32]*3, unit=self.id) | ||
|
|
||
| def get_model(self) -> Optional[str]: | ||
| # model will never change - at least until power cycle | ||
| if self.model_cached is None: | ||
| model_id = self.client.read_holding_registers(0x505, ModbusDataType.UINT_16, unit=self.id) | ||
| model_string = "unknown" | ||
| if model_id == 0x03: | ||
| model_string = "6 A, 3 phases, 4 wires" | ||
| elif model_id == 0x08: | ||
| model_string = "80 A, 3 phases, 4 wires" | ||
| elif model_id == 0x0c: | ||
| model_string = "80 A, 1 phase, 2 wires" | ||
| elif model_id == 0x10: | ||
| model_string = "40 A, 1 phase, 2 wires" | ||
| elif model_id == 0x12: | ||
| model_string = "63 A, 3 phases, 4 wires" | ||
|
|
||
| type_id = self.client.read_holding_registers(0x506, ModbusDataType.UINT_16, unit=self.id) | ||
| type_string = "unknown" | ||
| if type_id == 0x00: | ||
| type_string = "NO MID, RESET" | ||
| elif type_id == 0x01: | ||
| type_string = "MID" | ||
| elif type_id == 0x02: | ||
| type_string = "NO MID" | ||
| elif type_id == 0x03: | ||
| type_string = "NO MID, Wiring selection" | ||
| elif type_id == 0x05: | ||
| type_string = "MID no varh" | ||
| elif type_id == 0x09: | ||
| type_string = "MID Wiring selection" | ||
| elif type_id == 0x0a: | ||
| type_string = "MID no varh, Wiring selection" | ||
| elif type_id == 0x0b: | ||
| type_string = "NO MID, RESET, Wiring selection" | ||
| meterinfo = "Algodue UEM " + model_string + ", " + type_string | ||
|
|
||
| # due to caching this appears rarely - but it's nice to have always have it in main log | ||
| with ModifyLoglevelContext(log, logging.DEBUG): | ||
| log.debug("Algodue model: " + meterinfo) | ||
| self.model_cached = meterinfo | ||
| return self.model_cached | ||
Empty file.
Empty file.
This file contains hidden or 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,38 @@ | ||
| from typing import Optional | ||
|
|
||
| from modules.common.component_setup import ComponentSetup | ||
| from ..vendor import vendor_descriptor | ||
|
|
||
|
|
||
| class AlgodueConfiguration: | ||
| 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 Algodue: | ||
| def __init__(self, | ||
| name: str = "Algodue", | ||
| type: str = "Algodue", | ||
| id: int = 0, | ||
| configuration: AlgodueConfiguration = None) -> None: | ||
| self.name = name | ||
| self.type = type | ||
| self.vendor = vendor_descriptor.configuration_factory().type | ||
| self.id = id | ||
| self.configuration = configuration or AlgodueConfiguration() | ||
|
|
||
|
|
||
| class AlgodueCounterConfiguration: | ||
| def __init__(self): | ||
| pass | ||
|
|
||
|
|
||
| class AlgodueCounterSetup(ComponentSetup[AlgodueCounterConfiguration]): | ||
| def __init__(self, | ||
| name: str = "Algodue Zähler", | ||
| type: str = "counter", | ||
| id: int = 0, | ||
| configuration: AlgodueCounterConfiguration = None) -> None: | ||
| super().__init__(name, type, id, configuration or AlgodueCounterConfiguration()) |
This file contains hidden or 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,53 @@ | ||
| #!/usr/bin/env python3 | ||
| from typing import Dict, Union | ||
|
|
||
| from dataclass_utils import dataclass_from_dict | ||
| from modules.devices.algodue.algodue.config import AlgodueCounterSetup | ||
| 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.simcount import SimCounter | ||
| from modules.common.store import get_counter_value_store | ||
|
|
||
|
|
||
| class AlgodueCounter(AbstractCounter): | ||
| def __init__(self, | ||
| device_id: int, | ||
| component_config: Union[Dict, AlgodueCounterSetup], | ||
| tcp_client: modbus.ModbusTcpClient_, | ||
| modbus_id: int) -> None: | ||
| self.__device_id = device_id | ||
| self.component_config = dataclass_from_dict(AlgodueCounterSetup, component_config) | ||
| self.__tcp_client = tcp_client | ||
| self.__modbus_id = modbus_id | ||
| self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="bezug") | ||
| 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: | ||
|
|
||
| frequency = self.__tcp_client.read_input_registers(0x1038, ModbusDataType.FLOAT_32, unit=self.id) | ||
| currents = self.__tcp_client.read_input_registers(0x100E, [ModbusDataType.FLOAT_32]*3, unit=self.id) | ||
| powers = self.__tcp_client.read_input_registers(0x1020, [ModbusDataType.FLOAT_32]*3, unit=self.id) | ||
| power = sum(powers) | ||
| voltages = self.__tcp_client.read_input_registers(0x1000, [ModbusDataType.FLOAT_32]*3, unit=self.id) | ||
|
|
||
| imported, exported = self.sim_counter.sim_count(power) | ||
|
|
||
| counter_state = CounterState( | ||
| voltages=voltages, | ||
| currents=currents, | ||
| powers=powers, | ||
| imported=imported, | ||
| exported=exported, | ||
| power=power, | ||
| frequency=frequency | ||
| ) | ||
| self.store.set(counter_state) | ||
|
|
||
|
|
||
| component_descriptor = ComponentDescriptor(configuration_factory=AlgodueCounterSetup) |
This file contains hidden or 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,39 @@ | ||
| #!/usr/bin/env python3 | ||
| import logging | ||
| from typing import Iterable | ||
|
|
||
| from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater | ||
| from modules.devices.algodue.algodue import counter | ||
| from modules.devices.algodue.algodue.config import Algodue, AlgodueCounterSetup | ||
| from modules.common import modbus | ||
| from modules.common.abstract_device import DeviceDescriptor | ||
| from modules.common.component_context import SingleComponentUpdateContext | ||
|
|
||
| log = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def create_device(device_config: Algodue): | ||
| def create_counter_component(component_config: AlgodueCounterSetup): | ||
| return counter.AlgodueCounter(device_config.id, component_config, client, | ||
| device_config.configuration.modbus_id) | ||
|
|
||
| def update_components(components: Iterable[counter.AlgodueCounter]): | ||
| with client: | ||
| for component in components: | ||
| with SingleComponentUpdateContext(component.fault_state): | ||
| component.update() | ||
|
|
||
| try: | ||
| client = modbus.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( | ||
| counter=create_counter_component | ||
| ), | ||
| component_updater=MultiComponentUpdater(update_components) | ||
| ) | ||
|
|
||
|
|
||
| device_descriptor = DeviceDescriptor(configuration_factory=Algodue) |
This file contains hidden or 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,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 = "Algodue" | ||
| self.group = VendorGroup.VENDORS.value | ||
|
|
||
|
|
||
| vendor_descriptor = DeviceDescriptor(configuration_factory=Vendor) |
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.
Uh oh!
There was an error while loading. Please reload this page.