diff --git a/devices/__init__.py b/devices/__init__.py index c06da9d..40ba95f 100644 --- a/devices/__init__.py +++ b/devices/__init__.py @@ -107,7 +107,7 @@ class base(dict): def unpack(self, message): content_key = message.topic[len(self.topic) + 1:] - if content_key not in self.RX_IGNORE_TOPICS: + if content_key not in self.RX_IGNORE_TOPICS and (not message.topic.endswith(self.TX_TOPIC) or len(self.TX_TOPIC) == 0): logger.debug("Unpacking content_key \"%s\" from message.", content_key) if is_json(message.payload): data = json.loads(message.payload) @@ -204,8 +204,7 @@ class shelly(base): RX_KEYS = [KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_INPUT_0, KEY_INPUT_1, KEY_LONGPUSH_0, KEY_LONGPUSH_1, KEY_OVERTEMPERATURE, KEY_TEMPERATURE] - RX_IGNORE_TOPICS = [KEY_OUTPUT_0 + '/' + TX_TOPIC, KEY_OUTPUT_1 + '/' + TX_TOPIC, - KEY_OUTPUT_0 + '/' + "energy", KEY_OUTPUT_1 + '/' + "energy", + 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, @@ -286,8 +285,6 @@ class silvercrest_powerplug(base): TX_FILTER_DATA_KEYS = [KEY_OUTPUT_0] # RX_KEYS = [KEY_LINKQUALITY, KEY_OUTPUT_0] - RX_IGNORE_TOPICS = [TX_TOPIC] - RX_IGNORE_KEYS = [] RX_FILTER_DATA_KEYS = [KEY_OUTPUT_0] def __init__(self, mqtt_client, topic): @@ -325,10 +322,6 @@ class my_powerplug(base): TX_TYPE = base.TX_VALUE # RX_KEYS = [KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_OUTPUT_2, KEY_OUTPUT_3] - RX_IGNORE_TOPICS = [KEY_OUTPUT_0 + "/" + TX_TOPIC, KEY_OUTPUT_1 + "/" + TX_TOPIC, - KEY_OUTPUT_2 + "/" + TX_TOPIC, KEY_OUTPUT_3 + "/" + TX_TOPIC] - RX_IGNORE_KEYS = [] - RX_FILTER_DATA_KEYS = [] def __init__(self, mqtt_client, topic): super().__init__(mqtt_client, topic) @@ -392,7 +385,6 @@ class tradfri_light(base): 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_TOPICS = [TX_TOPIC] RX_IGNORE_KEYS = ['update', 'color_mode'] RX_FILTER_DATA_KEYS = [KEY_OUTPUT_0, KEY_BRIGHTNESS, KEY_COLOR_TEMP] @@ -484,9 +476,7 @@ class tradfri_button(base): KEY_ACTION_DURATION = "action_duration" # RX_KEYS = [KEY_LINKQUALITY, KEY_BATTERY, KEY_ACTION] - RX_IGNORE_TOPICS = [] RX_IGNORE_KEYS = ['update', KEY_ACTION_DURATION] - RX_FILTER_DATA_KEYS = [] def __init__(self, mqtt_client, topic): super().__init__(mqtt_client, topic) @@ -521,11 +511,8 @@ class nodered_gui(base): # TX_TOPIC = 'set' TX_TYPE = base.TX_VALUE - TX_FILTER_DATA_KEYS = [] # RX_KEYS = [KEY_STATE, KEY_BRIGHTNESS, KEY_COLOR_TEMP, KEY_HEATING_BOOST, KEY_HEATING_SETPOINT, KEY_OFF_BUTTON] - RX_IGNORE_TOPICS = [KEY_FEEDBACK + '/' + TX_TOPIC, KEY_ENABLE + '/' + TX_TOPIC] - RX_FILTER_DATA_KEYS = [] def __init__(self, mqtt_client, topic): super().__init__(mqtt_client, topic) @@ -589,13 +576,10 @@ class brennenstuhl_heatingvalve(base): # TX_TOPIC = 'set' TX_TYPE = base.TX_DICT - TX_FILTER_DATA_KEYS = [] # RX_KEYS = [KEY_LINKQUALITY, KEY_BATTERY, KEY_HEATING_SETPOINT, KEY_TEMPERATURE] - RX_IGNORE_TOPICS = [TX_TOPIC] RX_IGNORE_KEYS = [KEY_AWAY_MODE, KEY_CHILD_LOCK, KEY_PRESET, KEY_SYSTEM_MODE, KEY_VALVE_DETECTION, KEY_WINDOW_DETECTION] - RX_FILTER_DATA_KEYS = [] def __init__(self, mqtt_client, topic): super().__init__(mqtt_client, topic) @@ -641,26 +625,22 @@ class remote(base): # TX_TOPIC = '' TX_TYPE = base.TX_VALUE - TX_FILTER_DATA_KEYS = [] + # + RX_IGNORE_TOPICS = [KEY_CD, KEY_LINE1, KEY_LINE3, KEY_MUTE, KEY_POWER, KEY_VOLUP, KEY_VOLDOWN] def set_cd(self): - """data: [True, False]""" self.pack(self.KEY_CD, None) def set_line1(self): - """data: [True, False]""" self.pack(self.KEY_LINE1, None) def set_line3(self): - """data: [True, False]""" self.pack(self.KEY_LINE3, None) def set_mute(self): - """data: [True, False]""" self.pack(self.KEY_MUTE, None) def set_power(self): - """data: [True, False]""" self.pack(self.KEY_POWER, None) def set_volume_up(self, data=False): @@ -684,10 +664,17 @@ class remote(base): class status(base): KEY_STATE = "state" # - TX_TOPIC = '' + TX_TOPIC = 'set' TX_TYPE = base.TX_VALUE - TX_FILTER_DATA_KEYS = [] + # + RX_KEYS = [KEY_STATE] def set_state(self, num, data): """data: [True, False]""" self.pack(self.KEY_STATE + "/" + str(num), data) + + +class audio_status(status): + KEY_TITLE = "title" + # + RX_KEYS = [status.KEY_STATE, KEY_TITLE] diff --git a/function/__init__.py b/function/__init__.py index 09bdd3c..688e30b 100644 --- a/function/__init__.py +++ b/function/__init__.py @@ -15,7 +15,6 @@ except ImportError: ROOT_LOGGER_NAME = 'root' logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__) -# TODO: implement existing nodered functionality "dirk" (ground floor west) # TODO: implement garland (incl. day events like sunset, sunrise, ...) # TODO: implement warning message diff --git a/function/common.py b/function/common.py index 3c210e0..a50b942 100644 --- a/function/common.py +++ b/function/common.py @@ -5,6 +5,7 @@ import devices from function.rooms import room_shelly from function.modules import heating_function_brennenstuhl +from function.helpers import changed_value_indicator import logging import task @@ -25,20 +26,19 @@ class common_circulation_pump(room_shelly): self.gui_timer_view = devices.nodered_gui(mqtt_client, "gui/ffe_circ_pump_timer") self.gui_timer_view.set_feedback('-') # - self.last_pump_value = None + self.cvi = changed_value_indicator() self.ct = task.periodic(6, self.cyclic_task) self.pump_timer = None # self.ct.run() def circ_pump_actions(self, device, key, data): - if self.last_pump_value != data and data is True: + if self.cvi.changed_here(device.topic, key, data) and data is True: self.pump_timer = 10 self.gui_timer_view.set_feedback(self.pump_timer) elif data is False: self.pump_timer = None self.gui_timer_view.set_feedback('-') - self.last_pump_value = data def cyclic_task(self, rt): if self.pump_timer is not None: diff --git a/function/first_floor_east.py b/function/first_floor_east.py index 465da9a..149b6e7 100644 --- a/function/first_floor_east.py +++ b/function/first_floor_east.py @@ -7,7 +7,7 @@ import devices import json import logging from function.rooms import room_shelly, room_shelly_tradfri_light - +from function.helpers import changed_value_indicator try: from config import APP_NAME as ROOT_LOGGER_NAME except ImportError: @@ -45,17 +45,16 @@ class first_floor_east_dining(room_shelly): self.floorlamp_powerplug.add_callback(devices.silvercrest_powerplug.KEY_OUTPUT_0, None, self.gui_switch_feedback_floorlamp) # - self.main_light_shelly_last = None + self.cvi = changed_value_indicator() def all_off(self, device=None, key=None, data=None): super().all_off(device, key, data) self.floorlamp_powerplug.set_output_0(False) def floorlamp_synchronisation(self, device, key, data): - if data != self.main_light_shelly_last: + if self.cvi.changed_here(device.topic, key, data): logger.info("Synching \"%s\" floorlamp with main light (%s)", type(self).__name__, str(data)) self.floorlamp_powerplug.set_output_0(data) - self.main_light_shelly_last = data def gui_switch_command_floorlamp(self, device, key, data): logger.info("Switching \"%s\" floorlamp: %s", type(self).__name__, str(data)) @@ -205,7 +204,7 @@ class first_floor_east_living(room_shelly_tradfri_light): self.gui_color_temp_floorlamp.add_callback( devices.nodered_gui.KEY_COLOR_TEMP, None, self.set_color_temp_floorlamp) # - self.main_light_shelly_last = None + self.cvi = changed_value_indicator() def all_off(self, device=None, key=None, data=None): super().all_off(device, key, data) @@ -219,11 +218,10 @@ class first_floor_east_living(room_shelly_tradfri_light): return rv def floorlamp_synchronisation(self, device, key, data): - if data != self.main_light_shelly_last: + if self.cvi.changed_here(device.topic, key, data): logger.info("Synching \"%s\" floorlamp with main light (%s)", type(self).__name__, str(data)) for device in self.__floorlamp_devices__(): device.set_output_0(data) - self.main_light_shelly_last = data def gui_switch_command_floorlamp(self, device, key, data): logger.info("Switching \"%s\" floorlamp: %s", type(self).__name__, str(data)) diff --git a/function/ground_floor_west.py b/function/ground_floor_west.py index 3cfb4c1..aca6c2d 100644 --- a/function/ground_floor_west.py +++ b/function/ground_floor_west.py @@ -5,6 +5,8 @@ import devices import logging from function.rooms import room_shelly, room_shelly_tradfri_light, room_shelly_silvercrest_light +from function.helpers import changed_value_indicator +import task try: from config import APP_NAME as ROOT_LOGGER_NAME @@ -49,6 +51,18 @@ class ground_floor_west_dirk(room_shelly_tradfri_light): STATE_ACTIVE_DEVICE_DESK_LIGHT = 1 STATE_ACTIVE_DEVICE_AMPLIFIER = 2 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_CD = 1 + AUDIO_SOURCE_RASPI = 2 + # + KEY_SPOTIFY = "hifi/spotify/state" + KEY_MPD = "hifi/mpd/state" # https://shelly1l-3C6105E44F27 def __init__(self, mqtt_client): @@ -59,10 +73,6 @@ class ground_floor_west_dirk(room_shelly_tradfri_light): self.desk_light_tradfri = devices.tradfri_light(mqtt_client, "zigbee_eg_w/light/dirk_desk") self.button_tradfri = devices.tradfri_button(mqtt_client, "zigbee_eg_w/input_device/dirk") # - self.powerplug_common.add_callback(None, None, self.powerplug_gui_feedback_actions) - self.powerplug_common.add_callback(devices.my_powerplug.KEY_OUTPUT_2, None, - self.powerplug_amplifier_synchronisation) - # self.gui_switch_desk_light = devices.nodered_gui(mqtt_client, "gui/gfw_sw_desk_light") self.gui_brightness_desk_light = devices.nodered_gui(mqtt_client, "gui/gfw_br_desk_light") self.gui_brightness_desk_light.enable(False) @@ -76,7 +86,19 @@ class ground_floor_west_dirk(room_shelly_tradfri_light): self.gui_switch_pc_dock = devices.nodered_gui(mqtt_client, "gui/gfw_sw_pc_dock") # self.remote_amplifier = devices.remote(mqtt_client, "hifi/remote/RAS5") - self.active_device_state_led = devices.status(mqtt_client, "gui/gfw_avtive_device_state") + self.active_device_state_led = devices.status(mqtt_client, "gui/gfw_active_device_state") + # + self.spotify_state = devices.audio_status(mqtt_client, "hifi/spotify") + self.mpd_state = devices.audio_status(mqtt_client, "hifi/mpd") + # + self.delayed_task = task.delayed(1.0, self.send_audio_source) + # + self.cvi = changed_value_indicator() + # + self.powerplug_common.add_callback(None, None, self.powerplug_gui_feedback_actions) + self.powerplug_common.add_callback(self.KEY_POWERPLUG_CD_PLAYER, None, self.cd_amplifier_synchronisation) + self.spotify_state.add_callback(devices.status.KEY_STATE, None, self.raspi_amplifier_synchronisation) + self.mpd_state.add_callback(devices.status.KEY_STATE, None, self.raspi_amplifier_synchronisation) # self.button_tradfri.add_callback(devices.tradfri_button.KEY_ACTION, devices.tradfri_button.ACTION_TOGGLE, self.toggle_main_light) @@ -107,20 +129,23 @@ class ground_floor_west_dirk(room_shelly_tradfri_light): self.main_light_shelly.add_callback(devices.shelly.KEY_OUTPUT_0, None, self.device_chooser_action) self.powerplug_common.add_callback(None, None, self.device_chooser_action) # - self.last_cd_player_powerplug_state = None - self.last_main_light_state = None - self.last_desk_light_state = None - self.last_amplifier_state = None + self.powerplug_common.add_callback(self.KEY_POWERPLUG_AMPLIFIER, None, self.audio_source_selector) + self.powerplug_common.add_callback(self.KEY_POWERPLUG_CD_PLAYER, None, self.audio_source_selector) + self.spotify_state.add_callback(devices.status.KEY_STATE, None, self.audio_source_selector) + self.mpd_state.add_callback(devices.status.KEY_STATE, None, self.audio_source_selector) + # self.active_device_state = None + # + self.audio_source = self.AUDIO_SOURCE_PC def all_off(self, device=None, key=None, data=None): super().all_off(device, key, data) self.powerplug_common.set_output_all(False) def powerplug_gui_feedback_actions(self, device, key, data): - if key == devices.my_powerplug.KEY_OUTPUT_0: + if key == self.KEY_POWERPLUG_AMPLIFIER: self.gui_switch_amplifier.set_feedback(data) - elif key == devices.my_powerplug.KEY_OUTPUT_1: + elif key == self.KEY_POWERPLUG_DESK_LIGHT: self.gui_switch_desk_light.set_feedback(data) self.gui_brightness_desk_light.enable(data) self.gui_color_temp_desk_light.enable(data) @@ -130,17 +155,21 @@ class ground_floor_west_dirk(room_shelly_tradfri_light): else: self.gui_brightness_desk_light.set_feedback(self.desk_light_tradfri.brightness) self.gui_color_temp_desk_light.set_feedback(self.desk_light_tradfri.color_temp / 10) - elif key == devices.my_powerplug.KEY_OUTPUT_2: + elif key == self.KEY_POWERPLUG_CD_PLAYER: self.gui_switch_cd_player.set_feedback(data) - elif key == devices.my_powerplug.KEY_OUTPUT_3: + elif key == self.KEY_POWERPLUG_PC_DOCK: self.gui_switch_pc_dock.set_feedback(data) - def powerplug_amplifier_synchronisation(self, device, key, data): + def cd_amplifier_synchronisation(self, device, key, data): if device == self.powerplug_common: - if data != self.last_cd_player_powerplug_state: + if self.cvi.changed_here(device.topic, key, data): logger.info("Setting \"%s\" amplifier: %s", type(self).__name__, data) self.powerplug_common.set_output_0(data) - self.last_cd_player_powerplug_state = data + + def raspi_amplifier_synchronisation(self, device, key, data): + if self.cvi.changed_here(device.topic, key, data): + logger.info("Setting \"%s\" amplifier: %s", type(self).__name__, data) + self.powerplug_common.set_output_0(data) def desk_light_switch_action(self, device, key, data): if device == self.button_tradfri: @@ -190,26 +219,23 @@ class ground_floor_west_dirk(room_shelly_tradfri_light): def device_chooser_action(self, device, key, data): if device == self.main_light_shelly: - if self.last_main_light_state != data: + if self.cvi.changed_here(device.topic, key, data): if data is True: self.active_device_state = self.STATE_ACTIVE_DEVICE_MAIN_LIGHT else: self.choose_next_device() - self.last_main_light_state = data - elif device == self.powerplug_common and key == devices.my_powerplug.KEY_OUTPUT_1: - if self.last_desk_light_state != data: + elif device == self.powerplug_common and key == self.KEY_POWERPLUG_DESK_LIGHT: + if self.cvi.changed_here(device.topic, key, data): if data is True: self.active_device_state = self.STATE_ACTIVE_DEVICE_DESK_LIGHT else: self.choose_next_device() - self.last_desk_light_state = data - elif device == self.powerplug_common and key == devices.my_powerplug.KEY_OUTPUT_0: - if self.last_amplifier_state != data: + elif device == self.powerplug_common and key == self.KEY_POWERPLUG_AMPLIFIER: + if self.cvi.changed_here(device.topic, key, data): if data is True: self.active_device_state = self.STATE_ACTIVE_DEVICE_AMPLIFIER else: self.choose_next_device() - self.last_amplifier_state = data for num in range(0, self.STATE_ACTIVE_DEVICE_MAX_VALUE + 1): self.active_device_state_led.set_state(num, self.active_device_state == num) @@ -260,3 +286,29 @@ class ground_floor_west_dirk(room_shelly_tradfri_light): target.default_dec() elif data in [devices.tradfri_button.ACTION_BRIGHTNESS_UP_RELEASE, devices.tradfri_button.ACTION_BRIGHTNESS_DOWN_RELEASE]: target.default_stop() + + def audio_source_selector(self, device, key, data): + if device == self.powerplug_common and key == self.KEY_POWERPLUG_CD_PLAYER: + if self.cvi.changed_here(device.topic, key, data) and data is True: + # switch on of cd player + self.audio_source = self.AUDIO_SOURCE_CD + elif device in [self.spotify_state, self.mpd_state]: + if self.cvi.changed_here(device.topic, key, data) and data is True: + # switch on raspi-source + self.audio_source = self.AUDIO_SOURCE_RASPI + elif device == self.powerplug_common and key == self.KEY_POWERPLUG_AMPLIFIER: + if self.cvi.changed_here(device.topic, key, data) and data is True: + # switch on of amplifier -> select source and reset stored source value + self.delayed_task.run() + + def send_audio_source(self): + if self.audio_source == self.AUDIO_SOURCE_PC: + logger.info("Sending IR command to change audio source to pc") + self.remote_amplifier.set_line3() + elif self.audio_source == self.AUDIO_SOURCE_CD: + logger.info("Sending IR command to change audio source to cd") + self.remote_amplifier.set_cd() + elif self.audio_source == self.AUDIO_SOURCE_RASPI: + logger.info("Sending IR command to change audio source to raspi") + self.remote_amplifier.set_line1() + self.audio_source = self.AUDIO_SOURCE_PC diff --git a/function/helpers.py b/function/helpers.py new file mode 100644 index 0000000..4e93229 --- /dev/null +++ b/function/helpers.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# + +import inspect + + +class changed_value_indicator(dict): + def __init__(self): + super().__init__(self) + + def changed_here(self, topic, key, value): + caller_name = inspect.getmodule(inspect.stack()[1][0]).__name__ + '.' + inspect.stack()[1][3] + key = '::'.join([caller_name, topic, key]) + # + rv = self.get(key) != value + self[key] = value + return rv