Skip to content

Commit

Permalink
Initial upload
Browse files Browse the repository at this point in the history
  • Loading branch information
jbouwh committed May 9, 2022
1 parent 2b6b468 commit 2e814bc
Show file tree
Hide file tree
Showing 25 changed files with 1,358 additions and 1 deletion.
8 changes: 8 additions & 0 deletions .devcontainer/configuration.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
default_config:

logger:
default: info
logs:
custom_components.elro_connects: debug
# If you need to debug uncomment the line below (doc: https://www.home-assistant.io/integrations/debugpy/)
# debugpy:
30 changes: 30 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
{
"image": "ludeeus/container:integration-debian",
"name": "Elro Connects development",
"context": "..",
"appPort": [
"9123:8123"
],
"postCreateCommand": "container install",
"extensions": [
"ms-python.python",
"github.vscode-pull-request-github",
"ryanluker.vscode-coverage-gutters",
"ms-python.vscode-pylance"
],
"settings": {
"files.eol": "\n",
"editor.tabSize": 4,
"terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": "/usr/bin/python3",
"python.analysis.autoSearchPaths": false,
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.formatting.provider": "black",
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true
}
}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
__pycache__
pythonenv*
venv
.venv
.coverage
.idea
64 changes: 63 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,64 @@
# ha-elro-connects
Elro Connects for Home Assistant via HACS

[![GitHub Release][releases-shield]][releases]
[![GitHub Activity][commits-shield]][commits]
[![License][license-shield]](LICENSE)

[![hacs][hacsbadge]][hacs]
![Project Maintenance][maintenance-shield]

[![Community Forum][forum-shield]][forum]

**This component will set up the following platforms.**

Platform | Description
-- | --
`siren` | Represents Elro Connects alarms as a siren. Turn the siren `ON` to test it. Turn it `OFF` to silence the (test) alarm.

Other platforms (sensor e.g. sensor for battery level and signal strength are to be added later)

## Installation

1. Using the tool of choice open the directory (folder) for your HA configuration (where you find `configuration.yaml`).
2. If you do not have a `custom_components` directory (folder) there, you need to create it.
3. In the `custom_components` directory (folder) create a new folder called `elro_connects`.
4. Download _all_ the files from the `custom_components/elro_connects/` directory (folder) in this repository.
5. Place the files you downloaded in the new directory (folder) you created.
6. Restart Home Assistant
7. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Elro Connects"

Using your HA configuration directory (folder) as a starting point you should now also have something like this:

```text
custom_components/elro_connects/translations/en.json
custom_components/elro_connects/translations/nl.json
custom_components/elro_connects/__init__.py
custom_components/elro_connects/api.py
custom_components/elro_connects/siren.py
custom_components/elro_connects/config_flow.py
custom_components/elro_connects/const.py
custom_components/elro_connects/manifest.json
...
```

## Configuration is done in the UI

<!---->

## Contributions are welcome

If you want to contribute to this please read the [Contribution guidelines](CONTRIBUTING.md)

***

