Compare commits

..

No commits in common. "master" and "dev_db" have entirely different histories.

44 changed files with 2927 additions and 3804 deletions

3
.gitmodules vendored
View File

@ -10,6 +10,3 @@
[submodule "geo"] [submodule "geo"]
path = geo path = geo
url = https://git.mount-mockery.de/pylib/geo.git url = https://git.mount-mockery.de/pylib/geo.git
[submodule "devdi"]
path = devdi
url = https://git.mount-mockery.de/smarthome/smart_devdi.git

2
.vscode/launch.json vendored
View File

@ -6,7 +6,7 @@
"configurations": [ "configurations": [
{ {
"name": "Python: Main File execution", "name": "Python: Main File execution",
"type": "debugpy", "type": "python",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/smart_brain.py", "program": "${workspaceFolder}/smart_brain.py",
"console": "integratedTerminal", "console": "integratedTerminal",

23
.vscode/settings.json vendored
View File

@ -1,14 +1,11 @@
{ {
"python.defaultInterpreterPath": "./venv/bin/python", "python.defaultInterpreterPath": "./venv/bin/python",
"autopep8.args": ["--max-line-length=150"], "editor.formatOnSave": true,
"[python]": { "autopep8.args": [
"python.formatting.provider": "none", "--max-line-length=150"
"editor.defaultFormatter": "ms-python.autopep8", ],
"editor.formatOnSave": true "editor.fontSize": 14,
}, "emmet.includeLanguages": {
"editor.fontSize": 14, "django-html": "html"
"emmet.includeLanguages": { "django-html": "html" }, }
"python.testing.pytestArgs": ["-v", "--cov", "--cov-report=xml", "__test__"], }
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}

41
__install__.py Normal file
View File

@ -0,0 +1,41 @@
#!/bin/python
#
import os
import sys
SERVICE_FILE = """
[Unit]
Description=Smarthome Ambient Information Service
After=network-online.target
Wants=network-online.target
[Service]
User=%(UID)d
Group=%(GID)d
ExecStart=%(MY_PATH)s/smart_brain.sh
Type=simple
[Install]
WantedBy=default.target
"""
def help():
print("Usage: prog <UID> <GID> <TARGET_PATH>")
if __name__ == "__main__":
if len(sys.argv) == 4:
try:
uid = int(sys.argv[1])
gid = int(sys.argv[2])
except ValueError:
help()
else:
if os.path.isdir(sys.argv[3]):
with open(os.path.join(sys.argv[3], 'smart_brain.service'), "w") as fh:
fh.write(SERVICE_FILE % {
"MY_PATH": os.path.dirname(os.path.abspath(__file__)),
"UID": uid,
"GID": gid})
else:
help()
else:
help()

847
__simulation__/devices.py Normal file
View File

@ -0,0 +1,847 @@
import colored
import json
import sys
import task
import time
COLOR_GUI_ACTIVE = colored.fg("light_blue")
COLOR_GUI_PASSIVE = COLOR_GUI_ACTIVE + colored.attr("dim")
COLOR_SHELLY = colored.fg("light_magenta")
COLOR_POWERPLUG = colored.fg("light_cyan")
COLOR_LIGHT_ACTIVE = colored.fg("yellow")
COLOR_LIGHT_PASSIVE = COLOR_LIGHT_ACTIVE + colored.attr("dim")
COLOR_MOTION_SENSOR = colored.fg("dark_orange_3b")
COLOR_RADIATOR_VALVE = colored.fg("red")
COLOR_REMOTE = colored.fg("green")
def payload_filter(payload):
try:
return json.loads(payload)
except json.decoder.JSONDecodeError:
return payload.decode("utf-8")
def command_int_value(value):
try:
return int(value)
except TypeError:
print("You need to give a integer parameter not '%s'" % str(value))
def command_float_value(value):
try:
return float(value)
except TypeError:
print("You need to give a integer parameter not '%s'" % str(value))
def devicename(topic):
return " - ".join(topic.split('/')[1:])
def percent_bar(value):
rv = ""
for i in range(0, 10):
rv += u"\u25ac" if (value - 5) > 10*i else u"\u25ad"
return rv
def print_light(color, state, topic, description, led=False):
if led is True:
if state is True:
icon = colored.fg('green') + "\u2b24" + color
else:
icon = colored.fg('light_gray') + "\u2b24" + color
else:
icon = u'\u2b24' if state is True else u'\u25ef'
print(color + 10 * ' ' + icon + 9 * ' ' + devicename(topic), description + colored.attr("reset"))
def print_switch(color, state, topic, description):
icon = u'\u25a0' if state is True else u'\u25a1'
print(color + 10 * ' ' + icon + 9 * ' ' + devicename(topic), description + colored.attr("reset"))
def print_percent(color, prefix, perc_value, value_str, topic, description):
if len(prefix) > 1 or len(value_str) > 7:
raise ValueError("Length of prefix (%d) > 1 or length of value_str (%d) > 7" % (len(prefix), len(value_str)))
print(color + prefix + percent_bar(perc_value), value_str + (8 - len(value_str)) * ' ' + devicename(topic), description + colored.attr("reset"))
class base(object):
AUTOSEND = True
COMMANDS = []
def __init__(self, mqtt_client, topic):
self.mqtt_client = mqtt_client
self.topic = topic
#
self.data = {}
self.callbacks = {}
self.names = {}
self.commands = self.COMMANDS[:]
#
self.mqtt_client.add_callback(self.topic, self.__rx__)
self.mqtt_client.add_callback(self.topic + '/#', self.__rx__)
def add_callback(self, key, callback, value):
if self.callbacks.get(key) is None:
self.callbacks[key] = []
self.callbacks[key].append((callback, value))
def add_channel_name(self, key, name):
self.names[key] = name
def capabilities(self):
return self.commands
def store_data(self, *args, **kwargs):
keys_changed = []
for key in kwargs:
if kwargs[key] is not None and kwargs[key] != self.data.get(key):
keys_changed.append(key)
self.data[key] = kwargs[key]
for callback, value in self.callbacks.get(key, [(None, None)]):
if callback is not None and (value is None or value == kwargs[key]):
callback(self, key, kwargs[key])
if self.AUTOSEND and len(keys_changed) > 0:
self.__tx__(keys_changed)
def __tx__(self, keys_changed):
self.mqtt_client.send(self.topic, json.dumps(self.data))
def __rx__(self, client, userdata, message):
print("%s: __rx__ not handled!" % self.__class__.__name__)
class shelly(base):
KEY_OUTPUT_0 = "relay/0"
KEY_OUTPUT_1 = "relay/1"
KEY_INPUT_0 = "input/0"
KEY_INPUT_1 = "input/1"
KEY_LONGPUSH_0 = "longpush/0"
KEY_LONGPUSH_1 = "longpush/1"
#
INPUT_FUNC_OUT1_FOLLOW = "out1_follow"
INPUT_FUNC_OUT1_TRIGGER = "out1_trigger"
INPUT_FUNC_OUT2_FOLLOW = "out2_follow"
INPUT_FUNC_OUT2_TRIGGER = "out2_trigger"
#
COMMANDS = [
"get_relay_0", "toggle_relay_0",
"get_relay_1", "toggle_relay_1",
"get_input_0", "toggle_input_0",
"get_input_1", "toggle_input_1",
"trigger_long_input_0", "trigger_long_input_1",
]
def __init__(self, mqtt_client, topic, input_0_func=None, input_1_func=None, output_0_auto_off=None):
super().__init__(mqtt_client, topic)
#
self.store_data(**{self.KEY_OUTPUT_0: "off", self.KEY_OUTPUT_1: "off", self.KEY_INPUT_0: "off", self.KEY_INPUT_1: "off"})
self.__input_0_func = input_0_func
self.__input_1_func = input_1_func
self.__output_0_auto_off__ = output_0_auto_off
if self.__output_0_auto_off__ is not None:
self.__delayed_off__ = task.delayed(float(self.__output_0_auto_off__), self.__auto_off__, self.KEY_OUTPUT_0)
#
self.add_callback(self.KEY_OUTPUT_0, self.print_formatted, None)
self.add_callback(self.KEY_OUTPUT_0, self.__start_auto_off__, "on")
self.add_callback(self.KEY_OUTPUT_0, self.__stop_auto_off__, "off")
self.add_callback(self.KEY_OUTPUT_1, self.print_formatted, None)
#
self.add_callback(self.KEY_INPUT_0, self.__input_function__, None)
self.add_callback(self.KEY_INPUT_1, self.__input_function__, None)
def __rx__(self, client, userdata, message):
value = payload_filter(message.payload)
if message.topic.startswith(self.topic) and message.topic.endswith("/command"):
key = '/'.join(message.topic.split('/')[-3:-1])
if value == 'toggle':
self.__toggle_data__(key)
else:
self.__set_data__(key, value)
def __tx__(self, keys_changed):
for key in keys_changed:
self.mqtt_client.send(self.topic + '/' + key, self.data.get(key))
def __input_function__(self, device, key, data):
if key == self.KEY_INPUT_0:
func = self.__input_0_func
elif key == self.KEY_INPUT_1:
func = self.__input_1_func
else:
func = None
if func == self.INPUT_FUNC_OUT1_FOLLOW:
self.__set_data__(self.KEY_OUTPUT_0, data)
elif func == self.INPUT_FUNC_OUT1_TRIGGER:
self.__toggle_data__(self.KEY_OUTPUT_0)
elif func == self.INPUT_FUNC_OUT2_FOLLOW:
self.__set_data__(self.KEY_OUTPUT_1, data)
elif func == self.INPUT_FUNC_OUT2_TRIGGER:
self.__toggle_data__(self.KEY_OUTPUT_1)
def __start_auto_off__(self, device, key, data):
self.__stop_auto_off__(device, key, data)
if self.__output_0_auto_off__ is not None:
self.__delayed_off__.run()
def __stop_auto_off__(self, device, key, data):
if self.__output_0_auto_off__ is not None:
if not self.__delayed_off__._stopped:
self.__delayed_off__.stop()
def __auto_off__(self, key):
if key == self.KEY_OUTPUT_0:
self.__set_data__(key, 'off')
def __set_data__(self, key, value):
if value in ["on", "off"]:
self.store_data(**{key: value})
else:
print("Wrong value (%s)!" % repr(value))
def __toggle_data__(self, key):
if key in self.data:
self.__set_data__(key, 'on' if self.data.get(key) == 'off' else 'off')
def command(self, command):
if command in self.COMMANDS:
if command == self.COMMANDS[0]:
self.print_formatted(self, self.KEY_OUTPUT_0, self.data.get(self.KEY_OUTPUT_0))
elif command == self.COMMANDS[1]:
self.__toggle_data__(self.KEY_OUTPUT_0)
elif command == self.COMMANDS[2]:
self.print_formatted(self, self.KEY_OUTPUT_1, self.data.get(self.KEY_OUTPUT_1))
elif command == self.COMMANDS[3]:
self.__toggle_data__(self.KEY_OUTPUT_1)
elif command == self.COMMANDS[4]:
self.print_formatted(self, self.KEY_INPUT_0, self.data.get(self.KEY_INPUT_0))
elif command == self.COMMANDS[5]:
self.__toggle_data__(self.KEY_INPUT_0)
elif command == self.COMMANDS[6]:
self.print_formatted(self, self.KEY_INPUT_1, self.data.get(self.KEY_INPUT_1))
elif command == self.COMMANDS[7]:
self.__toggle_data__(self.KEY_INPUT_1)
elif command == self.COMMANDS[8]:
self.__toggle_data__(self.KEY_INPUT_0)
time.sleep(0.4)
self.__set_data__(self.KEY_LONGPUSH_0, 'on')
time.sleep(0.1)
self.__set_data__(self.KEY_LONGPUSH_0, 'off')
elif command == self.COMMANDS[9]:
self.__toggle_data__(self.KEY_INPUT_1)
time.sleep(0.4)
self.__set_data__(self.KEY_LONGPUSH_1, 'on')
time.sleep(0.1)
self.__set_data__(self.KEY_LONGPUSH_1, 'off')
else:
print("%s: not yet implemented!" % command)
else:
print("Unknown command!")
def print_formatted(self, device, key, value):
if value is not None:
info = (" - %ds" % self.__output_0_auto_off__) if self.__output_0_auto_off__ is not None and value == "on" else ""
channel = "(%s%s)" % (self.names.get(key, key), info)
print_light(COLOR_SHELLY, value == "on", self.topic, channel)
class my_powerplug(base):
KEY_OUTPUT_0 = "0"
KEY_OUTPUT_1 = "1"
KEY_OUTPUT_2 = "2"
KEY_OUTPUT_3 = "3"
COMMANDS = [
"get_output_0", "toggle_output_0",
"get_output_1", "toggle_output_1",
"get_output_2", "toggle_output_2",
"get_output_3", "toggle_output_3",
]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
#
for i in range(0, 4):
self.data[str(i)] = False
self.add_callback(str(i), self.print_formatted, None)
def __rx__(self, client, userdata, message):
if message.topic.startswith(self.topic + '/output/') and message.topic.endswith('/set'):
try:
channels = [int(message.topic.split('/')[-2]) - 1]
except ValueError:
channels = range(0, 4)
for channel in channels:
payload = payload_filter(message.payload)
if payload == "toggle":
payload = not self.data.get(str(channel))
self.store_data(**{str(channel): payload})
def __tx__(self, keys_changed):
for key in keys_changed:
self.mqtt_client.send(self.topic + "/output/" + str(int(key) + 1), json.dumps(self.data.get(key)))
def command(self, command):
if command in self.COMMANDS:
if command == self.COMMANDS[0]:
self.print_formatted(self, self.KEY_OUTPUT_0, self.data.get(self.KEY_OUTPUT_0))
elif command == self.COMMANDS[1]:
self.store_data(**{self.KEY_OUTPUT_0: not self.data.get(self.KEY_OUTPUT_0)})
elif command == self.COMMANDS[2]:
self.print_formatted(self, self.KEY_OUTPUT_1, self.data.get(self.KEY_OUTPUT_1))
elif command == self.COMMANDS[3]:
self.store_data(**{self.KEY_OUTPUT_1: not self.data.get(self.KEY_OUTPUT_1)})
elif command == self.COMMANDS[4]:
self.print_formatted(self, self.KEY_OUTPUT_2, self.data.get(self.KEY_OUTPUT_2))
elif command == self.COMMANDS[5]:
self.store_data(**{self.KEY_OUTPUT_2: not self.data.get(self.KEY_OUTPUT_2)})
elif command == self.COMMANDS[6]:
self.print_formatted(self, self.KEY_OUTPUT_3, self.data.get(self.KEY_OUTPUT_3))
elif command == self.COMMANDS[7]:
self.store_data(**{self.KEY_OUTPUT_3: not self.data.get(self.KEY_OUTPUT_3)})
else:
print("%s: not yet implemented!" % command)
else:
print("Unknown command!")
def print_formatted(self, device, key, value):
if value is not None:
print_light(COLOR_POWERPLUG, value, self.topic, "(%s)" % self.names.get(key, "Channel %d" % (int(key) + 1)))
class silvercrest_powerplug(base):
KEY_OUTPUT_0 = "state"
#
COMMANDS = [
"get_state", "set_state", "unset_state",
]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
self.add_callback(self.KEY_OUTPUT_0, self.print_formatted, None)
#
self.store_data(**{self.KEY_OUTPUT_0: "off"})
def __rx__(self, client, userdata, message):
if message.topic == self.topic + '/set':
STATES = ["on", "off", "toggle"]
#
state = json.loads(message.payload).get('state').lower()
if state in STATES:
if state == STATES[0]:
self.store_data(**{self.KEY_OUTPUT_0: 'on'})
elif state == STATES[1]:
self.store_data(**{self.KEY_OUTPUT_0: 'off'})
else:
self.store_data(**{self.KEY_OUTPUT_0: "off" if self.data.get(self.KEY_OUTPUT_0) == "on" else "on"})
def command(self, command):
if command in self.COMMANDS:
if command == self.COMMANDS[0]:
self.print_formatted(self, self.KEY_OUTPUT_0, self.data.get(self.KEY_OUTPUT_0))
elif command == self.COMMANDS[1]:
self.store_data(**{self.KEY_OUTPUT_0: 'on'})
elif command == self.COMMANDS[2]:
self.store_data(**{self.KEY_OUTPUT_0: 'off'})
else:
print("%s: not yet implemented!" % command)
else:
print("Unknown command!")
def print_formatted(self, device, key, value):
if value is not None:
print_light(COLOR_POWERPLUG, value == "on", self.topic, "(%s)" % self.names.get(key, key))
class silvercrest_motion_sensor(base):
KEY_OCCUPANCY = "occupancy"
COMMANDS = ['motion']
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
self.data[self.KEY_OCCUPANCY] = False
self.add_callback(self.KEY_OCCUPANCY, self.print_formatted, None)
def __rx__(self, client, userdata, message):
pass
def command(self, command):
try:
command, value = command.split(' ')
except ValueError:
value = None
else:
value = json.loads(value)
if command == self.COMMANDS[0]:
self.store_data(**{self.KEY_OCCUPANCY: True})
time.sleep(value or 10)
self.store_data(**{self.KEY_OCCUPANCY: False})
def print_formatted(self, device, key, value):
if value is not None:
print_light(COLOR_MOTION_SENSOR, value, self.topic, "")
class tradfri_light(base):
KEY_STATE = "state"
KEY_BRIGHTNESS = "brightness"
KEY_COLOR_TEMP = "color_temp"
KEY_BRIGHTNESS_MOVE = "brightness_move"
#
STATE_COMMANDS = ("get_state", "toggle_state", )
BRIGHTNESS_COMMANDS = ("get_brightness", "set_brightness",)
COLOR_TEMP_COMMANDS = ("get_color_temp", "set_color_temp",)
def __init__(self, mqtt_client, topic, enable_state=True, enable_brightness=False, enable_color_temp=False, send_on_power_on=True):
super().__init__(mqtt_client, topic)
self.send_on_power_on = send_on_power_on
self.add_callback(self.KEY_STATE, self.print_formatted, None)
self.add_callback(self.KEY_BRIGHTNESS, self.print_formatted, None)
self.add_callback(self.KEY_COLOR_TEMP, self.print_formatted, None)
#
self.commands = []
if enable_state:
self.commands.extend(self.STATE_COMMANDS)
if enable_brightness:
self.commands.extend(self.BRIGHTNESS_COMMANDS)
if enable_color_temp:
self.commands.extend(self.COLOR_TEMP_COMMANDS)
self.__init_data__(enable_state, enable_brightness, enable_color_temp)
def __init_data__(self, enable_state, enable_brightness, enable_color_temp):
data = {}
if enable_state:
data[self.KEY_STATE] = 'off'
self.commands.extend(self.STATE_COMMANDS)
if enable_brightness:
data[self.KEY_BRIGHTNESS] = 128
self.brightnes_move = (0, time.time())
self.commands.extend(self.BRIGHTNESS_COMMANDS)
if enable_color_temp:
data[self.KEY_COLOR_TEMP] = 352
self.commands.extend(self.COLOR_TEMP_COMMANDS)
self.store_data(**data)
def __rx__(self, client, userdata, message):
data = json.loads(message.payload)
if self.data.get(self.KEY_STATE) == 'on' or data.get(self.KEY_STATE) in ['on', 'toggle']:
if message.topic.startswith(self.topic) and message.topic.endswith('/set'):
for targetkey in data:
value = data[targetkey]
if targetkey in self.data.keys():
if targetkey == self.KEY_STATE and value == "toggle":
value = "on" if self.data.get(self.KEY_STATE) == "off" else "off"
self.store_data(**{targetkey: value})
else:
if targetkey == self.KEY_BRIGHTNESS_MOVE:
new_value = self.data.get(self.KEY_BRIGHTNESS) + (time.time() - self.brightnes_move[1]) * self.brightnes_move[0]
if new_value < 0:
new_value = 0
if new_value > 255:
new_value = 255
self.store_data(**{self.KEY_BRIGHTNESS: int(new_value)})
self.brightnes_move = (value, time.time())
else:
print("%s: UNKNOWN KEY %s" % (message.topic, targetkey))
elif message.topic == self.topic + '/get':
self.__tx__(None)
def command(self, command):
try:
command, value = command.split(' ')
except ValueError:
value = None
if command in self.capabilities():
if command == self.STATE_COMMANDS[0]:
self.print_formatted(self, self.KEY_STATE, self.data.get(self.KEY_STATE))
elif command == self.STATE_COMMANDS[1]:
self.store_data(**{self.KEY_STATE: 'off' if self.data.get(self.KEY_STATE) == 'on' else 'on'})
elif command == self.BRIGHTNESS_COMMANDS[0]:
self.print_formatted(self, self.KEY_BRIGHTNESS, self.data.get(self.KEY_BRIGHTNESS))
elif command == self.BRIGHTNESS_COMMANDS[1]:
self.store_data(**{self.KEY_BRIGHTNESS: command_int_value(value)})
elif command == self.COLOR_TEMP_COMMANDS[0]:
self.print_formatted(self, self.KEY_COLOR_TEMP, self.data.get(self.KEY_COLOR_TEMP))
elif command == self.COLOR_TEMP_COMMANDS[1]:
self.store_data(**{self.KEY_COLOR_TEMP: command_int_value(value)})
else:
print("%s: not yet implemented!" % command)
else:
print("Unknown command!")
def power_off(self, device, key, value):
self.data[self.KEY_STATE] = 'off'
self.print_formatted(self, self.KEY_STATE, 'off')
def power_on(self, device, key, value):
if self.send_on_power_on:
self.store_data(**{self.KEY_STATE: 'on'})
else:
self.data[self.KEY_STATE] = 'on'
self.print_formatted(self, self.KEY_STATE, 'on')
def print_formatted(self, device, key, value):
if value is not None:
color = COLOR_LIGHT_ACTIVE
if key == self.KEY_STATE:
print_light(COLOR_LIGHT_ACTIVE, value == "on", self.topic, "")
self.print_formatted(device, self.KEY_BRIGHTNESS, self.data.get(self.KEY_BRIGHTNESS))
self.print_formatted(device, self.KEY_COLOR_TEMP, self.data.get(self.KEY_COLOR_TEMP))
elif key in [self.KEY_BRIGHTNESS, self.KEY_COLOR_TEMP]:
perc_value = round(value * 100 / 256, 0) if key == self.KEY_BRIGHTNESS else round((value - 250) * 100 / 204, 0)
print_percent(
COLOR_LIGHT_PASSIVE if self.data.get(self.KEY_STATE) != "on" else COLOR_LIGHT_ACTIVE,
'B' if key == gui_light.KEY_BRIGHTNESS else 'C',
perc_value,
"%3d%%" % perc_value,
self.topic,
""
)
class gui_light(tradfri_light):
AUTOSEND = False
#
KEY_ENABLE = "enable"
KEY_TIMER = "timer"
KEY_LED_X = "led%d"
def __init__(self, mqtt_client, topic, enable_state=True, enable_brightness=False, enable_color_temp=False):
super().__init__(mqtt_client, topic, enable_state, enable_brightness, enable_color_temp)
self.add_callback(self.KEY_ENABLE, self.print_formatted, None)
self.add_callback(self.KEY_TIMER, self.print_formatted, None)
for i in range(0, 10):
self.add_callback(self.KEY_LED_X % i, self.print_formatted, None)
self.led_names = {}
#
self.maxvalue = None
self.last_printed = None
def __init_data__(self, enable_state, enable_brightness, enable_color_temp):
data = {}
data[self.KEY_ENABLE] = False
if enable_state:
data[self.KEY_STATE] = False
if enable_brightness:
data[self.KEY_BRIGHTNESS] = 50
if enable_color_temp:
data[self.KEY_COLOR_TEMP] = 5
data[self.KEY_TIMER] = '-'
for i in range(0, 10):
data[self.KEY_LED_X % i] = False
self.store_data(**data)
def __rx__(self, client, userdata, message):
value = payload_filter(message.payload)
if message.topic.startswith(self.topic) and message.topic.endswith('/set'):
targetkey = message.topic.split('/')[-2]
if targetkey in self.data.keys():
self.store_data(**{targetkey: value})
else:
print("Unknown key %s in %s::%s" % (targetkey, message.topic, self.__class__.__name__))
elif message.topic == self.topic + '/get':
self.__tx__(None)
def send(self, key, data):
if data is not None:
topic = self.topic + '/' + key
self.mqtt_client.send(topic, json.dumps(data))
def command(self, command):
try:
command, value = command.split(' ')
except ValueError:
value = None
if command in self.capabilities():
if command == self.STATE_COMMANDS[0]:
self.print_formatted(self, self.KEY_STATE, self.data.get(self.KEY_STATE))
elif command == self.STATE_COMMANDS[1]:
self.send(self.KEY_STATE, not self.data.get(self.KEY_STATE))
elif command == self.BRIGHTNESS_COMMANDS[0]:
self.print_formatted(self, self.KEY_BRIGHTNESS, self.data.get(self.KEY_BRIGHTNESS))
elif command == self.BRIGHTNESS_COMMANDS[1]:
self.send(self.KEY_BRIGHTNESS, command_int_value(value))
elif command == self.COLOR_TEMP_COMMANDS[0]:
self.print_formatted(self, self.KEY_COLOR_TEMP, self.data.get(self.KEY_COLOR_TEMP))
elif command == self.COLOR_TEMP_COMMANDS[1]:
self.send(self.KEY_COLOR_TEMP, command_int_value(value))
else:
print("%s: not yet implemented!" % command)
else:
print("Unknown command!")
def add_led_name(self, key, name):
self.led_names[key] = name
def print_formatted(self, device, key, value):
if value is not None:
device = " - ".join(self.topic.split('/')[1:])
if key == self.KEY_STATE:
print_switch(COLOR_GUI_ACTIVE, value, self.topic, "")
elif key == self.KEY_ENABLE:
self.print_formatted(device, self.KEY_BRIGHTNESS, self.data.get(self.KEY_BRIGHTNESS))
self.print_formatted(device, self.KEY_COLOR_TEMP, self.data.get(self.KEY_COLOR_TEMP))
elif key in [self.KEY_BRIGHTNESS, self.KEY_COLOR_TEMP]:
perc_value = round(value * 10 if key == self.KEY_COLOR_TEMP else value, 0)
print_percent(
COLOR_GUI_PASSIVE if not self.data.get(self.KEY_ENABLE, False) else COLOR_GUI_ACTIVE,
'B' if key == self.KEY_BRIGHTNESS else 'C',
perc_value,
"%3d%%" % perc_value,
self.topic,
""
)
elif key == self.KEY_TIMER:
if isinstance(value, (int, float, complex)) and not isinstance(value, bool):
if self.maxvalue is None:
self.maxvalue = value
disp_value = value
perc = disp_value / self.maxvalue * 100
else:
disp_value = 0
perc = 0
self.maxvalue = None
self.last_printed = None
if self.last_printed is None or abs(self.last_printed - perc) >= 4.95:
print_percent(COLOR_GUI_ACTIVE, 't', perc, '%3d%%' % perc, self.topic, '(%.1f)' % disp_value)
self.last_printed = perc
elif key.startswith(self.KEY_LED_X[:-2]):
print_light(COLOR_GUI_ACTIVE, value, self.topic, '(%s)' % self.led_names.get(key, key), True)
class tradfri_button(base):
KEY_ACTION = "action"
#
ACTION_TOGGLE = "toggle"
ACTION_BRIGHTNESS_UP = "brightness_up_click"
ACTION_BRIGHTNESS_DOWN = "brightness_down_click"
ACTION_RIGHT = "arrow_right_click"
ACTION_LEFT = "arrow_left_click"
ACTION_BRIGHTNESS_UP_LONG = "brightness_up_hold"
ACTION_BRIGHTNESS_DOWN_LONG = "brightness_down_hold"
ACTION_RIGHT_LONG = "arrow_right_hold"
ACTION_LEFT_LONG = "arrow_left_hold"
#
COMMANDS = [ACTION_TOGGLE, ACTION_LEFT, ACTION_RIGHT, ACTION_BRIGHTNESS_UP, ACTION_BRIGHTNESS_DOWN,
ACTION_LEFT_LONG, ACTION_RIGHT_LONG, ACTION_BRIGHTNESS_UP_LONG, ACTION_BRIGHTNESS_DOWN_LONG]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
def __rx__(self, client, userdata, message):
pass
def command(self, command):
try:
command, value = command.split(' ')
except ValueError:
value = None
else:
value = json.loads(value)
if command in self.capabilities():
action = self.COMMANDS[self.COMMANDS.index(command)]
if self.COMMANDS.index(command) <= 4:
self.mqtt_client.send(self.topic, json.dumps({self.KEY_ACTION: action}))
elif self.COMMANDS.index(command) <= 8:
self.mqtt_client.send(self.topic, json.dumps({self.KEY_ACTION: action}))
time.sleep(value or 0.5)
action = '_'.join(action.split('_')[:-1] + ['release'])
self.mqtt_client.send(self.topic, json.dumps({self.KEY_ACTION: action}))
class gui_led_array(base):
AUTOSEND = False
#
KEY_LED_0 = "led0"
KEY_LED_1 = "led1"
KEY_LED_2 = "led2"
KEY_LED_3 = "led3"
KEY_LED_4 = "led4"
KEY_LED_5 = "led5"
KEY_LED_6 = "led6"
KEY_LED_7 = "led7"
KEY_LED_8 = "led8"
KEY_LED_9 = "led9"
def __init__(self, mqtt_client, topic, ):
super().__init__(mqtt_client, topic)
for i in range(0, 10):
key = getattr(self, "KEY_LED_%d" % i)
self.data[key] = False
self.add_callback(key, self.print_formatted, None)
def __rx__(self, client, userdata, message):
value = payload_filter(message.payload)
if message.topic.startswith(self.topic) and message.topic.endswith('/set'):
targetkey = message.topic.split('/')[-2]
if targetkey in self.data.keys():
self.store_data(**{targetkey: value})
else:
print("Unknown key %s in %s" % (targetkey, self.__class__.__name__))
def print_formatted(self, device, key, value):
print_light(COLOR_GUI_ACTIVE, value, self.topic, '(%s)' % self.names.get(key, key), True)
class remote(base):
def __rx__(self, client, userdata, message):
if message.topic == self.topic + "/VOLUP":
if payload_filter(message.payload):
icon = u'\u1403'
else:
icon = u'\u25a1'
elif message.topic == self.topic + "/VOLDOWN":
if payload_filter(message.payload):
icon = u'\u1401'
else:
icon = u'\u25a1'
else:
return
devicename = ' - '.join(self.topic.split('/')[1:-1])
print(COLOR_REMOTE + 10 * ' ' + icon + 6 * ' ' + devicename + colored.attr("reset"))
class brennenstuhl_radiator_valve(base):
TEMP_RANGE = [10, 30]
#
KEY_TEMPERATURE_SETPOINT = "current_heating_setpoint"
KEY_TEMPERATURE = "local_temperature"
#
COMMANDS = [
"get_temperature_setpoint", "set_temperature_setpoint",
]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
self.store_data(**{
self.KEY_TEMPERATURE_SETPOINT: 20,
self.KEY_TEMPERATURE: 20.7,
})
self.add_callback(self.KEY_TEMPERATURE_SETPOINT, self.print_formatted, None)
def __rx__(self, client, userdata, message):
if message.topic.startswith(self.topic) and message.topic.endswith("/set"):
payload = payload_filter(message.payload)
self.store_data(**payload)
def command(self, command):
try:
command, value = command.split(' ')
except ValueError:
value = None
if command in self.COMMANDS:
if command == self.COMMANDS[0]:
self.print_formatted(self, self.KEY_TEMPERATURE_SETPOINT, self.data.get(self.KEY_TEMPERATURE_SETPOINT))
elif command == self.COMMANDS[1]:
self.store_data(**{self.KEY_TEMPERATURE_SETPOINT: command_float_value(value)})
def print_formatted(self, device, key, value):
devicename = ' - '.join(self.topic.split('/')[1:])
if key == self.KEY_TEMPERATURE_SETPOINT:
perc = 100 * (value - self.TEMP_RANGE[0]) / (self.TEMP_RANGE[1] - self.TEMP_RANGE[0])
perc = 100 if perc > 100 else perc
perc = 0 if perc < 0 else perc
print_percent(COLOR_RADIATOR_VALVE, '\u03d1', perc, "%4.1f°C" % value, self.topic, "")
class gui_radiator_valve(base):
AUTOSEND = False
#
TEMP_RANGE = [10, 30]
#
KEY_TIMER = "timer"
KEY_TEMPERATURE = "temperature"
KEY_SETPOINT_TEMP = "setpoint_temp"
KEY_SETPOINT_TO_DEFAULT = "setpoint_to_default"
KEY_BOOST = 'boost'
KEY_AWAY = "away"
KEY_SUMMER = "summer"
KEY_ENABLE = "enable"
#
COMMANDS = [
"get_temperature",
"get_temperature_setpoint", "set_temperature_setpoint",
"trigger_boost", "trigger_setpoint_to_default",
"toggle_away", "toggle_summer",
]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
self.add_callback(self.KEY_SETPOINT_TEMP, self.print_formatted, None)
self.add_callback(self.KEY_TIMER, self.print_formatted, None)
self.add_callback(self.KEY_AWAY, self.print_formatted, None)
self.add_callback(self.KEY_SUMMER, self.print_formatted, None)
#
self.store_data(**{
self.KEY_TEMPERATURE: 20.7,
self.KEY_SETPOINT_TEMP: 20,
self.KEY_TIMER: 0,
self.KEY_AWAY: False,
self.KEY_SUMMER: False,
self.KEY_ENABLE: True
})
def __rx__(self, client, userdata, message):
value = payload_filter(message.payload)
if message.topic.startswith(self.topic) and message.topic.endswith('/set'):
targetkey = message.topic.split('/')[-2]
if targetkey in self.data.keys():
self.store_data(**{targetkey: value})
else:
print("Unknown key %s in %s::%s" % (targetkey, message.topic, self.__class__.__name__))
elif message.topic == self.topic + '/get':
self.__tx__(None)
def send(self, key, data):
if data is not None:
topic = self.topic + '/' + key
self.mqtt_client.send(topic, json.dumps(data))
def command(self, command):
try:
command, value = command.split(' ')
except ValueError:
value = None
if command in self.COMMANDS:
if command == self.COMMANDS[0]:
self.print_formatted(self, self.KEY_TEMPERATURE, self.data.get(self.KEY_TEMPERATURE))
elif command == self.COMMANDS[1]:
self.print_formatted(self, self.KEY_SETPOINT_TEMP, self.data.get(self.KEY_SETPOINT_TEMP))
elif command == self.COMMANDS[2]:
self.send(self.KEY_SETPOINT_TEMP, command_float_value(value))
elif command == self.COMMANDS[3]:
self.send(self.KEY_BOOST, True)
elif command == self.COMMANDS[4]:
self.send(self.KEY_SETPOINT_TO_DEFAULT, True)
elif command == self.COMMANDS[5]:
self.send(self.KEY_AWAY, not self.data.get(self.KEY_AWAY))
elif command == self.COMMANDS[6]:
self.send(self.KEY_SUMMER, not self.data.get(self.KEY_SUMMER))
def print_formatted(self, device, key, value):
devicename = ' - '.join(self.topic.split('/')[1:])
if key == self.KEY_TIMER:
try:
perc = 100 * value / 60
except TypeError:
value = 0
perc = 0
print_percent(COLOR_GUI_ACTIVE, 'T', perc, "%4.1fmin" % value, self.topic, "(Timer)")
elif key == self.KEY_TEMPERATURE:
perc = 100 * (value - self.TEMP_RANGE[0]) / (self.TEMP_RANGE[1] - self.TEMP_RANGE[0])
perc = 100 if perc > 100 else perc
perc = 0 if perc < 0 else perc
print_percent(COLOR_GUI_ACTIVE, '\u03d1', perc, "%4.1f°C" % value, self.topic, "(Temperature)")
elif key == self.KEY_SETPOINT_TEMP:
perc = 100 * (value - self.TEMP_RANGE[0]) / (self.TEMP_RANGE[1] - self.TEMP_RANGE[0])
perc = 100 if perc > 100 else perc
perc = 0 if perc < 0 else perc
print_percent(COLOR_GUI_ACTIVE if self.data.get(self.KEY_ENABLE) else COLOR_GUI_PASSIVE,
'\u03d1', perc, "%4.1f°C" % value, self.topic, "(Setpoint)")
elif key == self.KEY_AWAY:
print_switch(COLOR_GUI_ACTIVE, value, self.topic, "(Away Mode)")
elif key == self.KEY_SUMMER:
print_switch(COLOR_GUI_ACTIVE, value, self.topic, "(Summer Mode)")

221
__simulation__/rooms.py Normal file
View File

@ -0,0 +1,221 @@
import config
from __simulation__.devices import shelly, silvercrest_powerplug, tradfri_light, tradfri_button, silvercrest_motion_sensor, my_powerplug, remote, brennenstuhl_radiator_valve
from __simulation__.devices import gui_light, gui_led_array, gui_radiator_valve
import inspect
class base(object):
def getmembers(self, prefix=''):
rv = []
for name, obj in inspect.getmembers(self):
if prefix:
full_name = prefix + '.' + name
else:
full_name = name
if not name.startswith('_'):
try:
if obj.__module__.endswith('devices'):
rv.append(full_name)
else:
rv.extend(obj.getmembers(full_name))
except AttributeError:
pass
return rv
def getobjbyname(self, name):
obj = self
for subname in name.split('.'):
obj = getattr(obj, subname)
return obj
def command(self, full_command):
try:
parameter = " " + full_command.split(' ')[1]
except IndexError:
parameter = ""
command = full_command.split(' ')[0].split('.')[-1] + parameter
device_name = '.'.join(full_command.split(' ')[0].split('.')[:-1])
self.getobjbyname(device_name).command(command)
class gfw_floor(base):
def __init__(self, mqtt_client):
self.gui_main_light = gui_light(mqtt_client, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_GUI, True, True, True)
self.main_light = shelly(mqtt_client, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
self.main_light_zigbee_1 = tradfri_light(mqtt_client, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_1_ZIGBEE, True, True, True, False)
self.main_light.add_callback(shelly.KEY_OUTPUT_0, self.main_light_zigbee_1.power_on, "on")
self.main_light.add_callback(shelly.KEY_OUTPUT_0, self.main_light_zigbee_1.power_off, "off")
self.main_light_zigbee_2 = tradfri_light(mqtt_client, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_2_ZIGBEE, True, True, True, False)
self.main_light.add_callback(shelly.KEY_OUTPUT_0, self.main_light_zigbee_2.power_on, "on")
self.main_light.add_callback(shelly.KEY_OUTPUT_0, self.main_light_zigbee_1.power_off, "off")
class gfw_marion(base):
def __init__(self, mqtt_client):
self.gui_main_light = gui_light(mqtt_client, config.TOPIC_GFW_MARION_MAIN_LIGHT_GUI, True, False, False)
self.main_light = shelly(mqtt_client, config.TOPIC_GFW_MARION_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
class gfw_dirk(base):
def __init__(self, mqtt_client):
self.gui_main_light = gui_light(mqtt_client, config.TOPIC_GFW_DIRK_MAIN_LIGHT_GUI, True, True, True)
self.main_light = shelly(mqtt_client, config.TOPIC_GFW_DIRK_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
self.main_light_zigbee = tradfri_light(mqtt_client, config.TOPIC_GFW_DIRK_MAIN_LIGHT_ZIGBEE, True, True, True)
self.main_light.add_callback(shelly.KEY_OUTPUT_0, self.main_light_zigbee.power_on, "on")
self.main_light.add_callback(shelly.KEY_OUTPUT_0, self.main_light_zigbee.power_off, "off")
#
self.powerplug = my_powerplug(mqtt_client, config.TOPIC_GFW_DIRK_POWERPLUG)
self.powerplug.add_channel_name(my_powerplug.KEY_OUTPUT_0, "Amplifier")
self.powerplug.add_channel_name(my_powerplug.KEY_OUTPUT_1, "Desk_Light")
self.powerplug.add_channel_name(my_powerplug.KEY_OUTPUT_2, "CD_Player")
self.powerplug.add_channel_name(my_powerplug.KEY_OUTPUT_3, "PC_Dock")
self.gui_amplifier = gui_light(mqtt_client, config.TOPIC_GFW_DIRK_AMPLIFIER_GUI, True, False, False)
self.gui_desk_light = gui_light(mqtt_client, config.TOPIC_GFW_DIRK_DESK_LIGHT_GUI, True, True, True)
self.desk_light_zigbee = tradfri_light(mqtt_client, config.TOPIC_GFW_DIRK_DESK_LIGHT_ZIGBEE, True, True, True)
self.powerplug.add_callback(my_powerplug.KEY_OUTPUT_1, self.desk_light_zigbee.power_on, True)
self.powerplug.add_callback(my_powerplug.KEY_OUTPUT_1, self.desk_light_zigbee.power_off, False)
self.gui_cd_player = gui_light(mqtt_client, config.TOPIC_GFW_DIRK_CD_PLAYER_GUI, True, False, False)
self.gui_pc_dock = gui_light(mqtt_client, config.TOPIC_GFW_DIRK_PC_DOCK_GUI, True, False, False)
#
self.remote = remote(mqtt_client, config.TOPIC_GFW_DIRK_AMPLIFIER_REMOTE)
#
self.input_device = tradfri_button(mqtt_client, config.TOPIC_GFW_DIRK_INPUT_DEVICE)
self.led_array = gui_led_array(mqtt_client, config.TOPIC_GFW_DIRK_DEVICE_CHOOSER_LED)
self.led_array.add_channel_name(gui_led_array.KEY_LED_0, "Main Light")
self.led_array.add_channel_name(gui_led_array.KEY_LED_1, "Desk Light")
self.led_array.add_channel_name(gui_led_array.KEY_LED_2, "Amplifier")
class gfw(base):
def __init__(self, mqtt_client):
self.floor = gfw_floor(mqtt_client)
self.marion = gfw_marion(mqtt_client)
self.dirk = gfw_dirk(mqtt_client)
class ffw_julian(base):
def __init__(self, mqtt_client):
self.gui_main_light = gui_light(mqtt_client, config.TOPIC_FFW_JULIAN_MAIN_LIGHT_GUI, True, True, True)
self.main_light = shelly(mqtt_client, config.TOPIC_FFW_JULIAN_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
self.main_light_zigbee = tradfri_light(mqtt_client, config.TOPIC_FFW_JULIAN_MAIN_LIGHT_ZIGBEE, True, True, True)
self.main_light.add_callback(shelly.KEY_OUTPUT_0, self.main_light_zigbee.power_on, "on")
self.main_light.add_callback(shelly.KEY_OUTPUT_0, self.main_light_zigbee.power_off, "off")
class ffw_livingroom(base):
def __init__(self, mqtt_client):
self.gui_main_light = gui_light(mqtt_client, config.TOPIC_FFW_LIVINGROOM_MAIN_LIGHT_GUI, True, False, False)
self.main_light = shelly(mqtt_client, config.TOPIC_FFW_LIVINGROOM_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
class ffw(base):
def __init__(self, mqtt_client):
self.julian = ffw_julian(mqtt_client)
self.livingroom = ffw_livingroom(mqtt_client)
class ffe_floor(base):
def __init__(self, mqtt_client):
self.gui_main_light = gui_light(mqtt_client, config.TOPIC_FFE_FLOOR_MAIN_LIGHT_GUI, True, False, False)
self.main_light = shelly(mqtt_client, config.TOPIC_FFE_FLOOR_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
class ffe_kitchen(base):
def __init__(self, mqtt_client):
self.gui_main_light = gui_light(mqtt_client, config.TOPIC_FFE_KITCHEN_MAIN_LIGHT_GUI, True, False, False)
self.main_light = shelly(mqtt_client, config.TOPIC_FFE_KITCHEN_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
#
self.gui_circulation_pump = gui_light(mqtt_client, config.TOPIC_FFE_KITCHEN_CIRCULATION_PUMP_GUI, True, False, False)
self.circulation_pump = shelly(mqtt_client, config.TOPIC_FFE_KITCHEN_CIRCULATION_PUMP_SHELLY,
input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER, output_0_auto_off=10*60)
self.circulation_pump.add_channel_name(shelly.KEY_OUTPUT_0, "Circulation Pump")
class ffe_diningroom(base):
def __init__(self, mqtt_client):
self.gui_main_light = gui_light(mqtt_client, config.TOPIC_FFE_DININGROOM_MAIN_LIGHT_GUI, True, False, False)
self.main_light = shelly(mqtt_client, config.TOPIC_FFE_DININGROOM_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
self.gui_floor_lamp = gui_light(mqtt_client, config.TOPIC_FFE_DININGROOM_FLOOR_LAMP_GUI, True, False, False)
self.floor_lamp = silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_DININGROOM_FLOOR_LAMP_POWERPLUG)
self.floor_lamp.add_channel_name(silvercrest_powerplug.KEY_OUTPUT_0, "Floor Lamp")
if config.CHRISTMAS:
self.garland = silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_DININGROOM_GARLAND_POWERPLUG)
self.garland.add_channel_name(silvercrest_powerplug, "Garland")
class ffe_sleep(base):
def __init__(self, mqtt_client):
self.gui_main_light = gui_light(mqtt_client, config.TOPIC_FFE_SLEEP_MAIN_LIGHT_GUI, True, True, True)
self.main_light = shelly(mqtt_client, config.TOPIC_FFE_SLEEP_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
self.main_light_zigbee = tradfri_light(mqtt_client, config.TOPIC_FFE_SLEEP_MAIN_LIGHT_ZIGBEE, True, True, True)
self.main_light.add_callback(shelly.KEY_OUTPUT_0, self.main_light_zigbee.power_on, "on")
self.main_light.add_callback(shelly.KEY_OUTPUT_0, self.main_light_zigbee.power_off, "off")
#
self.gui_bed_light_di = gui_light(mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_DI_GUI, True, True, False)
self.bed_light_di_zigbee = tradfri_light(mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_DI_ZIGBEE, True, True, False)
self.gui_bed_light_ma = gui_light(mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_MA_GUI, True, False, False)
self.bed_light_ma_powerplug = silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_MA_POWERPLUG)
#
self.input_device = tradfri_button(mqtt_client, config.TOPIC_FFE_SLEEP_INPUT_DEVICE)
self.led_array = gui_led_array(mqtt_client, config.TOPIC_FFE_SLEEP_DEVICE_CHOOSER_LED)
self.led_array.add_channel_name(gui_led_array.KEY_LED_0, "Main Light")
self.led_array.add_channel_name(gui_led_array.KEY_LED_1, "Bed Light Dirk")
#
self.radiator_valve = brennenstuhl_radiator_valve(mqtt_client, config.TOPIC_FFE_SLEEP_RADIATOR_VALVE_ZIGBEE)
self.gui_radiator_valve = gui_radiator_valve(mqtt_client, config.TOPIC_FFE_SLEEP_RADIATOR_VALVE_GUI)
class ffe_livingroom(base):
def __init__(self, mqtt_client):
self.gui_main_light = gui_light(mqtt_client, config.TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_GUI, True, True, True)
self.main_light = shelly(mqtt_client, config.TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
self.main_light_zigbee = tradfri_light(mqtt_client, config.TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_ZIGBEE, True, True, True)
self.main_light.add_callback(shelly.KEY_OUTPUT_0, self.main_light_zigbee.power_on, "on")
self.main_light.add_callback(shelly.KEY_OUTPUT_0, self.main_light_zigbee.power_off, "off")
for i in range(1, 7):
setattr(self, "floor_lamp_zigbee_%d" % i, tradfri_light(mqtt_client, config.TOPIC_FFE_LIVINGROOM_FLOOR_LAMP_ZIGBEE % i, True, True, True))
self.gui_floor_lamp = gui_light(mqtt_client, config.TOPIC_FFE_LIVINGROOM_FLOOR_LAMP_GUI, True, True, True)
if config.CHRISTMAS:
self.xmas_tree = silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_LIVINGROOM_XMAS_TREE_POWERPLUG)
self.xmas_tree.add_channel_name(silvercrest_powerplug, "Xmas-Tree")
self.gui_xmas_tree = gui_light(mqtt_client, config.TOPIC_FFE_LIVINGROOM_XMAS_TREE_GUI)
self.xmas_star = silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_LIVINGROOM_XMAS_STAR_POWERPLUG)
self.xmas_star.add_channel_name(silvercrest_powerplug, "Xmas-Star")
class ffe(base):
def __init__(self, mqtt_client):
self.floor = ffe_floor(mqtt_client)
self.kitchen = ffe_kitchen(mqtt_client)
self.diningroom = ffe_diningroom(mqtt_client)
self.sleep = ffe_sleep(mqtt_client)
self.livingroom = ffe_livingroom(mqtt_client)
class stairway(base):
def __init__(self, mqtt_client):
self.gui_main_light = gui_light(mqtt_client, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_GUI, True, False, False)
self.gui_main_light.add_led_name(self.gui_main_light.KEY_LED_X % 0, "Motion Ground Floor")
self.gui_main_light.add_led_name(self.gui_main_light.KEY_LED_X % 1, "Motion First Floor")
self.main_light = shelly(mqtt_client, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
self.motion_sensor_gf = silvercrest_motion_sensor(mqtt_client, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_MOTION_SENSOR_GF)
self.motion_sensor_ff = silvercrest_motion_sensor(mqtt_client, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_MOTION_SENSOR_FF)
class house(base):
def __init__(self, mqtt_client):
self.gfw = gfw(mqtt_client)
self.ffw = ffw(mqtt_client)
self.ffe = ffe(mqtt_client)
self.stairway = stairway(mqtt_client)

232
__simulation__/test.py Normal file
View File

@ -0,0 +1,232 @@
import colored
import time
DT_TOGGLE = 0.1
class test_smarthome(object):
def __init__(self, house):
self.house = house
def __smoke__(self):
result = ""
result += "Smoke-Test\n"
result += " GUI Element exists for every shelly instance\n"
result += self.__gui_element_exists__("shelly")
result += " On-Off test for every shelly instance\n"
result += self.__on_off_test__("shelly")
result += " GUI Element exists for every silvercrest_powerplug instance\n"
result += self.__gui_element_exists__("silvercrest_powerplug")
result += " On-Off test for every silvercrest_powerplug instance\n"
result += self.__on_off_test__("silvercrest_powerplug")
result += " GUI Element exists for every my_powerplug instance and port\n"
result += self.__gui_element_exists_my_powerplug__()
result += " On-Off test for every my_powerplug instance\n"
result += self.__on_off_test_my_powerplug__()
result += " GUI Element exists for every tradfri_light instance\n"
result += self.__gui_element_exists__("tradfri_light")
result += " Enable and disable test for gui elements corresponding with tradfri_light\n"
result += self.__br_ct_enable_test__()
result += " Chnage brightness and color_temp by gui test\n"
result += self.__br_ct_change_test__()
return result
def __full__(self):
result = "Full-Test"
return result
def smoke(self):
print(self.__smoke__())
def full(self):
out = self.__smoke__()
out += self.__full__()
print(out)
def print_error(self, text, lvl=2):
return lvl*" " + colored.fg("light_red") + '* ' + text + "\n" + colored.attr("reset")
def print_success(self, text, lvl=2):
return lvl*" " + colored.fg("light_green") + '* ' + text + "\n" + colored.attr("reset")
def __gui_element_exists__(self, obj_name):
result = ""
for member in self.house.getmembers():
obj = self.house.getobjbyname(member)
if obj.__class__.__name__ == obj_name:
if obj_name == "tradfri_light":
basename = member[:member.index('_zigbee')]
else:
basename = member
try:
gui = self.house.getobjbyname('.'.join(basename.split('.')[:-1]) + '.gui_' + basename.split('.')[-1])
except AttributeError:
result += self.print_error("No GUI element available, while testing %s (%s)." % (member, obj_name))
else:
result += self.print_success("GUI element available, while testing %s (%s)." % (member, obj_name))
return result
def __gui_element_exists_my_powerplug__(self):
result = ""
for member in self.house.getmembers():
obj = self.house.getobjbyname(member)
if obj.__class__.__name__ == "my_powerplug":
for channel in [obj.KEY_OUTPUT_0, obj.KEY_OUTPUT_1, obj.KEY_OUTPUT_2, obj.KEY_OUTPUT_3]:
try:
gui = self.house.getobjbyname('.'.join(member.split(
'.')[:-1]) + '.gui_' + obj.names.get(channel.lower(), "__dummy__").lower())
except AttributeError:
result += self.print_error("No GUI element available, while testing %s (%s)." % (member, obj.names.get(channel)))
else:
result += self.print_success("GUI element available, while testing %s (%s)." % (member, obj.names.get(channel)))
return result
def __on_off_test_my_powerplug__(self):
result = ""
for member in self.house.getmembers():
obj = self.house.getobjbyname(member)
if obj.__class__.__name__ == "my_powerplug":
for channel in [obj.KEY_OUTPUT_0, obj.KEY_OUTPUT_1, obj.KEY_OUTPUT_2, obj.KEY_OUTPUT_3]:
try:
gui = self.house.getobjbyname('.'.join(member.split(
'.')[:-1]) + '.gui_' + obj.names.get(channel.lower(), "__dummy__").lower())
except AttributeError:
raise AttributeError
pass # exists test already covers non existing gui-elements
else:
success = True
# Initial state equal between obj and gui
obj_state = obj.data.get(channel)
gui_state = gui.data.get(gui.KEY_STATE)
if obj_state != gui_state:
result += self.print_error("Initial state of %s (%s) is not equal to GUI state (%s), while testing %s (%s)" %
("my_powerplug", obj_state, gui_state, member, obj.names.get(channel)))
success = False
# state obj change results in state change of obj and gui
for i in range(1, 3):
gui.command("toggle_state")
time.sleep(2 * DT_TOGGLE)
last_obj_state = obj_state
obj_state = obj.data.get(channel)
gui_state = gui.data.get(gui.KEY_STATE)
if last_obj_state == obj_state:
result += self.print_error("State after %d. toggle of gui state: State unchanged (%s), while testing %s (%s)" %
(i, obj_state, member, obj.names.get(channel)))
success = False
if obj_state != gui_state:
result += self.print_error("State after %d. toggle of gui state:: State of device (%s) is not equal to GUI state (%s), while testing %s (%s)" %
(i, obj_state, gui_state, member, obj.names.get(channel)))
success = False
#
if success:
result += self.print_success("On-Off test successfull, while testing %s (%s)." % (member, obj.names.get(channel)))
return result
def __on_off_test__(self, obj_name):
result = ""
for member in self.house.getmembers():
obj = self.house.getobjbyname(member)
if obj.__class__.__name__ == obj_name:
try:
gui = self.house.getobjbyname('.'.join(member.split('.')[:-1]) + '.gui_' + member.split('.')[-1])
except AttributeError:
pass # exists test already covers non existing gui-elements
else:
success = True
# Initial state equal between obj and gui
obj_state = obj.data.get(obj.KEY_OUTPUT_0).lower() == "on"
gui_state = gui.data.get(gui.KEY_STATE)
if obj_state != gui_state:
result += self.print_error("Initial state of %s (%s) is not equal to GUI state (%s), while testing %s (%s)" %
(obj_name, obj_state, gui_state, member, obj_name))
success = False
# state obj change results in state change of obj and gui
for i in range(1, 3):
gui.command("toggle_state")
time.sleep(2 * DT_TOGGLE)
last_obj_state = obj_state
obj_state = obj.data.get(obj.KEY_OUTPUT_0).lower() == "on"
gui_state = gui.data.get(gui.KEY_STATE)
if last_obj_state == obj_state:
result += self.print_error("State after %d. toggle of gui state: State unchanged (%s), while testing %s (%s)" %
(i, obj_state, member, obj_name))
success = False
if obj_state != gui_state:
result += self.print_error("State after %d. toggle of gui state:: State of device (%s) is not equal to GUI state (%s), while testing %s (%s)" %
(i, obj_state, gui_state, member, obj_name))
success = False
#
if success:
result += self.print_success("On-Off test successfull, while testing %s." % (member))
return result
def __br_ct_enable_test__(self):
result = ""
for member in self.house.getmembers():
obj = self.house.getobjbyname(member)
if obj.__class__.__name__ == "tradfri_light":
basename = member[:member.index('_zigbee')]
gui = self.house.getobjbyname('.'.join(basename.split('.')[:-1]) + '.gui_' + basename.split('.')[-1])
success = True
#
if gui.data.get(gui.KEY_ENABLE) != False:
result += self.print_error("Inital enable state is not False, while testing %s." % (member))
success = False
#
gui.command("toggle_state")
time.sleep(2 * DT_TOGGLE)
if gui.data.get(gui.KEY_ENABLE) != True:
result += self.print_error("Enable state is not True after switching on, while testing %s." % (member))
success = False
#
gui.command("toggle_state")
time.sleep(2 * DT_TOGGLE)
if gui.data.get(gui.KEY_ENABLE) != False:
result += self.print_error("Enable state is not False after switching off, while testing %s." % (member))
success = False
#
if success:
result += self.print_success("Enable-Disable test successfull, while testing %s." % (member))
return result
def __br_ct_change_test__(self):
result = ""
for member in self.house.getmembers():
obj = self.house.getobjbyname(member)
if obj.__class__.__name__ == "tradfri_light":
basename = member[:member.index('_zigbee')]
gui = self.house.getobjbyname('.'.join(basename.split('.')[:-1]) + '.gui_' + basename.split('.')[-1])
success = True
#
if gui.data.get(gui.KEY_STATE) != True:
gui.command("toggle_state")
time.sleep(2 * DT_TOGGLE)
if gui.data.get(gui.KEY_STATE) != True:
result += self.print_error("Unable to switch on light, while testing %s." % (member))
success = False
continue
#
if "set_brightness" in obj.capabilities():
brightness = gui.data.get(obj.KEY_BRIGHTNESS)
targetvalue = brightness + (25 if brightness <= 50 else -25)
gui.command("set_brightness %d" % targetvalue)
time.sleep(2 * DT_TOGGLE)
if gui.data.get(obj.KEY_BRIGHTNESS) != targetvalue:
result += self.print_error("Brightness change by gui was not successfull, while testing %s." % (member))
success = False
if "set_color_temp" in obj.capabilities():
color_temp = gui.data.get(obj.KEY_COLOR_TEMP)
targetvalue = color_temp + (3 if color_temp <= 5 else -3)
gui.command("set_color_temp %d" % targetvalue)
time.sleep(2 * DT_TOGGLE)
if gui.data.get(obj.KEY_COLOR_TEMP) != targetvalue:
result += self.print_error("Color temperature change by gui was not successfull, while testing %s." % (member))
success = False
#
gui.command("toggle_state")
time.sleep(2 * DT_TOGGLE)
#
if success:
result += self.print_success("Brightness-ColorTemp test successfull, while testing %s." % (member))
return result

View File

@ -1,35 +0,0 @@
import json
from mqtt import mqtt_client
import time
TEST_CLIENT_ID = "__test_device_tester__"
mqtt_test_client = mqtt_client(TEST_CLIENT_ID, "localhost")
def init_state(all_state_keys, device):
for state_topic in all_state_keys:
assert device.get(state_topic, 0) == None
def state_change_by_mqtt(all_state_keys, num_states, mqtt_test_client, base_topic, device, mqtt_data, state_data, warning_condition, mqtt_signal_time):
tm_warning = None
for i in range(num_states):
for state_topic in all_state_keys:
if device.TX_TYPE == device.TX_VALUE:
data = json.dumps(mqtt_data(state_topic)[i])
mqtt_test_client.send(base_topic + '/' + state_topic, data)
elif device.TX_TYPE == device.TX_DICT:
mqtt_test_client.send(base_topic, json.dumps({state_topic: mqtt_data(state_topic)[i]}))
else:
raise TypeError("Unknown TX_TYPE for device.")
if callable(warning_condition):
if warning_condition(state_topic, mqtt_data(state_topic)[i]):
tm_warning = int(time.time())
time.sleep(mqtt_signal_time)
for state_topic in all_state_keys:
assert device.get(state_topic) == state_data(state_topic)[i]
return tm_warning

View File

@ -1,45 +0,0 @@
from module import mqtt_test_client, init_state, state_change_by_mqtt
from devices import my_powerplug as test_device
from devices.base import warning
from mqtt import mqtt_client
import pytest
import time
DUT_CLIENT_ID = "__%s__" % __name__
TOPIC = "__test__/%s" % __name__
#
MQTT_SIGNAL_TIME = 0.2
ALL_STATE_KEYS = ["output/1", "output/2", "output/3", "output/4", ]
BOOL_KEYS = ALL_STATE_KEYS
@pytest.fixture
def this_device():
mc = mqtt_client(DUT_CLIENT_ID, 'localhost')
return test_device(mc, TOPIC)
def test_initial_states(this_device: test_device):
# test all initial values
init_state(ALL_STATE_KEYS, this_device)
def test_state_change_by_mqtt(this_device: test_device):
def state_data(key):
return (True, False)
def mqtt_data(key):
return state_data(key)
# test state changes
tm_warning = state_change_by_mqtt(ALL_STATE_KEYS, 2, mqtt_test_client, TOPIC, this_device,
mqtt_data, state_data, None, MQTT_SIGNAL_TIME)
def test_specific_get_functions(this_device: test_device):
assert this_device.output_0 == this_device.get(this_device.KEY_OUTPUT_0)
assert this_device.output_1 == this_device.get(this_device.KEY_OUTPUT_1)
assert this_device.output_2 == this_device.get(this_device.KEY_OUTPUT_2)
assert this_device.output_3 == this_device.get(this_device.KEY_OUTPUT_3)

View File

@ -1,250 +0,0 @@
from module import mqtt_test_client, init_state, state_change_by_mqtt
from devices import shelly_sw1 as test_device
from devices.base import warning
from mqtt import mqtt_client
import pytest
import time
DUT_CLIENT_ID = "__%s__" % __name__
TOPIC = "__test__/%s" % __name__
#
MQTT_SIGNAL_TIME = 0.2
ALL_STATE_KEYS = ["relay/0", "relay/1", "input/0", "input/1", "longpush/0", "longpush/1", "temperature", "overtemperature"]
BOOL_KEYS = ["relay/0", "relay/1", "input/0", "input/1", "longpush/0", "longpush/1", "overtemperature"]
@pytest.fixture
def this_device():
mc = mqtt_client(DUT_CLIENT_ID, 'localhost')
return test_device(mc, TOPIC)
def test_initial_states(this_device: test_device):
# test all initial values
init_state(ALL_STATE_KEYS, this_device)
def test_state_change_by_mqtt(this_device: test_device):
def state_data(key):
if key in BOOL_KEYS:
return (True, False)
elif key == "temperature":
return (85.3, 20.1)
else:
raise IndexError("No return value defined for key %s" % key)
def mqtt_data(key):
if key in ["relay/0", "relay/1"]:
return ('on', 'off')
elif key in ["input/0", "input/1", "longpush/0", "longpush/1", "overtemperature"]:
return (1, 0)
else:
return state_data(key)
def warning_condition(state_topic, value):
return state_topic == "overtemperature" and value == 1
# test state changes
tm_warning = state_change_by_mqtt(ALL_STATE_KEYS, 2, mqtt_test_client, TOPIC, this_device,
mqtt_data, state_data, warning_condition, MQTT_SIGNAL_TIME)
# test warning
w: warning = this_device.get(this_device.KEY_WARNING)
assert w.get(w.KEY_ID) == TOPIC
assert w.get(w.KEY_TYPE) == w.TYPE_OVERTEMPERATURE
wt = time.mktime(w.get(w.KEY_TM))
wt_min = tm_warning
wt_max = tm_warning + 2
assert wt >= wt_min and wt <= wt_max
def test_specific_get_functions(this_device: test_device):
assert this_device.output_0 == this_device.get(this_device.KEY_OUTPUT_0)
assert this_device.output_1 == this_device.get(this_device.KEY_OUTPUT_1)
assert this_device.input_0 == this_device.get(this_device.KEY_INPUT_0)
assert this_device.input_1 == this_device.get(this_device.KEY_INPUT_1)
assert this_device.longpush_0 == this_device.get(this_device.KEY_LONGPUSH_0)
assert this_device.longpush_1 == this_device.get(this_device.KEY_LONGPUSH_1)
assert this_device.temperature == this_device.get(this_device.KEY_TEMPERATURE)
def test_send_command(this_device: test_device):
this_device.set_output_0(True)
this_device.set_output_0(False)
'''
class shelly(base):
""" Communication (MQTT)
shelly
+- relay
| +- 0 ["on" / "off"] <- status
| | +- command ["on"/ "off"] <- command
| | +- energy [numeric] <- status
| +- 1 ["on" / "off"] <- status
| +- command ["on"/ "off"] <- command
| +- energy [numeric] <- status
+- input
| +- 0 [0 / 1] <- status
| +- 1 [0 / 1] <- status
+- input_event
| +- 0 <- status
| +- 1 <- status
+- logpush
| +- 0 [0 / 1] <- status
| +- 1 [0 / 1] <- status
+- temperature [numeric] °C <- status
+- temperature_f [numeric] F <- status
+- overtemperature [0 / 1] <- status
+- id <- status
+- model <- status
+- mac <- status
+- ip <- status
+- new_fw <- status
+- fw_ver <- status
"""
KEY_OUTPUT_0 = "relay/0"
KEY_OUTPUT_1 = "relay/1"
KEY_INPUT_0 = "input/0"
KEY_INPUT_1 = "input/1"
KEY_LONGPUSH_0 = "longpush/0"
KEY_LONGPUSH_1 = "longpush/1"
KEY_TEMPERATURE = "temperature"
KEY_OVERTEMPERATURE = "overtemperature"
KEY_ID = "id"
KEY_MODEL = "model"
KEY_MAC = "mac"
KEY_IP = "ip"
KEY_NEW_FIRMWARE = "new_fw"
KEY_FIRMWARE_VERSION = "fw_ver"
#
TX_TOPIC = "command"
TX_TYPE = base.TX_VALUE
TX_FILTER_DATA_KEYS = [KEY_OUTPUT_0, KEY_OUTPUT_1]
#
RX_KEYS = [KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_INPUT_0, KEY_INPUT_1, KEY_LONGPUSH_0, KEY_LONGPUSH_1, KEY_OVERTEMPERATURE, KEY_TEMPERATURE,
KEY_ID, KEY_MODEL, KEY_MAC, KEY_IP, KEY_NEW_FIRMWARE, KEY_FIRMWARE_VERSION]
RX_IGNORE_TOPICS = [KEY_OUTPUT_0 + '/' + "energy", KEY_OUTPUT_1 + '/' + "energy", 'input_event/0', 'input_event/1']
RX_IGNORE_KEYS = ['temperature_f']
RX_FILTER_DATA_KEYS = [KEY_INPUT_0, KEY_INPUT_1, KEY_LONGPUSH_0, KEY_LONGPUSH_1, KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_OVERTEMPERATURE]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
#
self.output_key_delayed = None
self.delayed_flash_task = task.delayed(0.3, self.flash_task)
self.delayed_off_task = task.delayed(0.3, self.off_task)
#
self.add_callback(self.KEY_OVERTEMPERATURE, True, self.__warning__, True)
#
self.all_off_requested = False
def flash_task(self, *args):
if self.flash_active:
self.send_command(self.output_key_delayed, not self.get(self.output_key_delayed))
self.output_key_delayed = None
if self.all_off_requested:
self.delayed_off_task.run()
def off_task(self, *args):
self.all_off()
@property
def flash_active(self):
return self.output_key_delayed is not None
#
# WARNING CALL
#
def __warning__(self, client, key, data):
w = warning(self.topic, warning.TYPE_OVERTEMPERATURE, "Temperature to high (%.1f°C)", self.get(self.KEY_TEMPERATURE) or math.nan)
self.logger.warning(w)
self.set(self.KEY_WARNING, w)
#
# RX
#
@property
def output_0(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_0)
@property
def output_1(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_1)
@property
def input_0(self):
"""rv: [True, False]"""
return self.get(self.KEY_INPUT_0)
@property
def input_1(self):
"""rv: [True, False]"""
return self.get(self.KEY_INPUT_1)
@property
def longpush_0(self):
"""rv: [True, False]"""
return self.get(self.KEY_LONGPUSH_0)
@property
def longpush_1(self):
"""rv: [True, False]"""
return self.get(self.KEY_LONGPUSH_1)
@property
def temperature(self):
"""rv: numeric value"""
return self.get(self.KEY_TEMPERATURE)
#
# TX
#
def set_output_0(self, state):
"""state: [True, False]"""
self.send_command(self.KEY_OUTPUT_0, state)
def set_output_0_mcb(self, device, key, data):
self.logger.log(logging.INFO if data != self.output_0 else logging.DEBUG, "Changing output 0 to %s", str(data))
self.set_output_0(data)
def toggle_output_0_mcb(self, device, key, data):
self.logger.info("Toggeling output 0")
self.set_output_0(not self.output_0)
def set_output_1(self, state):
"""state: [True, False]"""
self.send_command(self.KEY_OUTPUT_1, state)
def set_output_1_mcb(self, device, key, data):
self.logger.log(logging.INFO if data != self.output_1 else logging.DEBUG, "Changing output 1 to %s", str(data))
self.set_output_1(data)
def toggle_output_1_mcb(self, device, key, data):
self.logger.info("Toggeling output 1")
self.set_output_1(not self.output_1)
def flash_0_mcb(self, device, key, data):
self.output_key_delayed = self.KEY_OUTPUT_0
self.toggle_output_0_mcb(device, key, data)
self.delayed_flash_task.run()
def flash_1_mcb(self, device, key, data):
self.output_key_delayed = self.KEY_OUTPUT_1
self.toggle_output_1_mcb(device, key, data)
self.delayed_flash_task.run()
def all_off(self):
if self.flash_active:
self.all_off_requested = True
else:
if self.output_0:
self.set_output_0(False)
if self.output_1:
self.set_output_1(False)
'''

View File

@ -1,55 +0,0 @@
from module import mqtt_test_client, init_state, state_change_by_mqtt
from devices import silvercrest_motion_sensor as test_device
from devices.base import warning
from mqtt import mqtt_client
import pytest
import time
DUT_CLIENT_ID = "__%s__" % __name__
TOPIC = "__test__/%s" % __name__
#
MQTT_SIGNAL_TIME = 0.2
ALL_STATE_KEYS = ["battery", "battery_low", "linkquality", "occupancy", "tamper", "voltage"]
BOOL_KEYS = ["battery_low", "occupancy", "tamper"]
@pytest.fixture
def this_device():
mc = mqtt_client(DUT_CLIENT_ID, 'localhost')
return test_device(mc, TOPIC)
def test_initial_states(this_device: test_device):
# test all initial values
init_state(ALL_STATE_KEYS, this_device)
def test_state_change_by_mqtt(this_device: test_device):
def state_data(key):
if key in BOOL_KEYS:
return (True, False)
elif key == "battery":
return (2, 87)
elif key == "linkquality":
return (1, 217)
elif key == "voltage":
return (1.17, 2.53)
else:
raise IndexError("No return value defined for key %s" % key)
def mqtt_data(key):
return state_data(key)
def warning_condition(state_topic, value):
return state_topic == "battery_low" and value is True
# test state changes
tm_warning = state_change_by_mqtt(ALL_STATE_KEYS, 2, mqtt_test_client, TOPIC, this_device,
mqtt_data, state_data, None, MQTT_SIGNAL_TIME)
def test_specific_get_functions(this_device: test_device):
assert this_device.linkquality == this_device.get(this_device.KEY_LINKQUALITY)
assert this_device.battery == this_device.get(this_device.KEY_BATTERY)

View File

@ -1,49 +0,0 @@
from module import mqtt_test_client, init_state, state_change_by_mqtt
from devices import silvercrest_powerplug as test_device
from devices.base import warning
from mqtt import mqtt_client
import pytest
import time
DUT_CLIENT_ID = "__%s__" % __name__
TOPIC = "__test__/%s" % __name__
#
MQTT_SIGNAL_TIME = 0.2
ALL_STATE_KEYS = ["state"]
BOOL_KEYS = ["state"]
@pytest.fixture
def this_device():
mc = mqtt_client(DUT_CLIENT_ID, 'localhost')
return test_device(mc, TOPIC)
def test_initial_states(this_device: test_device):
# test all initial values
init_state(ALL_STATE_KEYS, this_device)
def test_state_change_by_mqtt(this_device: test_device):
def state_data(key):
if key in BOOL_KEYS:
return (True, False)
else:
raise IndexError("No return value defined for key %s" % key)
def mqtt_data(key):
if key in BOOL_KEYS:
return ('ON', 'OFF')
else:
return state_data(key)
# test state changes
tm_warning = state_change_by_mqtt(ALL_STATE_KEYS, 2, mqtt_test_client, TOPIC, this_device,
mqtt_data, state_data, None, MQTT_SIGNAL_TIME)
def test_specific_get_functions(this_device: test_device):
assert this_device.linkquality == this_device.get(this_device.KEY_LINKQUALITY)
assert this_device.output_0 == this_device.get(this_device.KEY_OUTPUT_0)

View File

@ -1,40 +0,0 @@
from function.modules import heating_function as test_class
"""
config.DEFAULT_TEMPERATURE[heating_valve.topic],
db_data = get_radiator_data(heating_valve.topic)
**{
test_class.KEY_USER_TEMPERATURE_SETPOINT: db_data[2],
test_class.KEY_TEMPERATURE_SETPOINT: db_data[3],
test_class.KEY_AWAY_MODE: db_data[0],
test_class.KEY_SUMMER_MODE: db_data[1],
})
"""
def test_initial_states():
class heating_valve(object):
KEY_HEATING_SETPOINT = 'hsp'
KEY_TEMPERATURE = 'temp'
def set_heating_setpoint(self, value):
pass
def add_callback(self, key, value, callback):
pass
#
#
#
tc = test_class(
heating_valve(),
21, **{
test_class.KEY_USER_TEMPERATURE_SETPOINT: 22,
test_class.KEY_TEMPERATURE_SETPOINT: 17,
test_class.KEY_AWAY_MODE: True,
test_class.KEY_SUMMER_MODE: False,
})
assert tc.get(test_class.KEY_USER_TEMPERATURE_SETPOINT) == 22
assert tc.get(test_class.KEY_TEMPERATURE_SETPOINT) == 17
assert tc.get(test_class.KEY_AWAY_MODE) == True
assert tc.get(test_class.KEY_SUMMER_MODE) == False

View File

@ -1,2 +0,0 @@
pytest
pytest-cov

138
base.py
View File

@ -1,138 +0,0 @@
import json
import logging
import task
try:
from config import APP_NAME as ROOT_LOGGER_NAME
except ImportError:
ROOT_LOGGER_NAME = 'root'
class common_base(dict):
DEFAULT_VALUES = {}
def __init__(self, default_values=None):
super().__init__(default_values or self.DEFAULT_VALUES)
#
self.__callback_list__ = []
self.logger = logging.getLogger(ROOT_LOGGER_NAME).getChild("devices")
def add_callback(self, key, data, callback, on_change_only=False, init_now=False):
"""
key: key or None for all keys
data: data or None for all data
"""
cb_tup = (key, data, callback, on_change_only)
if cb_tup not in self.__callback_list__:
self.__callback_list__.append(cb_tup)
if init_now and self.get(key) is not None:
callback(self, key, self[key])
def set(self, key, data, block_callback=[]):
if key in self.keys():
value_changed = self[key] != data
self[key] = data
for cb_key, cb_data, callback, on_change_only in self.__callback_list__:
if cb_key is None or key == cb_key: # key fits to callback definition
if cb_data is None or cb_data == self[key]: # data fits to callback definition
if value_changed or not on_change_only: # change status fits to callback definition
if not callback in block_callback: # block given callbacks
callback(self, key, self[key])
else:
self.logger.warning("Unexpected key %s", key)
class mqtt_base(common_base):
def __init__(self, mqtt_client, topic, default_values=None):
super().__init__(default_values)
#
self.mqtt_client = mqtt_client
self.topic = topic
for entry in self.topic.split('/'):
self.logger = self.logger.getChild(entry)
class videv_base(mqtt_base):
KEY_INFO = '__info__'
#
SET_TOPIC = "set"
def __init__(self, mqtt_client, topic, default_values=None):
super().__init__(mqtt_client, topic, default_values=default_values)
self.__display_dict__ = {}
self.__control_dict__ = {}
self.__periodic__ = task.periodic(300, self.send_all)
self.__periodic__.run()
def send_all(self, rt):
try:
for key in self:
if self[key] is not None:
self.__tx__(key, self[key])
except RuntimeError:
self.logger.warning("Runtimeerror while sending cyclic videv information. This may happen on startup.")
def add_display(self, my_key, ext_device, ext_key, on_change_only=True):
"""
listen to data changes of ext_device and update videv information
"""
if my_key not in self.keys():
self[my_key] = None
if ext_device.__class__.__name__ == "group":
# store information to identify callback from ext_device
self.__display_dict__[(id(ext_device[0]), ext_key)] = my_key
# register a callback to listen for data from external device
ext_device[0].add_callback(ext_key, None, self.__rx_ext_device_data__, on_change_only, init_now=True)
else:
# store information to identify callback from ext_device
self.__display_dict__[(id(ext_device), ext_key)] = my_key
# register a callback to listen for data from external device
ext_device.add_callback(ext_key, None, self.__rx_ext_device_data__, on_change_only, init_now=True)
# send initial display data to videv interface
data = ext_device.get(ext_key)
if data is not None:
self.__tx__(my_key, data)
def __rx_ext_device_data__(self, ext_device, ext_key, data):
my_key = self.__display_dict__[(id(ext_device), ext_key)]
self.set(my_key, data)
self.__tx__(my_key, data)
def __tx__(self, key, data):
if type(data) not in (str, ):
data = json.dumps(data)
self.mqtt_client.send('/'.join([self.topic, key]), data)
def add_control(self, my_key, ext_device, ext_key, on_change_only=True):
"""
listen to videv information and pass data to ext_device
"""
if my_key not in self.keys():
self[my_key] = None
# store information to identify callback from videv
self.__control_dict__[my_key] = (ext_device, ext_key, on_change_only)
# add callback for videv changes
self.mqtt_client.add_callback('/'.join([self.topic, my_key, self.SET_TOPIC]), self.__rx_videv_data__)
def __rx_videv_data__(self, client, userdata, message):
my_key = message.topic.split('/')[-2]
try:
data = json.loads(message.payload)
except json.decoder.JSONDecodeError:
data = message.payload
ext_device, ext_key, on_change_only = self.__control_dict__[my_key]
if my_key in self.keys():
if data != self[my_key] or not on_change_only:
ext_device.send_command(ext_key, data)
else:
self.logger.info("Ignoring rx message with topic %s", message.topic)
def add_routing(self, my_key, ext_device, ext_key, on_change_only_disp=True, on_change_only_videv=True):
"""
listen to data changes of ext_device and update videv information
and
listen to videv information and pass data to ext_device
"""
# add display
self.add_display(my_key, ext_device, ext_key, on_change_only_disp)
self.add_control(my_key, ext_device, ext_key, on_change_only_videv)

View File

@ -1,26 +0,0 @@
import geo
import logging
import report
from topics import *
DEBUG = False # False: logging to stdout with given LOGLEVEL - True: logging all to localhost:19996 and warnings or higher to stdout
LOGLEVEL = logging.INFO
GEO_POSITION = geo.gps.coordinate(lat=49.519167, lon=9.3672222)
APP_NAME = "smart_brain"
MQTT_SERVER = "{{ smart_srv_brain_hostname }}"
MQTT_PORT = 1883
MQTT_USER = "{{ smart_srv_brain_username }}"
MQTT_PASSWORD = "{{ smart_srv_brain_password }}"
CHRISTMAS = True
#
# PARAMETERS
#
USER_ON_TIME_STAIRWAYS = 100
DEFAULT_TEMPERATURE = 21.5

1
devdi

@ -1 +0,0 @@
Subproject commit 619d7f926f13ce03950db9c5dab3461e5b7da23a

View File

@ -1,114 +0,0 @@
import config
import mqtt
import readline
import sys
import report
import logging
import devices
import json
if __name__ == "__main__":
report.stdoutLoggingConfigure([[config.APP_NAME, logging.INFO], ], fmt=report.SHORT_FMT)
mc = mqtt.mqtt_client(
host=config.MQTT_SERVER,
port=config.MQTT_PORT,
username=config.MQTT_USER,
password=config.MQTT_PASSWORD,
name=config.APP_NAME + '_devicetest'
)
#
devicedict = {}
for device in [
# devices.shelly_pro3(mc, "shellies/gfw/pro3"),
# devices.brennenstuhl_heatingvalve(mc, "zigbee_raspi/heatvlv"),
# devices.silvercrest_button(mc, "zigbee_raspi/button"),
devices.hue_sw_br_ct(mc, "zigbee_ffe/kitchen/main_light_1"),
]:
devicedict[device.topic.replace("/", "_")] = device
#
COMMANDS = ['quit', 'help', 'action']
for key in devicedict:
device = devicedict[key]
for cmd in device.__class__.__dict__:
obj = getattr(device, cmd)
if callable(obj) and not cmd.startswith("_"):
COMMANDS.append(key + "." + cmd)
#
def reduced_list(text):
"""
Create reduced completation list
"""
reduced_list = {}
for cmd in COMMANDS:
next_dot = cmd[len(text):].find('.')
if next_dot >= 0:
reduced_list[cmd[:len(text) + next_dot + 1]] = None
else:
reduced_list[cmd] = None
return reduced_list.keys()
def completer(text, state):
"""
Our custom completer function
"""
options = [x for x in reduced_list(text) if x.startswith(text)]
return options[state]
def complete(text, state):
for cmd in COMMANDS:
if cmd.startswith(text):
if not state:
hit = ""
index = 0
sub_list = cmd.split('.')
while len(text) >= len(hit):
hit += sub_list[index] + '.'
return hit # cmd
else:
state -= 1
if len(sys.argv) == 1:
readline.parse_and_bind("tab: complete")
readline.set_completer(completer)
print("\nEnter command: ")
while True:
userfeedback = input('')
command = userfeedback.split(' ')[0]
if userfeedback == 'quit':
break
elif userfeedback == 'help':
print("Help is not yet implemented!")
else:
try:
key, command = userfeedback.split(".", 1)
except ValueError:
print("Unknown device.")
else:
device = devicedict[key]
try:
command, params = command.split(" ", 1)
except ValueError:
params = None
try:
obj = getattr(device, command)
except AttributeError:
print("Unknown command.")
else:
if params is not None:
params = params.replace("True", "true")
params = params.replace("False", "false")
params = params.replace("None", "null")
params = params.replace(",", " ")
params = params.split(" ")
params = " ".join([p for p in params if p])
try:
params = json.loads("[" + params.replace(" ", ", ") + "]")
except json.decoder.JSONDecodeError:
print("You need to give python like parameters (e.g. 'test' for a string containing test).")
params = None
try:
obj(*params)
except TypeError:
print("Give the correct parameters to execute.")

View File

@ -26,81 +26,887 @@ devices (DEVICES)
""" """
__DEPENDENCIES__ = []
import json
import logging
try: try:
from config import APP_NAME as ROOT_LOGGER_NAME from config import APP_NAME as ROOT_LOGGER_NAME
except ImportError: except ImportError:
ROOT_LOGGER_NAME = 'root' ROOT_LOGGER_NAME = 'root'
from devices.shelly import shelly as shelly_sw1 BATTERY_WARN_LEVEL = 5
from devices.shelly import shelly_rpc as shelly_pro3
from devices.hue import hue_light as hue_sw_br_ct
from devices.tradfri import tradfri_light as tradfri_sw
from devices.tradfri import tradfri_light as tradfri_sw_br
from devices.tradfri import tradfri_light as tradfri_sw_br_ct
from devices.tradfri import tradfri_button as tradfri_button
from devices.tradfri import tradfri_light as livarno_sw_br_ct
from devices.brennenstuhl import brennenstuhl_heatingvalve
from devices.silvercrest import silvercrest_button
from devices.silvercrest import silvercrest_powerplug
from devices.silvercrest import silvercrest_motion_sensor
from devices.mydevices import powerplug as my_powerplug
from devices.mydevices import audio_status
from devices.mydevices import remote
my_ambient = None
class group(object): def is_json(data):
def __init__(self, *args): try:
super().__init__() json.loads(data)
self._members = args except json.decoder.JSONDecodeError:
self._iter_counter = 0 return False
else:
return True
class base(dict):
TX_TOPIC = "set"
TX_VALUE = 0
TX_DICT = 1
TX_TYPE = -1
TX_FILTER_DATA_KEYS = []
#
RX_KEYS = []
RX_IGNORE_TOPICS = []
RX_IGNORE_KEYS = []
RX_FILTER_DATA_KEYS = []
def __init__(self, mqtt_client, topic):
# data storage
self.mqtt_client = mqtt_client
self.topic = topic
self.logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
for entry in self.topic.split('/'):
self.logger = self.logger.getChild(entry)
# initialisations
dict.__init__(self)
mqtt_client.add_callback(
topic=self.topic, callback=self.receive_callback)
mqtt_client.add_callback(
topic=self.topic+"/#", callback=self.receive_callback)
# #
self.methods = [] self.callback_list = []
self.variables = [] self.warning_callback = None
for name in [m for m in args[0].__class__.__dict__.keys()]:
if not name.startswith('_') and callable(getattr(args[0], name)): # add all public callable attributes to the list
self.methods.append(name)
if not name.startswith('_') and not callable(getattr(args[0], name)): # add all public callable attributes to the list
self.variables.append(name)
# #
for member in self: self.__previous__ = {}
methods = [m for m in member.__class__.__dict__.keys() if not m.startswith(
'_') if not m.startswith('_') and callable(getattr(args[0], m))]
if self.methods != methods:
raise ValueError("All given instances needs to have same methods:", self.methods, methods)
#
variables = [v for v in member.__class__.__dict__.keys() if not v.startswith(
'_') if not v.startswith('_') and not callable(getattr(args[0], v))]
if self.variables != variables:
raise ValueError("All given instances needs to have same variables:", self.variables, variables)
def __iter__(self): def receive_callback(self, client, userdata, message):
return self self.unpack(message)
def __next__(self): def unpack_filter(self, key):
if self._iter_counter < len(self): if key in self.RX_FILTER_DATA_KEYS:
self._iter_counter += 1 if self.get(key) == 1 or self.get(key) == 'on' or self.get(key) == 'ON':
return self._members[self._iter_counter - 1] self[key] = True
self._iter_counter = 0 elif self.get(key) == 0 or self.get(key) == 'off' or self.get(key) == 'OFF':
raise StopIteration self[key] = False
def __getitem__(self, i): def unpack_single_value(self, key, data):
return self._members[i] prev_value = self.get(key)
if key in self.RX_KEYS:
def __len__(self): self[key] = data
return len(self._members) self.__previous__[key] = prev_value
# Filter, if needed
def __getattribute__(self, name): self.unpack_filter(key)
def group_execution(*args, **kwargs): self.logger.debug("Received data %s - %s", key, str(self.get(key)))
for member in self[:]: self.callback_caller(key, self[key], self.get(key) != self.__previous__.get(key))
m = getattr(member, name) elif key not in self.RX_IGNORE_KEYS:
m(*args, **kwargs) self.logger.warning('Got a message with unparsed content: "%s - %s"', key, str(data))
try:
rv = super().__getattribute__(name)
except AttributeError:
if callable(getattr(self[0], name)):
return group_execution
else:
return getattr(self[0], name)
else: else:
return rv self.logger.debug("Ignoring key %s", key)
def unpack(self, message):
content_key = message.topic[len(self.topic) + 1:]
if content_key not in self.RX_IGNORE_TOPICS and (not message.topic.endswith(self.TX_TOPIC) or len(self.TX_TOPIC) == 0):
self.logger.debug("Unpacking content_key \"%s\" from message.", content_key)
if is_json(message.payload):
data = json.loads(message.payload)
if type(data) is dict:
for key in data:
self.unpack_single_value(key, data[key])
else:
self.unpack_single_value(content_key, data)
# String
else:
self.unpack_single_value(
content_key, message.payload.decode('utf-8'))
self.warning_caller()
else:
self.logger.debug("Ignoring topic %s", content_key)
def pack_filter(self, key, data):
if key in self.TX_FILTER_DATA_KEYS:
if data is True:
return "on"
elif data is False:
return "off"
else:
return data
return data
def set(self, key, data):
self.pack(key, data)
def pack(self, key, data):
data = self.pack_filter(key, data)
if self.TX_TOPIC is not None:
if self.TX_TYPE < 0:
self.logger.error("Unknown tx type. Set TX_TYPE of class to a known value")
else:
self.logger.debug("Sending data for %s - %s", key, str(data))
if self.TX_TYPE == self.TX_DICT:
self.mqtt_client.send('/'.join([self.topic, self.TX_TOPIC]), json.dumps({key: data}))
else:
if type(data) not in [str, bytes]:
data = json.dumps(data)
self.mqtt_client.send('/'.join([self.topic, key, self.TX_TOPIC] if len(self.TX_TOPIC) > 0 else [self.topic, key]), data)
else:
self.logger.error("Unknown tx toptic. Set TX_TOPIC of class to a known value")
def add_callback(self, key, data, callback, on_change_only=False):
"""
key: key or None for all keys
data: data or None for all data
"""
cb_tup = (key, data, callback, on_change_only)
if cb_tup not in self.callback_list:
self.callback_list.append(cb_tup)
def add_warning_callback(self, callback):
self.warning_callback = callback
def warning_call_condition(self):
return False
def callback_caller(self, key, data, value_changed):
for cb_key, cb_data, callback, on_change_only in self.callback_list:
if (cb_key == key or cb_key is None) and (cb_data == data or cb_data is None) and callback is not None:
if not on_change_only or value_changed:
callback(self, key, data)
def warning_caller(self):
if self.warning_call_condition():
warn_txt = self.warning_text()
self.logger.warning(warn_txt)
if self.warning_callback is not None:
self.warning_callback(self, warn_txt)
def warning_text(self):
return "default warning text - replace parent warning_text function"
def previous_value(self, key):
return self.__previous__.get(key)
class shelly(base):
KEY_OUTPUT_0 = "relay/0"
KEY_OUTPUT_1 = "relay/1"
KEY_INPUT_0 = "input/0"
KEY_INPUT_1 = "input/1"
KEY_LONGPUSH_0 = "longpush/0"
KEY_LONGPUSH_1 = "longpush/1"
KEY_TEMPERATURE = "temperature"
KEY_OVERTEMPERATURE = "overtemperature"
KEY_ID = "id"
KEY_MODEL = "model"
KEY_MAC = "mac"
KEY_IP = "ip"
KEY_NEW_FIRMWARE = "new_fw"
KEY_FIRMWARE_VERSION = "fw_ver"
#
TX_TOPIC = "command"
TX_TYPE = base.TX_VALUE
TX_FILTER_DATA_KEYS = [KEY_OUTPUT_0, KEY_OUTPUT_1]
#
RX_KEYS = [KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_INPUT_0, KEY_INPUT_1, KEY_LONGPUSH_0, KEY_LONGPUSH_1, KEY_OVERTEMPERATURE, KEY_TEMPERATURE,
KEY_ID, KEY_MODEL, KEY_MAC, KEY_IP, KEY_NEW_FIRMWARE, KEY_FIRMWARE_VERSION]
RX_IGNORE_TOPICS = [KEY_OUTPUT_0 + '/' + "energy", KEY_OUTPUT_1 + '/' + "energy", 'input_event/0', 'input_event/1']
RX_IGNORE_KEYS = ['temperature_f']
RX_FILTER_DATA_KEYS = [KEY_INPUT_0, KEY_INPUT_1, KEY_LONGPUSH_0, KEY_LONGPUSH_1, KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_OVERTEMPERATURE]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
#
# WARNING CALL
#
def warning_call_condition(self):
return self.get(self.KEY_OVERTEMPERATURE)
def warning_text(self):
if self.overtemperature:
if self.temperature is not None:
return "Overtemperature detected for %s. Temperature was %.1f°C." % (self.topic, self.temperature)
else:
return "Overtemperature detected for %s." % self.topic
#
# RX
#
@property
def output_0(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_0)
@property
def output_1(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_1)
@property
def input_0(self):
"""rv: [True, False]"""
return self.get(self.KEY_INPUT_0)
@property
def input_1(self):
"""rv: [True, False]"""
return self.get(self.KEY_INPUT_1)
@property
def longpush_0(self):
"""rv: [True, False]"""
return self.get(self.KEY_LONGPUSH_0)
@property
def longpush_1(self):
"""rv: [True, False]"""
return self.get(self.KEY_LONGPUSH_1)
@property
def temperature(self):
"""rv: numeric value"""
return self.get(self.KEY_TEMPERATURE)
#
# TX
#
def set_output_0(self, state):
"""state: [True, False, 'toggle']"""
self.pack(self.KEY_OUTPUT_0, state)
def set_output_0_mcb(self, device, key, data):
self.logger.log(logging.INFO if data != self.output_0 else logging.DEBUG, "Changing output 0 to %s", str(data))
self.set_output_0(data)
def toggle_output_0_mcb(self, device, key, data):
self.logger.info("Toggeling output 0")
self.set_output_0('toggle')
def set_output_1(self, state):
"""state: [True, False, 'toggle']"""
self.pack(self.KEY_OUTPUT_1, state)
def set_output_1_mcb(self, device, key, data):
self.logger.log(logging.INFO if data != self.output_1 else logging.DEBUG, "Changing output 1 to %s", str(data))
self.set_output_1(data)
def toggle_output_1_mcb(self, device, key, data):
self.logger.info("Toggeling output 1")
self.set_output_1('toggle')
class silvercrest_powerplug(base):
KEY_LINKQUALITY = "linkquality"
KEY_OUTPUT_0 = "state"
#
TX_TYPE = base.TX_DICT
TX_FILTER_DATA_KEYS = [KEY_OUTPUT_0]
#
RX_KEYS = [KEY_LINKQUALITY, KEY_OUTPUT_0]
RX_FILTER_DATA_KEYS = [KEY_OUTPUT_0]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
#
# RX
#
@property
def output_0(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_0)
@property
def linkquality(self):
"""rv: numeric value"""
return self.get(self.KEY_LINKQUALITY)
#
# TX
#
def set_output_0(self, state):
"""state: [True, False, 'toggle']"""
self.pack(self.KEY_OUTPUT_0, state)
def set_output_0_mcb(self, device, key, data):
self.logger.log(logging.INFO if data != self.output_0 else logging.DEBUG, "Changing output 0 to %s", str(data))
self.set_output_0(data)
def toggle_output_0_mcb(self, device, key, data):
self.logger.info("Toggeling output 0")
self.set_output_0('toggle')
class silvercrest_motion_sensor(base):
KEY_BATTERY = "battery"
KEY_BATTERY_LOW = "battery_low"
KEY_LINKQUALITY = "linkquality"
KEY_OCCUPANCY = "occupancy"
KEY_UNMOUNTED = "tamper"
KEY_VOLTAGE = "voltage"
#
RX_KEYS = [KEY_BATTERY, KEY_BATTERY_LOW, KEY_LINKQUALITY, KEY_OCCUPANCY, KEY_UNMOUNTED, KEY_VOLTAGE]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
def warning_call_condition(self):
return self.get(self.KEY_BATTERY_LOW)
def warning_text(self, data):
return "Battery low: level=%d" % self.get(self.KEY_BATTERY)
#
# RX
#
@property
def linkquality(self):
"""rv: numeric value"""
return self.get(self.KEY_LINKQUALITY)
class my_powerplug(base):
KEY_OUTPUT_0 = "output/1"
KEY_OUTPUT_1 = "output/2"
KEY_OUTPUT_2 = "output/3"
KEY_OUTPUT_3 = "output/4"
KEY_OUTPUT_ALL = "output/all"
KEY_OUTPUT_LIST = [KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_OUTPUT_2, KEY_OUTPUT_3]
#
TX_TYPE = base.TX_VALUE
#
RX_KEYS = [KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_OUTPUT_2, KEY_OUTPUT_3]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
#
# RX
#
@property
def output_0(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_0)
@property
def output_1(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_1)
@property
def output_2(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_2)
@property
def output_3(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_3)
#
# TX
#
def set_output(self, key, state):
if key in self.KEY_OUTPUT_LIST:
self.pack(key, state)
else:
logging.error("Unknown key to set the output!")
def set_output_0(self, state):
"""state: [True, False, 'toggle']"""
self.pack(self.KEY_OUTPUT_0, state)
def set_output_0_mcb(self, device, key, data):
self.logger.log(logging.INFO if data != self.output_0 else logging.DEBUG, "Changing output 0 to %s", str(data))
self.set_output_0(data)
def toggle_output_0_mcb(self, device, key, data):
self.logger.info("Toggeling output 0")
self.set_output_0('toggle')
def set_output_1(self, state):
"""state: [True, False, 'toggle']"""
self.pack(self.KEY_OUTPUT_1, state)
def set_output_1_mcb(self, device, key, data):
self.logger.log(logging.INFO if data != self.output_1 else logging.DEBUG, "Changing output 1 to %s", str(data))
self.set_output_1(data)
def toggle_output_1_mcb(self, device, key, data):
self.logger.info("Toggeling output 1")
self.set_output_1('toggle')
def set_output_2(self, state):
"""state: [True, False, 'toggle']"""
self.pack(self.KEY_OUTPUT_2, state)
def set_output_2_mcb(self, device, key, data):
self.logger.log(logging.INFO if data != self.output_2 else logging.DEBUG, "Changing output 2 to %s", str(data))
self.set_output_2(data)
def toggle_output_2_mcb(self, device, key, data):
self.logger.info("Toggeling output 2")
self.set_output_2('toggle')
def set_output_3(self, state):
"""state: [True, False, 'toggle']"""
self.pack(self.KEY_OUTPUT_3, state)
def set_output_3_mcb(self, device, key, data):
self.logger.log(logging.INFO if data != self.output_3 else logging.DEBUG, "Changing output 3 to %s", str(data))
self.set_output_3(data)
def toggle_output_3_mcb(self, device, key, data):
self.logger.info("Toggeling output 3")
self.set_output_3('toggle')
def set_output_all(self, state):
"""state: [True, False, 'toggle']"""
self.pack(self.KEY_OUTPUT_ALL, state)
def set_output_all_mcb(self, device, key, data):
self.logger.info("Changing all outputs to %s", str(data))
self.set_output_all(data)
def toggle_output_all_mcb(self, device, key, data):
self.logger.info("Toggeling all outputs")
self.set_output_0('toggle')
class tradfri_light(base):
KEY_LINKQUALITY = "linkquality"
KEY_OUTPUT_0 = "state"
KEY_BRIGHTNESS = "brightness"
KEY_COLOR_TEMP = "color_temp"
KEY_BRIGHTNESS_FADE = "brightness_move"
#
TX_TYPE = base.TX_DICT
TX_FILTER_DATA_KEYS = [KEY_OUTPUT_0, KEY_BRIGHTNESS, KEY_COLOR_TEMP, KEY_BRIGHTNESS_FADE]
#
RX_KEYS = [KEY_LINKQUALITY, KEY_OUTPUT_0, KEY_BRIGHTNESS, KEY_COLOR_TEMP]
RX_IGNORE_KEYS = ['update', 'color_mode']
RX_FILTER_DATA_KEYS = [KEY_OUTPUT_0, KEY_BRIGHTNESS, KEY_COLOR_TEMP]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
def unpack_filter(self, key):
if key == self.KEY_BRIGHTNESS:
self[key] = (self[key] - 1) * 100 / 254
elif key == self.KEY_COLOR_TEMP:
self[key] = (self[key] - 250) * 10 / 204
else:
super().unpack_filter(key)
def pack_filter(self, key, data):
if key == self.KEY_BRIGHTNESS:
return data * 254 / 100 + 1
elif key == self.KEY_COLOR_TEMP:
return data * 204 / 10 + 250
else:
return super().pack_filter(key, data)
#
# RX
#
@property
def output_0(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_0, False)
@property
def linkquality(self):
"""rv: numeric value"""
return self.get(self.KEY_LINKQUALITY, 0)
@property
def brightness(self):
"""rv: numeric value [0%, ..., 100%]"""
return self.get(self.KEY_BRIGHTNESS, 0)
@property
def color_temp(self):
"""rv: numeric value [0, ..., 10]"""
return self.get(self.KEY_COLOR_TEMP, 0)
#
# TX
#
def set_output_0(self, state):
"""state: [True, False, 'toggle']"""
self.pack(self.KEY_OUTPUT_0, state)
def set_output_0_mcb(self, device, key, data):
self.logger.log(logging.INFO if data != self.output_0 else logging.DEBUG, "Changing output 0 to %s", str(data))
self.set_output_0(data)
def toggle_output_0_mcb(self, device, key, data):
self.logger.info("Toggeling output 0")
self.set_output_0('toggle')
def set_brightness(self, brightness):
"""brightness: [0, ..., 100]"""
self.pack(self.KEY_BRIGHTNESS, brightness)
def set_brightness_mcb(self, device, key, data):
self.logger.log(logging.INFO if data != self.brightness else logging.DEBUG, "Changing brightness to %s", str(data))
self.set_brightness(data)
def default_inc(self, speed=40):
self.pack(self.KEY_BRIGHTNESS_FADE, speed)
def default_dec(self, speed=-40):
self.default_inc(speed)
def default_stop(self):
self.default_inc(0)
def set_color_temp(self, color_temp):
"""color_temp: [0, ..., 10]"""
self.pack(self.KEY_COLOR_TEMP, color_temp)
def set_color_temp_mcb(self, device, key, data):
self.logger.log(logging.INFO if data != self.color_temp else logging.DEBUG, "Changing color temperature to %s", str(data))
self.set_color_temp(data)
class tradfri_button(base):
ACTION_TOGGLE = "toggle"
ACTION_BRIGHTNESS_UP = "brightness_up_click"
ACTION_BRIGHTNESS_DOWN = "brightness_down_click"
ACTION_RIGHT = "arrow_right_click"
ACTION_LEFT = "arrow_left_click"
ACTION_BRIGHTNESS_UP_LONG = "brightness_up_hold"
ACTION_BRIGHTNESS_UP_RELEASE = "brightness_up_release"
ACTION_BRIGHTNESS_DOWN_LONG = "brightness_down_hold"
ACTION_BRIGHTNESS_DOWN_RELEASE = "brightness_down_release"
ACTION_RIGHT_LONG = "arrow_right_hold"
ACTION_RIGHT_RELEASE = "arrow_right_release"
ACTION_LEFT_LONG = "arrow_left_hold"
ACTION_LEFT_RELEASE = "arrow_left_release"
#
KEY_LINKQUALITY = "linkquality"
KEY_BATTERY = "battery"
KEY_ACTION = "action"
KEY_ACTION_DURATION = "action_duration"
#
RX_KEYS = [KEY_LINKQUALITY, KEY_BATTERY, KEY_ACTION]
RX_IGNORE_KEYS = ['update', KEY_ACTION_DURATION]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
#
# RX
#
@property
def action(self):
"""rv: action_txt"""
return self.get(self.KEY_ACTION)
#
# WARNING CALL
#
def warning_call_condition(self):
return self.get(self.KEY_BATTERY) is not None and self.get(self.KEY_BATTERY) <= BATTERY_WARN_LEVEL
def warning_text(self):
return "Low battery level detected for %s. Battery level was %.0f%%." % (self.topic, self.get(self.KEY_BATTERY))
class nodered_gui_leds(base):
KEY_LED_0 = "led0"
KEY_LED_1 = "led1"
KEY_LED_2 = "led2"
KEY_LED_3 = "led3"
KEY_LED_4 = "led4"
KEY_LED_5 = "led5"
KEY_LED_6 = "led6"
KEY_LED_7 = "led7"
KEY_LED_8 = "led8"
KEY_LED_9 = "led9"
KEY_LED_LIST = [KEY_LED_0, KEY_LED_1, KEY_LED_2, KEY_LED_3, KEY_LED_4, KEY_LED_5, KEY_LED_6, KEY_LED_7, KEY_LED_8, KEY_LED_9]
#
TX_TYPE = base.TX_VALUE
def set_led(self, key, data):
"""data: [True, False]"""
self.logger.debug("Sending %s with content %s", key, str(data))
self.pack(key, data)
class nodered_gui_timer(base):
KEY_TIMER = "timer"
#
TX_TYPE = base.TX_VALUE
def set_timer(self, data):
"""data: numeric"""
self.pack(self.KEY_TIMER, data)
def set_timer_mcb(self, device, key, data):
self.logger.debug("Sending %s with content %s", key, str(data))
self.set_timer(data)
class nodered_gui_button(base):
KEY_STATE = "state"
#
RX_KEYS = [KEY_STATE]
#
# RX
#
@property
def state(self):
"""rv: [True, False]"""
return self.get(self.KEY_STATE)
class nodered_gui_switch(nodered_gui_button):
TX_TYPE = base.TX_VALUE
#
# TX
#
def set_state(self, data):
"""data: [True, False]"""
self.pack(self.KEY_STATE, data)
def set_state_mcb(self, device, key, data):
self.logger.debug("Sending %s with content %s", key, str(data))
self.set_state(data)
class nodered_gui_light(nodered_gui_switch, nodered_gui_leds, nodered_gui_timer):
KEY_ENABLE = "enable"
KEY_BRIGHTNESS = "brightness"
KEY_COLOR_TEMP = "color_temp"
#
TX_TYPE = base.TX_VALUE
#
RX_KEYS = nodered_gui_switch.RX_KEYS + [KEY_ENABLE, KEY_BRIGHTNESS, KEY_COLOR_TEMP]
#
# RX
#
@property
def enable(self):
"""rv: [True, False]"""
return self.get(self.KEY_ENABLE)
@property
def brightness(self):
"""rv: [True, False]"""
return self.get(self.KEY_BRIGHTNESS)
@property
def color_temp(self):
"""rv: [True, False]"""
return self.get(self.KEY_COLOR_TEMP)
#
# TX
#
def set_enable(self, data):
"""data: [True, False]"""
self.pack(self.KEY_ENABLE, data)
def set_enable_mcb(self, device, key, data):
self.logger.debug("Sending %s with content %s", key, str(data))
self.set_enable(data)
def set_brightness(self, data):
"""data: [0%, ..., 100%]"""
self.pack(self.KEY_BRIGHTNESS, data)
def set_brightness_mcb(self, device, key, data):
self.logger.debug("Sending %s with content %s", key, str(data))
self.set_brightness(data)
def set_color_temp(self, data):
"""data: [0, ..., 10]"""
self.pack(self.KEY_COLOR_TEMP, data)
def set_color_temp_mcb(self, device, key, data):
self.logger.debug("Sending %s with content %s", key, str(data))
self.set_color_temp(data)
class nodered_gui_radiator(nodered_gui_timer):
KEY_TEMPERATURE = "temperature"
KEY_SETPOINT_TEMP = "setpoint_temp"
KEY_SETPOINT_TO_DEFAULT = "setpoint_to_default"
KEY_BOOST = 'boost'
KEY_AWAY = "away"
KEY_SUMMER = "summer"
KEY_ENABLE = "enable"
#
RX_KEYS = [KEY_TEMPERATURE, KEY_SETPOINT_TEMP, KEY_SETPOINT_TO_DEFAULT, KEY_BOOST, KEY_AWAY, KEY_SUMMER]
#
# TX
#
def set_temperature(self, data):
"""data: [True, False]"""
self.pack(self.KEY_TEMPERATURE, data)
def set_temperature_mcb(self, device, key, data):
self.logger.debug("Sending %s with content %s", key, str(data))
self.set_temperature(data)
def set_setpoint_temperature(self, data):
"""data: [True, False]"""
self.pack(self.KEY_SETPOINT_TEMP, data)
def set_setpoint_temperature_mcb(self, device, key, data):
self.logger.debug("Sending %s with content %s", key, str(data))
self.set_setpoint_temperature(data)
def set_away(self, data):
"""data: [True, False]"""
self.pack(self.KEY_AWAY, data)
def set_away_mcb(self, device, key, data):
self.logger.debug("Sending %s with content %s", key, str(data))
self.set_away(data)
def set_summer(self, data):
"""data: [True, False]"""
self.pack(self.KEY_SUMMER, data)
def set_summer_mcb(self, device, key, data):
self.logger.debug("Sending %s with content %s", key, str(data))
self.set_summer(data)
def set_enable(self, data):
"""data: [True, False]"""
self.pack(self.KEY_ENABLE, data)
def set_enable_mcb(self, device, key, data):
self.logger.debug("Sending %s with content %s", key, str(data))
self.set_enable(data)
class brennenstuhl_heatingvalve(base):
KEY_LINKQUALITY = "linkquality"
KEY_BATTERY = "battery"
KEY_HEATING_SETPOINT = "current_heating_setpoint"
KEY_TEMPERATURE = "local_temperature"
#
KEY_AWAY_MODE = "away_mode"
KEY_CHILD_LOCK = "child_lock"
KEY_PRESET = "preset"
KEY_SYSTEM_MODE = "system_mode"
KEY_VALVE_DETECTION = "valve_detection"
KEY_WINDOW_DETECTION = "window_detection"
#
TX_TYPE = base.TX_DICT
#
RX_KEYS = [KEY_LINKQUALITY, KEY_BATTERY, KEY_HEATING_SETPOINT, KEY_TEMPERATURE]
RX_IGNORE_KEYS = [KEY_AWAY_MODE, KEY_CHILD_LOCK, KEY_PRESET, KEY_SYSTEM_MODE, KEY_VALVE_DETECTION, KEY_WINDOW_DETECTION]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
self.mqtt_client.send(self.topic + '/' + self.TX_TOPIC, json.dumps({self.KEY_WINDOW_DETECTION: "ON",
self.KEY_CHILD_LOCK: "UNLOCK", self.KEY_VALVE_DETECTION: "ON", self.KEY_SYSTEM_MODE: "heat", self.KEY_PRESET: "manual"}))
def warning_call_condition(self):
return self.get(self.KEY_BATTERY, 100) <= BATTERY_WARN_LEVEL
def warning_text(self):
return "Low battery level detected for %s. Battery level was %.0f%%." % (self.topic, self.get(self.KEY_BATTERY))
#
# RX
#
@property
def linkqulity(self):
return self.get(self.KEY_LINKQUALITY)
@property
def heating_setpoint(self):
return self.get(self.KEY_HEATING_SETPOINT)
@property
def temperature(self):
return self.get(self.KEY_TEMPERATURE)
#
# TX
#
def set_heating_setpoint(self, setpoint):
self.pack(self.KEY_HEATING_SETPOINT, setpoint)
def set_heating_setpoint_mcb(self, device, key, data):
self.logger.info("Changing heating setpoint to %s", str(data))
self.set_heating_setpoint(data)
class remote(base):
KEY_CD = "CD"
KEY_LINE1 = "LINE1"
KEY_LINE3 = "LINE3"
KEY_MUTE = "MUTE"
KEY_POWER = "POWER"
KEY_VOLDOWN = "VOLDOWN"
KEY_VOLUP = "VOLUP"
#
TX_TOPIC = ''
TX_TYPE = base.TX_VALUE
#
RX_IGNORE_TOPICS = [KEY_CD, KEY_LINE1, KEY_LINE3, KEY_MUTE, KEY_POWER, KEY_VOLUP, KEY_VOLDOWN]
def set_cd(self, device=None, key=None, data=None):
self.pack(self.KEY_CD, None)
def set_line1(self, device=None, key=None, data=None):
self.pack(self.KEY_LINE1, None)
def set_line3(self, device=None, key=None, data=None):
self.pack(self.KEY_LINE3, None)
def set_mute(self, device=None, key=None, data=None):
self.pack(self.KEY_MUTE, None)
def set_power(self, device=None, key=None, data=None):
self.pack(self.KEY_POWER, None)
def set_volume_up(self, data=False):
"""data: [True, False]"""
self.pack(self.KEY_VOLUP, data)
def set_volume_down(self, data=False):
"""data: [True, False]"""
self.pack(self.KEY_VOLDOWN, data)
def default_inc(self, device=None, key=None, data=None):
self.set_volume_up(True)
def default_dec(self, device=None, key=None, data=None):
self.set_volume_down(True)
def default_stop(self, device=None, key=None, data=None):
self.set_volume_up(False)
class status(base):
KEY_STATE = "state"
#
TX_TYPE = base.TX_VALUE
#
RX_KEYS = [KEY_STATE]
def set_state(self, num, data):
"""data: [True, False]"""
self.pack(self.KEY_STATE + "/" + str(num), data)
def set_state_mcb(self, device, key, data):
self.logger.info("Changing state to %s", str(data))
self.set_state(data)
class audio_status(status):
KEY_TITLE = "title"
#
RX_KEYS = [status.KEY_STATE, KEY_TITLE]

View File

@ -1,235 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from base import mqtt_base
from base import videv_base
import json
def is_json(data):
try:
json.loads(data)
except json.decoder.JSONDecodeError:
return False
else:
return True
class base(mqtt_base):
TX_TOPIC = "set"
TX_VALUE = 0
TX_DICT = 1
TX_TYPE = -1
TX_FILTER_DATA_KEYS = []
#
RX_KEYS = []
RX_IGNORE_TOPICS = []
RX_IGNORE_KEYS = []
RX_FILTER_DATA_KEYS = []
#
CFG_DATA = {}
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic, default_values=dict.fromkeys(self.RX_KEYS))
# data storage
self.__cfg_by_mid__ = None
# initialisations
mqtt_client.add_callback(topic=self.topic, callback=self.receive_callback)
mqtt_client.add_callback(topic=self.topic+"/#", callback=self.receive_callback)
#
self.add_callback(None, None, self.__state_logging__, on_change_only=True)
def __cfg_callback__(self, key, value, mid):
if self.CFG_DATA.get(key) != value and self.__cfg_by_mid__ != mid and mid is not None:
self.__cfg_by_mid__ = mid
self.logger.warning("Differing configuration identified: Sending default configuration to defice: %s", repr(self.CFG_DATA))
if self.TX_TYPE == self.TX_DICT:
self.mqtt_client.send(self.topic + '/' + self.TX_TOPIC, json.dumps(self.CFG_DATA))
else:
for key in self.CFG_DATA:
self.send_command(key, self.CFG_DATA.get(key))
def set(self, key, data, mid=None, block_callback=[]):
if key in self.CFG_DATA:
self.__cfg_callback__(key, data, mid)
if key in self.RX_IGNORE_KEYS:
pass # ignore these keys
elif key in self.RX_KEYS:
return super().set(key, data, block_callback)
else:
self.logger.warning("Unexpected key %s", key)
def receive_callback(self, client, userdata, message):
if message.topic != self.topic + '/' + videv_base.KEY_INFO:
content_key = message.topic[len(self.topic) + 1:]
if content_key not in self.RX_IGNORE_TOPICS and (not message.topic.endswith(self.TX_TOPIC) or len(self.TX_TOPIC) == 0):
self.logger.debug("Unpacking content_key \"%s\" from message.", content_key)
if is_json(message.payload):
data = json.loads(message.payload)
if type(data) is dict:
for key in data:
self.set(key, self.__device_to_instance_filter__(key, data[key]), message.mid)
else:
self.set(content_key, self.__device_to_instance_filter__(content_key, data), message.mid)
# String
else:
self.set(content_key, self.__device_to_instance_filter__(content_key, message.payload.decode('utf-8')), message.mid)
else:
self.logger.debug("Ignoring topic %s", content_key)
def __device_to_instance_filter__(self, key, data):
if key in self.RX_FILTER_DATA_KEYS:
if data in [1, 'on', 'ON']:
return True
elif data in [0, 'off', 'OFF']:
return False
return data
def __instance_to_device_filter__(self, key, data):
if key in self.TX_FILTER_DATA_KEYS:
if data is True:
return "on"
elif data is False:
return "off"
return data
def send_command(self, key, data):
data = self.__instance_to_device_filter__(key, data)
if self.TX_TOPIC is not None:
if self.TX_TYPE < 0:
self.logger.error("Unknown tx type. Set TX_TYPE of class to a known value")
else:
self.logger.debug("Sending data for %s - %s", key, str(data))
if self.TX_TYPE == self.TX_DICT:
try:
self.mqtt_client.send('/'.join([self.topic, self.TX_TOPIC]), json.dumps({key: data}))
except TypeError:
print(self.topic)
print(key.__dict__)
print(key)
print(data)
raise TypeError
else:
if type(data) not in [str, bytes]:
data = json.dumps(data)
self.mqtt_client.send('/'.join([self.topic, key, self.TX_TOPIC] if len(self.TX_TOPIC) > 0 else [self.topic, key]), data)
else:
self.logger.error("Unknown tx toptic. Set TX_TOPIC of class to a known value")
class base_rpc(mqtt_base):
SRC_RESPONSE = "/response"
SRC_NULL = "/null"
#
EVENTS_TOPIC = "/events/rpc"
TX_TOPIC = "/rpc"
RESPONSE_TOPIC = SRC_RESPONSE + "/rpc"
NULL_TOPIC = SRC_NULL + "/rpc"
#
RPC_ID_GET_STATUS = 1
RPC_ID_SET = 1734
#
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic, default_values=dict.fromkeys(self.RX_KEYS))
# data storage
self.__cfg_by_mid__ = None
# initialisations
mqtt_client.add_callback(topic=self.topic, callback=self.receive_callback)
mqtt_client.add_callback(topic=self.topic+"/#", callback=self.receive_callback)
#
self.add_callback(None, None, self.__state_logging__, on_change_only=False)
#
self.rpc_get_status()
def receive_callback(self, client, userdata, message):
data = json.loads(message.payload)
#
if message.topic == self.topic + self.EVENTS_TOPIC:
self.events(data)
elif message.topic == self.topic + self.RESPONSE_TOPIC:
self.response(data)
elif message.topic == self.topic + self.NULL_TOPIC or message.topic == self.topic + self.TX_TOPIC or message.topic == self.topic + "/online":
pass # Ignore response
else:
self.logger.warning("Unexpected message received: %s::%s", message.topic, json.dumps(data, sort_keys=True, indent=4))
def events(self, data):
for rx_key in data["params"]:
if rx_key == "events":
for evt in data["params"]["events"]:
key = evt["component"]
event = evt["event"]
if key in self.RX_KEYS:
if event == "btn_down":
self.set(key, True)
elif event == "btn_up":
self.set(key, False)
else:
key = key + ":" + event
if key in self.RX_KEYS:
self.set(key, True)
else:
self.logger.warning("Unexpected event with data=%s", json.dumps(data, sort_keys=True, indent=4))
elif rx_key in self.RX_KEYS:
state = data["params"][rx_key].get("output")
if state is not None:
self.set(rx_key, state)
def response(self, data):
try:
rpc_id = data.get("id")
except AttributeError:
rpc_id = None
try:
rpc_method = data.get("method")
except AttributeError:
rpc_method = None
if rpc_id == self.RPC_ID_GET_STATUS:
#
# Shelly.GetStatus
#
for rx_key in data.get("result", []):
if rx_key in self.RX_KEYS:
key_data = data["result"][rx_key]
state = key_data.get("output", key_data.get("state"))
if state is not None:
self.set(rx_key, state)
else:
self.logger.warning("Unexpected response with data=%s", json.dumps(data, sort_keys=True, indent=4))
def rpc_tx(self, **kwargs):
if not "id" in kwargs:
raise AttributeError("'id' is missing in keyword arguments")
self.mqtt_client.send(self.topic + self.TX_TOPIC, json.dumps(kwargs))
def rpc_get_status(self):
self.rpc_tx(
id=self.RPC_ID_GET_STATUS,
src=self.topic + self.SRC_RESPONSE,
method="Shelly.GetStatus"
)
def rpc_switch_set(self, key, state: bool):
self.rpc_tx(
id=self.RPC_ID_SET,
src=self.topic + self.SRC_NULL,
method="Switch.Set",
params={"id": int(key[-1]), "on": state}
)
class base_output(base):
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
self.__all_off_enabled__ = True
def disable_all_off(self, state=True):
self.__all_off_enabled__ = not state
def all_off(self):
if self.__all_off_enabled__:
try:
self.__all_off__()
except (AttributeError, TypeError) as e:
self.logger.warning("Method all_off was used, but __all_off__ method wasn't callable: %s", repr(e))

View File

@ -1,118 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from devices.base import base
import task
import time
class brennenstuhl_heatingvalve(base):
""" Communication (MQTT)
brennenstuhl_heatingvalve {
| "away_mode": ["ON", "OFF"]
| "battery": [0...100] %
| "child_lock": ["LOCK", "UNLOCK"]
| "current_heating_setpoint": [5...30] °C
| "linkquality": [0...255] lqi
| "local_temperature": [numeric] °C
| "preset": ["manual", ...]
| "system_mode": ["heat", ...]
| "valve_detection": ["ON", "OFF"]
| "window_detection": ["ON", "OFF"]
| }
+- set {
"away_mode": ["ON", "OFF", "TOGGLE"]
"child_lock": ["LOCK", "UNLOCK"]
"current_heating_setpoint": [5...30] °C
"preset": ["manual", ...]
"system_mode": ["heat", ...]
"valve_detection": ["ON", "OFF", "TOGGLE"]
"window_detection": ["ON", "OFF", "TOGGLE"]
}
"""
KEY_LINKQUALITY = "linkquality"
KEY_BATTERY = "battery"
KEY_HEATING_SETPOINT = "current_heating_setpoint"
KEY_TEMPERATURE = "local_temperature"
#
KEY_AWAY_MODE = "away_mode"
KEY_CHILD_LOCK = "child_lock"
KEY_PRESET = "preset"
KEY_SYSTEM_MODE = "system_mode"
KEY_VALVE_DETECTION = "valve_detection"
KEY_WINDOW_DETECTION = "window_detection"
#
RETRY_CYCLE_TIME = 2.5
MAX_TX_RETRIES = 20
RETRY_TIMEOUT = RETRY_CYCLE_TIME * MAX_TX_RETRIES
#
TX_TYPE = base.TX_DICT
#
RX_KEYS = [KEY_LINKQUALITY, KEY_BATTERY, KEY_HEATING_SETPOINT, KEY_TEMPERATURE]
RX_IGNORE_KEYS = [KEY_AWAY_MODE, KEY_CHILD_LOCK, KEY_PRESET, KEY_SYSTEM_MODE, KEY_VALVE_DETECTION, KEY_WINDOW_DETECTION]
#
CFG_DATA = {
KEY_WINDOW_DETECTION: "ON",
KEY_VALVE_DETECTION: "ON",
KEY_SYSTEM_MODE: "heat",
KEY_PRESET: "manual"
}
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
self.add_callback(self.KEY_HEATING_SETPOINT, None, self.__valave_temp_rx__)
self.__tx_temperature__ = None
self.__rx_temperature__ = None
self.__tx_timestamp__ = 0
#
self.task = task.periodic(self.RETRY_CYCLE_TIME, self.__task__)
self.task.run()
def __state_logging__(self, inst, key, data):
if key in [self.KEY_HEATING_SETPOINT, self.KEY_CHILD_LOCK, self.KEY_WINDOW_DETECTION, self.KEY_VALVE_DETECTION]:
self.logger.info("State change of '%s' to '%s'", key, repr(data))
def send_command(self, key, data):
if key == self.KEY_HEATING_SETPOINT:
self.__tx_temperature__ = data
self.__tx_timestamp__ = time.time()
base.send_command(self, key, data)
def __valave_temp_rx__(self, inst, key, data):
if key == self.KEY_HEATING_SETPOINT:
self.__rx_temperature__ = data
def __task__(self, rt):
if self.__tx_temperature__ is not None and self.__tx_timestamp__ is not None: # Already send a setpoint
if self.__tx_temperature__ != self.__rx_temperature__: # Setpoint and valve feedback are unequal
if time.time() - self.__tx_timestamp__ < self.RETRY_TIMEOUT: # Timeout condition allows resend of setpoint
self.logger.warning("Setpoint not yet acknoledged by device. Sending setpoint again")
self.set_heating_setpoint(self.__tx_temperature__)
return
else:
self.__tx_timestamp__ = None # Disable resend logic, if setpoint and valve setpoint are equal
#
# RX
#
@property
def linkqulity(self):
return self.get(self.KEY_LINKQUALITY)
@property
def heating_setpoint(self):
return self.get(self.KEY_HEATING_SETPOINT)
@property
def temperature(self):
return self.get(self.KEY_TEMPERATURE)
#
# TX
#
def set_heating_setpoint(self, setpoint):
self.send_command(self.KEY_HEATING_SETPOINT, setpoint)
def set_heating_setpoint_mcb(self, device, key, data):
self.set_heating_setpoint(data)

View File

@ -1,118 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from devices.base import base, base_output
import logging
class hue_light(base_output):
""" Communication (MQTT)
hue_light {
| "state": ["ON" / "OFF" / "TOGGLE"]
| "linkquality": [0...255] lqi
| "brightness": [0...254]
| "color_mode": ["color_temp"]
| "color_temp": ["coolest", "cool", "neutral", "warm", "warmest", 250...454]
| }
+- get {
| "state": ""
| }
+- set {
"state": ["ON" / "OFF"]
"brightness": [0...256]
"color_temp": [250...454]
"transition": [0...] seconds
"brightness_move": [-X...0...X] X/s
"brightness_step": [-X...0...X]
"color_temp_move": [-X...0...X] X/s
"color_temp_step": [-X...0...X]
}
"""
KEY_LINKQUALITY = "linkquality"
KEY_OUTPUT_0 = "state"
KEY_BRIGHTNESS = "brightness"
KEY_COLOR_TEMP = "color_temp"
#
TX_TYPE = base.TX_DICT
TX_FILTER_DATA_KEYS = [KEY_OUTPUT_0, KEY_BRIGHTNESS, KEY_COLOR_TEMP]
STATE_KEYS = TX_FILTER_DATA_KEYS
#
RX_KEYS = [KEY_LINKQUALITY, KEY_OUTPUT_0, KEY_BRIGHTNESS, KEY_COLOR_TEMP]
RX_IGNORE_KEYS = ['update', 'color_mode']
RX_FILTER_DATA_KEYS = [KEY_OUTPUT_0, KEY_BRIGHTNESS, KEY_COLOR_TEMP]
def __state_logging__(self, inst, key, data):
if key in [self.KEY_OUTPUT_0, self.KEY_BRIGHTNESS, self.KEY_COLOR_TEMP]:
self.logger.info("State change of '%s' to '%s'", key, repr(data))
def __device_to_instance_filter__(self, key, data):
if key == self.KEY_BRIGHTNESS:
return int(round((data - 1) * 100 / 253, 0))
elif key == self.KEY_COLOR_TEMP:
return int(round((data - 250) * 10 / 204, 0))
return super().__device_to_instance_filter__(key, data)
def __instance_to_device_filter__(self, key, data):
if key == self.KEY_BRIGHTNESS:
return int(round(data * 253 / 100 + 1, 0))
elif key == self.KEY_COLOR_TEMP:
return int(round(data * 204 / 10 + 250, 0))
return super().__instance_to_device_filter__(key, data)
#
# RX
#
@property
def output_0(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_0, False)
@property
def linkquality(self):
"""rv: numeric value"""
return self.get(self.KEY_LINKQUALITY, 0)
@property
def brightness(self):
"""rv: numeric value [0%, ..., 100%]"""
return self.get(self.KEY_BRIGHTNESS, 0)
@property
def color_temp(self):
"""rv: numeric value [0, ..., 10]"""
return self.get(self.KEY_COLOR_TEMP, 0)
#
# TX
#
def request_data(self, device=None, key=None, data=None):
self.mqtt_client.send(self.topic + "/set", '{"hue_power_on_behavior": "recover"}')
def set_output_0(self, state):
"""state: [True, False]"""
self.send_command(self.KEY_OUTPUT_0, state)
def set_output_0_mcb(self, device, key, data):
self.set_output_0(data)
def toggle_output_0_mcb(self, device, key, data):
self.set_output_0(not self.output_0)
def set_brightness(self, brightness):
"""brightness: [0, ..., 100]"""
self.send_command(self.KEY_BRIGHTNESS, brightness)
def set_brightness_mcb(self, device, key, data):
self.set_brightness(data)
def set_color_temp(self, color_temp):
"""color_temp: [0, ..., 10]"""
self.send_command(self.KEY_COLOR_TEMP, color_temp)
def set_color_temp_mcb(self, device, key, data):
self.set_color_temp(data)
def __all_off__(self):
if self.output_0:
self.set_output_0(False)

View File

@ -1,262 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from devices.base import base, base_output
import logging
class powerplug(base_output):
""" Communication (MQTT)
my_powerplug
+- output
+- 1 [True, False] <- status
| +- set [True, False, "toggle"] <- command
+- 2 [True, False] <- status
| +- set [True, False, "toggle"] <- command
+- 3 [True, False] <- status
| +- set [True, False, "toggle"] <- command
+- 4 [True, False] <- status
| +- set [True, False, "toggle"] <- command
+- all
+- set [True, False, "toggle"] <- command
"""
KEY_OUTPUT_0 = "output/1"
KEY_OUTPUT_1 = "output/2"
KEY_OUTPUT_2 = "output/3"
KEY_OUTPUT_3 = "output/4"
KEY_OUTPUT_ALL = "output/all"
KEY_OUTPUT_LIST = [KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_OUTPUT_2, KEY_OUTPUT_3]
#
TX_TYPE = base.TX_VALUE
#
RX_KEYS = [KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_OUTPUT_2, KEY_OUTPUT_3]
def __state_logging__(self, inst, key, data):
if key in self.KEY_OUTPUT_LIST:
self.logger.info("State change of '%s' to '%s'", key, repr(data))
#
# RX
#
@property
def output_0(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_0)
@property
def output_1(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_1)
@property
def output_2(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_2)
@property
def output_3(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_3)
#
# TX
#
def set_output(self, key, state):
if key in self.KEY_OUTPUT_LIST:
self.send_command(key, state)
else:
logging.error("Unknown key to set the output!")
def set_output_0(self, state):
"""state: [True, False]"""
self.send_command(self.KEY_OUTPUT_0, state)
def set_output_0_mcb(self, device, key, data):
self.set_output_0(data)
def toggle_output_0_mcb(self, device, key, data):
self.set_output_0(not self.output_0)
def set_output_1(self, state):
"""state: [True, False]"""
self.send_command(self.KEY_OUTPUT_1, state)
def set_output_1_mcb(self, device, key, data):
self.set_output_1(data)
def toggle_output_1_mcb(self, device, key, data):
self.set_output_1(not self.output_1)
def set_output_2(self, state):
"""state: [True, False]"""
self.send_command(self.KEY_OUTPUT_2, state)
def set_output_2_mcb(self, device, key, data):
self.set_output_2(data)
def toggle_output_2_mcb(self, device, key, data):
self.set_output_2(not self.output_2)
def set_output_3(self, state):
"""state: [True, False]"""
self.send_command(self.KEY_OUTPUT_3, state)
def set_output_3_mcb(self, device, key, data):
self.set_output_3(data)
def toggle_output_3_mcb(self, device, key, data):
self.set_output_3(not self.output_3)
def set_output_all(self, state):
"""state: [True, False, 'toggle']"""
self.send_command(self.KEY_OUTPUT_ALL, state)
def set_output_all_mcb(self, device, key, data):
self.set_output_all(data)
def __all_off__(self):
self.set_output_all(False)
class remote(base):
""" Communication (MQTT)
remote (RAS5) <- command
+- CD [dc]
+- LINE1 [dc]
+- LINE2 [dc]
+- LINE3 [dc]
+- MUTE [dc]
+- POWER [dc]
+- VOLDOWN [dc]
+- VOLUP [dc]
+- PHONO [dc]
+- DOCK [dc]
remote (EUR642100) <- command
+- OPEN_CLOSE [dc]
+- VOLDOWN [dc]
+- VOLUP [dc]
+- ONE [dc]
+- TWO [dc]
+- THREE [dc]
+- FOUR [dc]
+- FIVE [dc]
+- SIX [dc]
+- SEVEN [dc]
+- EIGHT [dc]
+- NINE [dc]
+- ZERO [dc]
+- TEN [dc]
+- TEN_PLUS [dc]
+- PROGRAM [dc]
+- CLEAR [dc]
+- RECALL [dc]
+- TIME_MODE [dc]
+- A_B_REPEAT [dc]
+- REPEAT [dc]
+- RANDOM [dc]
+- AUTO_CUE [dc]
+- TAPE_LENGTH [dc]
+- SIDE_A_B [dc]
+- TIME_FADE [dc]
+- PEAK_SEARCH [dc]
+- SEARCH_BACK [dc]
+- SEARCH_FOR [dc]
+- TRACK_NEXT [dc]
+- TRACK_PREV [dc]
+- STOP [dc]
+- PAUSE [dc]
+- PLAY [dc]
"""
KEY_CD = "CD"
KEY_LINE1 = "LINE1"
KEY_LINE2 = "LINE2"
KEY_LINE3 = "LINE3"
KEY_PHONO = "PHONO"
KEY_MUTE = "MUTE"
KEY_POWER = "POWER"
KEY_VOLDOWN = "VOLDOWN"
KEY_VOLUP = "VOLUP"
#
TX_TOPIC = ''
TX_TYPE = base.TX_VALUE
#
RX_IGNORE_TOPICS = [KEY_CD, KEY_LINE1, KEY_LINE2, KEY_LINE3, KEY_PHONO, KEY_MUTE, KEY_POWER, KEY_VOLUP, KEY_VOLDOWN]
def __state_logging__(self, inst, key, data):
pass # This is just a TX device using self.set_*
def set_cd(self, device=None, key=None, data=None):
self.logger.info("Changing amplifier source to CD")
self.send_command(self.KEY_CD, None)
def set_line1(self, device=None, key=None, data=None):
self.logger.info("Changing amplifier source to LINE1")
self.send_command(self.KEY_LINE1, None)
def set_line2(self, device=None, key=None, data=None):
self.logger.info("Changing amplifier source to LINE2")
self.send_command(self.KEY_LINE2, None)
def set_line3(self, device=None, key=None, data=None):
self.logger.info("Changing amplifier source to LINE3")
self.send_command(self.KEY_LINE3, None)
def set_phono(self, device=None, key=None, data=None):
self.logger.info("Changing amplifier source to PHONO")
self.send_command(self.KEY_PHONO, None)
def set_mute(self, device=None, key=None, data=None):
self.logger.info("Muting / Unmuting amplifier")
self.send_command(self.KEY_MUTE, None)
def set_power(self, device=None, key=None, data=None):
self.logger.info("Power on/off amplifier")
self.send_command(self.KEY_POWER, None)
def set_volume_up(self, data=False):
"""data: [True, False]"""
self.logger.info("Increasing amplifier volume")
self.send_command(self.KEY_VOLUP, data)
def set_volume_down(self, data=False):
"""data: [True, False]"""
self.logger.info("Decreasing amplifier volume")
self.send_command(self.KEY_VOLDOWN, data)
def default_inc(self, device=None, key=None, data=None):
self.set_volume_up(True)
def default_dec(self, device=None, key=None, data=None):
self.set_volume_down(True)
def default_stop(self, device=None, key=None, data=None):
self.set_volume_up(False)
class audio_status(base):
""" Communication (MQTT)
audio_status
+- state [True, False] <- status
+- title [text] <- status
"""
KEY_STATE = "state"
KEY_TITLE = "title"
#
TX_TYPE = base.TX_VALUE
#
RX_KEYS = [KEY_STATE, KEY_TITLE]
def __state_logging__(self, inst, key, data):
if key in [self.KEY_STATE, self.KEY_TITLE]:
self.logger.info("State change of '%s' to '%s'", key, repr(data))
def set_state(self, num, data):
"""data: [True, False]"""
self.send_command(self.KEY_STATE + "/" + str(num), data)
def set_state_mcb(self, device, key, data):
self.set_state(data)

View File

@ -1,238 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from devices.base import base_output
from devices.base import base_rpc
import task
class shelly(base_output):
""" Communication (MQTT)
shelly
+- relay
| +- 0 ["on" / "off"] <- status
| | +- command ["on"/ "off"] <- command
| | +- energy [numeric] <- status
| +- 1 ["on" / "off"] <- status
| +- command ["on"/ "off"] <- command
| +- energy [numeric] <- status
+- input
| +- 0 [0 / 1] <- status
| +- 1 [0 / 1] <- status
+- input_event
| +- 0 <- status
| +- 1 <- status
+- logpush
| +- 0 [0 / 1] <- status
| +- 1 [0 / 1] <- status
+- temperature [numeric] °C <- status
+- temperature_f [numeric] F <- status
+- overtemperature [0 / 1] <- status
+- id <- status
+- model <- status
+- mac <- status
+- ip <- status
+- new_fw <- status
+- fw_ver <- status
"""
KEY_OUTPUT_0 = "relay/0"
KEY_OUTPUT_1 = "relay/1"
KEY_INPUT_0 = "input/0"
KEY_INPUT_1 = "input/1"
KEY_LONGPUSH_0 = "longpush/0"
KEY_LONGPUSH_1 = "longpush/1"
KEY_TEMPERATURE = "temperature"
KEY_OVERTEMPERATURE = "overtemperature"
KEY_ID = "id"
KEY_MODEL = "model"
KEY_MAC = "mac"
KEY_IP = "ip"
KEY_NEW_FIRMWARE = "new_fw"
KEY_FIRMWARE_VERSION = "fw_ver"
#
TX_TOPIC = "command"
TX_TYPE = base_output.TX_VALUE
TX_FILTER_DATA_KEYS = [KEY_OUTPUT_0, KEY_OUTPUT_1]
#
RX_KEYS = [KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_INPUT_0, KEY_INPUT_1, KEY_LONGPUSH_0, KEY_LONGPUSH_1, KEY_OVERTEMPERATURE, KEY_TEMPERATURE,
KEY_ID, KEY_MODEL, KEY_MAC, KEY_IP, KEY_NEW_FIRMWARE, KEY_FIRMWARE_VERSION]
RX_IGNORE_TOPICS = [KEY_OUTPUT_0 + '/' + "energy", KEY_OUTPUT_1 + '/' + "energy", 'input_event/0', 'input_event/1']
RX_IGNORE_KEYS = ['temperature_f']
RX_FILTER_DATA_KEYS = [KEY_INPUT_0, KEY_INPUT_1, KEY_LONGPUSH_0, KEY_LONGPUSH_1, KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_OVERTEMPERATURE]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
#
self.output_key_delayed = None
self.delayed_flash_task = task.delayed(0.75, self.flash_task)
self.delayed_off_task = task.delayed(0.75, self.off_task)
#
self.all_off_requested = False
def __state_logging__(self, inst, key, data):
if key in [self.KEY_OUTPUT_0, self.KEY_OUTPUT_1]:
self.logger.info("State change of '%s' to '%s'", key, repr(data))
elif key in [self.KEY_INPUT_0, self.KEY_INPUT_1, self.KEY_LONGPUSH_0, self.KEY_LONGPUSH_1]:
self.logger.info("Input action '%s' with '%s'", key, repr(data))
def flash_task(self, *args):
if self.flash_active:
self.send_command(self.output_key_delayed, not self.get(self.output_key_delayed))
self.output_key_delayed = None
if self.all_off_requested:
self.delayed_off_task.run()
def off_task(self, *args):
self.all_off()
@property
def flash_active(self):
return self.output_key_delayed is not None
#
# RX
#
@property
def output_0(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_0)
@property
def output_1(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_1)
@property
def input_0(self):
"""rv: [True, False]"""
return self.get(self.KEY_INPUT_0)
@property
def input_1(self):
"""rv: [True, False]"""
return self.get(self.KEY_INPUT_1)
@property
def longpush_0(self):
"""rv: [True, False]"""
return self.get(self.KEY_LONGPUSH_0)
@property
def longpush_1(self):
"""rv: [True, False]"""
return self.get(self.KEY_LONGPUSH_1)
@property
def temperature(self):
"""rv: numeric value"""
return self.get(self.KEY_TEMPERATURE)
#
# TX
#
def set_output_0(self, state):
"""state: [True, False]"""
self.send_command(self.KEY_OUTPUT_0, state)
def set_output_0_mcb(self, device, key, data):
self.set_output_0(data)
def toggle_output_0_mcb(self, device, key, data):
self.set_output_0(not self.output_0)
def set_output_1(self, state):
"""state: [True, False]"""
self.send_command(self.KEY_OUTPUT_1, state)
def set_output_1_mcb(self, device, key, data):
self.set_output_1(data)
def toggle_output_1_mcb(self, device, key, data):
self.set_output_1(not self.output_1)
def flash_0_mcb(self, device, key, data):
self.output_key_delayed = self.KEY_OUTPUT_0
self.toggle_output_0_mcb(device, key, data)
self.delayed_flash_task.run()
def flash_1_mcb(self, device, key, data):
self.output_key_delayed = self.KEY_OUTPUT_1
self.toggle_output_1_mcb(device, key, data)
self.delayed_flash_task.run()
def __all_off__(self):
if self.flash_active:
self.all_off_requested = True
else:
if self.output_0:
self.set_output_0(False)
if self.output_1:
self.set_output_1(False)
class shelly_rpc(base_rpc):
KEY_OUTPUT_0 = "switch:0"
KEY_OUTPUT_1 = "switch:1"
KEY_OUTPUT_2 = "switch:2"
KEY_INPUT_0 = "input:0"
KEY_INPUT_1 = "input:1"
KEY_INPUT_2 = "input:2"
KEY_LONGPUSH_0 = "input:0:long_push"
KEY_LONGPUSH_1 = "input:1:long_push"
KEY_LONGPUSH_2 = "input:2:long_push"
KEY_SINGLEPUSH_0 = "input:0:single_push"
KEY_SINGLEPUSH_1 = "input:1:single_push"
KEY_SINGLEPUSH_2 = "input:2:single_push"
KEY_DOUBLEPUSH_0 = "input:0:double_push"
KEY_DOUBLEPUSH_1 = "input:1:double_push"
KEY_DOUBLEPUSH_2 = "input:2:double_push"
KEY_TRIPLEPUSH_0 = "input:0:triple_push"
KEY_TRIPLEPUSH_1 = "input:1:triple_push"
KEY_TRIPLEPUSH_2 = "input:2:triple_push"
RX_KEYS = [KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_OUTPUT_2, KEY_INPUT_0, KEY_INPUT_1, KEY_INPUT_2,
KEY_LONGPUSH_0, KEY_LONGPUSH_1, KEY_LONGPUSH_2, KEY_SINGLEPUSH_0, KEY_SINGLEPUSH_1, KEY_SINGLEPUSH_2,
KEY_DOUBLEPUSH_0, KEY_DOUBLEPUSH_1, KEY_DOUBLEPUSH_2, KEY_TRIPLEPUSH_0, KEY_TRIPLEPUSH_1, KEY_TRIPLEPUSH_2]
def __state_logging__(self, inst, key, data):
if key in [self.KEY_OUTPUT_0, self.KEY_OUTPUT_1, self.KEY_OUTPUT_2]:
self.logger.info("State change of '%s' to '%s'", key, repr(data))
elif key in [self.KEY_INPUT_0, self.KEY_INPUT_1, self.KEY_INPUT_2]:
self.logger.info("Input action '%s' with '%s'", key, repr(data))
elif key in [self.KEY_LONGPUSH_0, self.KEY_LONGPUSH_1, self.KEY_LONGPUSH_2,
self.KEY_SINGLEPUSH_0, self.KEY_SINGLEPUSH_1, self.KEY_SINGLEPUSH_2,
self.KEY_DOUBLEPUSH_0, self.KEY_DOUBLEPUSH_1, self.KEY_DOUBLEPUSH_2,
self.KEY_TRIPLEPUSH_0, self.KEY_TRIPLEPUSH_1, self.KEY_TRIPLEPUSH_2]:
self.logger.info("Input action '%s'", key)
def set_output_0(self, state):
"""state: [True, False]"""
self.rpc_switch_set(self.KEY_OUTPUT_0, state)
def set_output_0_mcb(self, device, key, data):
self.set_output_0(data)
def toggle_output_0_mcb(self, device, key, data):
self.set_output_0(not self.get(self.KEY_OUTPUT_0))
def set_output_1(self, state):
"""state: [True, False]"""
self.rpc_switch_set(self.KEY_OUTPUT_1, state)
def set_output_1_mcb(self, device, key, data):
self.set_output_1(data)
def toggle_output_1_mcb(self, device, key, data):
print(self.get(self.KEY_OUTPUT_1))
self.set_output_1(not self.get(self.KEY_OUTPUT_1))
def set_output_2(self, state):
"""state: [True, False]"""
self.rpc_switch_set(self.KEY_OUTPUT_2, state)
def set_output_2_mcb(self, device, key, data):
self.set_output_2(data)
def toggle_output_2_mcb(self, device, key, data):
self.set_output_2(not self.get(self.KEY_OUTPUT_2))

View File

@ -1,148 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from devices.base import base, base_output
import logging
class silvercrest_button(base):
""" Communication (MQTT)
tradfri_button {
"action": ["pressed"]
"battery": [0...100] %
"battery_low": [true | false]
"tamper": [true | false]
"linkquality": [0...255] lqi
"update": []
}
"""
ACTION_PRESSED = "pressed"
#
KEY_LINKQUALITY = "linkquality"
KEY_BATTERY = "battery"
KEY_BATTERY_LOW = "battery_low"
KEY_TAMPER = "tamper"
KEY_ACTION = "action"
#
RX_KEYS = [KEY_LINKQUALITY, KEY_BATTERY, KEY_ACTION, KEY_BATTERY_LOW, KEY_TAMPER]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
def __state_logging__(self, inst, key, data):
if key == self.KEY_ACTION:
self.logger.info("Input '%s' with '%s'", key, repr(data))
self[self.KEY_ACTION] = None
elif key in [self.KEY_BATTERY_LOW, self.KEY_TAMPER]:
self.logger.info("Input '%s' with '%s'", key, repr(data))
#
# RX
#
@property
def action(self):
"""rv: action_txt"""
return self.get(self.KEY_ACTION)
class silvercrest_powerplug(base_output):
""" Communication (MQTT)
silvercrest_powerplug {
| "state": ["ON" / "OFF"]
| "linkquality": [0...255] lqi
| }
+- get {
| "state": ""
| }
+- set {
"state": ["ON" / "OFF"]
}
"""
KEY_LINKQUALITY = "linkquality"
KEY_OUTPUT_0 = "state"
#
TX_TYPE = base.TX_DICT
TX_FILTER_DATA_KEYS = [KEY_OUTPUT_0]
#
RX_KEYS = [KEY_LINKQUALITY, KEY_OUTPUT_0]
RX_FILTER_DATA_KEYS = [KEY_OUTPUT_0]
def __state_logging__(self, inst, key, data):
if key in [self.KEY_OUTPUT_0]:
self.logger.info("State change of '%s' to '%s'", key, repr(data))
#
# RX
#
@property
def output_0(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_0)
@property
def linkquality(self):
"""rv: numeric value"""
return self.get(self.KEY_LINKQUALITY)
#
# TX
#
def set_output_0(self, state):
"""state: [True, False]"""
self.send_command(self.KEY_OUTPUT_0, state)
def set_output_0_mcb(self, device, key, data):
self.set_output_0(data)
def toggle_output_0_mcb(self, device, key, data):
self.set_output_0(not self.output_0)
def __all_off__(self):
if self.output_0:
self.set_output_0(False)
class silvercrest_motion_sensor(base):
""" Communication (MQTT)
silvercrest_motion_sensor {
battery: [0...100] %
battery_low: [True, False]
linkquality: [0...255] lqi
occupancy: [True, False]
tamper: [True, False]
voltage: [0...] mV
}
"""
KEY_BATTERY = "battery"
KEY_BATTERY_LOW = "battery_low"
KEY_LINKQUALITY = "linkquality"
KEY_OCCUPANCY = "occupancy"
KEY_UNMOUNTED = "tamper"
KEY_VOLTAGE = "voltage"
#
TX_TYPE = base.TX_DICT
#
RX_KEYS = [KEY_BATTERY, KEY_BATTERY_LOW, KEY_LINKQUALITY, KEY_OCCUPANCY, KEY_UNMOUNTED, KEY_VOLTAGE]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
def __state_logging__(self, inst, key, data):
if key in [self.KEY_OCCUPANCY, self.KEY_UNMOUNTED]:
self.logger.info("State change of '%s' to '%s'", key, repr(data))
#
# RX
#
@property
def linkquality(self):
"""rv: numeric value"""
return self.get(self.KEY_LINKQUALITY)
@property
def battery(self):
"""rv: numeric value"""
return self.get(self.KEY_BATTERY)

View File

@ -1,193 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from devices.base import base, base_output
import logging
class tradfri_light(base_output):
""" Communication (MQTT)
tradfri_light {
| "state": ["ON" / "OFF" / "TOGGLE"]
| "linkquality": [0...255] lqi
| "brightness": [0...254]
| "color_mode": ["color_temp"]
| "color_temp": ["coolest", "cool", "neutral", "warm", "warmest", 250...454]
| "color_temp_startup": ["coolest", "cool", "neutral", "warm", "warmest", "previous", 250...454]
| "update": []
| }
+- get {
| "state": ""
| }
+- set {
"state": ["ON" / "OFF"]
"brightness": [0...256]
"color_temp": [250...454]
"transition": [0...] seconds
"brightness_move": [-X...0...X] X/s
"brightness_step": [-X...0...X]
"color_temp_move": [-X...0...X] X/s
"color_temp_step": [-X...0...X]
}
"""
KEY_LINKQUALITY = "linkquality"
KEY_OUTPUT_0 = "state"
KEY_BRIGHTNESS = "brightness"
KEY_COLOR_TEMP = "color_temp"
KEY_BRIGHTNESS_FADE = "brightness_move"
#
TX_TYPE = base.TX_DICT
TX_FILTER_DATA_KEYS = [KEY_OUTPUT_0, KEY_BRIGHTNESS, KEY_COLOR_TEMP, KEY_BRIGHTNESS_FADE]
#
RX_KEYS = [KEY_LINKQUALITY, KEY_OUTPUT_0, KEY_BRIGHTNESS, KEY_COLOR_TEMP]
RX_IGNORE_KEYS = ['update', 'color_mode', 'color_temp_startup']
RX_FILTER_DATA_KEYS = [KEY_OUTPUT_0, KEY_BRIGHTNESS, KEY_COLOR_TEMP]
def __state_logging__(self, inst, key, data):
if key in [self.KEY_OUTPUT_0, self.KEY_BRIGHTNESS, self.KEY_COLOR_TEMP, self.KEY_BRIGHTNESS_FADE]:
self.logger.info("State change of '%s' to '%s'", key, repr(data))
def __device_to_instance_filter__(self, key, data):
if key == self.KEY_BRIGHTNESS:
return int(round((data - 1) * 100 / 253, 0))
elif key == self.KEY_COLOR_TEMP:
return int(round((data - 250) * 10 / 204, 0))
return super().__device_to_instance_filter__(key, data)
def __instance_to_device_filter__(self, key, data):
if key == self.KEY_BRIGHTNESS:
return int(round(data * 253 / 100 + 1, 0))
elif key == self.KEY_COLOR_TEMP:
return int(round(data * 204 / 10 + 250, 0))
return super().__instance_to_device_filter__(key, data)
#
# RX
#
@property
def output_0(self):
"""rv: [True, False]"""
return self.get(self.KEY_OUTPUT_0, False)
@property
def linkquality(self):
"""rv: numeric value"""
return self.get(self.KEY_LINKQUALITY, 0)
@property
def brightness(self):
"""rv: numeric value [0%, ..., 100%]"""
return self.get(self.KEY_BRIGHTNESS, 0)
@property
def color_temp(self):
"""rv: numeric value [0, ..., 10]"""
return self.get(self.KEY_COLOR_TEMP, 0)
#
# TX
#
def request_data(self, device=None, key=None, data=None):
self.mqtt_client.send(self.topic + "/get", '{"%s": ""}' % self.KEY_OUTPUT_0)
def set_output_0(self, state):
"""state: [True, False]"""
self.send_command(self.KEY_OUTPUT_0, state)
def set_output_0_mcb(self, device, key, data):
self.set_output_0(data)
def toggle_output_0_mcb(self, device, key, data):
self.set_output_0(not self.output_0)
def set_brightness(self, brightness):
"""brightness: [0, ..., 100]"""
self.send_command(self.KEY_BRIGHTNESS, brightness)
def set_brightness_mcb(self, device, key, data):
self.set_brightness(data)
def default_inc(self, speed=40):
self.send_command(self.KEY_BRIGHTNESS_FADE, speed)
def default_dec(self, speed=-40):
self.default_inc(speed)
def default_stop(self):
self.default_inc(0)
def set_color_temp(self, color_temp):
"""color_temp: [0, ..., 10]"""
self.send_command(self.KEY_COLOR_TEMP, color_temp)
def set_color_temp_mcb(self, device, key, data):
self.set_color_temp(data)
def __all_off__(self):
if self.output_0:
self.set_output_0(False)
class tradfri_button(base):
""" Communication (MQTT)
tradfri_button {
"action": [
"arrow_left_click",
"arrow_left_hold",
"arrow_left_release",
"arrow_right_click",
"arrow_right_hold",
"arrow_right_release",
"brightness_down_click",
"brightness_down_hold",
"brightness_down_release",
"brightness_up_click",
"brightness_up_hold",
"brightness_up_release",
"toggle"
]
"action_duration": [0...] s
"battery": [0...100] %
"linkquality": [0...255] lqi
"update": []
}
"""
ACTION_TOGGLE = "toggle"
ACTION_BRIGHTNESS_UP = "brightness_up_click"
ACTION_BRIGHTNESS_DOWN = "brightness_down_click"
ACTION_RIGHT = "arrow_right_click"
ACTION_LEFT = "arrow_left_click"
ACTION_BRIGHTNESS_UP_LONG = "brightness_up_hold"
ACTION_BRIGHTNESS_UP_RELEASE = "brightness_up_release"
ACTION_BRIGHTNESS_DOWN_LONG = "brightness_down_hold"
ACTION_BRIGHTNESS_DOWN_RELEASE = "brightness_down_release"
ACTION_RIGHT_LONG = "arrow_right_hold"
ACTION_RIGHT_RELEASE = "arrow_right_release"
ACTION_LEFT_LONG = "arrow_left_hold"
ACTION_LEFT_RELEASE = "arrow_left_release"
#
KEY_LINKQUALITY = "linkquality"
KEY_BATTERY = "battery"
KEY_ACTION = "action"
KEY_ACTION_DURATION = "action_duration"
#
RX_KEYS = [KEY_LINKQUALITY, KEY_BATTERY, KEY_ACTION]
RX_IGNORE_KEYS = ['update', KEY_ACTION_DURATION]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
def __state_logging__(self, inst, key, data):
if key in [self.KEY_ACTION]:
self.logger.info("Input '%s' with '%s'", key, repr(data))
self[self.KEY_ACTION] = None
#
# RX
#
@property
def action(self):
"""rv: action_txt"""
return self.get(self.KEY_ACTION)

View File

@ -1,15 +1,12 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import config
import devices import devices
from function.garden import garden
from function.stairway import stairway from function.stairway import stairway
from function.ground_floor_west import ground_floor_west from function.ground_floor_west import ground_floor_west_floor, ground_floor_west_marion, ground_floor_west_dirk
from function.first_floor_west import first_floor_west from function.first_floor_west import first_floor_west_julian, first_floor_west_living, first_floor_west_bath
from function.first_floor_east import first_floor_east from function.first_floor_east import first_floor_east_floor, first_floor_east_kitchen, first_floor_east_dining, first_floor_east_sleep, first_floor_east_living
from function.rooms import room_collection import inspect
from function.videv import all_off, videv_pure_switch
import logging import logging
try: try:
@ -18,23 +15,31 @@ except ImportError:
ROOT_LOGGER_NAME = 'root' ROOT_LOGGER_NAME = 'root'
logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__) logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
# TODO: implement garland (incl. day events like sunset, sunrise, ...)
# TODO: implement warning message
class all_functions(room_collection):
def __init__(self, mqtt_client, pd, vd): class all_functions(object):
super().__init__(mqtt_client, pd, vd) def __init__(self, mqtt_client):
self.mqtt_client = mqtt_client
# #
# Rooms self.__devices__ = None
#
# garden
self.gar = garden(self.mqtt_client, pd, vd)
# stairway # stairway
self.stw = stairway(self.mqtt_client, pd, vd) self.stw_stairway = stairway(self.mqtt_client)
# ground floor west # ground floor west
self.gfw = ground_floor_west(self.mqtt_client, pd, vd) self.gfw_floor = ground_floor_west_floor(self.mqtt_client)
self.gfw_marion = ground_floor_west_marion(self.mqtt_client)
self.gfw_dirk = ground_floor_west_dirk(self.mqtt_client)
# first floor west # first floor west
self.ffw = first_floor_west(self.mqtt_client, pd, vd) self.ffw_julian = first_floor_west_julian(self.mqtt_client)
self.ffw_living = first_floor_west_living(self.mqtt_client)
self.ffw_bath = first_floor_west_bath(self.mqtt_client)
# first floor east # first floor east
self.ffe = first_floor_east(self.mqtt_client, pd, vd) self.ffe_floor = first_floor_east_floor(self.mqtt_client)
self.ffe_kitchen = first_floor_east_kitchen(self.mqtt_client)
self.ffe_dining = first_floor_east_dining(self.mqtt_client)
self.ffe_sleep = first_floor_east_sleep(self.mqtt_client)
self.ffe_living = first_floor_east_living(self.mqtt_client)
# #
# Interactions # Interactions
# #
@ -42,50 +47,82 @@ class all_functions(room_collection):
self.init_cross_room_interactions() self.init_cross_room_interactions()
# Off Buttons # Off Buttons
self.init_off_functionality() self.init_off_functionality()
# Summer / Winter mode
self.init_sumer_winter_mode() def init_off_functionality(self):
# Off Buttons
self.gui_button_all_off = devices.nodered_gui_button(self.mqtt_client, "gui/all/common/off/button")
self.gui_button_gfw_off = devices.nodered_gui_button(self.mqtt_client, "gui/gfw/common/off/button")
self.gui_button_ffw_off = devices.nodered_gui_button(self.mqtt_client, "gui/ffw/common/off/button")
self.gui_button_ffe_off = devices.nodered_gui_button(self.mqtt_client, "gui/ffe/common/off/button")
#
self.gui_button_all_off.add_callback(devices.nodered_gui_button.KEY_STATE, True, self.all_off)
self.gui_button_gfw_off.add_callback(devices.nodered_gui_button.KEY_STATE, True, self.gfw_off)
self.gui_button_ffw_off.add_callback(devices.nodered_gui_button.KEY_STATE, True, self.ffw_off)
self.gui_button_ffe_off.add_callback(devices.nodered_gui_button.KEY_STATE, True, self.ffe_off)
# Long push ffe_floor
self.ffe_floor.main_light_shelly.add_callback(devices.shelly.KEY_LONGPUSH_0, True, self.ffe_floor.all_off_feedback)
self.ffe_floor.main_light_shelly.add_callback(devices.shelly.KEY_LONGPUSH_0, True, self.ffe_off)
# Long push stairway
self.stw_stairway.main_light_shelly.add_callback(devices.shelly.KEY_LONGPUSH_0, True, self.stw_stairway.all_off_feedback)
self.stw_stairway.main_light_shelly.add_callback(devices.shelly.KEY_LONGPUSH_0, True, self.all_off)
# Long push input device
self.ffe_sleep.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_RIGHT_LONG, self.ffe_off)
def getmembers(self, prefix):
rv = []
for name, obj in inspect.getmembers(self):
if name.startswith(prefix) and obj.__module__.split('.')[0] == 'function' and len(obj.__module__.split('.')) == 2:
rv.append(obj)
return rv
def common_off(self, device=None, key=None, data=None):
logger.info("Switching \"common\" off.")
for common in self.getmembers('common'):
common.all_off()
def gfw_off(self, device=None, key=None, data=None):
logger.info("Switching \"ground floor west\" off.")
for gfw in self.getmembers('gfw'):
gfw.all_off()
def ffw_off(self, device=None, key=None, data=None):
logger.info("Switching \"first floor west\" off.")
for ffw in self.getmembers('ffw'):
ffw.all_off()
def ffe_off(self, device=None, key=None, data=None):
logger.info("Switching \"first floor east\" off.")
for ffe in self.getmembers('ffe'):
ffe.all_off()
def all_off(self, device=None, key=None, data=None):
self.common_off(device, key, data)
self.gfw_off(device, key, data)
self.ffw_off(device, key, data)
self.ffe_off(device, key, data)
def init_cross_room_interactions(self): def init_cross_room_interactions(self):
# shelly dirk input 1 # shelly dirk input 1
self.last_gfw_dirk_input_1 = None self.last_gfw_dirk_input_1 = None
self.gfw.dirk.main_light_shelly.add_callback(self.gfw.dirk.main_light_shelly.KEY_INPUT_1, None, self.gfw_dirk_input_1) self.gfw_dirk.main_light_shelly.add_callback(devices.shelly.KEY_INPUT_1, None, self.gfw_dirk_input_1)
# tradfri button ffe_sleep right click # tradfri button ffe_sleep right click
self.ffe.sleep.button_tradfri.add_callback(self.ffe.sleep.button_tradfri.KEY_ACTION, self.ffe_sleep.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION,
self.ffe.sleep.button_tradfri.ACTION_RIGHT, devices.tradfri_button.ACTION_RIGHT, self.ffe_floor.main_light_shelly.toggle_output_0_mcb)
self.ffe.floor.main_light_shelly.toggle_output_0_mcb)
def init_off_functionality(self):
# ALL OFF - Virtual device
self.videv_all_off = all_off(self.mqtt_client, config.TOPIC_ALL_OFF_VIDEV, self)
# ALL OFF - Long push stairway
self.stw.stairway.main_light_shelly.add_callback(self.stw.stairway.main_light_shelly.KEY_LONGPUSH_0,
True, self.stw.stairway.main_light_shelly.flash_0_mcb)
self.stw.stairway.main_light_shelly.add_callback(self.stw.stairway.main_light_shelly.KEY_LONGPUSH_0, True, self.all_off)
# FFE ALL OFF - Long push ffe_floor
self.ffe.floor.main_light_shelly.add_callback(self.ffe.floor.main_light_shelly.KEY_LONGPUSH_0,
True, self.ffe.floor.main_light_shelly.flash_0_mcb)
self.ffe.floor.main_light_shelly.add_callback(self.ffe.floor.main_light_shelly.KEY_LONGPUSH_0, True, self.ffe.all_off)
# FFE ALL OFF - Long push input device
self.ffe.sleep.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_RIGHT_LONG, self.ffe.all_off)
# FFW ALL OFF - Long push ffw_floor
self.ffw.floor.main_light_shelly.add_callback(self.ffw.floor.main_light_shelly.KEY_LONGPUSH_0,
True, self.ffw.floor.main_light_shelly.flash_0_mcb)
self.ffw.floor.main_light_shelly.add_callback(self.ffw.floor.main_light_shelly.KEY_LONGPUSH_0, True, self.ffw.all_off)
def init_sumer_winter_mode(self):
# ALL summer/winter mode
self.videv_summer_mode = videv_pure_switch(self.mqtt_client, config.TOPIC_ALL_SUMMER_WINTER_MODE)
self.videv_summer_mode.add_callback(self.videv_summer_mode.KEY_STATE, None, self.gfw.summer_mode)
self.videv_summer_mode.add_callback(self.videv_summer_mode.KEY_STATE, None, self.ffw.summer_mode)
self.videv_summer_mode.add_callback(self.videv_summer_mode.KEY_STATE, None, self.ffe.summer_mode)
def gfw_dirk_input_1(self, device, key, data): def gfw_dirk_input_1(self, device, key, data):
if self.last_gfw_dirk_input_1 is not None: if self.last_gfw_dirk_input_1 is not None:
if self.last_gfw_dirk_input_1 != data: if self.last_gfw_dirk_input_1 != data:
self.gfw.floor.main_light_shelly.toggle_output_0_mcb(device, key, data) self.gfw_floor.main_light_shelly.toggle_output_0_mcb(device, key, data)
self.last_gfw_dirk_input_1 = data self.last_gfw_dirk_input_1 = data
def devicelist(self):
if self.__devices__ is None:
self.__devices__ = []
for name, obj in inspect.getmembers(self):
if obj.__class__.__module__ == "devices":
self.__devices__.append(obj)
elif obj.__class__.__module__.split('.')[0] == 'function':
for devicename, device in inspect.getmembers(obj):
if device.__class__.__module__ == "devices":
self.__devices__.append(device)
return self.__devices__

View File

@ -1,31 +1,15 @@
from function.modules import heating_function
import os import os
import sqlite3 import sqlite3
db_file = os.path.join(os.path.dirname(__file__), '..', 'database.db') db_file = os.path.join(os.path.dirname(__file__), '..', 'database.db')
db_mapping_radiator = {
0: heating_function.KEY_AWAY_MODE, def get_gui_radiator_data(topic):
1: heating_function.KEY_SUMMER_MODE, return __storage__().get_gui_radiator_data(topic)
2: heating_function.KEY_USER_TEMPERATURE_SETPOINT,
3: heating_function.KEY_TEMPERATURE_SETPOINT
}
def get_radiator_data(topic): def set_gui_radiator_data(topic, away_mode, summer_mode, user_temperatur_setpoint):
db_data = __storage__().get_radiator_data(topic) return __storage__().store_gui_radiator_data(topic, away_mode, summer_mode, user_temperatur_setpoint)
rv = {}
for index in db_mapping_radiator:
rv[db_mapping_radiator[index]] = db_data[index]
return rv
def set_radiator_data(device, key, data):
if key in db_mapping_radiator.values():
db_data = []
for index in range(0, len(db_mapping_radiator)):
db_data.append(device.get(db_mapping_radiator[index]))
return __storage__().store_radiator_data(device.heating_valve.topic, db_data)
class __storage__(object): class __storage__(object):
@ -33,29 +17,30 @@ class __storage__(object):
self.conn = sqlite3.connect(db_file) self.conn = sqlite3.connect(db_file)
self.c = self.conn.cursor() self.c = self.conn.cursor()
with self.conn: with self.conn:
self.c.execute("""CREATE TABLE IF NOT EXISTS radiator ( self.c.execute("""CREATE TABLE IF NOT EXISTS gui_radiator (
topic text PRIMARY KEY, topic text PRIMARY KEY,
away_mode integer, away_mode integer,
summer_mode integer, summer_mode integer,
user_temperatur_setpoint real, user_temperatur_setpoint real
temperatur_setpoint real
)""") )""")
def store_radiator_data(self, topic, target_data): def store_gui_radiator_data(self, topic, away_mode, summer_mode, user_temperatur_setpoint):
data = [topic, away_mode, summer_mode, user_temperatur_setpoint]
try: try:
with self.conn: with self.conn:
self.c.execute( self.c.execute(
'INSERT INTO radiator VALUES (?, ?, ?, ?, ?)', [topic] + target_data) 'INSERT INTO gui_radiator VALUES (?, ?, ?, ?)', data)
except sqlite3.IntegrityError: except sqlite3.IntegrityError:
db_data = self.get_radiator_data(topic) data = [away_mode, summer_mode, user_temperatur_setpoint]
if db_data != target_data: db_data = self.get_gui_radiator_data(topic)
if db_data != data:
with self.conn: with self.conn:
self.c.execute( self.c.execute(
'UPDATE radiator SET away_mode = ?, summer_mode = ?, user_temperatur_setpoint = ?, temperatur_setpoint = ? WHERE topic = ?', target_data + [topic]) 'UPDATE gui_radiator SET away_mode = ?, summer_mode = ?, user_temperatur_setpoint = ? WHERE topic = ?', data + [topic])
def get_radiator_data(self, topic): def get_gui_radiator_data(self, topic):
""" returns a list [away_mode, summer_mode, user_temperatur_setpoint, temperatur_setpoint] or [None, None, None, None]""" """ returns a list [away_mode, summer_mode, user_temperatur_setpoint] or [None, None, None]"""
self.c.execute("SELECT * FROM radiator WHERE topic=?", (topic, )) self.c.execute("SELECT * FROM gui_radiator WHERE topic=?", (topic, ))
data = self.c.fetchone() data = self.c.fetchone()
if data is not None: if data is not None:
data = list(data) data = list(data)
@ -63,7 +48,7 @@ class __storage__(object):
data[2] = data[2] == 1 data[2] = data[2] == 1
return data[1:] return data[1:]
else: else:
return [None, None, None, None] return [None, None, None]
def __del__(self): def __del__(self):
self.conn.close() self.conn.close()

View File

@ -3,326 +3,152 @@
# #
import config import config
from devdi import topic as props import devices
from devices import group from function.modules import brightness_choose_n_action, circulation_pump, radiator_function
from function.db import get_radiator_data, set_radiator_data
from function.helpers import day_event
from function.modules import brightness_choose_n_action, timer_on_activation, heating_function, switched_light
from function.rooms import room, room_collection
from function.videv import videv_switching, videv_switch_brightness, videv_switching_timer, videv_switch_brightness_color_temp, videv_heating, videv_multistate
import logging import logging
from function.rooms import room_shelly, room_shelly_motion_sensor, room_shelly_tradfri_light
try: try:
from config import APP_NAME as ROOT_LOGGER_NAME from config import APP_NAME as ROOT_LOGGER_NAME
except ImportError: except ImportError:
ROOT_LOGGER_NAME = 'root' ROOT_LOGGER_NAME = 'root'
logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__) logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
loc = props.LOC_FFE
class first_floor_east_floor(room_shelly):
class first_floor_east(room_collection): def __init__(self, mqtt_client):
def __init__(self, mqtt_client, pd, vd):
super().__init__(mqtt_client, pd, vd)
self.dining = first_floor_east_dining(mqtt_client, pd, vd)
self.floor = first_floor_east_floor(mqtt_client, pd, vd)
self.kitchen = first_floor_east_kitchen(mqtt_client, pd, vd)
self.livingroom = first_floor_east_living(mqtt_client, pd, vd)
self.sleep = first_floor_east_sleep(mqtt_client, pd, vd)
class first_floor_east_floor(room):
def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_FLO
#
# Device initialisation
#
# http://shelly1l-3C6105E4E629 # http://shelly1l-3C6105E4E629
# main light super().__init__(mqtt_client, config.TOPIC_FFE_FLOOR_MAIN_LIGHT_SHELLY, config.TOPIC_FFE_FLOOR_MAIN_LIGHT_GUI)
self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
super().__init__(mqtt_client, pd, vd)
#
# Virtual Device Interface
#
# main light
self.main_light = videv_switching(
mqtt_client, config.TOPIC_FFE_FLOOR_MAIN_LIGHT_VIDEV,
self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0
)
class first_floor_east_kitchen(room): class first_floor_east_kitchen(room_shelly):
def __init__(self, mqtt_client, pd, vd): def __init__(self, mqtt_client):
roo = props.ROO_KIT
#
# Device initialisation
#
# http://shelly1l-8CAAB5616C01 # http://shelly1l-8CAAB5616C01
# main light super().__init__(mqtt_client, config.TOPIC_FFE_KITCHEN_MAIN_LIGHT_SHELLY, config.TOPIC_FFE_KITCHEN_MAIN_LIGHT_GUI)
self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
self.main_light_hue = pd.get(props.STG_ZFE, loc, roo, props.FUN_MAL)
# http://shelly1-e89f6d85a466
# circulation pump
self.circulation_pump_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_CIR)
# heating function
self.heating_valve = pd.get(props.STG_ZFE, loc, roo, props.FUN_HEA)
super().__init__(mqtt_client, pd, vd)
# #
# Functionality initialisation self.circulation_pump = circulation_pump(mqtt_client)
# self.circulation_pump.main_light_shelly.add_callback(devices.shelly.KEY_OUTPUT_0, None, self.flash_main_light)
# circulation pump
self.circulation_pump = timer_on_activation(self.circulation_pump_shelly, self.circulation_pump_shelly.KEY_OUTPUT_0, 10*60)
self.circulation_pump_shelly.add_callback(self.circulation_pump_shelly.KEY_OUTPUT_0, True, self.main_light_shelly.flash_0_mcb, True)
# Request hue data of lead light after power on
switched_light(self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0, self.main_light_hue)
# heating function
self.heating_function = heating_function(
self.heating_valve,
config.DEFAULT_TEMPERATURE,
**get_radiator_data(self.heating_valve.topic)
)
self.heating_function.add_callback(None, None, set_radiator_data, True)
# def all_off(self, device=None, key=None, data=None):
# Virtual Device Interface self.circulation_pump.all_off(device, key, data)
# return super().all_off(device, key, data)
# main light
self.main_light_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_FFE_KITCHEN_MAIN_LIGHT_VIDEV,
self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0,
self.main_light_hue, self.main_light_hue.KEY_BRIGHTNESS,
self.main_light_hue, self.main_light_hue.KEY_COLOR_TEMP
)
# circulation pump
self.circulation_pump_videv = videv_switching_timer(
mqtt_client, config.TOPIC_FFE_KITCHEN_CIRCULATION_PUMP_VIDEV,
self.circulation_pump_shelly, self.circulation_pump_shelly.KEY_OUTPUT_0,
self.circulation_pump, timer_on_activation.KEY_TIMER
)
# heating function
self.heating_function_videv = videv_heating(
mqtt_client, config.TOPIC_FFE_KITCHEN_HEATING_VALVE_VIDEV,
self.heating_function
)
class first_floor_east_dining(room): class first_floor_east_dining(room_shelly):
def __init__(self, mqtt_client, pd, vd): def __init__(self, mqtt_client):
roo = props.ROO_DIN
#
# Device initialisation
#
self.day_events = day_event((6, 0), (22, 0), 30, -30)
# http://shelly1l-84CCA8ADD055 # http://shelly1l-84CCA8ADD055
# main light super().__init__(mqtt_client, config.TOPIC_FFE_DININGROOM_MAIN_LIGHT_SHELLY, config.TOPIC_FFE_DININGROOM_MAIN_LIGHT_GUI)
self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL) self.floorlamp_powerplug = devices.silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_DININGROOM_FLOOR_LAMP_POWERPLUG)
# floor lamp
self.floorlamp_powerplug = pd.get(props.STG_ZFE, loc, roo, props.FUN_FLL)
# heating function
self.heating_valve = pd.get(props.STG_ZFE, loc, roo, props.FUN_HEA)
# garland
if config.CHRISTMAS: if config.CHRISTMAS:
self.garland_powerplug = pd.get(props.STG_ZFE, loc, roo, props.FUN_GAR) self.garland_powerplug = devices.silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_DININGROOM_GARLAND_POWERPLUG)
super().__init__(mqtt_client, pd, vd)
# #
# Functionality initialisation self.gui_floorlamp = devices.nodered_gui_switch(mqtt_client, config.TOPIC_FFE_DININGROOM_FLOOR_LAMP_GUI)
# #
self.day_events.add_callback(None, True, self.__day_events__, True) # Callback initialisation
# main light
self.main_light_shelly.add_callback(self.main_light_shelly.KEY_OUTPUT_0, None, self.floorlamp_powerplug.set_output_0_mcb, True)
# heating function
self.heating_function = heating_function(
self.heating_valve,
config.DEFAULT_TEMPERATURE,
**get_radiator_data(self.heating_valve.topic)
)
self.heating_function.add_callback(None, None, set_radiator_data, True)
# #
# Virtual Device Interface self.main_light_shelly.add_callback(devices.shelly.KEY_OUTPUT_0, None, self.floorlamp_powerplug.set_output_0_mcb, True)
# self.gui_floorlamp.add_callback(devices.nodered_gui_switch.KEY_STATE, None, self.floorlamp_powerplug.set_output_0_mcb)
# main light self.floorlamp_powerplug.add_callback(devices.silvercrest_powerplug.KEY_OUTPUT_0, None, self.gui_floorlamp.set_state_mcb)
self.main_light_videv = videv_switching(
mqtt_client, config.TOPIC_FFE_DININGROOM_MAIN_LIGHT_VIDEV, def all_off(self, device=None, key=None, data=None):
self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0 super().all_off(device, key, data)
) self.floorlamp_powerplug.set_output_0(False)
# floor lamp
self.floorlamp_videv = videv_switching(
mqtt_client, config.TOPIC_FFE_DININGROOM_FLOOR_LAMP_VIDEV,
self.floorlamp_powerplug, self.floorlamp_powerplug.KEY_OUTPUT_0
)
# heating function
self.heating_function_videv = videv_heating(
mqtt_client, config.TOPIC_FFE_DININGROOM_HEATING_VALVE_VIDEV,
self.heating_function
)
# garland
if config.CHRISTMAS: if config.CHRISTMAS:
self.garland_videv = videv_switching( self.garland_powerplug.set_output_0(False)
mqtt_client, config.TOPIC_FFE_DININGROOM_GARLAND_VIDEV,
self.garland_powerplug, self.garland_powerplug.KEY_OUTPUT_0
)
def __day_events__(self, device, key, data):
if key in (self.day_events.KEY_SUNSET, self.day_events.KEY_START_OF_DAY):
if config.CHRISTMAS:
self.garland_powerplug.set_output_0(True)
elif key in (self.day_events.KEY_START_OF_NIGHT, self.day_events.KEY_SUNRISE):
if config.CHRISTMAS:
self.garland_powerplug.set_output_0(False)
class first_floor_east_sleep(room): class first_floor_east_sleep(room_shelly_tradfri_light):
def __init__(self, mqtt_client, pd, vd): def __init__(self, mqtt_client):
roo = props.ROO_SLP
#
# Device initialisation
#
# http://shelly1l-E8DB84A254C7 # http://shelly1l-E8DB84A254C7
# main light super().__init__(mqtt_client, config.TOPIC_FFE_SLEEP_MAIN_LIGHT_SHELLY, config.TOPIC_FFE_SLEEP_MAIN_LIGHT_GUI, config.TOPIC_FFE_SLEEP_MAIN_LIGHT_ZIGBEE)
self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
self.main_light_tradfri = pd.get(props.STG_ZFE, loc, roo, props.FUN_MAL)
# bed light # bed light
self.bed_light_di_tradfri = pd.get(props.STG_ZFE, loc, roo, props.FUN_BLD) self.bed_light_di_tradfri = devices.tradfri_light(mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_DI_ZIGBEE)
self.bed_light_ma_powerplug = pd.get(props.STG_ZFE, loc, roo, props.FUN_BLM) self.gui_bed_light_di = devices.nodered_gui_light(mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_DI_GUI)
# heating function self.bed_light_ma_powerplug = devices.silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_MA_POWERPLUG)
self.heating_valve = pd.get(props.STG_ZFE, loc, roo, props.FUN_HEA) self.gui_bed_light_ma = devices.nodered_gui_switch(mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_MA_GUI)
# button
self.button_tradfri = pd.get(props.STG_ZFE, loc, roo, props.FUN_INP)
# wardrobe light
self.wardrobe_light = pd.get(props.STG_ZFE, loc, roo, props.FUN_WLI)
self.wardrobe_light.disable_all_off() # Always on - Off by light sensor
super().__init__(mqtt_client, pd, vd)
#
# Functionality initialisation
# #
self.button_tradfri = devices.tradfri_button(mqtt_client, config.TOPIC_FFE_SLEEP_INPUT_DEVICE)
# button / brightness function # button / brightness function
self.brightness_functions = brightness_choose_n_action(self.button_tradfri) self.brightness_functions = brightness_choose_n_action(mqtt_client, self.button_tradfri, config.TOPIC_FFE_SLEEP_DEVICE_CHOOSER_LED)
self.brightness_functions.add(self.main_light_tradfri, self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0) self.brightness_functions.add(self.main_light_tradfri, self.main_light_shelly, devices.shelly.KEY_OUTPUT_0)
self.brightness_functions.add(self.bed_light_di_tradfri, self.bed_light_di_tradfri, self.bed_light_di_tradfri.KEY_OUTPUT_0) self.brightness_functions.add(self.bed_light_di_tradfri, self.bed_light_di_tradfri, devices.tradfri_light.KEY_OUTPUT_0)
# button / main light # radiator valve
self.button_tradfri.add_callback(self.button_tradfri.KEY_ACTION, self.button_tradfri.ACTION_TOGGLE, self.radiator_function = radiator_function(mqtt_client, config.TOPIC_FFE_SLEEP_RADIATOR_VALVE_ZIGBEE,
config.TOPIC_FFE_SLEEP_RADIATOR_VALVE_GUI, config.DEFAULT_TEMPERATURE_FFE_SLEEP)
#
# Callback initialisation
#
# on/off with button
self.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_TOGGLE,
self.main_light_shelly.toggle_output_0_mcb) self.main_light_shelly.toggle_output_0_mcb)
# button / bed light self.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_LEFT,
self.button_tradfri.add_callback(self.button_tradfri.KEY_ACTION, self.button_tradfri.ACTION_LEFT,
self.bed_light_di_tradfri.toggle_output_0_mcb) self.bed_light_di_tradfri.toggle_output_0_mcb)
self.button_tradfri.add_callback(self.button_tradfri.KEY_ACTION, self.button_tradfri.ACTION_LEFT_LONG, self.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_LEFT_LONG,
self.bed_light_ma_powerplug.toggle_output_0_mcb) self.bed_light_ma_powerplug.toggle_output_0_mcb)
# heating function
self.heating_function = heating_function(
self.heating_valve,
config.DEFAULT_TEMPERATURE,
**get_radiator_data(self.heating_valve.topic)
)
self.heating_function.add_callback(None, None, set_radiator_data, True)
#
# Virtual Device Interface
#
# main light
self.main_light_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_FFE_SLEEP_MAIN_LIGHT_VIDEV,
self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0,
self.main_light_tradfri, self.main_light_tradfri.KEY_BRIGHTNESS,
self.main_light_tradfri, self.main_light_tradfri.KEY_COLOR_TEMP
)
# bed light # bed light
self.bed_light_di_videv = videv_switch_brightness( # switch
mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_DI_VIDEV, self.gui_bed_light_di.add_callback(devices.nodered_gui_light.KEY_STATE, None, self.bed_light_di_tradfri.set_output_0_mcb)
self.bed_light_di_tradfri, self.bed_light_di_tradfri.KEY_OUTPUT_0, self.bed_light_di_tradfri.add_callback(devices.tradfri_light.KEY_OUTPUT_0, None, self.gui_bed_light_di.set_state_mcb)
self.bed_light_di_tradfri, self.bed_light_di_tradfri.KEY_BRIGHTNESS, self.gui_bed_light_ma.add_callback(devices.nodered_gui_switch.KEY_STATE, None, self.bed_light_ma_powerplug.set_output_0_mcb)
) self.bed_light_ma_powerplug.add_callback(devices.silvercrest_powerplug.KEY_OUTPUT_0, None, self.gui_bed_light_ma.set_state_mcb)
self.bed_light_ma_videv = videv_switching( # brightness and color temperature
mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_MA_VIDEV, self.bed_light_di_tradfri.add_callback(devices.tradfri_light.KEY_OUTPUT_0, None, self.gui_bed_light_di.set_enable_mcb)
self.bed_light_ma_powerplug, self.bed_light_ma_powerplug.KEY_OUTPUT_0 self.gui_bed_light_di.add_callback(devices.nodered_gui_light.KEY_BRIGHTNESS, None, self.bed_light_di_tradfri.set_brightness_mcb)
) self.bed_light_di_tradfri.add_callback(devices.tradfri_light.KEY_OUTPUT_0, None, self.gui_bed_light_di.set_enable_mcb)
# heating function self.bed_light_di_tradfri.add_callback(devices.tradfri_light.KEY_BRIGHTNESS, None, self.gui_bed_light_di.set_brightness_mcb)
self.heating_function_videv = videv_heating(
mqtt_client, config.TOPIC_FFE_SLEEP_HEATING_VALVE_VIDEV, def all_off(self, device=None, key=None, data=None):
self.heating_function super().all_off(device, key, data)
) self.bed_light_di_tradfri.set_output_0(False)
# button
self.brightness_functions_device_videv = videv_multistate(
mqtt_client, config.TOPIC_FFE_SLEEP_ACTIVE_BRIGHTNESS_DEVICE_VIDEV,
brightness_choose_n_action.KEY_ACTIVE_DEVICE, self.brightness_functions, 2
)
self.wardrobe_light_videv = videv_switch_brightness(
mqtt_client, config.TOPIC_FFE_SLEEP_WARDROBE_LIGHT_VIDEV,
self.wardrobe_light, self.wardrobe_light.KEY_OUTPUT_0,
self.wardrobe_light, self.wardrobe_light.KEY_BRIGHTNESS,
)
class first_floor_east_living(room): class first_floor_east_living(room_shelly_tradfri_light):
def __init__(self, mqtt_client, pd, vd): def __init__(self, mqtt_client):
roo = props.ROO_LIV
#
# Device initialisation
#
# http://shelly1l-3C6105E3F910 # http://shelly1l-3C6105E3F910
# main light super().__init__(mqtt_client, config.TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_SHELLY,
self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL) config.TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_GUI, config.TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_ZIGBEE)
self.main_light_tradfri = pd.get(props.STG_ZFE, loc, roo, props.FUN_MAL) for i in range(1, 7):
# floor lamp setattr(self, 'floorlamp_tradfri_%d' % i, devices.tradfri_light(mqtt_client, config.TOPIC_FFE_LIVINGROOM_FLOOR_LAMP_ZIGBEE % i))
self.floorlamp_tradfri = pd.get(props.STG_ZFE, loc, roo, props.FUN_FLL) #
# heating function
self.heating_valve = pd.get(props.STG_ZFE, loc, roo, props.FUN_HEA)
# xmas tree
if config.CHRISTMAS: if config.CHRISTMAS:
self.powerplug_xmas_tree = pd.get(props.STG_ZFE, loc, roo, props.FUN_XTR) self.powerplug_xmas_tree = devices.silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_LIVINGROOM_XMAS_TREE_POWERPLUG)
self.powerplug_xmas_star = pd.get(props.STG_ZFE, loc, roo, props.FUN_XST) self.powerplug_xmas_star = devices.silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_LIVINGROOM_XMAS_STAR_POWERPLUG)
super().__init__(mqtt_client, pd, vd)
# #
# Functionality initialisation self.gui_floorlamp = devices.nodered_gui_light(mqtt_client, config.TOPIC_FFE_LIVINGROOM_FLOOR_LAMP_GUI)
# #
# floor lamp synchronisation with main_light
self.main_light_shelly.add_callback(self.main_light_shelly.KEY_OUTPUT_0, None, self.floorlamp_tradfri.set_output_0_mcb, True)
# heating function
self.heating_function = heating_function(
self.heating_valve,
config.DEFAULT_TEMPERATURE,
**get_radiator_data(self.heating_valve.topic)
)
self.heating_function.add_callback(None, None, set_radiator_data, True)
#
# Virtual Device Interface
#
# main light
self.main_light_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_VIDEV,
self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0,
self.main_light_tradfri, self.main_light_tradfri.KEY_BRIGHTNESS,
self.main_light_tradfri, self.main_light_tradfri.KEY_COLOR_TEMP
)
# floor lamp
self.floorlamp_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_FFE_LIVINGROOM_FLOOR_LAMP_VIDEV,
self.floorlamp_tradfri, self.floorlamp_tradfri.KEY_OUTPUT_0,
self.floorlamp_tradfri, self.floorlamp_tradfri.KEY_BRIGHTNESS,
self.floorlamp_tradfri, self.floorlamp_tradfri.KEY_COLOR_TEMP
)
# heating function
self.heating_function_videv = videv_heating(
mqtt_client, config.TOPIC_FFE_LIVINGROOM_HEATING_VALVE_VIDEV,
self.heating_function
)
# xmas tree
if config.CHRISTMAS: if config.CHRISTMAS:
self.xmas_tree_videv = videv_switching( self.gui_xmas_tree = devices.nodered_gui_switch(mqtt_client, config.TOPIC_FFE_LIVINGROOM_XMAS_TREE_GUI)
mqtt_client, config.TOPIC_FFE_LIVINGROOM_XMAS_TREE_VIDEV, #
self.powerplug_xmas_tree, self.powerplug_xmas_tree.KEY_OUTPUT_0 # Callback initialisation
) #
# floor lamp
for device in self.__floorlamp_devices__():
self.main_light_shelly.add_callback(devices.shelly.KEY_OUTPUT_0, None, device.set_output_0_mcb, True)
self.gui_floorlamp.add_callback(devices.nodered_gui_light.KEY_STATE, None, device.set_output_0_mcb)
self.gui_floorlamp.add_callback(devices.nodered_gui_light.KEY_BRIGHTNESS, None, device.set_brightness_mcb)
self.gui_floorlamp.add_callback(devices.nodered_gui_light.KEY_COLOR_TEMP, None, device.set_color_temp_mcb)
self.floorlamp_tradfri_1.add_callback(devices.tradfri_light.KEY_OUTPUT_0, None, self.gui_floorlamp.set_state_mcb)
self.floorlamp_tradfri_1.add_callback(devices.tradfri_light.KEY_OUTPUT_0, None, self.gui_floorlamp.set_enable_mcb)
self.floorlamp_tradfri_1.add_callback(devices.tradfri_light.KEY_BRIGHTNESS, None, self.gui_floorlamp.set_brightness_mcb)
self.floorlamp_tradfri_1.add_callback(devices.tradfri_light.KEY_COLOR_TEMP, None, self.gui_floorlamp.set_color_temp_mcb)
#
if config.CHRISTMAS:
self.powerplug_xmas_tree.add_callback(devices.silvercrest_powerplug.KEY_OUTPUT_0, None, self.gui_xmas_tree.set_state_mcb)
self.gui_xmas_tree.add_callback(devices.nodered_gui_switch.KEY_STATE, None, self.powerplug_xmas_tree.set_output_0_mcb)
#
self.powerplug_xmas_tree.add_callback(devices.silvercrest_powerplug.KEY_OUTPUT_0, None, self.powerplug_xmas_star.set_output_0_mcb)
def all_off(self, device=None, key=None, data=None):
super().all_off(device, key, data)
for floorlamp in self.__floorlamp_devices__():
floorlamp.set_output_0(False)
if config.CHRISTMAS:
self.powerplug_xmas_tree.set_output_0(False)
self.powerplug_xmas_star.set_output_0(False)
def __floorlamp_devices__(self):
rv = []
for i in range(1, 7):
rv.append(getattr(self, 'floorlamp_tradfri_%d' % i))
return rv

