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

Merge config_flow into master #175

Merged
merged 124 commits into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
124 commits
Select commit Hold shift + click to select a range
e69e6db
Initial config flow PoC
trvrnrth Dec 23, 2023
5e0c36c
Import from yaml is one-off
trvrnrth Dec 31, 2023
e12c87a
Update device info as it becomes available and fix entry reload
trvrnrth Dec 31, 2023
012fa99
Merge branch 'refactor' into config-flow
trvrnrth Dec 31, 2023
397de35
Merge remote-tracking branch 'upstream/refactor' into config-flow
trvrnrth Jan 5, 2024
eea8bea
Majority of config flow now implenented
trvrnrth Jan 10, 2024
13265d2
Merge remote-tracking branch 'upstream/refactor' into config-flow
trvrnrth Jan 10, 2024
6683793
Merge remote-tracking branch 'upstream/refactor' into config-flow
trvrnrth Jan 11, 2024
c335f55
Lint
trvrnrth Jan 11, 2024
f4d477a
Merge remote-tracking branch 'upstream/refactor' into config-flow
trvrnrth Jan 13, 2024
82fa467
Restore support for reading remote commands from known list traits
trvrnrth Jan 13, 2024
de7bc10
Working multi-stage serial port config
trvrnrth Jan 13, 2024
d6eed29
Merge remote-tracking branch 'upstream/refactor' into config-flow
trvrnrth Jan 13, 2024
36d975b
known list fixes
trvrnrth Jan 13, 2024
10d6609
Add missing config translations
trvrnrth Jan 13, 2024
9f86f6d
Fix adding new entry and provide title for better messaging
trvrnrth Jan 13, 2024
e907c2c
Better default serial port handling
trvrnrth Jan 13, 2024
c6fb44b
Guide user through all config on initial setup
trvrnrth Jan 13, 2024
ae5c198
Primary entities use device name
trvrnrth Jan 13, 2024
966c603
Set entity names via descriptions
trvrnrth Jan 13, 2024
7202638
Friendly name for controller device
trvrnrth Jan 13, 2024
730b546
Merge remote-tracking branch 'upstream/refactor' into config-flow
trvrnrth Jan 13, 2024
6c02a24
Clearer label for send_packet advanced feature
trvrnrth Jan 13, 2024
c0fbe11
Lint
trvrnrth Jan 13, 2024
a22d213
remove debug code
trvrnrth Jan 13, 2024
56e9ce0
Fix discarding legacy restore_cache config on import if not present
trvrnrth Jan 14, 2024
61b8b9a
Remove example from schema config UI
trvrnrth Jan 14, 2024
a46632a
Merge remote-tracking branch 'upstream/refactor' into config-flow
trvrnrth Jan 15, 2024
4d0e671
Ensure unique evohome controller device names
trvrnrth Jan 15, 2024
25b5ba6
Merge remote-tracking branch 'upstream/refactor' into config-flow
trvrnrth Jan 17, 2024
ab0c1c0
Extend "Controler 01:123456" device naming to all system types
trvrnrth Jan 18, 2024
e6dd8c1
Merge remote-tracking branch 'upstream/refactor' into config-flow
trvrnrth Jan 18, 2024
318fcb2
Fix generated sensor entity ID
trvrnrth Jan 18, 2024
5ad890b
Consistent approach to entity ID generation across all domains
trvrnrth Jan 18, 2024
639a0e8
Merge remote-tracking branch 'upstream/refactor' into config-flow
trvrnrth Jan 18, 2024
8768d3b
Lint
trvrnrth Jan 18, 2024
4754cbd
Merge branch 'neaten-entity-ids' into config-flow
trvrnrth Jan 18, 2024
b757b4d
Merge remote-tracking branch 'upstream/refactor' into config-flow
trvrnrth Jan 18, 2024
0d4c3d4
Merge remote-tracking branch 'upstream/refactor' into config-flow
trvrnrth Jan 28, 2024
11ee1f7
Neaten entity descriptions
trvrnrth Feb 3, 2024
a471440
Merge pull request #150 from trvrnrth/neaten-descriptions
zxdavb Feb 4, 2024
956b6d1
Merge remote-tracking branch 'upstream/config_flow' into config-flow
trvrnrth Feb 4, 2024
7e6039e
Merge pull request #121 from trvrnrth/config-flow
zxdavb Feb 4, 2024
dbcf2da
Fix bad merge of message events regex match/search fix
trvrnrth Feb 4, 2024
bf268f0
Merge pull request #152 from trvrnrth/fix-bad-merge-message-event-reg…
zxdavb Feb 4, 2024
14128ea
bump to 0.40.x
zxdavb Feb 4, 2024
8f55f77
fix #85 send_pkt fails with 18:730 as dest
zxdavb Feb 5, 2024
e85332e
Final instead of Final[str]
zxdavb Feb 5, 2024
9755e83
mypy work
zxdavb Feb 5, 2024
81d84d3
bump to 0.41.7
zxdavb Feb 5, 2024
503e013
Merge branch 'config_flow' into master
zxdavb Feb 7, 2024
76d648d
Merge pull request #158 from trvrnrth/master
zxdavb Feb 7, 2024
dcb10be
Add description to cache clear config options step
trvrnrth Feb 8, 2024
707bc1c
Merge pull request #160 from trvrnrth/cache-clear-description
zxdavb Feb 11, 2024
1a4f3e1
initial commit
zxdavb Feb 11, 2024
8020072
Merge pull request #165 from zxdavb/bugfix_missing_dhw
zxdavb Feb 11, 2024
2ba8700
names should be restored if the schema is not restored
zxdavb Feb 11, 2024
a35257e
fix #162
zxdavb Feb 11, 2024
4e50a9a
evo_control test
zxdavb Feb 14, 2024
c2a5b86
improve evo_control test
zxdavb Feb 15, 2024
2ef4058
tweak evo_control test
zxdavb Feb 15, 2024
5ca4e68
add log file for tests
zxdavb Feb 15, 2024
8e8a9ae
tweak test
zxdavb Feb 15, 2024
793de26
tweak test
zxdavb Feb 15, 2024
5fe3a2b
tweak test log
zxdavb Feb 15, 2024
83112cd
expose battery ESA more cleanly
zxdavb Feb 15, 2024
1473386
use WATER_HEATER_DESCRIPTIONS (for tests)
zxdavb Feb 15, 2024
4993538
evocontrol test - add temperature attr to namespace
zxdavb Feb 17, 2024
70df0f5
tidy up
zxdavb Feb 17, 2024
dc956c8
tidy up
zxdavb Feb 17, 2024
187485e
tweak test
zxdavb Feb 17, 2024
bd51ef4
tidy up tests
zxdavb Feb 17, 2024
e01cf0f
mypy
zxdavb Feb 17, 2024
1b8cf68
minor tweak of test
zxdavb Feb 17, 2024
63875f5
doctweak
zxdavb Feb 17, 2024
dd89109
doctweak
zxdavb Feb 17, 2024
33c1136
move tests folder
zxdavb Feb 18, 2024
b021d00
lint
zxdavb Feb 18, 2024
b36eadb
correct .gitignore
zxdavb Feb 18, 2024
bff3bc9
add test_setup
zxdavb Feb 18, 2024
5ca1801
doctweak
zxdavb Feb 18, 2024
bd3e5bd
working test
zxdavb Feb 21, 2024
1414ff9
pre-commit
zxdavb Feb 22, 2024
b3c124f
add virtual RF
zxdavb Feb 22, 2024
4143bf2
tweak test
zxdavb Feb 22, 2024
772e332
change required to make tests work
zxdavb Feb 22, 2024
1c2847d
refactor test
zxdavb Feb 22, 2024
1027ea8
tweak meta file
zxdavb Feb 23, 2024
884200a
tweak tests
zxdavb Feb 23, 2024
82c1aa5
move test data
zxdavb Feb 23, 2024
ae2f0bd
tweak virtual RF
zxdavb Feb 23, 2024
80736be
tweak test data
zxdavb Feb 23, 2024
29dc6ad
tweak test
zxdavb Feb 24, 2024
7aa08ff
tweak tests
zxdavb Feb 24, 2024
9afd091
tweak test_data
zxdavb Feb 24, 2024
46dbd72
tweak tests
zxdavb Feb 24, 2024
173069d
add services tests
zxdavb Feb 28, 2024
bf008b6
extend tests
zxdavb Feb 28, 2024
5575b40
bugfix async_unload_entry, and changes for tests
zxdavb Feb 28, 2024
365449a
extend ests, mypy tests
zxdavb Feb 29, 2024
4643470
requirements fro HA 2024.02.x
zxdavb Feb 29, 2024
5142917
tweak manifest.json - add paho-mqtt
zxdavb Feb 29, 2024
007e346
possible bugfix #169
zxdavb Feb 29, 2024
b45d62f
assert fro bug #163
zxdavb Feb 29, 2024
e3564be
doctweak
zxdavb Feb 29, 2024
3aafda1
use fixture
zxdavb Feb 29, 2024
8f43b14
add tests
zxdavb Mar 1, 2024
3374618
refactor, extend tests
zxdavb Mar 1, 2024
aa0a2b1
harden service schemas
zxdavb Mar 1, 2024
8cef4df
bugfix DHW_PARAMS schema
zxdavb Mar 1, 2024
54640a7
add last tests
zxdavb Mar 3, 2024
c657264
doctweaks
zxdavb Mar 3, 2024
8a788b4
requirements
zxdavb Mar 3, 2024
601d8cf
isort config
zxdavb Mar 3, 2024
1d61542
meta data
zxdavb Mar 4, 2024
2cb4a25
lint
zxdavb Mar 9, 2024
d57480e
requirements
zxdavb Mar 9, 2024
313e7fd
update requirements
zxdavb Mar 9, 2024
812b5d8
add logging for config_flow
zxdavb Mar 10, 2024
d27e4aa
lint
zxdavb Mar 10, 2024
8b69939
bump library ver (CP)
zxdavb Mar 10, 2024
39f94d1
tweak tests
zxdavb Mar 10, 2024
05da638
mypy fix (can CP)
zxdavb Mar 10, 2024
e90beb8
tweak requirements for HA 2024.3.0
zxdavb Mar 10, 2024
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
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ custom_components/*

### local .gitignore used by this repo

!circleci/
!tests/**/*.json
!tests/**/*.log
!tests/**/*.yaml

