diff --git a/README.md b/README.md index b32a774..d80556d 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,11 @@ to the device's internal Wifi access point. For information about how to connect - Python >= 3.9 +## Disclaimer + +By using the content in this repository, you acknowledge that you do so at your own risk. I am not liable for any damages or issues that may arise from use of +this library. + ## Usage sun2000_modbus provides easy and comfortable access to your inverter's Modbus TCP interface. The following example gives a short introduction about how to read @@ -65,6 +70,10 @@ Looking at the [above example](#usage) the different methods would return the fo Furthermore, a method `read_range` exists accepting the address of the register to start reading and either a quantity of registers or the address of the last register to be read. The result is returned as byte-string for further processing. +### Write settings + +For writing a register the `write` method can be used, taking the register address and the value as arguments. + ## Registers The following registers are provided by the Sun2000's Modbus interface and can be read accordingly. Documentation can be found @@ -72,304 +81,304 @@ The following registers are provided by the Sun2000's Modbus interface and can b ### InverterEquipmentRegister -| Name | Type | Gain | Unit | Remark | -|-----------------------------------------|------------------------|------|------|------------------------------------| -| Model | String | | | | -| SN | String | | | | -| PN | String | | | | -| FirmwareVerion | String | | | | -| SoftwareVerion | String | | | | -| ProtocolVerion | String | | | | -| ModelID | Number | 1 | | | -| NumberOfPVStrings | Number | 1 | | | -| NumberOfMPPTrackers | Number | 1 | | | -| RatedPower | Number | 1 | W | | -| MaximumActivePower | Number | 1 | W | | -| MaximumApparentPower | Number | 1000 | kVA | | -| MaximumReactivePowerFedToTheGrid | Number | 1000 | kvar | | -| MaximumReactivePowerAbsorbedFromTheGrid | Number | 1000 | kvar | | -| State1 | Binary String/Bitfield | | | | -| State2 | Binary String/Bitfield | | | | -| State3 | Binary String/Bitfield | | | | -| Alarm1 | Binary String/Bitfield | | | | -| Alarm2 | Binary String/Bitfield | | | | -| Alarm3 | Binary String/Bitfield | | | | -| PV1Voltage | Number | 10 | V | | -| PV1Current | Number | 100 | A | | -| PV2Voltage | Number | 10 | V | | -| PV2Current | Number | 100 | A | | -| PV3Voltage | Number | 10 | V | | -| PV3Current | Number | 100 | A | | -| PV4Voltage | Number | 10 | V | | -| PV4Current | Number | 100 | A | | -| PV5Voltage | Number | 10 | V | | -| PV5Current | Number | 100 | A | | -| PV6Voltage | Number | 10 | V | | -| PV6Current | Number | 100 | A | | -| PV7Voltage | Number | 10 | V | | -| PV7Current | Number | 100 | A | | -| PV8Voltage | Number | 10 | V | | -| PV8Current | Number | 100 | A | | -| PV9Voltage | Number | 10 | V | | -| PV9Current | Number | 100 | A | | -| PV10Voltage | Number | 10 | V | | -| PV10Current | Number | 100 | A | | -| PV11Voltage | Number | 10 | V | | -| PV11Current | Number | 100 | A | | -| PV12Voltage | Number | 10 | V | | -| PV12Current | Number | 100 | A | | -| PV13Voltage | Number | 10 | V | | -| PV13Current | Number | 100 | A | | -| PV14Voltage | Number | 10 | V | | -| PV14Current | Number | 100 | A | | -| PV15Voltage | Number | 10 | V | | -| PV15Current | Number | 100 | A | | -| PV16Voltage | Number | 10 | V | | -| PV16Current | Number | 100 | A | | -| PV17Voltage | Number | 10 | V | | -| PV17Current | Number | 100 | A | | -| PV18Voltage | Number | 10 | V | | -| PV18Current | Number | 100 | A | | -| PV19Voltage | Number | 10 | V | | -| PV19Current | Number | 100 | A | | -| PV20Voltage | Number | 10 | V | | -| PV20Current | Number | 100 | A | | -| PV21Voltage | Number | 10 | V | | -| PV21Current | Number | 100 | A | | -| PV22Voltage | Number | 10 | V | | -| PV22Current | Number | 100 | A | | -| PV23Voltage | Number | 10 | V | | -| PV23Current | Number | 100 | A | | -| PV24Voltage | Number | 10 | V | | -| PV24Current | Number | 100 | A | | -| InputPower | Number | 1 | W | | -| LineVoltageBetweenPhasesAAndB | Number | 10 | V | | -| LineVoltageBetweenPhasesBAndC | Number | 10 | V | | -| LineVoltageBetweenPhasesCAndA | Number | 10 | V | | -| PhaseAVoltage | Number | 10 | V | | -| PhaseBVoltage | Number | 10 | V | | -| PhaseCVoltage | Number | 10 | V | | -| PhaseACurrent | Number | 1000 | A | | -| PhaseBCurrent | Number | 1000 | A | | -| PhaseCCurrent | Number | 1000 | A | | -| PeakActivePowerOfCurrentDay | Number | 1 | W | | -| ActivePower | Number | 1 | W | | -| ReactivePower | Number | 1000 | kvar | | -| PowerFactor | Number | 1000 | | | -| GridFrequency | Number | 100 | Hz | | -| Efficiency | Number | 100 | % | | -| InternalTemperature | Number | 10 | °C | | -| InsulationResistance | Number | 1000 | MOhm | | -| DeviceStatus | Number | 1 | | | -| FaultCode | Number | 1 | | | -| StartupTime | Number | 1 | | | -| ShutdownTime | Number | 1 | | | -| AccumulatedEnergyYield | Number | 100 | kWh | | -| DailyEnergyYield | Number | 100 | kWh | | -| ActiveAdjustmentMode | Number | 1 | | | -| ActiveAdjustmentValue | Number | 1 | | | -| ActiveAdjustmentCommand | Number | 1 | | | -| ReactiveAdjustmentMode | Number | 1 | | | -| ReactiveAdjustmentValue | Number | 1 | | | -| ReactiveAdjustmentCommand | Number | 1 | | | -| PowerMeterCollectionActivePower | Number | 1 | W | | -| TotalNumberOfOptimizers | Number | 1 | | | -| NumberOfOnlineOptimizers | Number | 1 | | | -| FeatureData | Number | 1 | | | -| SystemTime | Number | 1 | | | -| QUCharacteristicCurveMode | Number | 1 | | | -| QUDispatchTriggerPower | Number | 1 | % | | -| FixedActivePowerDeratedInKW | Number | 10 | kW | | -| ReactivePowerCompensationInPF | Number | 1000 | | | -| ReactivePowerCompensationQS | Number | 1000 | | | -| ActivePowerPercentageDerating | Number | 10 | % | | -| FixedActivePowerDeratedInW | Number | 1 | W | | -| ReactivePowerCompensationAtNight | Number | 1000 | kvar | | -| CosPhiPPnCharacteristicCurve | Bytestring | | | | -| QUCharacteristicCurve | Bytestring | | | | -| PFUCharacteristicCurve | Bytestring | | | | -| ReactivePowerAdjustmentTime | Number | 1 | s | | -| QUPowerPercentageToExitScheduling | Number | 1 | % | | -| Startup | Number | 1 | | Write only, not available for read | -| Shutdown | Number | 1 | | Write only, not available for read | -| GridCode | Number | 1 | | | -| ReactivePowerChangeGradient | Number | 1000 | %/s | | -| ActivePowerChangeGradient | Number | 1000 | %/s | | -| ScheduleInstructionValidDuration | Number | 1 | s | | -| TimeZone | Number | 1 | min | | +| Name | Type | Gain | Unit | Access Type | +|-----------------------------------------|------------------------|------|------|-------------| +| Model | String | | | RO | +| SN | String | | | RO | +| PN | String | | | RO | +| FirmwareVerion | String | | | RO | +| SoftwareVerion | String | | | RO | +| ProtocolVerion | String | | | RO | +| ModelID | Number | 1 | | RO | +| NumberOfPVStrings | Number | 1 | | RO | +| NumberOfMPPTrackers | Number | 1 | | RO | +| RatedPower | Number | 1 | W | RO | +| MaximumActivePower | Number | 1 | W | RO | +| MaximumApparentPower | Number | 1000 | kVA | RO | +| MaximumReactivePowerFedToTheGrid | Number | 1000 | kvar | RO | +| MaximumReactivePowerAbsorbedFromTheGrid | Number | 1000 | kvar | RO | +| State1 | Binary String/Bitfield | | | RO | +| State2 | Binary String/Bitfield | | | RO | +| State3 | Binary String/Bitfield | | | RO | +| Alarm1 | Binary String/Bitfield | | | RO | +| Alarm2 | Binary String/Bitfield | | | RO | +| Alarm3 | Binary String/Bitfield | | | RO | +| PV1Voltage | Number | 10 | V | RO | +| PV1Current | Number | 100 | A | RO | +| PV2Voltage | Number | 10 | V | RO | +| PV2Current | Number | 100 | A | RO | +| PV3Voltage | Number | 10 | V | RO | +| PV3Current | Number | 100 | A | RO | +| PV4Voltage | Number | 10 | V | RO | +| PV4Current | Number | 100 | A | RO | +| PV5Voltage | Number | 10 | V | RO | +| PV5Current | Number | 100 | A | RO | +| PV6Voltage | Number | 10 | V | RO | +| PV6Current | Number | 100 | A | RO | +| PV7Voltage | Number | 10 | V | RO | +| PV7Current | Number | 100 | A | RO | +| PV8Voltage | Number | 10 | V | RO | +| PV8Current | Number | 100 | A | RO | +| PV9Voltage | Number | 10 | V | RO | +| PV9Current | Number | 100 | A | RO | +| PV10Voltage | Number | 10 | V | RO | +| PV10Current | Number | 100 | A | RO | +| PV11Voltage | Number | 10 | V | RO | +| PV11Current | Number | 100 | A | RO | +| PV12Voltage | Number | 10 | V | RO | +| PV12Current | Number | 100 | A | RO | +| PV13Voltage | Number | 10 | V | RO | +| PV13Current | Number | 100 | A | RO | +| PV14Voltage | Number | 10 | V | RO | +| PV14Current | Number | 100 | A | RO | +| PV15Voltage | Number | 10 | V | RO | +| PV15Current | Number | 100 | A | RO | +| PV16Voltage | Number | 10 | V | RO | +| PV16Current | Number | 100 | A | RO | +| PV17Voltage | Number | 10 | V | RO | +| PV17Current | Number | 100 | A | RO | +| PV18Voltage | Number | 10 | V | RO | +| PV18Current | Number | 100 | A | RO | +| PV19Voltage | Number | 10 | V | RO | +| PV19Current | Number | 100 | A | RO | +| PV20Voltage | Number | 10 | V | RO | +| PV20Current | Number | 100 | A | RO | +| PV21Voltage | Number | 10 | V | RO | +| PV21Current | Number | 100 | A | RO | +| PV22Voltage | Number | 10 | V | RO | +| PV22Current | Number | 100 | A | RO | +| PV23Voltage | Number | 10 | V | RO | +| PV23Current | Number | 100 | A | RO | +| PV24Voltage | Number | 10 | V | RO | +| PV24Current | Number | 100 | A | RO | +| InputPower | Number | 1 | W | RO | +| LineVoltageBetweenPhasesAAndB | Number | 10 | V | RO | +| LineVoltageBetweenPhasesBAndC | Number | 10 | V | RO | +| LineVoltageBetweenPhasesCAndA | Number | 10 | V | RO | +| PhaseAVoltage | Number | 10 | V | RO | +| PhaseBVoltage | Number | 10 | V | RO | +| PhaseCVoltage | Number | 10 | V | RO | +| PhaseACurrent | Number | 1000 | A | RO | +| PhaseBCurrent | Number | 1000 | A | RO | +| PhaseCCurrent | Number | 1000 | A | RO | +| PeakActivePowerOfCurrentDay | Number | 1 | W | RO | +| ActivePower | Number | 1 | W | RO | +| ReactivePower | Number | 1000 | kvar | RO | +| PowerFactor | Number | 1000 | | RO | +| GridFrequency | Number | 100 | Hz | RO | +| Efficiency | Number | 100 | % | RO | +| InternalTemperature | Number | 10 | °C | RO | +| InsulationResistance | Number | 1000 | MOhm | RO | +| DeviceStatus | Number | 1 | | RO | +| FaultCode | Number | 1 | | RO | +| StartupTime | Number | 1 | | RO | +| ShutdownTime | Number | 1 | | RO | +| AccumulatedEnergyYield | Number | 100 | kWh | RO | +| DailyEnergyYield | Number | 100 | kWh | RO | +| ActiveAdjustmentMode | Number | 1 | | RO | +| ActiveAdjustmentValue | Number | 1 | | RO | +| ActiveAdjustmentCommand | Number | 1 | | RO | +| ReactiveAdjustmentMode | Number | 1 | | RO | +| ReactiveAdjustmentValue | Number | 1 | | RO | +| ReactiveAdjustmentCommand | Number | 1 | | RO | +| PowerMeterCollectionActivePower | Number | 1 | W | RO | +| TotalNumberOfOptimizers | Number | 1 | | RO | +| NumberOfOnlineOptimizers | Number | 1 | | RO | +| FeatureData | Number | 1 | | RO | +| SystemTime | Number | 1 | | RW | +| QUCharacteristicCurveMode | Number | 1 | | RW | +| QUDispatchTriggerPower | Number | 1 | % | RW | +| FixedActivePowerDeratedInKW | Number | 10 | kW | RW | +| ReactivePowerCompensationInPF | Number | 1000 | | RW | +| ReactivePowerCompensationQS | Number | 1000 | | RW | +| ActivePowerPercentageDerating | Number | 10 | % | RW | +| FixedActivePowerDeratedInW | Number | 1 | W | RW | +| ReactivePowerCompensationAtNight | Number | 1000 | kvar | RW | +| CosPhiPPnCharacteristicCurve | Bytestring | | | RW | +| QUCharacteristicCurve | Bytestring | | | RW | +| PFUCharacteristicCurve | Bytestring | | | RW | +| ReactivePowerAdjustmentTime | Number | 1 | s | RW | +| QUPowerPercentageToExitScheduling | Number | 1 | % | RW | +| Startup | Number | 1 | | WO | +| Shutdown | Number | 1 | | WO | +| GridCode | Number | 1 | | RW | +| ReactivePowerChangeGradient | Number | 1000 | %/s | RW | +| ActivePowerChangeGradient | Number | 1000 | %/s | RW | +| ScheduleInstructionValidDuration | Number | 1 | s | RW | +| TimeZone | Number | 1 | min | RW | ### BatteryEquipmentRegister -| Name | Type | Gain | Unit | Remark | -|----------------------------------------|------------|------|---------|------------------------------------| -| RunningStatus | Number | 1 | | | -| WorkingModeSettings | Number | 1 | | | -| BusVoltage | Number | 10 | V | | -| BusCurrent | Number | 10 | A | | -| ChargeDischargePower | Number | 1 | W | | -| MaximumChargePower | Number | 1 | W | | -| MaximumDischargePower | Number | 1 | W | | -| RatedCapacity | Number | 1 | Wh | | -| SOC | Number | 10 | % | | -| BackupPowerSOC | Number | 10 | % | | -| TotalCharge | Number | 100 | kWh | | -| TotalDischarge | Number | 100 | kWh | | -| CurrentDayChargeCapacity | Number | 100 | kWh | | -| CurrentDayDischargeCapacity | Number | 100 | kWh | | -| TimeOfUseElectricityPricePeriods | Bytestring | | | | -| MaximumChargingPower | Number | 1 | W | | -| MaximumDischargingPower | Number | 1 | W | | -| ChargingCutoffCapacity | Number | 10 | % | | -| DischargeCutoffCapacity | Number | 10 | % | | -| ForcedChargingAndDischargingPeriod | Number | 1 | minutes | | -| ChargeFromGridFunction | Number | 1 | | | -| GridChargeCutoffSOC | Number | 10 | % | | -| ForcibleChargeDischarge | Number | 1 | | Write only, not available for read | -| FixedChargingAndDischargingPeriods | Bytestring | | | | -| PowerOfChargeFromGrid | Number | 0.1 | W | | -| MaximumPowerOfChargeFromGrid | Number | 0.1 | W | | -| ForcibleChargeDischargeSettingMode | Number | 1 | | | -| ForcibleChargePower | Number | 0.1 | W | | -| ForcibleDischargePower | Number | 0.1 | W | | -| TimeOfUseChargingAndDischargingPeriods | Bytestring | | | | -| ExcessPVEnergyUseInTOU | Number | 1 | | | -| ActivePowerControlMode | Number | 1 | | | -| MaximumFeedGridPowerInKW | Number | 1000 | kW | | -| MaximumFeedGridPowerInPercentage | Number | 10 | % | | -| MaximumChargeFromGridPower | Number | 0.1 | W | | -| SwitchToOffGrid | Number | 1 | | | -| VoltageInIndependentOperation | Number | 1 | | | -| Unit1ProductModel | Number | 1 | | | -| Unit1SN | String | | | | -| Unit1No | Number | 1 | | | -| Unit1SoftwareVersion | String | | | | -| Unit1DCDCVersion | String | | | | -| Unit1BMSVersion | String | | | | -| Unit1RunningStatus | Number | 1 | | | -| Unit1WorkingMode | Number | 1 | | | -| Unit1BusVoltage | Number | 10 | V | | -| Unit1BusCurrent | Number | 10 | A | | -| Unit1BatterySOC | Number | 10 | % | | -| Unit1ChargeAndDischargePower | Number | 1 | W | | -| Unit1RemainingChargeDischargeTime | Number | 1 | minutes | | -| Unit1RatedChargePower | Number | 1 | W | | -| Unit1RatedDischargePower | Number | 1 | W | | -| Unit1CurrentDayChargeCapacity | Number | 100 | kWh | | -| Unit1CurrentDayDischargeCapacity | Number | 100 | kWh | | -| Unit1TotalCharge | Number | 100 | kWh | | -| Unit1TotalDischarge | Number | 100 | kWh | | -| Unit1BatteryTemperature | Number | 10 | °C | | -| Unit1FaultID | Number | 1 | | | -| Unit2ProductModel | Number | 1 | | | -| Unit2SN | String | | | | -| Unit2No | Number | 1 | | | -| Unit2SoftwareVersion | String | | | | -| Unit2RunningStatus | Number | 1 | | | -| Unit2BusVoltage | Number | 10 | V | | -| Unit2BusCurrent | Number | 10 | A | | -| Unit2BatterySOC | Number | 10 | % | | -| Unit2ChargeAndDischargePower | Number | 1 | W | | -| Unit2CurrentDayChargeCapacity | Number | 100 | kWh | | -| Unit2CurrentDayDischargeCapacity | Number | 100 | kWh | | -| Unit2TotalCharge | Number | 100 | kWh | | -| Unit2TotalDischarge | Number | 100 | kWh | | -| Unit2BatteryTemperature | Number | 10 | °C | | -| Unit1BatteryPack1SN | String | | | | -| Unit1BatteryPack1No | Number | 1 | | | -| Unit1BatteryPack1FirmwareVersion | String | | | | -| Unit1BatteryPack1WorkingStatus | Number | 1 | | | -| Unit1BatteryPack1Voltage | Number | 10 | V | | -| Unit1BatteryPack1Current | Number | 10 | A | | -| Unit1BatteryPack1SOC | Number | 10 | % | | -| Unit1BatteryPack1ChargeDischargePower | Number | 1 | W | | -| Unit1BatteryPack1TotalCharge | Number | 100 | kWh | | -| Unit1BatteryPack1TotalDischarge | Number | 100 | kWh | | -| Unit1BatteryPack1MinimumTemperature | Number | 10 | °C | | -| Unit1BatteryPack1MaximumTemperature | Number | 10 | °C | | -| Unit1BatteryPack2SN | String | | | | -| Unit1BatteryPack2No | Number | 1 | | | -| Unit1BatteryPack2FirmwareVersion | String | | | | -| Unit1BatteryPack2WorkingStatus | Number | 1 | | | -| Unit1BatteryPack2Voltage | Number | 10 | V | | -| Unit1BatteryPack2Current | Number | 10 | A | | -| Unit1BatteryPack2SOC | Number | 10 | % | | -| Unit1BatteryPack2ChargeDischargePower | Number | 1 | W | | -| Unit1BatteryPack2TotalCharge | Number | 100 | kWh | | -| Unit1BatteryPack2TotalDischarge | Number | 100 | kWh | | -| Unit1BatteryPack2MinimumTemperature | Number | 10 | °C | | -| Unit1BatteryPack2MaximumTemperature | Number | 10 | °C | | -| Unit1BatteryPack3SN | String | | | | -| Unit1BatteryPack3No | Number | 1 | | | -| Unit1BatteryPack3FirmwareVersion | String | | | | -| Unit1BatteryPack3WorkingStatus | Number | 1 | | | -| Unit1BatteryPack3Voltage | Number | 10 | V | | -| Unit1BatteryPack3Current | Number | 10 | A | | -| Unit1BatteryPack3SOC | Number | 10 | % | | -| Unit1BatteryPack3ChargeDischargePower | Number | 1 | W | | -| Unit1BatteryPack3TotalCharge | Number | 100 | kWh | | -| Unit1BatteryPack3TotalDischarge | Number | 100 | kWh | | -| Unit1BatteryPack3MinimumTemperature | Number | 10 | °C | | -| Unit1BatteryPack3MaximumTemperature | Number | 10 | °C | | -| Unit2BatteryPack1SN | String | | | | -| Unit2BatteryPack1No | Number | 1 | | | -| Unit2BatteryPack1FirmwareVersion | String | | | | -| Unit2BatteryPack1WorkingStatus | Number | 1 | | | -| Unit2BatteryPack1Voltage | Number | 10 | V | | -| Unit2BatteryPack1Current | Number | 10 | A | | -| Unit2BatteryPack1SOC | Number | 10 | % | | -| Unit2BatteryPack1ChargeDischargePower | Number | 1 | W | | -| Unit2BatteryPack1TotalCharge | Number | 100 | kWh | | -| Unit2BatteryPack1TotalDischarge | Number | 100 | kWh | | -| Unit2BatteryPack1MinimumTemperature | Number | 10 | °C | | -| Unit2BatteryPack1MaximumTemperature | Number | 10 | °C | | -| Unit2BatteryPack2SN | String | | | | -| Unit2BatteryPack2No | Number | 1 | | | -| Unit2BatteryPack2FirmwareVersion | String | | | | -| Unit2BatteryPack2WorkingStatus | Number | 1 | | | -| Unit2BatteryPack2Voltage | Number | 10 | V | | -| Unit2BatteryPack2Current | Number | 10 | A | | -| Unit2BatteryPack2SOC | Number | 10 | % | | -| Unit2BatteryPack2ChargeDischargePower | Number | 1 | W | | -| Unit2BatteryPack2TotalCharge | Number | 100 | kWh | | -| Unit2BatteryPack2TotalDischarge | Number | 100 | kWh | | -| Unit2BatteryPack2MinimumTemperature | Number | 10 | °C | | -| Unit2BatteryPack2MaximumTemperature | Number | 10 | °C | | -| Unit2BatteryPack3SN | String | | | | -| Unit2BatteryPack3No | Number | 1 | | | -| Unit2BatteryPack3FirmwareVersion | String | | | | -| Unit2BatteryPack3WorkingStatus | Number | 1 | | | -| Unit2BatteryPack3Voltage | Number | 10 | V | | -| Unit2BatteryPack3Current | Number | 10 | A | | -| Unit2BatteryPack3SOC | Number | 10 | % | | -| Unit2BatteryPack3ChargeDischargePower | Number | 1 | W | | -| Unit2BatteryPack3TotalCharge | Number | 100 | kWh | | -| Unit2BatteryPack3TotalDischarge | Number | 100 | kWh | | -| Unit2BatteryPack3MinimumTemperature | Number | 10 | °C | | -| Unit2BatteryPack3MaximumTemperature | Number | 10 | °C | | +| Name | Type | Gain | Unit | Access Type | +|----------------------------------------|------------|------|---------|-------------| +| RunningStatus | Number | 1 | | RO | +| WorkingModeSettings | Number | 1 | | RW | +| BusVoltage | Number | 10 | V | RO | +| BusCurrent | Number | 10 | A | RO | +| ChargeDischargePower | Number | 1 | W | RO | +| MaximumChargePower | Number | 1 | W | RO | +| MaximumDischargePower | Number | 1 | W | RO | +| RatedCapacity | Number | 1 | Wh | RO | +| SOC | Number | 10 | % | RO | +| BackupPowerSOC | Number | 10 | % | RW | +| TotalCharge | Number | 100 | kWh | RO | +| TotalDischarge | Number | 100 | kWh | RO | +| CurrentDayChargeCapacity | Number | 100 | kWh | RO | +| CurrentDayDischargeCapacity | Number | 100 | kWh | RO | +| TimeOfUseElectricityPricePeriods | Bytestring | | | RW | +| MaximumChargingPower | Number | 1 | W | RW | +| MaximumDischargingPower | Number | 1 | W | RW | +| ChargingCutoffCapacity | Number | 10 | % | RW | +| DischargeCutoffCapacity | Number | 10 | % | RW | +| ForcedChargingAndDischargingPeriod | Number | 1 | minutes | RW | +| ChargeFromGridFunction | Number | 1 | | RW | +| GridChargeCutoffSOC | Number | 10 | % | RW | +| ForcibleChargeDischarge | Number | 1 | | WO | +| FixedChargingAndDischargingPeriods | Bytestring | | | RW | +| PowerOfChargeFromGrid | Number | 0.1 | W | RW | +| MaximumPowerOfChargeFromGrid | Number | 0.1 | W | RW | +| ForcibleChargeDischargeSettingMode | Number | 1 | | RW | +| ForcibleChargePower | Number | 0.1 | W | RW | +| ForcibleDischargePower | Number | 0.1 | W | RW | +| TimeOfUseChargingAndDischargingPeriods | Bytestring | | | RW | +| ExcessPVEnergyUseInTOU | Number | 1 | | RW | +| ActivePowerControlMode | Number | 1 | | RW | +| MaximumFeedGridPowerInKW | Number | 1000 | kW | RW | +| MaximumFeedGridPowerInPercentage | Number | 10 | % | RW | +| MaximumChargeFromGridPower | Number | 0.1 | W | RW | +| SwitchToOffGrid | Number | 1 | | RW | +| VoltageInIndependentOperation | Number | 1 | | RW | +| Unit1ProductModel | Number | 1 | | RW | +| Unit1SN | String | | | RO | +| Unit1No | Number | 1 | | RW | +| Unit1SoftwareVersion | String | | | RO | +| Unit1DCDCVersion | String | | | RO | +| Unit1BMSVersion | String | | | RO | +| Unit1RunningStatus | Number | 1 | | RO | +| Unit1WorkingMode | Number | 1 | | RO | +| Unit1BusVoltage | Number | 10 | V | RO | +| Unit1BusCurrent | Number | 10 | A | RO | +| Unit1BatterySOC | Number | 10 | % | RO | +| Unit1ChargeAndDischargePower | Number | 1 | W | RO | +| Unit1RemainingChargeDischargeTime | Number | 1 | minutes | RO | +| Unit1RatedChargePower | Number | 1 | W | RO | +| Unit1RatedDischargePower | Number | 1 | W | RO | +| Unit1CurrentDayChargeCapacity | Number | 100 | kWh | RO | +| Unit1CurrentDayDischargeCapacity | Number | 100 | kWh | RO | +| Unit1TotalCharge | Number | 100 | kWh | RO | +| Unit1TotalDischarge | Number | 100 | kWh | RO | +| Unit1BatteryTemperature | Number | 10 | °C | RO | +| Unit1FaultID | Number | 1 | | RO | +| Unit2ProductModel | Number | 1 | | RW | +| Unit2SN | String | | | RO | +| Unit2No | Number | 1 | | RW | +| Unit2SoftwareVersion | String | | | RO | +| Unit2RunningStatus | Number | 1 | | RO | +| Unit2BusVoltage | Number | 10 | V | RO | +| Unit2BusCurrent | Number | 10 | A | RO | +| Unit2BatterySOC | Number | 10 | % | RO | +| Unit2ChargeAndDischargePower | Number | 1 | W | RO | +| Unit2CurrentDayChargeCapacity | Number | 100 | kWh | RO | +| Unit2CurrentDayDischargeCapacity | Number | 100 | kWh | RO | +| Unit2TotalCharge | Number | 100 | kWh | RO | +| Unit2TotalDischarge | Number | 100 | kWh | RO | +| Unit2BatteryTemperature | Number | 10 | °C | RO | +| Unit1BatteryPack1SN | String | | | RO | +| Unit1BatteryPack1No | Number | 1 | | RW | +| Unit1BatteryPack1FirmwareVersion | String | | | RO | +| Unit1BatteryPack1WorkingStatus | Number | 1 | | RO | +| Unit1BatteryPack1Voltage | Number | 10 | V | RO | +| Unit1BatteryPack1Current | Number | 10 | A | RO | +| Unit1BatteryPack1SOC | Number | 10 | % | RO | +| Unit1BatteryPack1ChargeDischargePower | Number | 1 | W | RO | +| Unit1BatteryPack1TotalCharge | Number | 100 | kWh | RO | +| Unit1BatteryPack1TotalDischarge | Number | 100 | kWh | RO | +| Unit1BatteryPack1MinimumTemperature | Number | 10 | °C | RO | +| Unit1BatteryPack1MaximumTemperature | Number | 10 | °C | RO | +| Unit1BatteryPack2SN | String | | | RO | +| Unit1BatteryPack2No | Number | 1 | | RW | +| Unit1BatteryPack2FirmwareVersion | String | | | RO | +| Unit1BatteryPack2WorkingStatus | Number | 1 | | RO | +| Unit1BatteryPack2Voltage | Number | 10 | V | RO | +| Unit1BatteryPack2Current | Number | 10 | A | RO | +| Unit1BatteryPack2SOC | Number | 10 | % | RO | +| Unit1BatteryPack2ChargeDischargePower | Number | 1 | W | RO | +| Unit1BatteryPack2TotalCharge | Number | 100 | kWh | RO | +| Unit1BatteryPack2TotalDischarge | Number | 100 | kWh | RO | +| Unit1BatteryPack2MinimumTemperature | Number | 10 | °C | RO | +| Unit1BatteryPack2MaximumTemperature | Number | 10 | °C | RO | +| Unit1BatteryPack3SN | String | | | RO | +| Unit1BatteryPack3No | Number | 1 | | RW | +| Unit1BatteryPack3FirmwareVersion | String | | | RO | +| Unit1BatteryPack3WorkingStatus | Number | 1 | | RO | +| Unit1BatteryPack3Voltage | Number | 10 | V | RO | +| Unit1BatteryPack3Current | Number | 10 | A | RO | +| Unit1BatteryPack3SOC | Number | 10 | % | RO | +| Unit1BatteryPack3ChargeDischargePower | Number | 1 | W | RO | +| Unit1BatteryPack3TotalCharge | Number | 100 | kWh | RO | +| Unit1BatteryPack3TotalDischarge | Number | 100 | kWh | RO | +| Unit1BatteryPack3MinimumTemperature | Number | 10 | °C | RO | +| Unit1BatteryPack3MaximumTemperature | Number | 10 | °C | RO | +| Unit2BatteryPack1SN | String | | | RO | +| Unit2BatteryPack1No | Number | 1 | | RW | +| Unit2BatteryPack1FirmwareVersion | String | | | RO | +| Unit2BatteryPack1WorkingStatus | Number | 1 | | RO | +| Unit2BatteryPack1Voltage | Number | 10 | V | RO | +| Unit2BatteryPack1Current | Number | 10 | A | RO | +| Unit2BatteryPack1SOC | Number | 10 | % | RO | +| Unit2BatteryPack1ChargeDischargePower | Number | 1 | W | RO | +| Unit2BatteryPack1TotalCharge | Number | 100 | kWh | RO | +| Unit2BatteryPack1TotalDischarge | Number | 100 | kWh | RO | +| Unit2BatteryPack1MinimumTemperature | Number | 10 | °C | RO | +| Unit2BatteryPack1MaximumTemperature | Number | 10 | °C | RO | +| Unit2BatteryPack2SN | String | | | RO | +| Unit2BatteryPack2No | Number | 1 | | RW | +| Unit2BatteryPack2FirmwareVersion | String | | | RO | +| Unit2BatteryPack2WorkingStatus | Number | 1 | | RO | +| Unit2BatteryPack2Voltage | Number | 10 | V | RO | +| Unit2BatteryPack2Current | Number | 10 | A | RO | +| Unit2BatteryPack2SOC | Number | 10 | % | RO | +| Unit2BatteryPack2ChargeDischargePower | Number | 1 | W | RO | +| Unit2BatteryPack2TotalCharge | Number | 100 | kWh | RO | +| Unit2BatteryPack2TotalDischarge | Number | 100 | kWh | RO | +| Unit2BatteryPack2MinimumTemperature | Number | 10 | °C | RO | +| Unit2BatteryPack2MaximumTemperature | Number | 10 | °C | RO | +| Unit2BatteryPack3SN | String | | | RO | +| Unit2BatteryPack3No | Number | 1 | | RW | +| Unit2BatteryPack3FirmwareVersion | String | | | RO | +| Unit2BatteryPack3WorkingStatus | Number | 1 | | RO | +| Unit2BatteryPack3Voltage | Number | 10 | V | RO | +| Unit2BatteryPack3Current | Number | 10 | A | RO | +| Unit2BatteryPack3SOC | Number | 10 | % | RO | +| Unit2BatteryPack3ChargeDischargePower | Number | 1 | W | RO | +| Unit2BatteryPack3TotalCharge | Number | 100 | kWh | RO | +| Unit2BatteryPack3TotalDischarge | Number | 100 | kWh | RO | +| Unit2BatteryPack3MinimumTemperature | Number | 10 | °C | RO | +| Unit2BatteryPack3MaximumTemperature | Number | 10 | °C | RO | ### MeterEquipmentRegister -| Name | Type | Gain | Unit | -|---------------------------|--------|------|------| -| MeterStatus | Number | 1 | | -| APhaseVoltage | Number | 10 | V | -| BPhaseVoltage | Number | 10 | V | -| CPhaseVoltage | Number | 10 | V | -| APhaseCurrent | Number | 100 | A | -| BPhaseCurrent | Number | 100 | A | -| CPhaseCurrent | Number | 100 | A | -| ActivePower | Number | 1 | W | -| ReactivePower | Number | 1 | var | -| PowerFactor | Number | 1000 | | -| GridFrequency | Number | 100 | Hz | -| PositiveActiveElectricity | Number | 100 | kWh | -| ReverseActivePower | Number | 100 | kWh | -| AccumulatedReactivePower | Number | 100 | kvar | -| MeterType | Number | 1 | | -| ABLineVoltage | Number | 10 | V | -| BCLineVoltage | Number | 10 | V | -| CALineVoltage | Number | 10 | V | -| APhaseActivePower | Number | 1 | W | -| BPhaseActivePower | Number | 1 | W | -| CPhaseActivePower | Number | 1 | W | -| MeterModelDetectionResult | Number | 1 | | +| Name | Type | Gain | Unit | Access Type | +|---------------------------|--------|------|------|-------------| +| MeterStatus | Number | 1 | | RO | +| APhaseVoltage | Number | 10 | V | RO | +| BPhaseVoltage | Number | 10 | V | RO | +| CPhaseVoltage | Number | 10 | V | RO | +| APhaseCurrent | Number | 100 | A | RO | +| BPhaseCurrent | Number | 100 | A | RO | +| CPhaseCurrent | Number | 100 | A | RO | +| ActivePower | Number | 1 | W | RO | +| ReactivePower | Number | 1 | var | RO | +| PowerFactor | Number | 1000 | | RO | +| GridFrequency | Number | 100 | Hz | RO | +| PositiveActiveElectricity | Number | 100 | kWh | RO | +| ReverseActivePower | Number | 100 | kWh | RO | +| AccumulatedReactivePower | Number | 100 | kvar | RO | +| MeterType | Number | 1 | | RO | +| ABLineVoltage | Number | 10 | V | RO | +| BCLineVoltage | Number | 10 | V | RO | +| CALineVoltage | Number | 10 | V | RO | +| APhaseActivePower | Number | 1 | W | RO | +| BPhaseActivePower | Number | 1 | W | RO | +| CPhaseActivePower | Number | 1 | W | RO | +| MeterModelDetectionResult | Number | 1 | | RO | diff --git a/sun2000_modbus/datatypes.py b/sun2000_modbus/datatypes.py index e981bc3..968f2a5 100644 --- a/sun2000_modbus/datatypes.py +++ b/sun2000_modbus/datatypes.py @@ -2,32 +2,57 @@ class DataType(Enum): - STRING = "string" - UINT16_BE = "uint16" - UINT32_BE = "uint32" - INT16_BE = "int16" - INT32_BE = "int32" - BITFIELD16 = "bitfield16" - BITFIELD32 = "bitfield32" - MULTIDATA = "multidata" + STRING = 'string' + UINT16_BE = 'uint16' + UINT32_BE = 'uint32' + INT16_BE = 'int16' + INT32_BE = 'int32' + BITFIELD16 = 'bitfield16' + BITFIELD32 = 'bitfield32' + MULTIDATA = 'multidata' def decode_string(value): - return value.decode("utf-8", "replace").strip("\0") + return value.decode('utf-8', 'replace').strip('\0') + + +def encode_uint_be(value, length): + return int.to_bytes(value, length=length, byteorder='big', signed=False) def decode_uint_be(value): - return int.from_bytes(value, byteorder="big", signed=False) + return int.from_bytes(value, byteorder='big', signed=False) + + +def encode_int_be(value, length): + return int.to_bytes(value, length=length, byteorder='big', signed=True) def decode_int_be(value): - return int.from_bytes(value, byteorder="big", signed=True) + return int.from_bytes(value, byteorder='big', signed=True) def decode_bitfield(value): return ''.join(format(byte, '08b') for byte in value) +def encode(value, data_type): + if data_type == DataType.UINT16_BE: + return encode_uint_be(value, 2) + elif data_type == DataType.UINT32_BE: + return encode_uint_be(value, 4) + elif data_type == DataType.INT16_BE: + return encode_int_be(value, 2) + elif data_type == DataType.INT32_BE: + return encode_int_be(value, 4) + elif data_type == DataType.MULTIDATA: + if len(value) % 2 != 0: + raise ValueError('Multidata value length must be a multiple of 2') + return value + else: + raise ValueError('Writing is not supported for register type') + + def decode(value, data_type): if data_type == DataType.STRING: return decode_string(value) @@ -40,4 +65,4 @@ def decode(value, data_type): elif data_type == DataType.MULTIDATA: return value else: - raise ValueError("Unknown register type") + raise ValueError('Unknown register type') diff --git a/sun2000_modbus/inverter.py b/sun2000_modbus/inverter.py index 5a59ffe..969017a 100644 --- a/sun2000_modbus/inverter.py +++ b/sun2000_modbus/inverter.py @@ -5,6 +5,7 @@ from pymodbus.exceptions import ModbusIOException, ConnectionException from . import datatypes +from .registers import AccessType logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) @@ -35,8 +36,8 @@ def connect(self): def disconnect(self): """Close the underlying tcp socket""" # Some Sun2000 models with the SDongle WLAN-FE require the TCP connection to be closed - # as soon as possible. Leaving the TCP connection open for an extended time may cause - # dongle reboots and/or FusionSolar portal updates to be delayed or even paused. + # as soon as possible. Leaving the TCP connection open for an extended time may cause + # dongle reboots and/or FusionSolar portal updates to be delayed or even paused. self.inverter.close() def isConnected(self): @@ -54,10 +55,10 @@ def read_raw_value(self, register): try: register_value = self.inverter.read_holding_registers(register.value.address, register.value.quantity, slave=self.slave) if type(register_value) == ModbusIOException: - logger.error("Inverter unit did not respond") + logger.error('Inverter unit did not respond') raise register_value except ConnectionException: - logger.error("A connection error occurred") + logger.error('A connection error occurred') raise return datatypes.decode(register_value.encode()[1:], register.value.data_type) @@ -85,11 +86,11 @@ def read_formatted(self, register, use_locale=False): def read_range(self, start_address, quantity=0, end_address=0): if quantity == 0 and end_address == 0: - raise ValueError("Either parameter quantity or end_address is required and must be greater than 0") + raise ValueError('Either parameter quantity or end_address is required and must be greater than 0') if quantity != 0 and end_address != 0: - raise ValueError("Only one parameter quantity or end_address should be defined") + raise ValueError('Only one parameter quantity or end_address should be defined') if end_address != 0 and end_address <= start_address: - raise ValueError("end_address must be greater than start_address") + raise ValueError('end_address must be greater than start_address') if not self.isConnected(): raise ValueError('Inverter is not connected') @@ -99,10 +100,28 @@ def read_range(self, start_address, quantity=0, end_address=0): try: register_range_value = self.inverter.read_holding_registers(start_address, quantity, slave=self.slave) if type(register_range_value) == ModbusIOException: - logger.error("Inverter unit did not respond") + logger.error('Inverter unit did not respond') raise register_range_value except ConnectionException: - logger.error("A connection error occurred") + logger.error('A connection error occurred') raise return datatypes.decode(register_range_value.encode()[1:], datatypes.DataType.MULTIDATA) + + def write(self, register, value): + if not self.isConnected(): + raise ValueError('Inverter is not connected') + if not register.value.access_type in [AccessType.RW, AccessType.WO]: + raise ValueError('Register is not writeable') + + encoded_value = datatypes.encode(value, register.value.data_type) + chunks = [encoded_value[i:i+2] for i in range(0, len(encoded_value), 2)] + + try: + response = self.inverter.write_registers(register.value.address, chunks, slave=self.slave, skip_encode=True) + if type(response) == ModbusIOException: + logger.error('Inverter unit did not respond') + raise response + except ConnectionException: + logger.error('A connection error occurred') + raise diff --git a/sun2000_modbus/registers.py b/sun2000_modbus/registers.py index f4acc08..17e54cb 100644 --- a/sun2000_modbus/registers.py +++ b/sun2000_modbus/registers.py @@ -5,9 +5,9 @@ class AccessType(Enum): - RO = "ro" - RW = "rw" - WO = "wo" + RO = 'ro' + RW = 'rw' + WO = 'wo' class Register: @@ -39,159 +39,159 @@ class InverterEquipmentRegister(Enum): ModelID = Register(30070, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) NumberOfPVStrings = Register(30071, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) NumberOfMPPTrackers = Register(30072, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) - RatedPower = Register(30073, 2, datatypes.DataType.UINT32_BE, 1, "W", AccessType.RO, None) - MaximumActivePower = Register(30075, 2, datatypes.DataType.UINT32_BE, 1, "W", AccessType.RO, None) - MaximumApparentPower = Register(30077, 2, datatypes.DataType.UINT32_BE, 1000, "kVA", AccessType.RO, None) - MaximumReactivePowerFedToTheGrid = Register(30079, 2, datatypes.DataType.INT32_BE, 1000, "kvar", AccessType.RO, None) - MaximumReactivePowerAbsorbedFromTheGrid = Register(30081, 2, datatypes.DataType.INT32_BE, 1000, "kvar", AccessType.RO, None) + RatedPower = Register(30073, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RO, None) + MaximumActivePower = Register(30075, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RO, None) + MaximumApparentPower = Register(30077, 2, datatypes.DataType.UINT32_BE, 1000, 'kVA', AccessType.RO, None) + MaximumReactivePowerFedToTheGrid = Register(30079, 2, datatypes.DataType.INT32_BE, 1000, 'kvar', AccessType.RO, None) + MaximumReactivePowerAbsorbedFromTheGrid = Register(30081, 2, datatypes.DataType.INT32_BE, 1000, 'kvar', AccessType.RO, None) State1 = Register(32000, 1, datatypes.DataType.BITFIELD16, None, None, AccessType.RO, None) State2 = Register(32002, 1, datatypes.DataType.BITFIELD16, None, None, AccessType.RO, None) State3 = Register(32003, 2, datatypes.DataType.BITFIELD32, None, None, AccessType.RO, None) Alarm1 = Register(32008, 1, datatypes.DataType.BITFIELD16, None, None, AccessType.RO, None) Alarm2 = Register(32009, 1, datatypes.DataType.BITFIELD16, None, None, AccessType.RO, None) Alarm3 = Register(32010, 1, datatypes.DataType.BITFIELD16, None, None, AccessType.RO, None) - PV1Voltage = Register(32016, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV1Current = Register(32017, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV2Voltage = Register(32018, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV2Current = Register(32019, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV3Voltage = Register(32020, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV3Current = Register(32021, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV4Voltage = Register(32022, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV4Current = Register(32023, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV5Voltage = Register(32024, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV5Current = Register(32025, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV6Voltage = Register(32026, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV6Current = Register(32027, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV7Voltage = Register(32028, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV7Current = Register(32029, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV8Voltage = Register(32030, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV8Current = Register(32031, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV9Voltage = Register(32032, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV9Current = Register(32033, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV10Voltage = Register(32034, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV10Current = Register(32035, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV11Voltage = Register(32036, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV11Current = Register(32037, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV12Voltage = Register(32038, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV12Current = Register(32039, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV13Voltage = Register(32040, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV13Current = Register(32041, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV14Voltage = Register(32042, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV14Current = Register(32043, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV15Voltage = Register(32044, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV15Current = Register(32045, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV16Voltage = Register(32046, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV16Current = Register(32047, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV17Voltage = Register(32048, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV17Current = Register(32049, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV18Voltage = Register(32050, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV18Current = Register(32051, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV19Voltage = Register(32052, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV19Current = Register(32053, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV20Voltage = Register(32054, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV20Current = Register(32055, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV21Voltage = Register(32056, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV21Current = Register(32057, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV22Voltage = Register(32058, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV22Current = Register(32059, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV23Voltage = Register(32060, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV23Current = Register(32061, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - PV24Voltage = Register(32062, 1, datatypes.DataType.INT16_BE, 10, "V", AccessType.RO, None) - PV24Current = Register(32063, 1, datatypes.DataType.INT16_BE, 100, "A", AccessType.RO, None) - InputPower = Register(32064, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - LineVoltageBetweenPhasesAAndB = Register(32066, 1, datatypes.DataType.UINT16_BE, 10, "V", AccessType.RO, None) - LineVoltageBetweenPhasesBAndC = Register(32067, 1, datatypes.DataType.UINT16_BE, 10, "V", AccessType.RO, None) - LineVoltageBetweenPhasesCAndA = Register(32068, 1, datatypes.DataType.UINT16_BE, 10, "V", AccessType.RO, None) - PhaseAVoltage = Register(32069, 1, datatypes.DataType.UINT16_BE, 10, "V", AccessType.RO, None) - PhaseBVoltage = Register(32070, 1, datatypes.DataType.UINT16_BE, 10, "V", AccessType.RO, None) - PhaseCVoltage = Register(32071, 1, datatypes.DataType.UINT16_BE, 10, "V", AccessType.RO, None) - PhaseACurrent = Register(32072, 2, datatypes.DataType.INT32_BE, 1000, "A", AccessType.RO, None) - PhaseBCurrent = Register(32074, 2, datatypes.DataType.INT32_BE, 1000, "A", AccessType.RO, None) - PhaseCCurrent = Register(32076, 2, datatypes.DataType.INT32_BE, 1000, "A", AccessType.RO, None) - PeakActivePowerOfCurrentDay = Register(32078, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - ActivePower = Register(32080, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - ReactivePower = Register(32082, 2, datatypes.DataType.INT32_BE, 1000, "kvar", AccessType.RO, None) + PV1Voltage = Register(32016, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV1Current = Register(32017, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV2Voltage = Register(32018, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV2Current = Register(32019, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV3Voltage = Register(32020, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV3Current = Register(32021, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV4Voltage = Register(32022, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV4Current = Register(32023, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV5Voltage = Register(32024, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV5Current = Register(32025, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV6Voltage = Register(32026, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV6Current = Register(32027, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV7Voltage = Register(32028, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV7Current = Register(32029, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV8Voltage = Register(32030, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV8Current = Register(32031, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV9Voltage = Register(32032, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV9Current = Register(32033, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV10Voltage = Register(32034, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV10Current = Register(32035, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV11Voltage = Register(32036, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV11Current = Register(32037, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV12Voltage = Register(32038, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV12Current = Register(32039, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV13Voltage = Register(32040, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV13Current = Register(32041, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV14Voltage = Register(32042, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV14Current = Register(32043, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV15Voltage = Register(32044, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV15Current = Register(32045, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV16Voltage = Register(32046, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV16Current = Register(32047, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV17Voltage = Register(32048, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV17Current = Register(32049, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV18Voltage = Register(32050, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV18Current = Register(32051, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV19Voltage = Register(32052, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV19Current = Register(32053, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV20Voltage = Register(32054, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV20Current = Register(32055, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV21Voltage = Register(32056, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV21Current = Register(32057, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV22Voltage = Register(32058, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV22Current = Register(32059, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV23Voltage = Register(32060, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV23Current = Register(32061, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + PV24Voltage = Register(32062, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) + PV24Current = Register(32063, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) + InputPower = Register(32064, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + LineVoltageBetweenPhasesAAndB = Register(32066, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) + LineVoltageBetweenPhasesBAndC = Register(32067, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) + LineVoltageBetweenPhasesCAndA = Register(32068, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) + PhaseAVoltage = Register(32069, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) + PhaseBVoltage = Register(32070, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) + PhaseCVoltage = Register(32071, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) + PhaseACurrent = Register(32072, 2, datatypes.DataType.INT32_BE, 1000, 'A', AccessType.RO, None) + PhaseBCurrent = Register(32074, 2, datatypes.DataType.INT32_BE, 1000, 'A', AccessType.RO, None) + PhaseCCurrent = Register(32076, 2, datatypes.DataType.INT32_BE, 1000, 'A', AccessType.RO, None) + PeakActivePowerOfCurrentDay = Register(32078, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + ActivePower = Register(32080, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + ReactivePower = Register(32082, 2, datatypes.DataType.INT32_BE, 1000, 'kvar', AccessType.RO, None) PowerFactor = Register(32084, 1, datatypes.DataType.INT16_BE, 1000, None, AccessType.RO, None) - GridFrequency = Register(32085, 1, datatypes.DataType.UINT16_BE, 100, "Hz", AccessType.RO, None) - Efficiency = Register(32086, 1, datatypes.DataType.UINT16_BE, 100, "%", AccessType.RO, None) - InternalTemperature = Register(32087, 1, datatypes.DataType.INT16_BE, 10, "°C", AccessType.RO, None) - InsulationResistance = Register(32088, 1, datatypes.DataType.UINT16_BE, 1000, "MOhm", AccessType.RO, None) + GridFrequency = Register(32085, 1, datatypes.DataType.UINT16_BE, 100, 'Hz', AccessType.RO, None) + Efficiency = Register(32086, 1, datatypes.DataType.UINT16_BE, 100, '%', AccessType.RO, None) + InternalTemperature = Register(32087, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) + InsulationResistance = Register(32088, 1, datatypes.DataType.UINT16_BE, 1000, 'MOhm', AccessType.RO, None) DeviceStatus = Register(32089, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, mappings.DeviceStatus) FaultCode = Register(32090, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) StartupTime = Register(32091, 2, datatypes.DataType.UINT32_BE, 1, None, AccessType.RO, None) ShutdownTime = Register(32093, 2, datatypes.DataType.UINT32_BE, 1, None, AccessType.RO, None) - AccumulatedEnergyYield = Register(32106, 2, datatypes.DataType.UINT32_BE, 100, "kWh", AccessType.RO, None) - DailyEnergyYield = Register(32114, 2, datatypes.DataType.UINT32_BE, 100, "kWh", AccessType.RO, None) + AccumulatedEnergyYield = Register(32106, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + DailyEnergyYield = Register(32114, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) ActiveAdjustmentMode = Register(35300, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) ActiveAdjustmentValue = Register(35302, 2, datatypes.DataType.UINT32_BE, 1, None, AccessType.RO, None) ActiveAdjustmentCommand = Register(35303, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) ReactiveAdjustmentMode = Register(35304, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) ReactiveAdjustmentValue = Register(35305, 2, datatypes.DataType.UINT32_BE, 1, None, AccessType.RO, None) ReactiveAdjustmentCommand = Register(35307, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) - PowerMeterCollectionActivePower = Register(37113, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) + PowerMeterCollectionActivePower = Register(37113, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) TotalNumberOfOptimizers = Register(37200, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) NumberOfOnlineOptimizers = Register(37201, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) FeatureData = Register(37202, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) SystemTime = Register(40000, 2, datatypes.DataType.UINT32_BE, 1, None, AccessType.RW, None) QUCharacteristicCurveMode = Register(40037, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) - QUDispatchTriggerPower = Register(40038, 1, datatypes.DataType.UINT16_BE, 1, "%", AccessType.RW, None) - FixedActivePowerDeratedInKW = Register(40120, 1, datatypes.DataType.UINT16_BE, 10, "kW", AccessType.RW, None) + QUDispatchTriggerPower = Register(40038, 1, datatypes.DataType.UINT16_BE, 1, '%', AccessType.RW, None) + FixedActivePowerDeratedInKW = Register(40120, 1, datatypes.DataType.UINT16_BE, 10, 'kW', AccessType.RW, None) ReactivePowerCompensationInPF = Register(40122, 1, datatypes.DataType.INT16_BE, 1000, None, AccessType.RW, None) ReactivePowerCompensationQS = Register(40123, 1, datatypes.DataType.INT16_BE, 1000, None, AccessType.RW, None) - ActivePowerPercentageDerating = Register(40125, 1, datatypes.DataType.UINT16_BE, 10, "%", AccessType.RW, None) - FixedActivePowerDeratedInW = Register(40126, 2, datatypes.DataType.UINT32_BE, 1, "W", AccessType.RW, None) - ReactivePowerCompensationAtNight = Register(40129, 2, datatypes.DataType.INT32_BE, 1000, "kvar", AccessType.RW, None) + ActivePowerPercentageDerating = Register(40125, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RW, None) + FixedActivePowerDeratedInW = Register(40126, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RW, None) + ReactivePowerCompensationAtNight = Register(40129, 2, datatypes.DataType.INT32_BE, 1000, 'kvar', AccessType.RW, None) CosPhiPPnCharacteristicCurve = Register(40133, 21, datatypes.DataType.MULTIDATA, None, None, AccessType.RW, None) QUCharacteristicCurve = Register(40154, 21, datatypes.DataType.MULTIDATA, None, None, AccessType.RW, None) PFUCharacteristicCurve = Register(40175, 21, datatypes.DataType.MULTIDATA, None, None, AccessType.RW, None) - ReactivePowerAdjustmentTime = Register(40196, 1, datatypes.DataType.UINT16_BE, 1, "s", AccessType.RW, None) - QUPowerPercentageToExitScheduling = Register(40198, 1, datatypes.DataType.UINT16_BE, 1, "%", AccessType.RW, None) - # Startup = Register(40200, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.WO, None) # disabled because not readable (AccessType.WO) - # Shutdown = Register(40201, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.WO, None) # disabled because not readable (AccessType.WO) + ReactivePowerAdjustmentTime = Register(40196, 1, datatypes.DataType.UINT16_BE, 1, 's', AccessType.RW, None) + QUPowerPercentageToExitScheduling = Register(40198, 1, datatypes.DataType.UINT16_BE, 1, '%', AccessType.RW, None) + Startup = Register(40200, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.WO, None) + Shutdown = Register(40201, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.WO, None) GridCode = Register(42000, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) - ReactivePowerChangeGradient = Register(42015, 2, datatypes.DataType.UINT32_BE, 1000, "%/s", AccessType.RW, None) - ActivePowerChangeGradient = Register(42017, 2, datatypes.DataType.UINT32_BE, 1000, "%/s", AccessType.RW, None) - ScheduleInstructionValidDuration = Register(42019, 2, datatypes.DataType.UINT32_BE, 1, "s", AccessType.RW, None) - TimeZone = Register(43006, 1, datatypes.DataType.INT16_BE, 1, "min", AccessType.RW, None) + ReactivePowerChangeGradient = Register(42015, 2, datatypes.DataType.UINT32_BE, 1000, '%/s', AccessType.RW, None) + ActivePowerChangeGradient = Register(42017, 2, datatypes.DataType.UINT32_BE, 1000, '%/s', AccessType.RW, None) + ScheduleInstructionValidDuration = Register(42019, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RW, None) + TimeZone = Register(43006, 1, datatypes.DataType.INT16_BE, 1, 'min', AccessType.RW, None) class BatteryEquipmentRegister(Enum): # Overall RunningStatus = Register(37762, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, mappings.RunningStatus) WorkingModeSettings = Register(47086, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.WorkingModeSettings) - BusVoltage = Register(37763, 1, datatypes.DataType.UINT16_BE, 10, "V", AccessType.RO, None) - BusCurrent = Register(37764, 1, datatypes.DataType.INT16_BE, 10, "A", AccessType.RO, None) - ChargeDischargePower = Register(37765, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - MaximumChargePower = Register(37046, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - MaximumDischargePower = Register(37048, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - RatedCapacity = Register(37758, 2, datatypes.DataType.INT32_BE, 1, "Wh", AccessType.RO, None) - SOC = Register(37760, 1, datatypes.DataType.UINT16_BE, 10, "%", AccessType.RO, None) - BackupPowerSOC = Register(47102, 1, datatypes.DataType.UINT16_BE, 10, "%", AccessType.RW, None) - TotalCharge = Register(37780, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - TotalDischarge = Register(37782, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - CurrentDayChargeCapacity = Register(37784, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - CurrentDayDischargeCapacity = Register(37786, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) + BusVoltage = Register(37763, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) + BusCurrent = Register(37764, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) + ChargeDischargePower = Register(37765, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + MaximumChargePower = Register(37046, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + MaximumDischargePower = Register(37048, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + RatedCapacity = Register(37758, 2, datatypes.DataType.INT32_BE, 1, 'Wh', AccessType.RO, None) + SOC = Register(37760, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) + BackupPowerSOC = Register(47102, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RW, None) + TotalCharge = Register(37780, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + TotalDischarge = Register(37782, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + CurrentDayChargeCapacity = Register(37784, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + CurrentDayDischargeCapacity = Register(37786, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) TimeOfUseElectricityPricePeriods = Register(47028, 41, datatypes.DataType.MULTIDATA, None, None, AccessType.RW, None) - MaximumChargingPower = Register(47075, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RW, None) - MaximumDischargingPower = Register(47077, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RW, None) - ChargingCutoffCapacity = Register(47081, 1, datatypes.DataType.UINT16_BE, 10, "%", AccessType.RW, None) - DischargeCutoffCapacity = Register(47082, 1, datatypes.DataType.UINT16_BE, 10, "%", AccessType.RW, None) - ForcedChargingAndDischargingPeriod = Register(47083, 1, datatypes.DataType.UINT16_BE, 1, "minutes", AccessType.RW, None) + MaximumChargingPower = Register(47075, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) + MaximumDischargingPower = Register(47077, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) + ChargingCutoffCapacity = Register(47081, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RW, None) + DischargeCutoffCapacity = Register(47082, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RW, None) + ForcedChargingAndDischargingPeriod = Register(47083, 1, datatypes.DataType.UINT16_BE, 1, 'minutes', AccessType.RW, None) ChargeFromGridFunction = Register(47087, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ChargeFromGridFunction) - GridChargeCutoffSOC = Register(47088, 1, datatypes.DataType.UINT16_BE, 10, "%", AccessType.RW, None) - # ForcibleChargeDischarge = Register(47100, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.WO, mappings.ForcibleChargeDischarge) # disabled because not readable (AccessType.WO) + GridChargeCutoffSOC = Register(47088, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RW, None) + ForcibleChargeDischarge = Register(47100, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.WO, mappings.ForcibleChargeDischarge) FixedChargingAndDischargingPeriods = Register(47200, 41, datatypes.DataType.MULTIDATA, None, None, AccessType.RW, None) - PowerOfChargeFromGrid = Register(47242, 2, datatypes.DataType.INT32_BE, 0.1, "W", AccessType.RW, None) - MaximumPowerOfChargeFromGrid = Register(47244, 2, datatypes.DataType.INT32_BE, 0.1, "W", AccessType.RW, None) + PowerOfChargeFromGrid = Register(47242, 2, datatypes.DataType.INT32_BE, 0.1, 'W', AccessType.RW, None) + MaximumPowerOfChargeFromGrid = Register(47244, 2, datatypes.DataType.INT32_BE, 0.1, 'W', AccessType.RW, None) ForcibleChargeDischargeSettingMode = Register(47246, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ForcibleChargeDischargeSettingMode) - ForcibleChargePower = Register(47247, 2, datatypes.DataType.INT32_BE, 0.1, "W", AccessType.RW, None) - ForcibleDischargePower = Register(47249, 2, datatypes.DataType.INT32_BE, 0.1, "W", AccessType.RW, None) + ForcibleChargePower = Register(47247, 2, datatypes.DataType.INT32_BE, 0.1, 'W', AccessType.RW, None) + ForcibleDischargePower = Register(47249, 2, datatypes.DataType.INT32_BE, 0.1, 'W', AccessType.RW, None) TimeOfUseChargingAndDischargingPeriods = Register(47255, 43, datatypes.DataType.MULTIDATA, None, None, AccessType.RW, None) ExcessPVEnergyUseInTOU = Register(47299, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ExcessPVEnergyUseInTOU) ActivePowerControlMode = Register(47415, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ActivePowerControlMode) - MaximumFeedGridPowerInKW = Register(47416, 2, datatypes.DataType.INT32_BE, 1000, "kW", AccessType.RW, None) - MaximumFeedGridPowerInPercentage = Register(47418, 1, datatypes.DataType.INT16_BE, 10, "%", AccessType.RW, None) - MaximumChargeFromGridPower = Register(47590, 2, datatypes.DataType.INT32_BE, 0.1, "W", AccessType.RW, None) + MaximumFeedGridPowerInKW = Register(47416, 2, datatypes.DataType.INT32_BE, 1000, 'kW', AccessType.RW, None) + MaximumFeedGridPowerInPercentage = Register(47418, 1, datatypes.DataType.INT16_BE, 10, '%', AccessType.RW, None) + MaximumChargeFromGridPower = Register(47590, 2, datatypes.DataType.INT32_BE, 0.1, 'W', AccessType.RW, None) SwitchToOffGrid = Register(47604, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.SwitchToOffGrid) VoltageInIndependentOperation = Register(47605, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.VoltageIndependentOperation) @@ -204,18 +204,18 @@ class BatteryEquipmentRegister(Enum): Unit1BMSVersion = Register(37036, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) Unit1RunningStatus = Register(37000, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, mappings.RunningStatus) Unit1WorkingMode = Register(37006, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, mappings.WorkingMode) - Unit1BusVoltage = Register(37003, 1, datatypes.DataType.UINT16_BE, 10, "V", AccessType.RO, None) - Unit1BusCurrent = Register(37021, 1, datatypes.DataType.INT16_BE, 10, "A", AccessType.RO, None) - Unit1BatterySOC = Register(37004, 1, datatypes.DataType.UINT16_BE, 10, "%", AccessType.RO, None) - Unit1ChargeAndDischargePower = Register(37001, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - Unit1RemainingChargeDischargeTime = Register(37025, 1, datatypes.DataType.UINT16_BE, 1, "minutes", AccessType.RO, None) - Unit1RatedChargePower = Register(37007, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - Unit1RatedDischargePower = Register(37009, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - Unit1CurrentDayChargeCapacity = Register(37015, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit1CurrentDayDischargeCapacity = Register(37017, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit1TotalCharge = Register(37066, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit1TotalDischarge = Register(37068, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit1BatteryTemperature = Register(37022, 1, datatypes.DataType.INT16_BE, 10, "°C", AccessType.RO, None) + Unit1BusVoltage = Register(37003, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) + Unit1BusCurrent = Register(37021, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) + Unit1BatterySOC = Register(37004, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) + Unit1ChargeAndDischargePower = Register(37001, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + Unit1RemainingChargeDischargeTime = Register(37025, 1, datatypes.DataType.UINT16_BE, 1, 'minutes', AccessType.RO, None) + Unit1RatedChargePower = Register(37007, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + Unit1RatedDischargePower = Register(37009, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + Unit1CurrentDayChargeCapacity = Register(37015, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1CurrentDayDischargeCapacity = Register(37017, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1TotalCharge = Register(37066, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1TotalDischarge = Register(37068, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1BatteryTemperature = Register(37022, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) Unit1FaultID = Register(37014, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) # Unit 2 @@ -224,121 +224,121 @@ class BatteryEquipmentRegister(Enum): Unit2No = Register(47108, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) Unit2SoftwareVersion = Register(37799, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) Unit2RunningStatus = Register(37741, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, mappings.RunningStatus) - Unit2BusVoltage = Register(37750, 1, datatypes.DataType.UINT16_BE, 10, "V", AccessType.RO, None) - Unit2BusCurrent = Register(37751, 1, datatypes.DataType.INT16_BE, 10, "A", AccessType.RO, None) - Unit2BatterySOC = Register(37738, 1, datatypes.DataType.UINT16_BE, 10, "%", AccessType.RO, None) - Unit2ChargeAndDischargePower = Register(37743, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - Unit2CurrentDayChargeCapacity = Register(37746, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit2CurrentDayDischargeCapacity = Register(37748, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit2TotalCharge = Register(37753, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit2TotalDischarge = Register(37755, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit2BatteryTemperature = Register(37752, 1, datatypes.DataType.INT16_BE, 10, "°C", AccessType.RO, None) + Unit2BusVoltage = Register(37750, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) + Unit2BusCurrent = Register(37751, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) + Unit2BatterySOC = Register(37738, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) + Unit2ChargeAndDischargePower = Register(37743, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + Unit2CurrentDayChargeCapacity = Register(37746, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2CurrentDayDischargeCapacity = Register(37748, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2TotalCharge = Register(37753, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2TotalDischarge = Register(37755, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2BatteryTemperature = Register(37752, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) # Unit 1 BatteryPack 1 Unit1BatteryPack1SN = Register(38200, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) Unit1BatteryPack1No = Register(47750, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) Unit1BatteryPack1FirmwareVersion = Register(38210, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) Unit1BatteryPack1WorkingStatus = Register(38228, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) - Unit1BatteryPack1Voltage = Register(38235, 1, datatypes.DataType.UINT16_BE, 10, "V", AccessType.RO, None) - Unit1BatteryPack1Current = Register(38236, 1, datatypes.DataType.INT16_BE, 10, "A", AccessType.RO, None) - Unit1BatteryPack1SOC = Register(38229, 1, datatypes.DataType.UINT16_BE, 10, "%", AccessType.RO, None) - Unit1BatteryPack1ChargeDischargePower = Register(38233, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - Unit1BatteryPack1TotalCharge = Register(38238, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit1BatteryPack1TotalDischarge = Register(38240, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit1BatteryPack1MinimumTemperature = Register(38453, 1, datatypes.DataType.INT16_BE, 10, "°C", AccessType.RO, None) - Unit1BatteryPack1MaximumTemperature = Register(38452, 1, datatypes.DataType.INT16_BE, 10, "°C", AccessType.RO, None) + Unit1BatteryPack1Voltage = Register(38235, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) + Unit1BatteryPack1Current = Register(38236, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) + Unit1BatteryPack1SOC = Register(38229, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) + Unit1BatteryPack1ChargeDischargePower = Register(38233, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + Unit1BatteryPack1TotalCharge = Register(38238, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1BatteryPack1TotalDischarge = Register(38240, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1BatteryPack1MinimumTemperature = Register(38453, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) + Unit1BatteryPack1MaximumTemperature = Register(38452, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) # Unit 1 BatteryPack 2 Unit1BatteryPack2SN = Register(38242, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) Unit1BatteryPack2No = Register(47751, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) Unit1BatteryPack2FirmwareVersion = Register(38252, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) Unit1BatteryPack2WorkingStatus = Register(38270, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) - Unit1BatteryPack2Voltage = Register(38277, 1, datatypes.DataType.UINT16_BE, 10, "V", AccessType.RO, None) - Unit1BatteryPack2Current = Register(38278, 1, datatypes.DataType.INT16_BE, 10, "A", AccessType.RO, None) - Unit1BatteryPack2SOC = Register(38271, 1, datatypes.DataType.UINT16_BE, 10, "%", AccessType.RO, None) - Unit1BatteryPack2ChargeDischargePower = Register(38275, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - Unit1BatteryPack2TotalCharge = Register(38280, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit1BatteryPack2TotalDischarge = Register(38282, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit1BatteryPack2MinimumTemperature = Register(38455, 1, datatypes.DataType.INT16_BE, 10, "°C", AccessType.RO, None) - Unit1BatteryPack2MaximumTemperature = Register(38454, 1, datatypes.DataType.INT16_BE, 10, "°C", AccessType.RO, None) + Unit1BatteryPack2Voltage = Register(38277, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) + Unit1BatteryPack2Current = Register(38278, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) + Unit1BatteryPack2SOC = Register(38271, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) + Unit1BatteryPack2ChargeDischargePower = Register(38275, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + Unit1BatteryPack2TotalCharge = Register(38280, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1BatteryPack2TotalDischarge = Register(38282, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1BatteryPack2MinimumTemperature = Register(38455, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) + Unit1BatteryPack2MaximumTemperature = Register(38454, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) # Unit 1 BatteryPack 3 Unit1BatteryPack3SN = Register(38284, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) Unit1BatteryPack3No = Register(47752, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) Unit1BatteryPack3FirmwareVersion = Register(38294, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) Unit1BatteryPack3WorkingStatus = Register(38312, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) - Unit1BatteryPack3Voltage = Register(38319, 1, datatypes.DataType.UINT16_BE, 10, "V", AccessType.RO, None) - Unit1BatteryPack3Current = Register(38320, 1, datatypes.DataType.INT16_BE, 10, "A", AccessType.RO, None) - Unit1BatteryPack3SOC = Register(38313, 1, datatypes.DataType.UINT16_BE, 10, "%", AccessType.RO, None) - Unit1BatteryPack3ChargeDischargePower = Register(38317, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - Unit1BatteryPack3TotalCharge = Register(38322, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit1BatteryPack3TotalDischarge = Register(38324, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit1BatteryPack3MinimumTemperature = Register(38457, 1, datatypes.DataType.INT16_BE, 10, "°C", AccessType.RO, None) - Unit1BatteryPack3MaximumTemperature = Register(38456, 1, datatypes.DataType.INT16_BE, 10, "°C", AccessType.RO, None) + Unit1BatteryPack3Voltage = Register(38319, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) + Unit1BatteryPack3Current = Register(38320, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) + Unit1BatteryPack3SOC = Register(38313, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) + Unit1BatteryPack3ChargeDischargePower = Register(38317, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + Unit1BatteryPack3TotalCharge = Register(38322, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1BatteryPack3TotalDischarge = Register(38324, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1BatteryPack3MinimumTemperature = Register(38457, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) + Unit1BatteryPack3MaximumTemperature = Register(38456, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) # Unit 2 BatteryPack 1 Unit2BatteryPack1SN = Register(38326, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) Unit2BatteryPack1No = Register(47753, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) Unit2BatteryPack1FirmwareVersion = Register(38336, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) Unit2BatteryPack1WorkingStatus = Register(38354, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) - Unit2BatteryPack1Voltage = Register(38361, 1, datatypes.DataType.UINT16_BE, 10, "V", AccessType.RO, None) - Unit2BatteryPack1Current = Register(38362, 1, datatypes.DataType.INT16_BE, 10, "A", AccessType.RO, None) - Unit2BatteryPack1SOC = Register(38355, 1, datatypes.DataType.UINT16_BE, 10, "%", AccessType.RO, None) - Unit2BatteryPack1ChargeDischargePower = Register(38359, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - Unit2BatteryPack1TotalCharge = Register(38364, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit2BatteryPack1TotalDischarge = Register(38366, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit2BatteryPack1MinimumTemperature = Register(38459, 1, datatypes.DataType.INT16_BE, 10, "°C", AccessType.RO, None) - Unit2BatteryPack1MaximumTemperature = Register(38458, 1, datatypes.DataType.INT16_BE, 10, "°C", AccessType.RO, None) + Unit2BatteryPack1Voltage = Register(38361, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) + Unit2BatteryPack1Current = Register(38362, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) + Unit2BatteryPack1SOC = Register(38355, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) + Unit2BatteryPack1ChargeDischargePower = Register(38359, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + Unit2BatteryPack1TotalCharge = Register(38364, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2BatteryPack1TotalDischarge = Register(38366, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2BatteryPack1MinimumTemperature = Register(38459, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) + Unit2BatteryPack1MaximumTemperature = Register(38458, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) # Unit 2 BatteryPack 2 Unit2BatteryPack2SN = Register(38368, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) Unit2BatteryPack2No = Register(47754, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) Unit2BatteryPack2FirmwareVersion = Register(38378, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) Unit2BatteryPack2WorkingStatus = Register(38396, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) - Unit2BatteryPack2Voltage = Register(38403, 1, datatypes.DataType.UINT16_BE, 10, "V", AccessType.RO, None) - Unit2BatteryPack2Current = Register(38404, 1, datatypes.DataType.INT16_BE, 10, "A", AccessType.RO, None) - Unit2BatteryPack2SOC = Register(38397, 1, datatypes.DataType.UINT16_BE, 10, "%", AccessType.RO, None) - Unit2BatteryPack2ChargeDischargePower = Register(38401, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - Unit2BatteryPack2TotalCharge = Register(38406, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit2BatteryPack2TotalDischarge = Register(38408, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit2BatteryPack2MinimumTemperature = Register(38461, 1, datatypes.DataType.INT16_BE, 10, "°C", AccessType.RO, None) - Unit2BatteryPack2MaximumTemperature = Register(38460, 1, datatypes.DataType.INT16_BE, 10, "°C", AccessType.RO, None) + Unit2BatteryPack2Voltage = Register(38403, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) + Unit2BatteryPack2Current = Register(38404, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) + Unit2BatteryPack2SOC = Register(38397, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) + Unit2BatteryPack2ChargeDischargePower = Register(38401, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + Unit2BatteryPack2TotalCharge = Register(38406, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2BatteryPack2TotalDischarge = Register(38408, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2BatteryPack2MinimumTemperature = Register(38461, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) + Unit2BatteryPack2MaximumTemperature = Register(38460, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) # Unit 2 BatteryPack 3 Unit2BatteryPack3SN = Register(38410, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) Unit2BatteryPack3No = Register(47755, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) Unit2BatteryPack3FirmwareVersion = Register(38420, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) Unit2BatteryPack3WorkingStatus = Register(38438, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) - Unit2BatteryPack3Voltage = Register(38445, 1, datatypes.DataType.UINT16_BE, 10, "V", AccessType.RO, None) - Unit2BatteryPack3Current = Register(38446, 1, datatypes.DataType.INT16_BE, 10, "A", AccessType.RO, None) - Unit2BatteryPack3SOC = Register(38439, 1, datatypes.DataType.UINT16_BE, 10, "%", AccessType.RO, None) - Unit2BatteryPack3ChargeDischargePower = Register(38443, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - Unit2BatteryPack3TotalCharge = Register(38448, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit2BatteryPack3TotalDischarge = Register(38450, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - Unit2BatteryPack3MinimumTemperature = Register(38463, 1, datatypes.DataType.INT16_BE, 10, "°C", AccessType.RO, None) - Unit2BatteryPack3MaximumTemperature = Register(38462, 1, datatypes.DataType.INT16_BE, 10, "°C", AccessType.RO, None) + Unit2BatteryPack3Voltage = Register(38445, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) + Unit2BatteryPack3Current = Register(38446, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) + Unit2BatteryPack3SOC = Register(38439, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) + Unit2BatteryPack3ChargeDischargePower = Register(38443, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + Unit2BatteryPack3TotalCharge = Register(38448, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2BatteryPack3TotalDischarge = Register(38450, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2BatteryPack3MinimumTemperature = Register(38463, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) + Unit2BatteryPack3MaximumTemperature = Register(38462, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) class MeterEquipmentRegister(Enum): MeterType = Register(37125, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, mappings.MeterType) MeterStatus = Register(37100, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, mappings.MeterStatus) MeterModelDetectionResult = Register(37138, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, mappings.MeterModelDetectionResult) - APhaseVoltage = Register(37101, 2, datatypes.DataType.INT32_BE, 10, "V", AccessType.RO, None) - BPhaseVoltage = Register(37103, 2, datatypes.DataType.INT32_BE, 10, "V", AccessType.RO, None) - CPhaseVoltage = Register(37105, 2, datatypes.DataType.INT32_BE, 10, "V", AccessType.RO, None) - APhaseCurrent = Register(37107, 2, datatypes.DataType.INT32_BE, 100, "A", AccessType.RO, None) - BPhaseCurrent = Register(37109, 2, datatypes.DataType.INT32_BE, 100, "A", AccessType.RO, None) - CPhaseCurrent = Register(37111, 2, datatypes.DataType.INT32_BE, 100, "A", AccessType.RO, None) - ActivePower = Register(37113, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - ReactivePower = Register(37115, 2, datatypes.DataType.INT32_BE, 1, "var", AccessType.RO, None) + APhaseVoltage = Register(37101, 2, datatypes.DataType.INT32_BE, 10, 'V', AccessType.RO, None) + BPhaseVoltage = Register(37103, 2, datatypes.DataType.INT32_BE, 10, 'V', AccessType.RO, None) + CPhaseVoltage = Register(37105, 2, datatypes.DataType.INT32_BE, 10, 'V', AccessType.RO, None) + APhaseCurrent = Register(37107, 2, datatypes.DataType.INT32_BE, 100, 'A', AccessType.RO, None) + BPhaseCurrent = Register(37109, 2, datatypes.DataType.INT32_BE, 100, 'A', AccessType.RO, None) + CPhaseCurrent = Register(37111, 2, datatypes.DataType.INT32_BE, 100, 'A', AccessType.RO, None) + ActivePower = Register(37113, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + ReactivePower = Register(37115, 2, datatypes.DataType.INT32_BE, 1, 'var', AccessType.RO, None) PowerFactor = Register(37117, 1, datatypes.DataType.INT16_BE, 1000, None, AccessType.RO, None) - GridFrequency = Register(37118, 1, datatypes.DataType.INT16_BE, 100, "Hz", AccessType.RO, None) - PositiveActiveElectricity = Register(37119, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - ReverseActivePower = Register(37121, 2, datatypes.DataType.INT32_BE, 100, "kWh", AccessType.RO, None) - AccumulatedReactivePower = Register(37123, 2, datatypes.DataType.INT32_BE, 100, "kvar", AccessType.RO, None) - ABLineVoltage = Register(37126, 2, datatypes.DataType.INT32_BE, 10, "V", AccessType.RO, None) - BCLineVoltage = Register(37128, 2, datatypes.DataType.INT32_BE, 10, "V", AccessType.RO, None) - CALineVoltage = Register(37130, 2, datatypes.DataType.INT32_BE, 10, "V", AccessType.RO, None) - APhaseActivePower = Register(37132, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - BPhaseActivePower = Register(37134, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) - CPhaseActivePower = Register(37136, 2, datatypes.DataType.INT32_BE, 1, "W", AccessType.RO, None) + GridFrequency = Register(37118, 1, datatypes.DataType.INT16_BE, 100, 'Hz', AccessType.RO, None) + PositiveActiveElectricity = Register(37119, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + ReverseActivePower = Register(37121, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + AccumulatedReactivePower = Register(37123, 2, datatypes.DataType.INT32_BE, 100, 'kvar', AccessType.RO, None) + ABLineVoltage = Register(37126, 2, datatypes.DataType.INT32_BE, 10, 'V', AccessType.RO, None) + BCLineVoltage = Register(37128, 2, datatypes.DataType.INT32_BE, 10, 'V', AccessType.RO, None) + CALineVoltage = Register(37130, 2, datatypes.DataType.INT32_BE, 10, 'V', AccessType.RO, None) + APhaseActivePower = Register(37132, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + BPhaseActivePower = Register(37134, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) + CPhaseActivePower = Register(37136, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) diff --git a/tests/sun2000mock.py b/tests/sun2000mock.py index b9fc276..7d8f339 100644 --- a/tests/sun2000mock.py +++ b/tests/sun2000mock.py @@ -12,13 +12,13 @@ def encode(self): MockedRegisters = { # InverterEquipmentRegister.Model - 'SUN2000' - (30000, 15): b'\x1eSUN2000\x00\x00\x00', + (30000, 15): b'\x1ESUN2000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', # InverterEquipmentRegister.ModelID - 429 - (30070, 1): b'\x02\x01\xad', + (30070, 1): b'\x02\x01\xAD', # InverterEquipmentRegister.RatedPower - 10000 - (30073, 2): b"\x04\x00\x00'\x10", + (30073, 2): b'\x04\x00\x00\x27\x10', # InverterEquipmentRegister.State1 - '0000000000000110' (32000, 1): b'\x02\x00\x06', @@ -27,10 +27,10 @@ def encode(self): (32089, 1): b'\x02\x02\x00', # MeterEquipmentRegister.ActivePower - 1000 - (37113, 2): b'\x04\x03\xe8', + (37113, 2): b'\x04\x00\x00\x03\xE8', # Range of Registers - (30000, 35): b'FSUN2000-10KTL-M1\x00\x00\x00\x00SUN2000-12HV2220100135\x00\x00\x00\x00\x00\x00\x00\x0001074311-002\x00\x00\x00\x00\x00\x00\x00\x00' + (30000, 35): b'\x46SUN2000-10KTL-M1\x00\x00\x00\x00SUN2000-12HV2220100135\x00\x00\x00\x00\x00\x00\x00\x0001074311-002\x00\x00\x00\x00\x00\x00\x00\x00' } @@ -39,11 +39,19 @@ def mock_read_holding_registers(self, address, quantity, slave): def mock_read_holding_registers_ModbusIOException(self, address, quantity, slave): - return ModbusIOException("Requested slave is not available") + return ModbusIOException('Requested slave is not available') def mock_read_holding_registers_ConnectionException(self, address, quantity, slave): - raise ConnectionException("Connection unexpectedly closed") + raise ConnectionException('Connection unexpectedly closed') + + +def mock_write_registers_ModbusIOException(self, address, value, slave, skip_encode): + return ModbusIOException('Requested slave is not available') + + +def mock_write_registers_ConnectionException(self, address, value, slave, skip_encode): + raise ConnectionException('Connection unexpectedly closed') def connect_success(self): diff --git a/tests/test_sun2000_modbus.py b/tests/test_sun2000_modbus.py index 7ebc7e2..9bedba0 100644 --- a/tests/test_sun2000_modbus.py +++ b/tests/test_sun2000_modbus.py @@ -4,8 +4,8 @@ from pymodbus.exceptions import ModbusIOException, ConnectionException from sun2000_modbus.inverter import Sun2000 -from sun2000_modbus.datatypes import decode, DataType -from sun2000_modbus.registers import InverterEquipmentRegister, MeterEquipmentRegister +from sun2000_modbus.datatypes import encode, decode, DataType +from sun2000_modbus.registers import InverterEquipmentRegister, MeterEquipmentRegister, BatteryEquipmentRegister class TestDataTypes(unittest.TestCase): @@ -14,43 +14,68 @@ def test_decode_string(self): decoded = decode(value, DataType.STRING) self.assertEqual(decoded, 'SUN2000') + def test_encode_uint16_be(self): + value = 1234 + encoded = encode(value, DataType.UINT16_BE) + self.assertEqual(encoded, b'\x04\xD2') + def test_decode_uint16_be(self): value = b'\x04\xD2' decoded = decode(value, DataType.UINT16_BE) self.assertEqual(decoded, 1234) + def test_encode_uint32_be(self): + value = 123456789 + encoded = encode(value, DataType.UINT32_BE) + self.assertEqual(encoded, b'\x07\x5B\xCD\x15') + def test_decode_uint32_be(self): value = b'\x07\x5B\xCD\x15' decoded = decode(value, DataType.UINT32_BE) self.assertEqual(decoded, 123456789) + def test_encode_int16_be(self): + value = -1234 + encoded = encode(value, DataType.INT16_BE) + self.assertEqual(encoded, b'\xFB\x2E') + def test_decode_int16_be(self): - value = b'\xfb\x2e' + value = b'\xFB\x2E' decoded = decode(value, DataType.INT16_BE) self.assertEqual(decoded, -1234) + def test_encode_int32_be(self): + value = -123456789 + encoded = encode(value, DataType.INT32_BE) + self.assertEqual(encoded, b'\xF8\xA4\x32\xEB') + def test_decode_int32_be(self): - value = b'\xf8\xa4\x32\xeb' + value = b'\xF8\xA4\x32\xEB' decoded = decode(value, DataType.INT32_BE) self.assertEqual(decoded, -123456789) def test_decode_bitfield16(self): - value = b'\x3e\x22' + value = b'\x3E\x22' decoded = decode(value, DataType.BITFIELD16) self.assertEqual(decoded, '0011111000100010') def test_decode_bitfield32(self): - value = b'\x3e\x22\xaf\x45' + value = b'\x3E\x22\xAF\x45' decoded = decode(value, DataType.BITFIELD16) self.assertEqual(decoded, '00111110001000101010111101000101') + def test_encode_multidata(self): + value = b'\x3E\x22\xAF\x45' + encoded = encode(value, DataType.MULTIDATA) + self.assertEqual(encoded, b'\x3E\x22\xAF\x45') + def test_decode_multidata(self): - value = b'\x3e\x22\xaf\x45' + value = b'\x3E\x22\xAF\x45' decoded = decode(value, DataType.MULTIDATA) - self.assertEqual(decoded, b'\x3e\x22\xaf\x45') + self.assertEqual(decoded, b'\x3E\x22\xAF\x45') def test_decode_invalid(self): - value = b'\x3e' + value = b'\x3E' self.assertRaises(ValueError, decode, value, 'invalid') @@ -88,7 +113,9 @@ def test_connect_fail(self): ) def test_read_raw_value_string_from_disconnected_unit(self): self.test_inverter.connect() - self.assertRaises(ValueError, self.test_inverter.read_raw_value, InverterEquipmentRegister.Model) + with self.assertRaises(ValueError) as cm: + self.test_inverter.read_raw_value(InverterEquipmentRegister.Model) + self.assertEqual(str(cm.exception), 'Inverter is not connected') @patch( 'pymodbus.client.ModbusTcpClient.read_holding_registers', sun2000mock.mock_read_holding_registers_ModbusIOException @@ -101,7 +128,9 @@ def test_read_raw_value_string_from_disconnected_unit(self): ) def test_read_raw_value_string_from_unavailable_unit(self): self.test_inverter.connect() - self.assertRaises(ModbusIOException, self.test_inverter.read_raw_value, InverterEquipmentRegister.Model) + with self.assertRaises(ModbusIOException) as cm: + self.test_inverter.read_raw_value(InverterEquipmentRegister.Model) + self.assertEqual(str(cm.exception), 'Modbus Error: [Input/Output] Requested slave is not available') @patch( 'pymodbus.client.ModbusTcpClient.read_holding_registers', sun2000mock.mock_read_holding_registers_ConnectionException @@ -114,7 +143,9 @@ def test_read_raw_value_string_from_unavailable_unit(self): ) def test_read_raw_value_string_connection_unexpectedly_closed(self): self.test_inverter.connect() - self.assertRaises(ConnectionException, self.test_inverter.read_raw_value, InverterEquipmentRegister.Model) + with self.assertRaises(ConnectionException) as cm: + self.test_inverter.read_raw_value(InverterEquipmentRegister.Model) + self.assertEqual(str(cm.exception), 'Modbus Error: [Connection] Connection unexpectedly closed') @patch( 'pymodbus.client.ModbusTcpClient.read_holding_registers', sun2000mock.mock_read_holding_registers @@ -184,7 +215,7 @@ def test_read_uint32be(self): def test_read_formatted_uint32be(self): self.test_inverter.connect() result = self.test_inverter.read_formatted(InverterEquipmentRegister.RatedPower) - self.assertEqual(result, "10000.0 W") + self.assertEqual(result, '10000.0 W') @patch( 'pymodbus.client.ModbusTcpClient.read_holding_registers', sun2000mock.mock_read_holding_registers @@ -293,36 +324,49 @@ def test_read_range_returns_range_of_register_values(self): ) def test_read_range_with_no_right_border(self): self.test_inverter.connect() - self.assertRaises(ValueError, self.test_inverter.read_range, 30000) + with self.assertRaises(ValueError) as cm: + self.test_inverter.read_range(30000) + self.assertEqual(str(cm.exception), 'Either parameter quantity or end_address is required and must be greater than 0') @patch( 'pymodbus.client.ModbusTcpClient.connect', sun2000mock.connect_success ) def test_read_range_with_both_quantity_and_end_address_defined(self): self.test_inverter.connect() - self.assertRaises(ValueError, self.test_inverter.read_range, 30000, quantity=35, end_address=30034) + with self.assertRaises(ValueError) as cm: + self.test_inverter.read_range(30000, quantity=35, end_address=30034) + self.assertEqual(str(cm.exception), 'Only one parameter quantity or end_address should be defined') @patch( 'pymodbus.client.ModbusTcpClient.connect', sun2000mock.connect_success ) def test_read_range_with_quantity_set_to_0(self): self.test_inverter.connect() - self.assertRaises(ValueError, self.test_inverter.read_range, 30000, quantity=0) + with self.assertRaises(ValueError) as cm: + self.test_inverter.read_range(30000, quantity=0) + self.assertEqual(str(cm.exception), 'Either parameter quantity or end_address is required and must be greater than 0') @patch( 'pymodbus.client.ModbusTcpClient.connect', sun2000mock.connect_success ) def test_read_range_with_end_address_not_being_greater_than_start_address(self): self.test_inverter.connect() - self.assertRaises(ValueError, self.test_inverter.read_range, 30000, end_address=30000) - self.assertRaises(ValueError, self.test_inverter.read_range, 30000, end_address=29999) + with self.assertRaises(ValueError) as cm: + self.test_inverter.read_range(30000, end_address=30000) + self.assertEqual(str(cm.exception), 'end_address must be greater than start_address') + + with self.assertRaises(ValueError) as cm: + self.test_inverter.read_range(30000, end_address=29999) + self.assertEqual(str(cm.exception), 'end_address must be greater than start_address') @patch( 'pymodbus.client.ModbusTcpClient.connect', sun2000mock.connect_fail ) def test_read_range_from_disconnected_unit(self): self.test_inverter.connect() - self.assertRaises(ValueError, self.test_inverter.read_range, 30000, quantity=35) + with self.assertRaises(ValueError) as cm: + self.test_inverter.read_range(30000, quantity=35) + self.assertEqual(str(cm.exception), 'Inverter is not connected') @patch( 'pymodbus.client.ModbusTcpClient.read_holding_registers', sun2000mock.mock_read_holding_registers_ConnectionException @@ -335,7 +379,9 @@ def test_read_range_from_disconnected_unit(self): ) def test_read_range_from_unavailable_unit(self): self.test_inverter.connect() - self.assertRaises(ConnectionException, self.test_inverter.read_range, 30000, quantity=35) + with self.assertRaises(ConnectionException) as cm: + self.test_inverter.read_range(30000, quantity=35) + self.assertEqual(str(cm.exception), 'Modbus Error: [Connection] Connection unexpectedly closed') @patch( 'pymodbus.client.ModbusTcpClient.read_holding_registers', sun2000mock.mock_read_holding_registers_ModbusIOException @@ -348,4 +394,142 @@ def test_read_range_from_unavailable_unit(self): ) def test_read_range_from_unavailable_unit2(self): self.test_inverter.connect() - self.assertRaises(ModbusIOException, self.test_inverter.read_range, 30000, quantity=35) + with self.assertRaises(ModbusIOException) as cm: + self.test_inverter.read_range(30000, quantity=35) + self.assertEqual(str(cm.exception), 'Modbus Error: [Input/Output] Requested slave is not available') + + @patch( + 'pymodbus.client.ModbusTcpClient.connect', sun2000mock.connect_fail + ) + def test_write_to_disconnected_unit(self): + self.test_inverter.connect() + with self.assertRaises(ValueError) as cm: + self.test_inverter.write(BatteryEquipmentRegister.BackupPowerSOC, 10) + self.assertEqual(str(cm.exception), 'Inverter is not connected') + + @patch( + 'pymodbus.client.ModbusTcpClient.write_registers', sun2000mock.mock_write_registers_ModbusIOException + ) + @patch( + 'pymodbus.client.ModbusTcpClient.connect', sun2000mock.connect_success + ) + @patch( + 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success + ) + def test_write_to_unavailable_unit(self): + self.test_inverter.connect() + with self.assertRaises(ModbusIOException) as cm: + self.test_inverter.write(BatteryEquipmentRegister.BackupPowerSOC, 10) + self.assertEqual(str(cm.exception), 'Modbus Error: [Input/Output] Requested slave is not available') + + @patch( + 'pymodbus.client.ModbusTcpClient.connect', sun2000mock.connect_success + ) + @patch( + 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success + ) + def test_write_to_read_only_register(self): + self.test_inverter.connect() + with self.assertRaises(ValueError) as cm: + self.test_inverter.write(BatteryEquipmentRegister.RunningStatus, 1) + self.assertEqual(str(cm.exception), 'Register is not writeable') + + @patch( + 'pymodbus.client.ModbusTcpClient.write_registers', sun2000mock.mock_write_registers_ConnectionException + ) + @patch( + 'pymodbus.client.ModbusTcpClient.connect', sun2000mock.connect_success + ) + @patch( + 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success + ) + def test_write_uint16be_connection_unexpectedly_closed(self): + self.test_inverter.connect() + with self.assertRaises(ConnectionException) as cm: + self.test_inverter.write(BatteryEquipmentRegister.BackupPowerSOC, 10) + self.assertEqual(str(cm.exception), 'Modbus Error: [Connection] Connection unexpectedly closed') + + @patch( + 'pymodbus.client.ModbusTcpClient.write_registers' + ) + @patch( + 'pymodbus.client.ModbusTcpClient.connect', sun2000mock.connect_success + ) + @patch( + 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success + ) + def test_write_uint16be(self, write_registers_mock): + self.test_inverter.connect() + self.test_inverter.write(BatteryEquipmentRegister.BackupPowerSOC, 10) + write_registers_mock.assert_called_once_with(47102, [b'\x00\x0A'], slave=1, skip_encode=True) + + @patch( + 'pymodbus.client.ModbusTcpClient.write_registers' + ) + @patch( + 'pymodbus.client.ModbusTcpClient.connect', sun2000mock.connect_success + ) + @patch( + 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success + ) + def test_write_uint32be(self, write_registers_mock): + self.test_inverter.connect() + self.test_inverter.write(InverterEquipmentRegister.FixedActivePowerDeratedInW, 10200) + write_registers_mock.assert_called_once_with(40126, [b'\x00\x00', b'\x27\xD8'], slave=1, skip_encode=True) + + @patch( + 'pymodbus.client.ModbusTcpClient.write_registers' + ) + @patch( + 'pymodbus.client.ModbusTcpClient.connect', sun2000mock.connect_success + ) + @patch( + 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success + ) + def test_write_int16be(self, write_registers_mock): + self.test_inverter.connect() + self.test_inverter.write(BatteryEquipmentRegister.MaximumFeedGridPowerInPercentage, -90) + write_registers_mock.assert_called_once_with(47418, [b'\xFF\xA6'], slave=1, skip_encode=True) + + @patch( + 'pymodbus.client.ModbusTcpClient.write_registers' + ) + @patch( + 'pymodbus.client.ModbusTcpClient.connect', sun2000mock.connect_success + ) + @patch( + 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success + ) + def test_write_int32be(self, write_registers_mock): + self.test_inverter.connect() + self.test_inverter.write(BatteryEquipmentRegister.MaximumChargeFromGridPower, -10200) + write_registers_mock.assert_called_once_with(47590, [b'\xFF\xFF', b'\xD8\x28'], slave=1, skip_encode=True) + + @patch( + 'pymodbus.client.ModbusTcpClient.write_registers' + ) + @patch( + 'pymodbus.client.ModbusTcpClient.connect', sun2000mock.connect_success + ) + @patch( + 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success + ) + def test_write_multidata(self, write_registers_mock): + self.test_inverter.connect() + self.test_inverter.write(InverterEquipmentRegister.CosPhiPPnCharacteristicCurve, b'\x01\x02\x03\x04') + write_registers_mock.assert_called_once_with(40133, [b'\x01\x02', b'\x03\x04'], slave=1, skip_encode=True) + + @patch( + 'pymodbus.client.ModbusTcpClient.write_registers' + ) + @patch( + 'pymodbus.client.ModbusTcpClient.connect', sun2000mock.connect_success + ) + @patch( + 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success + ) + def test_write_multidata_invalid(self, write_registers_mock): + self.test_inverter.connect() + with self.assertRaises(ValueError) as cm: + self.test_inverter.write(InverterEquipmentRegister.CosPhiPPnCharacteristicCurve, b'\x01\x02\x03') + self.assertEqual(str(cm.exception), 'Multidata value length must be a multiple of 2')