View File

@ -3,12 +3,9 @@
# #
import config import config
from devdi import topic as props
from function.db import get_radiator_data, set_radiator_data
from function.modules import heating_function
from function.rooms import room, room_collection
from function.videv import videv_switch_brightness, videv_switch_brightness_color_temp, videv_heating, videv_switching
import logging import logging
from function.modules import radiator_function
from function.rooms import room_shelly, room_shelly_tradfri_light
try: try:
@ -17,210 +14,21 @@ except ImportError:
ROOT_LOGGER_NAME = 'root' ROOT_LOGGER_NAME = 'root'
logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__) logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
loc = props.LOC_FFW
class first_floor_west_julian(room_shelly_tradfri_light):
# http://shelly1l-3C6105E43452
def __init__(self, mqtt_client):
super().__init__(mqtt_client, config.TOPIC_FFW_JULIAN_MAIN_LIGHT_SHELLY, config.TOPIC_FFW_JULIAN_MAIN_LIGHT_GUI, config.TOPIC_FFW_JULIAN_MAIN_LIGHT_ZIGBEE)
class first_floor_west(room_collection): class first_floor_west_living(room_shelly):
def __init__(self, mqtt_client, pd, vd): # http://shelly1l-84CCA8ACE6A1
super().__init__(mqtt_client, pd, vd) def __init__(self, mqtt_client):
self.floor = first_floor_west_floor(mqtt_client, pd, vd) super().__init__(mqtt_client, config.TOPIC_FFW_LIVINGROOM_MAIN_LIGHT_SHELLY, config.TOPIC_FFW_LIVINGROOM_MAIN_LIGHT_GUI)
self.bath = first_floor_west_bath(mqtt_client, pd, vd)
self.julian = first_floor_west_julian(mqtt_client, pd, vd)
self.livingroom = first_floor_west_living(mqtt_client, pd, vd)
self.sleep = first_floor_west_sleep(mqtt_client, pd, vd)
class first_floor_west_floor(room): class first_floor_west_bath(object):
def __init__(self, mqtt_client, pd, vd): def __init__(self, mqtt_client):
roo = props.ROO_FLO # radiator valve
# self.radiator_function = radiator_function(mqtt_client, config.TOPIC_FFW_BATH_RADIATOR_VALVE_ZIGBEE,
# Device initialisation config.TOPIC_FFW_BATH_RADIATOR_VALVE_GUI, config.DEFAULT_TEMPERATURE_FFW_BATH)
#
# http://shelly1-58BF25D848EA
# main light
self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
super().__init__(mqtt_client, pd, vd)
#
# Virtual Device Interface
#
# main light
self.main_light = videv_switching(
mqtt_client, config.TOPIC_FFW_FLOOR_MAIN_LIGHT_VIDEV,
self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0
)
class first_floor_west_julian(room):
def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_JUL
#
# Device initialisation
#
# http://shelly1l-3C6105E43452
# main light
self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
self.main_light_tradfri = pd.get(props.STG_ZFW, loc, roo, props.FUN_MAL)
# heating function
self.heating_valve = pd.get(props.STG_ZFW, loc, roo, props.FUN_HEA)
super().__init__(mqtt_client, pd, vd)
#
# Functionality initialisation
#
# heating function
self.heating_function = heating_function(
self.heating_valve,
config.DEFAULT_TEMPERATURE,
**get_radiator_data(self.heating_valve.topic)
)
self.heating_function.add_callback(None, None, set_radiator_data, True)
#
# Virtual Device Interface
#
# main light
self.main_light_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_FFW_JULIAN_MAIN_LIGHT_VIDEV,
self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0,
self.main_light_tradfri, self.main_light_tradfri.KEY_BRIGHTNESS,
self.main_light_tradfri, self.main_light_tradfri.KEY_COLOR_TEMP
)
# heating function
self.heating_function_videv = videv_heating(
mqtt_client, config.TOPIC_FFW_JULIAN_HEATING_VALVE_VIDEV,
self.heating_function
)
class first_floor_west_bath(room):
def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_BAT
#
# Device initialisation
#
# http://shelly1-58BF25D84219
# main light
self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
# heating function
self.heating_valve = pd.get(props.STG_ZFW, loc, roo, props.FUN_HEA)
super().__init__(mqtt_client, pd, vd)
#
# Functionality initialisation
#
# heating function
self.heating_function = heating_function(
self.heating_valve,
config.DEFAULT_TEMPERATURE,
**get_radiator_data(self.heating_valve.topic)
)
self.heating_function.add_callback(None, None, set_radiator_data, True)
#
# Virtual Device Interface
#
# main light
self.main_light = videv_switching(
mqtt_client, config.TOPIC_FFW_BATH_MAIN_LIGHT_VIDEV,
self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0
)
# heating function
self.heating_function_videv = videv_heating(
mqtt_client, config.TOPIC_FFW_BATH_HEATING_VALVE_VIDEV,
self.heating_function
)
class first_floor_west_living(room):
def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_LIV
#
# Device initialisation
#
# http://shelly1l-84CCA8ACE6A1
# main light
self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
self.main_light_tradfri = pd.get(props.STG_ZFW, loc, roo, props.FUN_MAL)
# heating function
self.heating_valve = pd.get(props.STG_ZFW, loc, roo, props.FUN_HEA)
super().__init__(mqtt_client, pd, vd)
#
# Functionality initialisation
#
# heating function
self.heating_function = heating_function(
self.heating_valve,
config.DEFAULT_TEMPERATURE,
**get_radiator_data(self.heating_valve.topic)
)
self.heating_function.add_callback(None, None, set_radiator_data, True)
#
# Virtual Device Interface
#
# main light
self.main_light_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_FFW_LIVINGROOM_MAIN_LIGHT_VIDEV,
self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0,
self.main_light_tradfri, self.main_light_tradfri.KEY_BRIGHTNESS,
self.main_light_tradfri, self.main_light_tradfri.KEY_COLOR_TEMP
)
# heating function
self.heating_function_videv = videv_heating(
mqtt_client, config.TOPIC_FFW_LIVINGROOM_HEATING_VALVE_VIDEV,
self.heating_function
)
class first_floor_west_sleep(room):
def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_SLP
#
# Device initialisation
#
# http://shelly1-3494546A51F2
# main light
self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
self.main_light_tradfri = pd.get(props.STG_ZFW, loc, roo, props.FUN_MAL)
# heating function
self.heating_valve = pd.get(props.STG_ZFW, loc, roo, props.FUN_HEA)
# window light
self.window_light = pd.get(props.STG_ZFW, loc, roo, props.FUN_WIL)
super().__init__(mqtt_client, pd, vd)
#
# Functionality initialisation
#
# heating function
self.heating_function = heating_function(
self.heating_valve,
config.DEFAULT_TEMPERATURE,
**get_radiator_data(self.heating_valve.topic)
)
self.heating_function.add_callback(None, None, set_radiator_data, True)
# main light
self.main_light_shelly.add_callback(self.main_light_shelly.KEY_OUTPUT_0, None, self.window_light.set_output_0_mcb, True)
#
# Virtual Device Interface
#
# main light
self.main_light_videv = videv_switch_brightness(
mqtt_client, config.TOPIC_FFW_SLEEP_MAIN_LIGHT_VIDEV,
self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0,
self.main_light_tradfri, self.main_light_tradfri.KEY_BRIGHTNESS
)
# heating function
self.heating_function_videv = videv_heating(
mqtt_client, config.TOPIC_FFW_SLEEP_HEATING_VALVE_VIDEV,
self.heating_function
)
# window lamp
self.windowlamp_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_FFW_SLEEP_WINDOW_LAMP_VIDEV,
self.window_light, self.window_light.KEY_OUTPUT_0,
self.window_light, self.window_light.KEY_BRIGHTNESS,
self.window_light, self.window_light.KEY_COLOR_TEMP
)