!custom_components/ramses_cc
!custom_components/ramses_cc/manifest.json

Expand Down Expand Up @@ -100,7 +103,7 @@ cover/
*.pot

# Django stuff:
*.log
# *.log
local_settings.py
db.sqlite3
db.sqlite3-journal
Expand Down
28 changes: 17 additions & 11 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
# .pre-commit-config.yaml file for https://github.com/zxdavb/ramses_cc
# last updated 2024-01-21
# last updated 2024-02-22, based on HA 2024.03.dev0

# exclude: (^.secrets/|^docs/|^misc)

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.8 # same as HA
rev: v0.3.2
hooks:
- id: ruff # linter
- id: ruff-format # formatter

# - repo: https://github.com/pre-commit/mirrors-mypy
# rev: v1.8.0
# hooks:
# - id: mypy
# additional_dependencies: [voluptuous==0.14.1]

- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
Expand All @@ -37,22 +31,34 @@ repos:
# entry: '[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+' # email address

- id: debugs
name: check DEBUG flags
name: check for DEBUG flags
entry: '_DBG_.*=.*True'
language: pygrep
args: [-i]
exclude: (.pre-commit-config.yaml|^tests/.*\.py$) # avoid false +ve

# - id: fixme
# name: check FIXME flags
# name: check for FIXME flags
# entry: '#.*(FIXME)' # |TODO)'
# language: pygrep
# args: [-i]
# exclude: (.pre-commit-config.yaml|^tests/.*\.py$) # avoid false +ve

