created stages dir
This commit is contained in:
48
apps/stages/bar.yaml
Normal file
48
apps/stages/bar.yaml
Normal file
@@ -0,0 +1,48 @@
|
||||
bar_lights:
|
||||
module: light
|
||||
class: StagedLight
|
||||
stages:
|
||||
- start: '03:00 am'
|
||||
scene:
|
||||
light.bar:
|
||||
state: on
|
||||
color_temp_kelvin: 4500
|
||||
brightness: 25
|
||||
- start: '09:00 am'
|
||||
scene:
|
||||
light.bar:
|
||||
state: on
|
||||
color_temp_kelvin: 3500
|
||||
brightness: 100
|
||||
- start: '13:00'
|
||||
scene:
|
||||
light.bar:
|
||||
state: on
|
||||
color_temp_kelvin: 2500
|
||||
brightness: 150
|
||||
- start: 'sunset'
|
||||
scene:
|
||||
light.bar:
|
||||
state: on
|
||||
color_temp_kelvin: 2202
|
||||
brightness: 100
|
||||
light.server_lamp:
|
||||
state: on
|
||||
rgb_color: [255, 112, 86]
|
||||
brightness: 175
|
||||
- start: '10:00 pm'
|
||||
scene:
|
||||
light.bar:
|
||||
state: on
|
||||
color_temp_kelvin: 2202
|
||||
brightness: 75
|
||||
light.server_lamp:
|
||||
state: on
|
||||
rgb_color: [255, 112, 86]
|
||||
brightness: 75
|
||||
- start: '11:30 pm'
|
||||
scene:
|
||||
light.bar:
|
||||
state: on
|
||||
color_temp_kelvin: 2202
|
||||
brightness: 30
|
||||
81
apps/stages/light.py
Normal file
81
apps/stages/light.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from collections.abc import Generator
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from itertools import count
|
||||
from itertools import cycle
|
||||
from itertools import pairwise
|
||||
from typing import Any
|
||||
|
||||
from appdaemon.plugins.hass import Hass
|
||||
from pydantic import TypeAdapter
|
||||
from stages import Stage
|
||||
|
||||
|
||||
class StagedControlEvent(str, Enum):
|
||||
ACTIVATE = 'activate'
|
||||
DEACTIVATE = 'deactivate'
|
||||
|
||||
|
||||
class StagedLight(Hass):
|
||||
def initialize(self):
|
||||
self.set_log_level('DEBUG')
|
||||
self._type_adapter = TypeAdapter(list[Stage])
|
||||
self._stages = self._type_adapter.validate_python(self.args['stages'])
|
||||
|
||||
self.log(f'Initialized Motion Sensor with {len(self._stages)} stages')
|
||||
self.listen_event(self.handle_event, 'stage_control', app=self.name)
|
||||
self.activate()
|
||||
|
||||
### Stages
|
||||
|
||||
def _stage_starts(self) -> Generator[datetime]:
|
||||
for offset in count(start=-1):
|
||||
for stage in self._stages:
|
||||
dt = self.parse_datetime(stage.start, days_offset=offset, aware=True, today=True)
|
||||
dt = dt.replace(microsecond=0)
|
||||
yield dt
|
||||
|
||||
def start_pairs(self):
|
||||
"""Yield from an infinite progression of start and end times for the stages."""
|
||||
yield from pairwise(self._stage_starts())
|
||||
|
||||
def current_stage(self) -> Stage:
|
||||
for stage, (t1, t2) in zip(cycle(self._stages), self.start_pairs()):
|
||||
start, end = sorted([t1, t2])
|
||||
if self.now_is_between(start, end):
|
||||
self.log(f'Current stage start time: {stage.start}', level='DEBUG')
|
||||
stage.assign_start(start)
|
||||
return stage
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
def current_scene(self):
|
||||
return self.current_stage().scene_json()
|
||||
|
||||
### Events
|
||||
|
||||
def handle_event(self, event_type: str, data: dict[str, Any], **kwargs: Any) -> None:
|
||||
self.log(f'Event handler: {event_type}', level='DEBUG')
|
||||
stage = self.current_stage()
|
||||
start_time_str = stage.formatted_start('%I:%M %p')
|
||||
match data:
|
||||
case {'action': StagedControlEvent.ACTIVATE}:
|
||||
self.log(f'Activating current stage: {start_time_str}')
|
||||
self.activate(scene=stage.scene_json())
|
||||
case {'action': StagedControlEvent.DEACTIVATE}:
|
||||
self.log(f'Deactivating current stage: {start_time_str}')
|
||||
self.deactivate()
|
||||
case _:
|
||||
self.log(str(data), level='DEBUG')
|
||||
self.log(str(kwargs), level='DEBUG')
|
||||
|
||||
### Actions
|
||||
|
||||
def activate(self, scene: dict | None = None, **kwargs):
|
||||
scene = scene if scene is not None else self.current_scene()
|
||||
return self.call_service('scene/apply', entities=scene)
|
||||
|
||||
def deactivate(self, stage: Stage | None = None, **kwargs):
|
||||
stage = stage if stage is not None else self.current_stage()
|
||||
for entity in stage.scene:
|
||||
self.turn_off(entity)
|
||||
36
apps/stages/stages.py
Normal file
36
apps/stages/stages.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic import PrivateAttr
|
||||
from pydantic import field_serializer
|
||||
|
||||
|
||||
class EntityState(BaseModel, extra='allow'):
|
||||
state: bool = True
|
||||
color_temp_kelvin: int | None = None
|
||||
rgb_color: list[int] | None = None
|
||||
brightness: int | None = None
|
||||
|
||||
@field_serializer('state')
|
||||
def convert_state(self, val: Any):
|
||||
if val:
|
||||
return 'on'
|
||||
else:
|
||||
return 'off'
|
||||
|
||||
|
||||
class Stage(BaseModel):
|
||||
# start: Annotated[time, BeforeValidator(lambda v: parser(v).time())]
|
||||
start: str
|
||||
_start: datetime = PrivateAttr()
|
||||
scene: dict[str, EntityState]
|
||||
|
||||
def assign_start(self, dt: datetime):
|
||||
self._start = dt
|
||||
|
||||
def formatted_start(self, fmt: str) -> str:
|
||||
return self._start.strftime(fmt)
|
||||
|
||||
def scene_json(self):
|
||||
return self.model_dump(mode='json')['scene']
|
||||
44
apps/stages/upper_stairs.yaml
Normal file
44
apps/stages/upper_stairs.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
upper_stairs:
|
||||
module: light
|
||||
class: StagedLight
|
||||
stages:
|
||||
- start: '04:00'
|
||||
scene:
|
||||
light.bathroom_stairs:
|
||||
state: "on"
|
||||
color_temp_kelvin: 4000
|
||||
brightness: 30
|
||||
light.spire:
|
||||
state: "on"
|
||||
color_temp_kelvin: 4000
|
||||
brightness: 30
|
||||
- start: '07:00'
|
||||
scene:
|
||||
light.bathroom_stairs:
|
||||
state: "on"
|
||||
color_temp_kelvin: 4000
|
||||
brightness: 60
|
||||
light.spire:
|
||||
state: "on"
|
||||
color_temp_kelvin: 4000
|
||||
brightness: 60
|
||||
- start: 'sunset'
|
||||
scene:
|
||||
light.bathroom_stairs:
|
||||
state: "on"
|
||||
color_temp_kelvin: 2202
|
||||
brightness: 60
|
||||
light.spire:
|
||||
state: "on"
|
||||
color_temp_kelvin: 2202
|
||||
brightness: 60
|
||||
- start: '11:00'
|
||||
scene:
|
||||
light.bathroom_stairs:
|
||||
state: "on"
|
||||
color_temp_kelvin: 2202
|
||||
brightness: 40
|
||||
light.spire:
|
||||
state: "on"
|
||||
color_temp_kelvin: 2202
|
||||
brightness: 40
|
||||
Reference in New Issue
Block a user