Skip to content

Commit

Permalink
Add support for multiple inverters
Browse files Browse the repository at this point in the history
Inverters must (for now) be numbered 1,2,3 etc on RS485-1 bus, with the
primary inverter being ID1.

All inverter sensor names are now prefixed by the inverter number.
  • Loading branch information
mpredfearn authored and BlaizeInc committed Oct 19, 2021
1 parent 2fad552 commit d66ba29
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 37 deletions.
67 changes: 41 additions & 26 deletions custom_components/solaredge_modbus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@
DOMAIN,
DEFAULT_NAME,
DEFAULT_SCAN_INTERVAL,
CONF_NUMBER_INVERTERS,
CONF_READ_METER1,
CONF_READ_METER2,
CONF_READ_METER3,
CONF_READ_BATTERY1,
CONF_READ_BATTERY2,
DEFAULT_NUMBER_INVERTERS,
DEFAULT_READ_METER1,
DEFAULT_READ_METER2,
DEFAULT_READ_METER3,
Expand All @@ -49,6 +51,9 @@
vol.Optional(CONF_READ_METER3, default=DEFAULT_READ_METER3): cv.boolean,
vol.Optional(CONF_READ_BATTERY1, default=DEFAULT_READ_BATTERY1): cv.boolean,
vol.Optional(CONF_READ_BATTERY2, default=DEFAULT_READ_BATTERY2): cv.boolean,
vol.Optional(
CONF_NUMBER_INVERTERS, default=DEFAULT_NUMBER_INVERTERS
): cv.positive_int,
vol.Optional(
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
): cv.positive_int,
Expand All @@ -74,6 +79,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
name = entry.data[CONF_NAME]
port = entry.data[CONF_PORT]
scan_interval = entry.data[CONF_SCAN_INTERVAL]
number_of_inverters = entry.data.get(CONF_NUMBER_INVERTERS, 1)
read_meter1 = entry.data.get(CONF_READ_METER1, False)
read_meter2 = entry.data.get(CONF_READ_METER2, False)
read_meter3 = entry.data.get(CONF_READ_METER3, False)
Expand All @@ -83,7 +89,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
_LOGGER.debug("Setup %s.%s", DOMAIN, name)

