From 235dab5accaa63d45f0e8d0235b73cf819a5aed6 Mon Sep 17 00:00:00 2001 From: Artanicus Date: Sun, 4 Mar 2018 17:26:44 +0200 Subject: [PATCH 01/14] Next version mint will be v0.2.13 --- cozify/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cozify/__init__.py b/cozify/__init__.py index b5c9b6c..11ef092 100644 --- a/cozify/__init__.py +++ b/cozify/__init__.py @@ -1 +1 @@ -__version__ = "0.2.12" +__version__ = "0.2.13" From 81d47f158065ea138f1b087ae92442c323489e9e Mon Sep 17 00:00:00 2001 From: Artanicus Date: Mon, 5 Mar 2018 20:06:25 +0200 Subject: [PATCH 02/14] Simple test utils and api functions for turning devices off and on. They don't work yet though. No idea why. --- cozify/hub.py | 37 ++++++++++++++++++++++++++++++-- util/device-off.py | 14 ++++++++++++ util/{toggle.py => device-on.py} | 2 +- util/device-toggle.py | 14 ++++++++++++ 4 files changed, 64 insertions(+), 3 deletions(-) create mode 100755 util/device-off.py rename util/{toggle.py => device-on.py} (93%) create mode 100755 util/device-toggle.py diff --git a/cozify/hub.py b/cozify/hub.py index a9bbf33..ad8eedc 100644 --- a/cozify/hub.py +++ b/cozify/hub.py @@ -72,7 +72,7 @@ def devices(*, capabilities=None, and_filter=False, **kwargs): else: # no filtering return devs -def toggle(device_id, **kwargs): +def device_toggle(device_id, **kwargs): """Toggle power state of any device capable of it such as lamps. Eligibility is determined by the capability ON_OFF. Args: @@ -89,14 +89,47 @@ def toggle(device_id, **kwargs): current_state = dev_state['isOn'] new_state = _clean_state(dev_state) new_state['isOn'] = not current_state # reverse state + request_type = 'CMD_DEVICE' command = { - "type": "CMD_DEVICE", + "type": request_type, "id": device_id, "state": new_state } hub_api.devices_command(command, **kwargs) +def device_on(device_id, **kwargs): + """Turn on a device that is capable of turning on. Eligibility is determined by the capability ON_OFF. + """ + _fill_kwargs(kwargs) + + # Get list of devices known to support toggle and find the device. + devs = devices(capabilities=capability.ON_OFF, **kwargs) + dev_state = devs[device_id]['state'] + request_type = 'CMD_DEVICE_ON' + + command = { + "type": request_type, + "id": device_id, + } + hub_api.devices_command(command, **kwargs) + +def device_off(device_id, **kwargs): + """Turn off a device that is capable of turning off. Eligibility is determined by the capability ON_OFF. + """ + _fill_kwargs(kwargs) + + # Get list of devices known to support toggle and find the device. + devs = devices(capabilities=capability.ON_OFF, **kwargs) + dev_state = devs[device_id]['state'] + request_type = 'CMD_DEVICE_OFF' + + command = { + "type": request_type, + "id": device_id, + } + hub_api.devices_command(command, **kwargs) + def _get_id(**kwargs): """Get a hub_id from various sources, meant so that you can just throw kwargs at it and get a valid id. If no data is available to determine which hub was meant, will default to the default hub. If even that fails, will raise an AttributeError. diff --git a/util/device-off.py b/util/device-off.py new file mode 100755 index 0000000..e225fef --- /dev/null +++ b/util/device-off.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +from cozify import hub +import pprint, sys + +from cozify.test import debug + +def main(device): + hub.device_off(device) + +if __name__ == "__main__": + if len(sys.argv) > 1: + main(sys.argv[1]) + else: + sys.exit(1) diff --git a/util/toggle.py b/util/device-on.py similarity index 93% rename from util/toggle.py rename to util/device-on.py index 7c30ba4..273539f 100755 --- a/util/toggle.py +++ b/util/device-on.py @@ -5,7 +5,7 @@ from cozify.test import debug def main(device): - hub.toggle(device) + hub.device_on(device) if __name__ == "__main__": if len(sys.argv) > 1: diff --git a/util/device-toggle.py b/util/device-toggle.py new file mode 100755 index 0000000..ab85af0 --- /dev/null +++ b/util/device-toggle.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +from cozify import hub +import pprint, sys + +from cozify.test import debug + +def main(device): + hub.device_toggle(device) + +if __name__ == "__main__": + if len(sys.argv) > 1: + main(sys.argv[1]) + else: + sys.exit(1) From 6caef7c9927398f9f4402a3c02e1d14b0f605811 Mon Sep 17 00:00:00 2001 From: Artanicus Date: Sun, 11 Mar 2018 14:56:36 +0200 Subject: [PATCH 03/14] Simplify call structure, generic device commanders --- cozify/hub_api.py | 90 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 17 deletions(-) diff --git a/cozify/hub_api.py b/cozify/hub_api.py index 0a677f0..9ab3368 100644 --- a/cozify/hub_api.py +++ b/cozify/hub_api.py @@ -13,11 +13,8 @@ apiPath = '/cc/1.8' -def _getBase(host, port=8893, api=apiPath): - return 'http://%s:%s%s' % (host, port, api) - -def _headers(hub_token): - return { 'Authorization': hub_token } +def _getBase(host, port=8893): + return 'http://{0}:{1}'.format(host, port) def get(call, hub_token_header=True, base=apiPath, **kwargs): """GET method for calling hub API. @@ -32,9 +29,8 @@ def get(call, hub_token_header=True, base=apiPath, **kwargs): **cloud_token(str): Cloud authentication token. Only needed if remote = True. """ return _call(method=requests.get, - call=call, + call='{0}{1}'.format(base, call), hub_token_header=hub_token_header, - base=base, **kwargs ) @@ -48,33 +44,38 @@ def put(call, payload, hub_token_header=True, base=apiPath, **kwargs): base(str): Base path to call from API instead of global apiPath. Defaults to apiPath. """ return _call(method=requests.put, - call=call, + call='{0}{1}'.format(base, call), hub_token_header=hub_token_header, - base=base, payload=payload, **kwargs ) -def _call(*, call, method, base, hub_token_header, payload=None, **kwargs): +def _call(*, call, method, hub_token_header, payload=None, **kwargs): """Backend for get & put + + Args: + call(str): Full API path to call. + method(function): requests.get|put function to use for call. """ response = None - headers = None + headers = {} if hub_token_header: - headers = _headers(kwargs['hub_token']) + if 'hub_token' not in kwargs: + raise AttributeError('Asked to do a call to the hub but no hub_token provided.') + headers['Authorization'] = kwargs['hub_token'] + if payload is not None: + headers['content-type'] = 'application/json' if kwargs['remote']: # remote call if 'cloud_token' not in kwargs: raise AttributeError('Asked to do remote call but no cloud_token provided.') logging.debug('_call routing to cloud.remote()') - response = cloud_api.remote(apicall=base + call, payload=payload, **kwargs) + response = cloud_api.remote(apicall=call, payload=payload, **kwargs) else: # local call if not kwargs['host']: raise AttributeError('Local call but no hostname was provided. Either set keyword remote or host.') - if hub_token_header: - headers = _headers(kwargs['hub_token']) try: - response = method(_getBase(host=kwargs['host'], api=base) + call, headers=headers, data=payload) + response = method(_getBase(host=kwargs['host']) + call, headers=headers, data=payload) except RequestException as e: raise APIError('connection failure', 'issues connection to \'{0}\': {1}'.format(kwargs['host'], e)) @@ -123,8 +124,63 @@ def devices_command(command, **kwargs): command(dict): dictionary of type DeviceData containing the changes wanted. Will be converted to json. Returns: - str: What ever the API replied or an APIException on failure. + str: What ever the API replied or raises an APIEerror on failure. """ command = json.dumps(command) logging.debug('command json to send: {0}'.format(command)) return put('/devices/command', command, **kwargs) + +def devices_command_generic(*, device_id, command=None, request_type, **kwargs): + """Command helper for CMD type of actions. + No checks are made wether the device supports the command or not. For kwargs see cozify.hub_api.put() + + Args: + device_id(str): ID of the device to operate on. + request_type(str): Type of CMD to run, e.g. CMD_DEVICE_OFF + command(dict): Optional dictionary to override command sent. Defaults to None which is interpreted as { device_id, type } + Returns: + str: What ever the API replied or raises an APIError on failure. + """ + if command is None: + command = [{ + "id": device_id, + "type": request_type + }] + return devices_command(command, **kwargs) + +def devices_command_state(*, device_id, state, **kwargs): + """Command helper for CMD type of actions. + No checks are made wether the device supports the command or not. For kwargs see cozify.hub_api.put() + + Args: + device_id(str): ID of the device to operate on. + state(dict): New state dictionary containing changes. + Returns: + str: What ever the API replied or raises an APIError on failure. + """ + command = [{ + "id": device_id, + "type": 'CMD_DEVICE', + "state": state + }] + return devices_command(command, **kwargs) + +def devices_command_on(device_id, **kwargs): + """Command helper for CMD_DEVICE_ON. + + Args: + device_id(str): ID of the device to operate on. + Returns: + str: What ever the API replied or raises an APIError on failure. + """ + return devices_command_generic(device_id=device_id, request_type='CMD_DEVICE_ON', **kwargs) + +def devices_command_off(device_id, **kwargs): + """Command helper for CMD_DEVICE_OFF. + + Args: + device_id(str): ID of the device to operate on. + Returns: + str: What ever the API replied or raises an APIException on failure. + """ + return devices_command_generic(device_id=device_id, request_type='CMD_DEVICE_OFF', **kwargs) From 8cd8b8519b56f238b4093b58dd0a3ac9d7622a5c Mon Sep 17 00:00:00 2001 From: Artanicus Date: Sun, 11 Mar 2018 14:57:46 +0200 Subject: [PATCH 04/14] Wrap new device commands with nicer interfaces --- cozify/hub.py | 57 +++++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/cozify/hub.py b/cozify/hub.py index ad8eedc..d9bb761 100644 --- a/cozify/hub.py +++ b/cozify/hub.py @@ -76,7 +76,7 @@ def device_toggle(device_id, **kwargs): """Toggle power state of any device capable of it such as lamps. Eligibility is determined by the capability ON_OFF. Args: - device_id: ID of the device to toggle. + device_id(str): ID of the device to toggle. **hub_id(str): optional id of hub to operate on. A specified hub_id takes presedence over a hub_name or default Hub. **hub_name(str): optional name of hub to operate on. **remote(bool): Remote or local query. @@ -86,49 +86,44 @@ def device_toggle(device_id, **kwargs): # Get list of devices known to support toggle and find the device and it's state. devs = devices(capabilities=capability.ON_OFF, **kwargs) dev_state = devs[device_id]['state'] - current_state = dev_state['isOn'] + current_power = dev_state['isOn'] new_state = _clean_state(dev_state) - new_state['isOn'] = not current_state # reverse state - request_type = 'CMD_DEVICE' - - command = { - "type": request_type, - "id": device_id, - "state": new_state - } - hub_api.devices_command(command, **kwargs) + new_state['isOn'] = not current_power # reverse power state + hub_api.devices_command_state(device_id=device_id, state=new_state, **kwargs) def device_on(device_id, **kwargs): """Turn on a device that is capable of turning on. Eligibility is determined by the capability ON_OFF. """ _fill_kwargs(kwargs) - - # Get list of devices known to support toggle and find the device. - devs = devices(capabilities=capability.ON_OFF, **kwargs) - dev_state = devs[device_id]['state'] - request_type = 'CMD_DEVICE_ON' - - command = { - "type": request_type, - "id": device_id, - } - hub_api.devices_command(command, **kwargs) + if _is_eligible(device_id, capability.ON_OFF, **kwargs): + hub_api.devices_command_on(device_id, **kwargs) + else: + raise AttributeError('Device not found or not eligible for action.') def device_off(device_id, **kwargs): """Turn off a device that is capable of turning off. Eligibility is determined by the capability ON_OFF. """ _fill_kwargs(kwargs) + if _is_eligible(device_id, capability.ON_OFF, **kwargs): + hub_api.devices_command_off(device_id, **kwargs) + else: + raise AttributeError('Device not found or not eligible for action.') - # Get list of devices known to support toggle and find the device. - devs = devices(capabilities=capability.ON_OFF, **kwargs) - dev_state = devs[device_id]['state'] - request_type = 'CMD_DEVICE_OFF' +def _is_eligible(device_id, capability_filter, **kwargs): + """Check if device matches a AND devices filter. + + Args: + device_id(str): ID of the device to check. + filter(): Single hub.capability or a list of them to match against. + Returns: + bool: True if filter matches. + """ + devs = devices(capabilities=capability_filter, **kwargs) + if device_id in devs: + return True + else: + return False - command = { - "type": request_type, - "id": device_id, - } - hub_api.devices_command(command, **kwargs) def _get_id(**kwargs): """Get a hub_id from various sources, meant so that you can just throw kwargs at it and get a valid id. From 9766574264fd152aa25a9e3859280483e34ba344 Mon Sep 17 00:00:00 2001 From: Artanicus Date: Sun, 11 Mar 2018 15:12:21 +0200 Subject: [PATCH 05/14] Remove useless debug output --- cozify/hub.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cozify/hub.py b/cozify/hub.py index d9bb761..2e8f99a 100644 --- a/cozify/hub.py +++ b/cozify/hub.py @@ -62,7 +62,6 @@ def devices(*, capabilities=None, and_filter=False, **kwargs): devs = hub_api.devices(**kwargs) if capabilities: if isinstance(capabilities, capability): # single capability given - logging.debug("single capability {0}".format(capabilities.name)) return { key : value for key, value in devs.items() if capabilities.name in value['capabilities']['values'] } else: # multi-filter if and_filter: From d2e2a4a895603be0dfd0922e0922d35ba5802591 Mon Sep 17 00:00:00 2001 From: Artanicus Date: Sun, 11 Mar 2018 15:44:25 +0200 Subject: [PATCH 06/14] Mark getHubId for deprecation, replace with hub.hub_id --- cozify/hub.py | 19 ++++++++++++++++--- cozify/test/test_hub.py | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/cozify/hub.py b/cozify/hub.py index 2e8f99a..e983bdc 100644 --- a/cozify/hub.py +++ b/cozify/hub.py @@ -202,21 +202,34 @@ def default(): return config.state['Hubs']['default'] def getHubId(hub_name): + """Deprecated, use hub_id(). Return id of hub by it's name. + + Args: + hub_name(str): Name of hub to query. The name is given when registering a hub to an account. + str: hub_id on success, raises an attributeerror on failure. + + Returns: + str: Hub id or raises + """ + logging.warn('hub.getHubId is deprecated and will be removed soon. Use hub.hub_id()') + return hub_id(hub_name) + +def hub_id(hub_name): """Get hub id by it's name. Args: hub_name(str): Name of hub to query. The name is given when registering a hub to an account. Returns: - str: Hub id or None if the hub wasn't found. + str: hub_id on success, raises an attributeerror on failure. """ for section in config.state.sections(): if section.startswith("Hubs."): - logging.debug('Found hub {0}'.format(section)) + logging.debug('Found hub: {0}'.format(section)) if config.state[section]['hubname'] == hub_name: return section[5:] # cut out "Hubs." - return None + raise AttributeError('Hub not found: {0}'.format(hub_name)) def _getAttr(hub_id, attr, default=None, boolean=False): """Get hub state attributes by attr name. Optionally set a default value if attribute not found. diff --git a/cozify/test/test_hub.py b/cozify/test/test_hub.py index 87dfda0..66d9a68 100755 --- a/cozify/test/test_hub.py +++ b/cozify/test/test_hub.py @@ -19,7 +19,7 @@ def test_hub_id_to_name(tmp_hub): assert hub.name(tmp_hub.id) == tmp_hub.name def test_hub_name_to_id(tmp_hub): - assert hub.getHubId(tmp_hub.name) == tmp_hub.id + assert hub.hub_id(tmp_hub.name) == tmp_hub.id @pytest.mark.live def test_multisensor(live_hub): From 54194fe68008fb1086e710f92936cd288be6c067 Mon Sep 17 00:00:00 2001 From: Artanicus Date: Sun, 11 Mar 2018 15:46:05 +0200 Subject: [PATCH 07/14] Use hub_id instead of getHubId --- cozify/hub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cozify/hub.py b/cozify/hub.py index e983bdc..0361ebb 100644 --- a/cozify/hub.py +++ b/cozify/hub.py @@ -141,7 +141,7 @@ def _get_id(**kwargs): return kwargs['hubId'] if 'hub_name' in kwargs or 'hubName' in kwargs: if 'hub_name' in kwargs: - return getHubId(kwargs['hub_name']) + return hub_id(kwargs['hub_name']) return getHubId(kwargs['hubName']) return default() From c6f78c3db356dc10458f98f623ce4beae167b723 Mon Sep 17 00:00:00 2001 From: Artanicus Date: Sun, 11 Mar 2018 15:57:15 +0200 Subject: [PATCH 08/14] Update versionExplorer to new apibase structure --- util/versionExplorer.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/util/versionExplorer.py b/util/versionExplorer.py index 786520d..7bc1389 100755 --- a/util/versionExplorer.py +++ b/util/versionExplorer.py @@ -5,21 +5,21 @@ from cozify.Error import APIError def main(start=hub_api.apiPath): - id = hub.default() - host = hub.host(id) - token = hub.token(id) - api = start + hub_id = hub.default() + host = hub.host(hub_id) + token = hub.token(hub_id) + api_ver = start + base = hub_api._getBase(host) - print('Testing against {0}, starting from {1}'.format(id, hub_api._getBase(host, api=start))) + print('Testing against {0}, starting from {1}{2}'.format(hub_id, base, start)) while True: - base = hub_api._getBase(host, api=api) - if not ping(base, token): - print('Fail: {0}'.format(api)) + if not ping(base + api_ver, token): + print('Fail: {0}'.format(api_ver)) else: - print('Works: {0}'.format(api)) + print('Works: {0}'.format(api_ver)) break - api = increment(api) + api_ver = increment(api_ver) def increment(apipath): From 51eb19b2a772650b04ed0cd6169cd40eb674af19 Mon Sep 17 00:00:00 2001 From: Artanicus Date: Sun, 11 Mar 2018 16:46:22 +0200 Subject: [PATCH 09/14] hub.light_temperature basic implementation --- cozify/hub.py | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/cozify/hub.py b/cozify/hub.py index 0361ebb..dd30da7 100644 --- a/cozify/hub.py +++ b/cozify/hub.py @@ -92,6 +92,9 @@ def device_toggle(device_id, **kwargs): def device_on(device_id, **kwargs): """Turn on a device that is capable of turning on. Eligibility is determined by the capability ON_OFF. + + Args: + device_id(str): ID of the device to operate on. """ _fill_kwargs(kwargs) if _is_eligible(device_id, capability.ON_OFF, **kwargs): @@ -101,6 +104,9 @@ def device_on(device_id, **kwargs): def device_off(device_id, **kwargs): """Turn off a device that is capable of turning off. Eligibility is determined by the capability ON_OFF. + + Args: + device_id(str): ID of the device to operate on. """ _fill_kwargs(kwargs) if _is_eligible(device_id, capability.ON_OFF, **kwargs): @@ -108,17 +114,40 @@ def device_off(device_id, **kwargs): else: raise AttributeError('Device not found or not eligible for action.') -def _is_eligible(device_id, capability_filter, **kwargs): +def light_temperature(device_id, temperature=2700, **kwargs): + """Set temperature of a light. + + Args: + device_id(str): ID of the device to operate on. + temperature(str): Temperature in Kelvins. + """ + _fill_kwargs(kwargs) + state = {} # will be populated by _is_eligible + if _is_eligible(device_id, capability.COLOR_TEMP, state=state, **kwargs): + logging.debug('dirty state: {0}'.format(state)) + state = _clean_state(state) + state['colorMode'] = 'ct' + state['temperature'] = temperature + hub_api.devices_command_state(device_id=device_id, state=state, **kwargs) + else: + raise AttributeError('Device not found or not eligible for action.') + +def _is_eligible(device_id, capability_filter, devs=None, state=None, **kwargs): """Check if device matches a AND devices filter. Args: device_id(str): ID of the device to check. - filter(): Single hub.capability or a list of them to match against. + filter(hub.capability): Single hub.capability or a list of them to match against. + devs(dict): Optional devices dictionary to use. If not defined, will be retrieved live. + state(dict): Optional state dictionary, will be populated with state of checked device if device is eligible. Returns: bool: True if filter matches. """ - devs = devices(capabilities=capability_filter, **kwargs) + if devs is None: # only retrieve if we didn't get them + devs = devices(capabilities=capability_filter, **kwargs) if device_id in devs: + state.update(devs[device_id]['state']) + logging.debug('Implicitly returning state: {0}'.format(state)) return True else: return False From f26f8f598a5aa121668d00c76a2993c8b4d8d9ac Mon Sep 17 00:00:00 2001 From: Artanicus Date: Sun, 11 Mar 2018 16:56:01 +0200 Subject: [PATCH 10/14] Check temperature bounds, use min/max if out of bounds --- cozify/hub.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/cozify/hub.py b/cozify/hub.py index dd30da7..59c7646 100644 --- a/cozify/hub.py +++ b/cozify/hub.py @@ -119,12 +119,21 @@ def light_temperature(device_id, temperature=2700, **kwargs): Args: device_id(str): ID of the device to operate on. - temperature(str): Temperature in Kelvins. + temperature(float): Temperature in Kelvins. If outside the operating range of the device the extreme value is used. """ _fill_kwargs(kwargs) state = {} # will be populated by _is_eligible if _is_eligible(device_id, capability.COLOR_TEMP, state=state, **kwargs): - logging.debug('dirty state: {0}'.format(state)) + # Make sure temperature is within bounds [state.minTemperature, state.maxTemperature] + minimum = state['minTemperature'] + maximum = state['maxTemperature'] + if temperature < minimum: + logging.warn('Device does not support temperature {0}K, using minimum instead: {1}'.format(temperature, minimum)) + temperature = minimum + elif temperature > maximum: + logging.warn('Device does not support temperature {0}K, using maximum instead: {1}'.format(temperature, maximum)) + temperature = maximum + state = _clean_state(state) state['colorMode'] = 'ct' state['temperature'] = temperature From b23df889be672200590c116377f5d7623efb97e1 Mon Sep 17 00:00:00 2001 From: Artanicus Date: Sun, 11 Mar 2018 19:32:46 +0200 Subject: [PATCH 11/14] Theoretical support for transitions. It just doesn't seem to work at least with Osram. --- cozify/hub.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cozify/hub.py b/cozify/hub.py index 59c7646..51264b2 100644 --- a/cozify/hub.py +++ b/cozify/hub.py @@ -114,12 +114,13 @@ def device_off(device_id, **kwargs): else: raise AttributeError('Device not found or not eligible for action.') -def light_temperature(device_id, temperature=2700, **kwargs): +def light_temperature(device_id, temperature=2700, transition=0, **kwargs): """Set temperature of a light. Args: device_id(str): ID of the device to operate on. - temperature(float): Temperature in Kelvins. If outside the operating range of the device the extreme value is used. + temperature(float): Temperature in Kelvins. If outside the operating range of the device the extreme value is used. Defaults to 2700K. + transition(int): Transition length in milliseconds. Defaults to instant. """ _fill_kwargs(kwargs) state = {} # will be populated by _is_eligible @@ -137,6 +138,7 @@ def light_temperature(device_id, temperature=2700, **kwargs): state = _clean_state(state) state['colorMode'] = 'ct' state['temperature'] = temperature + state['transitionMsec'] = transition hub_api.devices_command_state(device_id=device_id, state=state, **kwargs) else: raise AttributeError('Device not found or not eligible for action.') From 47b0eaca562672bff41bdbe004a9252c883ce140 Mon Sep 17 00:00:00 2001 From: Artanicus Date: Sun, 11 Mar 2018 22:20:51 +0200 Subject: [PATCH 12/14] light_color & light_brightness --- cozify/hub.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/cozify/hub.py b/cozify/hub.py index 51264b2..2fe5a2e 100644 --- a/cozify/hub.py +++ b/cozify/hub.py @@ -6,6 +6,7 @@ """ import logging +import math from . import config from . import hub_api from enum import Enum @@ -143,6 +144,53 @@ def light_temperature(device_id, temperature=2700, transition=0, **kwargs): else: raise AttributeError('Device not found or not eligible for action.') +def light_color(device_id, hue, saturation=1.0, transition=0, **kwargs): + """Set temperature of a light. + + Args: + device_id(str): ID of the device to operate on. + hue(float): Hue in the range of [0, Pi*2]. If outside the range an AttributeError is raised. + saturation(float): Saturation in the range of [0, 1]. If outside the range an AttributeError is raised. Defaults to 1.0 (full saturation.) + transition(int): Transition length in milliseconds. Defaults to instant. + """ + _fill_kwargs(kwargs) + state = {} # will be populated by _is_eligible + if _is_eligible(device_id, capability.COLOR_HS, state=state, **kwargs): + # Make sure hue & saturation are within bounds + if hue < 0 or hue > math.pi * 2: + raise AttributeError('Hue out of bounds [0, pi*2]: {0}'.format(hue)) + elif saturation < 0 or saturation > 1.0: + raise AttributeError('Saturation out of bounds [0, 1.0]: {0}'.format(saturation)) + + state = _clean_state(state) + state['colorMode'] = 'hs' + state['hue'] = hue + state['saturation'] = saturation + hub_api.devices_command_state(device_id=device_id, state=state, **kwargs) + else: + raise AttributeError('Device not found or not eligible for action.') + +def light_brightness(device_id, brightness, transition=0, **kwargs): + """Set temperature of a light. + + Args: + device_id(str): ID of the device to operate on. + brightness(float): Brightness in the range of [0, 1]. If outside the range an AttributeError is raised. + transition(int): Transition length in milliseconds. Defaults to instant. + """ + _fill_kwargs(kwargs) + state = {} # will be populated by _is_eligible + if _is_eligible(device_id, capability.BRIGHTNESS, state=state, **kwargs): + # Make sure hue & saturation are within bounds + if brightness < 0 or brightness > 1.0: + raise AttributeError('Brightness out of bounds [0, 1.0]: {0}'.format(brightness)) + + state = _clean_state(state) + state['brightness'] = brightness + hub_api.devices_command_state(device_id=device_id, state=state, **kwargs) + else: + raise AttributeError('Device not found or not eligible for action.') + def _is_eligible(device_id, capability_filter, devs=None, state=None, **kwargs): """Check if device matches a AND devices filter. From b8f5009dd252f9dc6a9e8f84755d80cacdfdc78d Mon Sep 17 00:00:00 2001 From: Artanicus Date: Mon, 12 Mar 2018 21:39:15 +0200 Subject: [PATCH 13/14] Add capabilities LUX and MOTION. This adds support for motion and light sensors. --- cozify/hub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cozify/hub.py b/cozify/hub.py index 2fe5a2e..8ec74ed 100644 --- a/cozify/hub.py +++ b/cozify/hub.py @@ -14,7 +14,7 @@ from .Error import APIError -capability = Enum('capability', 'ALERT BASS BATTERY_U BRIGHTNESS COLOR_HS COLOR_LOOP COLOR_TEMP CONTACT CONTROL_LIGHT CONTROL_POWER DEVICE DIMMER_CONTROL GENERATE_ALERT HUMIDITY IDENTIFY LOUDNESS MOISTURE MUTE NEXT ON_OFF PAUSE PLAY PREVIOUS PUSH_NOTIFICATION REMOTE_CONTROL SEEK SMOKE STOP TEMPERATURE TRANSITION TREBLE TWILIGHT USER_PRESENCE VOLUME') +capability = Enum('capability', 'ALERT BASS BATTERY_U BRIGHTNESS COLOR_HS COLOR_LOOP COLOR_TEMP CONTACT CONTROL_LIGHT CONTROL_POWER DEVICE DIMMER_CONTROL GENERATE_ALERT HUMIDITY IDENTIFY LOUDNESS LUX MOISTURE MOTION MUTE NEXT ON_OFF PAUSE PLAY PREVIOUS PUSH_NOTIFICATION REMOTE_CONTROL SEEK SMOKE STOP TEMPERATURE TRANSITION TREBLE TWILIGHT USER_PRESENCE VOLUME') def getDevices(**kwargs): """Deprecated, will be removed in v0.3. Get up to date full devices data set as a dict. From 3e7e854a8ff13112d1119ae8f37e3353b6981074 Mon Sep 17 00:00:00 2001 From: Artanicus Date: Mon, 12 Mar 2018 21:40:52 +0200 Subject: [PATCH 14/14] Fix some misleading comments --- cozify/hub.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cozify/hub.py b/cozify/hub.py index 8ec74ed..738fcea 100644 --- a/cozify/hub.py +++ b/cozify/hub.py @@ -145,7 +145,7 @@ def light_temperature(device_id, temperature=2700, transition=0, **kwargs): raise AttributeError('Device not found or not eligible for action.') def light_color(device_id, hue, saturation=1.0, transition=0, **kwargs): - """Set temperature of a light. + """Set color (hue & saturation) of a light. Args: device_id(str): ID of the device to operate on. @@ -171,7 +171,7 @@ def light_color(device_id, hue, saturation=1.0, transition=0, **kwargs): raise AttributeError('Device not found or not eligible for action.') def light_brightness(device_id, brightness, transition=0, **kwargs): - """Set temperature of a light. + """Set brightness of a light. Args: device_id(str): ID of the device to operate on.