Compare commits

..

5 Commits

7 changed files with 528 additions and 48 deletions

View File

@@ -2,20 +2,66 @@ hello_world:
module: hello
class: HelloWorld
my_tap_dial:
ManCaveLights:
module: tap_dial
class: TapDial
entity: sensor.tap_dial_action
entity: sensor.tapdial01_action
init_brightness: 200
max_brightness: 254
step_size: 25
color: antique_white
button1: light.stick_lamp
button2: light.wet_bar
button3: light.monkey_lamp
button4: light.kitchen_candlelabra
LivingRoomLights:
module: tap_dial
class: TapDial
entity: sensor.tapdial02_action
init_brightness: 200
max_brightness: 254
step_size: 25
button1: light.living_room_lamps
button2: light.living_room_floodlights
button3: light.living_room_spot
button4: switch.christmas_tree
piano_button:
button02:
module: button_switch
class: AqaraSwitch
button: sensor.button02_action
switch: switch.keyboards
# action: double
my_timer:
module: timer
class: TimerClass
on_time: '18:00:00'
light: light.wet_bar
brightness: 225
color: ff99ff
effect: candle
LightColorTest:
module: color_test
class: TapDial
entity: sensor.tapdial01a_action
init_brightness: 200
max_brightness: 254
step_size: 25
color1: FF0018
color2: ff99ff
color3: 008018
color4: 0000F9
brightness1: 100
brightness2: 150
brightness3: 200
brightness4: 254
effect1: candle
effect2: finish_effect
effect3: okay
effect4: channel_change
button1: light.wet_bar
button2: light.wet_bar
button3: light.wet_bar
button4: light.wet_bar

144
apps/color_test.py Normal file
View File

@@ -0,0 +1,144 @@
from appdaemon.entity import Entity
from appdaemon.plugins.hass.hassapi import Hass
import json
class TapDial(Hass):
active_entity: Entity
def initialize(self):
self.tap_dial.listen_state(self.handle_state_change)
self.log(f'010 Tap Dial entity: {self.tap_dial.entity_id}')
@property
def tap_dial(self) -> Entity:
return self.get_entity(self.args['entity'])
@property
def step_size(self) -> int:
return int(self.args.get('step_size', 25))
@property
def init_brightness(self) -> int:
return int(self.args.get('init_brightness', 200))
@property
def max_brightness(self) -> int:
return int(self.args.get('max_brightness', 254))
@property
def active_entity_is_on(self) -> bool:
return self.active_entity.get_state() == 'on'
@property
def light_brightness(self) -> int:
return self.active_entity.attributes['brightness']
@light_brightness.setter
def light_brightness(self, val: int):
return self.active_entity.turn_on(brightness=val)
@property
def light_color(self) -> list[int]:
return self.active_entity.get_state('rgb_color')
@light_color.setter
def light_color(self, val: list[int]):
return self.active_entity.turn_on(rgb_color=val)
@property
def light_effect(self) -> str:
return self.active_entity.get_state('effect')
@light_effect.setter
def light_effect(self, val: str):
return self.active_entity.turn_on(effect=val)
def handle_state_change(self, entity: str, attribute: str, old: str, new: str, **kwargs):
self.log(f'059 {new}')
# Dial actions
if new.startswith('dial_rotate') and self.active_entity_is_on:
self.log('063 Ignore dial_rotate commands for now...')
# Button actions
elif new.endswith('release'):
_, n, typ, _ = new.split('_', 4)
# type will be either press or hold
self.log(f'069 Button {n} {typ}')
if eid := self.args.get(f'button{n}'):
self.active_entity = self.get_entity(eid)
self.log(f'073 Set active entity to: {self.active_entity.name}')
self.log(json.dumps(self.active_entity.get_state('all'), indent=4))
domain, entity = eid.split('.')
self.log(f'076 domain: {domain}, entity: {entity}')
match domain:
case 'light':
# Set the light to maximum brightness if the button is held.
if typ == 'hold':
self.log('080 Ignore button_hold command for now...')
else:
if self.active_entity_is_on:
self.log(f'083 6Current values for Brightness: {self.light_brightness}, RGB Color: {self.light_color}, Effect: {self.light_effect}')
# self.log(f'New values for Color: {self.args.get(f"color{n}")} and Effect: {self.args.get(f"effect{n}")}')
hex = self.args.get(f"color{n}")
# self.log(f'Translate {self.args.get(f"color{n}")} to RGB {self.hex_to_rgb(hex)} ')
br_val = self.args.get(f'brightness{n}')
match n:
case '1': # do nothing
self.log(f'090 Made it to case{n}')
# self.active_entity.turn_on(brightness=br_val,rgb_color=self.hex_to_rgb(hex),transition=10)
self.active_entity.turn_on(brightness=br_val,rgb_color=self.hex_to_rgb(hex))
# self.active_entity.turn_on(rgb_color=self.hex_to_rgb('FFFFFF'))
# self.active_entity.turn_on(effect='candle')
# self.light_color = self.hex_to_rgb(hex)
# self.light_effect = self.args.get(f'effect{n}')
self.log(json.dumps(self.active_entity.get_state('all'), indent=4))
case '2': # attempt to change brightness, color
self.log(f'099 Made it to case{n}')
# self.active_entity.turn_on(brightness=br_val,rgb_color=self.hex_to_rgb(hex),transition=10)
self.active_entity.turn_on(brightness=br_val,rgb_color=self.hex_to_rgb(hex))
# self.active_entity.turn_on(rgb_color=self.hex_to_rgb('FFFFFF'))
# self.active_entity.turn_on(effect='candle')
# self.light_color = self.hex_to_rgb(hex)
# self.light_effect = self.args.get(f'effect{n}')
# self.log(json.dumps(self.active_entity.get_state('all'), indent=4))
case '3': # attempt to apply 'candle' effect
self.log(f'108 Made it to case{n}')
self.active_entity.turn_on(effect='stop_hue_effect')
# self.active_entity.turn_on(effect='candle')
self.active_entity.set_state(effect='fireplace')
# self.log(json.dumps(self.active_entity.get_state('all'), indent=4))
case '4': # json.dump
self.log(f'114 Made it to case{n}')
# self.active_entity.turn_on(brightness=br_val,rgb_color=self.hex_to_rgb(hex),transition=10)
# self.active_entity.turn_on(brightness=br_val,rgb_color=self.hex_to_rgb(hex))
# self.active_entity.turn_on(rgb_color=self.hex_to_rgb('FFFFFF'))
# self.active_entity.turn_on(effect='candle')
# self.light_color = self.hex_to_rgb(hex)
# self.light_effect = self.args.get(f'effect{n}')
self.log(json.dumps(self.active_entity.get_state('all'), indent=4))
else:
self.log('124 The light is off...')
case 'switch':
self.log('126 Ignore non-light entities for now...')
# This function was written by chatgpt...
def hex_to_rgb(self, hex_color: str) -> list:
# Remove the "#" if it's included
hex_color = hex_color.lstrip('#')
# Check if the string has a valid length
if len(hex_color) != 6:
raise ValueError(f"136 Invalid hex color: {hex_color}. Must be 6 characters long.")
try:
# Split the hex color into its RGB components and convert to integers
r = int(hex_color[0:2], 16)
g = int(hex_color[2:4], 16)
b = int(hex_color[4:6], 16)
return [r, g, b]
except ValueError:
raise ValueError(f"145 Invalid hex color: {hex_color}. Must contain only valid hex digits.")