View File

@ -1,70 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import config
import devdi.topic as props
from function.helpers import day_event
from function.rooms import room, room_collection
from function.videv import videv_switching, videv_pure_switch
import logging
try:
from config import APP_NAME as ROOT_LOGGER_NAME
except ImportError:
ROOT_LOGGER_NAME = 'root'
logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
loc = props.LOC_GAR
class garden(room_collection):
def __init__(self, mqtt_client, pd, vd):
super().__init__(mqtt_client, pd, vd)
self.garden = garden_garden(mqtt_client, pd, vd)
class garden_garden(room):
def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_GAR
#
# Device initialisation
#
self.day_events = day_event((6, 0), (22, 0), 30, -30)
# garden powerplugs
self.garland_powerplug = pd.get(props.STG_ZGW, loc, roo, props.FUN_GAR)
# repeater
self.repeater = pd.get(props.STG_ZGW, loc, roo, props.FUN_REP)
super().__init__(mqtt_client, pd, vd)
#
# Functionality initialisation
#
self.day_events.add_callback(None, True, self.__day_events__, True)
#
# Virtual Device Interface
#
# mode
self.mode_videv = videv_pure_switch(
mqtt_client, config.TOPIC_GAR_GARDEN_MODE_VIDEV
)
# garland
self.garland_videv = videv_switching(
mqtt_client, config.TOPIC_GAR_GARDEN_GARLAND_VIDEV,
self.garland_powerplug, self.garland_powerplug.KEY_OUTPUT_0
)
# repeater
self.repeater_videv = videv_switching(
mqtt_client, config.TOPIC_GAR_GARDEN_REPEATER_VIDEV,
self.repeater, self.repeater.KEY_OUTPUT_0
)
def __day_events__(self, device, key, data):
if self.mode_videv.get(self.mode_videv.KEY_STATE):
if key in (self.day_events.KEY_SUNSET, self.day_events.KEY_START_OF_DAY):
self.garland_powerplug.set_output_0(True)
elif key in (self.day_events.KEY_START_OF_NIGHT, self.day_events.KEY_SUNRISE):
self.garland_powerplug.set_output_0(False)

