Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ The current charge config, discharge config and charging range will only update

If you want to adjust the restrictions yourself, you are able to by modifying the `ALPHA_POST_REQUEST_RESTRICTION` varible in const.py to the amount of seconds allowed per call

## Local Inverter Support

To use the local inverter support, you will need to have a local inverter that is able to reach your HA instance (preferably on the same subnet).

To add a local inverter to an existing AlphaESS integration, you will need to select the "Configure" option from the AlphaESS integration in Home Assistant, and then input your inverter's IP address, you can also do this if you need to reconfigure your inverter's IP address (due to DHCP changes, etc).

To remove/reset the local inverter integration, you will need to go back to the configuration settings, and set it to 0. (this will "remove" all the sensors linked, and will need to be manually deleted)

For now, if you have more than one inverter linked to your OpenAPI Account, the local inverter settings will only work on the first inverter that is linked to your account. support for setting it to be a custom one is coming.

![](https://i.imgur.com/rHWI2gh.png)


## Issues with registering systems to the AlphaESS OpenAPI

There has been a few issues regarding registering systems to the AlphaESS OpenAPI. The following are some of the issues that have been reported and how to resolve them.
Expand Down
4 changes: 3 additions & 1 deletion custom_components/alphaess/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Alpha ESS from a config entry."""

client = alphaess.alphaess(entry.data["AppID"], entry.data["AppSecret"])
ip_address = entry.options.get("IPAddress", entry.data.get("IPAddress"))

client = alphaess.alphaess(entry.data["AppID"], entry.data["AppSecret"], ipaddress=ip_address)

ESSList = await client.getESSList()
for unit in ESSList:
Expand Down
21 changes: 18 additions & 3 deletions custom_components/alphaess/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,21 @@ async def async_setup_entry(hass, entry, async_add_entities) -> None:
description.key: description for description in EV_DISCHARGE_AND_CHARGE_BUTTONS
}


for serial, data in coordinator.data.items():
model = data.get("Model")
has_local_ip_data = 'Local IP' in data
if model not in INVERTER_SETTING_BLACKLIST:
for description in full_button_supported_states:
button_entities.append(
AlphaESSBatteryButton(coordinator, entry, serial, full_button_supported_states[description]))
AlphaESSBatteryButton(coordinator, entry, serial, full_button_supported_states[description], has_local_connection=has_local_ip_data))

ev_charger = data.get("EV Charger S/N")
if ev_charger:
for description in ev_charging_supported_states:
button_entities.append(
AlphaESSBatteryButton(
coordinator, entry, serial, ev_charging_supported_states[description], True
coordinator, entry, serial, ev_charging_supported_states[description], True, has_local_connection=has_local_ip_data
)
)

Expand All @@ -63,7 +65,7 @@ async def async_setup_entry(hass, entry, async_add_entities) -> None:

class AlphaESSBatteryButton(CoordinatorEntity, ButtonEntity):

def __init__(self, coordinator, config, serial, key_supported_states, ev_charger=False):
def __init__(self, coordinator, config, serial, key_supported_states, ev_charger=False, has_local_connection=False):
super().__init__(coordinator)
self._serial = serial
self._coordinator = coordinator
Expand Down Expand Up @@ -92,6 +94,19 @@ def __init__(self, coordinator, config, serial, key_supported_states, ev_charger
model_id=coordinator.data[invertor]["EV Charger S/N"],
name=f"Alpha ESS Charger : {coordinator.data[invertor]["EV Charger S/N"]}",
)
elif "Local IP" in coordinator.data[invertor] and coordinator.data[invertor].get('Local IP') != '0':
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, serial)},
serial_number=coordinator.data[invertor]["Device Serial Number"],
sw_version=coordinator.data[invertor]["Software Version"],
hw_version=coordinator.data[invertor]["Hardware Version"],
manufacturer="AlphaESS",
model=coordinator.data[invertor]["Model"],
model_id=self._serial,
name=f"Alpha ESS Energy Statistics : {serial}",
configuration_url=f"http://{coordinator.data[invertor]["Local IP"]}"
)
elif self._serial == serial:
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
Expand Down
49 changes: 41 additions & 8 deletions custom_components/alphaess/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,23 @@
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError

from .const import DOMAIN, add_inverter_to_list, increment_inverter_count


STEP_USER_DATA_SCHEMA = vol.Schema(
{vol.Required("AppID", description={"AppID"}): str, vol.Required("AppSecret", description={"AppSecret"}): str}
)
STEP_USER_DATA_SCHEMA = vol.Schema({
vol.Required("AppID", description="AppID"): str,
vol.Required("AppSecret", description="AppSecret"): str,
vol.Optional("IPAddress", default='0'): vol.Any(None, str)
})


async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
"""Validate the user input allows us to connect."""

client = alphaess.alphaess(data["AppID"], data["AppSecret"])
client = alphaess(data["AppID"], data["AppSecret"], ipaddress=data["IPAddress"])

try:
await client.authenticate()
Expand Down Expand Up @@ -56,7 +57,7 @@ class AlphaESSConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1

async def async_step_user(
self, user_input: dict[str, Any] | None = None
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""

Expand All @@ -73,17 +74,49 @@ async def async_step_user(
errors["base"] = "invalid_auth"

return self.async_create_entry(
title=user_input["AppID"], data=user_input
title=user_input["AppID"], data=user_input
)

return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)

@staticmethod
@callback
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> AlphaESSOptionsFlowHandler:
return AlphaESSOptionsFlowHandler(config_entry)


class InvalidAuth(HomeAssistantError):
"""Error to indicate there is invalid auth."""


class CannotConnect(HomeAssistantError):
"""Error to indicate there is a problem connecting."""


class AlphaESSOptionsFlowHandler(config_entries.OptionsFlow):
"""AlphaESS options flow."""

def __init__(self, config_entry: config_entries.ConfigEntry):
self._config_entry = config_entry

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> config_entries.ConfigFlowResult:
if user_input is not None:
return self.async_create_entry(title="", data=user_input)

schema = {
vol.Optional(
"IPAddress",
default=self._config_entry.options.get(
"IPAddress",
self._config_entry.data.get("IPAddress", ""),
),
): str
}

return self.async_show_form(step_id="init", data_schema=vol.Schema(schema))
57 changes: 49 additions & 8 deletions custom_components/alphaess/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,55 @@
-------------------------------------------------------------------
"""

ev_charger_states = {
1: "Available (not plugged in)",
2: "Preparing (plugged in and not activated)",
3: "Charging (charging with power output)",
4: "SuspendedEVSE (already started but no available power)",
5: "SuspendedEV (waiting for the car to respond)",
6: "Finishing (actively stopping charging)",
9: "Faulted (pile failure)"
EV_CHARGER_STATE_KEYS = {
1: "available",
2: "preparing",
3: "charging",
4: "suspended_evse",
5: "suspended_ev",
6: "finishing",
9: "faulted"
}

TCP_STATUS_KEYS = {
0: "connected_ok",
-1: "initialization",
-2: "not_connected_router",
-3: "dns_lookup_error",
-4: "connect_fail",
-5: "signal_too_weak",
-6: "failed_register_base_station",
-7: "sim_card_not_inserted",
-8: "not_bound_plant",
-9: "key_error",
-10: "sn_error",
-11: "communication_timeout",
-12: "communication_abort_server",
-13: "server_address_error"
}

WIFI_STATUS_KEYS = {
0: "connection_idle",
1: "connecting",
2: "password_error",
3: "ap_not_found",
4: "connect_fail",
5: "connected_ok"
# All other values default to unknown_error
}

ETHERNET_STATUS_KEYS = {
0: "link_up",
# All other values default to link_down
}

FOUR_G_STATUS_KEYS = {
0: "ok",
-1: "initialization",
-2: "connected_fail",
-3: "connected_lost",
-4: "connected_fail"
# All other values default to unknown_error
}


Expand Down
Loading