- id: secrets
name: check secrets
name: check for secrets
entry: '#.*(secret|password|pwd)'
language: pygrep
args: [-i]
exclude: .pre-commit-config.yaml # avoid false +ve

# We do not use pre-commit/mirrors-mypy, as it comes with opinionated defaults
# (like --ignore-missing-imports) and is difficult to configure to run
# with the dependencies correctly installed.

# - repo: https://github.com/pre-commit/mirrors-mypy
# rev: v1.8.0
# hooks:
# - id: mypy
# additional_dependencies:
# - voluptuous==0.13.1
# - pytest-homeassistant-custom-component==0.13.100
160 changes: 90 additions & 70 deletions custom_components/ramses_cc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,31 @@

Requires a Honeywell HGI80 (or compatible) gateway.
"""

from __future__ import annotations

from dataclasses import dataclass
import logging
import re
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Final

from ramses_rf.device import Fakeable
from ramses_rf.entity_base import Entity as RamsesRFEntity
from ramses_tx.address import pkt_addrs
from ramses_tx.command import Command
from ramses_tx.exceptions import PacketAddrSetInvalid, TransportSerialError
import voluptuous as vol # type: ignore[import-untyped]

from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ID, Platform
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.service import verify_domain_control
from homeassistant.helpers.typing import ConfigType

from ramses_rf.entity_base import Entity as RamsesRFEntity
from ramses_tx.exceptions import TransportSerialError

from .broker import RamsesBroker
from .const import (
BROKER,
CONF_ADVANCED_FEATURES,
CONF_MESSAGE_EVENTS,
CONF_SEND_PACKET,
Expand All @@ -33,7 +35,7 @@
)
from .schemas import (
SCH_BIND_DEVICE,
SCH_DOMAIN_CONFIG,
SCH_NO_SVC_PARAMS,
SCH_SEND_PACKET,
SVC_BIND_DEVICE,
SVC_FORCE_UPDATE,
Expand All @@ -47,7 +49,7 @@
_LOGGER = logging.getLogger(__name__)


CONFIG_SCHEMA = vol.Schema({DOMAIN: SCH_DOMAIN_CONFIG}, extra=vol.ALLOW_EXTRA)
CONFIG_SCHEMA = cv.deprecated(DOMAIN, raise_if_present=False)

PLATFORMS: Final[Platform] = (
Platform.BINARY_SENSOR,
Expand All @@ -59,34 +61,87 @@


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Ramses integration."""

