Skip to content

Commit

Permalink
improve data validation, improve updating entity_id from name
Browse files Browse the repository at this point in the history
  • Loading branch information
niels committed Dec 22, 2020
1 parent 49bed12 commit 243204c
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 97 deletions.
9 changes: 8 additions & 1 deletion custom_components/scheduler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,16 @@ async def async_edit_schedule(self, schedule_id: str, data: dict):
_LOGGER.debug("async_edit_schedule")
if schedule_id not in self.hass.data[DOMAIN]["schedules"]:
return
item = self.async_get_schedule(schedule_id)

if "name" in data and item["name"] != data["name"]:
data["name"] = data["name"].strip()
elif "name" in data:
del data["name"]

entry = self.store.async_update_schedule(schedule_id, data)
entity = self.hass.data[DOMAIN]["schedules"][schedule_id]
if "name" in data and data["name"]:
if "name" in data:
entity_registry = await get_entity_registry(self.hass)
entity_registry.async_remove(entity.entity_id)
self._create_schedule_handler(entry)
Expand Down
88 changes: 1 addition & 87 deletions custom_components/scheduler/const.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
"""Store constants."""
import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.helpers import config_validation as cv

VERSION = "3.0.0b"
VERSION = "3.0.0"

DOMAIN = "scheduler"
ENTITY_ID_FORMAT = DOMAIN + ".{}"
Expand All @@ -27,86 +24,3 @@
REPEAT_TYPE_REPEAT = "repeat"
REPEAT_TYPE_SINGLE = "single"
REPEAT_TYPE_PAUSE = "pause"

CONDITION_SCHEMA = vol.Schema(
{
vol.Required("entity_id"): cv.entity_id,
vol.Required("value"): vol.Any(int, float, str),
vol.Optional("attribute"): cv.string,
vol.Required("match_type"): vol.In(
[MATCH_TYPE_EQUAL, MATCH_TYPE_UNEQUAL, MATCH_TYPE_BELOW, MATCH_TYPE_ABOVE]
),
}
)

ACTION_SCHEMA = vol.Schema(
{
vol.Optional("entity_id"): cv.entity_id,
vol.Optional("service"): cv.entity_id,
vol.Optional("service_data"): dict,
}
)

TIMESLOT_SCHEMA = vol.Schema(
{
vol.Required("start"): cv.string,
vol.Optional("stop"): cv.string,
vol.Optional("conditions"): vol.All(
cv.ensure_list, vol.Length(min=1), [CONDITION_SCHEMA]
),
vol.Optional("condition_type"): vol.In(
[
CONDITION_TYPE_AND,
CONDITION_TYPE_OR,
]
),
vol.Required("actions"): vol.All(
cv.ensure_list, vol.Length(min=1), [ACTION_SCHEMA]
),
}
)

SCHEDULE_SCHEMA = vol.Schema(
{
vol.Required("weekdays"): vol.All(
cv.ensure_list,
vol.Unique(),
vol.Length(min=1),
[
vol.In(
[
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun",
DAY_TYPE_WORKDAY,
DAY_TYPE_WEEKEND,
DAY_TYPE_DAILY,
]
)
],
),
vol.Required("timeslots"): vol.All(
cv.ensure_list, vol.Length(min=1), [TIMESLOT_SCHEMA]
),
vol.Required("repeat_type"): vol.In(
[
REPEAT_TYPE_REPEAT,
REPEAT_TYPE_SINGLE,
REPEAT_TYPE_PAUSE,
]
),
vol.Optional("name"): cv.string,
}
)

SERVICE_RUN_ACTION = "run_action"
RUN_ACTION_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional("time"): cv.time
}
)
19 changes: 19 additions & 0 deletions custom_components/scheduler/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
import math
import re
import voluptuous as vol

import homeassistant.util.dt as dt_util
from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET
Expand Down Expand Up @@ -252,3 +253,21 @@ def has_overlapping_timeslot(
return i

return None


def validate_time(time):
res = OffsetTimePattern.match(time)
if not res:
if dt_util.parse_time(time):
return time
else:
raise vol.Invalid("Invalid time entered: {}".format(time))
else:
if res.group(1) not in [SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET]:
raise vol.Invalid("Invalid time entered: {}".format(time))
elif res.group(2) not in ['+', '-']:
raise vol.Invalid("Invalid time entered: {}".format(time))
elif not dt_util.parse_time(res.group(3)):
raise vol.Invalid("Invalid time entered: {}".format(time))
else:
return time
19 changes: 14 additions & 5 deletions custom_components/scheduler/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
import copy
import datetime
import logging
import voluptuous as vol

from homeassistant.components.switch import DOMAIN as PLATFORM
from homeassistant.helpers import entity_platform
from homeassistant.helpers import (entity_platform, config_validation as cv)
from homeassistant.const import (
STATE_ALARM_TRIGGERED as STATE_TRIGGERED,
STATE_OFF,
STATE_ON,
SUN_EVENT_SUNRISE,
SUN_EVENT_SUNSET
SUN_EVENT_SUNSET,
ATTR_ENTITY_ID,
)
from homeassistant.core import callback
from homeassistant.helpers.device_registry import async_entries_for_config_entry
Expand Down Expand Up @@ -38,8 +40,6 @@
REPEAT_TYPE_PAUSE,
REPEAT_TYPE_SINGLE,
VERSION,
SERVICE_RUN_ACTION,
RUN_ACTION_SCHEMA,
)
from .helpers import calculate_next_start_time, has_overlapping_timeslot
from .migrate import migrate_old_entity
Expand All @@ -49,6 +49,15 @@
_LOGGER = logging.getLogger(__name__)


