diff --git a/deye_controller/__init__.py b/deye_controller/__init__.py index 95b2ede..ba6c1e2 100644 --- a/deye_controller/__init__.py +++ b/deye_controller/__init__.py @@ -1,4 +1,8 @@ from .modbus.protocol import (HoldingRegisters, WritableRegisters, BatteryOnlyRegisters, TotalPowerOnly) +from .modbus.single_phase import (HoldingRegistersSingleHybrid, HoldingRegistersSingleMicro, + HoldingRegistersSingleString) + + from .sell_programmer import SellProgrammer diff --git a/deye_controller/deye_reader.py b/deye_controller/deye_reader.py index a158107..1eac795 100644 --- a/deye_controller/deye_reader.py +++ b/deye_controller/deye_reader.py @@ -1,5 +1,7 @@ from pysolarmanv5 import PySolarmanV5, V5FrameError -from .modbus.protocol import HoldingRegisters, BatteryOnlyRegisters, TotalPowerOnly +from .modbus import (HoldingRegisters, BatteryOnlyRegisters, TotalPowerOnly, InverterType, + HoldingRegistersSingleCommon, HoldingRegistersSingleString, HoldingRegistersSingleHybrid, + HoldingRegistersSingleMicro) from .logger_scan import solar_scan from argparse import ArgumentParser from .utils import group_registers, map_response @@ -10,23 +12,40 @@ def read_inverter(address: str, logger_serial: int, batt_only=False, power_only= as_json=False, to_file=None): inv = PySolarmanV5(address, int(logger_serial), port=8899, mb_slave_id=1, verbose=False, socket_timeout=10, error_correction=True) + + type_detection = HoldingRegisters.DeviceType + type_detection.value = inv.read_holding_registers(type_detection.address, type_detection.len)[0] + inv_type = InverterType(type_detection.value) + + print(f'Detected inverter: {inv_type.name}') + iterator = [] js = {'logger': logger_serial, 'serial': 0, 'data': [] } - if batt_only: - iterator = [HoldingRegisters.SerialNumber] + BatteryOnlyRegisters - elif power_only: - iterator = [HoldingRegisters.SerialNumber] + TotalPowerOnly - elif combo: - iterator = [HoldingRegisters.SerialNumber] + BatteryOnlyRegisters - for reg in TotalPowerOnly: - if reg not in iterator: - iterator.append(reg) - - else: - iterator = HoldingRegisters.as_list() + + if inv_type == InverterType.Hybrid3Phase: + if batt_only: + iterator = [HoldingRegisters.SerialNumber] + BatteryOnlyRegisters + elif power_only: + iterator = [HoldingRegisters.SerialNumber] + TotalPowerOnly + elif combo: + iterator = [HoldingRegisters.SerialNumber] + BatteryOnlyRegisters + for reg in TotalPowerOnly: + if reg not in iterator: + iterator.append(reg) + + else: + iterator = HoldingRegisters.as_list() + + elif inv_type == InverterType.Hybrid: + iterator = HoldingRegistersSingleHybrid.as_list() + elif inv_type == InverterType.Microinverter: + iterator = HoldingRegistersSingleMicro.as_list() + elif inv_type == InverterType.Inverter: + iterator = HoldingRegistersSingleString.as_list() + reg_groups = group_registers(iterator) for group in reg_groups: res = inv.read_holding_registers(group.start_address, group.len) @@ -38,7 +57,7 @@ def read_inverter(address: str, logger_serial: int, batt_only=False, power_only= else: suffix = '' if as_json: - if reg == HoldingRegisters.SerialNumber: + if reg in [HoldingRegisters.SerialNumber, HoldingRegistersSingleCommon.SerialNumber]: js['serial'] = reg.format() else: js['data'].append({reg.description: {'addr': reg.address, 'value': reg.format(), 'unit': suffix}}) diff --git a/deye_controller/modbus/__init__.py b/deye_controller/modbus/__init__.py index e69de29..63e907a 100644 --- a/deye_controller/modbus/__init__.py +++ b/deye_controller/modbus/__init__.py @@ -0,0 +1,13 @@ +from .protocol import HoldingRegisters, BatteryOnlyRegisters, WritableRegisters, TotalPowerOnly +from .single_phase import (HoldingRegistersSingleHybrid, HoldingRegistersSingleString, HoldingRegistersSingleMicro, + HoldingRegistersSingleCommon) +from .enums import InverterType + + +__all__ = [ + 'HoldingRegisters', 'BatteryOnlyRegisters', 'WritableRegisters', 'TotalPowerOnly', + 'HoldingRegistersSingleHybrid', 'HoldingRegistersSingleMicro', + 'HoldingRegistersSingleString', 'HoldingRegistersSingleCommon', + 'InverterType', +] + diff --git a/deye_controller/modbus/enums.py b/deye_controller/modbus/enums.py index 44ea63f..ee04971 100644 --- a/deye_controller/modbus/enums.py +++ b/deye_controller/modbus/enums.py @@ -1,6 +1,19 @@ import enum +class InverterType(int, enum.Enum): + + Inverter = 2 + Hybrid = 3 + Microinverter = 4 + Hybrid3Phase = 5 + Unknown = 0 + + @classmethod + def _missing_(cls, value): + return InverterType.Unknown + + class InverterState(int, enum.Enum): StandBy = 0 @@ -172,3 +185,48 @@ def __format__(self, format_spec): def __str__(self): return self.name + +class WorkMode(int, enum.Enum): + """ + Register 142 - GH #16 + """ + + SellingFirst = 0 + ZeroExportToLoad = 1 + ZeroExportToCT = 2 + Unknown = -1 + + def __str__(self): + return self.name + + @classmethod + def _missing_(cls, value): + return WorkMode.Unknown + + +class Relay(int, enum.Enum): + Open = 0 + Closed = 1 + Error = -1 + + @classmethod + def _missing_(cls, value): + return Relay.Error + + def __format__(self, format_spec): + return self.name + + +class BatteryControlSingle(int, enum.Enum): + + Lead = 0 + Lithium = 1 + + Error = -1 + + def __format__(self, format_spec): + return self.name + + @classmethod + def _missing_(cls, value): + return BatteryControlSingle.Error \ No newline at end of file diff --git a/deye_controller/modbus/protocol.py b/deye_controller/modbus/protocol.py index bf9c962..5133cf9 100644 --- a/deye_controller/modbus/protocol.py +++ b/deye_controller/modbus/protocol.py @@ -11,19 +11,6 @@ from .enums import * -class InverterType(int, enum.Enum): - - Inverter = 2 - Hybrid = 3 - Microinverter = 4 - Hybrid3Phase = 5 - Unknown = 0 - - @classmethod - def _missing_(cls, value): - return InverterType.Unknown - - class Register(object): """ Base register of 2 bytes """ @@ -111,6 +98,26 @@ def format(self): return round(calculated / self.scale, 2) +class LongUnsignedHoleType(Register): + """ + For single phase inverters where the data is not in sequential registers + """ + + def __init__(self, address, name, scale, suffix=''): + super(LongUnsignedHoleType, self).__init__(address, 3, name) + self.scale = scale + self.suffix = suffix + + def format(self): + v = to_unsigned_bytes(self.value[::-2]) + calculated = int.from_bytes(v, byteorder='big') + if self.scale == 1: + return calculated + else: + return round(calculated / self.scale, 2) + + + class DeviceType(Register): def __init__(self): @@ -230,8 +237,18 @@ def __init__(self): def format(self): return BatteryControlMode(self.value) - - + + +class BatteryTemp(Register): + + def __init__(self, address): + super(BatteryTemp, self).__init__(address, 1, 'battery_temp') + self.suffix = '°C' + + def format(self): + return round((self.value - 1000) / 10, 2) + + class BMSBatteryTemp(Register): def __init__(self): @@ -345,6 +362,14 @@ def format(self): return str(GenPortMode(self.value)) +class InverterWorkMode(Register): + def __init__(self): + super().__init__(142, 1, 'work_mode') + + def format(self): + return str(WorkMode(self.value)) + + class HoldingRegisters: DeviceType = DeviceType() @@ -393,7 +418,7 @@ class HoldingRegisters: """ Smart Load control - need more info """ GeneratorPortSetup = GenPortUse() """ Smart Load """ - + IverterWorkMode = InverterWorkMode() GridExportLimit = IntType(143, 'grid_max_output_pwr', suffix='W') SolarSell = BoolType(145, 'solar_sell') SellTimeOfUse = TimeOfUseSell() @@ -861,6 +886,7 @@ class WritableRegisters: SmartLoadOnVoltage = FloatWritable(address=136, low_limit=38, high_limit=63, scale=100) SmartLoadOnCapacity = IntWritable(address=137, low_limit=0, high_limit=100) + InverterWorkMode = IntWritable(address=142, low_limit=0, high_limit=2) GridExportLimit = IntWritable(address=143, low_limit=0, high_limit=15000) SolarSell = BoolWritable(address=145) diff --git a/deye_controller/modbus/single_phase.py b/deye_controller/modbus/single_phase.py new file mode 100644 index 0000000..9e2e5c9 --- /dev/null +++ b/deye_controller/modbus/single_phase.py @@ -0,0 +1,385 @@ +""" +MODBUS Registers for DEYE single phase inverters +""" +from typing import List +from .protocol import (LongType, LongUnsignedType, BoolType, DeviceTime, DeviceType, + DeviceSerialNumber, IntType, FloatType, RunState, Register, + LongUnsignedHoleType, TempWithOffset, BatteryTemp) + +from .enums import Relay, BatteryControlSingle, BatteryControlMode, TwoBitState + + +class RelayStatus(Register): + """ + Relay status for single phase Hybrid & Micro - Reg: 194 + """ + def __init__(self): + super().__init__(194, 1, 'relay_status') + + def format(self): + return Relay(self.value) + + +class DeviceTimeSingle(DeviceTime): + + def __init__(self): + super().__init__() + self.address = 22 + + +class OffsetValue(FloatType): + def __init__(self, address, name, scale=10, offset=1000, suffix=''): + super().__init__(address, name, scale, suffix=suffix) + self.offset = offset + + +class RunStateSingle(RunState): + + def __init__(self): + super().__init__() + self.address = 59 + + +class BatteryMode(Register): + + def __init__(self): + super().__init__(200, 1, 'battery_mode') + + def format(self): + return BatteryControlSingle(self.value) + + +class BatteryControl(Register): + def __init__(self): + super().__init__(213, 1,'battery_control_mode') + + def format(self): + return BatteryControlMode(self.value) + + +class LithiumWakeUpSingle(Register): + """ + Register: 214 + """ + def __init__(self): + super().__init__(214, 1, 'battery_wakeup') + + def format(self): + return TwoBitState(self.value) + + +class HoldingRegistersSingleCommon: + + DeviceType = DeviceType() + CommProtocol = IntType(1, 'modbus_address') + SerialNumber = DeviceSerialNumber() + RatedPower = IntType(8, 'rated_power') + + DeviceTime = DeviceTimeSingle() # Address 22 + 3 + MinimumInsulationImpedance = FloatType(25, 'minimum_insulation_impedance', 10, suffix='kOhm') + DCUpperLimit_V = FloatType(26, 'dc_voltage_upper_limit', 10, suffix='V') + + GridUpperLimit_A = FloatType(31, 'grid_current_upper_limit', 10, suffix='A') + + StartingUpperLimit_V = FloatType(32, 'starting_voltage_upper_limit', 10, suffix='V') + StartingLowerLimit_V = FloatType(33, 'starting_voltage_lower_limit', 10, suffix='V') + + InternalTempUpperLimit = FloatType(36, 'internal_temp_upper_limit', 10, suffix='°C') + + PFRegulation = OffsetValue(39, 'power_factor_regulation', scale=1000, offset=1000) + ActivePowerRegulation = FloatType(40, 'active_power_regulation', scale=10, suffix='%') + ReactivePowerRegulation = FloatType(41, 'reactive_power_regulation', scale=10, suffix='%') + ApparentPowerRegulation = FloatType(42, 'apparent_power_regulation', scale=10, suffix='%') + SwitchOnOff = BoolType(43, 'switch_on_off') + """ Factory reset skipped """ + + SelfCheck = BoolType(45, 'self_check_enabled') + IslandProtect = BoolType(46, 'island_protection_enabled') + + """ Some skipped for now """ + + RunState = RunStateSingle() # 59 + DailyActivePower = FloatType(60, 'daily_active_power', scale=10, suffix='kWh') + DailyReactivePower = FloatType(61, 'daily_reactive_power', scale=10, suffix='kWh') + DailyGridWorkingTime = IntType(62, 'daily_grid_working_time', suffix='s') + + TotalActivePower = LongType(63, 'total_active_power', scale=10, suffix='kWh') + """ #63 - probably Micro only """ + + """ 91 """ + ACRadiatorTemp = TempWithOffset(91, 'ac_radiator_temperature', scale=10) + PowerFactor = FloatType(93, 'power_factor', scale=1) + """ Probably wrong value - needs research """ + AmbientTemp = FloatType(95, 'ambient_temperature', scale=10, suffix='°C') + PVPowerHistory = LongUnsignedType(96, 'pv_power_history', scale=10, suffix='kWh') + + # TODO: 100 through 106 - handling for Warning and Fault messages + CorrectedAH = IntType(107, 'corrected_Ah', suffix='Ah') + DailyPVPower = FloatType(108, 'daily_pv_power', scale=10, suffix='kWh') + + """ Debug & MI data up to #150 """ + """ String, Hybrid & micro separated """ + """ Next common 200 - not sure if applies to Micro """ + + """ R/W """ + BatteryMode = BatteryMode() + EqualizationV = FloatType(201, 'equalization_voltage', scale=10, suffix='V') + AbsorptionV = FloatType(202, 'absorption_voltage', scale=10, suffix='V') + FloatV = FloatType(203, 'absorption_voltage', scale=10, suffix='V') + BatteryCapacity = IntType(204, 'battery_capacity', suffix='Ah') + DisplayedSOC = IntType(205, 'LCD_displayed_SOC', suffix='%') + BatteryTempProtectionLow = OffsetValue(206, 'battery_low_temp_protection', scale=10, + offset=1000, suffix='°C') + + EqualizationCycle = IntType(207, 'equalization_cycle', suffix='days') + EqualizationTime = IntType(208, 'equalization_time', suffix='half-hour') + TEMPCO = IntType(209, 'TEMPCO', suffix='mv/°C') + + MaxChargeCurrent = IntType(210, 'max_charge_current', suffix='A') + MaxDischargeCurrent = IntType(211, 'max_discharge_current', suffix='A') + + BatteryControl = BatteryControl() + BatteryWakeup = LithiumWakeUpSingle() + BatteryResistance = IntType(215, 'battery_resistance', suffix='mOhm') + BatterChargingEff = FloatType(216, 'battery_charge_efficiency', suffix='%', scale=10) + BatterySocShutDown = IntType(217, 'battery_shutdown_soc', suffix='%') + BatterySocRestart = IntType(218, 'battery_restart_soc', suffix='%') + BatterySocLow = IntType(219, 'battery_low_soc', suffix='%') + + BatteryVShutDown = FloatType(220, 'battery_shutdown_voltage', suffix='V', scale=100) + BatteryVRestart = FloatType(221, 'battery_restart_voltage', suffix='V', scale=100) + BatteryVLow = FloatType(222, 'battery_low_voltage', suffix='V', scale=100) + + GeneratorMaxTime = FloatType(223, 'gen_max_work_time', suffix='hour(s)', scale=10) + GeneratorCoolingTime = FloatType(224, 'gen_cooling_time', suffix='hour(s)', scale=10) + + GeneratorChargingVPoint = FloatType(225, 'gen_charge_start_v', scale=100, suffix='V') + GeneratorChargingSOCPoint = IntType(226, 'gen_charge_start_soc', suffix='%') + GeneratorChargeCurrent = IntType(227, 'gen_charge_current_limit', suffix='A') + + GridChargingVPoint = FloatType(228, 'grid_charge_start_v', scale=100, suffix='V') + GridChargingSOCPoint = IntType(229, 'grid_charge_start_soc', suffix='%') + GridChargeCurrent = IntType(230, 'grid_charge_current_limit', suffix='A') + + GeneratorChargeEnabled = BoolType(231, 'gen_charge_enabled') + GridChargeEnabled = BoolType(232, 'grid_charge_enabled') + # TODO: continue + + + + + +class HoldingRegistersSingleNoString: + """ + Common registers for Micro & Hybrid + """ + """ CT maybe """ + GridVoltageL1 = FloatType(150, 'grid_side_voltage_l1', scale=10, suffix='V') + GridVoltageL2 = FloatType(151, 'grid_side_voltage_l2', scale=10, suffix='V') + GridVoltageDiff = FloatType(152, 'grid_side_voltage_l1-l2', scale=10, suffix='V') + + """ Relay voltage """ + RelayVoltage = FloatType(153, 'voltage_at_the_relay', scale=10, suffix='V') + + InverterOutVoltageL1 = FloatType(154, 'inv_out_voltage_l1', scale=10, suffix='V') + InverterOutVoltageL2 = FloatType(155, 'inv_out_voltage_l2', scale=10, suffix='V') + InverterOutVoltageDiff = FloatType(156, 'inv_out_voltage_l1-l2', scale=10, suffix='V') + + LoadVoltageL1 = FloatType(157, 'load_voltage_l1', scale=10, suffix='V') + LoadVoltageL2 = FloatType(158, 'load_voltage_l1', scale=10, suffix='V') + + GridCurrentL1 = FloatType(160, 'grid_side_current_l1', scale=100, suffix='A', signed=True) + GridCurrentL2 = FloatType(161, 'grid_side_current_l2', scale=100, suffix='A', signed=True) + GridExtLimiterL1 = FloatType(162, 'grid_limiter_l1', scale=100, suffix='A', signed=True) + GridExtLimiterL2 = FloatType(163, 'grid_limiter_l2', scale=100, suffix='A', signed=True) + InverterCurrentL1 = FloatType(164, 'inverter_current_l1', scale=100, suffix='A', signed=True) + InverterCurrentL2 = FloatType(165, 'inverter_current_l2', scale=100, suffix='A', signed=True) + + GridPowerL1 = IntType(167, 'grid_side_power_l1', suffix='W', signed=True) + GridPowerL2 = IntType(168, 'grid_side_power_l1', suffix='W', signed=True) + GridPowerTotal = IntType(169, 'grid_side_total_power', suffix='W', signed=True) + + GridExtLimiterPwrL1 = IntType(170, 'grid_side_limiter_l1_power', suffix='W', signed=True) + GridExtLimiterPwrL2 = IntType(171, 'grid_side_limiter_l2_power', suffix='W', signed=True) + GridExtLimiterPwrT = IntType(172, 'grid_side_limiter_total_power', suffix='W', signed=True) + + InverterPowerL1 = IntType(173, 'inverter_power_l1', suffix='W', signed=True) + InverterPowerL2 = IntType(174, 'inverter_power_l2', suffix='W', signed=True) + InverterTotalPower = IntType(175, 'inverter_total_power', suffix='W', signed=True) + + LoadPowerL1 = IntType(176, 'load_power_l1', suffix='W', signed=True) + LoadPowerL2 = IntType(177, 'load_power_l2', suffix='W', signed=True) + LoadTotalPower = IntType(178, 'load_total_power', suffix='W', signed=True) + LoadCurrentL1 = FloatType(179, 'load_current_l1', scale=100, suffix='A', signed=True) + LoadCurrentL2 = FloatType(180, 'load_current_l2', scale=100, suffix='A', signed=True) + + """" Battery """ + BatteryTemp = BatteryTemp(182) + BatteryVoltage = FloatType(183, 'battery_voltage', scale=100, suffix='V') + BatterySOC = IntType(184, 'battery_SOC', suffix='%') + + """ PV """ + PV1Power = IntType(186, 'pv1_power', suffix='W') + PV2Power = IntType(187, 'pv2_power', suffix='W') + PV3Power = IntType(188, 'pv3_power', suffix='W') + PV4Power = IntType(189, 'pv4_power', suffix='W') + + BatteryPower = IntType(190, 'battery_power', suffix='W', signed=True) + BatteryCurrent = FloatType(191, 'battery_current', scale=100, suffix='A', signed=True) + + LoadFrequency = FloatType(192, 'load_frequency', scale=100, suffix='Hz') + InverterFrequency = FloatType(193, 'inverter_frequency', scale=100, suffix='Hz') + + RelayStatus = RelayStatus() + + +class HoldingRegistersSingleMicro(HoldingRegistersSingleCommon, HoldingRegistersSingleNoString): + """ Single phase + Micro Inverter specific """ + + GridUpperLimit_V = FloatType(27, 'grid_voltage_upper_limit', 10, suffix='V') + GridLowerLimit_V = FloatType(28, 'grid_voltage_lower_limit', 10, suffix='V') + GridUpperLimit_F = FloatType(29, 'grid_frequency_upper_limit', 100, suffix='Hz') + GridLowerLimit_F = FloatType(30, 'grid_frequency_lower_limit', 100, suffix='Hz') + + OverFreqDeratePoint = FloatType(34, 'over_frequency_derate_point', 100, suffix='Hz') + OverFreqDerateRate = IntType(35, 'over_frequency_derate_rate', suffix='%') + + DailyPowerComponent1 = FloatType(65, 'daily_power_component_1', scale=10, suffix='kWh') + DailyPowerComponent2 = FloatType(66, 'daily_power_component_2', scale=10, suffix='kWh') + DailyPowerComponent3 = FloatType(67, 'daily_power_component_3', scale=10, suffix='kWh') + DailyPowerComponent4 = FloatType(68, 'daily_power_component_4', scale=10, suffix='kWh') + TotalPowerComponent1 = LongUnsignedType(69, 'total_power_component_1', scale=10, suffix='kWh') + TotalPowerComponent2 = LongUnsignedType(71, 'total_power_component_2', scale=10, suffix='kWh') + """ #73 not used """ + TotalPowerComponent3 = LongUnsignedType(74, 'total_power_component_3', scale=10, suffix='kWh') + TotalPowerComponent4 = LongUnsignedType(77, 'total_power_component_4', scale=10, suffix='kWh') + GridFrequency = FloatType(79, 'grid_frequency', scale=100, suffix='Hz') + + DCTransformerTemp = FloatType(90, 'dc_transformer_temperature', scale=10, suffix='°C') + + DCVoltage_1 = FloatType(109, 'dc_voltage_1', scale=10, suffix='V') + DCCurrent_1 = FloatType(110, 'dc_current_1', scale=10, suffix='A') + DCVoltage_2 = FloatType(111, 'dc_voltage_2', scale=10, suffix='V') + DCCurrent_2 = FloatType(112, 'dc_current_2', scale=10, suffix='A') + DCVoltage_3 = FloatType(113, 'dc_voltage_3', scale=10, suffix='V') + DCCurrent_3 = FloatType(114, 'dc_current_3', scale=10, suffix='A') + DCVoltage_4 = FloatType(115, 'dc_voltage_4', scale=10, suffix='V') + DCCurrent_4 = FloatType(116, 'dc_current_4', scale=10, suffix='A') + + @staticmethod + def as_list() -> List[Register]: + """ Method for easy iteration over the registers defined here """ + return [getattr(HoldingRegistersSingleMicro, x) for x in + HoldingRegistersSingleMicro.__dict__ if not x.startswith('_') + and not x.startswith('as_')] + + +class HoldingRegistersSingleHybrid(HoldingRegistersSingleCommon, HoldingRegistersSingleNoString): + """ Single phase + Hybrid Inverter specific """ + + MonthlyPVPower = IntType(65, 'monthly_pv_power', suffix='kWh') + MonthlyLoadPower = IntType(66, 'monthly_load_power', suffix='kWh') + MonthlyGridPower = IntType(67, 'monthly_grid_power', suffix='kWh') + YearlyPVPower = LongUnsignedType(68, 'yearly_pv_power', scale=10, suffix='kWh') + DailyBatteryCharge = FloatType(70, 'daily_battery_charge', scale=10, suffix='kWh') + DailyBatteryDischarge = FloatType(71, 'daily_battery_discharge', scale=10, suffix='kWh') + TotalBatteryCharge = LongUnsignedType(72, 'total_battery_charge', 10, suffix='kWh') + TotalBatteryDischarge = LongUnsignedType(74, 'total_battery_discharge', 10, suffix='kWh') + + DailyGridBought = FloatType(76, 'daily_bought_from_grid', 10, suffix='kWh') + DailyGridSold = FloatType(77, 'daily_sold_to_grid', 10, suffix='kWh') + + TotalGridInPower = LongUnsignedHoleType(78, 'total_grid_in_power', scale=10, suffix='kWh') + TotalGridOutPower = LongUnsignedType(81, 'total_grid_out_power', scale=10, suffix='kWh') + + GeneratorDailyTime = FloatType(83, 'gen_daily_operating_time', scale=10, suffix='hours') + TotalLoadPower = LongUnsignedType(85, 'total_load_power', scale=10, suffix='kWh') + AnnualLoadPower = LongUnsignedType(87, 'annual_load_power', scale=10, suffix='kWh') + AnnualGridOutPower = LongUnsignedType(98, 'annual_grid_out_power', scale=10, suffix='kWh') + + @staticmethod + def as_list() -> List[Register]: + """ Method for easy iteration over the registers defined here """ + return [getattr(HoldingRegistersSingleHybrid, x) for x in + HoldingRegistersSingleHybrid.__dict__ if not x.startswith('_') + and not x.startswith('as_')] + + +class HoldingRegistersSingleString(HoldingRegistersSingleCommon): + """ String inverter specific """ + + TotalReactivePower = LongType(65, 'total_reactive_power', scale=10, suffix='kVarH') + TotalWorkTime = LongType(67, 'total_work_time', scale=10, suffix='hours') + InverterEfficiency = FloatType(69, 'inverter_efficiency', scale=10, suffix='%') + GridVoltageAB = FloatType(70, 'grid_voltage_ab', scale=10, suffix='V') + GridVoltageBC = FloatType(71, 'grid_voltage_bc', scale=10, suffix='V') + GridVoltageAC = FloatType(72, 'grid_voltage_ac', scale=10, suffix='V') + GridVoltageA = FloatType(73, 'grid_voltage_a', scale=10, suffix='V') + GridVoltageB = FloatType(74, 'grid_voltage_b', scale=10, suffix='V') + GridVoltageC = FloatType(75, 'grid_voltage_c', scale=10, suffix='V') + GridCurrentA = FloatType(76, 'grid_current_a', scale=10, suffix='A') + GridCurrentB = FloatType(77, 'grid_current_b', scale=10, suffix='A') + GridCurrentC = FloatType(78, 'grid_current_b', scale=10, suffix='A') + + CurrentPower = LongUnsignedType(80, 'current_power', scale=10, suffix='W') + InputActivePower = LongUnsignedType(82, 'input_active_power', scale=10, suffix='W') + OutputApparentPower = LongUnsignedType(84, 'output_apparent_power', scale=10, suffix='W') + OutputActivePower = LongUnsignedType(86, 'output_active_power', scale=10, suffix='W') + OutputReactivePower = LongUnsignedType(88, 'output_reactive_power', scale=10, suffix='W') + + RCDLeakCurrent = FloatType(98, 'RCD_leak_current', scale=100, suffix='A') + PowerLimiter = FloatType(99, 'power_limiter', scale=1, suffix='W') + + """ String current """ + String_1_Current = FloatType(150, 'string_1_current', scale=10, suffix='A') + String_2_Current = FloatType(151, 'string_2_current', scale=10, suffix='A') + String_3_Current = FloatType(152, 'string_3_current', scale=10, suffix='A') + String_4_Current = FloatType(153, 'string_4_current', scale=10, suffix='A') + String_5_Current = FloatType(154, 'string_5_current', scale=10, suffix='A') + String_6_Current = FloatType(155, 'string_6_current', scale=10, suffix='A') + String_7_Current = FloatType(156, 'string_7_current', scale=10, suffix='A') + String_8_Current = FloatType(157, 'string_8_current', scale=10, suffix='A') + String_9_Current = FloatType(158, 'string_9_current', scale=10, suffix='A') + String_10_Current = FloatType(159, 'string_10_current', scale=10, suffix='A') + String_11_Current = FloatType(160, 'string_11_current', scale=10, suffix='A') + String_12_Current = FloatType(161, 'string_12_current', scale=10, suffix='A') + String_13_Current = FloatType(162, 'string_13_current', scale=10, suffix='A') + String_14_Current = FloatType(163, 'string_14_current', scale=10, suffix='A') + String_15_Current = FloatType(164, 'string_15_current', scale=10, suffix='A') + String_16_Current = FloatType(165, 'string_16_current', scale=10, suffix='A') + """ String power """ + String_1_Power = LongUnsignedType(166, 'string_1_power', scale=10, suffix='kWh') + String_2_Power = LongUnsignedType(168, 'string_2_power', scale=10, suffix='kWh') + String_3_Power = LongUnsignedType(170, 'string_3_power', scale=10, suffix='kWh') + String_4_Power = LongUnsignedType(172, 'string_4_power', scale=10, suffix='kWh') + String_5_Power = LongUnsignedType(174, 'string_5_power', scale=10, suffix='kWh') + String_6_Power = LongUnsignedType(176, 'string_6_power', scale=10, suffix='kWh') + String_7_Power = LongUnsignedType(178, 'string_7_power', scale=10, suffix='kWh') + String_8_Power = LongUnsignedType(180, 'string_8_power', scale=10, suffix='kWh') + String_9_Power = LongUnsignedType(182, 'string_9_power', scale=10, suffix='kWh') + String_10_Power = LongUnsignedType(184, 'string_10_power', scale=10, suffix='kWh') + String_11_Power = LongUnsignedType(186, 'string_11_power', scale=10, suffix='kWh') + String_12_Power = LongUnsignedType(188, 'string_12_power', scale=10, suffix='kWh') + String_13_Power = LongUnsignedType(190, 'string_13_power', scale=10, suffix='kWh') + String_14_Power = LongUnsignedType(192, 'string_14_power', scale=10, suffix='kWh') + String_15_Power = LongUnsignedType(194, 'string_15_power', scale=10, suffix='kWh') + String_16_Power = LongUnsignedType(196, 'string_16_power', scale=10, suffix='kWh') + + """ Daily & Total + Hist values probably should be renamed to Total + """ + LoadActivePower = LongUnsignedType(198, 'load_active_power', scale=1, suffix='W') + DailyLoadPower = FloatType(200, 'daily_load_power', scale=100, suffix='kWh') + HistActivePower = LongUnsignedType(201, 'history_active_power', scale=10, suffix='kWh') + MeterActivePower = LongType(203, 'meter_active_power', scale=10, suffix='kWh') + DailyGridSell = FloatType(205, 'daily_grid_sell', scale=100, suffix='kWh') + HistGridSell = LongUnsignedType(206, 'history_grid_sell', scale=10, suffix='kWh') + DailyGridBuy = FloatType(208, 'daily_grid_buy', scale=100, suffix='kWh') + HistGridBuy = LongUnsignedType(209, 'history_grid_buy', scale=10, suffix='kWh') + + @staticmethod + def as_list() -> List[Register]: + """ Method for easy iteration over the registers defined here """ + return [getattr(HoldingRegistersSingleString, x) for x in + HoldingRegistersSingleString.__dict__ if not x.startswith('_') + and not x.startswith('as_')]