Skip to content

Commit

Permalink
Added support for ConfigFlow, close #5
Browse files Browse the repository at this point in the history
  • Loading branch information
nidble committed Aug 5, 2024
1 parent 99df175 commit 4e257ea
Show file tree
Hide file tree
Showing 11 changed files with 608 additions and 156 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Custom Component/Integration for controlling Inim alarm through [Home Assistant]

TODO

### Manual Installation
<!-- ### Manual Installation
You can manually install as a custom component on your Home Assistant installation.
Expand All @@ -26,7 +26,7 @@ Follow these steps:
```
for further detail look at `config/configuration.yam` present in this repository.
* Restart Home Assistant
* Restart Home Assistant -->

## Configuration

Expand Down
25 changes: 1 addition & 24 deletions config/configuration.yaml
Original file line number Diff line number Diff line change
@@ -1,24 +1 @@
# Loads default set of integrations. Do not remove.
default_config:

inim:
password: !secret inim_alarm_password
username: !secret inim_alarm_username
client_id: "123456"
device_id: "789012"
# TODO: add support for panels
# panels:
# - name: roof
# unique_id: roof
# scenarios: # use this to override default scenario ids
# armed_away: 0
# disarmed: 1
# armed_night: 2
# armed_home: 3
# - name: 1st Floor
# unique_id: 1stfloor
# scenarios:
# armed_away: 3
# disarmed: 3
# armed_night: 1
# armed_home: 5
# configuration setup has been deprecated, please use HACS and UI
267 changes: 178 additions & 89 deletions custom_components/inim/__init__.py
Original file line number Diff line number Diff line change
@@ -1,97 +1,82 @@
"""Nintendo Wishlist integration."""
from collections.abc import Callable
from dataclasses import dataclass
from datetime import timedelta
import logging

from pyinim.inim_cloud import InimCloud
import voluptuous as vol

# import voluptuous as vol
from homeassistant import core
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_USERNAME,
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED,
Platform,
)
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.typing import ConfigType

# import homeassistant.helpers.config_validation as cv
# from homeassistant.helpers.discovery import async_load_platform
# from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from .const import (
CONF_CLIENT_ID,
CONF_DEVICE_ID,
CONF_SCENARIOS,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
)
from .const import CONF_CLIENT_ID, CONF_DEVICE_ID, DOMAIN
from .types import InimResult

_LOGGER = logging.getLogger(__name__)
SCENARIOS_SCHEMA = vol.Schema(
{
# vol.Optional(STATE_ALARM_ARMED_AWAY, default=0): cv.positive_int,
vol.Optional(STATE_ALARM_ARMED_AWAY): cv.positive_int,
vol.Optional(STATE_ALARM_DISARMED): cv.positive_int,
vol.Optional(STATE_ALARM_ARMED_NIGHT): cv.positive_int,
vol.Optional(STATE_ALARM_ARMED_HOME): cv.positive_int,
}
)

DEFAULT_SCENARIOS_SCHEMA = {
STATE_ALARM_ARMED_AWAY: 0,
STATE_ALARM_DISARMED: 1,
STATE_ALARM_ARMED_NIGHT: 2,
STATE_ALARM_ARMED_HOME: 3,
}

PLATFORM_SCHEMA = vol.Schema(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_CLIENT_ID): cv.string,
vol.Required(CONF_DEVICE_ID): cv.string,
vol.Optional(
CONF_SCENARIOS, default=DEFAULT_SCENARIOS_SCHEMA
): SCENARIOS_SCHEMA,
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): vol.All(
cv.time_period, cv.positive_timedelta
),
}
)

CONFIG_SCHEMA = vol.Schema(
{DOMAIN: PLATFORM_SCHEMA},
# The full HA configurations gets passed to `async_setup` so we need to allow
# extra keys.
extra=vol.ALLOW_EXTRA,
)


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

@NOTE: `config` is the full dict from `configuration.yaml`.
:returns: A boolean to indicate that initialization was successful.
"""
conf = config[DOMAIN]
scan_interval = conf[CONF_SCAN_INTERVAL]
device_id = conf[CONF_DEVICE_ID]

inim = InimCloud(
# ----------------------------------------------------------------------------
# A list of the different platforms we wish to setup.
# Add or remove from this list based on your specific need
# of entity platform types.
# ----------------------------------------------------------------------------
PLATFORMS: list[Platform] = [
Platform.BINARY_SENSOR,
Platform.ALARM_CONTROL_PANEL,
]


@dataclass
class RuntimeData:
"""Class to hold your data."""

coordinator: DataUpdateCoordinator
inim_cloud_api: InimCloud
cancel_update_listener: Callable


async def async_setup_entry(
hass: core.HomeAssistant, config_entry: ConfigEntry
) -> bool:
"""Set up Example Integration from a config entry."""

hass.data.setdefault(DOMAIN, {})

# ----------------------------------------------------------------------------
# Initialise the coordinator that manages data updates from your api.
# This is defined in coordinator.py
# ----------------------------------------------------------------------------
# coordinator = ExampleCoordinator(hass, config_entry)
username = config_entry.data[CONF_USERNAME]
password = config_entry.data[CONF_PASSWORD]
client_id = config_entry.data[CONF_CLIENT_ID]
device_id = config_entry.data[CONF_DEVICE_ID]
scan_interval = timedelta(seconds=config_entry.data[CONF_SCAN_INTERVAL])

inim_cloud_api = InimCloud(
async_get_clientsession(hass),
name="Inim",
username=conf[CONF_USERNAME],
password=conf[CONF_PASSWORD],
client_id=conf[CONF_CLIENT_ID],
username=username,
password=password,
client_id=client_id,
)

async def async_fetch_inim() -> InimResult:
await inim.get_request_poll(device_id)
_, _, res = await inim.get_devices_extended(device_id)
await inim_cloud_api.get_request_poll(device_id)
_, _, res = await inim_cloud_api.get_devices_extended(device_id)
return res

coordinator = DataUpdateCoordinator(
Expand All @@ -104,30 +89,134 @@ async def async_fetch_inim() -> InimResult:
update_interval=scan_interval,
)

# Fetch initial data so we have data when entities subscribe
await coordinator.async_refresh()

hass.data[DOMAIN] = {
"conf": conf,
"coordinator": coordinator,
"inim_cloud_api": inim,
}

# hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
# RING_API: ring,
# RING_DEVICES: ring.devices(),
# RING_DEVICES_COORDINATOR: devices_coordinator,
# RING_NOTIFICATIONS_COORDINATOR: notifications_coordinator,
# }
# ----------------------------------------------------------------------------
# Perform an initial data load from api.
# async_config_entry_first_refresh() is special in that it does not log errors
# if it fails.
# ----------------------------------------------------------------------------
await coordinator.async_config_entry_first_refresh()
# or
# await coordinator.async_refresh()

# ----------------------------------------------------------------------------
# Test to see if api initialised correctly, else raise ConfigNotReady to make
# HA retry setup.
# Change this to match how your api will know if connected or successful
# update.
# ----------------------------------------------------------------------------
if not coordinator.data:
raise ConfigEntryNotReady

# ----------------------------------------------------------------------------
# Initialise a listener for config flow options changes.
# This will be removed automatically if the integraiton is unloaded.
# See config_flow for defining an options setting that shows up as configure
# on the integration.
# If you do not want any config flow options, no need to have listener.
# ----------------------------------------------------------------------------
cancel_update_listener = config_entry.async_on_unload(
config_entry.add_update_listener(_async_update_listener)
)

hass.async_create_task(
async_load_platform(hass, "alarm_control_panel", DOMAIN, {}, conf)
# ----------------------------------------------------------------------------
# Add the coordinator and update listener to hass data to make
# accessible throughout your integration
# Note: this will change on HA2024.6 to save on the config entry.
# ----------------------------------------------------------------------------
# hass.data[DOMAIN][config_entry.entry_id] = RuntimeData(
# coordinator, cancel_update_listener
# )
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = RuntimeData(
coordinator, inim_cloud_api, cancel_update_listener
)
hass.async_create_task(async_load_platform(hass, "binary_sensor", DOMAIN, {}, conf))

# ----------------------------------------------------------------------------
# Setup platforms (based on the list of entity types in PLATFORMS defined above)
# This calls the async_setup method in each of your entity type files.
# ----------------------------------------------------------------------------
for platform in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(config_entry, platform)
)

# ----------------------------------------------------------------------------
# Setup global services
# This can be done here but included in a seperate file for ease of reading.
# See also switch.py for entity services examples
# ----------------------------------------------------------------------------
# TODO: ExampleServicesSetup(hass, config_entry)

# Return true to denote a successful setup.
return True


# async def deprecated_async_setup(hass: core.HomeAssistant, config: ConfigType) -> bool:
# """Set up the platform.

# @NOTE: `config` is the full dict from `configuration.yaml`.

# :returns: A boolean to indicate that initialization was successful.
# """
# conf = config[DOMAIN]
# scan_interval = conf[CONF_SCAN_INTERVAL]
# device_id = conf[CONF_DEVICE_ID]

# inim = InimCloud(
# async_get_clientsession(hass),
# name="Inim",
# username=conf[CONF_USERNAME],
# password=conf[CONF_PASSWORD],
# client_id=conf[CONF_CLIENT_ID],
# )

# async def async_fetch_inim() -> InimResult:
# await inim.get_request_poll(device_id)
# _, _, res = await inim.get_devices_extended(device_id)
# return res

# coordinator = DataUpdateCoordinator(
# hass,
# _LOGGER,
# # Name of the data. For logging purposes.
# name=DOMAIN,
# update_method=async_fetch_inim,
# # Polling interval. Will only be polled if there are subscribers.
# update_interval=scan_interval,
# )

# # Fetch initial data so we have data when entities subscribe
# await coordinator.async_refresh()

# hass.data[DOMAIN] = {
# "conf": conf,
# "coordinator": coordinator,
# "inim_cloud_api": inim,
# }

# # hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
# # INIM_API: inim,
# # INIM_DEVICES: inim.devices(),
# # INIM_DEVICES_COORDINATOR: devices_coordinator,
# # INIM_NOTIFICATIONS_COORDINATOR: notifications_coordinator,
# # }

# hass.async_create_task(
# async_load_platform(hass, "alarm_control_panel", DOMAIN, {}, conf)
# )
# hass.async_create_task(async_load_platform(hass, "binary_sensor", DOMAIN, {}, conf))

# return True


async def _async_update_listener(hass: core.HomeAssistant, config_entry: ConfigEntry):
"""Handle config options update.
Reload the integration when the options change.
Called from our listener created above.
"""
await hass.config_entries.async_reload(config_entry.entry_id)


# class MyCoordinator(DataUpdateCoordinator):
# """My custom coordinator."""

Expand Down
7 changes: 7 additions & 0 deletions custom_components/inim/_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,10 @@ def _handle_coordinator_update(self) -> None:
if device := self._get_coordinator_device():
self._device = device
super()._handle_coordinator_update()


# see daikin integration, async_schedule_update_ha_state
# await self.async_device_update()
# self.async_write_ha_state()
# await self.async_update_ha_state()
# self.async_schedule_update_ha_state(True)
Loading

0 comments on commit 4e257ea

Please sign in to comment.