hass.data[DOMAIN] = {}

# One-off import of entry from config yaml
if DOMAIN in config and not hass.config_entries.async_entries(DOMAIN):
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=config[DOMAIN],
)
)

return True


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Create a ramses_rf (RAMSES_II)-based system."""

broker = RamsesBroker(hass, config)
_LOGGER.debug("Setting up entry %s...", entry.entry_id)

broker = RamsesBroker(hass, entry) # KeyError: 'serial_port'

try:
await broker.start()
await broker.async_setup()
except TransportSerialError as exc:
_LOGGER.error("There is a problem with the serial port: %s", exc)
msg = f"There is a problem with the serial port: {exc}"
_LOGGER.debug("Failed to set up entry %s (will retry): %s", entry.entry_id, msg)
raise ConfigEntryNotReady(msg) from exc

# Setup is complete and config is valid, so start polling
hass.data[DOMAIN][entry.entry_id] = broker
await broker.async_start()

async_register_domain_services(hass, entry, broker)
async_register_domain_events(hass, entry, broker)

entry.async_on_unload(entry.add_update_listener(async_update_listener))

_LOGGER.debug("Successfully set up entry %s", entry.entry_id)
return True


async def async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle options update."""
hass.async_create_task(hass.config_entries.async_reload(entry.entry_id))


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""

broker: RamsesBroker = hass.data[DOMAIN][entry.entry_id]
if not await broker.async_unload_platforms():
return False

hass.data.setdefault(DOMAIN, {})[BROKER] = broker
for svc in hass.services.async_services_for_domain(DOMAIN):
hass.services.async_remove(DOMAIN, svc)

register_domain_services(hass, broker)
register_domain_events(hass, broker)
hass.data[DOMAIN].pop(entry.entry_id)

return True


@callback # TODO: the following is a mess - to add register/deregister of clients
def register_domain_events(hass: HomeAssistant, broker: RamsesBroker) -> None:
def async_register_domain_events(
hass: HomeAssistant, entry: ConfigEntry, broker: RamsesBroker
) -> None:
"""Set up the handlers for the system-wide events."""

features: dict[str, Any] = entry.options.get(CONF_ADVANCED_FEATURES, {})
if message_events := features.get(CONF_MESSAGE_EVENTS):
message_events_regex = re.compile(message_events)
else:
message_events_regex = None

@callback
def async_process_msg(msg: Message, *args: Any, **kwargs: Any) -> None:
"""Process a message from the event bus as pass it on."""

