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
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ omit =
homeassistant/components/limitlessled/light.py
homeassistant/components/linksys_ap/device_tracker.py
homeassistant/components/linksys_smart/device_tracker.py
homeassistant/components/linky/__init__.py
homeassistant/components/linky/sensor.py
homeassistant/components/linode/*
homeassistant/components/linux_battery/sensor.py
Expand Down
2 changes: 1 addition & 1 deletion CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ homeassistant/components/life360/* @pnbruckner
homeassistant/components/lifx/* @amelchio
homeassistant/components/lifx_cloud/* @amelchio
homeassistant/components/lifx_legacy/* @amelchio
homeassistant/components/linky/* @tiste @Quentame
homeassistant/components/linky/* @Quentame
homeassistant/components/linux_battery/* @fabaff
homeassistant/components/liveboxplaytv/* @pschmitt
homeassistant/components/logger/* @home-assistant/core
Expand Down
25 changes: 25 additions & 0 deletions homeassistant/components/linky/.translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"config": {
"abort": {
"username_exists": "Account already configured"
},
"error": {
"access": "Could not access to Enedis.fr, please check your internet connection",
"enedis": "Enedis.fr answered with an error: please retry later (usually not between 11PM and 2AM)",
"unknown": "Unknown error: please retry later (usually not between 11PM and 2AM)",
"username_exists": "Account already configured",
"wrong_login": "Login error: please check your email & password"
},
"step": {
"user": {
"data": {
"password": "Password",
"username": "Email"
},
"description": "Enter your credentials",
"title": "Linky"
}
},
"title": "Linky"
}
}
54 changes: 54 additions & 0 deletions homeassistant/components/linky/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,55 @@
"""The linky component."""
import logging

import voluptuous as vol

from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import HomeAssistantType

from .const import DEFAULT_TIMEOUT, DOMAIN

_LOGGER = logging.getLogger(__name__)

ACCOUNT_SCHEMA = vol.Schema(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
}
)

CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.Schema(vol.All(cv.ensure_list, [ACCOUNT_SCHEMA]))},
extra=vol.ALLOW_EXTRA,
)


Comment thread
Quentame marked this conversation as resolved.
async def async_setup(hass, config):
"""Set up Linky sensors from legacy config file."""

conf = config.get(DOMAIN)
if conf is None:
return True

for linky_account_conf in conf:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=linky_account_conf.copy(),
)
)

return True
Comment thread
Quentame marked this conversation as resolved.


async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
"""Set up Linky sensors."""

hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "sensor")
)

return True
118 changes: 118 additions & 0 deletions homeassistant/components/linky/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"""Config flow to configure the Linky integration."""
import logging

import voluptuous as vol
from pylinky.client import LinkyClient
from pylinky.exceptions import (
PyLinkyAccessException,
PyLinkyEnedisException,
PyLinkyException,
PyLinkyWrongLoginException,
)

from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME
from homeassistant.core import callback

from .const import DEFAULT_TIMEOUT, DOMAIN

_LOGGER = logging.getLogger(__name__)


class LinkyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow."""

VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL

def __init__(self):
"""Initialize Linky config flow."""
self._username = None
self._password = None
self._timeout = None

def _configuration_exists(self, username: str) -> bool:
"""Return True if username exists in configuration."""
for entry in self.hass.config_entries.async_entries(DOMAIN):
if entry.data[CONF_USERNAME] == username:
return True
return False

@callback
def _show_setup_form(self, user_input=None, errors=None):
"""Show the setup form to the user."""

if user_input is None:
user_input = {}

return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(
CONF_USERNAME, default=user_input.get(CONF_USERNAME, "")
): str,
vol.Required(
CONF_PASSWORD, default=user_input.get(CONF_PASSWORD, "")
): str,
}
),
errors=errors or {},
)

async def async_step_user(self, user_input=None):
"""Handle a flow initiated by the user."""
errors = {}

if user_input is None:
return self._show_setup_form(user_input, None)

self._username = user_input[CONF_USERNAME]
self._password = user_input[CONF_PASSWORD]
self._timeout = user_input.get(CONF_TIMEOUT, DEFAULT_TIMEOUT)

if self._configuration_exists(self._username):
errors[CONF_USERNAME] = "username_exists"
return self._show_setup_form(user_input, errors)

client = LinkyClient(self._username, self._password, None, self._timeout)
try:
await self.hass.async_add_executor_job(client.login)
await self.hass.async_add_executor_job(client.fetch_data)
except PyLinkyAccessException as exp:
_LOGGER.error(exp)
errors["base"] = "access"
return self._show_setup_form(user_input, errors)
except PyLinkyEnedisException as exp:
_LOGGER.error(exp)
errors["base"] = "enedis"
return self._show_setup_form(user_input, errors)
except PyLinkyWrongLoginException as exp:
_LOGGER.error(exp)
errors["base"] = "wrong_login"
return self._show_setup_form(user_input, errors)
except PyLinkyException as exp:
_LOGGER.error(exp)
errors["base"] = "unknown"
return self._show_setup_form(user_input, errors)
finally:
client.close_session()

return self.async_create_entry(
title=self._username,
data={
CONF_USERNAME: self._username,
CONF_PASSWORD: self._password,
CONF_TIMEOUT: self._timeout,
},
)

async def async_step_import(self, user_input=None):
"""Import a config entry.

Only host was required in the yaml file all other fields are optional
"""
if self._configuration_exists(user_input[CONF_USERNAME]):
return self.async_abort(reason="username_exists")

return await self.async_step_user(user_input)
5 changes: 5 additions & 0 deletions homeassistant/components/linky/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Linky component constants."""

DOMAIN = "linky"

DEFAULT_TIMEOUT = 10
4 changes: 2 additions & 2 deletions homeassistant/components/linky/manifest.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"domain": "linky",
"name": "Linky",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/linky",
"requirements": [
"pylinky==0.3.3"
"pylinky==0.4.0"
],
"dependencies": [],
"codeowners": [
"@tiste",
"@Quentame"
]
}
Loading