Set qos=2, adapted to new fluffyd version
This commit is contained in:
parent
66fa06c11a
commit
e64c157421
1 changed files with 118 additions and 121 deletions
237
c4ctrl.py
237
c4ctrl.py
|
@ -1,15 +1,24 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
# c4ctrl: Command line client for AutoC4
|
# c4ctrl: Command line client for AutoC4
|
||||||
|
#
|
||||||
|
# Author: Shy
|
||||||
|
|
||||||
|
"""
|
||||||
|
This is a command line client for Autoc4, the home automation system of the C4.
|
||||||
|
|
||||||
|
Some parts of it **may** be useful as python module for simple tasks.
|
||||||
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
class C4Interface():
|
class C4Interface():
|
||||||
"""Interaction with the C4 home automation system."""
|
""" Interaction with the C4 home automation system. """
|
||||||
|
|
||||||
port = 1883
|
port = 1883
|
||||||
broker = "autoc4.labor.koeln.ccc.de"
|
broker = "autoc4.labor.koeln.ccc.de"
|
||||||
qos = 0
|
qos = 2
|
||||||
retain = True
|
retain = True
|
||||||
debug = False
|
debug = False
|
||||||
|
|
||||||
|
@ -18,7 +27,10 @@ class C4Interface():
|
||||||
if topic: self.topic = topic
|
if topic: self.topic = topic
|
||||||
|
|
||||||
def push(self, cmd, topic=None, retain=None):
|
def push(self, cmd, topic=None, retain=None):
|
||||||
"""Send cmd to topic via the MQTT broker."""
|
""" Send a message to the MQTT broker.
|
||||||
|
|
||||||
|
cmd may a byte encoded payload or a list of byte encoded
|
||||||
|
payloads. """
|
||||||
from paho.mqtt import publish
|
from paho.mqtt import publish
|
||||||
|
|
||||||
# Overwrite defaults
|
# Overwrite defaults
|
||||||
|
@ -60,7 +72,9 @@ class C4Interface():
|
||||||
port=self.port)
|
port=self.port)
|
||||||
|
|
||||||
def pull(self, topic=[]):
|
def pull(self, topic=[]):
|
||||||
"""Return current state of topic."""
|
""" Return the state of a topic.
|
||||||
|
|
||||||
|
topic may be a list of topics or a single topic given as string. """
|
||||||
from paho.mqtt import subscribe
|
from paho.mqtt import subscribe
|
||||||
topic = topic or self.topic
|
topic = topic or self.topic
|
||||||
# <topic> must be a list
|
# <topic> must be a list
|
||||||
|
@ -78,7 +92,7 @@ class C4Interface():
|
||||||
port=self.port)
|
port=self.port)
|
||||||
|
|
||||||
def status(self):
|
def status(self):
|
||||||
"""Print current status (open or closed) of C4."""
|
""" Returns current status (string "open" or "closed") of the club. """
|
||||||
st = self.pull("club/status")
|
st = self.pull("club/status")
|
||||||
|
|
||||||
# Produce fake result to prevent errors if in debug mode
|
# Produce fake result to prevent errors if in debug mode
|
||||||
|
@ -88,9 +102,9 @@ class C4Interface():
|
||||||
st.payload = b'\x00'
|
st.payload = b'\x00'
|
||||||
|
|
||||||
if st.payload == b'\x01':
|
if st.payload == b'\x01':
|
||||||
print("Club is open")
|
return "open"
|
||||||
else:
|
else:
|
||||||
print("Club is closed")
|
return "closed"
|
||||||
|
|
||||||
def open_gate(self):
|
def open_gate(self):
|
||||||
"""Open the gate."""
|
"""Open the gate."""
|
||||||
|
@ -106,7 +120,7 @@ class C4Interface():
|
||||||
|
|
||||||
|
|
||||||
class Dmx:
|
class Dmx:
|
||||||
"""Abstraction of the 3 channel LED cans in the club."""
|
""" Abstraction of the 3 channel LED cans in the club. """
|
||||||
|
|
||||||
template = "000000"
|
template = "000000"
|
||||||
|
|
||||||
|
@ -115,13 +129,10 @@ class Dmx:
|
||||||
self.set_color(color or self.template)
|
self.set_color(color or self.template)
|
||||||
self.is_master = topic.rfind("/master") == len(topic)-7 # 7 = len("/master")
|
self.is_master = topic.rfind("/master") == len(topic)-7 # 7 = len("/master")
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<Dmx '{}: {}'>".format(self.topic, self.color)
|
|
||||||
|
|
||||||
def _pad_color(self, color):
|
def _pad_color(self, color):
|
||||||
"""Merge hex color value into hex template.
|
""" Merge hex color value into hex template.
|
||||||
|
|
||||||
Expand 4 bit hex code notation (eg. #f0f) and pad with template."""
|
Expand 4 bit hex code notation (eg. #f0f) and pad with template. """
|
||||||
if len(color) > len(self.template):
|
if len(color) > len(self.template):
|
||||||
# Silently truncate
|
# Silently truncate
|
||||||
return color[:len(self.template)]
|
return color[:len(self.template)]
|
||||||
|
@ -142,9 +153,9 @@ class Dmx:
|
||||||
return color
|
return color
|
||||||
|
|
||||||
def set_color(self, color):
|
def set_color(self, color):
|
||||||
"""Set color (hex) for this instance.
|
""" Set color (hex) for this instance.
|
||||||
|
|
||||||
The color is then available via its color variable."""
|
The color is then available via its color variable. """
|
||||||
color = self._pad_color(color)
|
color = self._pad_color(color)
|
||||||
|
|
||||||
self.color = color
|
self.color = color
|
||||||
|
@ -152,25 +163,19 @@ class Dmx:
|
||||||
|
|
||||||
|
|
||||||
class Dmx4(Dmx):
|
class Dmx4(Dmx):
|
||||||
"""Abstraction of the 4 channel LED cans in the club."""
|
""" Abstraction of the 4 channel LED cans in the club. """
|
||||||
|
|
||||||
template = "000000ff"
|
template = "000000ff"
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<Dmx4 '{}: {}'>".format(self.topic, self.color)
|
|
||||||
|
|
||||||
|
|
||||||
class Dmx7(Dmx):
|
class Dmx7(Dmx):
|
||||||
"""Abstraction of the 7 channel LED cans in the club."""
|
""" Abstraction of the 7 channel LED cans in the club. """
|
||||||
|
|
||||||
template = "000000000000ff"
|
template = "000000000000ff"
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<Dmx7 '{}: {}'>".format(self.topic, self.color)
|
|
||||||
|
|
||||||
|
|
||||||
class C4Room:
|
class C4Room:
|
||||||
"""Base class for club rooms."""
|
""" Methods of rooms in the club. """
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.c4 = C4Interface()
|
self.c4 = C4Interface()
|
||||||
|
@ -205,7 +210,8 @@ class C4Room:
|
||||||
return userinput
|
return userinput
|
||||||
|
|
||||||
def light_switch(self, userinput=""):
|
def light_switch(self, userinput=""):
|
||||||
"""Switch lamps in this rooms on and off."""
|
""" Switch lamps in a room on or off. """
|
||||||
|
|
||||||
if not userinput:
|
if not userinput:
|
||||||
userinput = self._interactive_light_switch()
|
userinput = self._interactive_light_switch()
|
||||||
if userinput == "": return
|
if userinput == "": return
|
||||||
|
@ -237,40 +243,42 @@ class C4Room:
|
||||||
|
|
||||||
return self.c4.push(cmd)
|
return self.c4.push(cmd)
|
||||||
|
|
||||||
def set_colorscheme(self, colorscheme, magic):
|
def set_colorscheme(self, colorscheme, no_magic):
|
||||||
"""Apply colorscheme to the LED Cans in this room."""
|
""" Apply colorscheme to the LED Cans in this room. """
|
||||||
cmd = []
|
cmd = []
|
||||||
for light in self.lights:
|
for light in self.lights:
|
||||||
if colorscheme.color_for(light.topic):
|
if colorscheme.color_for(light.topic):
|
||||||
if magic != "none" and magic != '0':
|
|
||||||
# Send color to ghost instead of the "real" light
|
|
||||||
mode_id, error = Fluffy().mode_id(magic)
|
|
||||||
if error:
|
|
||||||
print("Warning: unknown mode \"{}\". Using default.".format(
|
|
||||||
magic), file=sys.stderr)
|
|
||||||
|
|
||||||
|
# Update internal state of this Dmx object, so we can query
|
||||||
|
# <object>.payload later
|
||||||
light.set_color(colorscheme.color_for(light.topic))
|
light.set_color(colorscheme.color_for(light.topic))
|
||||||
cmd.append({
|
|
||||||
"topic" : Fluffy().ghostly_topic(light.topic),
|
if no_magic:
|
||||||
"payload" : Fluffy().ghostly_payload(light.payload, mode_id)
|
# Send data to the real lanterns, not fluffyd.
|
||||||
})
|
|
||||||
else:
|
|
||||||
light.set_color(colorscheme.color_for(light.topic))
|
|
||||||
cmd.append({
|
cmd.append({
|
||||||
"topic" : light.topic,
|
"topic" : light.topic,
|
||||||
"payload" : light.payload
|
"payload" : light.payload
|
||||||
})
|
})
|
||||||
|
else:
|
||||||
|
# Send color to ghost instead of the "real" light
|
||||||
|
# Generate the ghost topic for topic
|
||||||
|
ghost = "ghosts" + light.topic[light.topic.find('/'):]
|
||||||
|
|
||||||
|
cmd.append({
|
||||||
|
"topic" : ghost,
|
||||||
|
"payload" : light.payload
|
||||||
|
})
|
||||||
|
|
||||||
if cmd == []: return
|
if cmd == []: return
|
||||||
|
|
||||||
if magic: # Do not retain "magic" messages
|
if no_magic:
|
||||||
return self.c4.push(cmd, retain=(not magic))
|
|
||||||
else:
|
|
||||||
return self.c4.push(cmd)
|
return self.c4.push(cmd)
|
||||||
|
else: # Do not retain "magic" messages
|
||||||
|
return self.c4.push(cmd, retain=False)
|
||||||
|
|
||||||
|
|
||||||
class Wohnzimmer(C4Room):
|
class Wohnzimmer(C4Room):
|
||||||
"""The Wohnzimmer."""
|
""" Description of the Wohnzimmer. """
|
||||||
|
|
||||||
name = "Wohnzimmer"
|
name = "Wohnzimmer"
|
||||||
switches = (
|
switches = (
|
||||||
|
@ -295,7 +303,7 @@ class Wohnzimmer(C4Room):
|
||||||
|
|
||||||
|
|
||||||
class Plenarsaal(C4Room):
|
class Plenarsaal(C4Room):
|
||||||
"""The Plenarsaal."""
|
""" Description of the Plenarsaal. """
|
||||||
|
|
||||||
name = "Plenarsaal"
|
name = "Plenarsaal"
|
||||||
switches = (
|
switches = (
|
||||||
|
@ -318,7 +326,7 @@ class Plenarsaal(C4Room):
|
||||||
|
|
||||||
|
|
||||||
class Fnordcenter(C4Room):
|
class Fnordcenter(C4Room):
|
||||||
"""The Fnordcenter."""
|
""" Description of the Fnordcenter. """
|
||||||
|
|
||||||
name = "Fnordcenter"
|
name = "Fnordcenter"
|
||||||
switches = (
|
switches = (
|
||||||
|
@ -336,7 +344,7 @@ class Fnordcenter(C4Room):
|
||||||
|
|
||||||
|
|
||||||
class Keller(C4Room):
|
class Keller(C4Room):
|
||||||
"""The Keller."""
|
""" Description of the Keller. """
|
||||||
|
|
||||||
name = "Keller"
|
name = "Keller"
|
||||||
switches = (
|
switches = (
|
||||||
|
@ -349,7 +357,7 @@ class Keller(C4Room):
|
||||||
|
|
||||||
|
|
||||||
class Kitchenlight:
|
class Kitchenlight:
|
||||||
"""The Kitchenlight."""
|
""" The Kitchenlight. """
|
||||||
|
|
||||||
_END = "little" # Endianess
|
_END = "little" # Endianess
|
||||||
_available_modes = """
|
_available_modes = """
|
||||||
|
@ -374,7 +382,7 @@ class Kitchenlight:
|
||||||
self.autopower = autopower # Power on on every mode change?
|
self.autopower = autopower # Power on on every mode change?
|
||||||
|
|
||||||
def _switch(self, data, poweron=False, poweroff=False):
|
def _switch(self, data, poweron=False, poweroff=False):
|
||||||
"""Send commands via a C4Interface to the MQTT broker."""
|
""" Send commands via a C4Interface to the MQTT broker. """
|
||||||
if self.autopower or poweron or poweroff:
|
if self.autopower or poweron or poweroff:
|
||||||
c4 = C4Interface(self.topic)
|
c4 = C4Interface(self.topic)
|
||||||
cmd = []
|
cmd = []
|
||||||
|
@ -384,11 +392,11 @@ class Kitchenlight:
|
||||||
if poweroff:
|
if poweroff:
|
||||||
cmd.append({
|
cmd.append({
|
||||||
"topic" : self.powertopic,
|
"topic" : self.powertopic,
|
||||||
"payload" : bytearray((0,))})
|
"payload" : b'\x00'})
|
||||||
elif self.autopower or poweron:
|
elif self.autopower or poweron:
|
||||||
cmd.append({
|
cmd.append({
|
||||||
"topic" : self.powertopic,
|
"topic" : self.powertopic,
|
||||||
"payload" : bytearray((1,))})
|
"payload" : b'\x01'})
|
||||||
c4.push(cmd)
|
c4.push(cmd)
|
||||||
else:
|
else:
|
||||||
c4 = C4Interface(self.topic)
|
c4 = C4Interface(self.topic)
|
||||||
|
@ -421,13 +429,13 @@ class Kitchenlight:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def empty(self):
|
def empty(self):
|
||||||
"""Set to mode "empty" and turn off Kitchenlight."""
|
""" Set to mode "empty" and turn off Kitchenlight. """
|
||||||
# Screen 0
|
# Screen 0
|
||||||
d = int(0).to_bytes(4, self._END)
|
d = int(0).to_bytes(4, self._END)
|
||||||
self._switch(d, poweroff=True)
|
self._switch(d, poweroff=True)
|
||||||
|
|
||||||
def checker(self, delay=500, colA="0000ff", colB="00ff00"):
|
def checker(self, delay=500, colA="0000ff", colB="00ff00"):
|
||||||
"""Set to mode "checker"."""
|
""" Set to mode "checker". """
|
||||||
# Kind of a hack: lets treat the two colors as DMX lights
|
# Kind of a hack: lets treat the two colors as DMX lights
|
||||||
ca = Dmx("checker/a", colA.lstrip('#'))
|
ca = Dmx("checker/a", colA.lstrip('#'))
|
||||||
cb = Dmx("checker/b", colB.lstrip('#'))
|
cb = Dmx("checker/b", colB.lstrip('#'))
|
||||||
|
@ -448,7 +456,7 @@ class Kitchenlight:
|
||||||
self._switch(d)
|
self._switch(d)
|
||||||
|
|
||||||
def matrix(self, lines=8):
|
def matrix(self, lines=8):
|
||||||
"""Set to mode "matrix"."""
|
""" Set to mode "matrix". """
|
||||||
if int(lines) > 31: lines = 31 # Maximal line count
|
if int(lines) > 31: lines = 31 # Maximal line count
|
||||||
d = bytearray(8)
|
d = bytearray(8)
|
||||||
v = memoryview(d)
|
v = memoryview(d)
|
||||||
|
@ -458,7 +466,7 @@ class Kitchenlight:
|
||||||
self._switch(d)
|
self._switch(d)
|
||||||
|
|
||||||
def moodlight(self, mode=1):
|
def moodlight(self, mode=1):
|
||||||
"""Set to mode "moodlight"."""
|
""" Set to mode "moodlight". """
|
||||||
if mode == 1: # Mode "Colorwheel"
|
if mode == 1: # Mode "Colorwheel"
|
||||||
d = bytearray(19)
|
d = bytearray(19)
|
||||||
v = memoryview(d)
|
v = memoryview(d)
|
||||||
|
@ -490,7 +498,7 @@ class Kitchenlight:
|
||||||
self._switch(d)
|
self._switch(d)
|
||||||
|
|
||||||
def openchaos(self, delay=1000):
|
def openchaos(self, delay=1000):
|
||||||
"""Set to mode "openchaos"."""
|
""" Set to mode "openchaos". """
|
||||||
d = bytearray(8)
|
d = bytearray(8)
|
||||||
v = memoryview(d)
|
v = memoryview(d)
|
||||||
# Screen 4
|
# Screen 4
|
||||||
|
@ -499,13 +507,13 @@ class Kitchenlight:
|
||||||
self._switch(d)
|
self._switch(d)
|
||||||
|
|
||||||
def pacman(self):
|
def pacman(self):
|
||||||
"""Set to mode "pacman"."""
|
""" Set to mode "pacman". """
|
||||||
# Screen 5
|
# Screen 5
|
||||||
d = int(5).to_bytes(4, self._END)
|
d = int(5).to_bytes(4, self._END)
|
||||||
self._switch(d)
|
self._switch(d)
|
||||||
|
|
||||||
def sine(self):
|
def sine(self):
|
||||||
"""Set to mode "sine"."""
|
""" Set to mode "sine". """
|
||||||
# Screen 6
|
# Screen 6
|
||||||
d = int(6).to_bytes(4, self._END)
|
d = int(6).to_bytes(4, self._END)
|
||||||
self._switch(d)
|
self._switch(d)
|
||||||
|
@ -514,7 +522,7 @@ class Kitchenlight:
|
||||||
# the Kitchenlight. Evil strobo!
|
# the Kitchenlight. Evil strobo!
|
||||||
|
|
||||||
def text(self, text="Hello World", delay=250):
|
def text(self, text="Hello World", delay=250):
|
||||||
"""Set to mode "text"."""
|
""" Set to mode "text". """
|
||||||
text = text.encode("ascii", "ignore")
|
text = text.encode("ascii", "ignore")
|
||||||
if len(text) > 256: # Maximum text length
|
if len(text) > 256: # Maximum text length
|
||||||
print("Warning: text length must not exceed 256 characters!", file=sys.stderr)
|
print("Warning: text length must not exceed 256 characters!", file=sys.stderr)
|
||||||
|
@ -529,22 +537,23 @@ class Kitchenlight:
|
||||||
self._switch(d)
|
self._switch(d)
|
||||||
|
|
||||||
def flood(self):
|
def flood(self):
|
||||||
"""Set to mode "flood"."""
|
""" Set to mode "flood". """
|
||||||
# Screen 9
|
# Screen 9
|
||||||
d = int(9).to_bytes(4, self._END)
|
d = int(9).to_bytes(4, self._END)
|
||||||
self._switch(d)
|
self._switch(d)
|
||||||
|
|
||||||
def clock(self):
|
def clock(self):
|
||||||
"""Set to mode "clock"."""
|
""" Set to mode "clock". """
|
||||||
# Screen 11
|
# Screen 11
|
||||||
d = int(11).to_bytes(4, self._END)
|
d = int(11).to_bytes(4, self._END)
|
||||||
self._switch(d)
|
self._switch(d)
|
||||||
|
|
||||||
|
|
||||||
class ColorScheme:
|
class ColorScheme:
|
||||||
"""Abstraction of a colorscheme."""
|
""" Abstraction of a colorscheme. """
|
||||||
|
|
||||||
# Names of virtual presets
|
# Names of virtual presets. These are always listed as available and the
|
||||||
|
# user may not save presets under this name.
|
||||||
_virtual_presets = ["off", "random"]
|
_virtual_presets = ["off", "random"]
|
||||||
|
|
||||||
def __init__(self, autoinit=""):
|
def __init__(self, autoinit=""):
|
||||||
|
@ -574,7 +583,8 @@ class ColorScheme:
|
||||||
else: return False
|
else: return False
|
||||||
|
|
||||||
def _get_cfg_dir(self, quiet=False, create=False):
|
def _get_cfg_dir(self, quiet=False, create=False):
|
||||||
"""Returns path of the config dir."""
|
""" Returns path of the config dir. """
|
||||||
|
|
||||||
import os
|
import os
|
||||||
# The name of our config directory
|
# The name of our config directory
|
||||||
XDG_NAME = "c4ctrl"
|
XDG_NAME = "c4ctrl"
|
||||||
|
@ -601,7 +611,7 @@ class ColorScheme:
|
||||||
return cfg_dir
|
return cfg_dir
|
||||||
|
|
||||||
def _expand_preset(self, preset):
|
def _expand_preset(self, preset):
|
||||||
"""Tries to expand given string to a valid preset name."""
|
""" Tries to expand given string to a valid preset name. """
|
||||||
import os
|
import os
|
||||||
if not self.available:
|
if not self.available:
|
||||||
cfg_dir = self._get_cfg_dir(quiet=True)
|
cfg_dir = self._get_cfg_dir(quiet=True)
|
||||||
|
@ -620,11 +630,13 @@ class ColorScheme:
|
||||||
return preset
|
return preset
|
||||||
|
|
||||||
def _topic_is_master(self, topic):
|
def _topic_is_master(self, topic):
|
||||||
"""Does the given topic look like a master topic?"""
|
""" Does the given topic look like a master topic? """
|
||||||
|
|
||||||
return topic.lower().rfind("/master") == len(topic)-7 # 7 = len("/master")
|
return topic.lower().rfind("/master") == len(topic)-7 # 7 = len("/master")
|
||||||
|
|
||||||
def _random_color(self):
|
def _random_color(self):
|
||||||
"""Returns a 3*4 bit pseudo random color in 6 char hex notation."""
|
""" Returns a 3*4 bit pseudo random color in 6 char hex notation. """
|
||||||
|
|
||||||
from random import randint, sample
|
from random import randint, sample
|
||||||
chls = [15]
|
chls = [15]
|
||||||
chls.append(randint(0,15))
|
chls.append(randint(0,15))
|
||||||
|
@ -660,9 +672,13 @@ class ColorScheme:
|
||||||
return color
|
return color
|
||||||
|
|
||||||
def color_for(self, topic):
|
def color_for(self, topic):
|
||||||
"""Returns the color (hex) this ColorScheme provides for the given 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
|
# We need to take care not to return colors for both "normal" topics
|
||||||
# and masters, as setting masters would override other settings
|
# 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 self.mapping:
|
||||||
if topic in self.mapping.keys():
|
if topic in self.mapping.keys():
|
||||||
return self.mapping[topic]
|
return self.mapping[topic]
|
||||||
|
@ -676,7 +692,8 @@ class ColorScheme:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def from_file(self, preset):
|
def from_file(self, preset):
|
||||||
"""Load ColorScheme from file."""
|
""" Load ColorScheme from file. """
|
||||||
|
|
||||||
if preset == '-':
|
if preset == '-':
|
||||||
fd = sys.stdin
|
fd = sys.stdin
|
||||||
else:
|
else:
|
||||||
|
@ -721,15 +738,15 @@ class ColorScheme:
|
||||||
fd.close()
|
fd.close()
|
||||||
|
|
||||||
def from_color(self, color):
|
def from_color(self, color):
|
||||||
"""Derive ColorScheme from a single hex color."""
|
""" Derive ColorScheme from a single hex color. """
|
||||||
self.single_color = color.lstrip('#').strip('-')
|
self.single_color = color.lstrip('#').strip('-')
|
||||||
|
|
||||||
def from_random(self):
|
def from_random(self):
|
||||||
"""Derive ColorScheme from random colors."""
|
""" Derive ColorScheme from random colors. """
|
||||||
self.return_random_color = True
|
self.return_random_color = True
|
||||||
|
|
||||||
def list_available(self):
|
def list_available(self):
|
||||||
"""List available presets."""
|
""" List available presets. """
|
||||||
import os
|
import os
|
||||||
cfg_dir = self._get_cfg_dir()
|
cfg_dir = self._get_cfg_dir()
|
||||||
if not cfg_dir:
|
if not cfg_dir:
|
||||||
|
@ -745,7 +762,7 @@ class ColorScheme:
|
||||||
print(" " + entry)
|
print(" " + entry)
|
||||||
|
|
||||||
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. """
|
||||||
# First of all, refuse to override virtual presets
|
# First of all, refuse to override virtual presets
|
||||||
if name in self._virtual_presets:
|
if name in self._virtual_presets:
|
||||||
print("I'm sorry Dave. I'm afraid I can't do that. The name \"{}\" is reserved. Please choose a different one.".format(name))
|
print("I'm sorry Dave. I'm afraid I can't do that. The name \"{}\" is reserved. Please choose a different one.".format(name))
|
||||||
|
@ -809,39 +826,8 @@ class ColorScheme:
|
||||||
print("Wrote preset \"{}\"".format(name))
|
print("Wrote preset \"{}\"".format(name))
|
||||||
|
|
||||||
|
|
||||||
class Fluffy:
|
|
||||||
"""Fluffyd functions."""
|
|
||||||
|
|
||||||
modes = {
|
|
||||||
# Fluffy modes and their id's
|
|
||||||
"fade" : 1,
|
|
||||||
"wave" : 4,
|
|
||||||
"pulse" : 8,
|
|
||||||
"emp" : 9,
|
|
||||||
"flash" : 12
|
|
||||||
}
|
|
||||||
|
|
||||||
def ghostly_topic(self, topic):
|
|
||||||
# Return the ghost topic of topic
|
|
||||||
return "ghosts" + topic[topic.find('/'):]
|
|
||||||
|
|
||||||
def ghostly_payload(self, payload, mode_id):
|
|
||||||
return payload + int(mode_id).to_bytes(1, "little")
|
|
||||||
|
|
||||||
def mode_id(self, name):
|
|
||||||
if name.isdecimal() and int(name) <= 255:
|
|
||||||
# Let's trust the user with this
|
|
||||||
return (int(name), False)
|
|
||||||
else:
|
|
||||||
if name.lower() in self.modes.keys():
|
|
||||||
return (self.modes[name.lower()], False)
|
|
||||||
|
|
||||||
# Fallback
|
|
||||||
return (0, True)
|
|
||||||
|
|
||||||
|
|
||||||
class RemotePresets:
|
class RemotePresets:
|
||||||
"""Remote preset control."""
|
""" Remote preset control. """
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.map = {
|
self.map = {
|
||||||
|
@ -878,7 +864,7 @@ class RemotePresets:
|
||||||
}
|
}
|
||||||
|
|
||||||
def _expand_room_name(self, name):
|
def _expand_room_name(self, name):
|
||||||
"""Try to expand partial names."""
|
""" Returns a valid room name expanded from the given name. """
|
||||||
if name in self.map.keys():
|
if name in self.map.keys():
|
||||||
# Return on exact match
|
# Return on exact match
|
||||||
return name
|
return name
|
||||||
|
@ -890,12 +876,15 @@ class RemotePresets:
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def _expand_preset_name(self, name, rooms, available):
|
def _expand_preset_name(self, name, rooms, available):
|
||||||
"""Try to expand partial preset names.
|
""" Returns a valid preset name expanded from the given name.
|
||||||
|
|
||||||
<rooms> must be a list of rooms to consider.
|
Takes care to match only presets which are available for all rooms
|
||||||
<available> must be a dict as returned by query_available()."""
|
specified.
|
||||||
# We need to take care to match only presets which are available for
|
|
||||||
# every room specified
|
rooms is a list of rooms for which the preset should be a valid
|
||||||
|
option.
|
||||||
|
available is a dict containing valid presets for rooms as returned
|
||||||
|
by query_available(). """
|
||||||
|
|
||||||
# Strip every "global" out of the room list. We take special care of
|
# Strip every "global" out of the room list. We take special care of
|
||||||
# "global" later on.
|
# "global" later on.
|
||||||
|
@ -932,7 +921,7 @@ class RemotePresets:
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def query_available(self, rooms=["global"]):
|
def query_available(self, rooms=["global"]):
|
||||||
"""Returns a dict of remotely available presets for [rooms]."""
|
""" Returns a dict of remotely available presets for [rooms]. """
|
||||||
import json
|
import json
|
||||||
|
|
||||||
# Presets in "global" are available everywhere and should always be included
|
# Presets in "global" are available everywhere and should always be included
|
||||||
|
@ -961,7 +950,8 @@ class RemotePresets:
|
||||||
return available
|
return available
|
||||||
|
|
||||||
def list_available(self, room="global"):
|
def list_available(self, room="global"):
|
||||||
"""Print a list of available Presets."""
|
""" Print a list of available Presets. """
|
||||||
|
|
||||||
room = self._expand_room_name(room)
|
room = self._expand_room_name(room)
|
||||||
available = self.query_available([room])
|
available = self.query_available([room])
|
||||||
|
|
||||||
|
@ -974,7 +964,8 @@ class RemotePresets:
|
||||||
print( " " + preset)
|
print( " " + preset)
|
||||||
|
|
||||||
def apply_preset(self, preset, rooms=["global"]):
|
def apply_preset(self, preset, rooms=["global"]):
|
||||||
"""Apply preset to given rooms."""
|
""" Apply preset to given rooms. """
|
||||||
|
|
||||||
# Strip spaces and expand rooms names
|
# Strip spaces and expand rooms names
|
||||||
for i in range(len(rooms)):
|
for i in range(len(rooms)):
|
||||||
rooms[i] = self._expand_room_name(rooms[i].strip())
|
rooms[i] = self._expand_room_name(rooms[i].strip())
|
||||||
|
@ -1015,6 +1006,7 @@ if __name__ == "__main__":
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-d", "--debug", action="store_true",
|
"-d", "--debug", action="store_true",
|
||||||
help="display what would be send to the MQTT broker, but do not actually connect")
|
help="display what would be send to the MQTT broker, but do not actually connect")
|
||||||
|
|
||||||
# Various club functions
|
# Various club functions
|
||||||
group_fn = parser.add_argument_group(title="various functions")
|
group_fn = parser.add_argument_group(title="various functions")
|
||||||
group_fn.add_argument(
|
group_fn.add_argument(
|
||||||
|
@ -1026,6 +1018,7 @@ if __name__ == "__main__":
|
||||||
group_fn.add_argument(
|
group_fn.add_argument(
|
||||||
"-S", "--shutdown", action="count",
|
"-S", "--shutdown", action="count",
|
||||||
help="shutdown (give twice to force shutdown)")
|
help="shutdown (give twice to force shutdown)")
|
||||||
|
|
||||||
# Kitchenlight control
|
# Kitchenlight control
|
||||||
group_kl = parser.add_argument_group(title="Kitchenlight control")
|
group_kl = parser.add_argument_group(title="Kitchenlight control")
|
||||||
group_kl.add_argument(
|
group_kl.add_argument(
|
||||||
|
@ -1034,6 +1027,7 @@ if __name__ == "__main__":
|
||||||
group_kl.add_argument(
|
group_kl.add_argument(
|
||||||
"-i", "--list-kl-modes", action="store_true",
|
"-i", "--list-kl-modes", action="store_true",
|
||||||
help="list available Kitchenlight modes and their options")
|
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 (which may be abbreviated), '#' followed by a color value in hex notation (eg. \"#ff0066\") or '-' to read from stdin.")
|
description="PRESET may be either a preset name (which may be abbreviated), '#' followed by a color value in hex notation (eg. \"#ff0066\") or '-' to read from stdin.")
|
||||||
|
@ -1047,14 +1041,15 @@ if __name__ == "__main__":
|
||||||
"-f", "--fnordcenter", type=str, dest="f_color", metavar="PRESET",
|
"-f", "--fnordcenter", type=str, dest="f_color", metavar="PRESET",
|
||||||
help="apply local colorscheme PRESET to Fnordcenter")
|
help="apply local colorscheme PRESET to Fnordcenter")
|
||||||
group_cl.add_argument(
|
group_cl.add_argument(
|
||||||
"-m", "--magic", type=str, default="fade", metavar="MODE",
|
"-N", "--no-magic", action="store_true",
|
||||||
help="EXPERIMENTAL: blend into preset (needs a running instance of fluffyd on the network). MODE is either \"fade\", \"wave\", \"pulse\", \"emp\", \"flash\" or \"none\".")
|
help="Do not use fluffyd to change colors.")
|
||||||
group_cl.add_argument(
|
group_cl.add_argument(
|
||||||
"-l", "--list-presets", action="store_true",
|
"-l", "--list-presets", action="store_true",
|
||||||
help="list locally 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 ('-' to write to stdout)")
|
help="store current state as preset NAME ('-' to write to stdout)")
|
||||||
|
|
||||||
# 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="BINARY_CODE is a string of 0s or 1s for every light in the room. Accepts integers also. Will show some information and ask for input if omitted.")
|
description="BINARY_CODE is a string of 0s or 1s for every light in the room. Accepts integers also. Will show some information and ask for input if omitted.")
|
||||||
|
@ -1070,6 +1065,7 @@ if __name__ == "__main__":
|
||||||
group_sw.add_argument(
|
group_sw.add_argument(
|
||||||
"-K", nargs='?', dest="k_switch", const="", metavar="BINARY_CODE",
|
"-K", nargs='?', dest="k_switch", const="", metavar="BINARY_CODE",
|
||||||
help="switch lights in Keller on/off")
|
help="switch lights in Keller on/off")
|
||||||
|
|
||||||
# Remote presets
|
# Remote presets
|
||||||
group_rp = parser.add_argument_group(title="remote preset functions",
|
group_rp = parser.add_argument_group(title="remote preset functions",
|
||||||
description="Available room names are \"wohnzimmer\", \"plenar\", \"fnord\" and \"keller\". Preset and room names may be abbreviated.")
|
description="Available room names are \"wohnzimmer\", \"plenar\", \"fnord\" and \"keller\". Preset and room names may be abbreviated.")
|
||||||
|
@ -1085,7 +1081,8 @@ if __name__ == "__main__":
|
||||||
if args.debug:
|
if args.debug:
|
||||||
C4Interface.debug = True
|
C4Interface.debug = True
|
||||||
if args.status:
|
if args.status:
|
||||||
C4Interface().status()
|
status = C4Interface().status()
|
||||||
|
print("Club is", status)
|
||||||
if args.gate:
|
if args.gate:
|
||||||
C4Interface().open_gate()
|
C4Interface().open_gate()
|
||||||
if args.shutdown:
|
if args.shutdown:
|
||||||
|
@ -1113,15 +1110,15 @@ if __name__ == "__main__":
|
||||||
if args.w_color:
|
if args.w_color:
|
||||||
if args.w_color not in presets:
|
if args.w_color not in presets:
|
||||||
presets[args.w_color] = ColorScheme(autoinit=args.w_color)
|
presets[args.w_color] = ColorScheme(autoinit=args.w_color)
|
||||||
if presets[args.w_color]: Wohnzimmer().set_colorscheme(presets[args.w_color], args.magic)
|
if presets[args.w_color]: Wohnzimmer().set_colorscheme(presets[args.w_color], args.no_magic)
|
||||||
if args.p_color:
|
if args.p_color:
|
||||||
if args.p_color not in presets:
|
if args.p_color not in presets:
|
||||||
presets[args.p_color] = ColorScheme(autoinit=args.p_color)
|
presets[args.p_color] = ColorScheme(autoinit=args.p_color)
|
||||||
if presets[args.p_color]: Plenarsaal().set_colorscheme(presets[args.p_color], args.magic)
|
if presets[args.p_color]: Plenarsaal().set_colorscheme(presets[args.p_color], args.no_magic)
|
||||||
if args.f_color:
|
if args.f_color:
|
||||||
if args.f_color not in presets:
|
if args.f_color not in presets:
|
||||||
presets[args.f_color] = ColorScheme(autoinit=args.f_color)
|
presets[args.f_color] = ColorScheme(autoinit=args.f_color)
|
||||||
if presets[args.f_color]: Fnordcenter().set_colorscheme(presets[args.f_color], args.magic)
|
if presets[args.f_color]: Fnordcenter().set_colorscheme(presets[args.f_color], args.no_magic)
|
||||||
if args.list_presets:
|
if args.list_presets:
|
||||||
ColorScheme().list_available()
|
ColorScheme().list_available()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue