From 4a48e1a45d5e20e3a5e843cc23367cded9785502 Mon Sep 17 00:00:00 2001 From: Matt Redfearn Date: Wed, 22 Sep 2021 20:15:52 +0100 Subject: [PATCH] Add support for multiple inverters Inverters must (for now) be numbered 1,2,3 etc on RS485-1 bus, with the primary inverter being ID1. If multiple inverters are configured, inverter sensor names are prefixed by the inverter number. --- .../solaredge_modbus/__init__.py | 69 ++++++++++++------- .../solaredge_modbus/config_flow.py | 3 + custom_components/solaredge_modbus/const.py | 2 + custom_components/solaredge_modbus/sensor.py | 26 ++++--- .../solaredge_modbus/strings.json | 1 + .../solaredge_modbus/translations/en.json | 1 + .../solaredge_modbus/translations/nb.json | 1 + .../solaredge_modbus/translations/nl.json | 1 + 8 files changed, 67 insertions(+), 37 deletions(-) diff --git a/custom_components/solaredge_modbus/__init__.py b/custom_components/solaredge_modbus/__init__.py index efe53b3..bb09c66 100644 --- a/custom_components/solaredge_modbus/__init__.py +++ b/custom_components/solaredge_modbus/__init__.py @@ -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, @@ -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, @@ -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) @@ -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} @@ -134,6 +141,7 @@ def __init__( host, port, scan_interval, + number_of_inverters=1, read_meter1=False, read_meter2=False, read_meter3=False, @@ -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 @@ -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() @@ -548,8 +557,16 @@ 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): + prefix = "" if self.number_of_inverters == 1 else f"i{idx}_" + if not self.read_modbus_data_inverter(prefix, idx): + _LOGGER.warning(f"Failed to read inverter {idx} data") + 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 @@ -565,10 +582,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() @@ -585,66 +602,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) @@ -657,12 +674,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: diff --git a/custom_components/solaredge_modbus/config_flow.py b/custom_components/solaredge_modbus/config_flow.py index 9d94d64..ad341b0 100644 --- a/custom_components/solaredge_modbus/config_flow.py +++ b/custom_components/solaredge_modbus/config_flow.py @@ -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, @@ -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, } ) diff --git a/custom_components/solaredge_modbus/const.py b/custom_components/solaredge_modbus/const.py index adce588..99ea763 100644 --- a/custom_components/solaredge_modbus/const.py +++ b/custom_components/solaredge_modbus/const.py @@ -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 @@ -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" diff --git a/custom_components/solaredge_modbus/sensor.py b/custom_components/solaredge_modbus/sensor.py index 6b64363..f19c2b5 100644 --- a/custom_components/solaredge_modbus/sensor.py +++ b/custom_components/solaredge_modbus/sensor.py @@ -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 = "" if hub.number_of_inverters == 1 else f"I{idx} " + key_prefix = "" if hub.number_of_inverters == 1 else 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(): diff --git a/custom_components/solaredge_modbus/strings.json b/custom_components/solaredge_modbus/strings.json index b39f07a..846204d 100644 --- a/custom_components/solaredge_modbus/strings.json +++ b/custom_components/solaredge_modbus/strings.json @@ -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)", diff --git a/custom_components/solaredge_modbus/translations/en.json b/custom_components/solaredge_modbus/translations/en.json index 1b0a11d..e665969 100644 --- a/custom_components/solaredge_modbus/translations/en.json +++ b/custom_components/solaredge_modbus/translations/en.json @@ -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)", diff --git a/custom_components/solaredge_modbus/translations/nb.json b/custom_components/solaredge_modbus/translations/nb.json index 40b1308..596f2a9 100644 --- a/custom_components/solaredge_modbus/translations/nb.json +++ b/custom_components/solaredge_modbus/translations/nb.json @@ -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)", diff --git a/custom_components/solaredge_modbus/translations/nl.json b/custom_components/solaredge_modbus/translations/nl.json index a2d657d..953c5e6 100644 --- a/custom_components/solaredge_modbus/translations/nl.json +++ b/custom_components/solaredge_modbus/translations/nl.json @@ -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)",