[elro_connects]: https://github.com/jbouwh/ha-elro-connects
[buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow.svg?style=for-the-badge
[commits]: https://github.com/jbouwh/ha-elro-connects/commits/main
[hacs]: https://github.com/custom-components/hacs
[hacsbadge]: https://img.shields.io/badge/HACS-Custom-orange.svg?style=for-the-badge
[forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg?style=for-the-badge
[forum]: https://community.home-assistant.io/
[license-shield]: https://img.shields.io/github/license/custom-components/blueprint.svg?style=for-the-badge
[maintenance-shield]: https://img.shields.io/badge/maintainer-Joakim%20Sørensen%20%40ludeeus-blue.svg?style=for-the-badge
[releases-shield]: https://img.shields.io/github/release/elro_connects/blueprint.svg?style=for-the-badge
[releases]: https://github.com/jbouwh/ha-elro-connects/releases
91 changes: 91 additions & 0 deletions custom_components/elro_connects/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""The Elro Connects integration."""
from __future__ import annotations

from datetime import timedelta
import logging

from elro.api import K1

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT, SERVICE_RELOAD, Platform
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import CONF_CONNECTOR_ID, DEFAULT_INTERVAL, DOMAIN
from .device import ElroConnectsK1

_LOGGER = logging.getLogger(__name__)

PLATFORMS: list[Platform] = [Platform.SIREN]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Elro Connects from a config entry."""

current_device_set: set | None = None

async def _async_update_data() -> dict[int, dict]:
"""Update data via API."""
nonlocal current_device_set
try:
await elro_connects_api.async_update()
except K1.K1ConnectionError as err:
raise UpdateFailed(err) from err
new_set = set(elro_connects_api.data.keys())
if current_device_set is None:
current_device_set = new_set
if new_set - current_device_set:
current_device_set = new_set
# New devices discovered, trigger a reload
await hass.services.async_call(
DOMAIN,
SERVICE_RELOAD,
{},
blocking=False,
)
return elro_connects_api.data

async def async_reload(call: ServiceCall) -> None:
"""Reload the integration."""
await async_unload_entry(hass, entry)
await async_setup_entry(hass, entry)

coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name=DOMAIN.title(),
update_method=_async_update_data,
update_interval=timedelta(seconds=DEFAULT_INTERVAL),
)
elro_connects_api = ElroConnectsK1(
coordinator,
entry.data[CONF_HOST],
entry.data[CONF_CONNECTOR_ID],
entry.data[CONF_PORT],
)

await coordinator.async_config_entry_first_refresh()

hass.data[DOMAIN] = {}
hass.data[DOMAIN][entry.entry_id] = elro_connects_api

hass.config_entries.async_setup_platforms(entry, PLATFORMS)

entry.async_on_unload(
entry.add_update_listener(elro_connects_api.async_update_settings)
)
hass.helpers.service.async_register_admin_service(
DOMAIN, SERVICE_RELOAD, async_reload
)

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
elro_connects_api: ElroConnectsK1 = hass.data[DOMAIN][entry.entry_id]
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
await elro_connects_api.async_disconnect()
hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok
145 changes: 145 additions & 0 deletions custom_components/elro_connects/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
"""Config flow for Elro Connects integration."""
from __future__ import annotations

import logging
from typing import Any

from elro.api import K1
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv

from .const import CONF_CONNECTOR_ID, DEFAULT_PORT, DOMAIN

_LOGGER = logging.getLogger(__name__)

ELRO_CONNECTS_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_CONNECTOR_ID): str,
vol.Required(CONF_HOST): str,
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port,
}
)


class K1ConnectionTest:
"""Elro Connects K1 connection test."""

def __init__(self, host: str) -> None:
"""Initialize."""
self.host = host

async def async_try_connection(self, connector_id: str, port: int) -> bool:
"""Test if we can authenticate with the host."""
connector = K1(self.host, connector_id, port)
try:
await connector.async_connect()
except K1.K1ConnectionError:
return False
finally:
await connector.async_disconnect()
return True


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

hub = K1ConnectionTest(data["host"])

if not await hub.async_try_connection(data["connector_id"], data["port"]):
raise CannotConnect

return {"title": "Elro Connects K1 Connector"}


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

VERSION = 1

@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Handle configuring options."""
return OptionsFlowHandler(config_entry)

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=ELRO_CONNECTS_DATA_SCHEMA
)

errors = {}

try:
info = await async_validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
await self.async_set_unique_id(user_input[CONF_CONNECTOR_ID])
self._abort_if_unique_id_configured()
return self.async_create_entry(title=info["title"], data=user_input)

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


class OptionsFlowHandler(config_entries.OptionsFlow):
"""Manage the options."""

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

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage configuration options."""
errors = {}
entry_data = self.config_entry.data
if user_input is not None:
changed_input = {}
changed_input.update(user_input)
changed_input[CONF_CONNECTOR_ID] = entry_data.get(CONF_CONNECTOR_ID)
try:
await async_validate_input(self.hass, changed_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
self.hass.config_entries.async_update_entry(
self.config_entry, data=changed_input
)
return self.async_create_entry(title="", data=user_input)

return self.async_show_form(
step_id="init",
errors=errors,
data_schema=vol.Schema(
{
vol.Required(CONF_HOST, default=entry_data.get(CONF_HOST)): str,
vol.Required(CONF_PORT, default=entry_data.get(CONF_PORT)): cv.port,
}
),
)


class CannotConnect(HomeAssistantError):
"""Error to indicate we cannot connect."""
8 changes: 8 additions & 0 deletions custom_components/elro_connects/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Constants for the Elro Connects integration."""

DOMAIN = "elro_connects"

DEFAULT_INTERVAL = 15
DEFAULT_PORT = 1025

CONF_CONNECTOR_ID = "connector_id"
Loading

0 comments on commit 2e814bc

Please sign in to comment.