Compare commits
17 Commits
2f52febf38
...
d03a5e8a19
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d03a5e8a19 | ||
|
|
699d314862 | ||
|
|
610c2b68e2 | ||
|
|
27db3be4a0 | ||
|
|
dac73a8398 | ||
|
|
3f6688821a | ||
|
|
25c529da60 | ||
|
|
9bbe28c1bb | ||
|
|
3a8a0e48d0 | ||
|
|
3ae7c2b8e9 | ||
|
|
01c6faa362 | ||
|
|
56ee0c3d3e | ||
|
|
657d01a724 | ||
|
|
e21eca4f42 | ||
|
|
61d5d99dee | ||
|
|
29759692b2 | ||
|
|
fbd60ab6ac |
@@ -1,6 +1,5 @@
|
|||||||
appdaemon:
|
appdaemon:
|
||||||
uvloop: True
|
uvloop: True
|
||||||
use_dictionary_unpacking: True
|
|
||||||
import_method: expert
|
import_method: expert
|
||||||
latitude: 30.250968
|
latitude: 30.250968
|
||||||
longitude: -97.748193
|
longitude: -97.748193
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
hello-world:
|
hello-world:
|
||||||
module: hello
|
module: hello
|
||||||
class: HelloWorld
|
class: HelloWorld
|
||||||
|
|
||||||
|
gone:
|
||||||
|
module: gone
|
||||||
|
class: Gone
|
||||||
|
entities:
|
||||||
|
- light.bar
|
||||||
|
- light.h6076
|
||||||
|
- light.h6076_2
|
||||||
|
|
||||||
|
blue-button:
|
||||||
|
module: media_button
|
||||||
|
class: Button
|
||||||
7
apps/drivers/moes.yaml
Normal file
7
apps/drivers/moes.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
moes_driver:
|
||||||
|
module: moes_pad
|
||||||
|
class: MoesBridge
|
||||||
|
|
||||||
|
moes_pad:
|
||||||
|
module: moes_pad
|
||||||
|
class: MoesPad
|
||||||
104
apps/drivers/moes_pad.py
Normal file
104
apps/drivers/moes_pad.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
from enum import Enum
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from appdaemon.adapi import ADAPI
|
||||||
|
from appdaemon.adbase import ADBase
|
||||||
|
|
||||||
|
|
||||||
|
class MoesButtons(int, Enum):
|
||||||
|
TOP_LEFT = 1
|
||||||
|
TOP_RIGHT = 2
|
||||||
|
BOTTOM_LEFT = 3
|
||||||
|
BOTTOM_RIGHT = 4
|
||||||
|
|
||||||
|
|
||||||
|
class MoesActions(str, Enum):
|
||||||
|
SINGLE_PRESS = 'single'
|
||||||
|
DOUBLE_PRESS = 'double'
|
||||||
|
LONG_PRESS = 'hold'
|
||||||
|
|
||||||
|
|
||||||
|
class MoesBridge(ADBase):
|
||||||
|
"""Class for an app that listens for state changes generated by 4 button Moes Pads and makes the events more
|
||||||
|
sensible with enums."""
|
||||||
|
|
||||||
|
adapi: ADAPI
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
self.adapi = self.get_ad_api()
|
||||||
|
self.log = self.adapi.log
|
||||||
|
self.event_handle = self.adapi.listen_state(self.handle_state_change)
|
||||||
|
|
||||||
|
def handle_state_change(self, entity: str, attribute: str, old: Any, new: Any, **kwargs: Any) -> None:
|
||||||
|
if new == '' or not new[0].isdigit():
|
||||||
|
return
|
||||||
|
|
||||||
|
domain, ent = entity.split('.', 1)
|
||||||
|
if domain == 'sensor' and ent.startswith('moes'):
|
||||||
|
button, action = new.split('_')
|
||||||
|
button = MoesButtons(int(button))
|
||||||
|
button_str = button.name.lower().replace('_', ' ')
|
||||||
|
action = MoesActions(action)
|
||||||
|
self.log(f'Moes Pad action detected: {action.value} on button {button_str}')
|
||||||
|
self.adapi.fire_event(
|
||||||
|
'moes_pad',
|
||||||
|
device=entity,
|
||||||
|
button=button,
|
||||||
|
action=action,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MoesPad(ADBase):
|
||||||
|
"""Specfic implementation of a Moes Pad."""
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
self.adapi = self.get_ad_api()
|
||||||
|
self.log = self.adapi.log
|
||||||
|
self.event_handle = self.adapi.listen_event(self.handle_moes_event, 'moes_pad')
|
||||||
|
|
||||||
|
self.bedroom_offs = ('bedroom_stick', 'bedroom_john', 'bedroom_sydney')
|
||||||
|
self.main_offs = ('bar', 'server_lamp', 'living_room_stick')
|
||||||
|
# self.set_many(self.main_offs, state='on')
|
||||||
|
|
||||||
|
def handle_moes_event(self, event_name: str, data: dict[str, Any], **kwargs: Any) -> None:
|
||||||
|
button = MoesButtons(data.get('button'))
|
||||||
|
action = MoesActions(data.get('action'))
|
||||||
|
self.log(f'Received Moes Pad event: {action} on button {button}')
|
||||||
|
|
||||||
|
match action:
|
||||||
|
case MoesActions.SINGLE_PRESS:
|
||||||
|
self.handle_single_press(button)
|
||||||
|
case MoesActions.DOUBLE_PRESS:
|
||||||
|
self.handle_double_press(button)
|
||||||
|
|
||||||
|
def handle_single_press(self, button: MoesButtons) -> None:
|
||||||
|
match button:
|
||||||
|
case MoesButtons.TOP_LEFT:
|
||||||
|
self.adapi.call_service('light/toggle', entity_id='light.bedroom_sydney')
|
||||||
|
case MoesButtons.TOP_RIGHT:
|
||||||
|
self.adapi.call_service('light/toggle', entity_id='light.bedroom_john')
|
||||||
|
case MoesButtons.BOTTOM_LEFT:
|
||||||
|
self.adapi.call_service('light/toggle', entity_id='light.bedroom_stick')
|
||||||
|
case MoesButtons.BOTTOM_RIGHT:
|
||||||
|
any_on = any(self.adapi.get_state(f'light.{e}') == 'on' for e in self.bedroom_offs)
|
||||||
|
scene = {'entities': {f'light.{e}': {'state': 'off' if any_on else 'on'}} for e in self.bedroom_offs}
|
||||||
|
self.adapi.call_service('scene/apply', **scene)
|
||||||
|
|
||||||
|
def handle_double_press(self, button: MoesButtons) -> None:
|
||||||
|
match button:
|
||||||
|
case MoesButtons.BOTTOM_RIGHT:
|
||||||
|
all_lights = [*self.bedroom_offs, *self.main_offs]
|
||||||
|
any_on = self.check_many(all_lights, state='on')
|
||||||
|
new_state = 'off' if any_on else 'on'
|
||||||
|
self.log(f'New state from double press: {new_state}')
|
||||||
|
self.set_many(self.bedroom_offs, state=new_state)
|
||||||
|
self.set_many(self.main_offs, state=new_state)
|
||||||
|
|
||||||
|
def set_many(self, lights: set[str], state: str = 'off'):
|
||||||
|
entity_ids = [f'light.{e}' if not e.startswith('light') else e for e in lights]
|
||||||
|
scene = {'entities': {eid: {'state': state} for eid in entity_ids}}
|
||||||
|
return self.adapi.call_service('scene/apply', **scene)
|
||||||
|
|
||||||
|
def check_many(self, lights: set[str], state: str = 'off') -> bool:
|
||||||
|
entity_ids = [f'light.{e}' if not e.startswith('light') else e for e in lights]
|
||||||
|
return any(self.adapi.get_state(eid) == state for eid in entity_ids)
|
||||||
27
apps/gone.py
Normal file
27
apps/gone.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from appdaemon.plugins.hass import Hass
|
||||||
|
|
||||||
|
|
||||||
|
class Gone(Hass):
|
||||||
|
def initialize(self):
|
||||||
|
people = self.get_state('person')
|
||||||
|
# self.log(json.dumps(people, indent=2))
|
||||||
|
|
||||||
|
self.log(list(people.keys()))
|
||||||
|
for person in self.get_state('person'):
|
||||||
|
self.listen_state(self.handle_state, entity_id=person, new='not_home')
|
||||||
|
|
||||||
|
self.log(f'No one home: {self.no_one_home()}')
|
||||||
|
|
||||||
|
def no_one_home(self) -> bool:
|
||||||
|
return all(state.get('state') != 'home' for state in self.get_state('person', copy=False).values())
|
||||||
|
|
||||||
|
def handle_state(self, entity: str, attribute: str, old: Any, new: Any, **kwargs: Any) -> None:
|
||||||
|
if self.no_one_home():
|
||||||
|
for ent in self.args['entities']:
|
||||||
|
try:
|
||||||
|
self.turn_off(ent)
|
||||||
|
except Exception:
|
||||||
|
self.log(f'Failed to turn off {ent}', level='ERROR')
|
||||||
|
continue
|
||||||
38
apps/media_button.py
Normal file
38
apps/media_button.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
from enum import Enum
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from appdaemon.plugins.hass import Hass
|
||||||
|
|
||||||
|
|
||||||
|
class ButtonPress(str, Enum):
|
||||||
|
SINGLE = 'single'
|
||||||
|
DOUBLE = 'double'
|
||||||
|
HOLD = 'hold'
|
||||||
|
|
||||||
|
|
||||||
|
class Button(Hass):
|
||||||
|
def initialize(self):
|
||||||
|
self.set_log_level('DEBUG')
|
||||||
|
self.listen_state(
|
||||||
|
self.handle_button,
|
||||||
|
entity_id='sensor.blue_lamp_button_action',
|
||||||
|
new=lambda s: s.strip() != '',
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle_button(self, entity: str, attribute: str, old: Any, new: Any, **kwargs: Any) -> None:
|
||||||
|
match new:
|
||||||
|
case ButtonPress.SINGLE:
|
||||||
|
self.log('Single press')
|
||||||
|
self.call_service('media_player/media_play_pause', entity_id='media_player.living_room_tv')
|
||||||
|
case ButtonPress.DOUBLE:
|
||||||
|
self.log('Double')
|
||||||
|
self.call_service(
|
||||||
|
'scene/apply',
|
||||||
|
entities={
|
||||||
|
'light.bar': {'state': 'on', 'brightness': 10},
|
||||||
|
'light.living_room_stick': {'state': 'on', 'brightness': 100},
|
||||||
|
'light.server_lamp': {'state': 'on', 'brightness': 25},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
case ButtonPress.HOLD:
|
||||||
|
self.log('Hold down')
|
||||||
@@ -69,15 +69,15 @@ class StagedLight(Hass):
|
|||||||
|
|
||||||
### Transitions
|
### Transitions
|
||||||
|
|
||||||
def schedule_transition_checks(self, **kwargs: Any):
|
def schedule_transition_checks(self, **_):
|
||||||
now = self.get_now()
|
now = self.get_now()
|
||||||
for stage in self._stages:
|
for stage in self._stages:
|
||||||
dt = self.parse_datetime(stage.start, aware=True, today=True)
|
dt = self.parse_datetime(stage.start, aware=True, today=True)
|
||||||
if dt > now:
|
if dt > now:
|
||||||
self.log(f'Scehduling transition at: {dt.isoformat()}', level='DEBUG')
|
self.log(f'Scehduling transition at: {dt.strftime("%I:%M %p")}', level='DEBUG')
|
||||||
self.run_at(self._check_transition, start=dt)
|
self.run_at(self._check_transition, start=dt)
|
||||||
|
|
||||||
def _check_transition(self, **kwargs: Any):
|
def _check_transition(self, **_):
|
||||||
self.log('Firing transition event', level='DEBUG')
|
self.log('Firing transition event', level='DEBUG')
|
||||||
self.fire_event(
|
self.fire_event(
|
||||||
'stage_control',
|
'stage_control',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
bar_lights:
|
main_floor:
|
||||||
module: light
|
module: light
|
||||||
class: StagedLight
|
class: StagedLight
|
||||||
activate-at-start: true
|
activate-at-start: true
|
||||||
@@ -33,19 +33,46 @@ bar_lights:
|
|||||||
light.bar:
|
light.bar:
|
||||||
state: on
|
state: on
|
||||||
color_temp_kelvin: 3000
|
color_temp_kelvin: 3000
|
||||||
brightness: 150
|
brightness: 75
|
||||||
light.server_lamp:
|
light.server_lamp:
|
||||||
state: off
|
state: off
|
||||||
|
- start: 'sunset - 60min'
|
||||||
|
scene:
|
||||||
|
light.bar:
|
||||||
|
state: on
|
||||||
|
color_temp_kelvin: 3000
|
||||||
|
brightness: 75
|
||||||
|
light.server_lamp:
|
||||||
|
state: on
|
||||||
|
brightness: 100
|
||||||
- start: 'sunset'
|
- 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
|
||||||
|
light.living_room_stick:
|
||||||
|
state: "on"
|
||||||
|
brightness: 150
|
||||||
|
effect: sunset
|
||||||
|
- start: 'sunset + 1:00'
|
||||||
scene:
|
scene:
|
||||||
light.bar:
|
light.bar:
|
||||||
state: on
|
state: on
|
||||||
color_temp_kelvin: 2202
|
color_temp_kelvin: 2202
|
||||||
brightness: 100
|
brightness: 75
|
||||||
light.server_lamp:
|
light.server_lamp:
|
||||||
state: on
|
state: "on"
|
||||||
rgb_color: [255, 112, 86]
|
rgb_color: [255, 112, 86]
|
||||||
brightness: 175
|
brightness: 175
|
||||||
|
light.living_room_stick:
|
||||||
|
state: "on"
|
||||||
|
brightness: 100
|
||||||
|
effect: sunset
|
||||||
- start: '10:00 pm'
|
- start: '10:00 pm'
|
||||||
scene:
|
scene:
|
||||||
light.bar:
|
light.bar:
|
||||||
@@ -56,6 +83,10 @@ bar_lights:
|
|||||||
state: on
|
state: on
|
||||||
rgb_color: [255, 112, 86]
|
rgb_color: [255, 112, 86]
|
||||||
brightness: 75
|
brightness: 75
|
||||||
|
light.living_room_stick:
|
||||||
|
state: "on"
|
||||||
|
brightness: 175
|
||||||
|
effect: sunset
|
||||||
- start: '11:30 pm'
|
- start: '11:30 pm'
|
||||||
scene:
|
scene:
|
||||||
light.bar:
|
light.bar:
|
||||||
@@ -63,4 +94,10 @@ bar_lights:
|
|||||||
color_temp_kelvin: 2202
|
color_temp_kelvin: 2202
|
||||||
brightness: 30
|
brightness: 30
|
||||||
light.server_lamp:
|
light.server_lamp:
|
||||||
state: off
|
state: on
|
||||||
|
rgb_color: [255, 112, 86]
|
||||||
|
brightness: 25
|
||||||
|
light.living_room_stick:
|
||||||
|
state: "on"
|
||||||
|
brightness: 75
|
||||||
|
effect: sunset
|
||||||
7
apps/stages/patio.py
Normal file
7
apps/stages/patio.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from light import StagedLight
|
||||||
|
|
||||||
|
|
||||||
|
class Patio(StagedLight):
|
||||||
|
def initialize(self):
|
||||||
|
super().initialize()
|
||||||
|
self.run_daily(lambda **kwargs: self.turn_on('light.patio', brightness=100), start='sunset - 31s')
|
||||||
34
apps/stages/patio.yaml
Normal file
34
apps/stages/patio.yaml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
patio:
|
||||||
|
module: patio
|
||||||
|
class: Patio
|
||||||
|
activate-at-start: true
|
||||||
|
transition: 30
|
||||||
|
stages:
|
||||||
|
- start: '01:00 am'
|
||||||
|
scene:
|
||||||
|
light.patio:
|
||||||
|
state: off
|
||||||
|
- start: sunset
|
||||||
|
scene:
|
||||||
|
light.patio:
|
||||||
|
state: on
|
||||||
|
brightness: 255
|
||||||
|
rgb_color: [0, 255, 0]
|
||||||
|
- start: '10:00 pm'
|
||||||
|
scene:
|
||||||
|
light.patio:
|
||||||
|
state: on
|
||||||
|
brightness: 175
|
||||||
|
rgb_color: [0, 255, 0]
|
||||||
|
- start: '11:00 pm'
|
||||||
|
scene:
|
||||||
|
light.patio:
|
||||||
|
state: on
|
||||||
|
brightness: 125
|
||||||
|
rgb_color: [0, 255, 0]
|
||||||
|
- start: '12:00 am'
|
||||||
|
scene:
|
||||||
|
light.patio:
|
||||||
|
state: on
|
||||||
|
brightness: 75
|
||||||
|
rgb_color: [0, 255, 0]
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
upper_stairs:
|
upper_stairs:
|
||||||
module: light
|
module: light
|
||||||
class: StagedLight
|
class: StagedLight
|
||||||
|
activate-at-start: True
|
||||||
stages:
|
stages:
|
||||||
- start: '04:00'
|
- start: '04:00'
|
||||||
scene:
|
scene:
|
||||||
@@ -31,14 +32,14 @@ upper_stairs:
|
|||||||
light.spire:
|
light.spire:
|
||||||
state: "on"
|
state: "on"
|
||||||
color_temp_kelvin: 2202
|
color_temp_kelvin: 2202
|
||||||
brightness: 60
|
brightness: 180
|
||||||
- start: '11:00 pm'
|
- start: '11:00 pm'
|
||||||
scene:
|
scene:
|
||||||
light.bathroom_stairs:
|
light.bathroom_stairs:
|
||||||
state: "on"
|
state: "on"
|
||||||
color_temp_kelvin: 2202
|
color_temp_kelvin: 2202
|
||||||
brightness: 40
|
brightness: 60
|
||||||
light.spire:
|
light.spire:
|
||||||
state: "on"
|
state: "on"
|
||||||
color_temp_kelvin: 2202
|
color_temp_kelvin: 2202
|
||||||
brightness: 40
|
brightness: 60
|
||||||
|
|||||||
Reference in New Issue
Block a user