SERVICE_RUN_ACTION = "run_action"
RUN_ACTION_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional("time"): cv.time
}
)


def entity_exists_in_hass(hass, entity_id):
"""Check that an entity exists."""
return hass.states.get(entity_id) is not None
Expand Down Expand Up @@ -98,7 +107,7 @@ def async_add_entity(schedule: ScheduleEntry):
schedule_id = schedule.schedule_id
name = schedule.name

if name:
if name and len(slugify(name)):
entity_id = "{}.schedule_{}".format(PLATFORM, slugify(name))
else:
entity_id = "{}.schedule_{}".format(PLATFORM, schedule_id)
Expand Down
100 changes: 96 additions & 4 deletions custom_components/scheduler/websockets.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,110 @@
import logging

import voluptuous as vol
from homeassistant.helpers import config_validation as cv
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.components import websocket_api
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv

from .const import DOMAIN, SCHEDULE_SCHEMA
from .helpers import validate_time

from .const import (
DOMAIN,
MATCH_TYPE_EQUAL,
MATCH_TYPE_UNEQUAL,
MATCH_TYPE_BELOW,
MATCH_TYPE_ABOVE,
CONDITION_TYPE_AND,
CONDITION_TYPE_OR,
DAY_TYPE_WORKDAY,
DAY_TYPE_WEEKEND,
DAY_TYPE_DAILY,
REPEAT_TYPE_REPEAT,
REPEAT_TYPE_SINGLE,
REPEAT_TYPE_PAUSE,
)

_LOGGER = logging.getLogger(__name__)
EVENT = "schedules_updated"


CONDITION_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required("value"): vol.Any(int, float, str),
vol.Optional("attribute"): cv.string,
vol.Required("match_type"): vol.In(
[MATCH_TYPE_EQUAL, MATCH_TYPE_UNEQUAL, MATCH_TYPE_BELOW, MATCH_TYPE_ABOVE]
),
}
)

ACTION_SCHEMA = vol.Schema(
{
vol.Optional(ATTR_ENTITY_ID): cv.entity_id,
vol.Optional("service"): cv.entity_id,
vol.Optional("service_data"): dict,
}
)

TIMESLOT_SCHEMA = vol.Schema(
{
vol.Required("start"): validate_time,
vol.Optional("stop"): validate_time,
vol.Optional("conditions"): vol.All(
cv.ensure_list, vol.Length(min=1), [CONDITION_SCHEMA]
),
vol.Optional("condition_type"): vol.In(
[
CONDITION_TYPE_AND,
CONDITION_TYPE_OR,
]
),
vol.Required("actions"): vol.All(
cv.ensure_list, vol.Length(min=1), [ACTION_SCHEMA]
),
}
)

SCHEDULE_SCHEMA = vol.Schema(
{
vol.Required("weekdays"): vol.All(
cv.ensure_list,
vol.Unique(),
vol.Length(min=1),
[
vol.In(
[
"mon",
"tue",
"wed",
"thu",
"fri",
"sat",
"sun",
DAY_TYPE_WORKDAY,
DAY_TYPE_WEEKEND,
DAY_TYPE_DAILY,
]
)
],
),
vol.Required("timeslots"): vol.All(
cv.ensure_list, vol.Length(min=1), [TIMESLOT_SCHEMA]
),
vol.Required("repeat_type"): vol.In(
[
REPEAT_TYPE_REPEAT,
REPEAT_TYPE_SINGLE,
REPEAT_TYPE_PAUSE,
]
),
vol.Optional("name"): cv.string,
}
)


class SchedulesAddView(HomeAssistantView):
"""Login to Home Assistant cloud."""

Expand Down Expand Up @@ -58,7 +150,7 @@ async def post(self, request, data):
"""Handle config update request."""
hass = request.app["hass"]
coordinator = hass.data[DOMAIN]["coordinator"]
coordinator.async_delete_schedule(data["schedule_id"])
await coordinator.async_delete_schedule(data["schedule_id"])
return self.json({"success": True})


Expand Down

0 comments on commit 243204c

Please sign in to comment.