Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New parameter jd #8

Merged
merged 12 commits into from
Aug 25, 2024
43 changes: 15 additions & 28 deletions custom_components/powerocean/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,12 @@
from __future__ import annotations

from homeassistant.config_entries import ConfigEntry

from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC

from homeassistant.core import HomeAssistant

from homeassistant.helpers import device_registry as dr

from .const import DOMAIN, PLATFORMS

from .ecoflow import ecoflow_api
from .const import DOMAIN, PLATFORMS, _LOGGER, DOMAIN, ISSUE_URL_ERROR_MESSAGE, STARTUP_MESSAGE
from .ecoflow import Ecoflow

from .const import _LOGGER, DOMAIN, ISSUE_URL_ERROR_MESSAGE, STARTUP_MESSAGE

from homeassistant.helpers.translation import async_get_translations
from homeassistant.helpers import entity_registry

_LOGGER.info(STARTUP_MESSAGE)

Expand All @@ -31,22 +22,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data[DOMAIN]["device_specific_sensors"] = {}

# Store an instance of the API instance in hass.data[domain]
user_input = entry.data[
"user_input"
] # This user_input object was stored after the device was setup and has the user/pass/serial info
device_info = entry.data.get(
"device_info"
) # This device_info object was stored after the device was setup and has the name and serial needed etc.
options = entry.data[
"options"
] # These are the options during setup, including custom device name
ecoflow = ecoflow_api(
user_input["serialnumber"], user_input["username"], user_input["password"]
)
user_input = entry.data["user_input"] # This user_input object was stored after the device
# was setup and has the user/pass/serial info
device_info = entry.data.get("device_info") # This device_info object was stored after the device
# was setup and has the name and serial needed etc.
options = entry.data["options"] # These are the options during setup, including custom device name
ecoflow = Ecoflow(user_input["serialnumber"], user_input["username"], user_input["password"])

if device_info:
ecoflow.device = device_info # Store the device information
ecoflow.options = options # Store the options
ecoflow.device = device_info # Store the device information
ecoflow.options = options # Store the options
hass.data[DOMAIN][entry.entry_id] = ecoflow

# Forward to sensor platform
Expand All @@ -66,9 +51,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
identifiers={(DOMAIN, device_info["serial"])},
manufacturer=device_info.get("vendor", "ECOFLOW"),
serial_number=device_info.get("serial"),
name=options.get(
"custom_device_name"
), # Custom device name from user step 2 (options)
name=options.get("custom_device_name"), # Custom device name from user step 2 (options)
model=device_info.get("product"),
sw_version=device_info.get("version"),
configuration_url="https://api-e.ecoflow.com",
Expand Down Expand Up @@ -97,3 +80,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)

return unload_ok


async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
await hass.config_entries.async_reload(entry.entry_id)
86 changes: 34 additions & 52 deletions custom_components/powerocean/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
"""config_flow.py: Config flow for PowerOcean integration."""
from __future__ import annotations

import logging
import re
from typing import Any

import voluptuous as vol

from homeassistant import config_entries
from homeassistant import exceptions

from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import IntegrationError

from .const import _LOGGER, DOMAIN, ISSUE_URL_ERROR_MESSAGE

from .ecoflow import ecoflow_api, AuthenticationFailed
from .ecoflow import Ecoflow, AuthenticationFailed


# This is the first step's schema when setting up the integration, or its devices
# The second schema is defined inside the ConfigFlow class as it has dynamice default values set via API call
# The second schema is defined inside the ConfigFlow class as it has dynamic default values set via API call
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required("serialnumber", default=""): str,
Expand All @@ -31,31 +26,28 @@
)


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

ecoflow = ecoflow_api(data["serialnumber"], data["username"], data["password"])
ecoflow = Ecoflow(data["serialnumber"], data["username"], data["password"])

try:
# Call the API with the detect_device method
device = await hass.async_add_executor_job(ecoflow.detect_device)

# Additionally, check for authentication by calling fetch_data_km2
auth_check = await hass.async_add_executor_job(ecoflow.fetch_data)
# Check for authentication
# auth_check = await hass.async_add_executor_job(ecoflow.fetch_data) # TODO what else is needed from fetch_data?
auth_check = await hass.async_add_executor_job(ecoflow.authorize)
if not auth_check:
# If authentication check returns False, raise an authentication failure exception
raise AuthenticationFailed("Invalid authentication")
raise AuthenticationFailed("Invalid authentication!")

# Get device info
device = await hass.async_add_executor_job(ecoflow.get_device)

# Return the device object with the device information
return device

