hass-4button/wallmote.py

357 lines
16 KiB
Python

#!/usr/bin/python3
import hassapi as hass
from threading import Timer
###############
# Action Tree #
###############
"""
1: States
1: Lights
1: Kitchen Lights
1: Off
2: 10%
3: 55%
4: 100%
2: Main Room Lights
[...]
3: Living Room Lights
[...]
4: Bedroom Lights
[...]
2: A/C
2: Main Room AC
1: Power
1: On
2: On
3: Off
4: Off
2: Mode
1: Cool
2: Heat
3: Fan
3: Fan Level
1: Auto
2: Quiet
3: Medium
4: High
4: Temperature
1: 72
2: 74
3: 76
4: 78
3: Living Room AC
[...]
4: Bedroom AC
[...]
3: Fans
1: Kitchen Fan
1: Off
2: 1
3: 2
4: 3
3: Living Room Fan
[...]
4: Bedroom Fan
[...]
2: Miscellaneous
1: Effects
1: Clear light RGB strip notifications
2: Music
1: Join/unjoin speakers
1. Unjoin speakers
# Select master
3. Living Room Sonos
4. Bedroom Sonos
2: Play Music
3: Play on Living Room Sonos
1: Spotify: Liked Songs
2: Spotify: Getting Things Done
4: Play on Bedroom Sonos
1: Spotify: Liked Songs
2: Spotify: Chill Sleep
3: Media Controls
3: Living Room Sonos
1. Play
2. Pause
3. Previous
4. Next
4: Bedroom Sonos
[...]
4. Volume: TODO
3: Living Room Sonos
4: Bedroom Sonos
3: Reserved
4: Configuration
"""
tree = {
1: { # States
1: { # Lights
1: { # Kitchen
1: { "function": lambda hass: set_brightness(hass,"light.kitchen_lights",0) },
2: { "function": lambda hass: set_brightness(hass,"light.kitchen_lights",10) },
3: { "function": lambda hass: set_brightness(hass,"light.kitchen_lights",55) },
4: { "function": lambda hass: set_brightness(hass,"light.kitchen_lights",100) }
},
2: { # Main Room
1: { "function": lambda hass: set_brightness(hass,"light.mainroom_lights",0) },
2: { "function": lambda hass: set_brightness(hass,"light.mainroom_lights",10) },
3: { "function": lambda hass: set_brightness(hass,"light.mainroom_lights",55) },
4: { "function": lambda hass: set_brightness(hass,"light.mainroom_lights",100) }
},
3: { # Living Room
1: { "function": lambda hass: set_brightness(hass,"light.livingroom_lights",0) },
2: { "function": lambda hass: set_brightness(hass,"light.livingroom_lights",10) },
3: { "function": lambda hass: set_brightness(hass,"light.livingroom_lights",55) },
4: { "function": lambda hass: set_brightness(hass,"light.livingroom_lights",100) }
},
4: { # Bedroom
1: { "function": lambda hass: set_brightness(hass,"light.bedroom_lights",0) },
2: { "function": lambda hass: set_brightness(hass,"light.bedroom_lights",10) },
3: { "function": lambda hass: set_brightness(hass,"light.bedroom_lights",55) },
4: { "function": lambda hass: set_brightness(hass,"light.bedroom_lights",100) }
}
},
2: { # A/C
2: { # Main Room
1: { # Power
1: { "function": lambda hass: hass.turn_on("climate.mainroom_ac") },
2: { "function": lambda hass: hass.turn_on("climate.mainroom_ac") },
3: { "function": lambda hass: hass.turn_off("climate.mainroom_ac") },
4: { "function": lambda hass: hass.turn_off("climate.mainroom_ac") },
},
2: { # Mode
1: { "function": lambda hass: gree_set_mode(hass,"mainroom_ac","cool") },
2: { "function": lambda hass: gree_set_mode(hass,"mainroom_ac","heat") },
3: { "function": lambda hass: gree_set_mode(hass,"mainroom_ac","fan_only") }
},
3: { # Fan Level
1: { "function": lambda hass: gree_set_fan_level(hass,"mainroom_ac","auto") },
2: { "function": lambda hass: gree_set_fan_level(hass,"mainroom_ac","quiet") },
3: { "function": lambda hass: gree_set_fan_level(hass,"mainroom_ac","medium") },
4: { "function": lambda hass: gree_set_fan_level(hass,"mainroom_ac","high") }
},
4: { # Temperature
1: { "function": lambda hass: set_temperature(hass,"mainroom_ac",72) },
2: { "function": lambda hass: set_temperature(hass,"mainroom_ac",74) },
3: { "function": lambda hass: set_temperature(hass,"mainroom_ac",76) },
4: { "function": lambda hass: set_temperature(hass,"mainroom_ac",78) }
}
},
3: { # Living Room
1: { # Power
1: { "function": lambda hass: hass.turn_on("climate.livingroom_ac") },
2: { "function": lambda hass: hass.turn_on("climate.livingroom_ac") },
3: { "function": lambda hass: hass.turn_off("climate.livingroom_ac") },
4: { "function": lambda hass: hass.turn_off("climate.livingroom_ac") },
},
2: { # Mode
1: { "function": lambda hass: gree_set_mode(hass,"livingroom_ac","cool") },
2: { "function": lambda hass: gree_set_mode(hass,"livingroom_ac","heat") },
3: { "function": lambda hass: gree_set_mode(hass,"livingroom_ac","fan_only") }
},
3: { # Fan Level
1: { "function": lambda hass: gree_set_fan_level(hass,"livingroom_ac","auto") },
2: { "function": lambda hass: gree_set_fan_level(hass,"livingroom_ac","quiet") },
3: { "function": lambda hass: gree_set_fan_level(hass,"livingroom_ac","medium") },
4: { "function": lambda hass: gree_set_fan_level(hass,"livingroom_ac","high") }
},
4: { # Temperature
1: { "function": lambda hass: set_temperature(hass,"livingroom_ac",72) },
2: { "function": lambda hass: set_temperature(hass,"livingroom_ac",74) },
3: { "function": lambda hass: set_temperature(hass,"livingroom_ac",76) },
4: { "function": lambda hass: set_temperature(hass,"livingroom_ac",78) }
}
},
4: { # Bedroom
1: { # Power
1: { "function": lambda hass: hass.turn_on("climate.bedroom_ac") },
2: { "function": lambda hass: hass.turn_on("climate.bedroom_ac") },
3: { "function": lambda hass: hass.turn_off("climate.bedroom_ac") },
4: { "function": lambda hass: hass.turn_off("climate.bedroom_ac") },
},
2: { # Mode
1: { "function": lambda hass: gree_set_mode(hass,"bedroom_ac","cool") },
2: { "function": lambda hass: gree_set_mode(hass,"bedroom_ac","heat") },
3: { "function": lambda hass: gree_set_mode(hass,"bedroom_ac","fan_only") }
},
3: { # Fan Level
1: { "function": lambda hass: gree_set_fan_level(hass,"bedroom_ac","auto") },
2: { "function": lambda hass: gree_set_fan_level(hass,"bedroom_ac","quiet") },
3: { "function": lambda hass: gree_set_fan_level(hass,"bedroom_ac","medium") },
4: { "function": lambda hass: gree_set_fan_level(hass,"bedroom_ac","high") }
},
4: { # Temperature
1: { "function": lambda hass: set_temperature(hass,"bedroom_ac",72) },
2: { "function": lambda hass: set_temperature(hass,"bedroom_ac",74) },
3: { "function": lambda hass: set_temperature(hass,"bedroom_ac",76) },
4: { "function": lambda hass: set_temperature(hass,"bedroom_ac",78) }
}
}
},
3: { # Fans
1: { # Kitchen
1: { "function": lambda hass: hass.set_value("input_number.kitchen_fan",0) },
2: { "function": lambda hass: hass.set_value("input_number.kitchen_fan",1) },
3: { "function": lambda hass: hass.set_value("input_number.kitchen_fan",2) },
4: { "function": lambda hass: hass.set_value("input_number.kitchen_fan",3) }
},
3: { # Living Room
1: { "function": lambda hass: hass.set_value("input_number.livingroom_fan",0) },
2: { "function": lambda hass: hass.set_value("input_number.livingroom_fan",1) },
3: { "function": lambda hass: hass.set_value("input_number.livingroom_fan",2) },
4: { "function": lambda hass: hass.set_value("input_number.livingroom_fan",3) }
},
4: { # Bedroom
1: { "function": lambda hass: hass.set_value("input_number.bedroom_fan",0) },
2: { "function": lambda hass: hass.set_value("input_number.bedroom_fan",1) },
3: { "function": lambda hass: hass.set_value("input_number.bedroom_fan",2) },
4: { "function": lambda hass: hass.set_value("input_number.bedroom_fan",3) }
}
}
},
2: { # Miscellaneous
1: { # Effects
1: { "service": "script/clear_lightswitch_leds" }
},
2: { # Music
1: { # Join/unjoin
1: { "function": lambda hass: hass.call_service("sonos/unjoin",
entity_id=["media_player.livingroom_sonos","media_player.bedroom_sonos"]) },
3: { "function": lambda hass: hass.call_service("sonos/join",
master="media_player.livingroom_sonos",entity_id="media_player.bedroom_sonos")},
4: { "function": lambda hass: hass.call_service("sonos/join",
master="media_player.livingroom_sonos",entity_id="media_player.bedroom_sonos")},
},
2: { # Play Music
3: { # Living Room Sonos
1: { "function": lambda hass: select_source(hass,"media_player.livingroom_sonos","Songs") },
2: { "function": lambda hass: select_source(hass,"media_player.livingroom_sonos","Getting Things Done") }
},
4: { # Bedroom Sonos
1: { "function": lambda hass: select_source(hass,"media_player.bedroom_sonos","Songs") },
2: { "function": lambda hass: select_source(hass,"media_player.bedroom_sonos","Chill sleep") }
}
},
3: { # Media Controls
3: { # Living Room Sonos
1: { "function": lambda hass: hass.call_service("media_player/media_pause",
entity_id="media_player.livingroom_sonos")},
2: { "function": lambda hass: hass.call_service("media_player/media_play",
entity_id="media_player.livingroom_sonos")},
3: { "function": lambda hass: hass.call_service("media_player/media_previous_track",
entity_id="media_player.livingroom_sonos")},
4: { "function": lambda hass: hass.call_service("media_player/media_next_track",
entity_id="media_player.livingroom_sonos")}
},
4: { # Bedroom Sonos
1: { "function": lambda hass: hass.call_service("media_player/media_pause",
entity_id="media_player.bedroom_sonos")},
2: { "function": lambda hass: hass.call_service("media_player/media_play",
entity_id="media_player.bedroom_sonos")},
3: { "function": lambda hass: hass.call_service("media_player/media_previous_track",
entity_id="media_player.bedroom_sonos")},
4: { "function": lambda hass: hass.call_service("media_player/media_next_track",
entity_id="media_player.bedroom_sonos")}
}
}
}
}
}
###########################
# Action Helper Functions #
###########################
def set_brightness(hass, device, brightness):
# Convert brightness from percent to 0-255
brightness = brightness * 255 / 100;
hass.turn_on(device, brightness=brightness);
def set_temperature(hass, device, temperature):
hass.call_service("climate/set_temperature",entity_id="climate."+device,temperature=temperature);
def gree_set_mode(hass, device, mode):
hass.call_service("climate/set_hvac_mode",entity_id="climate."+device,hvac_mode=mode);
def gree_set_fan_level(hass, device, level):
if (level == "quiet"):
hass.turn_on(f"switch.{device}_quiet");
else:
hass.turn_off(f"switch.{device}_quiet");
hass.call_service("climate/set_fan_mode",entity_id="climate."+device,fan_mode=level);
def select_source(hass, device, source):
hass.call_service("media_player/select_source",entity_id=device,source=source)
##################
# Sequence Logic #
##################
# Long press cancels any existing sequence and begins a new one. The first element in a
# sequence is always a long press, and all others are always short presses.
# While a sequence is being entered, normal actions from short presses in hass are
# disabled. A sequence can be ended either immediately by recognizing a full sequence
# or by TIMEOUT seconds passing.
# - self.currentSequence contains an array of keys representing a sequence that is
# actively being entered.
# - self.tot contains a timer for the process_sequence function, ending sequence entry.
TIMEOUT=2
class WallmotePlumbing(hass.Hass):
def initialize(self):
self.turn_on("automation.wallmote");
self.currentSequence = [];
self.listen_event(self.onevent, "zwave_js_value_notification");
def onevent(self, _, event, kwargs):
if (event.get('value') == 'KeyHeldDown'): return;
if (event.get('device_id') != '3f7add36f94839b1446886b5dab35e6d'): return;
key = int(event.get('property_key'));
# We don't get a KeyPressed event at all for long presses
evt = event.get('value');
if (evt == "KeyPressed"):
self.shortpress(key);
elif (evt == "KeyReleased"):
self.longpress(key);
def shortpress(self, key):
# Single presses are handled in hass; sequences must start with a long press
if (len(self.currentSequence) == 0): return;
self.currentSequence.append(key);
self.reset_tot();
def longpress(self, key):
self.turn_off("automation.wallmote");
self.currentSequence = [key];
self.reset_tot();
def reset_tot(self):
if (hasattr(self,"tot")): self.tot.cancel();
self.tot = Timer(TIMEOUT,self.process_sequence);
self.tot.start();
def process_sequence(self):
self.tot.cancel(); # So process_sequence can be called directly
self.turn_on("automation.wallmote");
# Clear self.currentSequence so future shortpresses are not
# treated as part of a sequence
sequence = self.currentSequence;
self.currentSequence = [];
self.log(sequence);
self.run_action(sequence);
def run_action(self, sequence, tree=tree):
if (len(sequence) > 0):
# Deeper into the rabbit hole
category = sequence.pop(0);
if (category in tree): self.run_action(sequence,tree[category]);
else:
# Perform an action from here
if ("function" in tree): tree["function"](self);
elif ("service" in tree): self.call_service(tree["service"]);