View File

@ -3,13 +3,10 @@
# #
import config import config
from devdi import topic as props import devices
from devices import group from function.modules import brightness_choose_n_action, radiator_function
from function.db import get_radiator_data, set_radiator_data
from function.modules import brightness_choose_n_action, heating_function, switched_light
from function.rooms import room, room_collection
from function.videv import videv_switching, videv_switch_brightness_color_temp, videv_heating, videv_multistate, videv_audio_player
import logging import logging
from function.rooms import room_shelly, room_shelly_tradfri_light, room_shelly_silvercrest_light
import task import task
try: try:
@ -18,247 +15,150 @@ except ImportError:
ROOT_LOGGER_NAME = 'root' ROOT_LOGGER_NAME = 'root'
logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__) logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
loc = props.LOC_GFW
class ground_floor_west_floor(room_shelly_silvercrest_light):
class ground_floor_west(room_collection): # http://shelly1l-84CCA8AD1148
def __init__(self, mqtt_client, pd, vd): def __init__(self, mqtt_client):
super().__init__(mqtt_client, pd, vd) super().__init__(mqtt_client, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_SHELLY, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_GUI, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_1_ZIGBEE)
self.dirk = ground_floor_west_dirk(mqtt_client, pd, vd)
self.floor = ground_floor_west_floor(mqtt_client, pd, vd)
self.marion = ground_floor_west_marion(mqtt_client, pd, vd)
class ground_floor_west_floor(room):
def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_FLO
# #
# Device initialisation # Callback initialisation
# #
# http://shelly1l-84CCA8AD1148 self.main_light_tradfri_2 = devices.tradfri_light(mqtt_client, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_2_ZIGBEE)
self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL) self.main_light_tradfri.add_callback(devices.tradfri_light.KEY_BRIGHTNESS, None, self.main_light_tradfri_2.set_brightness_mcb)
self.main_light_tradfri = pd.get(props.STG_ZGW, loc, roo, props.FUN_MAL) self.main_light_tradfri.add_callback(devices.tradfri_light.KEY_COLOR_TEMP, None, self.main_light_tradfri_2.set_color_temp_mcb)
super().__init__(mqtt_client, pd, vd)
# def send_init_message_main_light(self):
# Functionality initialisation super().send_init_message_main_light()
# self.main_light_tradfri_2.mqtt_client.send(self.main_light_tradfri_2.topic + "/get", '{"state": ""}')
# Request silvercrest data of lead light after power on
switched_light(self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0, self.main_light_tradfri)
#
# Virtual Device Interface
#
self.main_light_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_VIDEV,
self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0,
self.main_light_tradfri, self.main_light_tradfri.KEY_BRIGHTNESS,
self.main_light_tradfri, self.main_light_tradfri.KEY_COLOR_TEMP
)
class ground_floor_west_marion(room): class ground_floor_west_marion(room_shelly):
def __init__(self, mqtt_client, pd, vd): # http://shelly1l-E8DB84A1E067
roo = props.ROO_MAR def __init__(self, mqtt_client):
# super().__init__(mqtt_client, config.TOPIC_GFW_MARION_MAIN_LIGHT_SHELLY, config.TOPIC_GFW_MARION_MAIN_LIGHT_GUI)
# Device initialisation # radiator valve
# self.radiator_function = radiator_function(mqtt_client, config.TOPIC_GFW_MARION_RADIATOR_VALVE_ZIGBEE,
# http://shelly1l-E8DB84A1E067 config.TOPIC_GFW_MARION_RADIATOR_VALVE_GUI, config.DEFAULT_TEMPERATURE_GFW_MARION)
# main light
self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
# heating function
self.heating_valve = pd.get(props.STG_ZGW, loc, roo, props.FUN_HEA)
# window light
self.window_light = pd.get(props.STG_ZGW, loc, roo, props.FUN_WIL)
super().__init__(mqtt_client, pd, vd)
#
# Functionality initialisation
#
# heating function
self.heating_function = heating_function(
self.heating_valve,
config.DEFAULT_TEMPERATURE,
**get_radiator_data(self.heating_valve.topic)
)
self.heating_function.add_callback(None, None, set_radiator_data, True)
# main light
self.main_light_shelly.add_callback(self.main_light_shelly.KEY_OUTPUT_0, None, self.window_light.set_output_0_mcb, True)
#
# Virtual Device Interface
#
self.main_light_videv = videv_switching(
mqtt_client, config.TOPIC_GFW_MARION_MAIN_LIGHT_VIDEV,
self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0
)
self.heating_function_videv = videv_heating(
mqtt_client, config.TOPIC_GFW_MARION_HEATING_VALVE_VIDEV,
self.heating_function
)
# window lamp
self.windowlamp_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_GFW_MARION_WINDOW_LAMP_VIDEV,
self.window_light, self.window_light.KEY_OUTPUT_0,
self.window_light, self.window_light.KEY_BRIGHTNESS,
self.window_light, self.window_light.KEY_COLOR_TEMP
)
class ground_floor_west_dirk(room): class ground_floor_west_dirk(room_shelly_tradfri_light):
STATE_ACTIVE_DEVICE_MAIN_LIGHT = 0 STATE_ACTIVE_DEVICE_MAIN_LIGHT = 0
STATE_ACTIVE_DEVICE_DESK_LIGHT = 1 STATE_ACTIVE_DEVICE_DESK_LIGHT = 1
STATE_ACTIVE_DEVICE_AMPLIFIER = 2 STATE_ACTIVE_DEVICE_AMPLIFIER = 2
STATE_ACTIVE_DEVICE_MAX_VALUE = STATE_ACTIVE_DEVICE_AMPLIFIER STATE_ACTIVE_DEVICE_MAX_VALUE = STATE_ACTIVE_DEVICE_AMPLIFIER
# #
LED_ACTIVE_DEVICE_MAIN_LIGHT = devices.nodered_gui_leds.KEY_LED_0
LED_ACTIVE_DEVICE_DESK_LIGHT = devices.nodered_gui_leds.KEY_LED_1
LED_ACTIVE_DEVICE_AMPLIFIER = devices.nodered_gui_leds.KEY_LED_2
#
KEY_POWERPLUG_AMPLIFIER = devices.my_powerplug.KEY_OUTPUT_0
KEY_POWERPLUG_CD_PLAYER = devices.my_powerplug.KEY_OUTPUT_2
KEY_POWERPLUG_DESK_LIGHT = devices.my_powerplug.KEY_OUTPUT_1
KEY_POWERPLUG_PC_DOCK = devices.my_powerplug.KEY_OUTPUT_3
#
AUDIO_SOURCE_PC = 0 AUDIO_SOURCE_PC = 0
AUDIO_SOURCE_CD = 1 AUDIO_SOURCE_CD = 1
AUDIO_SOURCE_RASPI = 2 AUDIO_SOURCE_RASPI = 2
AUDIO_SOURCE_BT = 3
def __init__(self, mqtt_client, pd, vd): # http://shelly1l-3C6105E44F27
roo = props.ROO_DIR def __init__(self, mqtt_client):
super().__init__(mqtt_client, config.TOPIC_GFW_DIRK_MAIN_LIGHT_SHELLY, config.TOPIC_GFW_DIRK_MAIN_LIGHT_GUI, config.TOPIC_GFW_DIRK_MAIN_LIGHT_ZIGBEE)
# #
# Device initialisation self.powerplug_common = devices.my_powerplug(mqtt_client, config.TOPIC_GFW_DIRK_POWERPLUG)
self.desk_light_tradfri = devices.tradfri_light(mqtt_client, config.TOPIC_GFW_DIRK_DESK_LIGHT_ZIGBEE)
self.button_tradfri = devices.tradfri_button(mqtt_client, config.TOPIC_GFW_DIRK_INPUT_DEVICE)
# #
# http://shelly1l-3C6105E44F27 self.gui_desk_light = devices.nodered_gui_light(mqtt_client, config.TOPIC_GFW_DIRK_DESK_LIGHT_GUI)
# main light
self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
self.main_light_tradfri = pd.get(props.STG_ZGW, loc, roo, props.FUN_MAL)
# powerplug
self.powerplug_common = pd.get(props.STG_MYA, loc, roo, props.FUN_MPP)
self.KEY_POWERPLUG_AMPLIFIER = self.powerplug_common.KEY_OUTPUT_0
self.KEY_POWERPLUG_PHONO = self.powerplug_common.KEY_OUTPUT_1
self.KEY_POWERPLUG_CD_PLAYER = self.powerplug_common.KEY_OUTPUT_2
self.KEY_POWERPLUG_BT = self.powerplug_common.KEY_OUTPUT_3
# dock
self.dock_tradfri = pd.get(props.STG_ZGW, loc, roo, props.FUN_DCK)
# desk light
self.desk_light_tradfri = pd.get(props.STG_ZGW, loc, roo, props.FUN_DEL)
# button
self.button_tradfri = pd.get(props.STG_ZGW, loc, roo, props.FUN_INP)
# hifi
self.remote_amplifier = pd.get(props.STG_MYA, loc, roo, props.FUN_RCA)
self.spotify_state = pd.get(props.STG_MYA, loc, roo, props.FUN_ASS)
self.mpd_state = pd.get(props.STG_MYA, loc, roo, props.FUN_ASM)
self.bt_state = pd.get(props.STG_MYA, loc, roo, props.FUN_ASB)
# heating function
self.heating_valve = pd.get(props.STG_ZGW, loc, roo, props.FUN_HEA)
super().__init__(mqtt_client, pd, vd)
# #
# Functionality initialisation self.gui_amplifier = devices.nodered_gui_switch(mqtt_client, config.TOPIC_GFW_DIRK_AMPLIFIER_GUI)
self.gui_cd_player = devices.nodered_gui_switch(mqtt_client, config.TOPIC_GFW_DIRK_CD_PLAYER_GUI)
self.gui_pc_dock = devices.nodered_gui_switch(mqtt_client, config.TOPIC_GFW_DIRK_PC_DOCK_GUI)
# #
# Button - Brightness functionality self.remote_amplifier = devices.remote(mqtt_client, config.TOPIC_GFW_DIRK_AMPLIFIER_REMOTE)
self.brightness_functions = brightness_choose_n_action(self.button_tradfri) #
self.brightness_functions.add(self.main_light_tradfri, self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0) self.brightness_functions = brightness_choose_n_action(mqtt_client, self.button_tradfri, config.TOPIC_GFW_DIRK_DEVICE_CHOOSER_LED)
self.brightness_functions.add(self.desk_light_tradfri, self.desk_light_tradfri, self.desk_light_tradfri.KEY_OUTPUT_0) self.brightness_functions.add(self.main_light_tradfri, self.main_light_shelly, devices.shelly.KEY_OUTPUT_0)
self.brightness_functions.add(self.desk_light_tradfri, self.powerplug_common, self.KEY_POWERPLUG_DESK_LIGHT)
self.brightness_functions.add(self.remote_amplifier, self.powerplug_common, self.KEY_POWERPLUG_AMPLIFIER) self.brightness_functions.add(self.remote_amplifier, self.powerplug_common, self.KEY_POWERPLUG_AMPLIFIER)
# Button - Main light #
self.button_tradfri.add_callback(self.button_tradfri.KEY_ACTION, self.button_tradfri.ACTION_TOGGLE, self.spotify_state = devices.audio_status(mqtt_client, config.TOPIC_GFW_DIRK_SPOTIFY)
self.main_light_shelly.toggle_output_0_mcb) self.mpd_state = devices.audio_status(mqtt_client, config.TOPIC_GFW_DIRK_MPD)
# Button - Desk light # radiator valve
self.button_tradfri.add_callback(self.button_tradfri.KEY_ACTION, self.button_tradfri.ACTION_RIGHT, self.radiator_function = radiator_function(mqtt_client, config.TOPIC_GFW_DIRK_RADIATOR_VALVE_ZIGBEE,
self.desk_light_tradfri.toggle_output_0_mcb) config.TOPIC_GFW_DIRK_RADIATOR_VALVE_GUI, config.DEFAULT_TEMPERATURE_GFW_DIRK)
# Button - Amplifier #
self.button_tradfri.add_callback(self.button_tradfri.KEY_ACTION, self.button_tradfri.ACTION_LEFT_LONG, self.delayed_task = task.delayed(1.0, self.send_audio_source)
self.powerplug_common.toggle_output_0_mcb) #
# Button - CD player # Callback initialisation
self.button_tradfri.add_callback(self.button_tradfri.KEY_ACTION, self.button_tradfri.ACTION_RIGHT_LONG, #
self.powerplug_common.toggle_output_2_mcb)
# Button - PC dock
self.button_tradfri.add_callback(self.button_tradfri.KEY_ACTION, self.button_tradfri.ACTION_LEFT,
self.dock_tradfri.toggle_output_0_mcb)
# Mediaplayer - Amplifier auto on # main light
self.powerplug_common.add_callback(self.KEY_POWERPLUG_PHONO, None, self.powerplug_common.set_output_0_mcb, True) self.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_TOGGLE,
self.powerplug_common.add_callback(self.KEY_POWERPLUG_CD_PLAYER, None, self.powerplug_common.set_output_0_mcb, True) self.main_light_shelly.toggle_output_0_mcb)
self.powerplug_common.add_callback(self.KEY_POWERPLUG_BT, None, self.powerplug_common.set_output_0_mcb, True)
self.spotify_state.add_callback(self.spotify_state.KEY_STATE, None, self.powerplug_common.set_output_0_mcb, True) # desk light
self.mpd_state.add_callback(self.mpd_state.KEY_STATE, None, self.powerplug_common.set_output_0_mcb, True) # switch
self.bt_state.add_callback(self.bt_state.KEY_STATE, None, self.powerplug_common.set_output_0_mcb, True) self.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_RIGHT,
# Mediaplayer - Audio source selection self.powerplug_common.toggle_output_1_mcb)
self.gui_desk_light.add_callback(devices.nodered_gui_light.KEY_STATE, None, self.powerplug_common.set_output_1_mcb)
self.powerplug_common.add_callback(self.KEY_POWERPLUG_DESK_LIGHT, None, self.gui_desk_light.set_state_mcb)
# brightness and color temp
self.gui_desk_light.add_callback(devices.nodered_gui_light.KEY_BRIGHTNESS, None, self.desk_light_tradfri.set_brightness_mcb)
self.gui_desk_light.add_callback(devices.nodered_gui_light.KEY_COLOR_TEMP, None, self.desk_light_tradfri.set_color_temp_mcb)
self.powerplug_common.add_callback(self.KEY_POWERPLUG_DESK_LIGHT, None, self.gui_desk_light.set_enable_mcb)
self.desk_light_tradfri.add_callback(devices.tradfri_light.KEY_BRIGHTNESS, None, self.gui_desk_light.set_brightness_mcb)
self.desk_light_tradfri.add_callback(devices.tradfri_light.KEY_COLOR_TEMP, None, self.gui_desk_light.set_color_temp_mcb)
# amplifier
self.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_LEFT_LONG,
self.powerplug_common.toggle_output_0_mcb)
self.gui_amplifier.add_callback(devices.nodered_gui_switch.KEY_STATE, None, self.powerplug_common.set_output_0_mcb)
self.powerplug_common.add_callback(self.KEY_POWERPLUG_AMPLIFIER, None, self.gui_amplifier.set_state_mcb)
# amplifier auto on
self.powerplug_common.add_callback(self.KEY_POWERPLUG_CD_PLAYER, None, self.cd_amplifier_synchronisation, True)
self.spotify_state.add_callback(devices.status.KEY_STATE, None, self.raspi_amplifier_synchronisation, True)
self.mpd_state.add_callback(devices.status.KEY_STATE, None, self.raspi_amplifier_synchronisation, True)
# audio source
self.powerplug_common.add_callback(self.KEY_POWERPLUG_AMPLIFIER, True, self.audio_source_selector, True) self.powerplug_common.add_callback(self.KEY_POWERPLUG_AMPLIFIER, True, self.audio_source_selector, True)
self.powerplug_common.add_callback(self.KEY_POWERPLUG_CD_PLAYER, True, self.audio_source_selector, True) self.powerplug_common.add_callback(self.KEY_POWERPLUG_CD_PLAYER, True, self.audio_source_selector, True)
self.powerplug_common.add_callback(self.KEY_POWERPLUG_BT, True, self.audio_source_selector, True) self.spotify_state.add_callback(devices.status.KEY_STATE, True, self.audio_source_selector, True)
self.spotify_state.add_callback(self.spotify_state.KEY_STATE, True, self.audio_source_selector, True) self.mpd_state.add_callback(devices.status.KEY_STATE, True, self.audio_source_selector, True)
self.mpd_state.add_callback(self.mpd_state.KEY_STATE, True, self.audio_source_selector, True)
self.bt_state.add_callback(self.bt_state.KEY_STATE, True, self.audio_source_selector, True)
self.audio_source = self.AUDIO_SOURCE_PC self.audio_source = self.AUDIO_SOURCE_PC
# heating function # cd player
self.heating_function = heating_function( self.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_RIGHT_LONG,
self.heating_valve, self.powerplug_common.toggle_output_2_mcb)
config.DEFAULT_TEMPERATURE, self.gui_cd_player.add_callback(devices.nodered_gui_switch.KEY_STATE, None, self.powerplug_common.set_output_2_mcb)
**get_radiator_data(self.heating_valve.topic) self.powerplug_common.add_callback(self.KEY_POWERPLUG_CD_PLAYER, None, self.gui_cd_player.set_state_mcb)
)
self.heating_function.add_callback(None, None, set_radiator_data, True)
# # pc dock
# Virtual Device Interface self.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_LEFT,
# self.powerplug_common.toggle_output_3_mcb)
self.main_light_videv = videv_switch_brightness_color_temp( self.gui_pc_dock.add_callback(devices.nodered_gui_switch.KEY_STATE, None, self.powerplug_common.set_output_3_mcb)
mqtt_client, config.TOPIC_GFW_DIRK_MAIN_LIGHT_VIDEV, self.powerplug_common.add_callback(self.KEY_POWERPLUG_PC_DOCK, None, self.gui_pc_dock.set_state_mcb)
self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0,
self.main_light_tradfri, self.main_light_tradfri.KEY_BRIGHTNESS, def all_off(self, device=None, key=None, data=None):
self.main_light_tradfri, self.main_light_tradfri.KEY_COLOR_TEMP super().all_off(device, key, data)
) self.powerplug_common.set_output_all(False)
self.desk_light_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_GFW_DIRK_DESK_LIGHT_VIDEV, def cd_amplifier_synchronisation(self, device, key, data):
self.desk_light_tradfri, self.desk_light_tradfri.KEY_OUTPUT_0, logger.info("Syncing \"%s\" amplifier with cd player: %s", type(self).__name__, data)
self.desk_light_tradfri, self.desk_light_tradfri.KEY_BRIGHTNESS, self.powerplug_common.set_output(self.KEY_POWERPLUG_AMPLIFIER, data)
self.desk_light_tradfri, self.desk_light_tradfri.KEY_COLOR_TEMP
) def raspi_amplifier_synchronisation(self, device, key, data):
self.amplifier_videv = videv_switching( logger.info("Syncing \"%s\" amplifier with raspi player: %s", type(self).__name__, data)
mqtt_client, config.TOPIC_GFW_DIRK_AMPLIFIER_VIDEV, self.powerplug_common.set_output(self.KEY_POWERPLUG_AMPLIFIER, data)
self.powerplug_common, self.KEY_POWERPLUG_AMPLIFIER
)
self.phono_videv = videv_switching(
mqtt_client, config.TOPIC_GFW_DIRK_PHONO_VIDEV,
self.powerplug_common, self.KEY_POWERPLUG_PHONO
)
self.cd_player_videv = videv_switching(
mqtt_client, config.TOPIC_GFW_DIRK_CD_PLAYER_VIDEV,
self.powerplug_common, self.KEY_POWERPLUG_CD_PLAYER
)
self.bt_videv = videv_switching(
mqtt_client, config.TOPIC_GFW_DIRK_BT_VIDEV,
self.powerplug_common, self.KEY_POWERPLUG_BT
)
self.pc_dock_videv = videv_switching(
mqtt_client, config.TOPIC_GFW_DIRK_PC_DOCK_VIDEV,
self.dock_tradfri, self.dock_tradfri.KEY_OUTPUT_0
)
self.heating_function_videv = videv_heating(
mqtt_client, config.TOPIC_GFW_DIRK_HEATING_VALVE_VIDEV,
self.heating_function
)
self.brightness_functions_device_videv = videv_multistate(
mqtt_client, config.TOPIC_GFW_DIRK_ACTIVE_BRIGHTNESS_DEVICE_VIDEV,
brightness_choose_n_action.KEY_ACTIVE_DEVICE, self.brightness_functions, 3
)
self.audio_player_videv = videv_audio_player(
mqtt_client, config.TOPIC_GFW_DIRK_AUDIO_PLAYER_VIDEV,
self.spotify_state, self.mpd_state, self.bt_state
)
#
# Other stuff
#
self.delayed_task_remote = task.delayed(1.0, self.send_audio_source)
def audio_source_selector(self, device, key, data): def audio_source_selector(self, device, key, data):
if device == self.powerplug_common and key == self.KEY_POWERPLUG_CD_PLAYER: if device == self.powerplug_common and key == self.KEY_POWERPLUG_CD_PLAYER:
# switch on of cd player # switch on of cd player
self.audio_source = self.AUDIO_SOURCE_CD self.audio_source = self.AUDIO_SOURCE_CD
elif device == self.powerplug_common and key == self.KEY_POWERPLUG_BT: elif device in [self.spotify_state, self.mpd_state]:
# switch on of bluetooth
self.audio_source = self.AUDIO_SOURCE_BT
elif device in [self.spotify_state, self.mpd_state, self.bt_state]:
# switch on raspi-source # switch on raspi-source
self.audio_source = self.AUDIO_SOURCE_RASPI self.audio_source = self.AUDIO_SOURCE_RASPI
elif device == self.powerplug_common and key == self.KEY_POWERPLUG_AMPLIFIER: elif device == self.powerplug_common and key == self.KEY_POWERPLUG_AMPLIFIER:
# switch on of amplifier -> select source and reset stored source value # switch on of amplifier -> select source and reset stored source value
self.delayed_task_remote.run() self.delayed_task.run()
def send_audio_source(self): def send_audio_source(self):
if self.audio_source == self.AUDIO_SOURCE_PC: if self.audio_source == self.AUDIO_SOURCE_PC:
@ -267,9 +167,6 @@ class ground_floor_west_dirk(room):
elif self.audio_source == self.AUDIO_SOURCE_CD: elif self.audio_source == self.AUDIO_SOURCE_CD:
logger.info("Sending IR command to change audio source to cd") logger.info("Sending IR command to change audio source to cd")
self.remote_amplifier.set_cd() self.remote_amplifier.set_cd()
elif self.audio_source == self.AUDIO_SOURCE_BT:
logger.info("Sending IR command to change audio source to tuner")
self.remote_amplifier.set_line2()
elif self.audio_source == self.AUDIO_SOURCE_RASPI: elif self.audio_source == self.AUDIO_SOURCE_RASPI:
logger.info("Sending IR command to change audio source to raspi") logger.info("Sending IR command to change audio source to raspi")
self.remote_amplifier.set_line1() self.remote_amplifier.set_line1()

