Added support for remote presets
This commit is contained in:
parent
68f420e13b
commit
96343b9cec
1 changed files with 202 additions and 16 deletions
218
c4ctrl.py
218
c4ctrl.py
|
@ -31,8 +31,9 @@ class C4Interface():
|
||||||
item["qos"] = self.qos
|
item["qos"] = self.qos
|
||||||
item["retain"] = self.retain
|
item["retain"] = self.retain
|
||||||
|
|
||||||
if self.debug: return print("[DEBUG] inhibited message:", cmd)
|
if self.debug: return print("[DEBUG] inhibited messages:", cmd)
|
||||||
|
|
||||||
|
print(cmd)
|
||||||
publish.multiple(cmd,
|
publish.multiple(cmd,
|
||||||
hostname=self.broker,
|
hostname=self.broker,
|
||||||
port=self.port)
|
port=self.port)
|
||||||
|
@ -68,6 +69,13 @@ class C4Interface():
|
||||||
def status(self):
|
def status(self):
|
||||||
"""Print current status (open or closed) of C4."""
|
"""Print current status (open or closed) of C4."""
|
||||||
st = self.fetch("club/status")
|
st = self.fetch("club/status")
|
||||||
|
|
||||||
|
# Produce fake result to prevent errors if in debug mode
|
||||||
|
if C4Interface.debug:
|
||||||
|
print("[DEBUG] Warning: handing over fake data to allow further execution!")
|
||||||
|
class st: pass
|
||||||
|
st.payload = b'\x00'
|
||||||
|
|
||||||
if st.payload == b'\x01':
|
if st.payload == b'\x01':
|
||||||
print("Club is open")
|
print("Club is open")
|
||||||
else:
|
else:
|
||||||
|
@ -163,7 +171,6 @@ class C4Room:
|
||||||
|
|
||||||
def interactive_switchctrl(self, userinput="NULL"):
|
def interactive_switchctrl(self, userinput="NULL"):
|
||||||
"""Switch lamps in this rooms on and off."""
|
"""Switch lamps in this rooms on and off."""
|
||||||
import sys
|
|
||||||
c4 = C4Interface()
|
c4 = C4Interface()
|
||||||
|
|
||||||
if userinput == "NULL":
|
if userinput == "NULL":
|
||||||
|
@ -318,7 +325,7 @@ class Kitchenlight:
|
||||||
"""The Kitchenlight."""
|
"""The Kitchenlight."""
|
||||||
|
|
||||||
_available_modes = """
|
_available_modes = """
|
||||||
off disable
|
off turn off
|
||||||
checker[,DELAY[,COLOR_1[,COLOR_2]]]
|
checker[,DELAY[,COLOR_1[,COLOR_2]]]
|
||||||
Checker
|
Checker
|
||||||
matrix[,LINES] Matrix
|
matrix[,LINES] Matrix
|
||||||
|
@ -676,8 +683,7 @@ class ColorScheme:
|
||||||
print("Available presets:")
|
print("Available presets:")
|
||||||
for entry in self.available:
|
for entry in self.available:
|
||||||
if entry[0] == '.' or entry[-1:] == '~': continue
|
if entry[0] == '.' or entry[-1:] == '~': continue
|
||||||
print(entry)
|
print(" " + entry)
|
||||||
print("PRESET may also be a color in hex notation (eg. #f06 or #ff0066).")
|
|
||||||
|
|
||||||
def store(self, name):
|
def store(self, name):
|
||||||
"""Store the current state of all lights as preset."""
|
"""Store the current state of all lights as preset."""
|
||||||
|
@ -727,8 +733,165 @@ class ColorScheme:
|
||||||
print("Wrote preset \"{}\"".format(name))
|
print("Wrote preset \"{}\"".format(name))
|
||||||
|
|
||||||
|
|
||||||
|
class RemotePresets:
|
||||||
|
"""Remote preset control."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.map = {
|
||||||
|
"global" : {
|
||||||
|
"name" : "AutoC4",
|
||||||
|
"list_topic" : "preset/list",
|
||||||
|
"set_topic" : "preset/set",
|
||||||
|
"def_topic" : "preset/def"
|
||||||
|
},
|
||||||
|
"wohnzimmer" : {
|
||||||
|
"name" : "Wohnzimmer",
|
||||||
|
"list_topic" : "preset/wohnzimmer/list",
|
||||||
|
"set_topic" : "preset/wohnzimmer/set",
|
||||||
|
"def_topic" : "preset/wohnzimmer/def"
|
||||||
|
},
|
||||||
|
"plenar" : {
|
||||||
|
"name" : "Plenarsaal",
|
||||||
|
"list_topic" : "preset/plenar/list",
|
||||||
|
"set_topic" : "preset/plenar/set",
|
||||||
|
"def_topic" : "preset/plenar/def"
|
||||||
|
},
|
||||||
|
"fnord" : {
|
||||||
|
"name" : "Fnordcenter",
|
||||||
|
"list_topic" : "preset/fnord/list",
|
||||||
|
"set_topic" : "preset/fnord/set",
|
||||||
|
"def_topic" : "preset/fnord/def"
|
||||||
|
},
|
||||||
|
"keller" : {
|
||||||
|
"name" : "Keller",
|
||||||
|
"list_topic" : "preset/keller/list",
|
||||||
|
"set_topic" : "preset/keller/set",
|
||||||
|
"def_topic" : "preset/keller/def"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _expand_room_name(self, name):
|
||||||
|
"""Try to expand partial names."""
|
||||||
|
if name in self.map.keys():
|
||||||
|
# Return exact match
|
||||||
|
return name
|
||||||
|
|
||||||
|
for room in self.map.keys():
|
||||||
|
if room.find(name) == 0:
|
||||||
|
return room
|
||||||
|
# Fallback
|
||||||
|
return name
|
||||||
|
|
||||||
|
def _expand_preset_name(self, name, rooms, available):
|
||||||
|
"""Try to expand partial preset names.
|
||||||
|
|
||||||
|
<rooms> must be a list of rooms to consider.
|
||||||
|
<available> must be a dict as returned by query_available()."""
|
||||||
|
# We need to take care to match only presets which are available for
|
||||||
|
# every room
|
||||||
|
matchtable = {}
|
||||||
|
for room in rooms:
|
||||||
|
for preset in available[room]:
|
||||||
|
# Candidate?
|
||||||
|
if preset == name or preset.find(name) == 0:
|
||||||
|
if preset in matchtable.keys():
|
||||||
|
matchtable[preset] += 1
|
||||||
|
else:
|
||||||
|
matchtable[preset] = 1
|
||||||
|
|
||||||
|
# First check if there is an exact match
|
||||||
|
if name in matchtable.keys() and matchtable[name] == len(rooms):
|
||||||
|
return name # Exact match
|
||||||
|
|
||||||
|
# Return first preset available in all rooms
|
||||||
|
for match in matchtable.keys():
|
||||||
|
if matchtable[match] == len(rooms):
|
||||||
|
return match
|
||||||
|
# Fallback
|
||||||
|
return name
|
||||||
|
|
||||||
|
def query_available(self, rooms=["global"]):
|
||||||
|
"""Returns a dict of remotely available presets for [rooms]."""
|
||||||
|
import json
|
||||||
|
|
||||||
|
# "global" is available everywhere and should always be included
|
||||||
|
if "global" not in rooms:
|
||||||
|
rooms.append("global")
|
||||||
|
|
||||||
|
req = []
|
||||||
|
for room in rooms:
|
||||||
|
if room not in self.map.keys():
|
||||||
|
print("Error: unknown room \"{}\"".format(room))
|
||||||
|
return {}
|
||||||
|
|
||||||
|
req.append(self.map[room]["list_topic"])
|
||||||
|
|
||||||
|
c4 = C4Interface()
|
||||||
|
responce = c4.fetch(req)
|
||||||
|
# Make responce iterable
|
||||||
|
if type(responce) != list: responce = [responce]
|
||||||
|
|
||||||
|
available = {}
|
||||||
|
for room in rooms:
|
||||||
|
for r in responce:
|
||||||
|
if r.topic == self.map[room]["list_topic"]:
|
||||||
|
available[room] = json.decoder.JSONDecoder().decode(r.payload.decode())
|
||||||
|
|
||||||
|
return available
|
||||||
|
|
||||||
|
def list_available(self, room="global"):
|
||||||
|
"""Print a list of available Presets."""
|
||||||
|
room = self._expand_room_name(room)
|
||||||
|
if room == "global":
|
||||||
|
rooms = [room]
|
||||||
|
else: # "global" is available in every room, thus append
|
||||||
|
rooms = ["global", room]
|
||||||
|
available = self.query_available(rooms.copy())
|
||||||
|
|
||||||
|
if not available:
|
||||||
|
print("No presets available for {}".format(self.map[room]["name"]))
|
||||||
|
else:
|
||||||
|
print("Available presets for {}:".format(self.map[room]["name"]))
|
||||||
|
for r in available.keys():
|
||||||
|
for preset in available[r]:
|
||||||
|
print( " " + preset)
|
||||||
|
|
||||||
|
def apply_preset(self, preset, rooms=["global"]):
|
||||||
|
"""Apply preset to given rooms."""
|
||||||
|
# Strip spaces and expand rooms names
|
||||||
|
for i in range(len(rooms)):
|
||||||
|
rooms[i] = self._expand_room_name(rooms[i].strip())
|
||||||
|
|
||||||
|
available = self.query_available(rooms.copy())
|
||||||
|
# Produce some fake data to prevent KeyErrors if in debug mode
|
||||||
|
if C4Interface.debug:
|
||||||
|
print("[DEBUG] Warning: handing over fake data to allow further execution!")
|
||||||
|
available = {
|
||||||
|
"global" : [preset],
|
||||||
|
"wohnzimmer" : [preset],
|
||||||
|
"plenar" : [preset],
|
||||||
|
"fnord" : [preset],
|
||||||
|
"keller" : [preset]
|
||||||
|
}
|
||||||
|
# Expand preset name (stripping spaces)
|
||||||
|
preset = self._expand_preset_name(preset, rooms.copy(), available.copy())
|
||||||
|
|
||||||
|
for room in rooms:
|
||||||
|
if preset not in available[room] and preset not in available["global"]:
|
||||||
|
print("Error: preset \"{}\" not available for room \"{}\"!".format(
|
||||||
|
preset, self.map[room]["name"]))
|
||||||
|
return False
|
||||||
|
|
||||||
|
cmd = []
|
||||||
|
for room in rooms:
|
||||||
|
cmd.append({"topic" : self.map[room]["set_topic"],
|
||||||
|
"payload" : preset})
|
||||||
|
|
||||||
|
c4 = C4Interface()
|
||||||
|
return c4.update(cmd)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
|
@ -753,28 +916,38 @@ if __name__ == "__main__":
|
||||||
"-k", "--kl-mode", type=str, metavar="MODE[,OPTIONS]",
|
"-k", "--kl-mode", type=str, metavar="MODE[,OPTIONS]",
|
||||||
help="set Kitchenlight to MODE")
|
help="set Kitchenlight to MODE")
|
||||||
group_kl.add_argument(
|
group_kl.add_argument(
|
||||||
"-l", "--kl-list", action="store_true",
|
"-i", "--list-kl-modes", action="store_true",
|
||||||
help="list available Kitchenlight modes")
|
help="list available Kitchenlight modes and their options")
|
||||||
# Ambient control
|
# Ambient control
|
||||||
group_cl = parser.add_argument_group(title="ambient color control")
|
group_cl = parser.add_argument_group(title="ambient color control",
|
||||||
|
description="PRESET may be either a preset name or a color value in hex notation (eg. \"#ff0066\").")
|
||||||
group_cl.add_argument(
|
group_cl.add_argument(
|
||||||
"-w", "--wohnzimmer", type=str, dest="w_color", metavar="PRESET",
|
"-w", "--wohnzimmer", type=str, dest="w_color", metavar="PRESET",
|
||||||
help="apply colorscheme PRESET to Wohnzimmer")
|
help="apply local colorscheme PRESET to Wohnzimmer")
|
||||||
group_cl.add_argument(
|
group_cl.add_argument(
|
||||||
"-p", "--plenarsaal", type=str, dest="p_color", metavar="PRESET",
|
"-p", "--plenarsaal", type=str, dest="p_color", metavar="PRESET",
|
||||||
help="apply colorscheme PRESET to Plenarsaal")
|
help="apply local colorscheme PRESET to Plenarsaal")
|
||||||
group_cl.add_argument(
|
group_cl.add_argument(
|
||||||
"-f", "--fnordcenter", type=str, dest="f_color", metavar="PRESET",
|
"-f", "--fnordcenter", type=str, dest="f_color", metavar="PRESET",
|
||||||
help="apply colorscheme PRESET to Fnordcenter")
|
help="apply local colorscheme PRESET to Fnordcenter")
|
||||||
group_cl.add_argument(
|
group_cl.add_argument(
|
||||||
"-i", "--list-presets", action="store_true",
|
"-l", "--list-presets", action="store_true",
|
||||||
help="list available presets")
|
help="list locally available presets")
|
||||||
group_cl.add_argument(
|
group_cl.add_argument(
|
||||||
"-o", "--store-preset", type=str, dest="store_as", metavar="NAME",
|
"-o", "--store-preset", type=str, dest="store_as", metavar="NAME",
|
||||||
help="store current state as preset NAME")
|
help="store current state as preset NAME")
|
||||||
|
# Remote presets
|
||||||
|
group_rp = parser.add_argument_group(title="remote preset functions",
|
||||||
|
description="Available room names are \"wohnzimmer\", \"plenar\", \"fnord\" and \"keller\". Preset and room names can be abbreviated.")
|
||||||
|
group_rp.add_argument(
|
||||||
|
"-r", "--remote-preset", type=str, metavar="PRESET[:ROOM[,ROOM,...]]",
|
||||||
|
help="activate remote PRESET for ROOM.")
|
||||||
|
group_rp.add_argument(
|
||||||
|
"-R", "--list-remote", nargs='?', const="global", metavar="ROOM",
|
||||||
|
help="list remote presets for room ROOM")
|
||||||
# Switch control
|
# Switch control
|
||||||
group_sw = parser.add_argument_group(title="light switch control",
|
group_sw = parser.add_argument_group(title="light switch control",
|
||||||
description="The optional [DIGIT_CODE] is a string of 0s or 1s for every light in the room. Works interactivly if missing.")
|
description="The optional DIGIT_CODE is a string of 0s or 1s for every light in the room. Works interactivly if missing.")
|
||||||
group_sw.add_argument(
|
group_sw.add_argument(
|
||||||
"-W", nargs='?', dest="w_switch", const="NULL", metavar="DIGIT_CODE",
|
"-W", nargs='?', dest="w_switch", const="NULL", metavar="DIGIT_CODE",
|
||||||
help="switch lights in Wohnzimmer on/off")
|
help="switch lights in Wohnzimmer on/off")
|
||||||
|
@ -803,7 +976,7 @@ if __name__ == "__main__":
|
||||||
C4Interface().shutdown()
|
C4Interface().shutdown()
|
||||||
|
|
||||||
# Kitchenlight
|
# Kitchenlight
|
||||||
if args.kl_list:
|
if args.list_kl_modes:
|
||||||
print("Available Kitchenlight modes (options are optional):")
|
print("Available Kitchenlight modes (options are optional):")
|
||||||
print(Kitchenlight._available_modes)
|
print(Kitchenlight._available_modes)
|
||||||
if args.kl_mode:
|
if args.kl_mode:
|
||||||
|
@ -829,6 +1002,19 @@ if __name__ == "__main__":
|
||||||
if args.list_presets:
|
if args.list_presets:
|
||||||
ColorScheme().list_available()
|
ColorScheme().list_available()
|
||||||
|
|
||||||
|
# Remote presets
|
||||||
|
if args.list_remote:
|
||||||
|
RemotePresets().list_available(args.list_remote.lower())
|
||||||
|
if args.remote_preset:
|
||||||
|
# Lets try to preserve spaces
|
||||||
|
#remote_opts = ' '.join(args.remote_preset).split(':')
|
||||||
|
remote_opts = args.remote_preset.split(':')
|
||||||
|
if len(remote_opts) == 1:
|
||||||
|
RemotePresets().apply_preset(remote_opts[0].lower().strip())
|
||||||
|
else:
|
||||||
|
RemotePresets().apply_preset(remote_opts[0].lower().strip(),
|
||||||
|
remote_opts[1].split(','))
|
||||||
|
|
||||||
# Light switches
|
# Light switches
|
||||||
if args.w_switch:
|
if args.w_switch:
|
||||||
Wohnzimmer().interactive_switchctrl(args.w_switch)
|
Wohnzimmer().interactive_switchctrl(args.w_switch)
|
||||||
|
|
Loading…
Reference in a new issue