View File

@@ -1,47 +1,156 @@
import re
from appdaemon.entity import Entity
from appdaemon.plugins.hass.hassapi import Hass
BUTTON_PRESS = re.compile(r'button_(\d)_press_release')
import json
class TapDial(Hass):
active_entity: Entity
def initialize(self):
self.log(f"Action entity: {self.tap_dial_entity.friendly_name}")
self.tap_dial_entity.listen_state(self.my_callback)
self.tap_dial.listen_state(self.handle_state_change)
@property
def tap_dial_entity(self) -> Entity:
return self.get_entity(self.args["entity"])
def tap_dial(self) -> Entity:
return self.get_entity(self.args['entity'])
@property
def step_size(self) -> int:
return int(self.args.get("step_size", 25))
return int(self.args.get('step_size', 25))
def my_callback(self, entity: str, attribute: str, old: str, new: str, **kwargs):
# self.log(f'{new}')
if new:
if m := BUTTON_PRESS.match(new):
pressed_button = int(m.group(1))
self.log(f'Pressed button {pressed_button}')
@property
def init_brightness(self) -> int:
return int(self.args.get('init_brightness', 200))
if associated_light := self.args.get(f'button{pressed_button}'):
light_entity = self.get_entity(associated_light)
self.active_entity = light_entity
self.log(f'Setting active entity to {self.active_entity.friendly_name}')
self.active_entity.toggle()
@property
def max_brightness(self) -> int:
return int(self.args.get('max_brightness', 254))
# The active_entity_is_on property fails if no button has been pushed and the dial is rotated with the error:
# File "/conf/apps/tap_dial.py", line 59, in handle_state_change
# if new.startswith('dial_rotate') and self.active_entity_is_on:
# ^^^^^^^^^^^^^^^^^^^^^^^^
# File "/conf/apps/tap_dial.py", line 29, in active_entity_is_on
# return self.active_entity.get_state() == 'on'
# ^^^^^^^^^^^^^^^^^^
# AttributeError: 'TapDial' object has no attribute 'active_entity'. Did you mean: 'remove_entity'?
@property
def active_entity_is_on(self) -> bool:
return self.active_entity.get_state() == 'on'
@property
def light_brightness(self) -> int:
return self.active_entity.attributes['brightness']
@light_brightness.setter
def light_brightness(self, val: int):
return self.active_entity.turn_on(brightness=val)
@property
def light_color(self) -> list[int]:
return self.active_entity.get_state('rgb_color')
@light_color.setter
def light_color(self, val: list[int]):
return self.active_entity.turn_on(rgb_color=val)
@property
def light_effect(self) -> str:
return self.active_entity.get_state('effect')
@light_effect.setter
def light_effect(self, val: str):
return self.active_entity.turn_on(effect=val)
def handle_state_change(self, entity: str, attribute: str, old: str, new: str, **kwargs):
self.log(f'056 State change: {new}')
# Dial actions
if new.startswith('dial_rotate') and self.active_entity_is_on:
_, _, dir, speed = new.split('_', 4)
self.log(f'061 Direction {dir} speed {speed}')
match speed:
case 'step':
rate = self.step_size
case 'slow':
rate = 2 * self.step_size
case 'fast':
rate = 3 * self.step_size
val = self.active_entity.attributes['brightness']
if val != 'null':
self.log(f'071 Brightness value = {val} Change rate = {rate}')
if dir == 'right':
if val < self.max_brightness:
if (val + rate) > self.max_brightness:
val = self.max_brightness
else:
val += rate
self.active_entity.turn_on(brightness=val)
self.log(f'079 Brightness value = {val}')
else:
if val > 0:
if (val - rate) < 0:
val = 0
else:
val -= rate
self.active_entity.turn_on(brightness=val)
self.log(f'087 Brightness value = {val}')
else:
self.log(f'089 WARNING: Brightness value = {val}')
# Button actions
elif new.endswith('release'):
_, n, typ, _ = new.split('_', 4)
# type will be either press or hold
self.log(f'104 Button {n} {typ}')
if eid := self.args.get(f'button{n}'):
self.active_entity = self.get_entity(eid)
self.log(f'108 Set active entity to: {self.active_entity.name}')
# self.log(json.dumps(self.active_entity.get_state('all'), indent=4))
domain, entity = eid.split('.')
self.log(f'112 Curent domain: {domain}, entity: {entity}')
match domain:
case 'light':
# Set the light to maximum brightness if the button is held.
if typ == 'hold':
self.active_entity.turn_on(brightness=self.max_brightness)
self.log(f'118 Set {self.active_entity.friendly_name} to maximum brightness {self.max_brightness}')
else:
if self.active_entity_is_on:
self.active_entity.turn_off()
self.log(f'122 Turn off {self.active_entity.friendly_name}')
else:
self.active_entity.turn_on(brightness=self.init_brightness)
self.log(f'125 Turn on {self.active_entity.friendly_name} with brightness {self.init_brightness}')
case 'switch':
self.active_entity.toggle()
self.log(f'128 Toggle on/off power to {self.active_entity.friendly_name}')
elif switch := self.args.get(f'button{n}'):
self.active_entity = self.get_entity(switch)
self.log(f'132 Set active entity to: {self.active_entity.friendly_name}')
onoff = self.active_entity.get_state()
self.log(f'134 {self.active_entity.friendly_name} is currently {onoff}')
self.active_entity.toggle()
self.log(f'136 Toggle on/off power to {self.active_entity.friendly_name}')
# This function was written by chatgpt!
def hex_to_rgb(self, hex_color: str) -> list:
# Remove the "#" if it's included
hex_color = hex_color.lstrip('#')
# Check if the string has a valid length
if len(hex_color) != 6:
raise ValueError(f"146 Invalid hex color: {hex_color}. Must be 6 characters long.")
try:
# Split the hex color into its RGB components and convert to integers
r = int(hex_color[0:2], 16)
g = int(hex_color[2:4], 16)
b = int(hex_color[4:6], 16)
return [r, g, b]
except ValueError:
raise ValueError(f"155 Invalid hex color: {hex_color}. Must contain only valid hex digits.")
match new:
case "dial_rotate_right_step":
b = self.active_entity.attributes['brightness']
self.active_entity.turn_on(brightness=b+self.step_size)
case "dial_rotate_left_step":
b = self.active_entity.attributes['brightness']
self.active_entity.turn_on(brightness=b-self.step_size)
# case _:
# self.log(f'Unhandled action: {new}')

