Various modifications
This commit is contained in:
parent
8489443ac5
commit
ad5102f334
1 changed files with 302 additions and 294 deletions
700
c4ctrl.py
700
c4ctrl.py
|
@ -13,19 +13,20 @@ Dependencies:
|
|||
"""
|
||||
|
||||
import sys
|
||||
from random import choice # For client_id generation
|
||||
|
||||
|
||||
class C4Interface():
|
||||
""" Interaction with AutoC4, the C4 home automation system. """
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
broker = "autoc4.labor.koeln.ccc.de"
|
||||
port = 1883
|
||||
qos = 2
|
||||
retain = True
|
||||
# Generate a (sufficiently) unique client id
|
||||
client_id = "c4ctrl-" + uuid4().hex
|
||||
client_id = "c4ctrl-" + "".join(
|
||||
choice("0123456789abcdefABCDEF") for unused in range(16)
|
||||
)
|
||||
debug = False
|
||||
|
||||
def push(self, message, topic=None, retain=None):
|
||||
|
@ -140,312 +141,6 @@ class C4Interface():
|
|||
self.push(payload, topic="club/shutdown", retain=False)
|
||||
|
||||
|
||||
class Dmx:
|
||||
""" Abstraction of the 3 channel LED cans in the club. """
|
||||
|
||||
# 3 bytes for color, one each for red, green and blue
|
||||
template = "000000"
|
||||
|
||||
def __init__(self, topic, color=None):
|
||||
self.topic = topic
|
||||
self.set_color(color or self.template)
|
||||
self.is_master = topic.rfind("/master") == len(topic)-7 # 7 = len("/master")
|
||||
|
||||
def _pad_color(self, color):
|
||||
""" Merge hex color values or payloads into the template.
|
||||
|
||||
Expand 4 bit hex code notation (eg. #f0f) and pad with template
|
||||
to get a fitting payload for this kind of light. """
|
||||
|
||||
if len(color) > len(self.template):
|
||||
# Silently truncate bytes exceeding template length
|
||||
return color[:len(self.template)]
|
||||
|
||||
# Expand 3 char codes and codes of half the required length.
|
||||
# Yet, let's presume that a 6-char code is alway meant to be
|
||||
# interpreted as a color and should never be expanded.
|
||||
if len(color) != 6 and len(color) == 3 or len(color) == (len(self.template) / 2):
|
||||
color = "".join(char*2 for char in color)
|
||||
|
||||
if len(color) == len(self.template): # Nothing more to do
|
||||
return color
|
||||
|
||||
# Add padding
|
||||
color = color + self.template[len(color):]
|
||||
return color
|
||||
|
||||
def set_color(self, color):
|
||||
""" Set color (hex) for this instance.
|
||||
|
||||
The color is then available via its color variable. """
|
||||
|
||||
color = self._pad_color(color)
|
||||
|
||||
self.color = color
|
||||
self.payload = bytearray.fromhex(color)
|
||||
|
||||
|
||||
class Dmx4(Dmx):
|
||||
""" Abstraction of the 4 channel LED cans in the club. """
|
||||
|
||||
# 3 bytes for color plus 1 byte for brightness
|
||||
template = "000000ff"
|
||||
|
||||
|
||||
class Dmx7(Dmx):
|
||||
""" Abstraction of the 7 channel LED cans in the club. """
|
||||
|
||||
# 3 bytes for color, another 3 bytes for special functions and 1 byte
|
||||
# for brightness
|
||||
template = "000000000000ff"
|
||||
|
||||
|
||||
class C4Room:
|
||||
""" Methods of rooms in the club. """
|
||||
|
||||
def __init__(self):
|
||||
self.c4 = C4Interface()
|
||||
self.switch_state = "" # State of switches in the like of str("0010")
|
||||
|
||||
def _interactive_light_switch(self):
|
||||
""" Interactively ask for input.
|
||||
|
||||
Returns str(userinput). Will not write to stdout if sys.stdin is
|
||||
no tty. """
|
||||
|
||||
if sys.stdin.isatty():
|
||||
print("[{}]".format(self.name))
|
||||
print("Please enter 0 or 1 for every light:")
|
||||
for level in range(len(self.switches)):
|
||||
print((level * '|') + ",- " + self.switches[level][0])
|
||||
|
||||
self.switch_state = self.get_switch_state()
|
||||
print(self.switch_state) # Present current state
|
||||
|
||||
try:
|
||||
userinput = sys.stdin.readline().rstrip('\n')
|
||||
except KeyboardInterrupt:
|
||||
print("\rInterrupted by user.")
|
||||
return ""
|
||||
|
||||
return userinput
|
||||
|
||||
def get_switch_state(self):
|
||||
""" Returns current state of switches as a string of 1s and 0s. """
|
||||
|
||||
state = ""
|
||||
req = []
|
||||
for topic in self.switches:
|
||||
req.append(topic[1])
|
||||
responce = self.c4.pull(req)
|
||||
|
||||
for sw in self.switches:
|
||||
for r in responce:
|
||||
if r.topic == sw[1]:
|
||||
state += str(int.from_bytes(r.payload, byteorder="little"))
|
||||
|
||||
if C4Interface.debug:
|
||||
print("[DEBUG] Warning: handing over fake data to allow for further execution!",
|
||||
file=sys.stderr)
|
||||
state = '0' * len(self.switches)
|
||||
|
||||
return state
|
||||
|
||||
def light_switch(self, userinput=""):
|
||||
""" Switch lamps in a room on or off. """
|
||||
|
||||
if not userinput:
|
||||
# Derive user input from stdin
|
||||
userinput = self._interactive_light_switch()
|
||||
if userinput == "": return
|
||||
|
||||
mode = 'n' # n = normal, a = AND, o = OR
|
||||
if not userinput.isdecimal():
|
||||
if userinput[0] == '&' and userinput[1:].isdecimal():
|
||||
# AND operator, applied later after doing some more validation
|
||||
userinput = userinput[1:]
|
||||
mode = 'a'
|
||||
elif userinput[0] == '|' and userinput[1:].isdecimal():
|
||||
# OR operator, applied later after doing some more validation
|
||||
userinput = userinput[1:]
|
||||
mode = 'o'
|
||||
elif userinput == ">>" or userinput == "<<":
|
||||
# Left and right shift
|
||||
if not self.switch_state:
|
||||
self.switch_state = self.get_switch_state()
|
||||
if userinput == ">>":
|
||||
# Right shift. '[2:]' removes the leading 'b0...'.
|
||||
new_state = bin(int(self.switch_state, base=2) >> 1)[2:]
|
||||
else:
|
||||
# Left shift. '[2:]' removes the leading 'b0...'.
|
||||
new_state = bin(int(self.switch_state, base=2) << 1)[2:]
|
||||
# Cut any exceeding leftmost bits
|
||||
new_state = new_state[-len(self.switches):]
|
||||
# Pad with leading zeroes
|
||||
userinput = new_state.rjust(len(self.switches), '0')
|
||||
else:
|
||||
print("You're not paying attention!", file=sys.stderr)
|
||||
return
|
||||
|
||||
if len(userinput) != len(self.switches):
|
||||
# First try to convert from integer if userinput's length doesn't
|
||||
# match
|
||||
if len(bin(int(userinput))) <= len(self.switches)+2:
|
||||
# ^ +2 because bin() returns strings like 'b0...'
|
||||
binary = bin(int(userinput))[2:] # Strip leading 'b0'
|
||||
# Pad with leading zeroes
|
||||
userinput = binary.rjust(len(self.switches), '0')
|
||||
else:
|
||||
print("Error: wrong number of digits (expected {}, got {})!".format(
|
||||
len(self.switches), len(userinput)))
|
||||
return False
|
||||
|
||||
# Now that everything special is expanded it's time to check if
|
||||
# userinput really consists of 1s and 0s only
|
||||
for digit in userinput:
|
||||
if digit not in "01":
|
||||
print("Error: invalid digit: " + digit, file=sys.stderr)
|
||||
return False
|
||||
|
||||
if mode == 'a': # AND operator
|
||||
if not self.switch_state:
|
||||
self.switch_state = self.get_switch_state()
|
||||
userinput = "".join(map(lambda x, y: str(int(x) & int(y)),
|
||||
userinput, self.switch_state))
|
||||
elif mode == 'o': # OR operator
|
||||
if not self.switch_state:
|
||||
self.switch_state = self.get_switch_state()
|
||||
userinput = "".join(map(lambda x, y: str(int(x) | int(y)),
|
||||
userinput, self.switch_state))
|
||||
|
||||
command=[]
|
||||
for i in range(len(self.switches)):
|
||||
# If we know their state, skip switches which are unchanged
|
||||
if self.switch_state:
|
||||
if self.switch_state[i] == userinput[i]: continue
|
||||
|
||||
command.append({
|
||||
"topic" : self.switches[i][1],
|
||||
"payload" : bytes([int(userinput[i])])
|
||||
})
|
||||
|
||||
return self.c4.push(command)
|
||||
|
||||
def set_colorscheme(self, colorscheme, magic):
|
||||
""" Apply colorscheme to the LED Cans in this room. """
|
||||
|
||||
command = []
|
||||
for light in self.lights:
|
||||
if colorscheme.color_for(light.topic):
|
||||
|
||||
# Update internal state of this Dmx object, so we can query
|
||||
# <object>.payload later
|
||||
light.set_color(colorscheme.color_for(light.topic))
|
||||
|
||||
if magic:
|
||||
# Send color to ghost instead of the "real" light
|
||||
# Generate the ghost topic for topic
|
||||
ghost = "ghosts" + light.topic[light.topic.find('/'):]
|
||||
|
||||
command.append({
|
||||
"topic" : ghost,
|
||||
"payload" : light.payload
|
||||
})
|
||||
else:
|
||||
# Send data to the real lanterns, not fluffyd.
|
||||
command.append({
|
||||
"topic" : light.topic,
|
||||
"payload" : light.payload
|
||||
})
|
||||
|
||||
# Nothing to do. May happen if a preset defines no color for a room.
|
||||
if command == []: return
|
||||
|
||||
if magic: # Do not retain "magic" messages
|
||||
return self.c4.push(command, retain=False)
|
||||
else:
|
||||
return self.c4.push(command)
|
||||
|
||||
|
||||
class Wohnzimmer(C4Room):
|
||||
""" Description of the Wohnzimmer. """
|
||||
|
||||
name = "Wohnzimmer"
|
||||
switches = (
|
||||
("Tür", "licht/wohnzimmer/tuer"),
|
||||
("Mitte", "licht/wohnzimmer/mitte"),
|
||||
("Flur", "licht/wohnzimmer/gang"),
|
||||
("Küche", "licht/wohnzimmer/kueche")
|
||||
)
|
||||
master = Dmx7("dmx/wohnzimmer/master")
|
||||
lights = (
|
||||
Dmx7("dmx/wohnzimmer/master"),
|
||||
Dmx7("dmx/wohnzimmer/tuer1"),
|
||||
Dmx7("dmx/wohnzimmer/tuer2"),
|
||||
Dmx7("dmx/wohnzimmer/tuer3"),
|
||||
Dmx7("dmx/wohnzimmer/mitte1"),
|
||||
Dmx7("dmx/wohnzimmer/mitte2"),
|
||||
Dmx7("dmx/wohnzimmer/mitte3"),
|
||||
Dmx7("dmx/wohnzimmer/gang"),
|
||||
Dmx7("dmx/wohnzimmer/baellebad"),
|
||||
Dmx("led/kitchen/sink")
|
||||
)
|
||||
|
||||
|
||||
class Plenarsaal(C4Room):
|
||||
""" Description of the Plenarsaal. """
|
||||
|
||||
name = "Plenarsaal"
|
||||
switches = (
|
||||
("Vorne/Wand", "licht/plenar/vornewand"),
|
||||
("Vorne/Fenster", "licht/plenar/vornefenster"),
|
||||
("Hinten/Wand", "licht/plenar/hintenwand"),
|
||||
("Hinten/Fenster", "licht/plenar/hintenfenster")
|
||||
)
|
||||
master = Dmx7("dmx/plenar/master")
|
||||
lights = (
|
||||
Dmx7("dmx/plenar/master"),
|
||||
Dmx7("dmx/plenar/vorne1"),
|
||||
Dmx7("dmx/plenar/vorne2"),
|
||||
Dmx7("dmx/plenar/vorne3"),
|
||||
Dmx7("dmx/plenar/hinten1"),
|
||||
Dmx7("dmx/plenar/hinten2"),
|
||||
Dmx7("dmx/plenar/hinten3"),
|
||||
Dmx7("dmx/plenar/hinten4")
|
||||
)
|
||||
|
||||
|
||||
class Fnordcenter(C4Room):
|
||||
""" Description of the Fnordcenter. """
|
||||
|
||||
name = "Fnordcenter"
|
||||
switches = (
|
||||
("Links (Fairydust)", "licht/fnord/links"),
|
||||
("Rechts (SCUMM)", "licht/fnord/rechts")
|
||||
)
|
||||
master = Dmx4("dmx/fnord/master")
|
||||
lights = (
|
||||
Dmx4("dmx/fnord/master"),
|
||||
Dmx4("dmx/fnord/scummfenster"),
|
||||
Dmx4("dmx/fnord/schranklinks"),
|
||||
Dmx4("dmx/fnord/fairyfenster"),
|
||||
Dmx4("dmx/fnord/schrankrechts")
|
||||
)
|
||||
|
||||
|
||||
class Keller(C4Room):
|
||||
""" Description of the Keller. """
|
||||
|
||||
name = "Keller"
|
||||
switches = (
|
||||
("Außen", "licht/keller/aussen"),
|
||||
("Innen", "licht/keller/innen"),
|
||||
("Vorne", "licht/keller/vorne")
|
||||
)
|
||||
master = None
|
||||
lights = ()
|
||||
|
||||
|
||||
class Kitchenlight:
|
||||
""" Interface to the Kitchenlight and its functions. """
|
||||
|
||||
|
@ -665,6 +360,337 @@ class Kitchenlight:
|
|||
self._switch(d)
|
||||
|
||||
|
||||
class Dmx:
|
||||
""" Abstraction of the 3 channel LED cans in the club. """
|
||||
|
||||
# 3 bytes for color, one each for red, green and blue
|
||||
template = "000000"
|
||||
|
||||
def __init__(self, topic, color=None):
|
||||
self.topic = topic
|
||||
self.set_color(color or self.template)
|
||||
self.is_master = topic.rfind("/master") == len(topic)-7 # 7 = len("/master")
|
||||
|
||||
def _pad_color(self, color):
|
||||
""" Merge hex color values or payloads into the template.
|
||||
|
||||
Expand 4 bit hex code notation (eg. #f0f) and pad with template
|
||||
to get a fitting payload for this kind of light. """
|
||||
|
||||
if len(color) > len(self.template):
|
||||
# Silently truncate bytes exceeding template length
|
||||
return color[:len(self.template)]
|
||||
|
||||
# Expand 3 char codes and codes of half the required length.
|
||||
# Yet, let's presume that a 6-char code is alway meant to be
|
||||
# interpreted as a color and should never be expanded.
|
||||
if len(color) != 6 and len(color) == 3 or len(color) == (len(self.template) / 2):
|
||||
color = "".join(char*2 for char in color)
|
||||
|
||||
if len(color) == len(self.template): # Nothing more to do
|
||||
return color
|
||||
|
||||
# Add padding
|
||||
color = color + self.template[len(color):]
|
||||
return color
|
||||
|
||||
def set_color(self, color):
|
||||
""" Set color (hex) for this instance.
|
||||
|
||||
The color is then available via its color variable. """
|
||||
|
||||
color = self._pad_color(color)
|
||||
|
||||
self.color = color
|
||||
self.payload = bytearray.fromhex(color)
|
||||
|
||||
|
||||
class Dmx4(Dmx):
|
||||
""" Abstraction of the 4 channel LED cans in the club. """
|
||||
|
||||
# 3 bytes for color plus 1 byte for brightness
|
||||
template = "000000ff"
|
||||
|
||||
|
||||
class Dmx7(Dmx):
|
||||
""" Abstraction of the 7 channel LED cans in the club. """
|
||||
|
||||
# 3 bytes for color, another 3 bytes for special functions and 1 byte
|
||||
# for brightness
|
||||
template = "000000000000ff"
|
||||
|
||||
|
||||
class C4Room:
|
||||
""" Methods of rooms in the club. """
|
||||
|
||||
def __init__(self):
|
||||
self.c4 = C4Interface()
|
||||
# get_switch_state() will store its result and a timestamp to reduce
|
||||
# requests to the broker
|
||||
self._switch_state = ("", 0.0)
|
||||
|
||||
def _interactive_light_switch(self):
|
||||
""" Interactively ask for input.
|
||||
|
||||
Returns str(userinput). Will not write to stdout if sys.stdin is
|
||||
no tty. """
|
||||
|
||||
if sys.stdin.isatty():
|
||||
print("[{}]".format(self.name))
|
||||
print("Please enter 0 or 1 for every light:")
|
||||
for level in range(len(self.switches)):
|
||||
print((level * '|') + ",- " + self.switches[level][0])
|
||||
|
||||
switch_state = self.get_switch_state()
|
||||
print(switch_state) # Present current state
|
||||
|
||||
try:
|
||||
userinput = sys.stdin.readline().rstrip('\n')
|
||||
except KeyboardInterrupt:
|
||||
print("\rInterrupted by user.")
|
||||
return ""
|
||||
|
||||
return userinput
|
||||
|
||||
def get_switch_state(self, max_age=5):
|
||||
""" Returns current state of switches as a string of 1s and 0s.
|
||||
|
||||
max_age specifies how old (in seconds) a cached responce from a
|
||||
previously done request may be before it is considered outdated. """
|
||||
|
||||
from time import time
|
||||
|
||||
# We store switch states in self._switch_state to reduce requests to
|
||||
# the broker. If this variable is neither empty nor too old, use it!
|
||||
if self._switch_state[0] != "":
|
||||
if time() - self._switch_state[1] <= max_age:
|
||||
return self._switch_state[0]
|
||||
|
||||
state = ""
|
||||
req = []
|
||||
for topic in self.switches:
|
||||
req.append(topic[1])
|
||||
responce = self.c4.pull(req)
|
||||
|
||||
for sw in self.switches:
|
||||
for r in responce:
|
||||
if r.topic == sw[1]:
|
||||
state += str(int.from_bytes(r.payload, byteorder="little"))
|
||||
|
||||
if C4Interface.debug:
|
||||
print("[DEBUG] Warning: handing over fake data to allow for further execution!",
|
||||
file=sys.stderr)
|
||||
state = '0' * len(self.switches)
|
||||
|
||||
self._switch_state = (state, time())
|
||||
return state
|
||||
|
||||
def light_switch(self, userinput=""):
|
||||
""" Switch lamps in a room on or off. """
|
||||
|
||||
if not userinput:
|
||||
# Derive user input from stdin
|
||||
userinput = self._interactive_light_switch()
|
||||
if userinput == "": return
|
||||
|
||||
# Let's support some geeky binary operations!
|
||||
mode = 'n' # n = normal, a = AND, o = OR
|
||||
if not userinput.isdecimal():
|
||||
if userinput[0] == '&' and userinput[1:].strip().isdecimal():
|
||||
# AND operator, applied later after doing some more validation
|
||||
userinput = userinput[1:].strip()
|
||||
mode = 'a'
|
||||
|
||||
elif userinput[0] == '|' and userinput[1:].strip().isdecimal():
|
||||
# OR operator, applied later after doing some more validation
|
||||
userinput = userinput[1:].strip()
|
||||
mode = 'o'
|
||||
|
||||
elif (userinput[:2] == ">>" or userinput[:2] == "<<") \
|
||||
and (userinput[2:].strip() == "" or userinput[2:].strip().isdecimal()):
|
||||
# Left or right shift
|
||||
# How far shall we shift?
|
||||
if userinput[2:].strip().isdecimal():
|
||||
shift_by = int(userinput[2:])
|
||||
else:
|
||||
shift_by = 1
|
||||
|
||||
# Retrieve the current state of switches
|
||||
switch_state = self.get_switch_state()
|
||||
if userinput[:2] == ">>":
|
||||
# Right shift. '[2:]' removes the leading 'b0...'.
|
||||
new_state = bin(int(switch_state, base=2) >> shift_by)[2:]
|
||||
else:
|
||||
# Left shift. '[2:]' removes the leading 'b0...'.
|
||||
new_state = bin(int(switch_state, base=2) << shift_by)[2:]
|
||||
# Cut any exceeding leftmost bits
|
||||
new_state = new_state[-len(self.switches):]
|
||||
# Pad with leading zeroes
|
||||
userinput = new_state.rjust(len(self.switches), '0')
|
||||
|
||||
else:
|
||||
# Oh no, input contained non-decimal characters which we could
|
||||
# not parse. :(
|
||||
print("Error: could not parse input!", file=sys.stderr)
|
||||
return
|
||||
|
||||
if len(userinput) != len(self.switches):
|
||||
# First try to convert from decimal if userinput's length doesn't
|
||||
# match
|
||||
if len(bin(int(userinput))) <= len(self.switches)+2:
|
||||
# ^ +2 because bin() returns strings like 'b0...'
|
||||
binary = bin(int(userinput))[2:] # Strip leading 'b0'
|
||||
# Pad with leading zeroes
|
||||
userinput = binary.rjust(len(self.switches), '0')
|
||||
else:
|
||||
print("Error: wrong number of digits (expected {}, got {})!".format(
|
||||
len(self.switches), len(userinput)), file=sys.stderr)
|
||||
return False
|
||||
|
||||
# Now that everything special is expanded it's time to check if
|
||||
# userinput really consists of 1s and 0s only
|
||||
for digit in userinput:
|
||||
if digit not in "01":
|
||||
print("Error: invalid digit: " + digit, file=sys.stderr)
|
||||
return False
|
||||
|
||||
if mode == 'a': # AND operator
|
||||
switch_state = self.get_switch_state()
|
||||
userinput = "".join(map(lambda x, y: str(int(x) & int(y)),
|
||||
userinput, switch_state))
|
||||
elif mode == 'o': # OR operator
|
||||
switch_state = self.get_switch_state()
|
||||
userinput = "".join(map(lambda x, y: str(int(x) | int(y)),
|
||||
userinput, switch_state))
|
||||
|
||||
command=[]
|
||||
for i in range(len(self.switches)):
|
||||
# Skip unchanged switches if we happen to know their state
|
||||
if "switch_state" in dir():
|
||||
if switch_state[i] == userinput[i]: continue
|
||||
|
||||
command.append({
|
||||
"topic" : self.switches[i][1],
|
||||
"payload" : bytes([int(userinput[i])])
|
||||
})
|
||||
|
||||
return self.c4.push(command)
|
||||
|
||||
def set_colorscheme(self, colorscheme, magic):
|
||||
""" Apply colorscheme to the LED Cans in this room. """
|
||||
|
||||
command = []
|
||||
for light in self.lights:
|
||||
if colorscheme.get_color_for(light.topic):
|
||||
|
||||
# Update internal state of this Dmx object, so we can query
|
||||
# <object>.payload later
|
||||
light.set_color(colorscheme.get_color_for(light.topic))
|
||||
|
||||
if magic:
|
||||
# Send color to ghost instead of the "real" light
|
||||
# Generate the ghost topic for topic
|
||||
ghost = "ghosts" + light.topic[light.topic.find('/'):]
|
||||
|
||||
command.append({
|
||||
"topic" : ghost,
|
||||
"payload" : light.payload
|
||||
})
|
||||
else:
|
||||
# Send data to the real lanterns, not fluffyd.
|
||||
command.append({
|
||||
"topic" : light.topic,
|
||||
"payload" : light.payload
|
||||
})
|
||||
|
||||
# Nothing to do. May happen if a preset defines no color for a room.
|
||||
if command == []: return
|
||||
|
||||
if magic: # Do not retain "magic" messages
|
||||
return self.c4.push(command, retain=False)
|
||||
else:
|
||||
return self.c4.push(command)
|
||||
|
||||
|
||||
class Wohnzimmer(C4Room):
|
||||
""" Description of the Wohnzimmer. """
|
||||
|
||||
name = "Wohnzimmer"
|
||||
switches = (
|
||||
("Tür", "licht/wohnzimmer/tuer"),
|
||||
("Mitte", "licht/wohnzimmer/mitte"),
|
||||
("Flur", "licht/wohnzimmer/gang"),
|
||||
("Küche", "licht/wohnzimmer/kueche")
|
||||
)
|
||||
master = Dmx7("dmx/wohnzimmer/master")
|
||||
lights = (
|
||||
Dmx7("dmx/wohnzimmer/master"),
|
||||
Dmx7("dmx/wohnzimmer/tuer1"),
|
||||
Dmx7("dmx/wohnzimmer/tuer2"),
|
||||
Dmx7("dmx/wohnzimmer/tuer3"),
|
||||
Dmx7("dmx/wohnzimmer/mitte1"),
|
||||
Dmx7("dmx/wohnzimmer/mitte2"),
|
||||
Dmx7("dmx/wohnzimmer/mitte3"),
|
||||
Dmx7("dmx/wohnzimmer/gang"),
|
||||
Dmx7("dmx/wohnzimmer/baellebad"),
|
||||
Dmx("led/kitchen/sink")
|
||||
)
|
||||
|
||||
|
||||
class Plenarsaal(C4Room):
|
||||
""" Description of the Plenarsaal. """
|
||||
|
||||
name = "Plenarsaal"
|
||||
switches = (
|
||||
("Vorne/Wand", "licht/plenar/vornewand"),
|
||||
("Vorne/Fenster", "licht/plenar/vornefenster"),
|
||||
("Hinten/Wand", "licht/plenar/hintenwand"),
|
||||
("Hinten/Fenster", "licht/plenar/hintenfenster")
|
||||
)
|
||||
master = Dmx7("dmx/plenar/master")
|
||||
lights = (
|
||||
Dmx7("dmx/plenar/master"),
|
||||
Dmx7("dmx/plenar/vorne1"),
|
||||
Dmx7("dmx/plenar/vorne2"),
|
||||
Dmx7("dmx/plenar/vorne3"),
|
||||
Dmx7("dmx/plenar/hinten1"),
|
||||
Dmx7("dmx/plenar/hinten2"),
|
||||
Dmx7("dmx/plenar/hinten3"),
|
||||
Dmx7("dmx/plenar/hinten4")
|
||||
)
|
||||
|
||||
|
||||
class Fnordcenter(C4Room):
|
||||
""" Description of the Fnordcenter. """
|
||||
|
||||
name = "Fnordcenter"
|
||||
switches = (
|
||||
("Links (Fairydust)", "licht/fnord/links"),
|
||||
("Rechts (SCUMM)", "licht/fnord/rechts")
|
||||
)
|
||||
master = Dmx4("dmx/fnord/master")
|
||||
lights = (
|
||||
Dmx4("dmx/fnord/master"),
|
||||
Dmx4("dmx/fnord/scummfenster"),
|
||||
Dmx4("dmx/fnord/schranklinks"),
|
||||
Dmx4("dmx/fnord/fairyfenster"),
|
||||
Dmx4("dmx/fnord/schrankrechts")
|
||||
)
|
||||
|
||||
|
||||
class Keller(C4Room):
|
||||
""" Description of the Keller. """
|
||||
|
||||
name = "Keller"
|
||||
switches = (
|
||||
("Außen", "licht/keller/aussen"),
|
||||
("Innen", "licht/keller/innen"),
|
||||
("Vorne", "licht/keller/vorne")
|
||||
)
|
||||
master = None
|
||||
lights = ()
|
||||
|
||||
|
||||
class ColorScheme:
|
||||
""" Abstraction of a colorscheme. """
|
||||
|
||||
|
@ -692,7 +718,7 @@ class ColorScheme:
|
|||
return self.from_file(autoinit)
|
||||
|
||||
def __bool__(self):
|
||||
# Return true if color_for has a chance to present anything useful
|
||||
# Return true if get_color_for has a chance to present anything useful
|
||||
if self.mapping: return True
|
||||
if self.single_color: return True
|
||||
if self.return_random_color: return True
|
||||
|
@ -764,44 +790,20 @@ class ColorScheme:
|
|||
color += hex(ch)[2:]*2
|
||||
return color
|
||||
|
||||
def _single_color(self):
|
||||
# Check if there is a range in the color string. If yes, replace it
|
||||
# by a random color.
|
||||
if self.single_color.find('-', 1, -1) == -1:
|
||||
return self.single_color
|
||||
def get_color_for(self, topic):
|
||||
""" Returns color for topic.
|
||||
|
||||
from random import randint
|
||||
color = ""
|
||||
for i in range(len(self.single_color)):
|
||||
if self.single_color[i] != '-':
|
||||
try:
|
||||
if self.single_color[i-1] == '-':
|
||||
continue
|
||||
elif self.single_color[i+1] == '-':
|
||||
continue
|
||||
except IndexError: pass
|
||||
color += self.single_color[i]
|
||||
else:
|
||||
f, t = self.single_color[i-1], self.single_color[i+1]
|
||||
color += hex(randint(int(f, base=16), int(t, base=16)))[2:]
|
||||
Returns the color (in hexadecimal notation) this ColorScheme
|
||||
associates with for the given topic. """
|
||||
|
||||
return color
|
||||
|
||||
def color_for(self, topic):
|
||||
""" Returns the color (in hexadecimal notation) this ColorScheme assumes
|
||||
for the given topic. """
|
||||
|
||||
# We need to take care not to return colors for both "normal" topics
|
||||
# and masters, as setting masters would override other settings.
|
||||
# If this ColorScheme has been read from a file though, we asssume that
|
||||
# the user has taken care of this and apply what we are told to apply.
|
||||
if self.mapping:
|
||||
if topic in self.mapping.keys():
|
||||
return self.mapping[topic]
|
||||
elif self.single_color:
|
||||
if not self._topic_is_master(topic):
|
||||
return self._single_color()
|
||||
return self.single_color
|
||||
elif self.return_random_color:
|
||||
# We need to take care not to return colors for both "normal" and
|
||||
# master topics
|
||||
if not self._topic_is_master(topic):
|
||||
return self._random_color()
|
||||
# Fallback
|
||||
|
@ -856,7 +858,7 @@ class ColorScheme:
|
|||
def from_color(self, color):
|
||||
""" Derive ColorScheme from a single hex color. """
|
||||
|
||||
self.single_color = color.lstrip('#').strip('-')
|
||||
self.single_color = color.lstrip('#')
|
||||
|
||||
def from_random(self):
|
||||
""" Derive ColorScheme from random colors. """
|
||||
|
@ -884,8 +886,10 @@ class ColorScheme:
|
|||
def store(self, name):
|
||||
""" Store the current state of all lights as preset. """
|
||||
|
||||
# First of all, refuse to override virtual presets
|
||||
if name in self._virtual_presets:
|
||||
# Refuse to save under a name used by virtual presets. Let's also
|
||||
# refuse to save as "config" or "c4ctrl.conf", as we may use one these
|
||||
# file names in the future.
|
||||
if name in self._virtual_presets or name in ["config", "c4ctrl.conf"]:
|
||||
print("I'm sorry Dave. I'm afraid I can't do that. The name \"{}\" \
|
||||
is reserved. Please choose a different one.".format(name))
|
||||
return False
|
||||
|
@ -894,11 +898,15 @@ is reserved. Please choose a different one.".format(name))
|
|||
fd = sys.stdout
|
||||
else:
|
||||
import os
|
||||
cfg_dir = self._get_cfg_dir(create=True) # Create config dir if missing
|
||||
|
||||
# Put preset in our config directory, create it if necessary
|
||||
cfg_dir = self._get_cfg_dir(create=True)
|
||||
# Strip any path elements
|
||||
name = os.path.split(name)[1]
|
||||
fn = os.path.join(cfg_dir, name)
|
||||
|
||||
try:
|
||||
fd = open(fn, 'xt')
|
||||
fd = open(fn, 'xt') # x = new file (writing), t = text mode
|
||||
except FileExistsError:
|
||||
print("A preset with this name already exists, overwrite? [y/N]",
|
||||
end=' ', flush=True)
|
||||
|
|
Loading…
Reference in a new issue