View File

@ -1,10 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from base import common_base
import config import config
import geo import geo
import task import inspect
import time import time
@ -12,104 +11,9 @@ def now():
return time.mktime(time.localtime()) return time.mktime(time.localtime())
def next_sunrise_time(time_offs_min=30): def sunrise_time(time_offs_min=30):
tm = now() return time.mktime(geo.sun.sunrise(config.GEO_POSITION)) + time_offs_min * 60
rv = time.mktime(geo.sun.sunrise(config.GEO_POSITION)) + time_offs_min * 60
if tm > rv:
rv = time.mktime(geo.sun.sunrise(config.GEO_POSITION, date=time.localtime(tm + 24 * 60 * 60))) + time_offs_min * 60
return rv
def next_sunset_time(time_offs_min=-30): def sunset_time(time_offs_min=-30):
tm = now() return time.mktime(geo.sun.sunset(config.GEO_POSITION)) + time_offs_min * 60
rv = time.mktime(geo.sun.sunset(config.GEO_POSITION)) + time_offs_min * 60
if tm > rv:
rv = time.mktime(geo.sun.sunset(config.GEO_POSITION, date=time.localtime(tm + 24 * 60 * 60))) + time_offs_min * 60
return rv
def next_user_time(hh, mm):
ts = time.localtime()
tm = time.mktime(ts)
ut_ts = list(ts)
ut_ts[3] = hh
ut_ts[4] = mm
ut = time.mktime(time.struct_time(list(ts[:3]) + [hh, mm, 0] + list(ts[6:])))
if ts[3] > hh or (ts[3] == hh and ts[4] >= mm):
ut += 24 * 60 * 60
#
return ut
class day_state(common_base):
"""
Class to subscribe day events as a callback (see add_callback)
:param time_start_of_day: Time of a day (tuple including hour and minute) for start of day or None for no start of day state.
:type time_start_of_day: tuple
:param time_start_of_night: Time of a day (tuple including hour and minute) for start of night or None for no end of day state.
:type time_start_of_night: tuple
:param time_offset_sunrise: time offset for sunrise in minutes (negative values lead to earlier sunrise state) or None for no sunrise state.
:type time_start_of_day: int
:param time_offset_sunset: time offset for sunset in minutes (negative values lead to earlier sunset state) or None for no sunrise state.
:type time_start_of_day: int
"""
KEY_SUNRISE = 'sunrise'
KEY_SUNSET = 'sunset'
KEY_START_OF_NIGHT = 'start_of_night'
KEY_START_OF_DAY = 'start_of_day'
#
STATES = (KEY_START_OF_DAY, KEY_SUNRISE, KEY_SUNSET, KEY_START_OF_NIGHT)
def __init__(self, time_start_of_day, time_start_of_night, time_offset_sunrise, time_offset_sunset):
self.__time_start_of_day__ = time_start_of_day
self.__time_start_of_night__ = time_start_of_night
self.__time_offset_sunrise__ = time_offset_sunrise
self.__time_offset_sunset__ = time_offset_sunset
super().__init__()
#
def get_state(self):
tm = {}
if self.__time_offset_sunrise__ is not None:
tm[next_sunrise_time(self.__time_offset_sunrise__)] = self.KEY_SUNRISE
if self.__time_start_of_day__ is not None:
tm[next_user_time(*(self.__time_start_of_day__))] = self.KEY_START_OF_DAY
if self.__time_offset_sunset__ is not None:
tm[next_sunset_time(self.__time_offset_sunset__)] = self.KEY_SUNSET
if self.__time_start_of_night__ is not None:
tm[next_user_time(*(self.__time_start_of_night__))] = self.KEY_START_OF_NIGHT
#
tms = list(tm.keys())
tms.sort()
return tm[tms[-1]]
class day_event(day_state):
"""
Class to subscribe day events as a callback (see add_callback)
:param time_start_of_day: Time of a day (tuple including hour and minute) for start of day or None for no start of day state.
:type time_start_of_day: tuple
:param time_start_of_night: Time of a day (tuple including hour and minute) for start of night or None for no end of day state.
:type time_start_of_night: tuple
:param time_offset_sunrise: time offset for sunrise in seconds (negative values lead to earlier sunrise state) or None for no sunrise state.
:type time_start_of_day: int
:param time_offset_sunset: time offset for sunset in seconds (negative values lead to earlier sunset state) or None for no sunrise state.
:type time_start_of_day: int
"""
def __init__(self, time_start_of_day=(5, 0), time_start_of_night=(22, 0), time_offset_sunrise=30, time_offset_sunset=-30):
super().__init__(time_start_of_day, time_start_of_night, time_offset_sunrise, time_offset_sunset)
#
current_day_state = self.get_state()
for key in self.STATES:
self[key] = current_day_state == key
#
cyclic = task.periodic(30, self.__cyclic__)
cyclic.run()
def __cyclic__(self, a):
current_day_state = self.get_state()
for key in self.STATES:
self.set(key, current_day_state == key)