37
apps/timer.py Normal file
View File

@@ -0,0 +1,37 @@
from appdaemon.entity import Entity
from appdaemon.plugins.hass.hassapi import Hass
class TimerClass(Hass):
def initialize(self):
on_time = self.args['on_time']
self.run_daily(self.activate, start=on_time)
self.log(f'Initialized timer for {self.light.friendly_name} at {on_time}')
@property
def light(self) -> Entity:
return self.get_entity(self.args['light'])
def activate(self, **kwargs: dict):
hex = self.args.get('color')
br_val = self.args.get('brightness')
self.light.turn_on(brightness=br_val,rgb_color=self.hex_to_rgb(hex))
# This function was written by chatgpt...
def hex_to_rgb(self, hex_color: str) -> list:
# Remove the "#" if it's included
hex_color = hex_color.lstrip('#')
# Check if the string has a valid length
if len(hex_color) != 6:
raise ValueError(f"Invalid hex color: {hex_color}. Must be 6 characters long.")
try:
# Split the hex color into its RGB components and convert to integers
r = int(hex_color[0:2], 16)
g = int(hex_color[2:4], 16)
b = int(hex_color[4:6], 16)
return [r, g, b]
except ValueError:
raise ValueError(f"Invalid hex color: {hex_color}. Must contain only valid hex digits.")

