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:
Teemu R 2017-04-14 14:24:58 +02:00 committed by GitHub
parent c9662fcb7b
commit 86f14642c8
13 changed files with 497 additions and 497 deletions

View File

@ -1,9 +1,9 @@
sudo: false sudo: false
language: python language: python
python: python:
- "2.7"
- "3.4" - "3.4"
- "3.5" - "3.5"
- "3.6"
install: pip install tox-travis install: pip install tox-travis
script: tox script: tox

View File

@ -1,4 +1,5 @@
# flake8: noqa # flake8: noqa
from __future__ import absolute_import from .smartplug import SmartPlug
from __future__ import unicode_literals from .pyHS100 import SmartPlugException, SmartDevice
from pyHS100.pyHS100 import SmartBulb, SmartPlug, TPLinkSmartHomeProtocol, SmartPlugException from .smartbulb import SmartBulb
from .protocol import TPLinkSmartHomeProtocol

View File

@ -4,7 +4,12 @@ import logging
from click_datetime import Datetime from click_datetime import Datetime
from pprint import pformat 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) pass_dev = click.make_pass_decorator(SmartPlug)
@ -24,7 +29,7 @@ def cli(ctx, ip, debug):
return return
if ip is None: 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) sys.exit(-1)
plug = SmartPlug(ip) plug = SmartPlug(ip)

View File

@ -1,6 +1,3 @@
from __future__ import absolute_import
from __future__ import unicode_literals
import json import json
import socket import socket
import struct import struct

View File

