Skip to content
4 changes: 4 additions & 0 deletions deye_controller/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from .modbus.protocol import (HoldingRegisters, WritableRegisters, BatteryOnlyRegisters,
TotalPowerOnly)

from .modbus.single_phase import (HoldingRegistersSingleHybrid, HoldingRegistersSingleMicro,
HoldingRegistersSingleString)


from .sell_programmer import SellProgrammer
47 changes: 33 additions & 14 deletions deye_controller/deye_reader.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from pysolarmanv5 import PySolarmanV5, V5FrameError
from .modbus.protocol import HoldingRegisters, BatteryOnlyRegisters, TotalPowerOnly
from .modbus import (HoldingRegisters, BatteryOnlyRegisters, TotalPowerOnly, InverterType,
HoldingRegistersSingleCommon, HoldingRegistersSingleString, HoldingRegistersSingleHybrid,
HoldingRegistersSingleMicro)
from .logger_scan import solar_scan
from argparse import ArgumentParser
from .utils import group_registers, map_response
Expand All @@ -10,23 +12,40 @@ def read_inverter(address: str, logger_serial: int, batt_only=False, power_only=
as_json=False, to_file=None):
inv = PySolarmanV5(address, int(logger_serial), port=8899, mb_slave_id=1, verbose=False, socket_timeout=10,
error_correction=True)

type_detection = HoldingRegisters.DeviceType
type_detection.value = inv.read_holding_registers(type_detection.address, type_detection.len)[0]
inv_type = InverterType(type_detection.value)

print(f'Detected inverter: {inv_type.name}')

iterator = []
js = {'logger': logger_serial,
'serial': 0,
'data': []
}
if batt_only:
iterator = [HoldingRegisters.SerialNumber] + BatteryOnlyRegisters
elif power_only:
iterator = [HoldingRegisters.SerialNumber] + TotalPowerOnly
elif combo:
iterator = [HoldingRegisters.SerialNumber] + BatteryOnlyRegisters
for reg in TotalPowerOnly:
if reg not in iterator:
iterator.append(reg)

else:
iterator = HoldingRegisters.as_list()

if inv_type == InverterType.Hybrid3Phase:
if batt_only:
iterator = [HoldingRegisters.SerialNumber] + BatteryOnlyRegisters
elif power_only:
iterator = [HoldingRegisters.SerialNumber] + TotalPowerOnly
elif combo:
iterator = [HoldingRegisters.SerialNumber] + BatteryOnlyRegisters
for reg in TotalPowerOnly:
if reg not in iterator:
iterator.append(reg)

else:
iterator = HoldingRegisters.as_list()

elif inv_type == InverterType.Hybrid:
iterator = HoldingRegistersSingleHybrid.as_list()
elif inv_type == InverterType.Microinverter:
iterator = HoldingRegistersSingleMicro.as_list()
elif inv_type == InverterType.Inverter:
iterator = HoldingRegistersSingleString.as_list()