View File

@@ -5,17 +5,9 @@ services:
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- config:/conf
- ./:/conf
ports:
- 5050:5050
restart: unless-stopped
tty: true
volumes:
config:
driver: local
driver_opts:
o: bind
type: none
device: ./

68
inactive/living_room.yaml Normal file
View File

@@ -0,0 +1,68 @@
living_room:
module: room_control
class: RoomController
# rich: DEBUG
off_duration: 00:10:00
manual_mode: input_boolean.living_room_manual_mode
states:
- time: sunrise
off_duration: 00:15:00
scene:
light.living_room_lamps:
state: on
color_temp: 200
brightness: 255
light.white05:
state: on
color_temp: 200
brightness: 190
light.living_room_floodlights:
state: off
- time: '18:00:00'
off_duration: 01:00:00
scene:
light.living_room_lamps:
state: on
color_temp: 300
brightness: 190
light.living_room_floodlights:
state: off
- time: '23:00:00'
off_duration: 00:00:30
scene:
light.living_room_lamps:
state: on
color_temp: 400
brightness: 125
light.white05:
state: off
light.living_room_floodlights:
state: off
# sleep: input_boolean.sleeping
# sleep_state:
# scene:
# light.colorflood05:
# state: 'on'
# color_name: 'red'
# brightness: 10
# front_door:
# module: door
# class: Door
# app: living_room
# door: binary_sensor.front_contact
living_room_button:
module: button
class: Button
app: living_room
manual_mode: input_boolean.living_room_manual_mode
button: button01
ref_entity: light.color04
living_room_motion:
module: motion
class: Motion
app: living_room
sensor: binary_sensor.living_room_motion
ref_entity: light.color04

84
inactive/mancave.yaml Normal file
View File

@@ -0,0 +1,84 @@
mancave:
module: room_control
class: RoomController
# rich: DEBUG
off_duration: 00:00:10
manual_mode: input_boolean.mancave_manual_mode
states:
- time: sunrise
scene:
light.color01:
state: on
color_temp: 200
brightness: 255
light.colorflood05:
state: off
light.color03:
state: off
light.white08:
color_temp: 300
brightness: 175
light.wled_soundreactive:
state: off
preset: 'Stream'
- time: '18:00:00'
scene:
light.color01:
state: on
rgb_color: [255, 193, 193]
brightness: 255
light.colorflood05:
state: on
rgb_color: [255, 193, 193]
brightness: 170
light.color03:
state: off
light.white08:
state: on
color_temp: 400
brightness: 70
light.wled_soundreactive:
state: on
preset: 'WashingMachine'
- time: '23:00:00'
scene:
light.color01:
state: on
color_temp: 400
brightness: 125
light.colorflood05:
state: off
light.color03:
state: off
light.white08:
state: off
light.wled_soundreactive:
state: off
# sleep: input_boolean.sleeping
# sleep_state:
# scene:
# light.colorflood05:
# state: 'on'
# color_name: 'red'
# brightness: 10
# front_door:
# module: door
# class: Door
# app: living_room
# door: binary_sensor.front_contact
mancave_button:
module: button
class: Button
app: mancave
manual_mode: input_boolean.mancave_manual_mode
button: button07
ref_entity: light.color01
mancave_motion:
module: motion
class: Motion
app: mancave
sensor: binary_sensor.motionsensors01_occupancy
ref_entity: light.color01