diff --git a/devdi b/devdi index 118e80f..619d7f9 160000 --- a/devdi +++ b/devdi @@ -1 +1 @@ -Subproject commit 118e80f070072d7ac65531bfdf316baf04901a9a +Subproject commit 619d7f926f13ce03950db9c5dab3461e5b7da23a diff --git a/device_development.py b/device_development.py new file mode 100644 index 0000000..02b0e07 --- /dev/null +++ b/device_development.py @@ -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.") diff --git a/devices/__init__.py b/devices/__init__.py index d192291..b59101d 100644 --- a/devices/__init__.py +++ b/devices/__init__.py @@ -33,12 +33,14 @@ except ImportError: 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 from devices.tradfri import tradfri_light as tradfri_sw from devices.tradfri import tradfri_light as tradfri_sw_br from devices.tradfri import tradfri_light as tradfri_sw_br_ct from devices.tradfri import tradfri_button as tradfri_button from devices.tradfri import tradfri_light as livarno_sw_br_ct from devices.brennenstuhl import brennenstuhl_heatingvalve +from devices.silvercrest import silvercrest_button from devices.silvercrest import silvercrest_powerplug from devices.silvercrest import silvercrest_motion_sensor from devices.mydevices import powerplug as my_powerplug diff --git a/devices/brennenstuhl.py b/devices/brennenstuhl.py index 6cd1cba..c423b58 100644 --- a/devices/brennenstuhl.py +++ b/devices/brennenstuhl.py @@ -2,7 +2,6 @@ # -*- coding: utf-8 -*- # from devices.base import base -import json import task import time diff --git a/devices/hue.py b/devices/hue.py new file mode 100644 index 0000000..9fdba30 --- /dev/null +++ b/devices/hue.py @@ -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) diff --git a/devices/shelly.py b/devices/shelly.py index ff7fc94..0bdab1c 100644 --- a/devices/shelly.py +++ b/devices/shelly.py @@ -65,8 +65,8 @@ class shelly(base_output): 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.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 diff --git a/devices/silvercrest.py b/devices/silvercrest.py index 0aa7313..99bc140 100644 --- a/devices/silvercrest.py +++ b/devices/silvercrest.py @@ -5,6 +5,47 @@ 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) diff --git a/devices/tradfri.py b/devices/tradfri.py index 3b5faf1..5cd093c 100644 --- a/devices/tradfri.py +++ b/devices/tradfri.py @@ -182,6 +182,7 @@ class tradfri_button(base): def __state_logging__(self, inst, key, data): if key in [self.KEY_ACTION]: self.logger.info("Input '%s' with '%s'", key, repr(data)) + self[self.KEY_ACTION] = None # # RX diff --git a/function/first_floor_east.py b/function/first_floor_east.py index b74ae1f..3442fb6 100644 --- a/function/first_floor_east.py +++ b/function/first_floor_east.py @@ -7,7 +7,7 @@ from devdi import topic as props 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 +from function.modules import brightness_choose_n_action, timer_on_activation, heating_function, switched_light from function.rooms import room, room_collection from function.videv import videv_switching, videv_switch_brightness, videv_switching_timer, videv_switch_brightness_color_temp, videv_heating, videv_multistate import logging @@ -61,6 +61,8 @@ class first_floor_east_kitchen(room): # http://shelly1l-8CAAB5616C01 # main light self.main_light_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL) + self.main_light_hue = pd.get(props.STG_ZFE, loc, roo, props.FUN_MAL) + # http://shelly1-e89f6d85a466 # circulation pump self.circulation_pump_shelly = pd.get(props.STG_SHE, loc, roo, props.FUN_CIR) @@ -75,6 +77,8 @@ class first_floor_east_kitchen(room): # circulation pump self.circulation_pump = timer_on_activation(self.circulation_pump_shelly, self.circulation_pump_shelly.KEY_OUTPUT_0, 10*60) self.circulation_pump_shelly.add_callback(self.circulation_pump_shelly.KEY_OUTPUT_0, True, self.main_light_shelly.flash_0_mcb, True) + # Request hue data of lead light after power on + switched_light(self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0, self.main_light_hue) # heating function self.heating_function = heating_function( self.heating_valve, @@ -87,9 +91,11 @@ class first_floor_east_kitchen(room): # Virtual Device Interface # # main light - self.main_light_videv = videv_switching( + self.main_light_videv = videv_switch_brightness_color_temp( mqtt_client, config.TOPIC_FFE_KITCHEN_MAIN_LIGHT_VIDEV, - self.main_light_shelly, self.main_light_shelly.KEY_OUTPUT_0 + self.main_light_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(