# Exception if device cannot be found
except IntegrationError as e:
_LOGGER.error(
f"Failed to connect to PowerOcean device: {e}" + ISSUE_URL_ERROR_MESSAGE
)
_LOGGER.error(f"Failed to connect to PowerOcean device: {e}" + ISSUE_URL_ERROR_MESSAGE)
raise CannotConnect from e

# Exception if authentication fails
Expand All @@ -67,30 +59,27 @@ async def validate_input_for_device(
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for PowerOcean."""

VERSION = 1
VERSION = 1.3

# Make sure user input data is passed from one step to the next using user_input_from_step_user
def __init__(self):
self.user_input_from_step_user = None

# This is step 1 for the host/port/user/pass function.
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
async def async_step_user(self, user_input: dict[str, Any] | None = None) -> FlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
try:
# Valide the user input whilst setting up integration or adding new devices.
# validate_input_for_devices will try to detect the device and get more info from it, and authenticate and deal with exceptions
# validate_input_for_devices will try to detect the device and get more info from it,
# and authenticate and deal with exceptions
device = await validate_input_for_device(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
return self.async_show_form(step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors)
except Exception:
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
Expand All @@ -114,17 +103,11 @@ async def async_step_user(
return await self.async_step_device_options(user_input=None)

# Show the form for step 1 with the user/host/pass as defined in STEP_USER_DATA_SCHEMA
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
return self.async_show_form(step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors)

# This is step 2 for the options such as custom name, group and disable sensors
async def async_step_device_options(
self,
user_input: dict[str, Any] | None = None,
) -> FlowResult:
async def async_step_device_options(self, user_input: dict[str, Any] | None = None,) -> FlowResult:
"""Handle the device options step."""

errors: dict[str, str] = {}

if user_input is not None:
Expand All @@ -134,21 +117,19 @@ async def async_step_device_options(
user_input["custom_device_name"], self.device_info["name"]
)

# Since we have already set the unique ID and updated host if necessary create the entry with the additional options.
# Since we have already set the unique ID and updated host if necessary create
# the entry with the additional options.
# The title of the integration is the custom friendly device name given by the user in step 2
title = user_input["custom_device_name"]
return self.async_create_entry(
title=title,
data={
"user_input": self.user_input_from_step_user, # from previous step
"device_info": self.device_info, # from device detection
"options": user_input, # new options from this step
},
return self.async_create_entry(title=title,
data={
"user_input": self.user_input_from_step_user, # from previous step
"device_info": self.device_info, # from device detection
"options": user_input, # new options from this step
},
)
except Exception as e:
_LOGGER.error(
f"Failed to handle device options: {e}" + ISSUE_URL_ERROR_MESSAGE
)
_LOGGER.error(f"Failed to handle device options: {e}" + ISSUE_URL_ERROR_MESSAGE)
errors["base"] = "option_error"

# Prepare the second form's schema as it has dynamic values based on the API call
Expand All @@ -161,11 +142,13 @@ async def async_step_device_options(
step_device_options_schema = vol.Schema(
{
vol.Required("custom_device_name", default=default_device_name): str,
vol.Required("polling_time", default=60): vol.All(
# vol.Required("polling_time", default=60): vol.All(
vol.Required("polling_time", default=5): vol.All(
vol.Coerce(int), vol.Clamp(min=60)
),
vol.Required("group_sensors", default=True): bool,
vol.Required("disable_sensors", default=False): bool,
#vol.Required("disable_sensors", default=False): bool,
vol.Required("disable_sensors", default=True): bool,
}
)

Expand Down Expand Up @@ -198,9 +181,8 @@ def sanitize_device_name(device_name: str, fall_back: str, max_length=255) -> st

# Length check
if len(name) > max_length:
name = name[:max_length].rsplit(" ", 1)[
0
] # Split at the last space to avoid cutting off in the middle of a word
# Split at the last space to avoid cutting off in the middle of a word
name = name[:max_length].rsplit(" ", 1)[0]

# Fallback name
if not name:
Expand Down
3 changes: 2 additions & 1 deletion custom_components/powerocean/const.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"""Constants for the PowerOcean integration."""

import logging
from homeassistant.const import Platform

DOMAIN = "powerocean" # Have requested to add logos via https://github.com/home-assistant/brands/pull/4904
NAME = "Ecoflow PowerOcean"
VERSION = "2024.01.01"
ISSUE_URL = "https://github.com/evercape/powerocean/issues"
ISSUE_URL = "https://github.com/niltrip/powerocean/issues"
ISSUE_URL_ERROR_MESSAGE = " Please log any issues here: " + ISSUE_URL


Expand Down
Loading