diff --git a/packages/modules/common/algodue.py b/packages/modules/common/algodue.py new file mode 100644 index 000000000..f7879cb1e --- /dev/null +++ b/packages/modules/common/algodue.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +import logging +from typing import List, Tuple, Optional + +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 not None: + return self.serial_cached + + 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) + log.debug("Algodue serial is " + 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 not None: + return self.model_cached + + 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 + ")" + log.error("Algodue model returning: " + meterinfo) + self.model_cached = meterinfo + return self.model_cached diff --git a/packages/modules/internal_chargepoint_handler/clients.py b/packages/modules/internal_chargepoint_handler/clients.py index 8b4d3ebc2..b74629643 100644 --- a/packages/modules/internal_chargepoint_handler/clients.py +++ b/packages/modules/internal_chargepoint_handler/clients.py @@ -6,7 +6,7 @@ from modules.common.hardware_check import SeriesHardwareCheckMixin from modules.common.modbus import ModbusSerialClient_, ModbusTcpClient_ -from modules.common import mpm3pm, sdm +from modules.common import mpm3pm, sdm, algodue from modules.common import evse from modules.common import b23 @@ -15,13 +15,19 @@ BUS_SOURCES = ("/dev/ttyUSB0", "/dev/ttyUSB1", "/dev/ttyACM0", "/dev/serial0") -METERS = Union[mpm3pm.Mpm3pm, sdm.Sdm630_72, b23.B23] +METERS = Union[mpm3pm.Mpm3pm, sdm.Sdm630_72, b23.B23, algodue.Algodue] meter_config = NamedTuple("meter_config", [('type', METERS), ('modbus_id', int)]) + +# Note: Algodue meters expect entry of modbus ID in hex. 9b = 155, 9c = 156. +# We code ID in hex here so it's exactly what must be entered in meter. CP0_METERS = [meter_config(mpm3pm.Mpm3pm, modbus_id=5), meter_config(sdm.Sdm630_72, modbus_id=105), - meter_config(b23.B23, modbus_id=201)] + meter_config(b23.B23, modbus_id=201), + meter_config(algodue.Algodue, modbus_id=0x9b)] -CP1_METERS = [meter_config(mpm3pm.Mpm3pm, modbus_id=6), meter_config(sdm.Sdm630_72, modbus_id=106)] +CP1_METERS = [meter_config(mpm3pm.Mpm3pm, modbus_id=6), + meter_config(sdm.Sdm630_72, modbus_id=106), + meter_config(algodue.Algodue, modbus_id=0x9c)] EVSE_ID_CP0 = [1] EVSE_ID_TWO_BUSSES_CP1 = [1, 2] @@ -67,7 +73,7 @@ def find_meter_client(meters: List[meter_config], client: Union[ModbusSerialClie try: if meter_client.get_voltages()[0] > 200: with ModifyLoglevelContext(log, logging.DEBUG): - log.debug("Verbauter Zähler: "+str(meter_type)+" mit Modbus-ID: "+str(modbus_id)) + log.error("Verbauter Zähler: "+str(meter_type)+" mit Modbus-ID: "+str(modbus_id)) return meter_client except Exception: log.debug(client)