View File

@ -1,23 +1,13 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
"""
Functional Modules
Targets:
* Device like structure to be compatible with videv
- KEY_* as part of the class for all parameters which needs to be accessed from videv
- Method *.set(key, data) to pass data from videv to Module
- Method .add_calback(key, data, callback, on_change_only=False) to register videv actualisation on changes
"""
from base import common_base
import config import config
import devices import devices
from function.helpers import day_state from function.db import get_gui_radiator_data, set_gui_radiator_data
from function.rooms import room_shelly
import logging import logging
import task import task
import time
try: try:
from config import APP_NAME as ROOT_LOGGER_NAME from config import APP_NAME as ROOT_LOGGER_NAME
@ -26,30 +16,26 @@ except ImportError:
logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__) logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
class switched_light(object): class brightness_choose_n_action(object):
def __init__(self, sw_device, sw_key, li_device): def __init__(self, mqtt_client, button_tradfri, topic_led):
sw_device.add_callback(sw_device.KEY_OUTPUT_0, True, li_device.request_data, True) self.gui_led_active_device = devices.nodered_gui_leds(mqtt_client, topic_led)
class brightness_choose_n_action(common_base):
KEY_ACTIVE_DEVICE = 'active_device'
#
DEFAULT_VALUES = {KEY_ACTIVE_DEVICE: None}
def __init__(self, button_tradfri):
super().__init__()
# brightness change # brightness change
button_tradfri.add_callback(button_tradfri.KEY_ACTION, button_tradfri.ACTION_BRIGHTNESS_DOWN_LONG, self.brightness_action) button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION,
button_tradfri.add_callback(button_tradfri.KEY_ACTION, button_tradfri.ACTION_BRIGHTNESS_UP_LONG, self.brightness_action) devices.tradfri_button.ACTION_BRIGHTNESS_DOWN_LONG, self.brightness_action)
button_tradfri.add_callback(button_tradfri.KEY_ACTION, button_tradfri.ACTION_BRIGHTNESS_DOWN_RELEASE, self.brightness_action) button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_BRIGHTNESS_UP_LONG, self.brightness_action)
button_tradfri.add_callback(button_tradfri.KEY_ACTION, button_tradfri.ACTION_BRIGHTNESS_UP_RELEASE, self.brightness_action) button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION,
devices.tradfri_button.ACTION_BRIGHTNESS_DOWN_RELEASE, self.brightness_action)
button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION,
devices.tradfri_button.ACTION_BRIGHTNESS_UP_RELEASE, self.brightness_action)
# device change # device change
button_tradfri.add_callback(button_tradfri.KEY_ACTION, button_tradfri.ACTION_BRIGHTNESS_UP, self.choose_next_device) button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_BRIGHTNESS_UP, self.choose_next_device)
button_tradfri.add_callback(button_tradfri.KEY_ACTION, button_tradfri.ACTION_BRIGHTNESS_DOWN, self.choose_prev_device) button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_BRIGHTNESS_DOWN, self.choose_prev_device)
# #
self.brightness_device_list = [] self.brightness_device_list = []
self.callback_device_list = [] self.callback_device_list = []
self.device_states = [] self.device_states = []
self.active_device_state = None
self.update_active_device_led()
def add(self, brightness_device, callback_device, callback_key): def add(self, brightness_device, callback_device, callback_key):
""" """
@ -60,6 +46,8 @@ class brightness_choose_n_action(common_base):
callback_device: A device for installing callback which are executed, when the device is switched on or off. It needs the following method: callback_device: A device for installing callback which are executed, when the device is switched on or off. It needs the following method:
* .add_callback(key, data or None, callback, on_changes_only) * .add_callback(key, data or None, callback, on_changes_only)
""" """
if len(self.brightness_device_list) >= len(devices.nodered_gui_leds.KEY_LED_LIST):
raise ValueError("Number of devices is limited by number of leds in devices.nodered_gui_leds.")
self.brightness_device_list.append(brightness_device) self.brightness_device_list.append(brightness_device)
self.callback_device_list.append((callback_device, callback_key)) self.callback_device_list.append((callback_device, callback_key))
self.device_states.append(False) self.device_states.append(False)
@ -69,35 +57,42 @@ class brightness_choose_n_action(common_base):
def device_state_action(self, device, key, data): def device_state_action(self, device, key, data):
self.device_states[self.callback_device_list.index((device, key))] = data self.device_states[self.callback_device_list.index((device, key))] = data
if data is True: if data is True:
self.set(self.KEY_ACTIVE_DEVICE, self.callback_device_list.index((device, key))) self.active_device_state = self.callback_device_list.index((device, key))
self.update_active_device_led()
else: else:
if self[self.KEY_ACTIVE_DEVICE] is not None: self.choose_next_device()
if self.callback_device_list[self[self.KEY_ACTIVE_DEVICE]][0] == device:
self.choose_next_device() def update_active_device_led(self):
for i in range(0, len(self.brightness_device_list)):
self.gui_led_active_device.set_led(devices.nodered_gui_leds.KEY_LED_LIST[i], self.active_device_state == i)
def choose_prev_device(self, device=None, key=None, data=None): def choose_prev_device(self, device=None, key=None, data=None):
if self[self.KEY_ACTIVE_DEVICE] is not None: if self.active_device_state is not None:
start_value = self[self.KEY_ACTIVE_DEVICE] start_value = self.active_device_state
for i in range(0, len(self.brightness_device_list)): for i in range(0, len(self.brightness_device_list)):
target_state = (start_value - i - 1) % (len(self.brightness_device_list)) target_state = (start_value - i - 1) % (len(self.brightness_device_list))
if self.device_states[target_state]: if self.device_states[target_state]:
self.set(self.KEY_ACTIVE_DEVICE, target_state) self.active_device_state = target_state
self.update_active_device_led()
return return
self.set(self.KEY_ACTIVE_DEVICE, None) self.active_device_state = None
self.update_active_device_led()
def choose_next_device(self, device=None, key=None, data=None): def choose_next_device(self, device=None, key=None, data=None):
if self[self.KEY_ACTIVE_DEVICE] is not None: if self.active_device_state is not None:
start_value = self[self.KEY_ACTIVE_DEVICE] start_value = self.active_device_state
for i in range(0, len(self.brightness_device_list)): for i in range(0, len(self.brightness_device_list)):
target_state = (start_value + i + 1) % (len(self.brightness_device_list)) target_state = (start_value + i + 1) % (len(self.brightness_device_list))
if self.device_states[target_state]: if self.device_states[target_state]:
self.set(self.KEY_ACTIVE_DEVICE, target_state) self.active_device_state = target_state
self.update_active_device_led()
return return
self.set(self.KEY_ACTIVE_DEVICE, None) self.active_device_state = None
self.update_active_device_led()
def brightness_action(self, device, key, data): def brightness_action(self, device, key, data):
if self[self.KEY_ACTIVE_DEVICE] is not None: if self.active_device_state is not None:
target = self.brightness_device_list[self[self.KEY_ACTIVE_DEVICE]] target = self.brightness_device_list[self.active_device_state]
if data == devices.tradfri_button.ACTION_BRIGHTNESS_UP_LONG: if data == devices.tradfri_button.ACTION_BRIGHTNESS_UP_LONG:
logger.info("Increasing \"%s\" - %s", type(self).__name__, target.topic) logger.info("Increasing \"%s\" - %s", type(self).__name__, target.topic)
target.default_inc() target.default_inc()
@ -108,239 +103,140 @@ class brightness_choose_n_action(common_base):
target.default_stop() target.default_stop()
class timer_on_activation(common_base): class circulation_pump(room_shelly):
KEY_TIMER = 'timer' def __init__(self, mqtt_client):
# super().__init__(mqtt_client, config.TOPIC_FFE_KITCHEN_CIRCULATION_PUMP_SHELLY, config.TOPIC_FFE_KITCHEN_CIRCULATION_PUMP_GUI)
DEFAULT_VALUES = {
KEY_TIMER: 0
}
def __init__(self, sw_device, sw_key, timer_reload_value):
super().__init__()
# #
self.timer_reload_value = timer_reload_value self.main_light_shelly.add_callback(devices.shelly.KEY_OUTPUT_0, None, self.circ_pump_actions, True)
# #
sw_device.add_callback(sw_key, None, self.circ_pump_actions, True) self.gui_main_light.set_timer('-')
# #
self.ct = task.periodic(6, self.cyclic_task) self.ct = task.periodic(6, self.cyclic_task)
self.pump_timer = None
#
self.ct.run() self.ct.run()
def circ_pump_actions(self, device, key, data): def circ_pump_actions(self, device, key, data):
if data is True: if data is True:
self.set(self.KEY_TIMER, self.timer_reload_value) self.pump_timer = 10
self.gui_main_light.set_timer(self.pump_timer)
else: else:
self.set(self.KEY_TIMER, 0) self.pump_timer = None
self.gui_main_light.set_timer('-')
def cyclic_task(self, rt): def cyclic_task(self, rt):
timer_value = self[self.KEY_TIMER] - self.ct.cycle_time if self.pump_timer is not None:
if timer_value <= 0: if self.pump_timer <= 0:
self.set(self.KEY_TIMER, 0) self.pump_timer = None
else: self.gui_main_light.set_timer('-')
self.set(self.KEY_TIMER, timer_value) else:
self.gui_main_light.set_timer(self.pump_timer)
self.pump_timer -= self.ct.cycle_time / 60
class heating_function(common_base): class radiator_function(object):
KEY_USER_TEMPERATURE_SETPOINT = 'user_temperature_setpoint'
KEY_TEMPERATURE_SETPOINT = 'temperature_setpoint'
KEY_TEMPERATURE_CURRENT = 'temperature_current'
KEY_AWAY_MODE = 'away_mode'
KEY_SUMMER_MODE = 'summer_mode'
KEY_START_BOOST = 'start_boost'
KEY_SET_DEFAULT_TEMPERATURE = 'set_default_temperature'
KEY_BOOST_TIMER = 'boost_timer'
#
BOOST_TEMPERATURE = 30 BOOST_TEMPERATURE = 30
AWAY_REDUCTION = 5 AWAY_REDUCTION = 5
SUMMER_TEMPERATURE = 5 SUMMER_TEMPERATURE = 5
class value_timeout_list(object): def __init__(self, mqtt_client, topic_valve, topic_gui, default_temperature):
MAX_DELAY = 10
def __init__(self):
self.__data__ = []
self.__time__ = []
def __cleanup__(self):
now = time.time()
for i, tm in enumerate(self.__time__):
if tm + self.MAX_DELAY < now:
del (self.__data__[i])
del (self.__time__[i])
def new(self, item):
self.__cleanup__()
self.__data__.append(item)
self.__time__.append(time.time())
def is_valid_value(self, data):
self.__cleanup__()
return data not in self.__data__
def __init__(self, heating_valve, default_temperature, **kwargs):
self.heating_valve = heating_valve
self.default_temperature = default_temperature self.default_temperature = default_temperature
self.boost_timer = None
# device initialisation
self.heating_valve = devices.brennenstuhl_heatingvalve(mqtt_client, topic_valve)
self.gui_heating = devices.nodered_gui_radiator(mqtt_client, topic_gui)
# db-stored data initialisation
db_data = get_gui_radiator_data(topic_gui)
self.__away_mode__ = db_data[0] or False
self.__summer_mode__ = db_data[1] or False
self.__user_temperature_setpoint__ = db_data[2] or default_temperature
if self.__away_mode__:
self.away_mode(None, None, True)
elif self.__summer_mode__:
self.summer_mode(None, None, True)
else:
self.set_heating_setpoint(None, None, self.__user_temperature_setpoint__)
# callback initialisation
self.heating_valve.add_callback(devices.brennenstuhl_heatingvalve.KEY_TEMPERATURE, None, self.gui_heating.set_temperature_mcb)
self.heating_valve.add_callback(devices.brennenstuhl_heatingvalve.KEY_HEATING_SETPOINT, None, self.get_radiator_setpoint)
# #
self.valve_value = self.value_timeout_list() self.gui_heating.add_callback(devices.nodered_gui_radiator.KEY_SETPOINT_TEMP, None, self.set_heating_setpoint)
# self.gui_heating.add_callback(devices.nodered_gui_radiator.KEY_BOOST, None, self.boost)
super().__init__({ self.gui_heating.add_callback(devices.nodered_gui_radiator.KEY_SETPOINT_TO_DEFAULT, None, self.setpoint_to_default)
self.KEY_USER_TEMPERATURE_SETPOINT: kwargs.get(self.KEY_USER_TEMPERATURE_SETPOINT, self.default_temperature), self.gui_heating.add_callback(devices.nodered_gui_radiator.KEY_AWAY, None, self.away_mode)
self.KEY_TEMPERATURE_SETPOINT: kwargs.get(self.KEY_TEMPERATURE_SETPOINT, self.default_temperature), self.gui_heating.add_callback(devices.nodered_gui_radiator.KEY_SUMMER, None, self.summer_mode)
self.KEY_TEMPERATURE_CURRENT: kwargs.get(self.KEY_TEMPERATURE_CURRENT, None),
self.KEY_AWAY_MODE: kwargs.get(self.KEY_AWAY_MODE, False),
self.KEY_SUMMER_MODE: kwargs.get(self.KEY_SUMMER_MODE, False),
self.KEY_START_BOOST: kwargs.get(self.KEY_START_BOOST, True),
self.KEY_SET_DEFAULT_TEMPERATURE: kwargs.get(self.KEY_SET_DEFAULT_TEMPERATURE, False),
self.KEY_BOOST_TIMER: kwargs.get(self.KEY_BOOST_TIMER, 0)
})
#
self.heating_valve.set_heating_setpoint(self[self.KEY_TEMPERATURE_SETPOINT])
#
self.heating_valve.add_callback(self.heating_valve.KEY_HEATING_SETPOINT, None, self.get_radiator_setpoint)
self.heating_valve.add_callback(self.heating_valve.KEY_TEMPERATURE, None, self.get_radiator_temperature)
#
self.add_callback(self.KEY_USER_TEMPERATURE_SETPOINT, None, self.user_temperature_setpoint, False)
self.add_callback(self.KEY_TEMPERATURE_SETPOINT, None, self.set_heating_setpoint, True)
self.add_callback(self.KEY_AWAY_MODE, None, self.away_mode, True)
self.add_callback(self.KEY_SUMMER_MODE, None, self.summer_mode, True)
self.add_callback(self.KEY_SET_DEFAULT_TEMPERATURE, None, self.setpoint_to_default)
self.add_callback(self.KEY_START_BOOST, True, self.boost, False)
self.add_callback(self.KEY_BOOST_TIMER, 0, self.timer_expired, True)
# cyclic task initialisation # cyclic task initialisation
self.ct = task.periodic(1, self.cyclic_task) self.ct = task.periodic(1, self.cyclic_task)
self.ct.run() self.ct.run()
def timer_expired(self, device, data, key):
self.set(self.KEY_TEMPERATURE_SETPOINT, self[self.KEY_USER_TEMPERATURE_SETPOINT])
self.heating_valve.logger.info('Timer expired. returning to regular temperature setpoint %.1f°C.',
self[self.KEY_TEMPERATURE_SETPOINT])
def cyclic_task(self, rt): def cyclic_task(self, rt):
timer_value = self[self.KEY_BOOST_TIMER] - self.ct.cycle_time if self.boost_timer is not None:
if self[self.KEY_BOOST_TIMER] <= 0: self.gui_heating.set_timer(round(self.boost_timer / 60, 1))
self.set(self.KEY_BOOST_TIMER, 0) #
else: self.boost_timer -= self.ct.cycle_time
self.set(self.KEY_BOOST_TIMER, timer_value) if self.boost_timer <= 0:
self.cancel_boost()
self.heating_valve.set_heating_setpoint(self.__user_temperature_setpoint__)
self.heating_valve.logger.info('Timer expired. returning to regular temperature setpoint %.1f°C.', self.__user_temperature_setpoint__)
def cancel_boost(self): def cancel_boost(self, device=None, key=None, data=None):
self.set(self.KEY_BOOST_TIMER, 0, block_callback=[self.timer_expired]) if self.boost_timer is not None:
self.boost_timer = None
self.gui_heating.set_timer('-')
def send_command(self, key, data, block_callback=[]): def update_states(self, away_mode=None, summer_mode=None, user_temperature_setpoint=None):
return super().set(key, data, block_callback) if away_mode is not None:
self.__away_mode__ = away_mode
if summer_mode is not None:
self.__summer_mode__ = summer_mode
if user_temperature_setpoint is not None:
self.__user_temperature_setpoint__ = user_temperature_setpoint
set_gui_radiator_data(self.gui_heating.topic, self.__away_mode__, self.__summer_mode__, self.__user_temperature_setpoint__)
#
self.gui_heating.set_away(self.__away_mode__)
self.gui_heating.set_summer(self.__summer_mode__)
self.gui_heating.set_enable(not self.__away_mode__ and not self.__summer_mode__)
def away_mode(self, device, key, value): def away_mode(self, device, key, value):
if value is True: if value is True:
self.cancel_boost() self.cancel_boost()
self.set(self.KEY_SUMMER_MODE, False, [self.summer_mode]) self.update_states(away_mode=value, summer_mode=False)
self.set(self.KEY_TEMPERATURE_SETPOINT, self[self.KEY_USER_TEMPERATURE_SETPOINT] - self.AWAY_REDUCTION) self.heating_valve.set_heating_setpoint(self.__user_temperature_setpoint__ - self.AWAY_REDUCTION)
else: else:
self.set(self.KEY_TEMPERATURE_SETPOINT, self[self.KEY_USER_TEMPERATURE_SETPOINT]) self.update_states(away_mode=value)
self.heating_valve.set_heating_setpoint(self.__user_temperature_setpoint__)
def summer_mode(self, device, key, value): def summer_mode(self, device, key, value):
if value is True: if value is True:
self.cancel_boost() self.cancel_boost()
self.set(self.KEY_AWAY_MODE, False, [self.away_mode]) self.update_states(away_mode=False, summer_mode=value)
self.set(self.KEY_TEMPERATURE_SETPOINT, self.SUMMER_TEMPERATURE) self.heating_valve.set_heating_setpoint(self.SUMMER_TEMPERATURE)
else: else:
self.set(self.KEY_TEMPERATURE_SETPOINT, self[self.KEY_USER_TEMPERATURE_SETPOINT]) self.update_states(summer_mode=value)
self.heating_valve.set_heating_setpoint(self.__user_temperature_setpoint__)
def boost(self, device, key, data): def boost(self, device, key, data):
if self[self.KEY_BOOST_TIMER] == 0: if self.boost_timer is None:
self.heating_valve.logger.info('Starting boost mode with setpoint %.1f°C.', self.BOOST_TEMPERATURE) self.heating_valve.logger.info('Starting boost mode with setpoint %.1f°C.', self.BOOST_TEMPERATURE)
self.set(self.KEY_BOOST_TIMER, 15*60) self.boost_timer = 15*60
self.set(self.KEY_TEMPERATURE_SETPOINT, self.BOOST_TEMPERATURE) self.heating_valve.set_heating_setpoint(self.BOOST_TEMPERATURE)
else: else:
self.set(self.KEY_BOOST_TIMER, min(self[self.KEY_BOOST_TIMER] + 15 * 60, 60 * 60)) self.boost_timer += 15 * 60
self.set(self.KEY_AWAY_MODE, False, [self.away_mode]) if self.boost_timer > 60 * 60:
self.set(self.KEY_SUMMER_MODE, False, [self.summer_mode]) self.boost_timer = 60 * 60
self.update_states(away_mode=False, summer_mode=False)
def setpoint_to_default(self, device, key, data): def setpoint_to_default(self, device, key, data):
self.cancel_boost() self.set_heating_setpoint(device, key, self.default_temperature)
self.set(self.KEY_AWAY_MODE, False, [self.away_mode])
self.set(self.KEY_SUMMER_MODE, False, [self.summer_mode])
self.set(self.KEY_USER_TEMPERATURE_SETPOINT, self.default_temperature, [self.user_temperature_setpoint])
self.set(self.KEY_TEMPERATURE_SETPOINT, self.default_temperature)
def user_temperature_setpoint(self, device, key, data):
self.cancel_boost()
self.set(self.KEY_AWAY_MODE, False, [self.away_mode])
self.set(self.KEY_SUMMER_MODE, False, [self.summer_mode])
self.set(self.KEY_TEMPERATURE_SETPOINT, data)
def set_heating_setpoint(self, device, key, data): def set_heating_setpoint(self, device, key, data):
self.valve_value.new(data) self.cancel_boost()
self.update_states(away_mode=False, summer_mode=False, user_temperature_setpoint=data)
self.heating_valve.set_heating_setpoint(data) self.heating_valve.set_heating_setpoint(data)
def get_radiator_setpoint(self, device, key, data): def get_radiator_setpoint(self, device, key, data):
if self.valve_value.is_valid_value(data): if self.boost_timer is None and not self.__away_mode__ and not self.__summer_mode__:
if self[self.KEY_BOOST_TIMER] == 0 and not self[self.KEY_AWAY_MODE] and not self[self.KEY_SUMMER_MODE]: self.update_states(user_temperature_setpoint=data)
self.set(self.KEY_USER_TEMPERATURE_SETPOINT, data, block_callback=[self.set_heating_setpoint]) else:
self.update_states()
def get_radiator_temperature(self, device, key, data): self.gui_heating.set_setpoint_temperature(data)
self.set(self.KEY_TEMPERATURE_CURRENT, data)
class motion_sensor_light(common_base):
KEY_TIMER = 'timer'
KEY_MOTION_SENSOR = 'motion_%d'
KEY_MOTION_SENSOR_0 = 'motion_%d' % 0
KEY_MOTION_SENSOR_1 = 'motion_%d' % 1
KEY_MOTION_SENSOR_2 = 'motion_%d' % 2
KEY_MOTION_SENSOR_3 = 'motion_%d' % 3
KEY_MOTION_SENSOR_4 = 'motion_%d' % 4
def __init__(self, sw_device, sw_method, *args, timer_value=30):
"""
sw_device is the device switching the light, args are 0-n motion sensors
"""
dv = dict.fromkeys([self.KEY_MOTION_SENSOR % i for i in range(0, len(args))])
for key in dv:
dv[key] = False
dv[self.KEY_TIMER] = 0
super().__init__(default_values=dv)
#
self.sw_device = sw_device
self.sw_method = sw_method
self.motion_sensors = args
self.timer_reload_value = timer_value
#
sw_device.add_callback(devices.shelly_sw1.KEY_OUTPUT_0, True, self.reload_timer, True)
sw_device.add_callback(devices.shelly_sw1.KEY_OUTPUT_0, False, self.reset_timer, True)
for motion_sensor in args:
motion_sensor.add_callback(motion_sensor.KEY_OCCUPANCY, None, self.set_motion_detected, True)
#
self.add_callback(self.KEY_TIMER, 0, self.timer_expired, True)
#
cyclic_task = task.periodic(1, self.cyclic_task)
cyclic_task.run()
def reload_timer(self, device, key, data):
self.set(self.KEY_TIMER, self.timer_reload_value)
def reset_timer(self, device=None, key=None, data=None):
self.set(self.KEY_TIMER, 0)
def set_motion_detected(self, device, key, data):
for sensor_index, arg_device in enumerate(self.motion_sensors):
if arg_device.topic == device.topic:
break
self.set(self.KEY_MOTION_SENSOR % sensor_index, data)
# auto light on with state sunset -> time_offset_sunrise=60 (longer sunset) and time_offset_sunset=-60 (longer sunset)
if day_state(None, None, 60, -60).get_state() == day_state.KEY_SUNSET:
if data is True:
logger.info("%s: Motion detected - Switching on main light %s", device.topic, self.sw_device.topic)
self.sw_method(True)
def motion_detected(self):
for i in range(0, len(self.motion_sensors)):
if self[self.KEY_MOTION_SENSOR % i]:
return True
return False
def timer_expired(self, device, key, data):
logger.info("No motion and time ran out - Switching off main light %s", self.sw_device.topic)
self.sw_method(False)
def cyclic_task(self, cyclic_task):
min_value = 10 if self.motion_detected() else 0
if self[self.KEY_TIMER] != 0:
self.set(self.KEY_TIMER, max(min_value, self[self.KEY_TIMER] - cyclic_task.cycle_time))