hub = SolaredgeModbusHub(
hass, name, host, port, scan_interval, read_meter1, read_meter2, read_meter3, read_battery1, read_battery2
hass, name, host, port, scan_interval, number_of_inverters,
read_meter1, read_meter2, read_meter3, read_battery1, read_battery2
)
"""Register the hub."""
hass.data[DOMAIN][name] = {"hub": hub}
Expand Down Expand Up @@ -134,6 +141,7 @@ def __init__(
host,
port,
scan_interval,
number_of_inverters=1,
read_meter1=False,
read_meter2=False,
read_meter3=False,
Expand All @@ -145,6 +153,7 @@ def __init__(
self._client = ModbusTcpClient(host=host, port=port)
self._lock = threading.Lock()
self._name = name
self.number_of_inverters = number_of_inverters
self.read_meter1 = read_meter1
self.read_meter2 = read_meter2
self.read_meter3 = read_meter3
Expand Down Expand Up @@ -225,7 +234,7 @@ def calculate_value(self, value, sf):

def read_modbus_data(self):
return (
self.read_modbus_data_inverter()
self.read_modbus_data_inverters()
and self.read_modbus_data_meter1()
and self.read_modbus_data_meter2()
and self.read_modbus_data_meter3()
Expand Down Expand Up @@ -548,8 +557,14 @@ def read_modbus_data_meter(self, meter_prefix, start_address):
else:
return False

def read_modbus_data_inverter(self):
inverter_data = self.read_holding_registers(unit=1, address=40071, count=38)
def read_modbus_data_inverters(self):
for idx in range(1, 1 + self.number_of_inverters):
if not self.read_modbus_data_inverter(f"i{idx}_", idx):
return False
return True

def read_modbus_data_inverter(self, inverter_prefix, unit):
inverter_data = self.read_holding_registers(unit=unit, address=40071, count=38)
if not inverter_data.isError():
decoder = BinaryPayloadDecoder.fromRegisters(
inverter_data.registers, byteorder=Endian.Big
Expand All @@ -565,10 +580,10 @@ def read_modbus_data_inverter(self):
accurrentb = self.calculate_value(accurrentb, accurrentsf)
accurrentc = self.calculate_value(accurrentc, accurrentsf)

self.data["accurrent"] = round(accurrent, abs(accurrentsf))
self.data["accurrenta"] = round(accurrenta, abs(accurrentsf))
self.data["accurrentb"] = round(accurrentb, abs(accurrentsf))
self.data["accurrentc"] = round(accurrentc, abs(accurrentsf))
self.data[inverter_prefix + "accurrent"] = round(accurrent, abs(accurrentsf))
self.data[inverter_prefix + "accurrenta"] = round(accurrenta, abs(accurrentsf))
self.data[inverter_prefix + "accurrentb"] = round(accurrentb, abs(accurrentsf))
self.data[inverter_prefix + "accurrentc"] = round(accurrentc, abs(accurrentsf))

acvoltageab = decoder.decode_16bit_uint()
acvoltagebc = decoder.decode_16bit_uint()
Expand All @@ -585,66 +600,66 @@ def read_modbus_data_inverter(self):
acvoltagebn = self.calculate_value(acvoltagebn, acvoltagesf)
acvoltagecn = self.calculate_value(acvoltagecn, acvoltagesf)

self.data["acvoltageab"] = round(acvoltageab, abs(acvoltagesf))
self.data["acvoltagebc"] = round(acvoltagebc, abs(acvoltagesf))
self.data["acvoltageca"] = round(acvoltageca, abs(acvoltagesf))
self.data["acvoltagean"] = round(acvoltagean, abs(acvoltagesf))
self.data["acvoltagebn"] = round(acvoltagebn, abs(acvoltagesf))
self.data["acvoltagecn"] = round(acvoltagecn, abs(acvoltagesf))
self.data[inverter_prefix + "acvoltageab"] = round(acvoltageab, abs(acvoltagesf))
self.data[inverter_prefix + "acvoltagebc"] = round(acvoltagebc, abs(acvoltagesf))
self.data[inverter_prefix + "acvoltageca"] = round(acvoltageca, abs(acvoltagesf))
self.data[inverter_prefix + "acvoltagean"] = round(acvoltagean, abs(acvoltagesf))
self.data[inverter_prefix + "acvoltagebn"] = round(acvoltagebn, abs(acvoltagesf))
self.data[inverter_prefix + "acvoltagecn"] = round(acvoltagecn, abs(acvoltagesf))

acpower = decoder.decode_16bit_int()
acpowersf = decoder.decode_16bit_int()
acpower = self.calculate_value(acpower, acpowersf)

self.data["acpower"] = round(acpower, abs(acpowersf))
self.data[inverter_prefix + "acpower"] = round(acpower, abs(acpowersf))

acfreq = decoder.decode_16bit_uint()
acfreqsf = decoder.decode_16bit_int()
acfreq = self.calculate_value(acfreq, acfreqsf)

self.data["acfreq"] = round(acfreq, abs(acfreqsf))
self.data[inverter_prefix + "acfreq"] = round(acfreq, abs(acfreqsf))

acva = decoder.decode_16bit_int()
acvasf = decoder.decode_16bit_int()
acva = self.calculate_value(acva, acvasf)

self.data["acva"] = round(acva, abs(acvasf))
self.data[inverter_prefix + "acva"] = round(acva, abs(acvasf))

acvar = decoder.decode_16bit_int()
acvarsf = decoder.decode_16bit_int()
acvar = self.calculate_value(acvar, acvarsf)

self.data["acvar"] = round(acvar, abs(acvarsf))
self.data[inverter_prefix + "acvar"] = round(acvar, abs(acvarsf))

acpf = decoder.decode_16bit_int()
acpfsf = decoder.decode_16bit_int()
acpf = self.calculate_value(acpf, acpfsf)

self.data["acpf"] = round(acpf, abs(acpfsf))
self.data[inverter_prefix + "acpf"] = round(acpf, abs(acpfsf))

acenergy = decoder.decode_32bit_uint()
acenergysf = decoder.decode_16bit_uint()
acenergy = validate(self.calculate_value(acenergy, acenergysf), ">", 0)

self.data["acenergy"] = round(acenergy * 0.001, 3)
self.data[inverter_prefix + "acenergy"] = round(acenergy * 0.001, 3)

dccurrent = decoder.decode_16bit_uint()
dccurrentsf = decoder.decode_16bit_int()
dccurrent = self.calculate_value(dccurrent, dccurrentsf)

self.data["dccurrent"] = round(dccurrent, abs(dccurrentsf))
self.data[inverter_prefix + "dccurrent"] = round(dccurrent, abs(dccurrentsf))

dcvoltage = decoder.decode_16bit_uint()
dcvoltagesf = decoder.decode_16bit_int()
dcvoltage = self.calculate_value(dcvoltage, dcvoltagesf)

self.data["dcvoltage"] = round(dcvoltage, abs(dcvoltagesf))
self.data[inverter_prefix + "dcvoltage"] = round(dcvoltage, abs(dcvoltagesf))

dcpower = decoder.decode_16bit_int()
dcpowersf = decoder.decode_16bit_int()
dcpower = self.calculate_value(dcpower, dcpowersf)

self.data["dcpower"] = round(dcpower, abs(dcpowersf))
self.data[inverter_prefix + "dcpower"] = round(dcpower, abs(dcpowersf))

# skip register
decoder.skip_bytes(2)
Expand All @@ -657,12 +672,12 @@ def read_modbus_data_inverter(self):
tempsf = decoder.decode_16bit_int()
tempsink = self.calculate_value(tempsink, tempsf)

self.data["tempsink"] = round(tempsink, abs(tempsf))
self.data[inverter_prefix + "tempsink"] = round(tempsink, abs(tempsf))

status = decoder.decode_16bit_int()
self.data["status"] = status
self.data[inverter_prefix + "status"] = status
statusvendor = decoder.decode_16bit_int()
self.data["statusvendor"] = statusvendor
self.data[inverter_prefix + "statusvendor"] = statusvendor

return True
else:
Expand Down
3 changes: 3 additions & 0 deletions custom_components/solaredge_modbus/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
DEFAULT_NAME,
DEFAULT_SCAN_INTERVAL,
DEFAULT_PORT,
CONF_NUMBER_INVERTERS,
CONF_READ_METER1,
CONF_READ_METER2,
CONF_READ_METER3,
CONF_READ_BATTERY1,
CONF_READ_BATTERY2,
DEFAULT_NUMBER_INVERTERS,
DEFAULT_READ_METER1,
DEFAULT_READ_METER2,
DEFAULT_READ_METER3,
Expand All @@ -33,6 +35,7 @@
vol.Optional(CONF_READ_METER3, default=DEFAULT_READ_METER3): bool,
vol.Optional(CONF_READ_BATTERY1, default=DEFAULT_READ_BATTERY1): bool,
vol.Optional(CONF_READ_BATTERY2, default=DEFAULT_READ_BATTERY2): bool,
vol.Optional(CONF_NUMBER_INVERTERS, default=DEFAULT_NUMBER_INVERTERS): int,
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): int,
}
)
Expand Down
2 changes: 2 additions & 0 deletions custom_components/solaredge_modbus/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
DEFAULT_NAME = "solaredge"
DEFAULT_SCAN_INTERVAL = 30
DEFAULT_PORT = 1502
DEFAULT_NUMBER_INVERTERS = 1
DEFAULT_READ_METER1 = False
DEFAULT_READ_METER2 = False
DEFAULT_READ_METER3 = False
Expand All @@ -10,6 +11,7 @@
CONF_SOLAREDGE_HUB = "solaredge_hub"
ATTR_STATUS_DESCRIPTION = "status_description"
ATTR_MANUFACTURER = "Solaredge"
CONF_NUMBER_INVERTERS = "number_of_inverters"
CONF_READ_METER1 = "read_meter_1"
CONF_READ_METER2 = "read_meter_2"
CONF_READ_METER3 = "read_meter_3"
Expand Down
26 changes: 15 additions & 11 deletions custom_components/solaredge_modbus/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,21 @@ async def async_setup_entry(hass, entry, async_add_entities):
}

