diff --git a/pyHS100/cli.py b/pyHS100/cli.py index 0009723f..a3facbea 100644 --- a/pyHS100/cli.py +++ b/pyHS100/cli.py @@ -9,8 +9,10 @@ if sys.version_info < (3, 4): sys.version_info) sys.exit(1) -from pyHS100 import (SmartDevice, SmartPlug, SmartBulb, - TPLinkSmartHomeProtocol, Discover) # noqa: E402 +from pyHS100 import (SmartDevice, + SmartPlug, + SmartBulb, + Discover) # noqa: E402 pass_dev = click.make_pass_decorator(SmartDevice) diff --git a/pyHS100/smartdevice.py b/pyHS100/smartdevice.py index 92beaf81..5d68b87d 100644 --- a/pyHS100/smartdevice.py +++ b/pyHS100/smartdevice.py @@ -338,12 +338,12 @@ class SmartDevice(object): Retrive current energy readings from device. :returns: current readings or False - :rtype: dict, False - False if device has no energy meter or error occured + :rtype: dict, None + None if device has no energy meter or error occured :raises SmartDeviceException: on error """ if not self.has_emeter: - return False + return None return self._query_helper(self.emeter_type, "get_realtime") @@ -355,12 +355,12 @@ class SmartDevice(object): :param month: month for which to retrieve statistcs (default: this month) :return: mapping of day of month to value - False if device has no energy meter or error occured + None if device has no energy meter or error occured :rtype: dict :raises SmartDeviceException: on error """ if not self.has_emeter: - return False + return None if year is None: year = datetime.datetime.now().year @@ -384,12 +384,12 @@ class SmartDevice(object): :param year: year for which to retrieve statistics (default: this year) :return: dict: mapping of month to value - False if device has no energy meter + None if device has no energy meter :rtype: dict :raises SmartDeviceException: on error """ if not self.has_emeter: - return False + return None response = self._query_helper(self.emeter_type, "get_monthstat", {'year': year}) @@ -412,7 +412,7 @@ class SmartDevice(object): :raises SmartDeviceException: on error """ if not self.has_emeter: - return False + return None self._query_helper(self.emeter_type, "erase_emeter_stat", None) @@ -425,11 +425,11 @@ class SmartDevice(object): Get the current power consumption in Watt. :return: the current power consumption in Watt. - False if device has no energy meter. + None if device has no energy meter. :raises SmartDeviceException: on error """ if not self.has_emeter: - return False + return None response = self.get_emeter_realtime() if self.emeter_units: diff --git a/pyHS100/tests/fakes.py b/pyHS100/tests/fakes.py index 8a3b33b2..cbff15c8 100644 --- a/pyHS100/tests/fakes.py +++ b/pyHS100/tests/fakes.py @@ -58,7 +58,7 @@ sysinfo_hs100 = {'system': {'get_sysinfo': 'latitude': 12.2, 'led_off': 0, 'longitude': 12.2, - 'mac': '50:C7:BF:xx:xx:xx', + 'mac': '50:C7:BF:11:22:33', 'model': 'HS100(EU)', 'oemId': '812A90EB2FCF306A993FAD8748024B07', 'on_time': 255419, @@ -71,7 +71,7 @@ sysinfo_hs105 = {'system': {'get_sysinfo': {'sw_ver': '1.0.6 Build 160722 Rel.081616', 'hw_ver': '1.0', 'type': 'IOT.SMARTPLUGSWITCH', 'model': 'HS105(US)', - 'mac': '50:C7:BF:xx:xx:xx', + 'mac': '50:C7:BF:11:22:33', 'dev_name': 'Smart Wi-Fi Plug Mini', 'alias': 'TP-LINK_Smart Plug_CF0B', 'relay_state': 0, @@ -198,70 +198,73 @@ sysinfo_lb130 = {'system': {'get_sysinfo': 'smartlife.iot.common.emeter': emeter_units_support, } -sysinfo_lb110 = {'sys_info': {'emeter': - {'err_code': -2001, - 'err_msg': 'Module not support'}, - 'system': - {'get_sysinfo': - {'active_mode': 'schedule', - 'alias': 'Downstairs Light', - 'ctrl_protocols': - {'name': 'Linkie', - 'version': '1.0'}, - 'description': 'Smart Wi-Fi LED Bulb ' - 'with Dimmable Light', - 'dev_state': 'normal', - 'deviceId': - '80120B3D03E0B639CDF33E3CB1466490187FEF32', - 'disco_ver': '1.0', - 'err_code': 0, - 'heapsize': 309908, - 'hwId': '111E35908497A05512E259BB76801E10', - 'hw_ver': '1.0', - 'is_color': 0, - 'is_dimmable': 1, - 'is_factory': False, - 'is_variable_color_temp': 0, - 'light_state': - {'dft_on_state': - {'brightness': 92, - 'color_temp': 2700, - 'hue': 0, - 'mode': 'normal', - 'saturation': 0}, - 'on_off': 0}, - 'mic_mac': '50C7BF7BE306', - 'mic_type': 'IOT.SMARTBULB', - 'model': 'LB110(EU)', - 'oemId': - 'A68E15472071CB761E5CCFB388A1D8AE', - 'preferred_state': [{'brightness': 100, - 'color_temp': 2700, - 'hue': 0, - 'index': 0, - 'saturation': 0}, - {'brightness': 58, - 'color_temp': 2700, - 'hue': 0, - 'index': 1, - 'saturation': 0}, - {'brightness': 25, - 'color_temp': 2700, - 'hue': 0, - 'index': 2, - 'saturation': 0}, - {'brightness': 1, - 'color_temp': 2700, - 'hue': 0, - 'index': 3, - 'saturation': 0}], - 'rssi': -61, - 'sw_ver': '1.5.5 Build 170623 ' - 'Rel.090105' - } - } - } - } +sysinfo_lb110 = {'system': { + 'sys_info': + {'emeter': + {'err_code': -2001, + 'err_msg': 'Module not support'}, + 'system': + {'get_sysinfo': + {'active_mode': 'schedule', + 'alias': 'Downstairs Light', + 'ctrl_protocols': + {'name': 'Linkie', + 'version': '1.0'}, + 'description': 'Smart Wi-Fi LED Bulb ' + 'with Dimmable Light', + 'dev_state': 'normal', + 'deviceId': + '80120B3D03E0B639CDF33E3CB1466490187FEF32', + 'disco_ver': '1.0', + 'err_code': 0, + 'heapsize': 309908, + 'hwId': '111E35908497A05512E259BB76801E10', + 'hw_ver': '1.0', + 'is_color': 0, + 'is_dimmable': 1, + 'is_factory': False, + 'is_variable_color_temp': 0, + 'light_state': + {'dft_on_state': + {'brightness': 92, + 'color_temp': 2700, + 'hue': 0, + 'mode': 'normal', + 'saturation': 0}, + 'on_off': 0}, + 'mic_mac': '50C7BF7BE306', + 'mic_type': 'IOT.SMARTBULB', + 'model': 'LB110(EU)', + 'oemId': + 'A68E15472071CB761E5CCFB388A1D8AE', + 'preferred_state': [{'brightness': 100, + 'color_temp': 2700, + 'hue': 0, + 'index': 0, + 'saturation': 0}, + {'brightness': 58, + 'color_temp': 2700, + 'hue': 0, + 'index': 1, + 'saturation': 0}, + {'brightness': 25, + 'color_temp': 2700, + 'hue': 0, + 'index': 2, + 'saturation': 0}, + {'brightness': 1, + 'color_temp': 2700, + 'hue': 0, + 'index': 3, + 'saturation': 0}], + 'rssi': -61, + 'sw_ver': '1.5.5 Build 170623 ' + 'Rel.090105' + } + } + } +}} + def error(cls, target, cmd="no-command", msg="default msg"): return {target: {cmd: {"err_code": -1323, "msg": msg}}} diff --git a/pyHS100/tests/test_bulb.py b/pyHS100/tests/test_bulb.py index 9618a1d6..5699e9e6 100644 --- a/pyHS100/tests/test_bulb.py +++ b/pyHS100/tests/test_bulb.py @@ -3,7 +3,7 @@ from voluptuous import Schema, Invalid, All, Range from functools import partial from .. import SmartBulb, SmartDeviceException -from .fakes import FakeTransportProtocol, sysinfo_lb130 +from .fakes import FakeTransportProtocol, sysinfo_lb130, sysinfo_lb110 BULB_IP = '192.168.250.186' SKIP_STATE_TESTS = False @@ -23,6 +23,7 @@ def check_mode(x): class TestSmartBulb(TestCase): + SYSINFO = sysinfo_lb130 # these schemas should go to the mainlib as # they can be useful when adding support for new features/devices # as well as to check that faked devices are operating properly. @@ -80,7 +81,7 @@ class TestSmartBulb(TestCase): def setUp(self): self.bulb = SmartBulb(BULB_IP, - protocol=FakeTransportProtocol(sysinfo_lb130)) + protocol=FakeTransportProtocol(self.SYSINFO)) def tearDown(self): self.bulb = None @@ -91,7 +92,7 @@ class TestSmartBulb(TestCase): def test_initialize_invalid_connection(self): bulb = SmartBulb('127.0.0.1', - protocol=FakeTransportProtocol(sysinfo_lb130, + protocol=FakeTransportProtocol(self.SYSINFO, invalid=True)) with self.assertRaises(SmartDeviceException): bulb.sys_info['model'] @@ -187,3 +188,7 @@ class TestSmartBulb(TestCase): def test_rssi(self): self.sysinfo_schema({'rssi': self.bulb.rssi}) # wrapping for vol + + +class TestSmartBulbLB110(TestSmartBulb): + SYSINFO = sysinfo_lb110 diff --git a/pyHS100/tests/test_pyHS100.py b/pyHS100/tests/test_pyHS100.py index ab0f013a..0d7721a2 100644 --- a/pyHS100/tests/test_pyHS100.py +++ b/pyHS100/tests/test_pyHS100.py @@ -1,11 +1,15 @@ from unittest import TestCase, skip, skipIf -from voluptuous import Schema, Invalid, All, Range +from voluptuous import Schema, Invalid, All, Any, Range from functools import partial import datetime import re from .. import SmartPlug, SmartDeviceException -from .fakes import FakeTransportProtocol, sysinfo_hs110, sysinfo_hs105 +from .fakes import (FakeTransportProtocol, + sysinfo_hs100, + sysinfo_hs105, + sysinfo_hs110, + sysinfo_hs200) PLUG_IP = '192.168.250.186' SKIP_STATE_TESTS = False @@ -24,13 +28,14 @@ def check_mac(x): def check_mode(x): - if x in ['schedule']: + if x in ['schedule', 'none']: return x raise Invalid("invalid mode {}".format(x)) -class TestSmartPlug(TestCase): +class TestSmartPlugHS110(TestCase): + SYSINFO = sysinfo_hs110 # these schemas should go to the mainlib as # they can be useful when adding support for new features/devices # as well as to check that faked devices are operating properly. @@ -44,26 +49,29 @@ class TestSmartPlug(TestCase): '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)), + 'latitude': Any(All(float, Range(min=-90, max=90)), None), + 'latitude_i': Any(All(float, Range(min=-90, max=90)), None), + 'longitude': Any(All(float, Range(min=-180, max=180)), None), + 'longitude_i': Any(All(float, Range(min=-180, max=180)), None), 'mac': check_mac, 'model': str, 'oemId': str, 'on_time': int, 'relay_state': int, - 'rssi': All(int, Range(max=0)), + 'rssi': Any(int, None), # rssi can also be positive, see #54 'sw_ver': str, 'type': str, + 'mic_type': str, 'updating': check_int_bool, }) - current_consumption_schema = Schema({ + current_consumption_schema = Schema(Any({ 'voltage': All(float, Range(min=0, max=300)), 'power': All(float, Range(min=0)), 'total': All(float, Range(min=0)), 'current': All(float, Range(min=0)), - }) + }, None)) tz_schema = Schema({ 'zone_str': str, @@ -74,7 +82,7 @@ class TestSmartPlug(TestCase): def setUp(self): self.plug = SmartPlug(PLUG_IP, - protocol=FakeTransportProtocol(sysinfo_hs110)) + protocol=FakeTransportProtocol(self.SYSINFO)) def tearDown(self): self.plug = None @@ -85,7 +93,7 @@ class TestSmartPlug(TestCase): def test_initialize_invalid_connection(self): plug = SmartPlug('127.0.0.1', - protocol=FakeTransportProtocol(sysinfo_hs110, + protocol=FakeTransportProtocol(self.SYSINFO, invalid=True)) with self.assertRaises(SmartDeviceException): plug.sys_info['model'] @@ -152,31 +160,45 @@ class TestSmartPlug(TestCase): self.assertFalse(self.plug.has_emeter) def test_get_emeter_realtime(self): - self.current_consumption_schema((self.plug.get_emeter_realtime())) + if self.plug.has_emeter: + self.current_consumption_schema((self.plug.get_emeter_realtime())) + else: + self.assertEqual(self.plug.get_emeter_realtime(), None) def test_get_emeter_daily(self): - self.assertEqual(self.plug.get_emeter_daily(year=1900, month=1), {}) + if self.plug.has_emeter: + self.assertEqual(self.plug.get_emeter_daily(year=1900, month=1), + {}) - k, v = self.plug.get_emeter_daily().popitem() - self.assertTrue(isinstance(k, int)) - self.assertTrue(isinstance(v, float)) + k, v = self.plug.get_emeter_daily().popitem() + self.assertTrue(isinstance(k, int)) + self.assertTrue(isinstance(v, float)) + else: + self.assertEqual(self.plug.get_emeter_daily(year=1900, month=1), + None) def test_get_emeter_monthly(self): - self.assertEqual(self.plug.get_emeter_monthly(year=1900), {}) + if self.plug.has_emeter: + self.assertEqual(self.plug.get_emeter_monthly(year=1900), {}) - d = self.plug.get_emeter_monthly() - k, v = d.popitem() - self.assertTrue(isinstance(k, int)) - self.assertTrue(isinstance(v, float)) + d = self.plug.get_emeter_monthly() + k, v = d.popitem() + self.assertTrue(isinstance(k, int)) + self.assertTrue(isinstance(v, float)) + else: + self.assertEqual(self.plug.get_emeter_monthly(year=1900), None) @skip("not clearing your stats..") def test_erase_emeter_stats(self): self.fail() def test_current_consumption(self): - x = self.plug.current_consumption() - self.assertTrue(isinstance(x, float)) - self.assertTrue(x >= 0.0) + if self.plug.has_emeter: + x = self.plug.current_consumption() + self.assertTrue(isinstance(x, float)) + self.assertTrue(x >= 0.0) + else: + self.assertEqual(self.plug.current_consumption(), None) def test_identify(self): ident = self.plug.identify() @@ -221,14 +243,26 @@ class TestSmartPlug(TestCase): def test_location(self): self.sysinfo_schema(self.plug.location) - def test_location_i(self): - plug_i = SmartPlug(PLUG_IP, - protocol=FakeTransportProtocol(sysinfo_hs105)) - self.sysinfo_schema(plug_i.location) - def test_rssi(self): self.sysinfo_schema({'rssi': self.plug.rssi}) # wrapping for vol def test_mac(self): self.sysinfo_schema({'mac': self.plug.mac}) # wrapping for val # TODO check setting? + + +class TestSmartPlugHS100(TestSmartPlugHS110): + SYSINFO = sysinfo_hs100 + + +class TestSmartPlugHS200(TestSmartPlugHS110): + SYSINFO = sysinfo_hs200 + + +class TestSmartPlugHS105(TestSmartPlugHS110): + SYSINFO = sysinfo_hs105 + + def test_location_i(self): + plug_i = SmartPlug(PLUG_IP, + protocol=FakeTransportProtocol(self.SYSINFO)) + self.sysinfo_schema(plug_i.location)