mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-23 03:33:35 +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:
parent
c9662fcb7b
commit
86f14642c8
@ -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
|
||||||
|
|
||||||
|
@ -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
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
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 ..protocol import TPLinkSmartHomeProtocol
|
||||||
from pyHS100 import SmartPlugException
|
from .. import SmartPlugException
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
2
setup.py
2
setup.py
@ -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',
|
||||||
|
3
tox.ini
3
tox.ini
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user