diff --git a/light-schedule.py b/light-schedule.py index 2c77f15..9f3bce2 100644 --- a/light-schedule.py +++ b/light-schedule.py @@ -7,8 +7,15 @@ from tradfri import Tradfri # Config -transition_supported_attrs = ['brightness','color_temp'] -transition_supported_durations = ['second','seconds','minute','minutes','hour','hours'] +transition_supported_attrs = ["brightness", "color_temp"] +transition_supported_durations = [ + "second", + "seconds", + "minute", + "minutes", + "hour", + "hours", +] # Debug switch for this script. separate from tradfri.py debug switch. @@ -19,7 +26,7 @@ # Script -parser = argparse.ArgumentParser(conflict_handler='resolve') +parser = argparse.ArgumentParser(conflict_handler="resolve") # args: # device friendly name @@ -27,9 +34,21 @@ # action params (dict) parser.add_argument("device", help="Friendly name of the device to update.") -parser.add_argument("action", help="Action to take on the specified device. See tradfri.py for supported actions.") -parser.add_argument("params", nargs='*', default=None, help="Parameters for the specified action.") -parser.add_argument("--verbose", "-v", default=0, type=int, choices=[1,2], help="Increase verbosity of output.") +parser.add_argument( + "action", + help="Action to take on the specified device. See tradfri.py for supported actions.", +) +parser.add_argument( + "params", nargs="*", default=None, help="Parameters for the specified action." +) +parser.add_argument( + "--verbose", + "-v", + default=0, + type=int, + choices=[1, 2], + help="Increase verbosity of output.", +) # ^ this actually passes debug switch to the tradfri class instance. args = parser.parse_args() @@ -45,9 +64,9 @@ toggle() """ -# Transition is the special case. +# Transition is the special case. # Need to parse new_attr, duration, and start_time. -# This is... not the right way to do it, because argparse will probably do a better job at this sort of thing. +# This is... not the right way to do it, because argparse will probably do a better job at this sort of thing. # Plus, delimiting values by '=' doesn't match the param format in argparse. # example: @@ -55,113 +74,114 @@ def parse_transition_params(params): - # parse new setting / attr: - attr = params[0].replace(" ", "").split("=") - if attr[0] not in transition_supported_attrs: - print("error: attribute of", attr[0], "not supported. supported attributes:", transition_supported_attrs) - quit() - else: - new_attr = { attr[0]: int(attr[1]) } - #value = attr[1] - - # parse transition duration - dur = params[1].replace(" ", "").split("=") - if dur[0] not in transition_supported_durations: - print("error: duration unit of", dur[0], "not supported. supported durations:", transition_supported_durations) - quit() - else: - dur_unit = dur[0] - if 'second' in dur_unit: - dur_value = int(dur[1]) - elif 'minute' in dur_unit: - dur_value = int(dur[1]) * 60 - elif 'hour' in dur_unit: - dur_value = int(dur[1]) * 60 * 60 - duration = datetime.timedelta(seconds=dur_value) - - # if present, parse start time - try: - if 'start_time' in params[2]: - _p2 = params[2].replace(" ", "").split("=") - #print("params2:", params[2], "p2:", _p2) - start_time = dateutil.parser.parse(_p2[1]) - except: - #print("except block for start_time") - start_time = None - - if debug: - print("parsed values:") - print(new_attr) - print(duration) - if start_time: print(start_time) - - return new_attr, duration, start_time - - - -#### + # parse new setting / attr: + attr = params[0].replace(" ", "").split("=") + if attr[0] not in transition_supported_attrs: + print( + "error: attribute of", + attr[0], + "not supported. supported attributes:", + transition_supported_attrs, + ) + quit() + else: + new_attr = {attr[0]: int(attr[1])} + # value = attr[1] + + # parse transition duration + dur = params[1].replace(" ", "").split("=") + if dur[0] not in transition_supported_durations: + print( + "error: duration unit of", + dur[0], + "not supported. supported durations:", + transition_supported_durations, + ) + quit() + else: + dur_unit = dur[0] + if "second" in dur_unit: + dur_value = int(dur[1]) + elif "minute" in dur_unit: + dur_value = int(dur[1]) * 60 + elif "hour" in dur_unit: + dur_value = int(dur[1]) * 60 * 60 + duration = datetime.timedelta(seconds=dur_value) + + # if present, parse start time + try: + if "start_time" in params[2]: + _p2 = params[2].replace(" ", "").split("=") + # print("params2:", params[2], "p2:", _p2) + start_time = dateutil.parser.parse(_p2[1]) + except: + # print("except block for start_time") + start_time = None + + if debug: + print("parsed values:") + print(new_attr) + print(duration) + if start_time: + print(start_time) + + return new_attr, duration, start_time + + +#### #### MAIN -#### +#### if debug: - from pprint import pprint - print("args:") - pprint(args) - print("") - if args.params: - print("params:") - print(args.params) - print("") + from pprint import pprint - print("creating instance for device name:", args.device) + print("args:") + pprint(args) + print("") + if args.params: + print("params:") + print(args.params) + print("") + + print("creating instance for device name:", args.device) device = Tradfri(args.device, debug=args.verbose) func = getattr(device, args.action) -if args.action == 'transition': - #special case - new_attr, duration, start_time = parse_transition_params(args.params) - resp = func(new_attr, duration, start_time) - -elif args.action == 'lightswitch': - # convert to bool - test_arg = args.params[0].lower() - if test_arg == 'true' or test_arg == '1': - func_input = True - elif test_arg == 'false' or test_arg == '0': - func_input = False - else: - func_input = None - # lightswitch() function defaults to True if no input. - - resp = func(func_input) - -elif args.action in ['set_brightness','set_color','mireds_to_kelvin','kelvin_to_mireds']: - resp = func(int(args.params[0])) +if args.action == "transition": + # special case + new_attr, duration, start_time = parse_transition_params(args.params) + resp = func(new_attr, duration, start_time) + +elif args.action == "lightswitch": + # convert to bool + test_arg = args.params[0].lower() + if test_arg == "true" or test_arg == "1": + func_input = True + elif test_arg == "false" or test_arg == "0": + func_input = False + else: + func_input = None + # lightswitch() function defaults to True if no input. + + resp = func(func_input) + +elif args.action in [ + "set_brightness", + "set_color", + "mireds_to_kelvin", + "kelvin_to_mireds", +]: + resp = func(int(args.params[0])) # Above are just the commonly used functions. # There's nothing to stop user from calling a lower-level/internal function here, but args won't be used, so some may fail. else: - resp = func() + resp = func() if resp: - from pprint import pprint - pprint(resp) - - - - - - - - - - - - - - - + from pprint import pprint + pprint(resp) diff --git a/tradfri.py b/tradfri.py index 9d0fbbf..0cef51e 100644 --- a/tradfri.py +++ b/tradfri.py @@ -9,14 +9,13 @@ import requests - # Welp. I may have needlessly rewritten this - https://home-assistant.io/components/switch.flux/ # Though - it isn't perfect anyway. # - https://community.home-assistant.io/t/improving-the-fluxer/23729 # HomeAssistant base API URL. -#API_URL = "http://192.168.132.162:8123/api/" +# API_URL = "http://192.168.132.162:8123/api/" CONFIG_FILENAME = "tradfri.conf" # Min/max values for your light. These are for ikea TRADFRI white spectrum. @@ -27,7 +26,8 @@ # Updating too fast can cause flickering. Recommended 0.1-0.2, not less than 0.1. # Too high makes faster transitions stuttery as well, due to larger increases per step. -# If HomeAssistant is slower to respond than this, this may be significantly faster than actual min step duration. +# If HomeAssistant is slower to respond than this, this value +# may be significantly faster than actual min step duration. MIN_STEP_DURATION = datetime.timedelta(seconds=0.1) @@ -38,11 +38,15 @@ def __init__(self, device_name, debug=0): # get config self.config = configparser.ConfigParser() _ = self.config.read(CONFIG_FILENAME) - self.api_url = self.config['tradfri']['api_url'] + self.api_url = self.config["tradfri"]["api_url"] self.entity_id = self.get_entity(device_name) self.debug = debug self.state = self.get_state() + if self.state["state"] == "on": + self.attrs = state["attributes"] + else: + self.attrs = {} try: if "Entity not found" in self.state["message"]: @@ -54,20 +58,20 @@ def __init__(self, device_name, debug=0): pass def get_entity(self, device_name): - # try to get the entity ID for the given device name. + """ Try to get the entity ID for the given device name. """ # Check if it matches anything in the device map. - device_map = json.loads(self.config['tradfri']['device_map']) + device_map = json.loads(self.config["tradfri"]["device_map"]) if device_name in device_map: - return device_map['device_name'] + return device_map["device_name"] # Else, just try prepending "light" to the entity name. - else: - return "light." + device_name - + return "light." + device_name def apireq(self, endpoint, req_type="get", post_data=None): + """ Make an API request aginst HomeAssistant. """ + def handle_errors(data): if self.debug and data.status_code != 200: print( @@ -123,10 +127,7 @@ def get_attrs(self): def check_if_on(self): state = self.get_state() - if state["state"] == "on": - return True - else: - return False + return bool(state["state"] == "on") def get_temp_kelvin(self): """ Retrieves current bulb state and converts 'mireds' to kelvin. @@ -208,7 +209,7 @@ def transition(self, new_attr, duration, start_time=None): """ Highest-level function. Initiates a transition for the given entity_id, based on the target values contained in new_attr, and the 'duration' which is a timedelta. - If start_time is not set, start immediately. + If start_time is not set, start immediately. Otherwise, sleeps until start_time. """ @@ -229,12 +230,13 @@ def plan_transition( ): """ new_attr is a single-item dict containing type of attribute & new value, e.g., {"brightness": 0} - duration is a timedelta, as is time_per_step. One or the other must be set. If both are set, uses 'duration'. + duration is a timedelta, as is time_per_step. One or the other must be set. + If both are set, uses 'duration'. start_time is a datetime. - start_attrs will define the starting attributes to use; + start_attrs will define the starting attributes to use; if not set, this will start from current attributes. - ### TODO: implment time_per_step + ### TODO: implment time_per_step """ new_attr = self.sanity_check_values(new_attr)