Compare commits

..

90 Commits

Author SHA1 Message Date
b9fdd69a62 BugFix: State deleted while debug out 2025-01-26 12:57:18 +01:00
024877d2b3 Hue device (kitchen) and silvercrest button added as device 2025-01-25 10:32:59 +01:00
fd546ffcc3 Shelly pro3 added 2024-12-25 22:11:37 +01:00
3e00f009f8 Windowlights gfw/marion and ffw/sleep added 2024-12-01 19:21:05 +01:00
9fee0fc216 Bluetooth Belkin adapter added (powerplug and amoplifier source selection) 2024-11-10 15:29:30 +01:00
4a104a5034 FFW Topics for main_light bath and floor added / fixed 2024-09-29 13:22:42 +02:00
a0375245b4 Shellies for FFW floor and bath added 2024-09-29 12:57:17 +02:00
c9bf036591 Added pssibility to disable all_off functionality of output devices. Disabled all_off for ffe.sleep.wardrobe_light 2024-09-05 07:13:59 +02:00
248e9432d1 Added wifi garden and wardrobe light sleep ffe 2024-09-01 12:49:42 +02:00
430a60456d Devicename update 2024-08-20 17:45:16 +02:00
d25e0167a7 phono added to gfw.dirk 2024-07-05 14:34:13 +02:00
24297fae5f tradfri dock added for gfw/dirk 2024-06-26 21:13:59 +02:00
3dfdb5803c gfw-dirk-desk_light without powerplug 2024-06-23 19:36:59 +02:00
8455facb1c Automatic configuration improved 2024-05-27 11:40:35 +02:00
8573dcf43f HeatValve: Automatic configuration implemented 2024-05-26 18:43:39 +02:00
985e18e27f Mode Switch for garden light added 2024-05-05 18:55:53 +02:00
d613c1db5c Global Summer and Winter Mode 2024-04-20 16:56:13 +02:00
48313a2245 BugFix: init_venv - execution in bash 2024-03-09 16:58:10 +01:00
3bc38120c4 config template changed to jinja2 syntax for ansible 2024-03-09 12:42:41 +01:00
4afb781cf8 update paho to v2.0 2024-03-09 08:15:25 +01:00
084b340e70 v1.3.0 2024-02-26 19:04:10 +01:00
6638f2de69 Permanent setpoint sending disabled and time limited retries added to valve class 2024-02-25 08:56:38 +01:00
b728a472e7 Bluetooth audio status added 2024-02-11 15:45:01 +01:00
e0408bf5f6 Heatingvalves added 2024-02-11 15:17:34 +01:00
6e6d27685f Warning removed (leftovers in videv) 2023-12-25 07:27:04 +01:00
cb7f585feb Fix for grouped devices 2023-12-25 07:26:31 +01:00
e21e272ceb changed cyclic heat setpoint tx to every 15 minutes 2023-12-24 08:41:33 +01:00
d93c9548c8 warning mechanisms removed 2023-12-24 08:40:46 +01:00
da402db1ef cyclic setpoint for heating valve implemented 2023-12-23 08:36:34 +01:00
7462d7278e devdi update integration 2023-12-23 07:50:24 +01:00
9f1fa2cdf8 garden topic patch added 2023-12-23 07:43:40 +01:00
814e0ee1b2 garden added 2023-12-23 07:38:14 +01:00
e565dd717b BugFix: All off functionality 2023-11-13 20:46:52 +01:00
bc445a6ec4 device logging improved for functional visibility at loglevel info 2023-10-31 09:04:19 +01:00
ae0ae97b5c log level added to config 2023-10-31 09:03:13 +01:00
81b11969ca config_example fix for ansible deployment 2023-10-31 08:53:43 +01:00
36884d40c1 Ignore delayed heating valve setpoints - values set by user in the last two seconds 2023-10-30 09:05:19 +01:00
b181478258 Fix for warning import 2023-10-29 15:45:14 +01:00
6e9bc517a3 Integration of module devdi 2023-10-29 15:10:06 +01:00
de5a46e5bf Bug Fix Heatin functionality kitchen and julian 2023-10-20 21:33:32 +02:00
11295227d9 Heating valve ffe/kitchen added 2023-10-20 19:27:56 +02:00
dc259c5447 Heating valve ffe/julian added 2023-10-20 17:14:49 +02:00
348f4b1eea heating valves removed, defect 2023-10-15 19:04:16 +02:00
926974f5e6 heating valves moved from ffw->ffe 2023-10-15 16:35:08 +02:00
dacabbdde5 additiona heating valves implemented 2023-10-15 08:47:15 +02:00
769dffe20d initial testenvironment added 2023-10-15 08:10:32 +02:00
03e5e29f7b install routine deleted - now in ansible 2023-10-15 08:08:04 +02:00
7ea356bd84 default temperature moved from topics to config 2023-10-08 21:05:34 +02:00
30eed52514 splitted topics to seperate file and added a config_example 2023-10-04 16:15:23 +02:00
51859d9b5b scipt for venv creation added 2023-10-04 16:04:25 +02:00
ce85e1ca27 Exception handling for changed dict while loop in videv_base 2023-06-11 09:40:01 +02:00
2ec5487f00 videv initialisation and periodic status publishing 2023-05-07 17:49:46 +02:00
882ea0230c Rework store and restore heating function data from and to database 2023-03-26 11:00:18 +02:00
04f269c750 videv sends the initial state on startup 2023-03-25 19:23:00 +01:00
c076f516d8 videv base class moved to base.py 2023-02-22 07:34:06 +01:00
ce737b42d8 Bug-Fix: day_state 2023-02-19 20:32:51 +01:00
f0a05612bd day_state and day_events implemented as helpers 2023-02-18 17:16:23 +01:00
c7fcdfe9b4 Bug-Fix: Warning messages on pure RX content eliminated 2023-02-15 09:12:05 +01:00
f3ed72974e structere change for videv (set) 2023-02-15 07:11:32 +01:00
398d74ee81 Device information added (comment); clean up 2023-02-11 19:46:49 +01:00
0935605984 version update 2023-02-10 15:50:15 +01:00
5212cba366 warning improvements for nodered dashboard 2023-02-10 15:47:02 +01:00
7b3c7198be Warning collector implemented 2023-02-10 14:34:49 +01:00
8a3bbf77a4 Restructured devices 2023-02-10 10:37:35 +01:00
0b74ff04c9 Fix for git reference 2023-02-09 15:55:21 +01:00
d8cce5e245 v1.0.0 2023-02-09 14:33:25 +01:00
f73ad48ba7 Improvement: All off switches only active channels off 2023-02-06 14:58:34 +01:00
9e25a3bbb8 Slight rework of heating function 2023-02-06 14:46:05 +01:00
da9deef32e test and simulation extracted to own project space 2023-02-06 14:46:05 +01:00
6c1f81f0be all_off functionality added 2023-02-06 14:46:05 +01:00
6b22855ab5 Bug-Fix all-off functionality removed due to missing device 2023-02-06 14:46:05 +01:00
4ab312b278 simulation update 2023-02-06 14:46:05 +01:00
f86ad1813c device: brightness and color temperature calculations corrected 2023-02-06 14:46:05 +01:00
1c417fb73f old gui interface removed and clean up 2023-02-06 14:46:05 +01:00
f70ba22ce2 videv loopback prevention ignore rx after tx 2023-02-06 14:46:05 +01:00
8aabd272f9 Fix: display current_temperature videv on every rx 2023-02-06 14:46:05 +01:00
891e3bd111 rework of videv 2023-02-06 14:46:05 +01:00
d4c5411d47 all off improved 2023-02-06 14:46:05 +01:00
43308d858e User feedback (flash) implemented in new structure 2023-02-06 14:46:05 +01:00
52bb35c2c2 Revert "send setting always for heating valve device"
This reverts commit 54d72802c253a7f137b58eb3712ea0ecb2929978.
2023-02-06 14:46:05 +01:00
b9adc74a82 send setting always for heating valve device 2023-02-06 14:46:05 +01:00
e980941287 Bug-Fix for videv_multistate 2023-02-06 14:46:05 +01:00
c9165da525 videv audio_player implemented 2023-02-06 14:46:05 +01:00
b37d89fa30 Bug-Fix: timer motion_sensor_light 2023-02-06 14:46:05 +01:00
380fba46ab 2nd Bug-Fix: modules.brightness_choose_n_action 2023-02-06 14:46:05 +01:00
99e3271ec4 Bug-Fix: modules.brightness_choose_n_action 2023-02-06 14:46:05 +01:00
a6898ec044 Bug-Fix: current cemperature heating + brightness device selection in videv 2023-02-06 14:46:05 +01:00
3169b01270 Restructured: Splitting function from gui + videv implementation 2023-02-06 14:46:05 +01:00
9233468a49 videv implementation for lights 2023-02-06 14:46:05 +01:00
0bf885e58a Radiator boost deleted if circulation pump 2023-01-16 06:50:20 +01:00
40 changed files with 3050 additions and 1213 deletions

3
.gitmodules vendored
View File