entities = []
for sensor_info in SENSOR_TYPES.values():
sensor = SolarEdgeSensor(
hub_name,
hub,
device_info,
sensor_info[0],
sensor_info[1],
sensor_info[2],
sensor_info[3],
)
entities.append(sensor)

for idx in range(1, 1 + hub.number_of_inverters):
name_prefix = f"I{idx} "
key_prefix = f"i{idx}_"
for sensor_info in SENSOR_TYPES.values():
sensor = SolarEdgeSensor(
hub_name,
hub,
device_info,
name_prefix + sensor_info[0],
key_prefix + sensor_info[1],
sensor_info[2],
sensor_info[3],
)
entities.append(sensor)

if hub.read_meter1 == True:
for meter_sensor_info in METER1_SENSOR_TYPES.values():
Expand Down
1 change: 1 addition & 0 deletions custom_components/solaredge_modbus/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"host": "The ip-address of your Solaredge device",
"name": "The prefix to be used for your SolarEdge sensors",
"port": "The TCP port on which to connect to the SolarEdge",
"number_of_inverters": "The number of inverters connected",
"read_meter_1": "Read meter 1 data (only for meter models)",
"read_meter_2": "Read meter 2 data (only for meter models)",
"read_meter_3": "Read meter 3 data (only for meter models)",
Expand Down
1 change: 1 addition & 0 deletions custom_components/solaredge_modbus/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"host": "The ip-address of your Solaredge inverter",
"name": "The prefix to be used for your SolarEdge sensors",
"port": "The TCP port on which to connect to the SolarEdge inverter",
"number_of_inverters": "The number of inverters connected",
"read_meter_1": "Read meter 1 data (only for meter models)",
"read_meter_2": "Read meter 2 data (only for meter models)",
"read_meter_3": "Read meter 3 data (only for meter models)",
Expand Down
1 change: 1 addition & 0 deletions custom_components/solaredge_modbus/translations/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"host": "IP-adressen til din Solaredge-omformer",
"name": "Prefikset som skal brukes til SolarEdge-sensorene dine",
"port": "TCP-porten som skal kobles til SolarEdge-omformeren",
"number_of_inverters": "Antall omformere koblet sammen",
"read_meter_1": "Les måler 1-data (bare for målermodeller)",
"read_meter_2": "Les måler 2-data (bare for målermodeller)",
"read_meter_3": "Les måler 3-data (bare for målermodeller)",
Expand Down
1 change: 1 addition & 0 deletions custom_components/solaredge_modbus/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"host": "Het ip-adres van uw Solaredge-omvormer",
"name": "Het voorvoegsel dat moet worden gebruikt voor uw SolarEdge-sensoren",
"port": "De TCP-poort waarop verbinding moet worden gemaakt met de SolarEdge-omvormer",
"number_of_inverters": "Aantal aangesloten omvormers",
"read_meter_1": "Lees meter 1 data (enkel voor voor meter modellen)",
"read_meter_2": "Lees meter 2 data (enkel voor voor meter modellen)",
"read_meter_3": "Lees meter 3 data (enkel voor voor meter modellen)",
Expand Down

0 comments on commit d66ba29

Please sign in to comment.