Skip to content

Commit

Permalink
Read/Write Active Power Limit
Browse files Browse the repository at this point in the history
  • Loading branch information
Johan-EU committed May 20, 2023
1 parent 78a0f5a commit 7def610
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 18 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,22 @@ Appendix B of the Solaredge [power control document][2] gives the necessary step

Note that if you allow the battery to be charged from the grid, via the "Storage AC Charge Policy" selector, the self-consumption metric will disappear from the Solaredge monitoring - according to Solaredge technical support, this is because "they can't tell where the energy came from".

# Control of inverter power output
The active power limit of the inverter can be set from Home Assistant. This enables limiting or completely shutting down power output. For example in case of a dynamic energy contract in periods with a negative energy price.

The active power limit is set as the percentage of the inverter’s maximum power via `number.solaredge_active_power_limit`. For example: when you have a SE5000 inverter that has a maximum output of 5000W, setting the value of `number.solaredge_active_power_limit` to 20 will limit the inverter to 1000W which is 20% of 5000W. See [Power Control Protocol for Solaredge Inverters Technical Note][2] for detailed information.

## Enabling Power Control in Home Assistant
Power control is disabled by default. It can be enabled in the configuration of this integration by setting `power_control` to true. When power control is enabled `number.solaredge_active_power_limit` is available in Home Assistant for reading and writing. The actual value of the active power limit is read together with the other values of the inverter.

## Enabling Power Control on SolarEdge Inverter
With default settings the inverter will not allow power control. It can be enabled in the same way that Modbus TCP is enabled on the SolarEdge Inverter, by connecting to the inverter with a web browser.

These to settings must be applied in the Power Control menu:

1. Set Advanced Power Control to Enable
2. Set Reactive Power mode to RRCR


[1]: https://www.solaredge.com/sites/default/files/sunspec-implementation-technical-note.pdf
[2]: https://www.photovoltaikforum.com/core/attachment/88445-power-control-open-protocol-for-solaredge-inverters-pdf/
57 changes: 47 additions & 10 deletions custom_components/solaredge_modbus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
DEFAULT_SCAN_INTERVAL,
DEFAULT_MODBUS_ADDRESS,
CONF_MODBUS_ADDRESS,
CONF_POWER_CONTROL,
CONF_READ_METER1,
CONF_READ_METER2,
CONF_READ_METER3,
CONF_READ_BATTERY1,
CONF_READ_BATTERY2,
DEFAULT_POWER_CONTROL,
DEFAULT_READ_METER1,
DEFAULT_READ_METER2,
DEFAULT_READ_METER3,
Expand All @@ -51,6 +53,7 @@
vol.Optional(
CONF_MODBUS_ADDRESS, default=DEFAULT_MODBUS_ADDRESS
): cv.positive_int,
vol.Optional(CONF_POWER_CONTROL, default=DEFAULT_POWER_CONTROL): cv.boolean,
vol.Optional(CONF_READ_METER1, default=DEFAULT_READ_METER1): cv.boolean,
vol.Optional(CONF_READ_METER2, default=DEFAULT_READ_METER2): cv.boolean,
vol.Optional(CONF_READ_METER3, default=DEFAULT_READ_METER3): cv.boolean,
Expand Down Expand Up @@ -82,11 +85,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
port = entry.data[CONF_PORT]
address = entry.data.get(CONF_MODBUS_ADDRESS, 1)
scan_interval = entry.data[CONF_SCAN_INTERVAL]
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)
read_battery1 = entry.data.get(CONF_READ_BATTERY1, False)
read_battery2 = entry.data.get(CONF_READ_BATTERY2, False)
power_control = entry.data.get(CONF_POWER_CONTROL, DEFAULT_POWER_CONTROL),
read_meter1 = entry.data.get(CONF_READ_METER1, DEFAULT_READ_METER1)
read_meter2 = entry.data.get(CONF_READ_METER2, DEFAULT_READ_METER2)
read_meter3 = entry.data.get(CONF_READ_METER3, DEFAULT_READ_METER3)
read_battery1 = entry.data.get(CONF_READ_BATTERY1, DEFAULT_READ_BATTERY1)
read_battery2 = entry.data.get(CONF_READ_BATTERY2, DEFAULT_READ_BATTERY2)

_LOGGER.debug("Setup %s.%s", DOMAIN, name)