@ -10,3 +10,6 @@
[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": "python", "type": "debugpy",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/smart_brain.py", "program": "${workspaceFolder}/smart_brain.py",
"console": "integratedTerminal", "console": "integratedTerminal",

21
.vscode/settings.json vendored
View File

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

View File

@ -1,41 +0,0 @@
#!/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()

View File

@ -0,0 +1,35 @@
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

@ -0,0 +1,45 @@
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

@ -0,0 +1,250 @@
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

@ -0,0 +1,55 @@
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

@ -0,0 +1,49 @@
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

@ -0,0 +1,40 @@
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

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

114
base.py
View File

@ -1,4 +1,6 @@
import json
import logging import logging
import task
try: try:
from config import APP_NAME as ROOT_LOGGER_NAME from config import APP_NAME as ROOT_LOGGER_NAME
@ -11,12 +13,11 @@ class common_base(dict):
def __init__(self, default_values=None): def __init__(self, default_values=None):
super().__init__(default_values or self.DEFAULT_VALUES) super().__init__(default_values or self.DEFAULT_VALUES)
self['__type__'] = self.__class__.__name__
# #
self.__callback_list__ = [] self.__callback_list__ = []
self.logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__) self.logger = logging.getLogger(ROOT_LOGGER_NAME).getChild("devices")
def add_callback(self, key, data, callback, on_change_only=False): def add_callback(self, key, data, callback, on_change_only=False, init_now=False):
""" """
key: key or None for all keys key: key or None for all keys
data: data or None for all data data: data or None for all data
@ -24,16 +25,21 @@ class common_base(dict):
cb_tup = (key, data, callback, on_change_only) cb_tup = (key, data, callback, on_change_only)
if cb_tup not in self.__callback_list__: if cb_tup not in self.__callback_list__:
self.__callback_list__.append(cb_tup) 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=[]): def set(self, key, data, block_callback=[]):
value_changed = self[key] != data if key in self.keys():
self[key] = data value_changed = self[key] != data
for cb_key, cb_data, callback, on_change_only in self.__callback_list__: self[key] = data
if cb_key is None or key == cb_key: # key fits to callback definition for cb_key, cb_data, callback, on_change_only in self.__callback_list__:
if cb_data is None or cb_data == self[key]: # data fits to callback definition if cb_key is None or key == cb_key: # key fits to callback definition
if value_changed or not on_change_only: # change status fits to callback definition if cb_data is None or cb_data == self[key]: # data fits to callback definition
if not callback in block_callback: # block given callbacks if value_changed or not on_change_only: # change status fits to callback definition
callback(self, key, self[key]) 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): class mqtt_base(common_base):
@ -44,3 +50,89 @@ class mqtt_base(common_base):
self.topic = topic self.topic = topic
for entry in self.topic.split('/'): for entry in self.topic.split('/'):
self.logger = self.logger.getChild(entry) 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)

26
config_example/config.j2 Normal file
View File

@ -0,0 +1,26 @@
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

0
conftest.py Normal file
View File

1
devdi Submodule

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

114
device_development.py Normal file
View File

@ -0,0 +1,114 @@
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,25 +26,27 @@ devices (DEVICES)
""" """
import json
import logging
import task
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'
BATTERY_WARN_LEVEL = 5 from devices.shelly import shelly as shelly_sw1
from devices.shelly import shelly_rpc as shelly_pro3
from devices.hue import hue_light as hue_sw_br_ct
def is_json(data): from devices.tradfri import tradfri_light as tradfri_sw
try: from devices.tradfri import tradfri_light as tradfri_sw_br
json.loads(data) from devices.tradfri import tradfri_light as tradfri_sw_br_ct
except json.decoder.JSONDecodeError: from devices.tradfri import tradfri_button as tradfri_button
return False from devices.tradfri import tradfri_light as livarno_sw_br_ct
else: from devices.brennenstuhl import brennenstuhl_heatingvalve
return True 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): class group(object):
@ -54,15 +56,23 @@ class group(object):
self._iter_counter = 0 self._iter_counter = 0
# #
self.methods = [] self.methods = []
for method in [m for m in args[0].__class__.__dict__.keys()]: self.variables = []
if not method.startswith('_') and callable(getattr(args[0], method)): # add all public callable attributes to the list for name in [m for m in args[0].__class__.__dict__.keys()]:
self.methods.append(method) 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: for member in self:
methods = [m for m in member.__class__.__dict__.keys() if not m.startswith( methods = [m for m in member.__class__.__dict__.keys() if not m.startswith(
'_') if not m.startswith('_') and callable(getattr(args[0], m))] '_') if not m.startswith('_') and callable(getattr(args[0], m))]
if self.methods != methods: if self.methods != methods:
raise ValueError("All given instances needs to have same attributes:", 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 __iter__(self):
return self return self
@ -88,745 +98,9 @@ class group(object):
try: try:
rv = super().__getattribute__(name) rv = super().__getattribute__(name)
except AttributeError: except AttributeError:
return group_execution if callable(getattr(self[0], name)):
return group_execution
else:
return getattr(self[0], name)
else: else:
return rv return rv
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.callback_list = []
self.warning_callback = None
#
self.__previous__ = {}
def receive_callback(self, client, userdata, message):
self.unpack(message)
def unpack_filter(self, key):
if key in self.RX_FILTER_DATA_KEYS:
if self.get(key) == 1 or self.get(key) == 'on' or self.get(key) == 'ON':
self[key] = True
elif self.get(key) == 0 or self.get(key) == 'off' or self.get(key) == 'OFF':
self[key] = False
def unpack_single_value(self, key, data):
prev_value = self.get(key)
if key in self.RX_KEYS:
self[key] = data
self.__previous__[key] = prev_value
# Filter, if needed
self.unpack_filter(key)
self.logger.debug("Received data %s - %s", key, str(self.get(key)))
self.callback_caller(key, self[key], self.get(key) != self.__previous__.get(key))
elif key not in self.RX_IGNORE_KEYS:
self.logger.warning('Got a message with unparsed content: "%s - %s"', key, str(data))
else:
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)
#
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.all_off_requested = False
def flash_task(self, *args):
if self.flash_active:
self.pack(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_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')
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:
self.set_output_0(False)
self.set_output_1(False)
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')
def all_off(self):
self.set_output_0(False)
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')
def all_off(self):
self.set_output_all(False)
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', 'color_temp_startup']
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] = round((self[key] - 1) * 100 / 253, 0)
elif key == self.KEY_COLOR_TEMP:
self[key] = round((self[key] - 250) * 10 / 204, 0)
else:
super().unpack_filter(key)
def pack_filter(self, key, data):
if key == self.KEY_BRIGHTNESS:
return round(data * 253 / 100 + 1, 0)
elif key == self.KEY_COLOR_TEMP:
return round(data * 204 / 10 + 250, 0)
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 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, '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)
def all_off(self):
self.set_output_0(False)
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 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]

235
devices/base.py Normal file
View File

@ -0,0 +1,235 @@
#!/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))

118
devices/brennenstuhl.py Normal file
View File

@ -0,0 +1,118 @@
#!/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)

118
devices/hue.py Normal file
View File

@ -0,0 +1,118 @@
#!/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)

262
devices/mydevices.py Normal file
View File

@ -0,0 +1,262 @@
#!/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)

238
devices/shelly.py Normal file
View File

@ -0,0 +1,238 @@
#!/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))

148
devices/silvercrest.py Normal file
View File

@ -0,0 +1,148 @@
#!/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)

192
devices/tradfri.py Normal file
View File

@ -0,0 +1,192 @@
#!/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))
#
# RX
#
@property
def action(self):
"""rv: action_txt"""
return self.get(self.KEY_ACTION)

View File

