Skip to content

Commit

Permalink
Merge pull request #26 from MislavMandaric/feat/schedule-backend
Browse files Browse the repository at this point in the history
Support for scheduler-card
  • Loading branch information
MislavMandaric authored Dec 2, 2021
2 parents 7ce9928 + 6b940d4 commit 76a1bb7
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 1 deletion.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ Code template was mainly taken from [@Ludeeus](https://github.com/ludeeus)'s [in

This integration is a complete rewrite of [@pjmaenh](https://github.com/pjmaenh)'s original [Vaillant integration](https://github.com/pjmaenh/home-assistant-vaillant).

Thanks to [@philippelt](https://github.com/philippelt), [@jabesq](https://github.com/jabesq), [@samueldumont](https://github.com/samueldumont), [@jabesq](https://github.com/jabesq), [@pjmaenh](https://github.com/pjmaenh) and [@superbunika](https://github.com/superbunika) for providing many details of the underlying API, which this component uses.


[maintainer]: https://github.com/MislavMandaric
[maintainer-shield]: https://img.shields.io/badge/maintainer-%40MislavMandaric-blue.svg?style=for-the-badge
Expand Down
4 changes: 4 additions & 0 deletions custom_components/vaillant_vsmart/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@
)
from .entity import VaillantCoordinator

from .websockets import async_register_websockets


async def async_setup(hass: HomeAssistant, config: Config) -> bool:
"""Set up Vaillant vSMART component."""

hass.data.setdefault(DOMAIN, {})

await async_register_websockets(hass)

return True


Expand Down
2 changes: 1 addition & 1 deletion custom_components/vaillant_vsmart/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@

# Configuration and options
CONF_APP_VERSION = "app_version"
CONF_USER_PREFIX = "user_prefix"
CONF_USER_PREFIX = "user_prefix"
82 changes: 82 additions & 0 deletions custom_components/vaillant_vsmart/schedule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from datetime import datetime, timedelta

from vaillant_netatmo_api.thermostat import Program, TimeSlot


def map_program_to_schedule(
schedule_entity_id: str, profile_entity_id: str, program: Program
) -> dict:
daily_slots = program.get_timeslots_for_today()

return {
"schedule_id": program.id,
"weekdays": ["daily"],
"timeslots": map_timetable_to_timeslots(profile_entity_id, daily_slots),
"repeat_type": "repeat",
"name": program.name,
"enabled": program.selected,
"next_entries": map_timetable_to_next_entries(daily_slots),
"timestamps": map_timetable_to_timestamps(daily_slots),
"entity_id": schedule_entity_id,
"tags": [],
}


def map_timetable_to_timestamps(timetable: list[TimeSlot]) -> list[str]:
timestamps = []

today = datetime.now().date()
tomorrow = today + timedelta(days=1)

for time_slot in timetable:
d = datetime.combine(today, time_slot.time)

if time_slot.is_already_started:
d = datetime.combine(tomorrow, time_slot.time)

timestamps.append(d.isoformat())

return timestamps


def map_timetable_to_next_entries(timetable: list[TimeSlot]) -> list[int]:
previous_entries = []
next_entries = []

for i, time_slot in enumerate(timetable):
if time_slot.is_already_started:
previous_entries.append(i)
else:
next_entries.append(i)

return next_entries + previous_entries


def map_timetable_to_timeslots(
profile_entity_id: str, timetable: list[TimeSlot]
) -> list[dict]:
r = []

for i, time_slot in enumerate(timetable):
next_time_slot = timetable[(i + 1) % len(timetable)]
r.append(
{
"start": format_time(time_slot),
"stop": format_time(next_time_slot),
"actions": [
{
"service": "select.select_option",
"entity_id": profile_entity_id,
"service_data": {"option": time_slot.id.name},
}
],
}
)

return r


def format_time(time_slot: TimeSlot) -> str:
"""Returns a formatted start time for the time slot."""

return time_slot.time.isoformat()
122 changes: 122 additions & 0 deletions custom_components/vaillant_vsmart/websockets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import logging

import voluptuous as vol
from homeassistant.helpers import config_validation as cv
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.core import HomeAssistant
from homeassistant.components.websocket_api import (
decorators,
ActiveConnection,
)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_registry import EntityRegistry

from vaillant_netatmo_api.thermostat import Program

from .const import DOMAIN, SWITCH, SELECT
from .entity import VaillantCoordinator
from .schedule import map_program_to_schedule


_LOGGER = logging.getLogger(__name__)


@decorators.websocket_command(
{
vol.Required("type"): f"{DOMAIN}",
}
)
@decorators.async_response
async def websocket_get_schedules(
hass: HomeAssistant,
connection: ActiveConnection,
msg: dict,
) -> None:
"""Publish scheduler list data."""

er: EntityRegistry = await hass.helpers.entity_registry.async_get_registry()

schedule: list[dict] = []
for entry_id in hass.data[DOMAIN].keys():
coordinator: VaillantCoordinator = hass.data[DOMAIN][entry_id]
for program in coordinator.data.programs.values():
schedule_entity_id = er.async_get_entity_id(SWITCH, DOMAIN, program.id)
profile_entity_id = er.async_get_entity_id(SELECT, DOMAIN, program.id)
schedule_item = map_program_to_schedule(
schedule_entity_id, profile_entity_id, program
)
schedule.append(schedule_item)

connection.send_result(msg["id"], schedule)


@decorators.websocket_command(
{
vol.Required("type"): f"{DOMAIN}/item",
vol.Required("schedule_id"): cv.string,
}
)
@decorators.async_response
async def websocket_get_schedule_item(
hass: HomeAssistant,
connection: ActiveConnection,
msg: dict,
) -> None:
"""Publish scheduler list data."""

er: EntityRegistry = await hass.helpers.entity_registry.async_get_registry()

for entry_id in hass.data[DOMAIN].keys():
coordinator: VaillantCoordinator = hass.data[DOMAIN][entry_id]

program = coordinator.data.programs.get(msg["schedule_id"])
if program is not None:
schedule_entity_id = er.async_get_entity_id(SWITCH, DOMAIN, program.id)
profile_entity_id = er.async_get_entity_id(SELECT, DOMAIN, program.id)
schedule_item = map_program_to_schedule(
schedule_entity_id, profile_entity_id, program
)
connection.send_result(msg["id"], schedule_item)
break


@decorators.websocket_command(
{
vol.Required("type"): f"{DOMAIN}_updated",
}
)
@decorators.async_response
async def websocket_schedule_item_updated(
hass: HomeAssistant,
connection: ActiveConnection,
msg: dict,
) -> None:
"""Publish scheduler list data."""

connection.send_result(msg["id"])


@decorators.websocket_command(
{
vol.Required("type"): f"{DOMAIN}/tags",
}
)
@decorators.async_response
async def websocket_get_tags(
hass: HomeAssistant,
connection: ActiveConnection,
msg: dict,
) -> None:
"""Publish tag list data."""

connection.send_result(msg["id"], [])


async def async_register_websockets(hass: HomeAssistant) -> None:
hass.components.websocket_api.async_register_command(websocket_get_schedules)
hass.components.websocket_api.async_register_command(websocket_get_schedule_item)
hass.components.websocket_api.async_register_command(
websocket_schedule_item_updated
)
hass.components.websocket_api.async_register_command(websocket_get_tags)
2 changes: 2 additions & 0 deletions info.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ Code template was mainly taken from [@Ludeeus](https://github.com/ludeeus)'s [in

This integration is a complete rewrite of [@pjmaenh](https://github.com/pjmaenh)'s original [Vaillant integration](https://github.com/pjmaenh/home-assistant-vaillant).

Thanks to [@philippelt](https://github.com/philippelt), [@jabesq](https://github.com/jabesq), [@samueldumont](https://github.com/samueldumont), [@jabesq](https://github.com/jabesq), [@pjmaenh](https://github.com/pjmaenh) and [@superbunika](https://github.com/superbunika) for providing many details of the underlying API, which this component uses.


[maintainer]: https://github.com/MislavMandaric
[maintainer-shield]: https://img.shields.io/badge/maintainer-%40MislavMandaric-blue.svg?style=for-the-badge
Expand Down

0 comments on commit 76a1bb7

Please sign in to comment.