if (
regex := broker.config[CONF_ADVANCED_FEATURES][CONF_MESSAGE_EVENTS]
) and regex.search(f"{msg!r}"):
if message_events_regex and message_events_regex.search(f"{msg!r}"):
event_data = {
"dtm": msg.dtm.isoformat(),
"src": msg.src.id,
Expand All @@ -110,69 +165,31 @@ def async_process_msg(msg: Message, *args: Any, **kwargs: Any) -> None:


@callback
def register_domain_services(hass: HomeAssistant, broker: RamsesBroker) -> None:
def async_register_domain_services(
hass: HomeAssistant, entry: ConfigEntry, broker: RamsesBroker
) -> None:
"""Set up the handlers for the domain-wide services."""

@verify_domain_control(hass, DOMAIN) # TODO: is a work in progress
async def async_bind_device(call: ServiceCall) -> None:
device: Fakeable

try:
device = broker.client.fake_device(call.data["device_id"])
except LookupError as exc:
_LOGGER.error("%s", exc)
return

if call.data["device_info"]:
cmd = Command(call.data["device_info"])
else:
cmd = None

await device._initiate_binding_process( # may: BindingFlowFailed
list(call.data["offer"].keys()),
confirm_code=list(call.data["confirm"].keys()),
ratify_cmd=cmd,
) # TODO: will need to re-discover schema
hass.helpers.event.async_call_later(5, broker.async_update)
await broker.async_bind_device(call)

@verify_domain_control(hass, DOMAIN)
async def async_force_update(_: ServiceCall) -> None:
await broker.async_update()
async def async_force_update(call: ServiceCall) -> None:
await broker.async_force_update(call)

@verify_domain_control(hass, DOMAIN)
async def async_send_packet(call: ServiceCall) -> None:
kwargs = dict(call.data.items()) # is ReadOnlyDict
if (
call.data["device_id"] == "18:000730"
and kwargs.get("from_id", "18:000730") == "18:000730"
and broker.client.hgi.id
):
kwargs["device_id"] = broker.client.hgi.id

cmd = broker.client.create_cmd(**kwargs)

# HACK: to fix the device_id when GWY announcing, will be:
# I --- 18:000730 18:006402 --:------ 0008 002 00C3 # because src != dst
# ... should be:
# I --- 18:000730 --:------ 18:006402 0008 002 00C3 # 18:730 is sentinel
if cmd.src.id == "18:000730" and cmd.dst.id == broker.client.hgi.id:
try:
pkt_addrs(broker.client.hgi.id + cmd._frame[16:37])
except PacketAddrSetInvalid:
cmd._addrs[1], cmd._addrs[2] = cmd._addrs[2], cmd._addrs[1]
cmd._repr = None

broker.client.send_cmd(cmd)
hass.helpers.event.async_call_later(5, broker.async_update)
await broker.async_send_packet(call)

hass.services.async_register(
DOMAIN, SVC_BIND_DEVICE, async_bind_device, schema=SCH_BIND_DEVICE
)
hass.services.async_register(
DOMAIN, SVC_FORCE_UPDATE, async_force_update, schema={}
DOMAIN, SVC_FORCE_UPDATE, async_force_update, schema=SCH_NO_SVC_PARAMS
)

if broker.config[CONF_ADVANCED_FEATURES].get(CONF_SEND_PACKET):
if entry.options.get(CONF_ADVANCED_FEATURES, {}).get(CONF_SEND_PACKET):
hass.services.async_register(
DOMAIN, SVC_SEND_PACKET, async_send_packet, schema=SCH_SEND_PACKET
)
Expand Down Expand Up @@ -201,6 +218,7 @@ def __init__(
self.entity_description = entity_description

self._attr_unique_id = device.id
self._attr_device_info = DeviceInfo(identifiers={(DOMAIN, device.id)})

@property
def extra_state_attributes(self) -> dict[str, Any]:
Expand Down Expand Up @@ -229,8 +247,10 @@ async def async_added_to_hass(self) -> None:
def async_write_ha_state_delayed(self, delay: int = 3) -> None:
"""Write to the state machine after a short delay to allow system to quiesce."""

# FIXME: doesn't work, as injects `_now: dt``, where only self is expected
# async_call_later(self.hass, delay, self.async_write_ha_state)
# NOTE: this doesn't work (below), as call_later injects `_now: dt`
# async_call_later(self.hass, delay, self.async_write_ha_state)
# but only self is expected:
# def async_write_ha_state(self) -> None:

self.hass.loop.call_later(delay, self.async_write_ha_state)

Expand Down
Loading