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

Add config flow to OpenEVSE component #55303

Closed
wants to merge 18 commits into from
Closed
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
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ homeassistant/components/ondilo_ico/* @JeromeHXP
homeassistant/components/onewire/* @garbled1 @epenet
homeassistant/components/onvif/* @hunterjm
homeassistant/components/openerz/* @misialq
homeassistant/components/openevse/* @firstof9
homeassistant/components/opengarage/* @danielhiversen
homeassistant/components/openhome/* @bazwilliams
homeassistant/components/opentherm_gw/* @mvn23
Expand Down
51 changes: 51 additions & 0 deletions homeassistant/components/openevse/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,52 @@
"""The openevse component."""
import logging

import openevsewifi
from openevsewifi import InvalidAuthentication
from requests import RequestException

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

from .const import DOMAIN, PLATFORMS

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up is called when Home Assistant is loading our component."""
config_entry.add_update_listener(update_listener)
await hass.config_entries.async_forward_entry_setup(config_entry, PLATFORMS)

return True


async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Update listener."""

_LOGGER.debug("Attempting to reload entities from the %s integration", DOMAIN)

if config_entry.data == config_entry.options:
_LOGGER.debug("No changes detected not reloading entities")
return

new_data = config_entry.options.copy()

hass.config_entries.async_update_entry(
entry=config_entry,
data=new_data,
)

await hass.config_entries.async_reload(config_entry.entry_id)


def test_connection(host: str, username: str, password: str) -> tuple:
"""Test connection to charger."""
charger = openevsewifi.Charger(host, username=username, password=password)
try:
charger.status
except RequestException:
return (False, "cannot_connect")
except InvalidAuthentication:
return (False, "invalid_auth")
return (True, "")
152 changes: 152 additions & 0 deletions homeassistant/components/openevse/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"""Adds config flow for OpenEVSE."""
from __future__ import annotations

import logging
from typing import Any

import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import slugify

from . import test_connection
from .const import DEFAULT_HOST, DEFAULT_NAME, DOMAIN

_LOGGER = logging.getLogger(__name__)


@config_entries.HANDLERS.register(DOMAIN)
class OpenEVSEFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for OpenEVSE."""

VERSION = 1
DEFAULTS = {CONF_HOST: DEFAULT_HOST, CONF_NAME: DEFAULT_NAME}

async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
return await _start_config_flow(
self,
"user",
user_input[CONF_NAME] if user_input else None,
user_input,
self.DEFAULTS,
)

async def async_step_import(self, import_config: ConfigType) -> FlowResult:
"""Import a config entry from configuration.yaml."""
for entry in self._async_current_entries():
if entry.data[CONF_HOST] == import_config[CONF_HOST]:
_LOGGER.warning(
"Already configured. This YAML configuration has already been imported. Please remove it"
)
return self.async_abort(reason="already_configured")

import_config.pop("platform", None)
if CONF_NAME not in import_config:
import_config[CONF_NAME] = DEFAULT_NAME

import_config[CONF_NAME] = slugify(import_config[CONF_NAME].lower())
return self.async_create_entry(
title=import_config[CONF_NAME], data=import_config
)

@staticmethod
@callback
def async_get_options_flow(config_entry: config_entries.ConfigEntry):
"""Return the option flow."""
return OpenEVSEOptionsFlow(config_entry)


class OpenEVSEOptionsFlow(config_entries.OptionsFlow):
"""Options flow for OpenEVSE."""

def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
"""Initialize."""
self.config_entry = config_entry

async def async_step_init(self, user_input=None):
"""Handle a flow initialized by the user."""
return await _start_config_flow(
self,
"init",
"",
user_input,
self.config_entry.data,
self.config_entry.entry_id,
)


def _get_schema(
hass: HomeAssistant,
user_input: dict[str, Any],
default_dict: dict[str, Any],
entry_id: str = None,
) -> vol.Schema:
"""Get a schema using the default_dict as a backup."""
if user_input is None:
user_input = {}

def _get_default(key: str, fallback_default: Any = None) -> dict[str, Any]:
"""Get default value for key."""
return user_input.get(key, default_dict.get(key, fallback_default))

return vol.Schema(
{
vol.Optional(
CONF_NAME, default=_get_default(CONF_NAME, DEFAULT_NAME)
): cv.string,
vol.Required(
CONF_HOST, default=_get_default(CONF_HOST, DEFAULT_HOST)
): cv.string,
vol.Optional(
CONF_USERNAME, default=_get_default(CONF_USERNAME, "")
): cv.string,
vol.Optional(
CONF_PASSWORD, default=_get_default(CONF_PASSWORD, "")
): cv.string,
},
)


async def _start_config_flow(
cls: OpenEVSEFlowHandler | OpenEVSEOptionsFlow,
step_id: str,
title: str,
user_input: dict[str, Any],
defaults: dict[str, Any],
entry_id: str = None,
):
"""Start a config flow."""
errors = {}
if user_input is not None:
user_input[CONF_NAME] = slugify(user_input[CONF_NAME].lower())
check, msg = await cls.hass.async_add_executor_job(
test_connection,
user_input[CONF_HOST],
user_input[CONF_USERNAME],
user_input[CONF_PASSWORD],
)
if msg == "cannot_connect":
errors = {CONF_HOST: "cannot_connect"}
elif msg == "invalid_auth":
errors = {CONF_USERNAME: "invalid_auth", CONF_PASSWORD: "invalid_auth"}

if not check:
return cls.async_show_form(
step_id=step_id,
data_schema=_get_schema(cls.hass, user_input, defaults, entry_id),
errors=errors,
)

return cls.async_create_entry(title=title, data=user_input)
firstof9 marked this conversation as resolved.
Show resolved Hide resolved

return cls.async_show_form(
step_id=step_id,
data_schema=_get_schema(cls.hass, user_input, defaults, entry_id),
errors=errors,
)
5 changes: 5 additions & 0 deletions homeassistant/components/openevse/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Consts for OpenEVSE integration."""
DOMAIN = "openevse"
PLATFORMS = "sensor"
DEFAULT_HOST = "openevse.local"
DEFAULT_NAME = "OpenEVSE"
5 changes: 3 additions & 2 deletions homeassistant/components/openevse/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"name": "OpenEVSE",
"documentation": "https://www.home-assistant.io/integrations/openevse",
"requirements": ["openevsewifi==1.1.0"],
"codeowners": [],
"iot_class": "local_polling"
"codeowners": ["@firstof9"],
"iot_class": "local_polling",
"config_flow": true
}
43 changes: 32 additions & 11 deletions homeassistant/components/openevse/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,23 @@
SensorEntity,
SensorEntityDescription,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import (
CONF_HOST,
CONF_MONITORED_VARIABLES,
CONF_PASSWORD,
CONF_USERNAME,
DEVICE_CLASS_TEMPERATURE,
ENERGY_KILO_WATT_HOUR,
TEMP_CELSIUS,
TIME_MINUTES,
)
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -76,20 +84,33 @@
)


def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the OpenEVSE sensor."""
host = config[CONF_HOST]
monitored_variables = config[CONF_MONITORED_VARIABLES]
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the OpenEVSE sensors."""
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=config
)
)


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the OpenEVSE sensors."""
host = entry.data[CONF_HOST]
user = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]

charger = openevsewifi.Charger(host)
charger = openevsewifi.Charger(host, username=user, password=password)

entities = [
OpenEVSESensor(charger, description)
for description in SENSOR_TYPES
if description.key in monitored_variables
]
entities = [OpenEVSESensor(charger, description) for description in SENSOR_TYPES]

add_entities(entities, True)
async_add_entities(entities, True)


class OpenEVSESensor(SensorEntity):
Expand Down
39 changes: 39 additions & 0 deletions homeassistant/components/openevse/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
},
"step": {
"user": {
"data": {
"name": "[%key:common::config_flow::data::name%]",
"host": "[%key:common::config_flow::data::host%]",
"password": "[%key:common::config_flow::data::password%] (optional)",
"username": "[%key:common::config_flow::data::username%] (optional)"
},
"description": "Please enter the connection information of OpenEVSE charger."
}
}
},
"options": {
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
},
"step": {
"init": {
"data": {
"name": "[%key:common::config_flow::data::name%]",
"host": "[%key:common::config_flow::data::host%]",
"password": "[%key:common::config_flow::data::password%] (optional)",
"username": "[%key:common::config_flow::data::username%] (optional)"
},
"description": "[%key:component::openevse::config::step::user::description%]"
}
}
}
}
39 changes: 39 additions & 0 deletions homeassistant/components/openevse/translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"config": {
"abort": {
"already_configured": "Account is already configured"
},
"error": {
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication"
},
"step": {
"user": {
"data": {
"host": "Host",
"name": "Name",
"password": "Password (optional)",
"username": "Username (optional)"
},
"description": "Please enter the connection information of OpenEVSE charger."
}
}
},
"options": {
"error": {
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication"
},
"step": {
"init": {
"data": {
"host": "Host",
"name": "Name",
"password": "Password (optional)",
"username": "Username (optional)"
},
"description": "Please enter the connection information of OpenEVSE charger."
}
}
}
}
1 change: 1 addition & 0 deletions homeassistant/generated/config_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@
"ondilo_ico",
"onewire",
"onvif",
"openevse",
"opengarage",
"opentherm_gw",
"openuv",
Expand Down
3 changes: 3 additions & 0 deletions requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,9 @@ open-garage==0.1.5
# homeassistant.components.openerz
openerz-api==0.1.0

# homeassistant.components.openevse
openevsewifi==1.1.0

# homeassistant.components.ovo_energy
ovoenergy==1.1.12

Expand Down
Loading