Expand All @@ -97,6 +101,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
port,
address,
scan_interval,
power_control,
read_meter1,
read_meter2,
read_meter3,
Expand Down Expand Up @@ -155,18 +160,20 @@ def __init__(
port,
address,
scan_interval,
read_meter1=False,
read_meter2=False,
read_meter3=False,
read_battery1=False,
read_battery2=False,
power_control=DEFAULT_POWER_CONTROL,
read_meter1=DEFAULT_READ_METER1,
read_meter2=DEFAULT_READ_METER2,
read_meter3=DEFAULT_READ_METER3,
read_battery1=DEFAULT_READ_BATTERY1,
read_battery2=DEFAULT_READ_BATTERY2,
):
"""Initialize the Modbus hub."""
self._hass = hass
self._client = ModbusTcpClient(host=host, port=port)
self._lock = threading.Lock()
self._name = name
self._address = address
self.power_control = power_control
self.read_meter1 = read_meter1
self.read_meter2 = read_meter2
self.read_meter3 = read_meter3
Expand Down Expand Up @@ -230,6 +237,11 @@ def connect(self):
with self._lock:
self._client.connect()

@property
def power_control_enabled(self):
"""Return true if power control has been enabled"""
return self.power_control

@property
def has_meter(self):
"""Return true if a meter is available"""
Expand Down Expand Up @@ -260,6 +272,7 @@ def calculate_value(self, value, sf):
def read_modbus_data(self):
return (
self.read_modbus_data_inverter()
and self.read_modbus_power_limit()
and self.read_modbus_data_meter1()
and self.read_modbus_data_meter2()
and self.read_modbus_data_meter3()
Expand Down Expand Up @@ -665,6 +678,30 @@ def read_modbus_data_inverter(self):

return True

def read_modbus_power_limit(self):
"""
Read the active power limit value (%)
"""

if not self.power_control_enabled:
return True

inverter_data = self.read_holding_registers(
unit=self._address, address=0xF001, count=1
)
if inverter_data.isError():
_LOGGER.debug("Could not read Active Power Limit")
# Don't stop reading other data, could just be advanced power management not enabled
return True

decoder = BinaryPayloadDecoder.fromRegisters(
inverter_data.registers, byteorder=Endian.Big, wordorder=Endian.Little
)
# 0xF001 - 1 - Active Power Limit
self.data["nominal_active_power_limit"] = decoder.decode_16bit_uint()

return True

def read_modbus_data_storage(self):
if self.has_battery:
count = 0x12 # Read storedge block as well
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 @@ -12,11 +12,13 @@
DEFAULT_PORT,
DEFAULT_MODBUS_ADDRESS,
CONF_MODBUS_ADDRESS,
CONF_POWER_CONTROL,
CONF_READ_METER1,
CONF_READ_METER2,
CONF_READ_METER3,
CONF_READ_BATTERY1,
CONF_READ_BATTERY2,
DEFAULT_POWER_CONTROL,
DEFAULT_READ_METER1,
DEFAULT_READ_METER2,
DEFAULT_READ_METER3,
Expand All @@ -31,6 +33,7 @@
vol.Required(CONF_HOST): str,
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
vol.Optional(CONF_MODBUS_ADDRESS, default=DEFAULT_MODBUS_ADDRESS): int,
vol.Optional(CONF_POWER_CONTROL, default=DEFAULT_POWER_CONTROL): bool,
vol.Optional(CONF_READ_METER1, default=DEFAULT_READ_METER1): bool,
vol.Optional(CONF_READ_METER2, default=DEFAULT_READ_METER2): bool,
vol.Optional(CONF_READ_METER3, default=DEFAULT_READ_METER3): bool,
Expand Down
6 changes: 5 additions & 1 deletion custom_components/solaredge_modbus/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
DEFAULT_SCAN_INTERVAL = 30
DEFAULT_PORT = 1502
DEFAULT_MODBUS_ADDRESS = 1
DEFAULT_POWER_CONTROL = False
DEFAULT_READ_METER1 = False
DEFAULT_READ_METER2 = False
DEFAULT_READ_METER3 = False
Expand All @@ -12,6 +13,7 @@
ATTR_STATUS_DESCRIPTION = "status_description"
ATTR_MANUFACTURER = "Solaredge"
CONF_MODBUS_ADDRESS = "modbus_address"
CONF_POWER_CONTROL = "power_control"
CONF_READ_METER1 = "read_meter_1"
CONF_READ_METER2 = "read_meter_2"
CONF_READ_METER3 = "read_meter_3"
Expand Down Expand Up @@ -331,6 +333,8 @@
["Export control site limit", "export_control_site_limit", 0xE002, "f", {"min": 0, "max": 10000, "unit": "W"}],
]

ACTIVE_POWER_LIMIT_TYPE = ["Active Power Limit", "nominal_active_power_limit", 0xF001, "u16", {"min": 0, "max": 100, "unit": "%"}]