@ -13,21 +13,11 @@ Stroetmann which is licensed under the Apache License, Version 2.0.
You may obtain a copy of the license at You may obtain a copy of the license at
http://www.apache.org/licenses/LICENSE-2.0 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 datetime
import logging import logging
import socket import socket
from pyHS100.protocol import TPLinkSmartHomeProtocol from .protocol import TPLinkSmartHomeProtocol
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -53,32 +43,35 @@ class SmartDevice(object):
protocol = TPLinkSmartHomeProtocol() protocol = TPLinkSmartHomeProtocol()
self.protocol = protocol 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. Helper returning unwrapped result object and doing error handling.
:param target: Target system {system, time, emeter, ..} :param target: Target system {system, time, emeter, ..}
:param cmd: Command to execute :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. :return: Unwrapped result for the call.
:rtype: dict :rtype: dict
:raises SmartPlugException: if command was not executed correctly :raises SmartPlugException: if command was not executed correctly
""" """
if arg is None:
arg = {}
try: try:
response = self.protocol.query( response = self.protocol.query(
host=self.ip_address, host=self.ip_address,
request={target: {cmd: arg}} request={target: {cmd: arg}}
) )
except Exception as ex: except Exception as ex:
raise_from(SmartPlugException(), ex) raise SmartPlugException('Communication error') from ex
if target not in response: 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] result = response[target]
if "err_code" in result and result["err_code"] != 0: 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] result = result[cmd]
del result["err_code"] del result["err_code"]
@ -90,6 +83,27 @@ class SmartDevice(object):
# TODO use volyptuous # TODO use volyptuous
return self.get_sysinfo() 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): def get_sysinfo(self):
""" """
Retrieve system information. Retrieve system information.
@ -167,9 +181,10 @@ class SmartDevice(object):
:raises NotImplementedError: when not implemented :raises NotImplementedError: when not implemented
:raises SmartPlugError: on error :raises SmartPlugError: on error
""" """
raise NotImplementedError("Values for this call are unknown at this point.") raise NotImplementedError()
# here just for the sake of completeness # 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() # self.initialize()
@property @property
@ -198,7 +213,10 @@ class SmartDevice(object):
:raises SmartPlugException: on error :raises SmartPlugException: on error
""" """
raise NotImplementedError("Fails with err_code == 0 with HS110.") 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 = { ts_obj = {
"index": self.timezone["index"], "index": self.timezone["index"],
"hour": ts.hour, "hour": ts.hour,
@ -384,407 +402,3 @@ class SmartDevice(object):
return response['power_mw'] return response['power_mw']
else: else:
return response['power'] 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
View 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
View 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"])

View File

@ -1,5 +1,5 @@
from pyHS100.protocol import TPLinkSmartHomeProtocol from ..protocol import TPLinkSmartHomeProtocol
from pyHS100 import SmartPlugException from .. import SmartPlugException
import logging import logging

View File

@ -1,22 +1,13 @@
from __future__ import absolute_import
from __future__ import unicode_literals
from unittest import TestCase, skip, skipIf from unittest import TestCase, skip, skipIf
from voluptuous import Schema, Invalid, All, Range from voluptuous import Schema, Invalid, All, Range
from functools import partial from functools import partial
from pyHS100 import SmartBulb, SmartPlugException from .. import SmartBulb, SmartPlugException
from pyHS100.tests.fakes import FakeTransportProtocol, sysinfo_lb130 from .fakes import FakeTransportProtocol, sysinfo_lb130
BULB_IP = '192.168.250.186' BULB_IP = '192.168.250.186'
SKIP_STATE_TESTS = False SKIP_STATE_TESTS = False
# python2 compatibility
try:
basestring
except NameError:
basestring = str
def check_int_bool(x): def check_int_bool(x):
if x != 0 and x != 1: if x != 0 and x != 1:
@ -37,18 +28,18 @@ class TestSmartBulb(TestCase):
# as well as to check that faked devices are operating properly. # as well as to check that faked devices are operating properly.
sysinfo_schema = Schema({ sysinfo_schema = Schema({
'active_mode': check_mode, 'active_mode': check_mode,
'alias': basestring, 'alias': str,
'ctrl_protocols': { 'ctrl_protocols': {
'name': basestring, 'name': str,
'version': basestring, 'version': str,
}, },
'description': basestring, 'description': str,
'dev_state': basestring, 'dev_state': str,
'deviceId': basestring, 'deviceId': str,
'disco_ver': basestring, 'disco_ver': str,
'heapsize': int, 'heapsize': int,
'hwId': basestring, 'hwId': str,
'hw_ver': basestring, 'hw_ver': str,
'is_color': check_int_bool, 'is_color': check_int_bool,
'is_dimmable': check_int_bool, 'is_dimmable': check_int_bool,
'is_factory': bool, 'is_factory': bool,
@ -57,14 +48,14 @@ class TestSmartBulb(TestCase):
'brightness': All(int, Range(min=0, max=100)), 'brightness': All(int, Range(min=0, max=100)),
'color_temp': int, 'color_temp': int,
'hue': All(int, Range(min=0, max=255)), 'hue': All(int, Range(min=0, max=255)),
'mode': basestring, 'mode': str,
'on_off': check_int_bool, 'on_off': check_int_bool,
'saturation': All(int, Range(min=0, max=255)), 'saturation': All(int, Range(min=0, max=255)),
}, },
'mic_mac': basestring, 'mic_mac': str,
'mic_type': basestring, 'mic_type': str,
'model': basestring, 'model': str,
'oemId': basestring, 'oemId': str,
'preferred_state': [{ 'preferred_state': [{
'brightness': All(int, Range(min=0, max=100)), 'brightness': All(int, Range(min=0, max=100)),
'color_temp': int, 'color_temp': int,
@ -73,7 +64,7 @@ class TestSmartBulb(TestCase):
'saturation': All(int, Range(min=0, max=255)), 'saturation': All(int, Range(min=0, max=255)),
}], }],
'rssi': All(int, Range(max=0)), 'rssi': All(int, Range(max=0)),
'sw_ver': basestring, 'sw_ver': str,
}) })
current_consumption_schema = Schema({ current_consumption_schema = Schema({
@ -81,10 +72,10 @@ class TestSmartBulb(TestCase):
}) })
tz_schema = Schema({ tz_schema = Schema({
'zone_str': basestring, 'zone_str': str,
'dst_offset': int, 'dst_offset': int,
'index': All(int, Range(min=0)), 'index': All(int, Range(min=0)),
'tz_str': basestring, 'tz_str': str,
}) })
def setUp(self): def setUp(self):
@ -185,7 +176,7 @@ class TestSmartBulb(TestCase):
def test_alias(self): def test_alias(self):
test_alias = "TEST1234" test_alias = "TEST1234"
original = self.bulb.alias original = self.bulb.alias
self.assertTrue(isinstance(original, basestring)) self.assertTrue(isinstance(original, str))
self.bulb.alias = test_alias self.bulb.alias = test_alias
self.assertEqual(self.bulb.alias, test_alias) self.assertEqual(self.bulb.alias, test_alias)
self.bulb.alias = original self.bulb.alias = original

View File

@ -1,8 +1,5 @@
from __future__ import absolute_import
from __future__ import unicode_literals
from unittest import TestCase from unittest import TestCase
from pyHS100.protocol import TPLinkSmartHomeProtocol from ..protocol import TPLinkSmartHomeProtocol
import json import json