View File

@ -2,8 +2,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import config
import devices
from function.helpers import now, sunset_time, sunrise_time
import logging import logging
import inspect import task
try: try:
from config import APP_NAME as ROOT_LOGGER_NAME from config import APP_NAME as ROOT_LOGGER_NAME
@ -13,64 +16,134 @@ logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
class room(object): class room(object):
def __init__(self, mqtt_client, pd, vd): def __init__(self, mqtt_client):
self.mqtt_client = mqtt_client self.mqtt_client = mqtt_client
self.pd = pd
self.vd = vd
def all_off(self, device=None, key=None, data=None):
logger.info("Switching all off \"%s\"", type(self).__name__)
for name, obj in inspect.getmembers(self):
try:
if obj.__module__.startswith('devices'):
obj.all_off()
except AttributeError:
pass # not a module or has no method all_off
def summer_mode(self, enable):
for name, obj in inspect.getmembers(self):
if obj.__class__.__name__ == 'heating_function':
if obj.__module__ == 'function.modules':
obj.set(obj.KEY_SUMMER_MODE, enable)
class room_collection(object): class room_shelly(room):
ALLOWED_CLASSES = ("room", "room_collection") def __init__(self, mqtt_client, topic_shelly, topic_gui):
super().__init__(mqtt_client)
def __init__(self, mqtt_client, pd, vd): self.main_light_shelly = devices.shelly(mqtt_client, topic_shelly)
self.mqtt_client = mqtt_client
self.pd = pd
self.vd = vd
def all_off(self, device=None, key=None, data=None):
logger.info("Switching all off \"%s\"", type(self).__name__)
for sub_name in dir(self):
# attribute name is not private
if not sub_name.startswith("__"):
sub = getattr(self, sub_name)
if sub.__class__.__bases__[0].__name__ in self.ALLOWED_CLASSES:
sub.all_off()
def summer_mode(self, device=None, key=None, data=None):
logger.info("Changing to %s \"%s\"", "summer mode" if data else "winter_mode", type(self).__name__)
for sub_name in dir(self):
# attribute name is not private
if not sub_name.startswith("__"):
sub = getattr(self, sub_name)
if sub.__class__.__bases__[0].__name__ in self.ALLOWED_CLASSES:
sub.summer_mode(data)
def all_devices(self, object_to_analyse=None, depth=0):
target = object_to_analyse or self
# #
devices = [] self.gui_main_light = devices.nodered_gui_light(mqtt_client, topic_gui)
for name, obj in inspect.getmembers(target): #
if not callable(obj): # sort out methods # Callback initialisation
try: #
if obj.__module__.startswith('function.') and not obj.__module__.endswith('.videv'): self.gui_main_light.add_callback(devices.nodered_gui_light.KEY_STATE, None, self.main_light_shelly.set_output_0_mcb)
devices.extend(self.all_devices(obj, depth+1)) # rekurse in function instances self.main_light_shelly.add_callback(devices.shelly.KEY_OUTPUT_0, None, self.gui_main_light.set_state_mcb)
elif obj.__module__ == "devices": #
devices.append(obj) self.block_all_off = False
except AttributeError: self.last_flash_data = None
pass # sort out non modules self.delayed_task = task.delayed(.25, self.main_light_shelly.toggle_output_0_mcb, None, None, None)
return devices
def all_off(self, device=None, key=None, data=None):
if not self.block_all_off:
logger.info("Switching all off \"%s\"", type(self).__name__)
self.main_light_shelly.set_output_0(False)
self.main_light_shelly.set_output_1(False)
self.block_all_off = False
def all_off_feedback(self, device=None, key=None, data=None):
logger.info("Flashing \"%s\" main light", type(self).__name__)
if self.main_light_shelly.output_0 is False:
self.main_light_shelly.set_output_0(True)
self.block_all_off = True
self.delayed_task.run()
def flash_main_light(self, device, key, data):
if self.last_flash_data != data and data is True:
logger.info("Flashing \"%s\" main light", type(self).__name__)
self.main_light_shelly.toggle_output_0_mcb(device, key, data)
self.delayed_task.run()
self.last_flash_data = data
class room_shelly_motion_sensor(room_shelly):
def __init__(self, mqtt_client, topic_shelly, topic_gui, topic_motion_sensor_1, topic_motion_sensor_2=None, timer_value=30):
super().__init__(mqtt_client, topic_shelly, topic_gui)
self.timer_value = timer_value
self.motion_detected_1 = False
self.motion_detected_2 = False
#
self.main_light_shelly.add_callback(devices.shelly.KEY_OUTPUT_0, True, self.reload_timer, True)
self.main_light_shelly.add_callback(devices.shelly.KEY_OUTPUT_0, False, self.reset_timer, True)
#
self.motion_sensor_silvercrest_1 = devices.silvercrest_motion_sensor(mqtt_client, topic_motion_sensor_1)
self.motion_sensor_silvercrest_1.add_callback(devices.silvercrest_motion_sensor.KEY_OCCUPANCY, None, self.set_motion_detected)
#
if topic_motion_sensor_2 is not None:
self.motion_sensor_silvercrest_2 = devices.silvercrest_motion_sensor(mqtt_client, topic_motion_sensor_2)
self.motion_sensor_silvercrest_2.add_callback(devices.silvercrest_motion_sensor.KEY_OCCUPANCY, None, self.set_motion_detected)
#
self.reset_timer()
#
cyclic_task = task.periodic(1, self.cyclic_task)
cyclic_task.run()
def set_motion_detected(self, device, key, data):
if device == self.motion_sensor_silvercrest_1:
self.motion_detected_1 = data
elif device == self.motion_sensor_silvercrest_2:
self.motion_detected_2 = data
self.gui_main_light.set_led(devices.nodered_gui_light.KEY_LED_0, self.motion_detected_1)
self.gui_main_light.set_led(devices.nodered_gui_light.KEY_LED_1, self.motion_detected_2)
if now() < sunrise_time(60) or now() > sunset_time(-60):
if data is True:
logger.info("%s: Motion detected - Switching on main light %s", device.topic, self.main_light_shelly.topic)
self.main_light_shelly.set_output_0(True)
def reload_timer(self, device, key, data):
self.main_light_timer = self.timer_value
def reset_timer(self, device=None, key=None, data=None):
self.main_light_timer = None
self.motion_detected_1 = False
self.motion_detected_2 = False
def cyclic_task(self, cyclic_task):
if self.main_light_timer is not None:
if self.main_light_timer <= 0:
logger.info("No motion and time ran out - Switching off main light %s", self.main_light_shelly.topic)
self.main_light_shelly.set_output_0(False)
self.main_light_timer = None
self.gui_main_light.set_timer('-')
else:
self.gui_main_light.set_timer(self.main_light_timer)
if (self.motion_detected_1 or self.motion_detected_2) and self.main_light_timer <= self.timer_value / 10:
self.main_light_timer = self.timer_value / 10
else:
self.main_light_timer -= cyclic_task.cycle_time
class room_shelly_tradfri_light(room_shelly):
def __init__(self, mqtt_client, topic_shelly, topic_gui, topic_tradfri_light):
super().__init__(mqtt_client, topic_shelly, topic_gui)
self.main_light_tradfri = devices.tradfri_light(mqtt_client, topic_tradfri_light)
#
# Callback initialisation
#
self.main_light_shelly.add_callback(devices.shelly.KEY_OUTPUT_0, None, self.gui_main_light.set_state_mcb)
self.main_light_shelly.add_callback(devices.shelly.KEY_OUTPUT_0, None, self.gui_main_light.set_enable_mcb)
self.main_light_tradfri.add_callback(devices.tradfri_light.KEY_BRIGHTNESS, None, self.gui_main_light.set_brightness_mcb)
self.main_light_tradfri.add_callback(devices.tradfri_light.KEY_COLOR_TEMP, None, self.gui_main_light.set_color_temp_mcb)
self.gui_main_light.add_callback(devices.nodered_gui_light.KEY_BRIGHTNESS, None, self.main_light_tradfri.set_brightness_mcb)
self.gui_main_light.add_callback(devices.nodered_gui_light.KEY_COLOR_TEMP, None, self.main_light_tradfri.set_color_temp_mcb)
class room_shelly_silvercrest_light(room_shelly_tradfri_light):
def __init__(self, mqtt_client, topic_shelly, topic_gui, topic_tradfri_light):
super().__init__(mqtt_client, topic_shelly, topic_gui, topic_tradfri_light)
#
# Callback initialisation
#
self.main_light_shelly.add_callback(devices.shelly.KEY_OUTPUT_0, None, self.get_initial_main_light_data)
#
self.main_light_shelly_last = None
def get_initial_main_light_data(self, device, key, data):
if data is True and self.main_light_shelly_last != data:
self.send_init_message_main_light()
self.main_light_shelly_last = data
def send_init_message_main_light(self):
self.main_light_tradfri.mqtt_client.send(self.main_light_tradfri.topic + "/get", '{"state": ""}')

