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

Added support for ConfigFlow #7

Merged
merged 3 commits into from
Aug 5, 2024
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
20 changes: 0 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,6 @@ Custom Component/Integration for controlling Inim alarm through [Home Assistant]

TODO

### Manual Installation

You can manually install as a custom component on your Home Assistant installation.

Follow these steps:

* In Home Assistant's config directory, you need to create a `custom_components` and copy all the content of the `custom_components/inim` folder.

* Open your `config/configuration.yaml` file and be sure to add - at the least - the following lines:
```yaml
inim:
password: !secret inim_alarm_password
username: !secret inim_alarm_username
client_id: "123456"
device_id: "789012"
```
for further detail look at `config/configuration.yam` present in this repository.

* Restart Home Assistant

## Configuration

TODO
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
174 changes: 91 additions & 83 deletions custom_components/inim/__init__.py
Original file line number Diff line number Diff line change
@@ -1,97 +1,67 @@
"""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

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
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
),
}
)
PLATFORMS: list[Platform] = [
Platform.BINARY_SENSOR,
Platform.ALARM_CONTROL_PANEL,
]

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,
)

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

async def async_setup(hass: core.HomeAssistant, config: ConfigType) -> bool:
"""Set up the platform.
coordinator: DataUpdateCoordinator
inim_cloud_api: InimCloud
cancel_update_listener: Callable

@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]
async def async_setup_entry(
hass: core.HomeAssistant, config_entry: ConfigEntry
) -> bool:
"""Set up Example Integration from a config entry."""

hass.data.setdefault(DOMAIN, {})

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 = InimCloud(
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 +74,68 @@ 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,
# }
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.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 _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)
55 changes: 33 additions & 22 deletions custom_components/inim/alarm_control_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
AlarmControlPanelEntity,
AlarmControlPanelEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
Expand All @@ -27,24 +28,28 @@
DataUpdateCoordinator,
)

from .const import CONF_DEVICE_ID, CONF_SCENARIOS, DOMAIN
from .const import CONF_DEVICE_ID, CONF_PANELS, CONF_SCENARIOS, DOMAIN

_LOGGER = logging.getLogger(__name__)

CONST_ALARM_CONTROL_PANEL_NAME = "Alarm Panel"
CONST_MANUFACTURER = "Inim"


async def async_setup_platform(
async def async_setup_entry(
hass: HomeAssistant,
config,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
discovery_info=None,
):
"""Setups the sensor platform."""
"""Set up the Alarm Control Panel."""
# This gets the DataUpdateCoordinator from hass.data as specified in your __init__.py
coordinator: DataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
].coordinator

coordinator = hass.data[DOMAIN]["coordinator"]
inim_cloud_api = hass.data[DOMAIN]["inim_cloud_api"]
conf = hass.data[DOMAIN]["conf"]
inim_cloud_api = hass.data[DOMAIN][config_entry.entry_id].inim_cloud_api

panels = config_entry.data[CONF_PANELS]
device_id = config_entry.data[CONF_DEVICE_ID]

# Fetch initial data so we have data when entities subscribe
#
Expand All @@ -56,17 +61,21 @@ async def async_setup_platform(
#
# await coordinator.async_config_entry_first_refresh()
# devices: InimResult = coordinator.data

_LOGGER.warning(
"INIM alarm panel was created/updated for the following panels: %s", panels
)
alarm_control_panels = [
InimAlarmControlPanelEntity(
coordinator,
inim_cloud_api,
conf[CONF_DEVICE_ID],
conf[CONF_SCENARIOS],
"alarm_control_panel",
device_id,
panel_conf,
"0.0.1",
)
for panel_conf in panels
]

# Create the alarm control panel
async_add_entities(alarm_control_panels, update_before_add=True)


Expand All @@ -77,6 +86,7 @@ class InimAlarmControlPanelEntity(CoordinatorEntity, AlarmControlPanelEntity):
AlarmControlPanelEntityFeature.ARM_NIGHT
| AlarmControlPanelEntityFeature.ARM_AWAY
| AlarmControlPanelEntityFeature.ARM_HOME
| AlarmControlPanelEntityFeature.ARM_VACATION
)
_attr_has_entity_name = True
_attr_name = None
Expand All @@ -86,24 +96,25 @@ def __init__(
coordinator: DataUpdateCoordinator, # TODO: provide proper Generic type ie: # coordinator: DataUpdateCoordinator[InimResult],
inim: InimCloud,
device_id: str,
scenarios: Mapping[str, int],
unique_id: str,
panel, # TODO add type
# scenarios: Mapping[str, int],
# unique_id: str,
version: str,
):
"""Initialize the alarm control panel."""

super().__init__(coordinator) # , context=zone.ZoneId)

panel_name = panel["panel_name"]
self._client = inim
self._device_id = device_id
self._scenarios = scenarios

self._attr_unique_id = unique_id
self._scenarios = panel[CONF_SCENARIOS]
self._attr_unique_id = panel["unique_id"]
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, unique_id)},
name=f"{self._client.name} {CONST_ALARM_CONTROL_PANEL_NAME}",
manufacturer="Inim",
model=CONST_ALARM_CONTROL_PANEL_NAME,
identifiers={(DOMAIN, self._attr_unique_id)},
name=panel_name,
manufacturer=CONST_MANUFACTURER,
model=panel_name,
sw_version=version,
)

Expand Down
Loading