mirror of
				https://github.com/python-kasa/python-kasa.git
				synced 2025-10-31 04:31:54 +00:00 
			
		
		
		
	Refactor and drop py2 support (#49)
* move is_off property to SmartDevice, implement is_on for bulb and use it * refactor by moving smartbulb and smartplug to their own classes * drop python2 compatibility, make flake8 happy * travis: remove 2.7, add 3.6
This commit is contained in:
		| @@ -1,9 +1,9 @@ | ||||
| sudo: false | ||||
| language: python | ||||
| python: | ||||
|   - "2.7" | ||||
|   - "3.4" | ||||
|   - "3.5" | ||||
|   - "3.6" | ||||
| install: pip install tox-travis | ||||
| script: tox | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| # flake8: noqa | ||||
| from __future__ import absolute_import | ||||
| from __future__ import unicode_literals | ||||
| from pyHS100.pyHS100 import SmartBulb, SmartPlug, TPLinkSmartHomeProtocol, SmartPlugException | ||||
| from .smartplug import SmartPlug | ||||
| from .pyHS100 import SmartPlugException, SmartDevice | ||||
| from .smartbulb import SmartBulb | ||||
| from .protocol import TPLinkSmartHomeProtocol | ||||
| @@ -4,7 +4,12 @@ import logging | ||||
| from click_datetime import Datetime | ||||
| from pprint import pformat | ||||
|  | ||||
| from pyHS100 import SmartPlug, TPLinkSmartHomeProtocol | ||||
| if sys.version_info < (3, 4): | ||||
|     print("To use this script you need python 3.4 or newer! got %s" % | ||||
|           sys.version_info) | ||||
|     sys.exit(1) | ||||
|  | ||||
| from pyHS100 import SmartPlug, TPLinkSmartHomeProtocol  # noqa: E402 | ||||
|  | ||||
| pass_dev = click.make_pass_decorator(SmartPlug) | ||||
|  | ||||
| @@ -24,7 +29,7 @@ def cli(ctx, ip, debug): | ||||
|         return | ||||
|  | ||||
|     if ip is None: | ||||
|         click.echo("You must specify the IP either by --ip or setting PYHS100_IP environment variable!") | ||||
|         click.echo("You must specify the IP!") | ||||
|         sys.exit(-1) | ||||
|  | ||||
|     plug = SmartPlug(ip) | ||||
|   | ||||
| @@ -1,6 +1,3 @@ | ||||
| from __future__ import absolute_import | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import json | ||||
| import socket | ||||
| import struct | ||||
|   | ||||
| @@ -13,21 +13,11 @@ Stroetmann which is licensed under the Apache License, Version 2.0. | ||||
| You may obtain a copy of the license at | ||||
| http://www.apache.org/licenses/LICENSE-2.0 | ||||
| """ | ||||
|  | ||||
| # python2 compatibility | ||||
| from __future__ import absolute_import | ||||
| from __future__ import unicode_literals | ||||
| from future.utils import raise_from | ||||
| try: | ||||
|     basestring | ||||
| except NameError: | ||||
|     basestring = str | ||||
|  | ||||
| import datetime | ||||
| import logging | ||||
| import socket | ||||
|  | ||||
| from pyHS100.protocol import TPLinkSmartHomeProtocol | ||||
| from .protocol import TPLinkSmartHomeProtocol | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -53,32 +43,35 @@ class SmartDevice(object): | ||||
|             protocol = TPLinkSmartHomeProtocol() | ||||
|         self.protocol = protocol | ||||
|  | ||||
|     def _query_helper(self, target, cmd, arg={}): | ||||
|     def _query_helper(self, target, cmd, arg=None): | ||||
|         """ | ||||
|         Helper returning unwrapped result object and doing error handling. | ||||
|  | ||||
|         :param target: Target system {system, time, emeter, ..} | ||||
|         :param cmd: Command to execute | ||||
|         :param arg: JSON object passed as parameter to the command, defaults to {} | ||||
|         :param arg: JSON object passed as parameter to the command | ||||
|         :return: Unwrapped result for the call. | ||||
|         :rtype: dict | ||||
|         :raises SmartPlugException: if command was not executed correctly | ||||
|         """ | ||||
|  | ||||
|         if arg is None: | ||||
|             arg = {} | ||||
|         try: | ||||
|             response = self.protocol.query( | ||||
|                 host=self.ip_address, | ||||
|                 request={target: {cmd: arg}} | ||||
|             ) | ||||
|         except Exception as ex: | ||||
|             raise_from(SmartPlugException(), ex) | ||||
|             raise SmartPlugException('Communication error') from ex | ||||
|  | ||||
|         if target not in response: | ||||
|             raise SmartPlugException("No required {} in response: {}".format(target, response)) | ||||
|             raise SmartPlugException("No required {} in response: {}" | ||||
|                                      .format(target, response)) | ||||
|  | ||||
|         result = response[target] | ||||
|         if "err_code" in result and result["err_code"] != 0: | ||||
|             raise SmartPlugException("Error on {}.{}: {}".format(target, cmd, result)) | ||||
|             raise SmartPlugException("Error on {}.{}: {}" | ||||
|                                      .format(target, cmd, result)) | ||||
|  | ||||
|         result = result[cmd] | ||||
|         del result["err_code"] | ||||
| @@ -90,6 +83,27 @@ class SmartDevice(object): | ||||
|         #  TODO use volyptuous | ||||
|         return self.get_sysinfo() | ||||
|  | ||||
|     @property | ||||
|     def is_off(self): | ||||
|         """ | ||||
|         Returns whether device is off. | ||||
|  | ||||
|         :return: True if device is off, False otherwise. | ||||
|         :rtype: bool | ||||
|         """ | ||||
|         return not self.is_on | ||||
|  | ||||
|     @property | ||||
|     def is_on(self): | ||||
|         """ | ||||
|         Returns whether the device is on. | ||||
|  | ||||
|         :return: True if the device is on, False otherwise. | ||||
|         :rtype: bool | ||||
|         :return: | ||||
|         """ | ||||
|         raise NotImplementedError("Your subclass needs to implement this.") | ||||
|  | ||||
|     def get_sysinfo(self): | ||||
|         """ | ||||
|         Retrieve system information. | ||||
| @@ -167,9 +181,10 @@ class SmartDevice(object): | ||||
|         :raises NotImplementedError: when not implemented | ||||
|         :raises SmartPlugError: on error | ||||
|         """ | ||||
|         raise NotImplementedError("Values for this call are unknown at this point.") | ||||
|         raise NotImplementedError() | ||||
|         # here just for the sake of completeness | ||||
|         # self._query_helper("system", "set_dev_icon", {"icon": "", "hash": ""}) | ||||
|         # self._query_helper("system", | ||||
|         #                    "set_dev_icon", {"icon": "", "hash": ""}) | ||||
|         # self.initialize() | ||||
|  | ||||
|     @property | ||||
| @@ -198,7 +213,10 @@ class SmartDevice(object): | ||||
|         :raises SmartPlugException: on error | ||||
|         """ | ||||
|         raise NotImplementedError("Fails with err_code == 0 with HS110.") | ||||
|         """ here just for the sake of completeness / if someone figures out why it doesn't work. | ||||
|         """ | ||||
|         here just for the sake of completeness. | ||||
|         if someone figures out why it doesn't work, | ||||
|         please create a PR :-) | ||||
|         ts_obj = { | ||||
|             "index": self.timezone["index"], | ||||
|             "hour": ts.hour, | ||||
| @@ -384,407 +402,3 @@ class SmartDevice(object): | ||||
|             return response['power_mw'] | ||||
|         else: | ||||
|             return response['power'] | ||||
|  | ||||
|  | ||||
| class SmartBulb(SmartDevice): | ||||
|     """Representation of a TP-Link Smart Bulb. | ||||
|  | ||||
|     Usage example when used as library: | ||||
|     p = SmartBulb("192.168.1.105") | ||||
|     # print the devices alias | ||||
|     print(p.alias) | ||||
|     # change state of bulb | ||||
|     p.state = "ON" | ||||
|     p.state = "OFF" | ||||
|     # query and print current state of plug | ||||
|     print(p.state) | ||||
|     # check whether the bulb supports color changes | ||||
|     if p.is_color: | ||||
|     # set the color to an HSV tuple | ||||
|     p.hsv = (100, 0, 255) | ||||
|     # get the current HSV value | ||||
|     print(p.hsv) | ||||
|     # check whether the bulb supports setting color temperature | ||||
|     if p.is_variable_color_temp: | ||||
|     # set the color temperature in Kelvin | ||||
|     p.color_temp = 3000 | ||||
|     # get the current color temperature | ||||
|     print(p.color_temp) | ||||
|     # check whether the bulb is dimmable | ||||
|     if p.is_dimmable: | ||||
|     # set the bulb to 50% brightness | ||||
|     p.brightness = 50 | ||||
|     # check the current brightness | ||||
|     print(p.brightness) | ||||
|  | ||||
|     Errors reported by the device are raised as SmartPlugExceptions, | ||||
|     and should be handled by the user of the library. | ||||
|  | ||||
|     """ | ||||
|     # bulb states | ||||
|     BULB_STATE_ON = 'ON' | ||||
|     BULB_STATE_OFF = 'OFF' | ||||
|  | ||||
|     def __init__(self, ip_address, protocol=None): | ||||
|         SmartDevice.__init__(self, ip_address, protocol) | ||||
|         self.emeter_type = "smartlife.iot.common.emeter" | ||||
|         self.emeter_units = True | ||||
|  | ||||
|     @property | ||||
|     def is_color(self): | ||||
|         """ | ||||
|         Whether the bulb supports color changes | ||||
|  | ||||
|         :return: True if the bulb supports color changes, False otherwise | ||||
|         :rtype: bool | ||||
|         """ | ||||
|         return bool(self.sys_info['is_color']) | ||||
|  | ||||
|     @property | ||||
|     def is_dimmable(self): | ||||
|         """ | ||||
|         Whether the bulb supports brightness changes | ||||
|  | ||||
|         :return: True if the bulb supports brightness changes, False otherwise | ||||
|         :rtype: bool | ||||
|         """ | ||||
|         return bool(self.sys_info['is_dimmable']) | ||||
|  | ||||
|     @property | ||||
|     def is_variable_color_temp(self): | ||||
|         """ | ||||
|         Whether the bulb supports color temperature changes | ||||
|  | ||||
|         :return: True if the bulb supports color temperature changes, False | ||||
|         otherwise | ||||
|         :rtype: bool | ||||
|         """ | ||||
|         return bool(self.sys_info['is_variable_color_temp']) | ||||
|  | ||||
|     def get_light_state(self): | ||||
|         return self._query_helper("smartlife.iot.smartbulb.lightingservice", | ||||
|                                   "get_light_state") | ||||
|  | ||||
|     def set_light_state(self, state): | ||||
|         return self._query_helper("smartlife.iot.smartbulb.lightingservice", | ||||
|                                   "transition_light_state", state) | ||||
|  | ||||
|     @property | ||||
|     def hsv(self): | ||||
|         """ | ||||
|         Returns the current HSV state of the bulb, if supported | ||||
|  | ||||
|         :return: tuple containing current hue, saturation and value (0-255) | ||||
|         :rtype: tuple | ||||
|         """ | ||||
|  | ||||
|         if not self.is_color: | ||||
|             return None | ||||
|  | ||||
|         light_state = self.get_light_state() | ||||
|         if light_state['on_off'] == 0: | ||||
|             hue = light_state['dft_on_state']['hue'] | ||||
|             saturation = light_state['dft_on_state']['saturation'] | ||||
|             value = int(light_state['dft_on_state']['brightness'] * 255 / 100) | ||||
|         else: | ||||
|             hue = light_state['hue'] | ||||
|             saturation = light_state['saturation'] | ||||
|             value = int(light_state['brightness'] * 255 / 100) | ||||
|  | ||||
|         return(hue, saturation, value) | ||||
|  | ||||
|     @hsv.setter | ||||
|     def hsv(self, state): | ||||
|         """ | ||||
|         Sets new HSV, if supported | ||||
|  | ||||
|         :param tuple state: hue, saturation and value (0-255 each) | ||||
|         """ | ||||
|         if not self.is_color: | ||||
|             return None | ||||
|  | ||||
|         light_state = { | ||||
|             "hue": state[0], | ||||
|             "saturation": state[1], | ||||
|             "brightness": int(state[2] * 100 / 255), | ||||
|             "color_temp": 0 | ||||
|             } | ||||
|         return self.set_light_state(light_state) | ||||
|  | ||||
|     @property | ||||
|     def color_temp(self): | ||||
|         """ | ||||
|         Color temperature of the device, if supported | ||||
|  | ||||
|         :return: Color temperature in Kelvin | ||||
|         :rtype: int | ||||
|         """ | ||||
|         if not self.is_variable_color_temp: | ||||
|             return None | ||||
|  | ||||
|         light_state = self.get_light_state() | ||||
|         if light_state['on_off'] == 0: | ||||
|             return(light_state['dft_on_state']['color_temp']) | ||||
|         else: | ||||
|             return(light_state['color_temp']) | ||||
|  | ||||
|     @color_temp.setter | ||||
|     def color_temp(self, temp): | ||||
|         """ | ||||
|         Set the color temperature of the device, if supported | ||||
|  | ||||
|         :param int temp: The new color temperature, in Kelvin | ||||
|         """ | ||||
|         if not self.is_variable_color_temp: | ||||
|             return None | ||||
|  | ||||
|         light_state = { | ||||
|             "color_temp": temp, | ||||
|         } | ||||
|         return self.set_light_state(light_state) | ||||
|  | ||||
|     @property | ||||
|     def brightness(self): | ||||
|         """ | ||||
|         Current brightness of the device, if supported | ||||
|  | ||||
|         :return: brightness in percent | ||||
|         :rtype: int | ||||
|         """ | ||||
|         if not self.is_dimmable: | ||||
|             return None | ||||
|  | ||||
|         light_state = self.get_light_state() | ||||
|         if light_state['on_off'] == 0: | ||||
|             return(light_state['dft_on_state']['brightness']) | ||||
|         else: | ||||
|             return(light_state['brightness']) | ||||
|  | ||||
|     @brightness.setter | ||||
|     def brightness(self, brightness): | ||||
|         """ | ||||
|         Set the current brightness of the device, if supported | ||||
|  | ||||
|         :param int brightness: brightness in percent | ||||
|         """ | ||||
|         if not self.is_dimmable: | ||||
|             return None | ||||
|  | ||||
|         light_state = { | ||||
|             "brightness": brightness, | ||||
|         } | ||||
|         return self.set_light_state(light_state) | ||||
|  | ||||
|     @property | ||||
|     def state(self): | ||||
|         """ | ||||
|         Retrieve the bulb state | ||||
|  | ||||
|         :returns: one of | ||||
|                   BULB_STATE_ON | ||||
|                   BULB_STATE_OFF | ||||
|         :rtype: str | ||||
|         """ | ||||
|         light_state = self.get_light_state() | ||||
|         if light_state['on_off']: | ||||
|             return self.BULB_STATE_ON | ||||
|         return self.BULB_STATE_OFF | ||||
|  | ||||
|     @state.setter | ||||
|     def state(self, bulb_state): | ||||
|         """ | ||||
|         Set the new bulb state | ||||
|  | ||||
|         :param bulb_state: one of | ||||
|                            BULB_STATE_ON | ||||
|                            BULB_STATE_OFF | ||||
|         """ | ||||
|         print(bulb_state) | ||||
|         print(self.BULB_STATE_ON) | ||||
|         print(self.BULB_STATE_OFF) | ||||
|         if bulb_state == self.BULB_STATE_ON: | ||||
|             bulb_state = 1 | ||||
|         elif bulb_state == self.BULB_STATE_OFF: | ||||
|             bulb_state = 0 | ||||
|         else: | ||||
|             raise ValueError | ||||
|          | ||||
|         light_state = { | ||||
|             "on_off": bulb_state, | ||||
|         } | ||||
|         return self.set_light_state(light_state) | ||||
|  | ||||
|     @property | ||||
|     def has_emeter(self): | ||||
|         return True | ||||
|  | ||||
|  | ||||
| class SmartPlug(SmartDevice): | ||||
|     """Representation of a TP-Link Smart Switch. | ||||
|  | ||||
|     Usage example when used as library: | ||||
|     p = SmartPlug("192.168.1.105") | ||||
|     # print the devices alias | ||||
|     print(p.alias) | ||||
|     # change state of plug | ||||
|     p.state = "ON" | ||||
|     p.state = "OFF" | ||||
|     # query and print current state of plug | ||||
|     print(p.state) | ||||
|  | ||||
|     Errors reported by the device are raised as SmartPlugExceptions, | ||||
|     and should be handled by the user of the library. | ||||
|  | ||||
|     Note: | ||||
|     The library references the same structure as defined for the D-Link Switch | ||||
|     """ | ||||
|     # switch states | ||||
|     SWITCH_STATE_ON = 'ON' | ||||
|     SWITCH_STATE_OFF = 'OFF' | ||||
|     SWITCH_STATE_UNKNOWN = 'UNKNOWN' | ||||
|  | ||||
|     # possible device features | ||||
|     FEATURE_ENERGY_METER = 'ENE' | ||||
|     FEATURE_TIMER = 'TIM' | ||||
|  | ||||
|     ALL_FEATURES = (FEATURE_ENERGY_METER, FEATURE_TIMER) | ||||
|  | ||||
|     def __init__(self, ip_address, protocol=None): | ||||
|         SmartDevice.__init__(self, ip_address, protocol) | ||||
|         self.emeter_type = "emeter" | ||||
|         self.emeter_units = False | ||||
|  | ||||
|     @property | ||||
|     def state(self): | ||||
|         """ | ||||
|         Retrieve the switch state | ||||
|  | ||||
|         :returns: one of | ||||
|                   SWITCH_STATE_ON | ||||
|                   SWITCH_STATE_OFF | ||||
|                   SWITCH_STATE_UNKNOWN | ||||
|         :rtype: str | ||||
|         """ | ||||
|         relay_state = self.sys_info['relay_state'] | ||||
|  | ||||
|         if relay_state == 0: | ||||
|             return SmartPlug.SWITCH_STATE_OFF | ||||
|         elif relay_state == 1: | ||||
|             return SmartPlug.SWITCH_STATE_ON | ||||
|         else: | ||||
|             _LOGGER.warning("Unknown state %s returned.", relay_state) | ||||
|             return SmartPlug.SWITCH_STATE_UNKNOWN | ||||
|  | ||||
|     @state.setter | ||||
|     def state(self, value): | ||||
|         """ | ||||
|         Set the new switch state | ||||
|  | ||||
|         :param value: one of | ||||
|                     SWITCH_STATE_ON | ||||
|                     SWITCH_STATE_OFF | ||||
|         :raises ValueError: on invalid state | ||||
|         :raises SmartPlugException: on error | ||||
|  | ||||
|         """ | ||||
|         if not isinstance(value, basestring): | ||||
|             raise ValueError("State must be str, not of %s.", type(value)) | ||||
|         elif value.upper() == SmartPlug.SWITCH_STATE_ON: | ||||
|             self.turn_on() | ||||
|         elif value.upper() == SmartPlug.SWITCH_STATE_OFF: | ||||
|             self.turn_off() | ||||
|         else: | ||||
|             raise ValueError("State %s is not valid.", value) | ||||
|  | ||||
|     @property | ||||
|     def is_on(self): | ||||
|         """ | ||||
|         Returns whether device is on. | ||||
|  | ||||
|         :return: True if device is on, False otherwise | ||||
|         """ | ||||
|         return bool(self.sys_info['relay_state']) | ||||
|  | ||||
|     @property | ||||
|     def is_off(self): | ||||
|         """ | ||||
|         Returns whether device is off. | ||||
|  | ||||
|         :return: True if device is off, False otherwise. | ||||
|          :rtype: bool | ||||
|         """ | ||||
|         return not self.is_on | ||||
|  | ||||
|     def turn_on(self): | ||||
|         """ | ||||
|         Turn the switch on. | ||||
|  | ||||
|         :raises SmartPlugException: on error | ||||
|         """ | ||||
|         self._query_helper("system", "set_relay_state", {"state": 1}) | ||||
|  | ||||
|     def turn_off(self): | ||||
|         """ | ||||
|         Turn the switch off. | ||||
|  | ||||
|         :raises SmartPlugException: on error | ||||
|         """ | ||||
|         self._query_helper("system", "set_relay_state", {"state": 0}) | ||||
|  | ||||
|     @property | ||||
|     def has_emeter(self): | ||||
|         """ | ||||
|         Checks feature list for energey meter support. | ||||
|  | ||||
|         :return: True if energey meter is available | ||||
|                  False if energymeter is missing | ||||
|         """ | ||||
|         return SmartPlug.FEATURE_ENERGY_METER in self.features | ||||
|  | ||||
|     @property | ||||
|     def features(self): | ||||
|         """ | ||||
|         Returns features of the devices | ||||
|  | ||||
|         :return: list of features | ||||
|         :rtype: list | ||||
|         """ | ||||
|         features = self.sys_info['feature'].split(':') | ||||
|  | ||||
|         for feature in features: | ||||
|             if feature not in SmartPlug.ALL_FEATURES: | ||||
|                 _LOGGER.warning("Unknown feature %s on device %s.", | ||||
|                                 feature, self.model) | ||||
|  | ||||
|         return features | ||||
|  | ||||
|     @property | ||||
|     def led(self): | ||||
|         """ | ||||
|         Returns the state of the led. | ||||
|  | ||||
|         :return: True if led is on, False otherwise | ||||
|         :rtype: bool | ||||
|         """ | ||||
|         return bool(1 - self.sys_info["led_off"]) | ||||
|  | ||||
|     @led.setter | ||||
|     def led(self, state): | ||||
|         """ | ||||
|         Sets the state of the led (night mode) | ||||
|  | ||||
|         :param bool state: True to set led on, False to set led off | ||||
|         :raises SmartPlugException: on error | ||||
|         """ | ||||
|         self._query_helper("system", "set_led_off", {"off": int(not state)}) | ||||
|  | ||||
|     @property | ||||
|     def on_since(self): | ||||
|         """ | ||||
|         Returns pretty-printed on-time | ||||
|  | ||||
|         :return: datetime for on since | ||||
|         :rtype: datetime | ||||
|         """ | ||||
|         return datetime.datetime.now() - \ | ||||
|             datetime.timedelta(seconds=self.sys_info["on_time"]) | ||||
|   | ||||
							
								
								
									
										238
									
								
								pyHS100/smartbulb.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								pyHS100/smartbulb.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,238 @@ | ||||
| from .pyHS100 import SmartDevice | ||||
|  | ||||
|  | ||||
| class SmartBulb(SmartDevice): | ||||
|     """Representation of a TP-Link Smart Bulb. | ||||
|  | ||||
|     Usage example when used as library: | ||||
|     p = SmartBulb("192.168.1.105") | ||||
|     # print the devices alias | ||||
|     print(p.alias) | ||||
|     # change state of bulb | ||||
|     p.state = "ON" | ||||
|     p.state = "OFF" | ||||
|     # query and print current state of plug | ||||
|     print(p.state) | ||||
|     # check whether the bulb supports color changes | ||||
|     if p.is_color: | ||||
|     # set the color to an HSV tuple | ||||
|     p.hsv = (100, 0, 255) | ||||
|     # get the current HSV value | ||||
|     print(p.hsv) | ||||
|     # check whether the bulb supports setting color temperature | ||||
|     if p.is_variable_color_temp: | ||||
|     # set the color temperature in Kelvin | ||||
|     p.color_temp = 3000 | ||||
|     # get the current color temperature | ||||
|     print(p.color_temp) | ||||
|     # check whether the bulb is dimmable | ||||
|     if p.is_dimmable: | ||||
|     # set the bulb to 50% brightness | ||||
|     p.brightness = 50 | ||||
|     # check the current brightness | ||||
|     print(p.brightness) | ||||
|  | ||||
|     Errors reported by the device are raised as SmartPlugExceptions, | ||||
|     and should be handled by the user of the library. | ||||
|  | ||||
|     """ | ||||
|     # bulb states | ||||
|     BULB_STATE_ON = 'ON' | ||||
|     BULB_STATE_OFF = 'OFF' | ||||
|  | ||||
|     def __init__(self, ip_address, protocol=None): | ||||
|         SmartDevice.__init__(self, ip_address, protocol) | ||||
|         self.emeter_type = "smartlife.iot.common.emeter" | ||||
|         self.emeter_units = True | ||||
|  | ||||
|     @property | ||||
|     def is_color(self): | ||||
|         """ | ||||
|         Whether the bulb supports color changes | ||||
|  | ||||
|         :return: True if the bulb supports color changes, False otherwise | ||||
|         :rtype: bool | ||||
|         """ | ||||
|         return bool(self.sys_info['is_color']) | ||||
|  | ||||
|     @property | ||||
|     def is_dimmable(self): | ||||
|         """ | ||||
|         Whether the bulb supports brightness changes | ||||
|  | ||||
|         :return: True if the bulb supports brightness changes, False otherwise | ||||
|         :rtype: bool | ||||
|         """ | ||||
|         return bool(self.sys_info['is_dimmable']) | ||||
|  | ||||
|     @property | ||||
|     def is_variable_color_temp(self): | ||||
|         """ | ||||
|         Whether the bulb supports color temperature changes | ||||
|  | ||||
|         :return: True if the bulb supports color temperature changes, False | ||||
|         otherwise | ||||
|         :rtype: bool | ||||
|         """ | ||||
|         return bool(self.sys_info['is_variable_color_temp']) | ||||
|  | ||||
|     def get_light_state(self): | ||||
|         return self._query_helper("smartlife.iot.smartbulb.lightingservice", | ||||
|                                   "get_light_state") | ||||
|  | ||||
|     def set_light_state(self, state): | ||||
|         return self._query_helper("smartlife.iot.smartbulb.lightingservice", | ||||
|                                   "transition_light_state", state) | ||||
|  | ||||
|     @property | ||||
|     def hsv(self): | ||||
|         """ | ||||
|         Returns the current HSV state of the bulb, if supported | ||||
|  | ||||
|         :return: tuple containing current hue, saturation and value (0-255) | ||||
|         :rtype: tuple | ||||
|         """ | ||||
|  | ||||
|         if not self.is_color: | ||||
|             return None | ||||
|  | ||||
|         light_state = self.get_light_state() | ||||
|         if not self.is_on: | ||||
|             hue = light_state['dft_on_state']['hue'] | ||||
|             saturation = light_state['dft_on_state']['saturation'] | ||||
|             value = int(light_state['dft_on_state']['brightness'] * 255 / 100) | ||||
|         else: | ||||
|             hue = light_state['hue'] | ||||
|             saturation = light_state['saturation'] | ||||
|             value = int(light_state['brightness'] * 255 / 100) | ||||
|  | ||||
|         return(hue, saturation, value) | ||||
|  | ||||
|     @hsv.setter | ||||
|     def hsv(self, state): | ||||
|         """ | ||||
|         Sets new HSV, if supported | ||||
|  | ||||
|         :param tuple state: hue, saturation and value (0-255 each) | ||||
|         """ | ||||
|         if not self.is_color: | ||||
|             return None | ||||
|  | ||||
|         light_state = { | ||||
|             "hue": state[0], | ||||
|             "saturation": state[1], | ||||
|             "brightness": int(state[2] * 100 / 255), | ||||
|             "color_temp": 0 | ||||
|             } | ||||
|         return self.set_light_state(light_state) | ||||
|  | ||||
|     @property | ||||
|     def color_temp(self): | ||||
|         """ | ||||
|         Color temperature of the device, if supported | ||||
|  | ||||
|         :return: Color temperature in Kelvin | ||||
|         :rtype: int | ||||
|         """ | ||||
|         if not self.is_variable_color_temp: | ||||
|             return None | ||||
|  | ||||
|         light_state = self.get_light_state() | ||||
|         if not self.is_on: | ||||
|             return(light_state['dft_on_state']['color_temp']) | ||||
|         else: | ||||
|             return(light_state['color_temp']) | ||||
|  | ||||
|     @color_temp.setter | ||||
|     def color_temp(self, temp): | ||||
|         """ | ||||
|         Set the color temperature of the device, if supported | ||||
|  | ||||
|         :param int temp: The new color temperature, in Kelvin | ||||
|         """ | ||||
|         if not self.is_variable_color_temp: | ||||
|             return None | ||||
|  | ||||
|         light_state = { | ||||
|             "color_temp": temp, | ||||
|         } | ||||
|         return self.set_light_state(light_state) | ||||
|  | ||||
|     @property | ||||
|     def brightness(self): | ||||
|         """ | ||||
|         Current brightness of the device, if supported | ||||
|  | ||||
|         :return: brightness in percent | ||||
|         :rtype: int | ||||
|         """ | ||||
|         if not self.is_dimmable: | ||||
|             return None | ||||
|  | ||||
|         light_state = self.get_light_state() | ||||
|         if not self.is_on: | ||||
|             return(light_state['dft_on_state']['brightness']) | ||||
|         else: | ||||
|             return(light_state['brightness']) | ||||
|  | ||||
|     @brightness.setter | ||||
|     def brightness(self, brightness): | ||||
|         """ | ||||
|         Set the current brightness of the device, if supported | ||||
|  | ||||
|         :param int brightness: brightness in percent | ||||
|         """ | ||||
|         if not self.is_dimmable: | ||||
|             return None | ||||
|  | ||||
|         light_state = { | ||||
|             "brightness": brightness, | ||||
|         } | ||||
|         return self.set_light_state(light_state) | ||||
|  | ||||
|     @property | ||||
|     def state(self): | ||||
|         """ | ||||
|         Retrieve the bulb state | ||||
|  | ||||
|         :returns: one of | ||||
|                   BULB_STATE_ON | ||||
|                   BULB_STATE_OFF | ||||
|         :rtype: str | ||||
|         """ | ||||
|         light_state = self.get_light_state() | ||||
|         if light_state['on_off']: | ||||
|             return self.BULB_STATE_ON | ||||
|         return self.BULB_STATE_OFF | ||||
|  | ||||
|     @property | ||||
|     def is_on(self): | ||||
|         return self.state == self.BULB_STATE_ON | ||||
|  | ||||
|     @state.setter | ||||
|     def state(self, bulb_state): | ||||
|         """ | ||||
|         Set the new bulb state | ||||
|  | ||||
|         :param bulb_state: one of | ||||
|                            BULB_STATE_ON | ||||
|                            BULB_STATE_OFF | ||||
|         """ | ||||
|         print(bulb_state) | ||||
|         print(self.BULB_STATE_ON) | ||||
|         print(self.BULB_STATE_OFF) | ||||
|         if bulb_state == self.BULB_STATE_ON: | ||||
|             bulb_state = 1 | ||||
|         elif bulb_state == self.BULB_STATE_OFF: | ||||
|             bulb_state = 0 | ||||
|         else: | ||||
|             raise ValueError | ||||
|  | ||||
|         light_state = { | ||||
|             "on_off": bulb_state, | ||||
|         } | ||||
|         return self.set_light_state(light_state) | ||||
|  | ||||
|     @property | ||||
|     def has_emeter(self): | ||||
|         return True | ||||
							
								
								
									
										167
									
								
								pyHS100/smartplug.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								pyHS100/smartplug.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,167 @@ | ||||
| import datetime | ||||
| import logging | ||||
|  | ||||
| from .pyHS100 import SmartDevice | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class SmartPlug(SmartDevice): | ||||
|     """Representation of a TP-Link Smart Switch. | ||||
|  | ||||
|     Usage example when used as library: | ||||
|     p = SmartPlug("192.168.1.105") | ||||
|     # print the devices alias | ||||
|     print(p.alias) | ||||
|     # change state of plug | ||||
|     p.state = "ON" | ||||
|     p.state = "OFF" | ||||
|     # query and print current state of plug | ||||
|     print(p.state) | ||||
|  | ||||
|     Errors reported by the device are raised as SmartPlugExceptions, | ||||
|     and should be handled by the user of the library. | ||||
|  | ||||
|     Note: | ||||
|     The library references the same structure as defined for the D-Link Switch | ||||
|     """ | ||||
|     # switch states | ||||
|     SWITCH_STATE_ON = 'ON' | ||||
|     SWITCH_STATE_OFF = 'OFF' | ||||
|     SWITCH_STATE_UNKNOWN = 'UNKNOWN' | ||||
|  | ||||
|     # possible device features | ||||
|     FEATURE_ENERGY_METER = 'ENE' | ||||
|     FEATURE_TIMER = 'TIM' | ||||
|  | ||||
|     ALL_FEATURES = (FEATURE_ENERGY_METER, FEATURE_TIMER) | ||||
|  | ||||
|     def __init__(self, ip_address, protocol=None): | ||||
|         SmartDevice.__init__(self, ip_address, protocol) | ||||
|         self.emeter_type = "emeter" | ||||
|         self.emeter_units = False | ||||
|  | ||||
|     @property | ||||
|     def state(self): | ||||
|         """ | ||||
|         Retrieve the switch state | ||||
|  | ||||
|         :returns: one of | ||||
|                   SWITCH_STATE_ON | ||||
|                   SWITCH_STATE_OFF | ||||
|                   SWITCH_STATE_UNKNOWN | ||||
|         :rtype: str | ||||
|         """ | ||||
|         relay_state = self.sys_info['relay_state'] | ||||
|  | ||||
|         if relay_state == 0: | ||||
|             return SmartPlug.SWITCH_STATE_OFF | ||||
|         elif relay_state == 1: | ||||
|             return SmartPlug.SWITCH_STATE_ON | ||||
|         else: | ||||
|             _LOGGER.warning("Unknown state %s returned.", relay_state) | ||||
|             return SmartPlug.SWITCH_STATE_UNKNOWN | ||||
|  | ||||
|     @state.setter | ||||
|     def state(self, value): | ||||
|         """ | ||||
|         Set the new switch state | ||||
|  | ||||
|         :param value: one of | ||||
|                     SWITCH_STATE_ON | ||||
|                     SWITCH_STATE_OFF | ||||
|         :raises ValueError: on invalid state | ||||
|         :raises SmartPlugException: on error | ||||
|  | ||||
|         """ | ||||
|         if not isinstance(value, str): | ||||
|             raise ValueError("State must be str, not of %s.", type(value)) | ||||
|         elif value.upper() == SmartPlug.SWITCH_STATE_ON: | ||||
|             self.turn_on() | ||||
|         elif value.upper() == SmartPlug.SWITCH_STATE_OFF: | ||||
|             self.turn_off() | ||||
|         else: | ||||
|             raise ValueError("State %s is not valid.", value) | ||||
|  | ||||
|     @property | ||||
|     def is_on(self): | ||||
|         """ | ||||
|         Returns whether device is on. | ||||
|  | ||||
|         :return: True if device is on, False otherwise | ||||
|         """ | ||||
|         return bool(self.sys_info['relay_state']) | ||||
|  | ||||
|     def turn_on(self): | ||||
|         """ | ||||
|         Turn the switch on. | ||||
|  | ||||
|         :raises SmartPlugException: on error | ||||
|         """ | ||||
|         self._query_helper("system", "set_relay_state", {"state": 1}) | ||||
|  | ||||
|     def turn_off(self): | ||||
|         """ | ||||
|         Turn the switch off. | ||||
|  | ||||
|         :raises SmartPlugException: on error | ||||
|         """ | ||||
|         self._query_helper("system", "set_relay_state", {"state": 0}) | ||||
|  | ||||
|     @property | ||||
|     def has_emeter(self): | ||||
|         """ | ||||
|         Checks feature list for energey meter support. | ||||
|  | ||||
|         :return: True if energey meter is available | ||||
|                  False if energymeter is missing | ||||
|         """ | ||||
|         return SmartPlug.FEATURE_ENERGY_METER in self.features | ||||
|  | ||||
|     @property | ||||
|     def features(self): | ||||
|         """ | ||||
|         Returns features of the devices | ||||
|  | ||||
|         :return: list of features | ||||
|         :rtype: list | ||||
|         """ | ||||
|         features = self.sys_info['feature'].split(':') | ||||
|  | ||||
|         for feature in features: | ||||
|             if feature not in SmartPlug.ALL_FEATURES: | ||||
|                 _LOGGER.warning("Unknown feature %s on device %s.", | ||||
|                                 feature, self.model) | ||||
|  | ||||
|         return features | ||||
|  | ||||
|     @property | ||||
|     def led(self): | ||||
|         """ | ||||
|         Returns the state of the led. | ||||
|  | ||||
|         :return: True if led is on, False otherwise | ||||
|         :rtype: bool | ||||
|         """ | ||||
|         return bool(1 - self.sys_info["led_off"]) | ||||
|  | ||||
|     @led.setter | ||||
|     def led(self, state): | ||||
|         """ | ||||
|         Sets the state of the led (night mode) | ||||
|  | ||||
|         :param bool state: True to set led on, False to set led off | ||||
|         :raises SmartPlugException: on error | ||||
|         """ | ||||
|         self._query_helper("system", "set_led_off", {"off": int(not state)}) | ||||
|  | ||||
|     @property | ||||
|     def on_since(self): | ||||
|         """ | ||||
|         Returns pretty-printed on-time | ||||
|  | ||||
|         :return: datetime for on since | ||||
|         :rtype: datetime | ||||
|         """ | ||||
|         return datetime.datetime.now() - \ | ||||
|             datetime.timedelta(seconds=self.sys_info["on_time"]) | ||||
| @@ -1,5 +1,5 @@ | ||||
| from pyHS100.protocol import TPLinkSmartHomeProtocol | ||||
| from pyHS100 import SmartPlugException | ||||
| from ..protocol import TPLinkSmartHomeProtocol | ||||
| from .. import SmartPlugException | ||||
| import logging | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,22 +1,13 @@ | ||||
| from __future__ import absolute_import | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| from unittest import TestCase, skip, skipIf | ||||
| from voluptuous import Schema, Invalid, All, Range | ||||
| from functools import partial | ||||
|  | ||||
| from pyHS100 import SmartBulb, SmartPlugException | ||||
| from pyHS100.tests.fakes import FakeTransportProtocol, sysinfo_lb130 | ||||
| from .. import SmartBulb, SmartPlugException | ||||
| from .fakes import FakeTransportProtocol, sysinfo_lb130 | ||||
|  | ||||
| BULB_IP = '192.168.250.186' | ||||
| SKIP_STATE_TESTS = False | ||||
|  | ||||
| # python2 compatibility | ||||
| try: | ||||
|     basestring | ||||
| except NameError: | ||||
|     basestring = str | ||||
|  | ||||
|  | ||||
| def check_int_bool(x): | ||||
|     if x != 0 and x != 1: | ||||
| @@ -37,18 +28,18 @@ class TestSmartBulb(TestCase): | ||||
|     # as well as to check that faked devices are operating properly. | ||||
|     sysinfo_schema = Schema({ | ||||
|         'active_mode': check_mode, | ||||
|         'alias': basestring, | ||||
|         'alias': str, | ||||
|         'ctrl_protocols': { | ||||
|             'name': basestring, | ||||
|             'version': basestring, | ||||
|             'name': str, | ||||
|             'version': str, | ||||
|         }, | ||||
|         'description': basestring, | ||||
|         'dev_state': basestring, | ||||
|         'deviceId': basestring, | ||||
|         'disco_ver': basestring, | ||||
|         'description': str, | ||||
|         'dev_state': str, | ||||
|         'deviceId': str, | ||||
|         'disco_ver': str, | ||||
|         'heapsize': int, | ||||
|         'hwId': basestring, | ||||
|         'hw_ver': basestring, | ||||
|         'hwId': str, | ||||
|         'hw_ver': str, | ||||
|         'is_color': check_int_bool, | ||||
|         'is_dimmable': check_int_bool, | ||||
|         'is_factory': bool, | ||||
| @@ -57,14 +48,14 @@ class TestSmartBulb(TestCase): | ||||
|             'brightness': All(int, Range(min=0, max=100)), | ||||
|             'color_temp': int, | ||||
|             'hue': All(int, Range(min=0, max=255)), | ||||
|             'mode': basestring, | ||||
|             'mode': str, | ||||
|             'on_off': check_int_bool, | ||||
|             'saturation': All(int, Range(min=0, max=255)), | ||||
|         }, | ||||
|         'mic_mac': basestring, | ||||
|         'mic_type': basestring, | ||||
|         'model': basestring, | ||||
|         'oemId': basestring, | ||||
|         'mic_mac': str, | ||||
|         'mic_type': str, | ||||
|         'model': str, | ||||
|         'oemId': str, | ||||
|         'preferred_state': [{ | ||||
|             'brightness': All(int, Range(min=0, max=100)), | ||||
|             'color_temp': int, | ||||
| @@ -73,7 +64,7 @@ class TestSmartBulb(TestCase): | ||||
|             'saturation': All(int, Range(min=0, max=255)), | ||||
|         }], | ||||
|         'rssi': All(int, Range(max=0)), | ||||
|         'sw_ver': basestring, | ||||
|         'sw_ver': str, | ||||
|     }) | ||||
|  | ||||
|     current_consumption_schema = Schema({ | ||||
| @@ -81,10 +72,10 @@ class TestSmartBulb(TestCase): | ||||
|     }) | ||||
|  | ||||
|     tz_schema = Schema({ | ||||
|         'zone_str': basestring, | ||||
|         'zone_str': str, | ||||
|         'dst_offset': int, | ||||
|         'index': All(int, Range(min=0)), | ||||
|         'tz_str': basestring, | ||||
|         'tz_str': str, | ||||
|     }) | ||||
|  | ||||
|     def setUp(self): | ||||
| @@ -185,7 +176,7 @@ class TestSmartBulb(TestCase): | ||||
|     def test_alias(self): | ||||
|         test_alias = "TEST1234" | ||||
|         original = self.bulb.alias | ||||
|         self.assertTrue(isinstance(original, basestring)) | ||||
|         self.assertTrue(isinstance(original, str)) | ||||
|         self.bulb.alias = test_alias | ||||
|         self.assertEqual(self.bulb.alias, test_alias) | ||||
|         self.bulb.alias = original | ||||
|   | ||||
| @@ -1,8 +1,5 @@ | ||||
| from __future__ import absolute_import | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| from unittest import TestCase | ||||
| from pyHS100.protocol import TPLinkSmartHomeProtocol | ||||
| from ..protocol import TPLinkSmartHomeProtocol | ||||
| import json | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,24 +1,15 @@ | ||||
| from __future__ import absolute_import | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| from unittest import TestCase, skip, skipIf | ||||
| from voluptuous import Schema, Invalid, All, Range | ||||
| from functools import partial | ||||
| import datetime | ||||
| import re | ||||
|  | ||||
| from pyHS100 import SmartPlug, SmartPlugException | ||||
| from pyHS100.tests.fakes import FakeTransportProtocol, sysinfo_hs110 | ||||
| from .. import SmartPlug, SmartPlugException | ||||
| from .fakes import FakeTransportProtocol, sysinfo_hs110 | ||||
|  | ||||
| PLUG_IP = '192.168.250.186' | ||||
| SKIP_STATE_TESTS = False | ||||
|  | ||||
| # python2 compatibility | ||||
| try: | ||||
|     basestring | ||||
| except NameError: | ||||
|     basestring = str | ||||
|  | ||||
|  | ||||
| def check_int_bool(x): | ||||
|     if x != 0 and x != 1: | ||||
| @@ -45,25 +36,25 @@ class TestSmartPlug(TestCase): | ||||
|     # as well as to check that faked devices are operating properly. | ||||
|     sysinfo_schema = Schema({ | ||||
|         'active_mode': check_mode, | ||||
|         'alias': basestring, | ||||
|         'dev_name': basestring, | ||||
|         'deviceId': basestring, | ||||
|         'feature': basestring, | ||||
|         'fwId': basestring, | ||||
|         'hwId': basestring, | ||||
|         'hw_ver': basestring, | ||||
|         'icon_hash': basestring, | ||||
|         'alias': str, | ||||
|         'dev_name': str, | ||||
|         'deviceId': str, | ||||
|         'feature': str, | ||||
|         'fwId': str, | ||||
|         'hwId': str, | ||||
|         'hw_ver': str, | ||||
|         'icon_hash': str, | ||||
|         'latitude': All(float, Range(min=-90, max=90)), | ||||
|         'led_off': check_int_bool, | ||||
|         'longitude': All(float, Range(min=-180, max=180)), | ||||
|         'mac': check_mac, | ||||
|         'model': basestring, | ||||
|         'oemId': basestring, | ||||
|         'model': str, | ||||
|         'oemId': str, | ||||
|         'on_time': int, | ||||
|         'relay_state': int, | ||||
|         'rssi': All(int, Range(max=0)), | ||||
|         'sw_ver': basestring, | ||||
|         'type': basestring, | ||||
|         'sw_ver': str, | ||||
|         'type': str, | ||||
|         'updating': check_int_bool, | ||||
|     }) | ||||
|  | ||||
| @@ -75,10 +66,10 @@ class TestSmartPlug(TestCase): | ||||
|     }) | ||||
|  | ||||
|     tz_schema = Schema({ | ||||
|         'zone_str': basestring, | ||||
|         'zone_str': str, | ||||
|         'dst_offset': int, | ||||
|         'index': All(int, Range(min=0)), | ||||
|         'tz_str': basestring, | ||||
|         'tz_str': str, | ||||
|     }) | ||||
|  | ||||
|     def setUp(self): | ||||
| @@ -195,7 +186,7 @@ class TestSmartPlug(TestCase): | ||||
|     def test_alias(self): | ||||
|         test_alias = "TEST1234" | ||||
|         original = self.plug.alias | ||||
|         self.assertTrue(isinstance(original, basestring)) | ||||
|         self.assertTrue(isinstance(original, str)) | ||||
|         self.plug.alias = test_alias | ||||
|         self.assertEqual(self.plug.alias, test_alias) | ||||
|         self.plug.alias = original | ||||
|   | ||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| @@ -8,7 +8,7 @@ setup(name='pyHS100', | ||||
|       author_email='sean@gadgetreactor.com', | ||||
|       license='GPLv3', | ||||
|       packages=['pyHS100'], | ||||
|       install_requires=['click', 'click-datetime', 'future'], | ||||
|       install_requires=['click', 'click-datetime'], | ||||
|       entry_points={ | ||||
|             'console_scripts': [ | ||||
|                   'pyhs100=pyHS100.cli:cli', | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Teemu R
					Teemu R