@ -3,13 +3,13 @@
# #
import config 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
from function.first_floor_west import first_floor_west from function.first_floor_west import first_floor_west
from function.first_floor_east import first_floor_east from function.first_floor_east import first_floor_east
from function.rooms import room_collection from function.rooms import room_collection
from function.videv import all_off from function.videv import all_off, videv_pure_switch
import inspect
import logging import logging
try: try:
@ -20,19 +20,21 @@ logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
class all_functions(room_collection): class all_functions(room_collection):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
super().__init__(mqtt_client) super().__init__(mqtt_client, pd, vd)
# #
# Rooms # Rooms
# #
# garden
self.gar = garden(self.mqtt_client, pd, vd)
# stairway # stairway
self.stw = stairway(self.mqtt_client) self.stw = stairway(self.mqtt_client, pd, vd)
# ground floor west # ground floor west
self.gfw = ground_floor_west(self.mqtt_client) self.gfw = ground_floor_west(self.mqtt_client, pd, vd)
# first floor west # first floor west
self.ffw = first_floor_west(self.mqtt_client) self.ffw = first_floor_west(self.mqtt_client, pd, vd)
# first floor east # first floor east
self.ffe = first_floor_east(self.mqtt_client) self.ffe = first_floor_east(self.mqtt_client, pd, vd)
# #
# Interactions # Interactions
# #
@ -40,30 +42,48 @@ 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_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(devices.shelly.KEY_INPUT_1, None, self.gfw_dirk_input_1) self.gfw.dirk.main_light_shelly.add_callback(self.gfw.dirk.main_light_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(devices.tradfri_button.KEY_ACTION, self.ffe.sleep.button_tradfri.add_callback(self.ffe.sleep.button_tradfri.KEY_ACTION,
devices.tradfri_button.ACTION_RIGHT, self.ffe.floor.main_light_shelly.toggle_output_0_mcb) self.ffe.sleep.button_tradfri.ACTION_RIGHT,
self.ffe.floor.main_light_shelly.toggle_output_0_mcb)
def init_off_functionality(self): def init_off_functionality(self):
# ALL OFF - Virtual device # ALL OFF - Virtual device
self.videv_all_off = all_off(self.mqtt_client, config.TOPIC_ALL_OFF_VIDEV, self) self.videv_all_off = all_off(self.mqtt_client, config.TOPIC_ALL_OFF_VIDEV, self)
# ALL OFF - Long push stairway # ALL OFF - Long push stairway
self.stw.stairway.main_light_shelly.add_callback(devices.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,
self.stw.stairway.main_light_shelly.add_callback(devices.shelly.KEY_LONGPUSH_0, True, self.all_off) 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 # FFE ALL OFF - Long push ffe_floor
self.ffe.floor.main_light_shelly.add_callback(devices.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,
self.ffe.floor.main_light_shelly.add_callback(devices.shelly.KEY_LONGPUSH_0, True, self.ffe.all_off) 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 # 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) 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:

View File

@ -1,15 +1,31 @@
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,
1: heating_function.KEY_SUMMER_MODE,
2: heating_function.KEY_USER_TEMPERATURE_SETPOINT,
3: heating_function.KEY_TEMPERATURE_SETPOINT
}
def get_radiator_data(topic): def get_radiator_data(topic):
return __storage__().get_radiator_data(topic) db_data = __storage__().get_radiator_data(topic)
rv = {}
for index in db_mapping_radiator:
rv[db_mapping_radiator[index]] = db_data[index]
return rv
def set_radiator_data(topic, away_mode, summer_mode, user_temperatur_setpoint, temperatur_setpoint): def set_radiator_data(device, key, data):
return __storage__().store_radiator_data(topic, away_mode, summer_mode, user_temperatur_setpoint, temperatur_setpoint) 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):
@ -25,19 +41,17 @@ class __storage__(object):
temperatur_setpoint real temperatur_setpoint real
)""") )""")
def store_radiator_data(self, topic, away_mode, summer_mode, user_temperatur_setpoint, temperatur_setpoint): def store_radiator_data(self, topic, target_data):
data = [topic, away_mode, summer_mode, user_temperatur_setpoint, temperatur_setpoint]
try: try:
with self.conn: with self.conn:
self.c.execute( self.c.execute(
'INSERT INTO radiator VALUES (?, ?, ?, ?, ?)', data) 'INSERT INTO radiator VALUES (?, ?, ?, ?, ?)', [topic] + target_data)
except sqlite3.IntegrityError: except sqlite3.IntegrityError:
data = [away_mode, summer_mode, user_temperatur_setpoint, temperatur_setpoint]
db_data = self.get_radiator_data(topic) db_data = self.get_radiator_data(topic)
if db_data != data: if db_data != target_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 = ?', data + [topic]) 'UPDATE radiator SET away_mode = ?, summer_mode = ?, user_temperatur_setpoint = ?, temperatur_setpoint = ? WHERE topic = ?', target_data + [topic])
def get_radiator_data(self, topic): def get_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, temperatur_setpoint] or [None, None, None, None]"""

View File

@ -3,8 +3,11 @@
# #
import config import config
import devices from devdi import topic as props
from function.modules import brightness_choose_n_action, timer_on_activation, heating_function from devices import group
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.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 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
@ -15,204 +18,311 @@ 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(room_collection): class first_floor_east(room_collection):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
super().__init__(mqtt_client) super().__init__(mqtt_client, pd, vd)
self.dining = first_floor_east_dining(mqtt_client) self.dining = first_floor_east_dining(mqtt_client, pd, vd)
self.floor = first_floor_east_floor(mqtt_client) self.floor = first_floor_east_floor(mqtt_client, pd, vd)
self.kitchen = first_floor_east_kitchen(mqtt_client) self.kitchen = first_floor_east_kitchen(mqtt_client, pd, vd)
self.livingroom = first_floor_east_living(mqtt_client) self.livingroom = first_floor_east_living(mqtt_client, pd, vd)
self.sleep = first_floor_east_sleep(mqtt_client) self.sleep = first_floor_east_sleep(mqtt_client, pd, vd)
class first_floor_east_floor(room): class first_floor_east_floor(room):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_FLO
# #
# Device initialisation # Device initialisation
# #
# http://shelly1l-3C6105E4E629 # http://shelly1l-3C6105E4E629
self.main_light_shelly = devices.shelly(mqtt_client, config.TOPIC_FFE_FLOOR_MAIN_LIGHT_SHELLY) # main light
super().__init__(mqtt_client) self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
super().__init__(mqtt_client, pd, vd)
# #
# Virtual Device Interface # Virtual Device Interface
# #
# main light
self.main_light = videv_switching( self.main_light = videv_switching(
mqtt_client, config.TOPIC_FFE_FLOOR_MAIN_LIGHT_VIDEV, mqtt_client, config.TOPIC_FFE_FLOOR_MAIN_LIGHT_VIDEV,
self.main_light_shelly, devices.shelly.KEY_OUTPUT_0 self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0
) )
class first_floor_east_kitchen(room): class first_floor_east_kitchen(room):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_KIT
# #
# Device initialisation # Device initialisation
# #
# http://shelly1l-8CAAB5616C01 # http://shelly1l-8CAAB5616C01
self.main_light_shelly = devices.shelly(mqtt_client, config.TOPIC_FFE_KITCHEN_MAIN_LIGHT_SHELLY) # main light
# http://shelly1-e89f6d85a466/ self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
self.circulation_pump_shelly = devices.shelly(mqtt_client, config.TOPIC_FFE_KITCHEN_CIRCULATION_PUMP_SHELLY) self.main_light_hue = pd.get(props.STG_ZFE, loc, roo, props.FUN_MAL)
super().__init__(mqtt_client) # 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 # Functionality initialisation
# #
self.circulation_pump = timer_on_activation(self.circulation_pump_shelly, devices.shelly.KEY_OUTPUT_0, 10*60) # circulation pump
self.circulation_pump_shelly.add_callback(devices.shelly.KEY_OUTPUT_0, True, self.main_light_shelly.flash_0_mcb, True) 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)
# #
# Virtual Device Interface # Virtual Device Interface
# #
self.main_light_videv = videv_switching( # main light
self.main_light_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_FFE_KITCHEN_MAIN_LIGHT_VIDEV, mqtt_client, config.TOPIC_FFE_KITCHEN_MAIN_LIGHT_VIDEV,
self.main_light_shelly, devices.shelly.KEY_OUTPUT_0 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( self.circulation_pump_videv = videv_switching_timer(
mqtt_client, config.TOPIC_FFE_KITCHEN_CIRCULATION_PUMP_VIDEV, mqtt_client, config.TOPIC_FFE_KITCHEN_CIRCULATION_PUMP_VIDEV,
self.circulation_pump_shelly, devices.shelly.KEY_OUTPUT_0, self.circulation_pump_shelly, self.circulation_pump_shelly.KEY_OUTPUT_0,
self.circulation_pump, timer_on_activation.KEY_TIMER 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):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_DIN
# #
# Device initialisation # Device initialisation
# #
self.day_events = day_event((6, 0), (22, 0), 30, -30)
# http://shelly1l-84CCA8ADD055 # http://shelly1l-84CCA8ADD055
self.main_light_shelly = devices.shelly(mqtt_client, config.TOPIC_FFE_DININGROOM_MAIN_LIGHT_SHELLY) # main light
self.floorlamp_powerplug = devices.silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_DININGROOM_FLOOR_LAMP_POWERPLUG) self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
# 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 = devices.silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_DININGROOM_GARLAND_POWERPLUG) self.garland_powerplug = pd.get(props.STG_ZFE, loc, roo, props.FUN_GAR)
super().__init__(mqtt_client) super().__init__(mqtt_client, pd, vd)
# #
# Functionality initialisation # Functionality initialisation
# #
self.main_light_shelly.add_callback(devices.shelly.KEY_OUTPUT_0, None, self.floorlamp_powerplug.set_output_0_mcb, True) self.day_events.add_callback(None, True, self.__day_events__, True)
# 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 # Virtual Device Interface
# #
# main light
self.main_light_videv = videv_switching( self.main_light_videv = videv_switching(
mqtt_client, config.TOPIC_FFE_DININGROOM_MAIN_LIGHT_VIDEV, mqtt_client, config.TOPIC_FFE_DININGROOM_MAIN_LIGHT_VIDEV,
self.main_light_shelly, devices.shelly.KEY_OUTPUT_0 self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0
) )
# floor lamp
self.floorlamp_videv = videv_switching( self.floorlamp_videv = videv_switching(
mqtt_client, config.TOPIC_FFE_DININGROOM_FLOOR_LAMP_VIDEV, mqtt_client, config.TOPIC_FFE_DININGROOM_FLOOR_LAMP_VIDEV,
self.floorlamp_powerplug, devices.silvercrest_powerplug.KEY_OUTPUT_0 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_videv = videv_switching(
mqtt_client, config.TOPIC_FFE_DININGROOM_GARLAND_VIDEV, mqtt_client, config.TOPIC_FFE_DININGROOM_GARLAND_VIDEV,
self.garland_powerplug, devices.silvercrest_powerplug.KEY_OUTPUT_0 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):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_SLP
# #
# Device initialisation # Device initialisation
# #
# http://shelly1l-E8DB84A254C7 # http://shelly1l-E8DB84A254C7
self.main_light_shelly = devices.shelly(mqtt_client, config.TOPIC_FFE_SLEEP_MAIN_LIGHT_SHELLY) # main light
self.main_light_tradfri = devices.tradfri_light(mqtt_client, config.TOPIC_FFE_SLEEP_MAIN_LIGHT_ZIGBEE) self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
self.bed_light_di_tradfri = devices.tradfri_light(mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_DI_ZIGBEE) self.main_light_tradfri = pd.get(props.STG_ZFE, loc, roo, props.FUN_MAL)
self.bed_light_ma_powerplug = devices.silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_MA_POWERPLUG) # bed light
self.heating_valve = devices.brennenstuhl_heatingvalve(mqtt_client, config.TOPIC_FFE_SLEEP_HEATING_VALVE_ZIGBEE) self.bed_light_di_tradfri = pd.get(props.STG_ZFE, loc, roo, props.FUN_BLD)
self.button_tradfri = devices.tradfri_button(mqtt_client, config.TOPIC_FFE_SLEEP_INPUT_DEVICE) self.bed_light_ma_powerplug = pd.get(props.STG_ZFE, loc, roo, props.FUN_BLM)
# heating function
self.heating_valve = pd.get(props.STG_ZFE, loc, roo, props.FUN_HEA)
# 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) super().__init__(mqtt_client, pd, vd)
# #
# Functionality initialisation # Functionality initialisation
# #
# button / brightness function # button / brightness function
self.brightness_functions = brightness_choose_n_action(self.button_tradfri) self.brightness_functions = brightness_choose_n_action(self.button_tradfri)
self.brightness_functions.add(self.main_light_tradfri, self.main_light_shelly, devices.shelly.KEY_OUTPUT_0) self.brightness_functions.add(self.main_light_tradfri, self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0)
self.brightness_functions.add(self.bed_light_di_tradfri, self.bed_light_di_tradfri, devices.tradfri_light.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)
# button / main light # button / main light
self.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_TOGGLE, self.button_tradfri.add_callback(self.button_tradfri.KEY_ACTION, self.button_tradfri.ACTION_TOGGLE,
self.main_light_shelly.toggle_output_0_mcb) self.main_light_shelly.toggle_output_0_mcb)
# button / bed light # 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(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_LEFT_LONG, self.button_tradfri.add_callback(self.button_tradfri.KEY_ACTION, self.button_tradfri.ACTION_LEFT_LONG,
self.bed_light_ma_powerplug.toggle_output_0_mcb) self.bed_light_ma_powerplug.toggle_output_0_mcb)
# heating function # heating function
self.heating_function = heating_function(self.heating_valve) 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 # Virtual Device Interface
# #
# main light
self.main_light_videv = videv_switch_brightness_color_temp( self.main_light_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_FFE_SLEEP_MAIN_LIGHT_VIDEV, mqtt_client, config.TOPIC_FFE_SLEEP_MAIN_LIGHT_VIDEV,
self.main_light_shelly, devices.shelly.KEY_OUTPUT_0, self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0,
self.main_light_tradfri, devices.tradfri_light.KEY_BRIGHTNESS, self.main_light_tradfri, self.main_light_tradfri.KEY_BRIGHTNESS,
self.main_light_tradfri, devices.tradfri_light.KEY_COLOR_TEMP self.main_light_tradfri, self.main_light_tradfri.KEY_COLOR_TEMP
) )
# bed light
self.bed_light_di_videv = videv_switch_brightness( self.bed_light_di_videv = videv_switch_brightness(
mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_DI_VIDEV, mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_DI_VIDEV,
self.bed_light_di_tradfri, devices.tradfri_light.KEY_OUTPUT_0, self.bed_light_di_tradfri, self.bed_light_di_tradfri.KEY_OUTPUT_0,
self.bed_light_di_tradfri, devices.tradfri_light.KEY_BRIGHTNESS, self.bed_light_di_tradfri, self.bed_light_di_tradfri.KEY_BRIGHTNESS,
) )
self.bed_light_ma_videv = videv_switching( self.bed_light_ma_videv = videv_switching(
mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_MA_VIDEV, mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_MA_VIDEV,
self.bed_light_ma_powerplug, devices.silvercrest_powerplug.KEY_OUTPUT_0 self.bed_light_ma_powerplug, self.bed_light_ma_powerplug.KEY_OUTPUT_0
) )
# heating function
self.heating_function_videv = videv_heating( self.heating_function_videv = videv_heating(
mqtt_client, config.TOPIC_FFE_SLEEP_HEATING_VALVE_VIDEV, mqtt_client, config.TOPIC_FFE_SLEEP_HEATING_VALVE_VIDEV,
self.heating_function self.heating_function
) )
# button
self.brightness_functions_device_videv = videv_multistate( self.brightness_functions_device_videv = videv_multistate(
mqtt_client, config.TOPIC_FFE_SLEEP_ACTIVE_BRIGHTNESS_DEVICE_VIDEV, mqtt_client, config.TOPIC_FFE_SLEEP_ACTIVE_BRIGHTNESS_DEVICE_VIDEV,
brightness_choose_n_action.KEY_ACTIVE_DEVICE, self.brightness_functions, 2 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):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_LIV
# #
# Device initialisation # Device initialisation
# #
# http://shelly1l-3C6105E3F910 # http://shelly1l-3C6105E3F910
self.main_light_shelly = devices.shelly(mqtt_client, config.TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_SHELLY) # main light
self.main_light_tradfri = devices.tradfri_light(mqtt_client, config.TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_ZIGBEE) self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
self.floorlamp_tradfri = devices.group( self.main_light_tradfri = pd.get(props.STG_ZFE, loc, roo, props.FUN_MAL)
*[devices.tradfri_light(mqtt_client, config.TOPIC_FFE_LIVINGROOM_FLOOR_LAMP_ZIGBEE % i) for i in range(1, 7)]) # floor lamp
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 = devices.silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_LIVINGROOM_XMAS_TREE_POWERPLUG) self.powerplug_xmas_tree = pd.get(props.STG_ZFE, loc, roo, props.FUN_XTR)
self.powerplug_xmas_star = devices.silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_LIVINGROOM_XMAS_STAR_POWERPLUG) self.powerplug_xmas_star = pd.get(props.STG_ZFE, loc, roo, props.FUN_XST)
super().__init__(mqtt_client) super().__init__(mqtt_client, pd, vd)
# #
# Functionality initialisation # Functionality initialisation
# #
# floor lamp synchronisation with main_light # floor lamp synchronisation with main_light
self.main_light_shelly.add_callback(devices.shelly.KEY_OUTPUT_0, None, self.floorlamp_tradfri.set_output_0_mcb, True) 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 # Virtual Device Interface
# #
# main light
self.main_light_videv = videv_switch_brightness_color_temp( self.main_light_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_VIDEV, mqtt_client, config.TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_VIDEV,
self.main_light_shelly, devices.shelly.KEY_OUTPUT_0, self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0,
self.main_light_tradfri, devices.tradfri_light.KEY_BRIGHTNESS, self.main_light_tradfri, self.main_light_tradfri.KEY_BRIGHTNESS,
self.main_light_tradfri, devices.tradfri_light.KEY_COLOR_TEMP self.main_light_tradfri, self.main_light_tradfri.KEY_COLOR_TEMP
) )
# floor lamp
self.floorlamp_videv = videv_switch_brightness_color_temp( self.floorlamp_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_FFE_LIVINGROOM_FLOOR_LAMP_VIDEV, mqtt_client, config.TOPIC_FFE_LIVINGROOM_FLOOR_LAMP_VIDEV,
self.floorlamp_tradfri, devices.tradfri_light.KEY_OUTPUT_0, self.floorlamp_tradfri, self.floorlamp_tradfri.KEY_OUTPUT_0,
self.floorlamp_tradfri, devices.tradfri_light.KEY_BRIGHTNESS, self.floorlamp_tradfri, self.floorlamp_tradfri.KEY_BRIGHTNESS,
self.floorlamp_tradfri, devices.tradfri_light.KEY_COLOR_TEMP 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.xmas_tree_videv = videv_switching(
mqtt_client, config.TOPIC_FFE_LIVINGROOM_XMAS_TREE_VIDEV, mqtt_client, config.TOPIC_FFE_LIVINGROOM_XMAS_TREE_VIDEV,
self.powerplug_xmas_tree, devices.silvercrest_powerplug.KEY_OUTPUT_0 self.powerplug_xmas_tree, self.powerplug_xmas_tree.KEY_OUTPUT_0
) )

View File

@ -3,10 +3,11 @@
# #
import config import config
import devices from devdi import topic as props
from function.db import get_radiator_data, set_radiator_data
from function.modules import heating_function from function.modules import heating_function
from function.rooms import room, room_collection from function.rooms import room, room_collection
from function.videv import videv_switch_brightness, videv_switch_brightness_color_temp, videv_heating from function.videv import videv_switch_brightness, videv_switch_brightness_color_temp, videv_heating, videv_switching
import logging import logging
@ -16,53 +17,114 @@ 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(room_collection): class first_floor_west(room_collection):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
super().__init__(mqtt_client) super().__init__(mqtt_client, pd, vd)
self.bath = first_floor_west_bath(mqtt_client) self.floor = first_floor_west_floor(mqtt_client, pd, vd)
self.julian = first_floor_west_julian(mqtt_client) self.bath = first_floor_west_bath(mqtt_client, pd, vd)
self.livingroom = first_floor_west_living(mqtt_client) self.julian = first_floor_west_julian(mqtt_client, pd, vd)
self.sleep = first_floor_west_sleep(mqtt_client) 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):
def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_FLO
#
# Device initialisation
#
# 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): class first_floor_west_julian(room):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_JUL
# #
# Device initialisation # Device initialisation
# #
# http://shelly1l-3C6105E43452 # http://shelly1l-3C6105E43452
self.main_light_shelly = devices.shelly(mqtt_client, config.TOPIC_FFW_JULIAN_MAIN_LIGHT_SHELLY) # main light
self.main_light_tradfri = devices.tradfri_light(mqtt_client, config.TOPIC_FFW_JULIAN_MAIN_LIGHT_ZIGBEE) self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
super().__init__(mqtt_client) 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)
# Virtual Device Interface super().__init__(mqtt_client, pd, vd)
#
self.main_light_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_FFW_JULIAN_MAIN_LIGHT_VIDEV,
self.main_light_shelly, devices.shelly.KEY_OUTPUT_0,
self.main_light_tradfri, devices.tradfri_light.KEY_BRIGHTNESS,
self.main_light_tradfri, devices.tradfri_light.KEY_COLOR_TEMP
)
class first_floor_west_bath(room):
def __init__(self, mqtt_client):
#
# Device initialisation
#
self.heating_valve = devices.brennenstuhl_heatingvalve(mqtt_client, config.TOPIC_FFW_BATH_HEATING_VALVE_ZIGBEE)
super().__init__(mqtt_client)
# #
# Functionality initialisation # Functionality initialisation
# #
# heating function # heating function
self.heating_function = heating_function(self.heating_valve) 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 # 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( self.heating_function_videv = videv_heating(
mqtt_client, config.TOPIC_FFW_BATH_HEATING_VALVE_VIDEV, mqtt_client, config.TOPIC_FFW_BATH_HEATING_VALVE_VIDEV,
self.heating_function self.heating_function
@ -70,41 +132,95 @@ class first_floor_west_bath(room):
class first_floor_west_living(room): class first_floor_west_living(room):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_LIV
# #
# Device initialisation # Device initialisation
# #
# http://shelly1l-84CCA8ACE6A1 # http://shelly1l-84CCA8ACE6A1
self.main_light_shelly = devices.shelly(mqtt_client, config.TOPIC_FFW_LIVINGROOM_MAIN_LIGHT_SHELLY) # main light
self.main_light_tradfri = devices.tradfri_light(mqtt_client, config.TOPIC_FFW_LIVINGROOM_MAIN_LIGHT_ZIGBEE) self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
super().__init__(mqtt_client) 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 # Virtual Device Interface
# #
# main light
self.main_light_videv = videv_switch_brightness_color_temp( self.main_light_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_FFW_LIVINGROOM_MAIN_LIGHT_VIDEV, mqtt_client, config.TOPIC_FFW_LIVINGROOM_MAIN_LIGHT_VIDEV,
self.main_light_shelly, devices.shelly.KEY_OUTPUT_0, self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0,
self.main_light_tradfri, devices.tradfri_light.KEY_BRIGHTNESS, self.main_light_tradfri, self.main_light_tradfri.KEY_BRIGHTNESS,
self.main_light_tradfri, devices.tradfri_light.KEY_COLOR_TEMP 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): class first_floor_west_sleep(room):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_SLP
# #
# Device initialisation # Device initialisation
# #
# http://shelly1-3494546A51F2 # http://shelly1-3494546A51F2
self.main_light_shelly = devices.shelly(mqtt_client, config.TOPIC_FFW_SLEEP_MAIN_LIGHT_SHELLY) # main light
self.main_light_tradfri = devices.tradfri_light(mqtt_client, config.TOPIC_FFW_SLEEP_MAIN_LIGHT_ZIGBEE) self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
super().__init__(mqtt_client) 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 # Virtual Device Interface
# #
# main light
self.main_light_videv = videv_switch_brightness( self.main_light_videv = videv_switch_brightness(
mqtt_client, config.TOPIC_FFW_SLEEP_MAIN_LIGHT_VIDEV, mqtt_client, config.TOPIC_FFW_SLEEP_MAIN_LIGHT_VIDEV,
self.main_light_shelly, devices.shelly.KEY_OUTPUT_0, self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0,
self.main_light_tradfri, devices.tradfri_light.KEY_BRIGHTNESS 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
) )

70
function/garden.py Normal file
View File

@ -0,0 +1,70 @@
#!/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,7 +3,9 @@
# #
import config import config
import devices from devdi import topic as props
from devices import group
from function.db import get_radiator_data, set_radiator_data
from function.modules import brightness_choose_n_action, heating_function, switched_light from function.modules import brightness_choose_n_action, heating_function, switched_light
from function.rooms import room, room_collection 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 from function.videv import videv_switching, videv_switch_brightness_color_temp, videv_heating, videv_multistate, videv_audio_player
@ -16,72 +18,92 @@ 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(room_collection): class ground_floor_west(room_collection):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
super().__init__(mqtt_client) super().__init__(mqtt_client, pd, vd)
self.dirk = ground_floor_west_dirk(mqtt_client) self.dirk = ground_floor_west_dirk(mqtt_client, pd, vd)
self.floor = ground_floor_west_floor(mqtt_client) self.floor = ground_floor_west_floor(mqtt_client, pd, vd)
self.marion = ground_floor_west_marion(mqtt_client) self.marion = ground_floor_west_marion(mqtt_client, pd, vd)
class ground_floor_west_floor(room): class ground_floor_west_floor(room):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_FLO
# #
# Device initialisation # Device initialisation
# #
# http://shelly1l-84CCA8AD1148 # http://shelly1l-84CCA8AD1148
self.main_light_shelly = devices.shelly(mqtt_client, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_SHELLY) self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
self.main_light_tradfri = devices.group( self.main_light_tradfri = pd.get(props.STG_ZGW, loc, roo, props.FUN_MAL)
devices.tradfri_light(mqtt_client, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_ZIGBEE % 1), super().__init__(mqtt_client, pd, vd)
devices.tradfri_light(mqtt_client, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_ZIGBEE % 2)
)
super().__init__(mqtt_client)
# #
# Functionality initialisation # Functionality initialisation
# #
# Request silvercrest data of lead light after power on # Request silvercrest data of lead light after power on
switched_light(self.main_light_shelly, devices.shelly.KEY_OUTPUT_0, self.main_light_tradfri) switched_light(self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0, self.main_light_tradfri)
# #
# Virtual Device Interface # Virtual Device Interface
# #
self.main_light_videv = videv_switch_brightness_color_temp( self.main_light_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_VIDEV, mqtt_client, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_VIDEV,
self.main_light_shelly, devices.shelly.KEY_OUTPUT_0, self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0,
self.main_light_tradfri, devices.tradfri_light.KEY_BRIGHTNESS, self.main_light_tradfri, self.main_light_tradfri.KEY_BRIGHTNESS,
self.main_light_tradfri, devices.tradfri_light.KEY_COLOR_TEMP self.main_light_tradfri, self.main_light_tradfri.KEY_COLOR_TEMP
) )
class ground_floor_west_marion(room): class ground_floor_west_marion(room):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_MAR
# #
# Device initialisation # Device initialisation
# #
# http://shelly1l-E8DB84A1E067 # http://shelly1l-E8DB84A1E067
self.main_light_shelly = devices.shelly(mqtt_client, config.TOPIC_GFW_MARION_MAIN_LIGHT_SHELLY) # main light
self.heating_valve = devices.brennenstuhl_heatingvalve(mqtt_client, config.TOPIC_GFW_MARION_HEATING_VALVE_ZIGBEE) self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
super().__init__(mqtt_client) # 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 # Functionality initialisation
# #
# heating function # heating function
self.heating_function = heating_function(self.heating_valve) 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 # Virtual Device Interface
# #
self.main_light_videv = videv_switching( self.main_light_videv = videv_switching(
mqtt_client, config.TOPIC_GFW_MARION_MAIN_LIGHT_VIDEV, mqtt_client, config.TOPIC_GFW_MARION_MAIN_LIGHT_VIDEV,
self.main_light_shelly, devices.shelly.KEY_OUTPUT_0 self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0
) )
self.heating_function_videv = videv_heating( self.heating_function_videv = videv_heating(
mqtt_client, config.TOPIC_GFW_MARION_HEATING_VALVE_VIDEV, mqtt_client, config.TOPIC_GFW_MARION_HEATING_VALVE_VIDEV,
self.heating_function 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):
@ -90,94 +112,122 @@ class ground_floor_west_dirk(room):
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
# #
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): def __init__(self, mqtt_client, pd, vd):
roo = props.ROO_DIR
# #
# Device initialisation # Device initialisation
# #
# http://shelly1l-3C6105E44F27 # http://shelly1l-3C6105E44F27
self.main_light_shelly = devices.shelly(mqtt_client, config.TOPIC_GFW_DIRK_MAIN_LIGHT_SHELLY) # main light
self.main_light_tradfri = devices.tradfri_light(mqtt_client, config.TOPIC_GFW_DIRK_MAIN_LIGHT_ZIGBEE) self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
self.powerplug_common = devices.my_powerplug(mqtt_client, config.TOPIC_GFW_DIRK_POWERPLUG) self.main_light_tradfri = pd.get(props.STG_ZGW, loc, roo, props.FUN_MAL)
self.desk_light_tradfri = devices.tradfri_light(mqtt_client, config.TOPIC_GFW_DIRK_DESK_LIGHT_ZIGBEE) # powerplug
self.button_tradfri = devices.tradfri_button(mqtt_client, config.TOPIC_GFW_DIRK_INPUT_DEVICE) self.powerplug_common = pd.get(props.STG_MYA, loc, roo, props.FUN_MPP)
self.remote_amplifier = devices.remote(mqtt_client, config.TOPIC_GFW_DIRK_AMPLIFIER_REMOTE) self.KEY_POWERPLUG_AMPLIFIER = self.powerplug_common.KEY_OUTPUT_0
self.spotify_state = devices.audio_status(mqtt_client, config.TOPIC_GFW_DIRK_SPOTIFY) self.KEY_POWERPLUG_PHONO = self.powerplug_common.KEY_OUTPUT_1
self.mpd_state = devices.audio_status(mqtt_client, config.TOPIC_GFW_DIRK_MPD) self.KEY_POWERPLUG_CD_PLAYER = self.powerplug_common.KEY_OUTPUT_2
self.heating_valve = devices.brennenstuhl_heatingvalve(mqtt_client, config.TOPIC_GFW_DIRK_HEATING_VALVE_ZIGBEE) self.KEY_POWERPLUG_BT = self.powerplug_common.KEY_OUTPUT_3
super().__init__(mqtt_client) # 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 # Functionality initialisation
# #
# Button - Brightness functionality # Button - Brightness functionality
self.brightness_functions = brightness_choose_n_action(self.button_tradfri) self.brightness_functions = brightness_choose_n_action(self.button_tradfri)
self.brightness_functions.add(self.main_light_tradfri, self.main_light_shelly, devices.shelly.KEY_OUTPUT_0) self.brightness_functions.add(self.main_light_tradfri, self.main_light_shelly, self.main_light_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.desk_light_tradfri, self.desk_light_tradfri, self.desk_light_tradfri.KEY_OUTPUT_0)
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 # Button - Main light
self.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_TOGGLE, self.button_tradfri.add_callback(self.button_tradfri.KEY_ACTION, self.button_tradfri.ACTION_TOGGLE,
self.main_light_shelly.toggle_output_0_mcb) self.main_light_shelly.toggle_output_0_mcb)
# Button - Desk light # Button - Desk light
self.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_RIGHT, self.button_tradfri.add_callback(self.button_tradfri.KEY_ACTION, self.button_tradfri.ACTION_RIGHT,
self.powerplug_common.toggle_output_1_mcb) self.desk_light_tradfri.toggle_output_0_mcb)
# Button - Amplifier # Button - Amplifier
self.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_LEFT_LONG, self.button_tradfri.add_callback(self.button_tradfri.KEY_ACTION, self.button_tradfri.ACTION_LEFT_LONG,
self.powerplug_common.toggle_output_0_mcb) self.powerplug_common.toggle_output_0_mcb)
# Button - CD player # Button - CD player
self.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_RIGHT_LONG, self.button_tradfri.add_callback(self.button_tradfri.KEY_ACTION, self.button_tradfri.ACTION_RIGHT_LONG,
self.powerplug_common.toggle_output_2_mcb) self.powerplug_common.toggle_output_2_mcb)
# Button - PC dock # Button - PC dock
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.powerplug_common.toggle_output_3_mcb) self.dock_tradfri.toggle_output_0_mcb)
# Mediaplayer - Amplifier auto on # Mediaplayer - Amplifier auto on
self.powerplug_common.add_callback(self.KEY_POWERPLUG_PHONO, None, self.powerplug_common.set_output_0_mcb, True)
self.powerplug_common.add_callback(self.KEY_POWERPLUG_CD_PLAYER, None, self.powerplug_common.set_output_0_mcb, True) self.powerplug_common.add_callback(self.KEY_POWERPLUG_CD_PLAYER, None, self.powerplug_common.set_output_0_mcb, True)
self.spotify_state.add_callback(devices.status.KEY_STATE, None, self.powerplug_common.set_output_0_mcb, True) self.powerplug_common.add_callback(self.KEY_POWERPLUG_BT, None, self.powerplug_common.set_output_0_mcb, True)
self.mpd_state.add_callback(devices.status.KEY_STATE, 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)
self.mpd_state.add_callback(self.mpd_state.KEY_STATE, None, self.powerplug_common.set_output_0_mcb, True)
self.bt_state.add_callback(self.bt_state.KEY_STATE, None, self.powerplug_common.set_output_0_mcb, True)
# Mediaplayer - Audio source selection # Mediaplayer - Audio source selection
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.spotify_state.add_callback(devices.status.KEY_STATE, True, self.audio_source_selector, True) self.powerplug_common.add_callback(self.KEY_POWERPLUG_BT, True, self.audio_source_selector, True)
self.mpd_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(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 # heating function
self.heating_function = heating_function(self.heating_valve) 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 # Virtual Device Interface
# #
self.main_light_videv = videv_switch_brightness_color_temp( self.main_light_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_GFW_DIRK_MAIN_LIGHT_VIDEV, mqtt_client, config.TOPIC_GFW_DIRK_MAIN_LIGHT_VIDEV,
self.main_light_shelly, devices.shelly.KEY_OUTPUT_0, self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0,
self.main_light_tradfri, devices.tradfri_light.KEY_BRIGHTNESS, self.main_light_tradfri, self.main_light_tradfri.KEY_BRIGHTNESS,
self.main_light_tradfri, devices.tradfri_light.KEY_COLOR_TEMP self.main_light_tradfri, self.main_light_tradfri.KEY_COLOR_TEMP
) )
self.desk_light_videv = videv_switch_brightness_color_temp( self.desk_light_videv = videv_switch_brightness_color_temp(
mqtt_client, config.TOPIC_GFW_DIRK_DESK_LIGHT_VIDEV, mqtt_client, config.TOPIC_GFW_DIRK_DESK_LIGHT_VIDEV,
self.powerplug_common, self.KEY_POWERPLUG_DESK_LIGHT, self.desk_light_tradfri, self.desk_light_tradfri.KEY_OUTPUT_0,
self.desk_light_tradfri, devices.tradfri_light.KEY_BRIGHTNESS, self.desk_light_tradfri, self.desk_light_tradfri.KEY_BRIGHTNESS,
self.desk_light_tradfri, devices.tradfri_light.KEY_COLOR_TEMP self.desk_light_tradfri, self.desk_light_tradfri.KEY_COLOR_TEMP
) )
self.amplifier_videv = videv_switching( self.amplifier_videv = videv_switching(
mqtt_client, config.TOPIC_GFW_DIRK_AMPLIFIER_VIDEV, mqtt_client, config.TOPIC_GFW_DIRK_AMPLIFIER_VIDEV,
self.powerplug_common, self.KEY_POWERPLUG_AMPLIFIER 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( self.cd_player_videv = videv_switching(
mqtt_client, config.TOPIC_GFW_DIRK_CD_PLAYER_VIDEV, mqtt_client, config.TOPIC_GFW_DIRK_CD_PLAYER_VIDEV,
self.powerplug_common, self.KEY_POWERPLUG_CD_PLAYER 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( self.pc_dock_videv = videv_switching(
mqtt_client, config.TOPIC_GFW_DIRK_PC_DOCK_VIDEV, mqtt_client, config.TOPIC_GFW_DIRK_PC_DOCK_VIDEV,
self.powerplug_common, self.KEY_POWERPLUG_PC_DOCK self.dock_tradfri, self.dock_tradfri.KEY_OUTPUT_0
) )
self.heating_function_videv = videv_heating( self.heating_function_videv = videv_heating(
mqtt_client, config.TOPIC_GFW_DIRK_HEATING_VALVE_VIDEV, mqtt_client, config.TOPIC_GFW_DIRK_HEATING_VALVE_VIDEV,
@ -189,7 +239,7 @@ class ground_floor_west_dirk(room):
) )
self.audio_player_videv = videv_audio_player( self.audio_player_videv = videv_audio_player(
mqtt_client, config.TOPIC_GFW_DIRK_AUDIO_PLAYER_VIDEV, mqtt_client, config.TOPIC_GFW_DIRK_AUDIO_PLAYER_VIDEV,
self.spotify_state, self.mpd_state self.spotify_state, self.mpd_state, self.bt_state
) )
# #
# Other stuff # Other stuff
@ -200,7 +250,10 @@ class ground_floor_west_dirk(room):
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 in [self.spotify_state, self.mpd_state]: elif device == self.powerplug_common and key == self.KEY_POWERPLUG_BT:
# 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:
@ -214,6 +267,9 @@ 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,9 +1,10 @@
#!/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 inspect import task
import time import time
@ -11,9 +12,104 @@ def now():
return time.mktime(time.localtime()) return time.mktime(time.localtime())
def sunrise_time(time_offs_min=30): def next_sunrise_time(time_offs_min=30):
return time.mktime(geo.sun.sunrise(config.GEO_POSITION)) + time_offs_min * 60 tm = now()
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 sunset_time(time_offs_min=-30): def next_sunset_time(time_offs_min=-30):
return time.mktime(geo.sun.sunset(config.GEO_POSITION)) + time_offs_min * 60 tm = now()
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

@ -14,10 +14,10 @@ Targets:
from base import common_base from base import common_base
import config import config
import devices import devices
from function.db import get_radiator_data, set_radiator_data from function.helpers import day_state
from function.helpers import now, sunset_time, sunrise_time
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
@ -28,7 +28,7 @@ logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
class switched_light(object): class switched_light(object):
def __init__(self, sw_device, sw_key, li_device): def __init__(self, sw_device, sw_key, li_device):
sw_device.add_callback(devices.shelly.KEY_OUTPUT_0, True, li_device.request_data, True) sw_device.add_callback(sw_device.KEY_OUTPUT_0, True, li_device.request_data, True)
class brightness_choose_n_action(common_base): class brightness_choose_n_action(common_base):
@ -153,19 +153,44 @@ class heating_function(common_base):
AWAY_REDUCTION = 5 AWAY_REDUCTION = 5
SUMMER_TEMPERATURE = 5 SUMMER_TEMPERATURE = 5
def __init__(self, heating_valve): class value_timeout_list(object):
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.heating_valve = heating_valve
self.default_temperature = config.DEFAULT_TEMPERATURE[heating_valve.topic] self.default_temperature = default_temperature
db_data = get_radiator_data(heating_valve.topic) #
self.valve_value = self.value_timeout_list()
#
super().__init__({ super().__init__({
self.KEY_USER_TEMPERATURE_SETPOINT: db_data[2] or self.default_temperature, self.KEY_USER_TEMPERATURE_SETPOINT: kwargs.get(self.KEY_USER_TEMPERATURE_SETPOINT, self.default_temperature),
self.KEY_TEMPERATURE_SETPOINT: db_data[3] or self.default_temperature, self.KEY_TEMPERATURE_SETPOINT: kwargs.get(self.KEY_TEMPERATURE_SETPOINT, self.default_temperature),
self.KEY_TEMPERATURE_CURRENT: None, self.KEY_TEMPERATURE_CURRENT: kwargs.get(self.KEY_TEMPERATURE_CURRENT, None),
self.KEY_AWAY_MODE: db_data[0] or False, self.KEY_AWAY_MODE: kwargs.get(self.KEY_AWAY_MODE, False),
self.KEY_SUMMER_MODE: db_data[1] or False, self.KEY_SUMMER_MODE: kwargs.get(self.KEY_SUMMER_MODE, False),
self.KEY_START_BOOST: True, self.KEY_START_BOOST: kwargs.get(self.KEY_START_BOOST, True),
self.KEY_SET_DEFAULT_TEMPERATURE: False, self.KEY_SET_DEFAULT_TEMPERATURE: kwargs.get(self.KEY_SET_DEFAULT_TEMPERATURE, False),
self.KEY_BOOST_TIMER: 0 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.set_heating_setpoint(self[self.KEY_TEMPERATURE_SETPOINT])
@ -199,11 +224,8 @@ class heating_function(common_base):
def cancel_boost(self): def cancel_boost(self):
self.set(self.KEY_BOOST_TIMER, 0, block_callback=[self.timer_expired]) self.set(self.KEY_BOOST_TIMER, 0, block_callback=[self.timer_expired])
def set(self, key, data, block_callback=[]): def send_command(self, key, data, block_callback=[]):
rv = super().set(key, data, block_callback) return super().set(key, data, block_callback)
set_radiator_data(self.heating_valve.topic, self[self.KEY_AWAY_MODE], self[self.KEY_SUMMER_MODE],
self[self.KEY_USER_TEMPERATURE_SETPOINT], self[self.KEY_TEMPERATURE_SETPOINT])
return rv
def away_mode(self, device, key, value): def away_mode(self, device, key, value):
if value is True: if value is True:
@ -245,11 +267,13 @@ class heating_function(common_base):
self.set(self.KEY_TEMPERATURE_SETPOINT, data) 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.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[self.KEY_BOOST_TIMER] == 0 and not self[self.KEY_AWAY_MODE] and not self[self.KEY_SUMMER_MODE]: if self.valve_value.is_valid_value(data):
self.set(self.KEY_USER_TEMPERATURE_SETPOINT, data, block_callback=[self.set_heating_setpoint]) if self[self.KEY_BOOST_TIMER] == 0 and not self[self.KEY_AWAY_MODE] and not self[self.KEY_SUMMER_MODE]:
self.set(self.KEY_USER_TEMPERATURE_SETPOINT, data, block_callback=[self.set_heating_setpoint])
def get_radiator_temperature(self, device, key, data): def get_radiator_temperature(self, device, key, data):
self.set(self.KEY_TEMPERATURE_CURRENT, data) self.set(self.KEY_TEMPERATURE_CURRENT, data)
@ -279,8 +303,8 @@ class motion_sensor_light(common_base):
self.motion_sensors = args self.motion_sensors = args
self.timer_reload_value = timer_value self.timer_reload_value = timer_value
# #
sw_device.add_callback(devices.shelly.KEY_OUTPUT_0, True, self.reload_timer, True) sw_device.add_callback(devices.shelly_sw1.KEY_OUTPUT_0, True, self.reload_timer, True)
sw_device.add_callback(devices.shelly.KEY_OUTPUT_0, False, self.reset_timer, True) sw_device.add_callback(devices.shelly_sw1.KEY_OUTPUT_0, False, self.reset_timer, True)
for motion_sensor in args: for motion_sensor in args:
motion_sensor.add_callback(motion_sensor.KEY_OCCUPANCY, None, self.set_motion_detected, True) motion_sensor.add_callback(motion_sensor.KEY_OCCUPANCY, None, self.set_motion_detected, True)
# #
@ -300,7 +324,8 @@ class motion_sensor_light(common_base):
if arg_device.topic == device.topic: if arg_device.topic == device.topic:
break break
self.set(self.KEY_MOTION_SENSOR % sensor_index, data) self.set(self.KEY_MOTION_SENSOR % sensor_index, data)
if now() < sunrise_time(60) or now() > sunset_time(-60): # 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: if data is True:
logger.info("%s: Motion detected - Switching on main light %s", device.topic, self.sw_device.topic) logger.info("%s: Motion detected - Switching on main light %s", device.topic, self.sw_device.topic)
self.sw_method(True) self.sw_method(True)

View File

@ -13,24 +13,34 @@ logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
class room(object): class room(object):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
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): def all_off(self, device=None, key=None, data=None):
logger.info("Switching all off \"%s\"", type(self).__name__) logger.info("Switching all off \"%s\"", type(self).__name__)
for name, obj in inspect.getmembers(self): for name, obj in inspect.getmembers(self):
try: try:
if obj.__module__ == 'devices': if obj.__module__.startswith('devices'):
obj.all_off() obj.all_off()
except AttributeError: except AttributeError:
pass # not a module or has no method all_off 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_collection(object):
ALLOWED_CLASSES = ("room", "room_collection") ALLOWED_CLASSES = ("room", "room_collection")
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
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): def all_off(self, device=None, key=None, data=None):
logger.info("Switching all off \"%s\"", type(self).__name__) logger.info("Switching all off \"%s\"", type(self).__name__)
@ -40,3 +50,27 @@ class room_collection(object):
sub = getattr(self, sub_name) sub = getattr(self, sub_name)
if sub.__class__.__bases__[0].__name__ in self.ALLOWED_CLASSES: if sub.__class__.__bases__[0].__name__ in self.ALLOWED_CLASSES:
sub.all_off() 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 = []
for name, obj in inspect.getmembers(target):
if not callable(obj): # sort out methods
try:
if obj.__module__.startswith('function.') and not obj.__module__.endswith('.videv'):
devices.extend(self.all_devices(obj, depth+1)) # rekurse in function instances
elif obj.__module__ == "devices":
devices.append(obj)
except AttributeError:
pass # sort out non modules
return devices

View File

@ -3,7 +3,7 @@
# #
import config import config
import devices from devdi import topic as props
import logging import logging
from function.modules import motion_sensor_light from function.modules import motion_sensor_light
from function.rooms import room, room_collection from function.rooms import room, room_collection
@ -15,23 +15,25 @@ 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_collection): class stairway(room_collection):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
super().__init__(mqtt_client) super().__init__(mqtt_client, pd, vd)
self.stairway = stairway_stairway(mqtt_client) self.stairway = stairway_stairway(mqtt_client, pd, vd)
class stairway_stairway(room): class stairway_stairway(room):
def __init__(self, mqtt_client): def __init__(self, mqtt_client, pd, vd):
# #
# Device initialisation # Device initialisation
# #
# http://shelly1-3494546A9364 # http://shelly1-3494546A9364
self.main_light_shelly = devices.shelly(mqtt_client, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_SHELLY) self.main_light_shelly = pd.get(props.STG_SHE, loc, props.ROO_STF, props.FUN_MAL)
self.motion_sensor_gf = devices.silvercrest_motion_sensor(mqtt_client, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_MOTION_SENSOR_GF) self.motion_sensor_ff = pd.get(props.STG_ZFE, loc, props.ROO_STF, props.FUN_MSE)
self.motion_sensor_ff = devices.silvercrest_motion_sensor(mqtt_client, 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)
super().__init__(mqtt_client) super().__init__(mqtt_client, pd, vd)
# #
# Functionality initialisation # Functionality initialisation
@ -47,6 +49,6 @@ class stairway_stairway(room):
# #
self.main_light_videv = videv_switching_motion( self.main_light_videv = videv_switching_motion(
mqtt_client, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_VIDEV, mqtt_client, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_VIDEV,
self.main_light_shelly, devices.shelly.KEY_OUTPUT_0, self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0,
self.motion_sensor_light self.motion_sensor_light
) )

View File

@ -10,9 +10,8 @@ Targets:
* No functionality should be implemented here * No functionality should be implemented here
""" """
from base import mqtt_base from base import videv_base
from function.rooms import room, room_collection from function.rooms import room, room_collection
import json
import time import time
try: try:
@ -21,115 +20,29 @@ except ImportError:
ROOT_LOGGER_NAME = 'root' ROOT_LOGGER_NAME = 'root'
class base(mqtt_base): class videv_pure_switch(videv_base):
KEY_INFO = '__info__' KEY_STATE = 'state'
def __init__(self, mqtt_client, topic, default_values=None): def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic, default_values=default_values) super().__init__(mqtt_client, topic)
self.__display_dict__ = {} self[self.KEY_STATE] = False
self.__control_dict__ = {} #
self.__capabilities__ = None self.mqtt_client.add_callback(self.topic + '/state/set', self.__state__)
self.__active_tx__ = {}
def add_display(self, my_key, ext_device, ext_key, on_change_only=True): def __state__(self, mqtt_client, userdata, message):
""" self.set(self.KEY_STATE, message.payload == b'true')
listen to data changes of ext_device and update videv information self.__tx__(self.KEY_STATE, message.payload == b'true')
"""
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)
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)
# send default data to videv interface
def __rx_ext_device_data__(self, ext_device, ext_key, data):
my_key = self.__display_dict__[(id(ext_device), ext_key)]
self[my_key] = data
self.__tx__(my_key, data)
def __tx__(self, key, data):
if key in self.__control_dict__:
self.__active_tx__[key] = (time.time(), data)
if type(data) not in (str, ):
data = json.dumps(data)
self.mqtt_client.send(self.topic + '/' + key, data)
self.__tx_capabilities__()
def __tx_capabilities__(self):
self.mqtt_client.send(self.topic + '/' + self.KEY_INFO, json.dumps(self.capabilities))
def add_control(self, my_key, ext_device, ext_key, on_change_only=True):
"""
listen to videv information and pass data to ext_device
"""
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(self.topic + '/' + my_key, self.__rx_videv_data__)
def __rx_videv_data__(self, client, userdata, message):
my_key = message.topic.split('/')[-1]
try:
data = json.loads(message.payload)
except json.decoder.JSONDecodeError:
data = message.payload
if my_key in self.__active_tx__:
tm, tx_data = self.__active_tx__.pop(my_key)
do_ex = data != tx_data and time.time() - tm < 2
else:
do_ex = True
if do_ex:
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.set(ext_key, data)
self.set(my_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)
@property
def capabilities(self):
if self.__capabilities__ is None:
self.__capabilities__ = {}
self.__capabilities__['__type__'] = self.__class__.__name__
for key in self.__control_dict__:
if not key in self.__capabilities__:
self.__capabilities__[key] = {}
self.__capabilities__[key]['control'] = True
for key in self.__display_dict__.values():
if not key in self.__capabilities__:
self.__capabilities__[key] = {}
self.__capabilities__[key]['display'] = True
return self.__capabilities__
class videv_switching(base): class videv_switching(videv_base):
KEY_STATE = 'state' KEY_STATE = 'state'
def __init__(self, mqtt_client, topic, sw_device, sw_key): def __init__(self, mqtt_client, topic, sw_device, sw_key):
super().__init__(mqtt_client, topic) super().__init__(mqtt_client, topic)
self.add_routing(self.KEY_STATE, sw_device, sw_key) self.add_routing(self.KEY_STATE, sw_device, sw_key)
#
self.__tx_capabilities__()
class videv_switching_timer(base): class videv_switching_timer(videv_base):
KEY_STATE = 'state' KEY_STATE = 'state'
KEY_TIMER = 'timer' KEY_TIMER = 'timer'
@ -137,11 +50,9 @@ class videv_switching_timer(base):
super().__init__(mqtt_client, topic) super().__init__(mqtt_client, topic)
self.add_routing(self.KEY_STATE, sw_device, sw_key) self.add_routing(self.KEY_STATE, sw_device, sw_key)
self.add_display(self.KEY_TIMER, tm_device, tm_key) self.add_display(self.KEY_TIMER, tm_device, tm_key)
#
self.__tx_capabilities__()
class videv_switching_motion(base): class videv_switching_motion(videv_base):
KEY_STATE = 'state' KEY_STATE = 'state'
# #
KEY_TIMER = 'timer' KEY_TIMER = 'timer'
@ -156,11 +67,9 @@ class videv_switching_motion(base):
# motion sensor state # motion sensor state
for index, motion_sensor in enumerate(self.motion_sensors): for index, motion_sensor in enumerate(self.motion_sensors):
self.add_display(self.KEY_MOTION_SENSOR % index, motion_sensor, motion_sensor.KEY_OCCUPANCY) self.add_display(self.KEY_MOTION_SENSOR % index, motion_sensor, motion_sensor.KEY_OCCUPANCY)
#
self.__tx_capabilities__()
class videv_switch_brightness(base): class videv_switch_brightness(videv_base):
KEY_STATE = 'state' KEY_STATE = 'state'
KEY_BRIGHTNESS = 'brightness' KEY_BRIGHTNESS = 'brightness'
@ -168,11 +77,9 @@ class videv_switch_brightness(base):
super().__init__(mqtt_client, topic) super().__init__(mqtt_client, topic)
self.add_routing(self.KEY_STATE, sw_device, sw_key) 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_BRIGHTNESS, br_device, br_key)
#
self.__tx_capabilities__()
class videv_switch_brightness_color_temp(base): class videv_switch_brightness_color_temp(videv_base):
KEY_STATE = 'state' KEY_STATE = 'state'
KEY_BRIGHTNESS = 'brightness' KEY_BRIGHTNESS = 'brightness'
KEY_COLOR_TEMP = 'color_temp' KEY_COLOR_TEMP = 'color_temp'
@ -182,11 +89,9 @@ class videv_switch_brightness_color_temp(base):
self.add_routing(self.KEY_STATE, sw_device, sw_key) 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_BRIGHTNESS, br_device, br_key)
self.add_routing(self.KEY_COLOR_TEMP, ct_device, ct_key) self.add_routing(self.KEY_COLOR_TEMP, ct_device, ct_key)
#
self.__tx_capabilities__()
class videv_heating(base): class videv_heating(videv_base):
KEY_USER_TEMPERATURE_SETPOINT = 'user_temperature_setpoint' KEY_USER_TEMPERATURE_SETPOINT = 'user_temperature_setpoint'
KEY_VALVE_TEMPERATURE_SETPOINT = 'valve_temperature_setpoint' KEY_VALVE_TEMPERATURE_SETPOINT = 'valve_temperature_setpoint'
KEY_AWAY_MODE = 'away_mode' KEY_AWAY_MODE = 'away_mode'
@ -210,11 +115,9 @@ class videv_heating(base):
self.add_display(self.KEY_VALVE_TEMPERATURE_SETPOINT, heating_function, heating_function.KEY_TEMPERATURE_SETPOINT) 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_BOOST_TIMER, heating_function, heating_function.KEY_BOOST_TIMER)
self.add_display(self.KEY_TEMPERATURE, heating_function, heating_function.KEY_TEMPERATURE_CURRENT, False) self.add_display(self.KEY_TEMPERATURE, heating_function, heating_function.KEY_TEMPERATURE_CURRENT, False)
#
self.__tx_capabilities__()
class videv_multistate(base): class videv_multistate(videv_base):
KEY_STATE = 'state_%d' KEY_STATE = 'state_%d'
def __init__(self, mqtt_client, topic, key_for_device, device, num_states, default_values=None): def __init__(self, mqtt_client, topic, key_for_device, device, num_states, default_values=None):
@ -225,17 +128,13 @@ class videv_multistate(base):
self.__tx__(self.KEY_STATE % i, False) self.__tx__(self.KEY_STATE % i, False)
# #
device.add_callback(key_for_device, None, self.__index_rx__, True) device.add_callback(key_for_device, None, self.__index_rx__, True)
#
self.__tx_capabilities__()
def __index_rx__(self, device, key, data): def __index_rx__(self, device, key, data):
for i in range(0, self.num_states): for i in range(0, self.num_states):
self.__tx__(self.KEY_STATE % i, i == data) self.__tx__(self.KEY_STATE % i, i == data)
#
self.__tx_capabilities__()
class videv_audio_player(base): class videv_audio_player(videv_base):
KEY_ACTIVE_PLAYER = 'player_%d' KEY_ACTIVE_PLAYER = 'player_%d'
KEY_TITLE = 'title' KEY_TITLE = 'title'
NO_TITLE = '---' NO_TITLE = '---'
@ -247,20 +146,12 @@ class videv_audio_player(base):
# #
for audio_device in args: for audio_device in args:
audio_device.add_callback(audio_device.KEY_TITLE, None, self.__title_rx__, True) audio_device.add_callback(audio_device.KEY_TITLE, None, self.__title_rx__, True)
#
self.__tx_capabilities__()
def __title_rx__(self, device, key, data): def __title_rx__(self, device, key, data):
self.__tx__(self.KEY_TITLE, data or self.NO_TITLE) self.__tx__(self.KEY_TITLE, data or self.NO_TITLE)
@property
def capabilities(self):
super().capabilities
self.__capabilities__[self.KEY_TITLE] = {'display': True}
return self.__capabilities__
class all_off(videv_base):
class all_off(base):
ALLOWED_CLASSES = (room, room_collection, ) ALLOWED_CLASSES = (room, room_collection, )
def __init__(self, mqtt_client, topic, room_collection): def __init__(self, mqtt_client, topic, room_collection):
@ -272,8 +163,6 @@ class all_off(base):
# register mqtt callbacks for all my keys # register mqtt callbacks for all my keys
for key in self.__inst_dict__: for key in self.__inst_dict__:
mqtt_client.add_callback(topic + "/" + key, self.all_off) mqtt_client.add_callback(topic + "/" + key, self.all_off)
#
self.__tx_capabilities__()
def __check_inst_capabilities__(self, name, inst): def __check_inst_capabilities__(self, name, inst):
# fits to specified classes # fits to specified classes
@ -304,14 +193,3 @@ class all_off(base):
def all_off(self, client, userdata, message): def all_off(self, client, userdata, message):
key = message.topic[len(self.topic) + 1:] key = message.topic[len(self.topic) + 1:]
self.__inst_dict__[key].all_off() self.__inst_dict__[key].all_off()
self.__tx_capabilities__()
@property
def capabilities(self):
if self.__capabilities__ is None:
self.__capabilities__ = {}
self.__capabilities__['__type__'] = self.__class__.__name__
for key in self.__inst_dict__:
self.__capabilities__[key] = {}
self.__capabilities__[key]['control'] = True
return self.__capabilities__

24
init_venv Executable file
View File

@ -0,0 +1,24 @@
#!/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 1adfb0626e7777c6d29be65d4ad4ce2d57541301 Subproject commit 14e56ccdbf6594f699b4afcfb4acafe9b899e914

2
report

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

View File

@ -1,28 +1,74 @@
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)
# TODO: Restructure nodered gui (own heating page - with circulation pump)
# TODO: Rework devices to base.mqtt (pack -> set, ...) VERS_MAJOR = 1
# TODO: Implement handling of warnings (videv element to show in webapp?) VERS_MINOR = 3
# TODO: implement garland (incl. day events like sunset, sunrise, ...) 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, None, ((config.APP_NAME, logging.DEBUG), ), fmt=report.SHORT_FMT, host='localhost', port=19996) report.appLoggingConfigure(None, 'stdout', ((config.APP_NAME, logging.DEBUG), ),
target_level=logging.WARNING, fmt=report.SHORT_FMT, host='localhost', port=19996)
else: else:
report.stdoutLoggingConfigure(((config.APP_NAME, logging.INFO), ), report.SHORT_FMT) report.stdoutLoggingConfigure(((config.APP_NAME, config.LOGLEVEL), ), 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)
#
# Smarthome physical Devices
#
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)

87
topics.py Normal file
View File

@ -0,0 +1,87 @@
#
# 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"