View File

@ -1,24 +1,15 @@
from __future__ import absolute_import
from __future__ import unicode_literals
from unittest import TestCase, skip, skipIf from unittest import TestCase, skip, skipIf
from voluptuous import Schema, Invalid, All, Range from voluptuous import Schema, Invalid, All, Range
from functools import partial from functools import partial
import datetime import datetime
import re import re
from pyHS100 import SmartPlug, SmartPlugException from .. import SmartPlug, SmartPlugException
from pyHS100.tests.fakes import FakeTransportProtocol, sysinfo_hs110 from .fakes import FakeTransportProtocol, sysinfo_hs110
PLUG_IP = '192.168.250.186' PLUG_IP = '192.168.250.186'
SKIP_STATE_TESTS = False SKIP_STATE_TESTS = False
# python2 compatibility
try:
basestring
except NameError:
basestring = str
def check_int_bool(x): def check_int_bool(x):
if x != 0 and x != 1: if x != 0 and x != 1:
@ -45,25 +36,25 @@ class TestSmartPlug(TestCase):
# as well as to check that faked devices are operating properly. # as well as to check that faked devices are operating properly.
sysinfo_schema = Schema({ sysinfo_schema = Schema({
'active_mode': check_mode, 'active_mode': check_mode,
'alias': basestring, 'alias': str,
'dev_name': basestring, 'dev_name': str,
'deviceId': basestring, 'deviceId': str,
'feature': basestring, 'feature': str,
'fwId': basestring, 'fwId': str,
'hwId': basestring, 'hwId': str,
'hw_ver': basestring, 'hw_ver': str,
'icon_hash': basestring, 'icon_hash': str,
'latitude': All(float, Range(min=-90, max=90)), 'latitude': All(float, Range(min=-90, max=90)),
'led_off': check_int_bool, 'led_off': check_int_bool,
'longitude': All(float, Range(min=-180, max=180)), 'longitude': All(float, Range(min=-180, max=180)),
'mac': check_mac, 'mac': check_mac,
'model': basestring, 'model': str,
'oemId': basestring, 'oemId': str,
'on_time': int, 'on_time': int,
'relay_state': int, 'relay_state': int,
'rssi': All(int, Range(max=0)), 'rssi': All(int, Range(max=0)),
'sw_ver': basestring, 'sw_ver': str,
'type': basestring, 'type': str,
'updating': check_int_bool, 'updating': check_int_bool,
}) })
@ -75,10 +66,10 @@ class TestSmartPlug(TestCase):
}) })
tz_schema = Schema({ tz_schema = Schema({
'zone_str': basestring, 'zone_str': str,
'dst_offset': int, 'dst_offset': int,
'index': All(int, Range(min=0)), 'index': All(int, Range(min=0)),
'tz_str': basestring, 'tz_str': str,
}) })
def setUp(self): def setUp(self):
@ -195,7 +186,7 @@ class TestSmartPlug(TestCase):
def test_alias(self): def test_alias(self):
test_alias = "TEST1234" test_alias = "TEST1234"
original = self.plug.alias original = self.plug.alias
self.assertTrue(isinstance(original, basestring)) self.assertTrue(isinstance(original, str))
self.plug.alias = test_alias self.plug.alias = test_alias
self.assertEqual(self.plug.alias, test_alias) self.assertEqual(self.plug.alias, test_alias)
self.plug.alias = original self.plug.alias = original

View File

@ -8,7 +8,7 @@ setup(name='pyHS100',
author_email='sean@gadgetreactor.com', author_email='sean@gadgetreactor.com',
license='GPLv3', license='GPLv3',
packages=['pyHS100'], packages=['pyHS100'],
install_requires=['click', 'click-datetime', 'future'], install_requires=['click', 'click-datetime'],
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'pyhs100=pyHS100.cli:cli', 'pyhs100=pyHS100.cli:cli',

View File

@ -1,5 +1,5 @@
[tox] [tox]
envlist=py27,py34,py35,py36,flake8 envlist=py34,py35,py36,flake8
[tox:travis] [tox:travis]
2.7 = py27 2.7 = py27
@ -9,7 +9,6 @@ envlist=py27,py34,py35,py36,flake8
[testenv] [testenv]
deps= deps=
future
pytest pytest
voluptuous voluptuous
commands=py.test pyHS100 commands=py.test pyHS100