STORAGE_SELECT_TYPES = [
["Storage Control Mode", "storage_contol_mode", 0xE004, STOREDGE_CONTROL_MODE],
["Storage AC Charge Policy", "storage_ac_charge_policy", 0xE005, STOREDGE_AC_CHARGE_POLICY],
Expand All @@ -342,7 +346,7 @@
STORAGE_NUMBER_TYPES = [
["Storage AC Charge Limit", "storage_ac_charge_limit", 0xE006, "f", {"min": 0, "max": 100000000000}],
["Storage Backup reserved", "storage_backup_reserved", 0xE008, "f", {"min": 0, "max": 100, "unit": "%"}],
["Storage Remote Command Timeout", "storage_remote_command_timeout", 0xE00B, "i", {"min": 0, "max": 86400, "unit": "s"}],
["Storage Remote Command Timeout", "storage_remote_command_timeout", 0xE00B, "u32", {"min": 0, "max": 86400, "unit": "s"}],
["Storage Remote Charge Limit", "storage_remote_charge_limit", 0xE00E, "f", {"min": 0, "max": 20000, "unit": "W"}],
["Storage Remote Discharge Limit", "storage_remote_discharge_limit", 0xE010, "f", {"min": 0, "max": 20000, "unit": "W"}],
]
29 changes: 26 additions & 3 deletions custom_components/solaredge_modbus/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .const import (
DOMAIN,
ATTR_MANUFACTURER,
ACTIVE_POWER_LIMIT_TYPE,
EXPORT_CONTROL_NUMBER_TYPES,
STORAGE_NUMBER_TYPES,
)
Expand Down Expand Up @@ -33,6 +34,20 @@ async def async_setup_entry(hass, entry, async_add_entities) -> None:

entities = []

# If power control is enabled add power control
if hub.power_control_enabled:
number = SolarEdgeNumber(
hub_name,
hub,
device_info,
ACTIVE_POWER_LIMIT_TYPE[0],
ACTIVE_POWER_LIMIT_TYPE[1],
ACTIVE_POWER_LIMIT_TYPE[2],
ACTIVE_POWER_LIMIT_TYPE[3],
ACTIVE_POWER_LIMIT_TYPE[4]
)
entities.append(number)

# If a meter is available add export control
if hub.has_meter:
for number_info in EXPORT_CONTROL_NUMBER_TYPES:
Expand Down Expand Up @@ -127,12 +142,20 @@ async def async_set_native_value(self, value: float) -> None:
"""Change the selected value."""
builder = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little)

if self._fmt == "i":
if self._fmt == "u32":
builder.add_32bit_uint(int(value))
elif self._fmt =="u16":
builder.add_16bit_uint(int(value))
elif self._fmt == "f":
builder.add_32bit_float(float(value))

self._hub.write_registers(unit=1, address=self._register, payload=builder.to_registers())
else:
_LOGGER.error(f"Invalid encoding format {self._fmt} for {self._key}")
return

response = self._hub.write_registers(unit=1, address=self._register, payload=builder.to_registers())
if response.isError():
_LOGGER.error(f"Could not write value {value} to {self._key}")
return

self._hub.data[self._key] = value
self.async_write_ha_state()
1 change: 1 addition & 0 deletions custom_components/solaredge_modbus/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"name": "The prefix to be used for your SolarEdge sensors",
"port": "The TCP port on which to connect to the SolarEdge",
"modbus_address": "The modbus address",
"power_control": "Enable setting active power limit",
"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
3 changes: 2 additions & 1 deletion custom_components/solaredge_modbus/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"host": "Die IP-Adresse des Solaredge Wechselrichters",
"name": "Das Prefix, das für die SolarEdge Sensoren verwendet werden soll",
"port": "Der TCP Port des SolarEdge Wechselrichters (z.B. 502)",
"modbus_address": "Modbus-Adresse",
"modbus_address": "Modbus-Adresse",
"power_control": "Einstellung der Wirkleistungsbegrenzung aktivieren",
"read_meter_1": "Lese die Stände des Zähler 1 (nur für Modelle mit Zähler)",
"read_meter_2": "Lese die Stände des Zähler 2 (nur für Modelle mit Zähler)",
"read_meter_3": "Lese die Stände des Zähler 3 (nur für Modelle mit Zähler)",
Expand Down
3 changes: 2 additions & 1 deletion custom_components/solaredge_modbus/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"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",
"modbus_address": "The modbus address",
"modbus_address": "The modbus address",
"power_control": "Enable setting active power limit",
"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
3 changes: 2 additions & 1 deletion custom_components/solaredge_modbus/translations/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"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",
"modbus_address": "Modbus adresse",
"modbus_address": "Modbus adresse",
"power_control": "Aktiver innstilling av aktiv effektgrense",
"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
3 changes: 2 additions & 1 deletion custom_components/solaredge_modbus/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"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",
"modbus_address": "Modbus adres",
"modbus_address": "Modbus adres",
"power_control": "Activeer het instellen van de limiet voor het actieve vermogen",
"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 7def610

Please sign in to comment.