Skip to content

Commit

Permalink
Merge pull request #303 from asantaga/dev
Browse files Browse the repository at this point in the history
v3.1.6
  • Loading branch information
msp1974 authored Sep 27, 2022
2 parents cc6e2a8 + f4a3e83 commit ae78033
Show file tree
Hide file tree
Showing 16 changed files with 1,103 additions and 721 deletions.
653 changes: 344 additions & 309 deletions README.md

Large diffs are not rendered by default.

277 changes: 13 additions & 264 deletions custom_components/wiser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import logging
import json
import voluptuous as vol

from wiserHeatAPIv2.wiserhub import (
TEMP_MINIMUM,
TEMP_MAXIMUM,
Expand All @@ -19,32 +20,27 @@
)

from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_MODE,
CONF_HOST,
CONF_MINIMUM,
CONF_NAME,
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
)
from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import (
config_validation as cv,
device_registry as dr,
entity_registry as er,
)
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.entity_registry import (
async_entries_for_device,
)

from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.util import Throttle

from .websockets import async_register_websockets

from .frontend import WiserCardRegistration
from .helpers import get_device_name, get_identifier
from .services import async_setup_services
from .websockets import async_register_websockets

from .const import (
CONF_MOMENTS,
Expand All @@ -69,58 +65,10 @@
WISER_SERVICES
)

from .helpers import get_device_name, get_identifier

_LOGGER = logging.getLogger(__name__)

MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)

ATTR_FILENAME = "filename"
ATTR_TO_ENTITY_ID = "to_entity_id"
ATTR_SCHEDULE_ID = "schedule_id"
CONF_HUB_ID = "wiser_hub_id"
CONF_ENDPOINT = "wiser_hub_endpoint"
SERVICE_REMOVE_ORPHANED_ENTRIES = "remove_orphaned_entries"
SERVICE_OUTPUT_HUB_JSON = "output_hub_json"

GET_SCHEDULE_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_FILENAME, default=""): vol.Coerce(str),
}
)

SET_SCHEDULE_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_FILENAME): vol.Coerce(str),
}
)

COPY_SCHEDULE_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(ATTR_TO_ENTITY_ID): cv.entity_ids,
}
)

ASSIGN_SCHEDULE_SCHEMA = vol.Schema(
{
vol.Optional(ATTR_ENTITY_ID): cv.entity_id,
vol.Optional(ATTR_SCHEDULE_ID): vol.Coerce(int),
vol.Required(ATTR_TO_ENTITY_ID): cv.entity_ids,
}
)

SET_DEVICE_MODE_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_MODE): vol.Coerce(str),
}
)

SELECT_HUB_SCHEMA = vol.All(vol.Schema({vol.Required(CONF_HUB_ID): str}))

CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.All(
Expand Down Expand Up @@ -205,172 +153,8 @@ async def async_setup_entry(hass, config_entry):
# Setup websocket services for frontend cards
await async_register_websockets(hass, data)

# Initialise global services
def get_entity_from_entity_id(entity: str):
"""Get wiser entity from entity_id"""
domain = entity.split(".", 1)[0]
entity_comp = hass.data.get("entity_components", {}).get(domain)
if entity_comp:
return entity_comp.get_entity(entity)
return None

@callback
def get_schedule(service_call):
"""Handle the service call."""
entity_ids = service_call.data[ATTR_ENTITY_ID]
for entity_id in entity_ids:
filename = (
service_call.data[ATTR_FILENAME]
if service_call.data[ATTR_FILENAME] != ""
else (hass.config.config_dir + "/schedules/schedule_" + entity_id.split(".", 1)[1] + ".yaml")
)
entity = get_entity_from_entity_id(entity_id)
if entity:
if hasattr(entity, "get_schedule"):
getattr(entity, "get_schedule")(filename)
else:
_LOGGER.error(f"Cannot save schedule from entity {entity_id}. Please see integration instructions for entities to choose")
else:
_LOGGER.error(f"Invalid entity. {entity_id} does not exist in this integration")

@callback
def set_schedule(service_call):
"""Handle the service call."""
entity_ids = service_call.data[ATTR_ENTITY_ID]
for entity_id in entity_ids:
filename = service_call.data[ATTR_FILENAME]
entity = get_entity_from_entity_id(entity_id)
if entity:
if hasattr(entity, "set_schedule"):
getattr(entity, "set_schedule")(filename)
else:
_LOGGER.error(f"Cannot set schedule for entity {entity_id}. Please see integration instructions for entities to choose")
else:
_LOGGER.error(f"Invalid entity. {entity_id} does not exist in this integration")

@callback
def copy_schedule(service_call):
"""Handle the service call"""
entity_id = service_call.data[ATTR_ENTITY_ID]
to_entity_ids = service_call.data[ATTR_TO_ENTITY_ID]
for to_entity_id in to_entity_ids:
from_entity = get_entity_from_entity_id(entity_id)
to_entity = get_entity_from_entity_id(to_entity_id)

if from_entity and to_entity:
# Check from entity is a schedule entity
if hasattr(from_entity, "copy_schedule"):
getattr(from_entity, "copy_schedule")(to_entity)
else:
_LOGGER.error(f"Cannot copy schedule from entity {from_entity.name}. Please see integration instructions for entities to choose")
else:
_LOGGER.error(f"Invalid entity - {entity_id if not from_entity else ''}{' and ' if not from_entity and not to_entity else ''}{to_entity_id if not to_entity else ''} does not exist in this integration")
return False

@callback
def assign_schedule(service_call):
"""Handle the service call"""
entity_id = service_call.data.get(ATTR_ENTITY_ID)
schedule_id = service_call.data.get(ATTR_SCHEDULE_ID)
to_entity_ids = service_call.data[ATTR_TO_ENTITY_ID]

if entity_id:
# Assign schedule from this entity to another
for to_entity_id in to_entity_ids:
from_entity = get_entity_from_entity_id(entity_id)
to_entity = get_entity_from_entity_id(to_entity_id)

if from_entity and to_entity:
if hasattr(from_entity, "assign_schedule_to_another_entity"):
getattr(from_entity, "assign_schedule_to_another_entity")(to_entity)
else:
_LOGGER.error(f"Cannot assign schedule from entity {from_entity.name}. Please see integration instructions for entities to choose")
else:
_LOGGER.error(f"Invalid entity - {entity_id if not from_entity else ''}{' and ' if not from_entity and not to_entity else ''}{to_entity_id if not to_entity else ''} does not exist in this integration")
elif schedule_id:
# Assign scheduel with id to this entity
for to_entity_id in to_entity_ids:
to_entity = get_entity_from_entity_id(to_entity_id)
if to_entity:
if hasattr(to_entity, "assign_schedule_by_id"):
getattr(to_entity, "assign_schedule_by_id")(schedule_id)
else:
_LOGGER.error(f"Cannot assign schedule to entity {to_entity.name}. Please see integration instructions for entities to choose")
else:
# Create default schedule and assign to entity
for to_entity_id in to_entity_ids:
entity = get_entity_from_entity_id(to_entity_id)
if hasattr(entity, "create_schedule"):
getattr(entity, "create_schedule")()
else:
_LOGGER.error(f"Cannot assign schedule to entity {to_entity.name}. Please see integration instructions for entities to choose")

@callback
def set_device_mode(service_call):
"""Handle the service call."""
entity_ids = service_call.data[ATTR_ENTITY_ID]
mode = service_call.data[ATTR_MODE]
for entity_id in entity_ids:
entity = get_entity_from_entity_id(entity_id)
if entity:
if hasattr(entity, "set_mode"):
if mode.lower() in [option.lower() for option in entity._options]:
getattr(entity, "set_mode")(mode)
else:
_LOGGER.error(F"{mode} is not a valid mode for this device. Options are {entity._options}")
else:
_LOGGER.error(f"Cannot set mode for entity {entity_id}. Please see integration instructions for entities to choose")
else:
_LOGGER.error(f"Invalid entity. {entity_id} does not exist in this integration")

@callback
def remove_orphaned_entries_service(service):
for entry_id in hass.data[DOMAIN]:
hass.async_create_task(
data.async_remove_orphaned_entries(entry_id, service.data[CONF_HUB_ID])
)

hass.services.async_register(
DOMAIN,
WISER_SERVICES["SERVICE_GET_SCHEDULE"],
get_schedule,
schema=GET_SCHEDULE_SCHEMA,
)

hass.services.async_register(
DOMAIN,
WISER_SERVICES["SERVICE_SET_SCHEDULE"],
set_schedule,
schema=SET_SCHEDULE_SCHEMA,
)

hass.services.async_register(
DOMAIN,
WISER_SERVICES["SERVICE_COPY_SCHEDULE"],
copy_schedule,
schema=COPY_SCHEDULE_SCHEMA,
)

hass.services.async_register(
DOMAIN,
WISER_SERVICES["SERVICE_ASSIGN_SCHEDULE"],
assign_schedule,
schema=ASSIGN_SCHEDULE_SCHEMA,
)

hass.services.async_register(
DOMAIN,
WISER_SERVICES["SERVICE_SET_DEVICE_MODE"],
set_device_mode,
schema=SET_DEVICE_MODE_SCHEMA,
)

hass.services.async_register(
DOMAIN,
SERVICE_REMOVE_ORPHANED_ENTRIES,
remove_orphaned_entries_service,
schema=SELECT_HUB_SCHEMA,
)
# Setup services
await async_setup_services(hass, data)

# Add hub as device
await data.async_update_device_registry()
Expand All @@ -391,6 +175,12 @@ async def _async_update_listener(hass, config_entry):
"""Handle options update."""
await hass.config_entries.async_reload(config_entry.entry_id)

async def async_remove_config_entry_device(hass, config_entry, device_entry) -> bool:
"""Delete device if not entities"""
if device_entry.model == "Controller":
_LOGGER.error("You cannot delete the Wiser Controller device via the device delete method. Please remove the integration instead.")
return False
return True

async def async_unload_entry(hass, config_entry):
"""
Expand All @@ -410,7 +200,6 @@ async def async_unload_entry(hass, config_entry):

# Deregister services
_LOGGER.debug("Unregister Wiser Services")
hass.services.async_remove(DOMAIN, SERVICE_REMOVE_ORPHANED_ENTRIES)
hass.services.async_remove(DOMAIN, WISER_SERVICES["SERVICE_GET_SCHEDULE"])
hass.services.async_remove(DOMAIN, WISER_SERVICES["SERVICE_SET_SCHEDULE"])
hass.services.async_remove(DOMAIN, WISER_SERVICES["SERVICE_COPY_SCHEDULE"])
Expand Down Expand Up @@ -516,43 +305,3 @@ async def async_update_device_registry(self):
model=self.wiserhub.system.model,
sw_version=self.wiserhub.system.firmware_version,
)

@callback
async def async_remove_orphaned_entries(self, entry_id, wiser_hub_id: str):
"""Remove orphaned Wiser entries from device registry"""
api = self._hass.data[DOMAIN][entry_id]["data"]

if api.wiserhub.system.name == wiser_hub_id:
_LOGGER.info(f"Removing orphaned devices for {wiser_hub_id}")

device_registry = dr.async_get(self._hass)
entity_registry = er.async_get(self._hass)

devices_to_be_removed = []

#Get list of all devices for integration
all_devices = [
entry
for entry in device_registry.devices.values()
if entry_id in entry.config_entries
]

# Don't remove the Gateway host entry
wiser_hub = device_registry.async_get_device(
connections={(CONNECTION_NETWORK_MAC, api.wiserhub.system.network.mac_address)},
identifiers={(DOMAIN, api.unique_id)},
)
devices_to_be_removed = [ device.id for device in all_devices if device.id != wiser_hub.id ]

# Remove devices that don't belong to any entity
for device_id in devices_to_be_removed:
if (
len(
async_entries_for_device(
entity_registry, device_id, include_disabled_entities=True
)
)
== 0
):
_LOGGER.info(f"Removed {device_id}")
device_registry.async_remove_device(device_id)
12 changes: 7 additions & 5 deletions custom_components/wiser/button.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@

from .const import (
ATTR_TIME_PERIOD,
DATA,
DEFAULT_BOOST_TEMP_TIME,
DOMAIN,
MANUFACTURER
MANUFACTURER,
WISER_SERVICES
)
from .climate import HVAC_MODE_HASS_TO_WISER
from .helpers import get_device_name, get_unique_id, get_identifier

import voluptuous as vol
from homeassistant.components.button import ButtonEntity
from homeassistant.components.climate.const import HVAC_MODE_AUTO
from homeassistant.core import callback
from homeassistant.helpers import entity_platform
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.util import dt as dt_util

Expand Down Expand Up @@ -42,7 +46,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):

async_add_entities(wiser_buttons, True)


class WiserButton(ButtonEntity):
def __init__(self, data, name = "Button"):
"""Initialize the sensor."""
Expand Down Expand Up @@ -141,7 +144,6 @@ async def async_press(self):
def icon(self):
return "mdi:water-plus"


class WiserCancelHotWaterOverridesButton(WiserButton):
def __init__(self, data):
super().__init__(data, "Cancel Hot Water Overrides")
Expand Down
Loading

0 comments on commit ae78033

Please sign in to comment.