View File

@ -3,52 +3,19 @@
# #
import config import config
from devdi import topic as props from function.modules import brightness_choose_n_action
import logging import logging
from function.modules import motion_sensor_light from function.rooms import room_shelly_motion_sensor
from function.rooms import room, room_collection
from function.videv import videv_switching_motion
try: try:
from config import APP_NAME as ROOT_LOGGER_NAME from config import APP_NAME as ROOT_LOGGER_NAME
except ImportError: except ImportError:
ROOT_LOGGER_NAME = 'root' ROOT_LOGGER_NAME = 'root'
logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__) logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
loc = props.LOC_STW
class stairway(room_shelly_motion_sensor):
class stairway(room_collection): def __init__(self, mqtt_client):
def __init__(self, mqtt_client, pd, vd):
super().__init__(mqtt_client, pd, vd)
self.stairway = stairway_stairway(mqtt_client, pd, vd)
class stairway_stairway(room):
def __init__(self, mqtt_client, pd, vd):
#
# Device initialisation
#
# http://shelly1-3494546A9364 # http://shelly1-3494546A9364
self.main_light_shelly = pd.get(props.STG_SHE, loc, props.ROO_STF, props.FUN_MAL) super().__init__(mqtt_client, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_SHELLY, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_GUI,
self.motion_sensor_ff = pd.get(props.STG_ZFE, loc, props.ROO_STF, props.FUN_MSE) config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_MOTION_SENSOR_GF, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_MOTION_SENSOR_FF,
self.motion_sensor_gf = pd.get(props.STG_ZGW, loc, props.ROO_STG, props.FUN_MSE) timer_value=config.USER_ON_TIME_STAIRWAYS)
super().__init__(mqtt_client, pd, vd)
#
# Functionality initialisation
#
self.motion_sensor_light = motion_sensor_light(
self.main_light_shelly, self.main_light_shelly.set_output_0,
self.motion_sensor_gf, self.motion_sensor_ff,
timer_value=config.USER_ON_TIME_STAIRWAYS
)
#
# Virtual Device Interface
#
self.main_light_videv = videv_switching_motion(
mqtt_client, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_VIDEV,
self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0,
self.motion_sensor_light
)

View File

@ -1,195 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
"""
Virtual Device(s)
Targets:
* MQTT-Interface to control joined devices as one virtual device
* Primary signal routing
* No functionality should be implemented here
"""
from base import videv_base
from function.rooms import room, room_collection
import time
try:
from config import APP_NAME as ROOT_LOGGER_NAME
except ImportError:
ROOT_LOGGER_NAME = 'root'
class videv_pure_switch(videv_base):
KEY_STATE = 'state'
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
self[self.KEY_STATE] = False
#
self.mqtt_client.add_callback(self.topic + '/state/set', self.__state__)
def __state__(self, mqtt_client, userdata, message):
self.set(self.KEY_STATE, message.payload == b'true')
self.__tx__(self.KEY_STATE, message.payload == b'true')
class videv_switching(videv_base):
KEY_STATE = 'state'
def __init__(self, mqtt_client, topic, sw_device, sw_key):
super().__init__(mqtt_client, topic)
self.add_routing(self.KEY_STATE, sw_device, sw_key)
class videv_switching_timer(videv_base):
KEY_STATE = 'state'
KEY_TIMER = 'timer'
def __init__(self, mqtt_client, topic, sw_device, sw_key, tm_device, tm_key):
super().__init__(mqtt_client, topic)
self.add_routing(self.KEY_STATE, sw_device, sw_key)
self.add_display(self.KEY_TIMER, tm_device, tm_key)
class videv_switching_motion(videv_base):
KEY_STATE = 'state'
#
KEY_TIMER = 'timer'
KEY_MOTION_SENSOR = 'motion_%d'
def __init__(self, mqtt_client, topic, sw_device, sw_key, motion_function):
self.motion_sensors = motion_function.motion_sensors
#
super().__init__(mqtt_client, topic)
self.add_routing(self.KEY_STATE, sw_device, sw_key)
self.add_display(self.KEY_TIMER, motion_function, motion_function.KEY_TIMER)
# motion sensor state
for index, motion_sensor in enumerate(self.motion_sensors):
self.add_display(self.KEY_MOTION_SENSOR % index, motion_sensor, motion_sensor.KEY_OCCUPANCY)
class videv_switch_brightness(videv_base):
KEY_STATE = 'state'
KEY_BRIGHTNESS = 'brightness'
def __init__(self, mqtt_client, topic, sw_device, sw_key, br_device, br_key):
super().__init__(mqtt_client, topic)
self.add_routing(self.KEY_STATE, sw_device, sw_key)
self.add_routing(self.KEY_BRIGHTNESS, br_device, br_key)
class videv_switch_brightness_color_temp(videv_base):
KEY_STATE = 'state'
KEY_BRIGHTNESS = 'brightness'
KEY_COLOR_TEMP = 'color_temp'
def __init__(self, mqtt_client, topic, sw_device, sw_key, br_device, br_key, ct_device, ct_key):
super().__init__(mqtt_client, topic)
self.add_routing(self.KEY_STATE, sw_device, sw_key)
self.add_routing(self.KEY_BRIGHTNESS, br_device, br_key)
self.add_routing(self.KEY_COLOR_TEMP, ct_device, ct_key)
class videv_heating(videv_base):
KEY_USER_TEMPERATURE_SETPOINT = 'user_temperature_setpoint'
KEY_VALVE_TEMPERATURE_SETPOINT = 'valve_temperature_setpoint'
KEY_AWAY_MODE = 'away_mode'
KEY_SUMMER_MODE = 'summer_mode'
KEY_START_BOOST = 'start_boost'
KEY_SET_DEFAULT_TEMPERATURE = 'set_default_temperature'
KEY_BOOST_TIMER = 'boost_timer'
#
KEY_TEMPERATURE = 'temperature'
def __init__(self, mqtt_client, topic, heating_function):
super().__init__(mqtt_client, topic)
#
self.add_routing(self.KEY_USER_TEMPERATURE_SETPOINT, heating_function, heating_function.KEY_USER_TEMPERATURE_SETPOINT)
self.add_routing(self.KEY_AWAY_MODE, heating_function, heating_function.KEY_AWAY_MODE)
self.add_routing(self.KEY_SUMMER_MODE, heating_function, heating_function.KEY_SUMMER_MODE)
#
self.add_control(self.KEY_START_BOOST, heating_function, heating_function.KEY_START_BOOST, False)
self.add_control(self.KEY_SET_DEFAULT_TEMPERATURE, heating_function, heating_function.KEY_SET_DEFAULT_TEMPERATURE, False)
#
self.add_display(self.KEY_VALVE_TEMPERATURE_SETPOINT, heating_function, heating_function.KEY_TEMPERATURE_SETPOINT)
self.add_display(self.KEY_BOOST_TIMER, heating_function, heating_function.KEY_BOOST_TIMER)
self.add_display(self.KEY_TEMPERATURE, heating_function, heating_function.KEY_TEMPERATURE_CURRENT, False)
class videv_multistate(videv_base):
KEY_STATE = 'state_%d'
def __init__(self, mqtt_client, topic, key_for_device, device, num_states, default_values=None):
super().__init__(mqtt_client, topic)
self.num_states = num_states
# send default values
for i in range(0, num_states):
self.__tx__(self.KEY_STATE % i, False)
#
device.add_callback(key_for_device, None, self.__index_rx__, True)
def __index_rx__(self, device, key, data):
for i in range(0, self.num_states):
self.__tx__(self.KEY_STATE % i, i == data)
class videv_audio_player(videv_base):
KEY_ACTIVE_PLAYER = 'player_%d'
KEY_TITLE = 'title'
NO_TITLE = '---'
def __init__(self, mqtt_client, topic, *args):
super().__init__(mqtt_client, topic)
for i, device in enumerate(args):
self.add_display(self.KEY_ACTIVE_PLAYER % i, device, device.KEY_STATE)
#
for audio_device in args:
audio_device.add_callback(audio_device.KEY_TITLE, None, self.__title_rx__, True)
def __title_rx__(self, device, key, data):
self.__tx__(self.KEY_TITLE, data or self.NO_TITLE)
class all_off(videv_base):
ALLOWED_CLASSES = (room, room_collection, )
def __init__(self, mqtt_client, topic, room_collection):
super().__init__(mqtt_client, topic)
self.__room_collection__ = room_collection
# init __inst_dict__
self.__inst_dict__ = {}
self.__add_instances__("all", self.__room_collection__)
# register mqtt callbacks for all my keys
for key in self.__inst_dict__:
mqtt_client.add_callback(topic + "/" + key, self.all_off)
def __check_inst_capabilities__(self, name, inst):
# fits to specified classes
if isinstance(inst, self.ALLOWED_CLASSES):
try:
# all_off method is callable
return callable(inst.all_off)
except AttributeError:
# all_off method does not exist
return False
return False
def __add_instances__(self, name, inst, level=0):
if self.__check_inst_capabilities__(name, inst):
# add given instance to my __inst_dict__
self.__inst_dict__[name] = inst
# iterate over all attribute names of instance
for sub_name in dir(inst):
# attribute name is not private
if not sub_name.startswith("__"):
sub = getattr(inst, sub_name)
# recurse with this object
if level == 0:
self.__add_instances__(sub_name, sub, level=level+1)
else:
self.__add_instances__(name + "/" + sub_name, sub, level=level+1)
def all_off(self, client, userdata, message):
key = message.topic[len(self.topic) + 1:]
self.__inst_dict__[key].all_off()

80
house_n_gui_sim.py Normal file
View File

@ -0,0 +1,80 @@
import config
import logging
import mqtt
import readline
import report
from __simulation__.rooms import house
from __simulation__.test import test_smarthome
import time
if __name__ == "__main__":
report.stdoutLoggingConfigure(((config.APP_NAME, logging.WARNING), ), report.SHORT_FMT)
#
mc = mqtt.mqtt_client(host=config.MQTT_SERVER, port=config.MQTT_PORT, username=config.MQTT_USER,
password=config.MQTT_PASSWORD, name=config.APP_NAME + '_simulation')
#
COMMANDS = ['quit', 'help', 'test.smoke', 'test.full']
#
h = house(mc)
for name in h.getmembers():
d = h.getobjbyname(name)
for c in d.capabilities():
COMMANDS.append(name + '.' + c)
#
ts = test_smarthome(h)
def reduced_list(text):
"""
Create reduced completation list
"""
reduced_list = {}
for cmd in COMMANDS:
next_dot = cmd[len(text):].find('.')
if next_dot >= 0:
reduced_list[cmd[:len(text) + next_dot + 1]] = None
else:
reduced_list[cmd] = None
return reduced_list.keys()
def completer(text, state):
"""
Our custom completer function
"""
options = [x for x in reduced_list(text) if x.startswith(text)]
return options[state]
def complete(text, state):
for cmd in COMMANDS:
if cmd.startswith(text):
if not state:
hit = ""
index = 0
sub_list = cmd.split('.')
while len(text) >= len(hit):
hit += sub_list[index] + '.'
return hit # cmd
else:
state -= 1
readline.parse_and_bind("tab: complete")
readline.set_completer(completer)
time.sleep(0.3)
print("\nEnter command: ")
while True:
userfeedback = input('')
command = userfeedback.split(' ')[0]
if userfeedback == 'quit':
break
elif userfeedback == 'help':
print("Help is not yet implemented!")
elif userfeedback == 'test.full':
ts.full()
elif userfeedback == 'test.smoke':
ts.smoke()
elif command in COMMANDS[2:]:
h.command(userfeedback)
elif userfeedback != "":
print("Unknown command!")
else:
print()

View File

@ -1,24 +0,0 @@
#!/bin/bash
#
BASEPATH=`realpath $(dirname $0)`
#
# Create venv
#
if [[ ! -e $BASEPATH/venv ]]; then
python3 -m venv $BASEPATH/venv > /dev/null 2>&1
# $BASEPATH/venv/bin/pip install --upgrade pip
find $BASEPATH -name requirements.txt | xargs -L 1 $BASEPATH/venv/bin/pip install -r > /dev/null 2>&1
echo "venv changed"
fi
#
# Update venv modules
#
for req_mod in $($BASEPATH/venv/bin/pip list --format=json | jq -r '.[] | .name'); do
$BASEPATH/venv/bin/pip install -U $req_mod | grep install > /dev/null 2>&1
if [[ "$?" -eq "0" ]]; then
echo $req_mod changed
fi
done
#|xargs -n1 $BASEPATH/venv/bin/pip install -U

2
mqtt

@ -1 +1 @@
Subproject commit 14e56ccdbf6594f699b4afcfb4acafe9b899e914 Subproject commit 1adfb0626e7777c6d29be65d4ad4ce2d57541301

2
report

@ -1 +1 @@
Subproject commit 7003c13ef8c7e7c3a55a545cbbad4039cc024a9f Subproject commit e2392c9f28d88ee54463681850acf95ae496c9a0

View File

@ -1,74 +1,26 @@
import config import config
import devdi.devices
import function import function
import json
import logging import logging
import mqtt import mqtt
import os
import report import report
import subprocess
import time import time
logger = logging.getLogger(config.APP_NAME) logger = logging.getLogger(config.APP_NAME)
VERS_MAJOR = 1
VERS_MINOR = 3
VERS_PATCH = 0
INFO_TOPIC = "__info__"
INFO_DATA = {
"app_name": os.path.splitext(os.path.basename(__file__))[0],
"version": {
"readable": "%d.%d.%d" % (VERS_MAJOR, VERS_MINOR, VERS_PATCH),
"major": VERS_MAJOR,
"minor": VERS_MINOR,
"patch": VERS_PATCH
},
"git": {
"url": subprocess.check_output(["git", "config", "--get", "remote.origin.url"])[:-1].decode("utf-8"),
"ref": subprocess.check_output(["git", "log", "-1", '--format="%H"'])[1:-2].decode("utf-8")
}
}
def __info_publisher__(client, userdata, message):
data = json.loads(message.payload)
if data != INFO_DATA:
client.publish(INFO_TOPIC, json.dumps(INFO_DATA))
if __name__ == "__main__": if __name__ == "__main__":
#
# Logging
#
if config.DEBUG: if config.DEBUG:
report.appLoggingConfigure(None, 'stdout', ((config.APP_NAME, logging.DEBUG), ), report.appLoggingConfigure(None, None, ((config.APP_NAME, logging.DEBUG), ), fmt=report.SHORT_FMT, host='localhost', port=19996)
target_level=logging.WARNING, fmt=report.SHORT_FMT, host='localhost', port=19996)
else: else:
report.stdoutLoggingConfigure(((config.APP_NAME, config.LOGLEVEL), ), report.SHORT_FMT) report.stdoutLoggingConfigure(((config.APP_NAME, logging.INFO), ), report.SHORT_FMT)
#
# MQTT Client
# #
mc = mqtt.mqtt_client(host=config.MQTT_SERVER, port=config.MQTT_PORT, username=config.MQTT_USER, mc = mqtt.mqtt_client(host=config.MQTT_SERVER, port=config.MQTT_PORT, username=config.MQTT_USER,
password=config.MQTT_PASSWORD, name=config.APP_NAME) password=config.MQTT_PASSWORD, name=config.APP_NAME)
mc.add_callback(INFO_TOPIC, __info_publisher__)
# func = function.all_functions(mc)
# Smarthome physical Devices
#
pd = devdi.devices.physical_devices(mc)
# # for device in func.devicelist():
# Smarthome physical Devices # device.add_warning_callback(None)
#
vd = devdi.devices.videv_devices(mc)
#
# Smart Home Functionality
#
func = function.all_functions(mc, pd, vd)
while (True): while (True):
time.sleep(1) time.sleep(1)

View File

@ -1,87 +0,0 @@
#
# TOPICS
#
TOPIC_WARNINGS = "videv/warnings"
TOPIC_ALL_OFF_VIDEV = "videv/off"
TOPIC_ALL_SUMMER_WINTER_MODE = "videv/summer_mode"
# ground floor west
# floor
TOPIC_GFW_FLOOR_MAIN_LIGHT_VIDEV = "videv/gfw/floor/main_light"
# marion
TOPIC_GFW_MARION_MAIN_LIGHT_VIDEV = "videv/gfw/marion/main_light"
TOPIC_GFW_MARION_HEATING_VALVE_VIDEV = "videv/gfw/marion/heating_valve"
TOPIC_GFW_MARION_WINDOW_LAMP_VIDEV = "videv/gfw/marion/window_light"
# dirk
TOPIC_GFW_DIRK_MAIN_LIGHT_VIDEV = "videv/gfw/dirk/main_light"
TOPIC_GFW_DIRK_DESK_LIGHT_VIDEV = "videv/gfw/dirk/desk_light"
TOPIC_GFW_DIRK_AMPLIFIER_VIDEV = "videv/gfw/dirk/amplifier"
TOPIC_GFW_DIRK_PHONO_VIDEV = "videv/gfw/dirk/phono"
TOPIC_GFW_DIRK_CD_PLAYER_VIDEV = "videv/gfw/dirk/cd_player"
TOPIC_GFW_DIRK_BT_VIDEV = "videv/gfw/dirk/bt"
TOPIC_GFW_DIRK_PC_DOCK_VIDEV = "videv/gfw/dirk/pc_dock"
TOPIC_GFW_DIRK_ACTIVE_BRIGHTNESS_DEVICE_VIDEV = "videv/gfw/dirk/active_brightness_device"
TOPIC_GFW_DIRK_AUDIO_PLAYER_VIDEV = "videv/gfw/dirk/audio_player"
TOPIC_GFW_DIRK_HEATING_VALVE_VIDEV = "videv/gfw/dirk/heating_valve"
# garden
TOPIC_GAR_GARDEN_MODE_VIDEV = "videv/gar/garden/mode"
TOPIC_GAR_GARDEN_GARLAND_VIDEV = "videv/gar/garden/garland"
TOPIC_GAR_GARDEN_REPEATER_VIDEV = "videv/gar/garden/repeater"
# first floor west
# floor
TOPIC_FFW_FLOOR_MAIN_LIGHT_VIDEV = "videv/ffw/floor/main_light"
# julian
TOPIC_FFW_JULIAN_MAIN_LIGHT_VIDEV = "videv/ffw/julian/main_light"
TOPIC_FFW_JULIAN_HEATING_VALVE_VIDEV = "videv/ffw/julian/heating_valve"
# bath
TOPIC_FFW_BATH_MAIN_LIGHT_VIDEV = "videv/ffw/bath/main_light"
TOPIC_FFW_BATH_HEATING_VALVE_VIDEV = "videv/ffw/bath/heating_valve"
# livingroom
TOPIC_FFW_LIVINGROOM_MAIN_LIGHT_VIDEV = "videv/ffw/livingroom/main_light"
TOPIC_FFW_LIVINGROOM_HEATING_VALVE_VIDEV = "videv/ffw/livingroom/heating_valve"
# sleep
TOPIC_FFW_SLEEP_MAIN_LIGHT_VIDEV = "videv/ffw/sleep/main_light"
TOPIC_FFW_SLEEP_HEATING_VALVE_VIDEV = "videv/ffw/sleep/heating_valve"
TOPIC_FFW_SLEEP_WINDOW_LAMP_VIDEV = "videv/ffw/sleep/window_light"
# first floor east
# floor
TOPIC_FFE_FLOOR_MAIN_LIGHT_VIDEV = "videv/ffe/floor/main_light"
# kitchen
TOPIC_FFE_KITCHEN_MAIN_LIGHT_VIDEV = "videv/ffe/kitchen/main_light"
TOPIC_FFE_KITCHEN_CIRCULATION_PUMP_VIDEV = "videv/ffe/kitchen/circulation_pump"
TOPIC_FFE_KITCHEN_HEATING_VALVE_VIDEV = "videv/ffe/kitchen/heating_valve"
# diningroom
TOPIC_FFE_DININGROOM_MAIN_LIGHT_VIDEV = "videv/ffe/diningroom/main_light"
TOPIC_FFE_DININGROOM_FLOOR_LAMP_VIDEV = "videv/ffe/diningroom/floorlamp"
TOPIC_FFE_DININGROOM_GARLAND_VIDEV = "videv/ffe/diningroom/garland"
TOPIC_FFE_DININGROOM_HEATING_VALVE_VIDEV = "videv/ffe/diningroom/heating_valve"
# sleep
TOPIC_FFE_SLEEP_MAIN_LIGHT_VIDEV = "videv/ffe/sleep/main_light"
TOPIC_FFE_SLEEP_BED_LIGHT_DI_VIDEV = "videv/ffe/sleep/bed_light_di"
TOPIC_FFE_SLEEP_BED_LIGHT_MA_VIDEV = "videv/ffe/sleep/bed_light_ma"
TOPIC_FFE_SLEEP_ACTIVE_BRIGHTNESS_DEVICE_VIDEV = "videv/ffe/sleep/active_brightness_device"
TOPIC_FFE_SLEEP_HEATING_VALVE_VIDEV = "videv/ffe/sleep/heating_valve"
TOPIC_FFE_SLEEP_WARDROBE_LIGHT_VIDEV = "videv/ffe/sleep/wardrobe_light"
# livingroom
TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_VIDEV = "videv/ffe/livingroom/main_light"
TOPIC_FFE_LIVINGROOM_FLOOR_LAMP_VIDEV = "videv/ffe/livingroom/floorlamp"
TOPIC_FFE_LIVINGROOM_XMAS_TREE_VIDEV = "videv/ffe/livingroom/xmas_tree"
TOPIC_FFE_LIVINGROOM_HEATING_VALVE_VIDEV = "videv/ffe/livingroom/heating_valve"
# stairway
# floor
TOPIC_STW_STAIRWAY_MAIN_LIGHT_VIDEV = "videv/stw/stairway/main_light"