reg_groups = group_registers(iterator)
for group in reg_groups:
res = inv.read_holding_registers(group.start_address, group.len)
Expand All @@ -38,7 +57,7 @@ def read_inverter(address: str, logger_serial: int, batt_only=False, power_only=
else:
suffix = ''
if as_json:
if reg == HoldingRegisters.SerialNumber:
if reg in [HoldingRegisters.SerialNumber, HoldingRegistersSingleCommon.SerialNumber]:
js['serial'] = reg.format()
else:
js['data'].append({reg.description: {'addr': reg.address, 'value': reg.format(), 'unit': suffix}})
Expand Down
13 changes: 13 additions & 0 deletions deye_controller/modbus/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from .protocol import HoldingRegisters, BatteryOnlyRegisters, WritableRegisters, TotalPowerOnly
from .single_phase import (HoldingRegistersSingleHybrid, HoldingRegistersSingleString, HoldingRegistersSingleMicro,
HoldingRegistersSingleCommon)
from .enums import InverterType


__all__ = [
'HoldingRegisters', 'BatteryOnlyRegisters', 'WritableRegisters', 'TotalPowerOnly',
'HoldingRegistersSingleHybrid', 'HoldingRegistersSingleMicro',
'HoldingRegistersSingleString', 'HoldingRegistersSingleCommon',
'InverterType',
]

58 changes: 58 additions & 0 deletions deye_controller/modbus/enums.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import enum


class InverterType(int, enum.Enum):

Inverter = 2
Hybrid = 3
Microinverter = 4
Hybrid3Phase = 5
Unknown = 0

@classmethod
def _missing_(cls, value):
return InverterType.Unknown


class InverterState(int, enum.Enum):

StandBy = 0
Expand Down Expand Up @@ -172,3 +185,48 @@ def __format__(self, format_spec):
def __str__(self):
return self.name


class WorkMode(int, enum.Enum):
"""
Register 142 - GH #16
"""

SellingFirst = 0
ZeroExportToLoad = 1
ZeroExportToCT = 2
Unknown = -1

def __str__(self):
return self.name

@classmethod
def _missing_(cls, value):
return WorkMode.Unknown


class Relay(int, enum.Enum):
Open = 0
Closed = 1
Error = -1

@classmethod
def _missing_(cls, value):
return Relay.Error

def __format__(self, format_spec):
return self.name


class BatteryControlSingle(int, enum.Enum):

Lead = 0
Lithium = 1

Error = -1

def __format__(self, format_spec):
return self.name

@classmethod
def _missing_(cls, value):
return BatteryControlSingle.Error
58 changes: 42 additions & 16 deletions deye_controller/modbus/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,6 @@
from .enums import *


class InverterType(int, enum.Enum):

Inverter = 2
Hybrid = 3
Microinverter = 4
Hybrid3Phase = 5
Unknown = 0

@classmethod
def _missing_(cls, value):
return InverterType.Unknown


class Register(object):

""" Base register of 2 bytes """
Expand Down Expand Up @@ -111,6 +98,26 @@ def format(self):
return round(calculated / self.scale, 2)


class LongUnsignedHoleType(Register):
"""
For single phase inverters where the data is not in sequential registers
"""

def __init__(self, address, name, scale, suffix=''):
super(LongUnsignedHoleType, self).__init__(address, 3, name)
self.scale = scale
self.suffix = suffix

def format(self):
v = to_unsigned_bytes(self.value[::-2])
calculated = int.from_bytes(v, byteorder='big')
if self.scale == 1:
return calculated
else:
return round(calculated / self.scale, 2)



class DeviceType(Register):

def __init__(self):
Expand Down Expand Up @@ -230,8 +237,18 @@ def __init__(self):

def format(self):
return BatteryControlMode(self.value)




class BatteryTemp(Register):

def __init__(self, address):
super(BatteryTemp, self).__init__(address, 1, 'battery_temp')
self.suffix = '°C'

def format(self):
return round((self.value - 1000) / 10, 2)


class BMSBatteryTemp(Register):

def __init__(self):
Expand Down Expand Up @@ -345,6 +362,14 @@ def format(self):
return str(GenPortMode(self.value))


class InverterWorkMode(Register):
def __init__(self):
super().__init__(142, 1, 'work_mode')

def format(self):
return str(WorkMode(self.value))


class HoldingRegisters:

DeviceType = DeviceType()
Expand Down Expand Up @@ -393,7 +418,7 @@ class HoldingRegisters:
""" Smart Load control - need more info """
GeneratorPortSetup = GenPortUse()
""" Smart Load """

IverterWorkMode = InverterWorkMode()
GridExportLimit = IntType(143, 'grid_max_output_pwr', suffix='W')
SolarSell = BoolType(145, 'solar_sell')
SellTimeOfUse = TimeOfUseSell()
Expand Down Expand Up @@ -861,6 +886,7 @@ class WritableRegisters:
SmartLoadOnVoltage = FloatWritable(address=136, low_limit=38, high_limit=63, scale=100)
SmartLoadOnCapacity = IntWritable(address=137, low_limit=0, high_limit=100)

InverterWorkMode = IntWritable(address=142, low_limit=0, high_limit=2)
GridExportLimit = IntWritable(address=143, low_limit=0, high_limit=15000)
SolarSell = BoolWritable(address=145)

Expand Down
Loading