Skip to content
Merged
98 changes: 98 additions & 0 deletions packages/modules/common/algodue.py
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.
38 changes: 38 additions & 0 deletions packages/modules/devices/algodue/algodue/config.py
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())
53 changes: 53 additions & 0 deletions packages/modules/devices/algodue/algodue/counter.py
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)
39 changes: 39 additions & 0 deletions packages/modules/devices/algodue/algodue/device.py
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)
14 changes: 14 additions & 0 deletions packages/modules/devices/algodue/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 = "Algodue"
self.group = VendorGroup.VENDORS.value


vendor_descriptor = DeviceDescriptor(configuration_factory=Vendor)