mass rename to (python-)kasa (#1)

This commit is contained in:
Teemu R
2019-12-18 09:11:18 +01:00
committed by Bas Nijholt
parent 624c44c27f
commit 3ef5086ffb
33 changed files with 63 additions and 69 deletions

0
kasa/tests/__init__.py Normal file
View File

124
kasa/tests/conftest.py Normal file
View File

@@ -0,0 +1,124 @@
import asyncio
import glob
import json
import os
from os.path import basename
import pytest
from kasa import Discover, SmartBulb, SmartPlug, SmartStrip
from .newfakes import FakeTransportProtocol
SUPPORTED_DEVICES = glob.glob(
os.path.dirname(os.path.abspath(__file__)) + "/fixtures/*.json"
)
BULBS = {"LB100", "LB120", "LB130", "KL120"}
VARIABLE_TEMP = {"LB120", "LB130", "KL120"}
PLUGS = {"HS100", "HS105", "HS110", "HS200", "HS220", "HS300"}
STRIPS = {"HS300"}
COLOR_BULBS = {"LB130"}
DIMMABLE = {*BULBS, "HS220"}
EMETER = {"HS110", "HS300", *BULBS}
ALL_DEVICES = BULBS.union(PLUGS)
def filter_model(filter):
print(filter)
filtered = list()
for dev in SUPPORTED_DEVICES:
for filt in filter:
if filt in basename(dev):
filtered.append(dev)
return filtered
def get_ioloop():
ioloop = asyncio.new_event_loop()
asyncio.set_event_loop(ioloop)
return ioloop
has_emeter = pytest.mark.parametrize("dev", filter_model(EMETER), indirect=True)
no_emeter = pytest.mark.parametrize(
"dev", filter_model(ALL_DEVICES - EMETER), indirect=True
)
bulb = pytest.mark.parametrize("dev", filter_model(BULBS), indirect=True)
plug = pytest.mark.parametrize("dev", filter_model(PLUGS), indirect=True)
strip = pytest.mark.parametrize("dev", filter_model(STRIPS), indirect=True)
dimmable = pytest.mark.parametrize("dev", filter_model(DIMMABLE), indirect=True)
non_dimmable = pytest.mark.parametrize(
"dev", filter_model(ALL_DEVICES - DIMMABLE), indirect=True
)
variable_temp = pytest.mark.parametrize(
"dev", filter_model(VARIABLE_TEMP), indirect=True
)
non_variable_temp = pytest.mark.parametrize(
"dev", filter_model(BULBS - VARIABLE_TEMP), indirect=True
)
color_bulb = pytest.mark.parametrize("dev", filter_model(COLOR_BULBS), indirect=True)
non_color_bulb = pytest.mark.parametrize(
"dev", filter_model(BULBS - COLOR_BULBS), indirect=True
)
# Parametrize tests to run with device both on and off
turn_on = pytest.mark.parametrize("turn_on", [True, False])
def handle_turn_on(dev, turn_on):
if turn_on:
dev.sync.turn_on()
else:
dev.sync.turn_off()
@pytest.fixture(params=SUPPORTED_DEVICES)
def dev(request):
ioloop = get_ioloop()
file = request.param
ip = request.config.getoption("--ip")
if ip:
d = ioloop.run_until_complete(Discover.discover_single(ip))
print(d.model)
if d.model in file:
return d
return
with open(file) as f:
sysinfo = json.load(f)
model = basename(file)
params = {
"host": "123.123.123.123",
"protocol": FakeTransportProtocol(sysinfo),
"cache_ttl": 0,
}
if "LB" in model or "KL" in model:
p = SmartBulb(**params, ioloop=ioloop)
elif "HS300" in model:
p = SmartStrip(**params, ioloop=ioloop)
elif "HS" in model:
p = SmartPlug(**params, ioloop=ioloop)
else:
raise Exception("No tests for %s" % model)
yield p
def pytest_addoption(parser):
parser.addoption("--ip", action="store", default=None, help="run against device")
def pytest_collection_modifyitems(config, items):
if not config.getoption("--ip"):
print("Testing against fixtures.")
return
else:
print("Running against ip %s" % config.getoption("--ip"))

47
kasa/tests/fixtures/HS100(US)_1.0.json vendored Normal file
View File

@@ -0,0 +1,47 @@
{
"emeter": {
"get_realtime": {
"err_code": -1,
"err_msg": "module not support"
}
},
"smartlife.iot.common.emeter": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.dimmer": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.smartbulb.lightingservice": {
"err_code": -1,
"err_msg": "module not support"
},
"system": {
"get_sysinfo": {
"active_mode": "schedule",
"alias": "Mock hs100",
"dev_name": "Wi-Fi Smart Plug",
"deviceId": "048F069965D3230FD1382F0B78EAE68D42CAA2DE",
"err_code": 0,
"feature": "TIM",
"hwId": "92688D028799C60F926049D1C9EFD9E8",
"hw_ver": "1.0",
"icon_hash": "",
"latitude": 63.5442,
"latitude_i": 63.5442,
"led_off": 0,
"longitude": -148.2817,
"longitude_i": -148.2817,
"mac": "50:c7:bf:a3:71:c0",
"model": "HS100(US)",
"oemId": "149C8A24AA3A1445DE84F00DFB210D60",
"on_time": 0,
"relay_state": 0,
"rssi": -65,
"sw_ver": "1.2.5 Build 171129 Rel.174814",
"type": "IOT.SMARTPLUGSWITCH",
"updating": 0
}
}
}

47
kasa/tests/fixtures/HS105(US)_1.0.json vendored Normal file
View File

@@ -0,0 +1,47 @@
{
"emeter": {
"get_realtime": {
"err_code": -1,
"err_msg": "module not support"
}
},
"smartlife.iot.common.emeter": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.dimmer": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.smartbulb.lightingservice": {
"err_code": -1,
"err_msg": "module not support"
},
"system": {
"get_sysinfo": {
"active_mode": "schedule",
"alias": "Mock hs105",
"dev_name": "Smart Wi-Fi Plug Mini",
"deviceId": "F0723FAFC1FA27FC755B9F228A2297D921FEBCD1",
"err_code": 0,
"feature": "TIM",
"hwId": "51E17031929D5FEF9147091AD67B954A",
"hw_ver": "1.0",
"icon_hash": "",
"INVALIDlatitude": 79.7779,
"latitude_i": 79.7779,
"led_off": 0,
"INVALIDlongitude": 90.8844,
"longitude_i": 90.8844,
"mac": "50:c7:bf:ac:c0:6a",
"model": "HS105(US)",
"oemId": "990ADB7AEDE871C41D1B7613D1FE7A76",
"on_time": 0,
"relay_state": 0,
"rssi": -65,
"sw_ver": "1.2.9 Build 170808 Rel.145916",
"type": "IOT.SMARTPLUGSWITCH",
"updating": 0
}
}
}

View File

@@ -0,0 +1,49 @@
{
"emeter": {
"get_realtime": {
"current": 0.015342,
"err_code": 0,
"power": 0.983971,
"total": 32.448,
"voltage": 235.595234
}
},
"smartlife.iot.common.emeter": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.dimmer": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.smartbulb.lightingservice": {
"err_code": -1,
"err_msg": "module not support"
},
"system": {
"get_sysinfo": {
"active_mode": "schedule",
"alias": "Kitchen",
"dev_name": "Wi-Fi Smart Plug With Energy Monitoring",
"deviceId": "8006588E50AD389303FF31AB6302907A17442F16",
"err_code": 0,
"feature": "TIM:ENE",
"fwId": "00000000000000000000000000000000",
"hwId": "45E29DA8382494D2E82688B52A0B2EB5",
"hw_ver": "1.0",
"icon_hash": "",
"latitude": 51.476938,
"led_off": 1,
"longitude": 7.216849,
"mac": "50:C7:BF:01:F8:CD",
"model": "HS110(EU)",
"oemId": "3D341ECE302C0642C99E31CE2430544B",
"on_time": 512874,
"relay_state": 1,
"rssi": -71,
"sw_ver": "1.2.5 Build 171213 Rel.101523",
"type": "IOT.SMARTPLUGSWITCH",
"updating": 0
}
}
}

50
kasa/tests/fixtures/HS110(EU)_2.0.json vendored Normal file
View File

@@ -0,0 +1,50 @@
{
"emeter": {
"get_realtime": {
"current_ma": 125,
"err_code": 0,
"power_mw": 3140,
"total_wh": 51493,
"voltage_mv": 122049
}
},
"smartlife.iot.common.emeter": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.dimmer": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.smartbulb.lightingservice": {
"err_code": -1,
"err_msg": "module not support"
},
"system": {
"get_sysinfo": {
"active_mode": "schedule",
"alias": "Mock hs110v2",
"dev_name": "Smart Wi-Fi Plug With Energy Monitoring",
"deviceId": "A466BCDB5026318939145B7CC7EF18D8C1D3A954",
"err_code": 0,
"feature": "TIM:ENE",
"hwId": "1F7FABB46373CA51E3AFDE5930ECBB36",
"hw_ver": "2.0",
"icon_hash": "",
"INVALIDlatitude": -60.4599,
"latitude_i": -60.4599,
"led_off": 0,
"INVALIDlongitude": 76.1249,
"longitude_i": 76.1249,
"mac": "50:c7:bf:b9:40:08",
"model": "HS110(EU)",
"oemId": "BB668B949FA4559655F1187DD56622BD",
"on_time": 0,
"relay_state": 0,
"rssi": -65,
"sw_ver": "1.5.2 Build 180130 Rel.085820",
"type": "IOT.SMARTPLUGSWITCH",
"updating": 0
}
}
}

50
kasa/tests/fixtures/HS110(US)_1.0.json vendored Normal file
View File

@@ -0,0 +1,50 @@
{
"emeter": {
"get_realtime": {
"current": 0.1256,
"err_code": 0,
"power": 3.14,
"total": 51.493,
"voltage": 122.049119
}
},
"smartlife.iot.common.emeter": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.dimmer": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.smartbulb.lightingservice": {
"err_code": -1,
"err_msg": "module not support"
},
"system": {
"get_sysinfo": {
"active_mode": "schedule",
"alias": "Mock hs110",
"dev_name": "Wi-Fi Smart Plug With Energy Monitoring",
"deviceId": "11A5FD4A0FA1FCE5468F55D23CE77D1753A93E11",
"err_code": 0,
"feature": "TIM:ENE",
"hwId": "6C56A17315351DD0EDE0BDB1D9EBBD66",
"hw_ver": "1.0",
"icon_hash": "",
"latitude": 82.2866,
"latitude_i": 82.2866,
"led_off": 0,
"longitude": 10.0036,
"longitude_i": 10.0036,
"mac": "50:c7:bf:66:29:29",
"model": "HS110(US)",
"oemId": "F7DFC14D43DA806B55DB66D21F212B60",
"on_time": 0,
"relay_state": 0,
"rssi": -65,
"sw_ver": "1.0.8 Build 151113 Rel.24658",
"type": "IOT.SMARTPLUGSWITCH",
"updating": 0
}
}
}

47
kasa/tests/fixtures/HS200(US)_1.0.json vendored Normal file
View File

@@ -0,0 +1,47 @@
{
"emeter": {
"get_realtime": {
"err_code": -1,
"err_msg": "module not support"
}
},
"smartlife.iot.common.emeter": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.dimmer": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.smartbulb.lightingservice": {
"err_code": -1,
"err_msg": "module not support"
},
"system": {
"get_sysinfo": {
"active_mode": "schedule",
"alias": "Mock hs200",
"dev_name": "Wi-Fi Smart Light Switch",
"deviceId": "EC565185337CF59A4C9A73442AAD5F11C6E91716",
"err_code": 0,
"feature": "TIM",
"hwId": "4B5DB5E42F13728107D075EF5C3ECFA1",
"hw_ver": "1.0",
"icon_hash": "",
"latitude": 58.7882,
"latitude_i": 58.7882,
"led_off": 0,
"longitude": 107.7225,
"longitude_i": 107.7225,
"mac": "50:c7:bf:95:4b:45",
"model": "HS200(US)",
"oemId": "D2A5D690B25980755216FD684AF8CD88",
"on_time": 0,
"relay_state": 0,
"rssi": -65,
"sw_ver": "1.1.0 Build 160521 Rel.085826",
"type": "IOT.SMARTPLUGSWITCH",
"updating": 0
}
}
}

75
kasa/tests/fixtures/HS220(US)_1.0.json vendored Normal file
View File

@@ -0,0 +1,75 @@
{
"emeter": {
"get_realtime": {
"err_code": -1,
"err_msg": "module not support"
}
},
"smartlife.iot.common.emeter": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.dimmer": {
"get_dimmer_parameters": {
"bulb_type": 1,
"err_code": 0,
"fadeOffTime": 3000,
"fadeOnTime": 3000,
"gentleOffTime": 510000,
"gentleOnTime": 3000,
"minThreshold": 0,
"rampRate": 30
}
},
"smartlife.iot.smartbulb.lightingservice": {
"err_code": -1,
"err_msg": "module not support"
},
"system": {
"get_sysinfo": {
"active_mode": "count_down",
"alias": "Mock hs220",
"brightness": 50,
"dev_name": "Smart Wi-Fi Dimmer",
"deviceId": "98E16F2D5ED204F3094CF472260237133DC0D547",
"err_code": 0,
"feature": "TIM",
"hwId": "231004CCCDB6C0B8FC7A3260C3470257",
"hw_ver": "1.0",
"icon_hash": "",
"INVALIDlatitude": 11.6210,
"latitude_i": 11.6210,
"led_off": 0,
"INVALIDlongitude": 42.2074,
"longitude_i": 42.2074,
"mac": "50:c7:bf:af:75:5d",
"mic_type": "IOT.SMARTPLUGSWITCH",
"model": "HS220(US)",
"oemId": "8FBD0F3CCF7E82836DC7996C524EF772",
"on_time": 0,
"preferred_state": [
{
"brightness": 100,
"index": 0
},
{
"brightness": 75,
"index": 1
},
{
"brightness": 50,
"index": 2
},
{
"brightness": 25,
"index": 3
}
],
"relay_state": 0,
"rssi": -65,
"sw_ver": "1.5.7 Build 180912 Rel.104837",
"type": "IOT.SMARTPLUGSWITCH",
"updating": 0
}
}
}

View File

@@ -0,0 +1,76 @@
{
"emeter": {
"get_realtime": {
"err_code": -1,
"err_msg": "module not support"
}
},
"smartlife.iot.common.emeter": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.dimmer": {
"get_dimmer_parameters": {
"bulb_type": 1,
"err_code": 0,
"fadeOffTime": 1000,
"fadeOnTime": 1000,
"gentleOffTime": 10000,
"gentleOnTime": 3000,
"minThreshold": 0,
"rampRate": 30
}
},
"smartlife.iot.smartbulb.lightingservice": {
"err_code": -1,
"err_msg": "module not support"
},
"system": {
"get_sysinfo": {
"active_mode": "none",
"alias": "Living room left dimmer",
"brightness": 25,
"dev_name": "Smart Wi-Fi Dimmer",
"deviceId": "000000000000000000000000000000000000000",
"err_code": 0,
"feature": "TIM",
"fwId": "00000000000000000000000000000000",
"hwId": "00000000000000000000000000000000",
"hw_ver": "1.0",
"icon_hash": "",
"latitude_i": 11.6210,
"led_off": 0,
"longitude_i": 42.2074,
"mac": "00:00:00:00:00:00",
"mic_type": "IOT.SMARTPLUGSWITCH",
"model": "HS220(US)",
"next_action": {
"type": -1
},
"oemId": "00000000000000000000000000000000",
"on_time": 0,
"preferred_state": [
{
"brightness": 100,
"index": 0
},
{
"brightness": 75,
"index": 1
},
{
"brightness": 50,
"index": 2
},
{
"brightness": 25,
"index": 3
}
],
"relay_state": 0,
"rssi": -35,
"sw_ver": "1.5.7 Build 180912 Rel.104837",
"updating": 0
}
}
}

102
kasa/tests/fixtures/HS300(US)_1.0.json vendored Normal file
View File

@@ -0,0 +1,102 @@
{
"emeter": {
"get_realtime": {
"current_ma": 125,
"err_code": 0,
"power_mw": 3140,
"total_wh": 51493,
"voltage_mv": 122049
}
},
"smartlife.iot.common.emeter": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.dimmer": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.smartbulb.lightingservice": {
"err_code": -1,
"err_msg": "module not support"
},
"system": {
"get_sysinfo": {
"alias": "Mock hs300",
"child_num": 6,
"children": [
{
"alias": "Mock One",
"id": "00",
"next_action": {
"type": -1
},
"on_time": 123,
"state": 1
},
{
"alias": "Mock Two",
"id": "01",
"next_action": {
"type": -1
},
"on_time": 333,
"state": 1
},
{
"alias": "Mock Three",
"id": "02",
"next_action": {
"type": -1
},
"on_time": 0,
"state": 0
},
{
"alias": "Mock Four",
"id": "03",
"next_action": {
"type": -1
},
"on_time": 0,
"state": 0
},
{
"alias": "Mock Five",
"id": "04",
"next_action": {
"type": -1
},
"on_time": 0,
"state": 0
},
{
"alias": "Mock Six",
"id": "05",
"next_action": {
"type": -1
},
"on_time": 0,
"state": 0
}
],
"deviceId": "4BFC2F2C8678FE623700FD3737EC4E245196F3CF",
"err_code": 0,
"feature": "TIM:ENE",
"hwId": "1B63E5DF21B5AFB52F364DE66BFAAF8A",
"hw_ver": "1.0",
"latitude": -68.9980,
"latitude_i": -68.9980,
"led_off": 0,
"longitude": -109.4400,
"longitude_i": -109.4400,
"mac": "50:c7:bf:c2:75:88",
"mic_type": "IOT.SMARTPLUGSWITCH",
"model": "HS300(US)",
"oemId": "FC71DAAB004326F9369EDEF4353E4FE1",
"rssi": -68,
"sw_ver": "1.0.6 Build 180627 Rel.081000",
"updating": 0
}
}
}

View File

@@ -0,0 +1,93 @@
{
"emeter": {
"err_code": -2001,
"err_msg": "Module not support"
},
"smartlife.iot.common.emeter": {
"get_realtime": {
"err_code": 0,
"power_mw": 1800
}
},
"smartlife.iot.dimmer": {
"err_code": -2001,
"err_msg": "Module not support"
},
"smartlife.iot.smartbulb.lightingservice": {
"get_light_state": {
"brightness": 10,
"color_temp": 2700,
"err_code": 0,
"hue": 0,
"mode": "normal",
"on_off": 1,
"saturation": 0
}
},
"system": {
"get_sysinfo": {
"active_mode": "none",
"alias": "",
"ctrl_protocols": {
"name": "Linkie",
"version": "1.0"
},
"description": "Smart Wi-Fi LED Bulb with Tunable White Light",
"dev_state": "normal",
"deviceId": "801200814AD69370AC59DE5501319C051AF409C3",
"disco_ver": "1.0",
"err_code": 0,
"heapsize": 290784,
"hwId": "111E35908497A05512E259BB76801E10",
"hw_ver": "1.0",
"is_color": 0,
"is_dimmable": 1,
"is_factory": false,
"is_variable_color_temp": 1,
"light_state": {
"brightness": 10,
"color_temp": 2700,
"hue": 0,
"mode": "normal",
"on_off": 1,
"saturation": 0
},
"mic_mac": "D80D17150474",
"mic_type": "IOT.SMARTBULB",
"model": "KL120(US)",
"oemId": "1210657CD7FBDC72895644388EEFAE8B",
"preferred_state": [
{
"brightness": 100,
"color_temp": 3500,
"hue": 0,
"index": 0,
"saturation": 0
},
{
"brightness": 50,
"color_temp": 5000,
"hue": 0,
"index": 1,
"saturation": 0
},
{
"brightness": 50,
"color_temp": 2700,
"hue": 0,
"index": 2,
"saturation": 0
},
{
"brightness": 1,
"color_temp": 2700,
"hue": 0,
"index": 3,
"saturation": 0
}
],
"rssi": -52,
"sw_ver": "1.8.6 Build 180809 Rel.091659"
}
}
}

103
kasa/tests/fixtures/LB100(US)_1.0.json vendored Normal file
View File

@@ -0,0 +1,103 @@
{
"emeter": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.common.emeter": {
"get_realtime": {
"err_code": 0,
"power_mw": 10800
}
},
"smartlife.iot.dimmer": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.smartbulb.lightingservice": {
"get_light_state": {
"dft_on_state": {
"brightness": 100,
"color_temp": 2700,
"hue": 0,
"mode": "normal",
"saturation": 0
},
"err_code": 0,
"on_off": 0
}
},
"system": {
"get_sysinfo": {
"active_mode": "none",
"alias": "Mock lb100",
"ctrl_protocols": {
"name": "Linkie",
"version": "1.0"
},
"description": "Smart Wi-Fi LED Bulb with Dimmable Light",
"dev_state": "normal",
"deviceId": "15BD5A6C4B729A7C0D4D46ADDFA7E2600793C56A",
"disco_ver": "1.0",
"err_code": 0,
"heapsize": 302452,
"hwId": "1B0DF0A2EFE6251DBE726D1D2167C78F",
"hw_ver": "1.0",
"is_color": 0,
"is_dimmable": 1,
"is_factory": false,
"is_variable_color_temp": 0,
"latitude": -51.8361,
"latitude_i": -51.8361,
"light_state": {
"dft_on_state": {
"brightness": 100,
"color_temp": 2700,
"hue": 0,
"mode": "normal",
"saturation": 0
},
"err_code": 0,
"on_off": 0
},
"longitude": -34.0697,
"longitude_i": -34.0697,
"mac": "50:c7:bf:51:10:65",
"mic_type": "IOT.SMARTBULB",
"model": "LB100(US)",
"oemId": "C9CF655C9A5AA101E66EBA5B382E40CC",
"on_time": 0,
"preferred_state": [
{
"brightness": 100,
"color_temp": 2700,
"hue": 0,
"index": 0,
"saturation": 0
},
{
"brightness": 75,
"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": -65,
"sw_ver": "1.4.3 Build 170504 Rel.144921"
}
}
}

103
kasa/tests/fixtures/LB120(US)_1.0.json vendored Normal file
View File

@@ -0,0 +1,103 @@
{
"emeter": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.common.emeter": {
"get_realtime": {
"err_code": 0,
"power_mw": 10800
}
},
"smartlife.iot.dimmer": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.smartbulb.lightingservice": {
"get_light_state": {
"dft_on_state": {
"brightness": 100,
"color_temp": 2700,
"hue": 0,
"mode": "normal",
"saturation": 0
},
"err_code": 0,
"on_off": 0
}
},
"system": {
"get_sysinfo": {
"active_mode": "none",
"alias": "Mock lb120",
"ctrl_protocols": {
"name": "Linkie",
"version": "1.0"
},
"description": "Smart Wi-Fi LED Bulb with Tunable White Light",
"dev_state": "normal",
"deviceId": "62FD818E5B66A509D571D07D0F00FA4DD6468494",
"disco_ver": "1.0",
"err_code": 0,
"heapsize": 302452,
"hwId": "CC0588817E251DF996F1848ED331F543",
"hw_ver": "1.0",
"is_color": 0,
"is_dimmable": 1,
"is_factory": false,
"is_variable_color_temp": 1,
"latitude": -76.9197,
"latitude_i": -76.9197,
"light_state": {
"dft_on_state": {
"brightness": 100,
"color_temp": 2700,
"hue": 0,
"mode": "normal",
"saturation": 0
},
"err_code": 0,
"on_off": 0
},
"longitude": 164.7293,
"longitude_i": 164.7293,
"mac": "50:c7:bf:dc:62:13",
"mic_type": "IOT.SMARTBULB",
"model": "LB120(US)",
"oemId": "05D0D97951F565579A7F5A70A57AED0B",
"on_time": 0,
"preferred_state": [
{
"brightness": 100,
"color_temp": 2700,
"hue": 0,
"index": 0,
"saturation": 0
},
{
"brightness": 75,
"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": -65,
"sw_ver": "1.1.0 Build 160630 Rel.085319"
}
}
}

104
kasa/tests/fixtures/LB130(US)_1.0.json vendored Normal file
View File

@@ -0,0 +1,104 @@
{
"emeter": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.common.emeter": {
"get_realtime": {
"err_code": 0,
"power_mw": 10800
}
},
"smartlife.iot.dimmer": {
"err_code": -1,
"err_msg": "module not support"
},
"smartlife.iot.smartbulb.lightingservice": {
"get_light_state": {
"dft_on_state": {
"brightness": 100,
"color_temp": 2700,
"hue": 0,
"mode": "normal",
"saturation": 0
},
"err_code": 0,
"on_off": 0
}
},
"system": {
"get_sysinfo": {
"active_mode": "none",
"alias": "Mock lb130",
"ctrl_protocols": {
"name": "Linkie",
"version": "1.0"
},
"description": "Smart Wi-Fi LED Bulb with Color Changing",
"dev_state": "normal",
"deviceId": "50BE9E7B6F26CA75D495C13EAA459C491768F143",
"disco_ver": "1.0",
"err_code": 0,
"heapsize": 302452,
"hwId": "C8AD962B53417C2845CC10CE25C00BB1",
"hw_ver": "1.0",
"is_color": 1,
"is_dimmable": 1,
"is_factory": false,
"is_variable_color_temp": 1,
"latitude": 76.8649,
"latitude_i": 76.8649,
"light_state": {
"dft_on_state": {
"brightness": 100,
"color_temp": 2700,
"hue": 0,
"mode": "normal",
"saturation": 0
},
"err_code": 0,
"on_off": 0
},
"longitude": -40.7284,
"longitude_i": -40.7284,
"INVALIDmac": "50:c7:bf:ac:f6:19",
"mic_mac": "50C7BFACF619",
"mic_type": "IOT.SMARTBULB",
"model": "LB130(US)",
"oemId": "CF78964560AAB75A43F15D2E468B63EF",
"on_time": 0,
"preferred_state": [
{
"brightness": 100,
"color_temp": 2700,
"hue": 0,
"index": 0,
"saturation": 0
},
{
"brightness": 75,
"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": -65,
"sw_ver": "1.6.0 Build 170703 Rel.141938"
}
}
}

425
kasa/tests/newfakes.py Normal file
View File

@@ -0,0 +1,425 @@
import logging
import re
from voluptuous import REMOVE_EXTRA, All, Any, Coerce, Invalid, Optional, Range, Schema
from ..protocol import TPLinkSmartHomeProtocol
_LOGGER = logging.getLogger(__name__)
def check_int_bool(x):
if x != 0 and x != 1:
raise Invalid(x)
return x
def check_mac(x):
if re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", x.lower()):
return x
raise Invalid(x)
def check_mode(x):
if x in ["schedule", "none", "count_down"]:
return x
raise Invalid(f"invalid mode {x}")
def lb_dev_state(x):
if x in ["normal"]:
return x
raise Invalid(f"Invalid dev_state {x}")
TZ_SCHEMA = Schema(
{"zone_str": str, "dst_offset": int, "index": All(int, Range(min=0)), "tz_str": str}
)
CURRENT_CONSUMPTION_SCHEMA = Schema(
Any(
{
"voltage": Any(All(float, Range(min=0, max=300)), None),
"power": Any(Coerce(float, Range(min=0)), None),
"total": Any(Coerce(float, Range(min=0)), None),
"current": Any(All(float, Range(min=0)), None),
"voltage_mv": Any(
All(float, Range(min=0, max=300000)), int, None
), # TODO can this be int?
"power_mw": Any(Coerce(float, Range(min=0)), None),
"total_wh": Any(Coerce(float, Range(min=0)), None),
"current_ma": Any(
All(float, Range(min=0)), int, None
), # TODO can this be int?
},
None,
)
)
# 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.
PLUG_SCHEMA = Schema(
{
"active_mode": check_mode,
"alias": str,
"dev_name": str,
"deviceId": str,
"feature": str,
"fwId": str,
"hwId": str,
"hw_ver": str,
"icon_hash": str,
"led_off": check_int_bool,
"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": Any(int, None), # rssi can also be positive, see #54
"sw_ver": str,
"type": str,
"mic_type": str,
"updating": check_int_bool,
# these are available on hs220
"brightness": int,
"preferred_state": [
{"brightness": All(int, Range(min=0, max=100)), "index": int}
],
"next_action": {"type": int},
"child_num": Optional(Any(None, int)), # TODO fix hs300 checks
"children": Optional(list), # TODO fix hs300
# TODO some tplink simulator entries contain invalid (mic_mac, _i variants for lat/lon)
# Therefore we add REMOVE_EXTRA..
# "INVALIDmac": Optional,
# "INVALIDlatitude": Optional,
# "INVALIDlongitude": Optional,
},
extra=REMOVE_EXTRA,
)
BULB_SCHEMA = PLUG_SCHEMA.extend(
{
"ctrl_protocols": Optional(dict),
"description": Optional(str), # TODO: LBxxx similar to dev_name
"dev_state": lb_dev_state,
"disco_ver": str,
"heapsize": int,
"is_color": check_int_bool,
"is_dimmable": check_int_bool,
"is_factory": bool,
"is_variable_color_temp": check_int_bool,
"light_state": {
"brightness": All(int, Range(min=0, max=100)),
"color_temp": int,
"hue": All(int, Range(min=0, max=255)),
"mode": str,
"on_off": check_int_bool,
"saturation": All(int, Range(min=0, max=255)),
"dft_on_state": Optional(
{
"brightness": All(int, Range(min=0, max=100)),
"color_temp": All(int, Range(min=2700, max=9000)),
"hue": All(int, Range(min=0, max=255)),
"mode": str,
"saturation": All(int, Range(min=0, max=255)),
}
),
"err_code": int,
},
"preferred_state": [
{
"brightness": All(int, Range(min=0, max=100)),
"color_temp": int,
"hue": All(int, Range(min=0, max=255)),
"index": int,
"saturation": All(int, Range(min=0, max=255)),
}
],
}
)
def get_realtime(obj, x, *args):
return {
"current": 0.268587,
"voltage": 125.836131,
"power": 33.495623,
"total": 0.199000,
}
def get_monthstat(obj, x, *args):
if x["year"] < 2016:
return {"month_list": []}
return {
"month_list": [
{"year": 2016, "month": 11, "energy": 1.089000},
{"year": 2016, "month": 12, "energy": 1.582000},
]
}
def get_daystat(obj, x, *args):
if x["year"] < 2016:
return {"day_list": []}
return {
"day_list": [
{"year": 2016, "month": 11, "day": 24, "energy": 0.026000},
{"year": 2016, "month": 11, "day": 25, "energy": 0.109000},
]
}
emeter_support = {
"get_realtime": get_realtime,
"get_monthstat": get_monthstat,
"get_daystat": get_daystat,
}
def get_realtime_units(obj, x, *args):
return {"power_mw": 10800}
def get_monthstat_units(obj, x, *args):
if x["year"] < 2016:
return {"month_list": []}
return {
"month_list": [
{"year": 2016, "month": 11, "energy_wh": 32},
{"year": 2016, "month": 12, "energy_wh": 16},
]
}
def get_daystat_units(obj, x, *args):
if x["year"] < 2016:
return {"day_list": []}
return {
"day_list": [
{"year": 2016, "month": 11, "day": 24, "energy_wh": 20},
{"year": 2016, "month": 11, "day": 25, "energy_wh": 32},
]
}
emeter_units_support = {
"get_realtime": get_realtime_units,
"get_monthstat": get_monthstat_units,
"get_daystat": get_daystat_units,
}
emeter_commands = {
"emeter": emeter_support,
"smartlife.iot.common.emeter": emeter_units_support,
}
def error(target, cmd="no-command", msg="default msg"):
return {target: {cmd: {"err_code": -1323, "msg": msg}}}
def success(target, cmd, res):
if res:
res.update({"err_code": 0})
else:
res = {"err_code": 0}
return {target: {cmd: res}}
class FakeTransportProtocol(TPLinkSmartHomeProtocol):
def __init__(self, info, invalid=False):
# TODO remove invalid when removing the old tests.
proto = FakeTransportProtocol.baseproto
for target in info:
# print("target %s" % target)
for cmd in info[target]:
# print("initializing tgt %s cmd %s" % (target, cmd))
proto[target][cmd] = info[target][cmd]
# if we have emeter support, check for it
for module in ["emeter", "smartlife.iot.common.emeter"]:
if module not in info:
# TODO required for old tests
continue
if "get_realtime" in info[module]:
get_realtime_res = info[module]["get_realtime"]
# TODO remove when removing old tests
if callable(get_realtime_res):
get_realtime_res = get_realtime_res()
if (
"err_code" not in get_realtime_res
or not get_realtime_res["err_code"]
):
proto[module] = emeter_commands[module]
self.proto = proto
def set_alias(self, x, child_ids=[]):
_LOGGER.debug("Setting alias to %s, child_ids: %s", x["alias"], child_ids)
if child_ids:
for child in self.proto["system"]["get_sysinfo"]["children"]:
if child["id"] in child_ids:
child["alias"] = x["alias"]
else:
self.proto["system"]["get_sysinfo"]["alias"] = x["alias"]
def set_relay_state(self, x, child_ids=[]):
_LOGGER.debug("Setting relay state to %s", x["state"])
if not child_ids and "children" in self.proto["system"]["get_sysinfo"]:
for child in self.proto["system"]["get_sysinfo"]["children"]:
child_ids.append(child["id"])
_LOGGER.info("child_ids: %s", child_ids)
if child_ids:
for child in self.proto["system"]["get_sysinfo"]["children"]:
if child["id"] in child_ids:
_LOGGER.info("Found %s, turning to %s", child, x["state"])
child["state"] = x["state"]
else:
self.proto["system"]["get_sysinfo"]["relay_state"] = x["state"]
def set_led_off(self, x, *args):
_LOGGER.debug("Setting led off to %s", x)
self.proto["system"]["get_sysinfo"]["led_off"] = x["off"]
def set_mac(self, x, *args):
_LOGGER.debug("Setting mac to %s", x)
self.proto["system"]["get_sysinfo"]["mac"] = x
def set_hs220_brightness(self, x, *args):
_LOGGER.debug("Setting brightness to %s", x)
self.proto["system"]["get_sysinfo"]["brightness"] = x["brightness"]
def transition_light_state(self, x, *args):
_LOGGER.debug("Setting light state to %s", x)
light_state = self.proto["smartlife.iot.smartbulb.lightingservice"][
"get_light_state"
]
# The required change depends on the light state,
# exception being turning the bulb on and off
if "on_off" in x:
if x["on_off"] and not light_state["on_off"]: # turning on
new_state = light_state["dft_on_state"]
new_state["on_off"] = 1
self.proto["smartlife.iot.smartbulb.lightingservice"][
"get_light_state"
] = new_state
elif not x["on_off"] and light_state["on_off"]:
new_state = {"dft_on_state": light_state, "on_off": 0}
self.proto["smartlife.iot.smartbulb.lightingservice"][
"get_light_state"
] = new_state
return
if not light_state["on_off"] and "on_off" not in x:
light_state = light_state["dft_on_state"]
_LOGGER.debug("Current state: %s", light_state)
for key in x:
light_state[key] = x[key]
def light_state(self, x, *args):
light_state = self.proto["smartlife.iot.smartbulb.lightingservice"][
"get_light_state"
]
# Our tests have light state off, so we simply return the dft_on_state when device is on.
_LOGGER.info("reporting light state: %s", light_state)
if light_state["on_off"]:
return light_state["dft_on_state"]
else:
return light_state
baseproto = {
"system": {
"set_relay_state": set_relay_state,
"set_dev_alias": set_alias,
"set_led_off": set_led_off,
"get_dev_icon": {"icon": None, "hash": None},
"set_mac_addr": set_mac,
"get_sysinfo": None,
},
"emeter": {
"get_realtime": None,
"get_daystat": None,
"get_monthstat": None,
"erase_emeter_state": None,
},
"smartlife.iot.common.emeter": {
"get_realtime": None,
"get_daystat": None,
"get_monthstat": None,
"erase_emeter_state": None,
},
"smartlife.iot.smartbulb.lightingservice": {
"get_light_state": light_state,
"transition_light_state": transition_light_state,
},
"time": {
"get_time": {
"year": 2017,
"month": 1,
"mday": 2,
"hour": 3,
"min": 4,
"sec": 5,
},
"get_timezone": {
"zone_str": "test",
"dst_offset": -1,
"index": 12,
"tz_str": "test2",
},
"set_timezone": None,
},
# HS220 brightness, different setter and getter
"smartlife.iot.dimmer": {"set_brightness": set_hs220_brightness},
}
async def query(self, host, request, port=9999):
proto = self.proto
# collect child ids from context
try:
child_ids = request["context"]["child_ids"]
request.pop("context", None)
except KeyError:
child_ids = []
target = next(iter(request))
if target not in proto.keys():
return error(target, msg="target not found")
cmd = next(iter(request[target]))
if cmd not in proto[target].keys():
return error(target, cmd, msg="command not found")
params = request[target][cmd]
_LOGGER.debug(f"Going to execute {target}.{cmd} (params: {params}).. ")
if callable(proto[target][cmd]):
res = proto[target][cmd](self, params, child_ids)
_LOGGER.debug("[callable] %s.%s: %s", target, cmd, res)
# verify that change didn't break schema, requires refactoring..
# TestSmartPlug.sysinfo_schema(self.proto["system"]["get_sysinfo"])
return success(target, cmd, res)
elif isinstance(proto[target][cmd], dict):
res = proto[target][cmd]
_LOGGER.debug("[static] %s.%s: %s", target, cmd, res)
return success(target, cmd, res)
else:
raise NotImplementedError(f"target {target} cmd {cmd}")

609
kasa/tests/test_fixtures.py Normal file
View File

@@ -0,0 +1,609 @@
import asyncio
import datetime
from unittest.mock import patch
import pytest
from kasa import DeviceType, SmartDeviceException, SmartStrip
from .conftest import (
bulb,
color_bulb,
dimmable,
handle_turn_on,
has_emeter,
no_emeter,
non_color_bulb,
non_dimmable,
non_variable_temp,
plug,
strip,
turn_on,
variable_temp,
)
from .newfakes import (
BULB_SCHEMA,
CURRENT_CONSUMPTION_SCHEMA,
PLUG_SCHEMA,
TZ_SCHEMA,
FakeTransportProtocol,
)
@plug
def test_plug_sysinfo(dev):
dev.sync.update()
assert dev.sys_info is not None
PLUG_SCHEMA(dev.sys_info)
assert dev.model is not None
assert dev.device_type == DeviceType.Plug or dev.device_type == DeviceType.Strip
assert dev.is_plug or dev.is_strip
@bulb
def test_bulb_sysinfo(dev):
dev.sync.update()
assert dev.sys_info is not None
BULB_SCHEMA(dev.sys_info)
assert dev.model is not None
assert dev.device_type == DeviceType.Bulb
assert dev.is_bulb
def test_state_info(dev):
dev.sync.update()
assert isinstance(dev.sync.state_information, dict)
def test_invalid_connection(dev):
with patch.object(FakeTransportProtocol, "query", side_effect=SmartDeviceException):
with pytest.raises(SmartDeviceException):
dev.sync.update()
dev.is_on
def test_query_helper(dev):
with pytest.raises(SmartDeviceException):
dev.sync._query_helper("test", "testcmd", {})
# TODO check for unwrapping?
@turn_on
def test_state(dev, turn_on):
handle_turn_on(dev, turn_on)
dev.sync.update()
orig_state = dev.is_on
if orig_state:
dev.sync.turn_off()
assert not dev.is_on
assert dev.is_off
dev.sync.turn_on()
assert dev.is_on
assert not dev.is_off
else:
dev.sync.turn_on()
assert dev.is_on
assert not dev.is_off
dev.sync.turn_off()
assert not dev.is_on
assert dev.is_off
@no_emeter
def test_no_emeter(dev):
dev.sync.update()
assert not dev.has_emeter
with pytest.raises(SmartDeviceException):
dev.sync.get_emeter_realtime()
with pytest.raises(SmartDeviceException):
dev.sync.get_emeter_daily()
with pytest.raises(SmartDeviceException):
dev.sync.get_emeter_monthly()
with pytest.raises(SmartDeviceException):
dev.sync.erase_emeter_stats()
@has_emeter
def test_get_emeter_realtime(dev):
dev.sync.update()
if dev.is_strip:
pytest.skip("Disabled for HS300 temporarily")
assert dev.has_emeter
current_emeter = dev.sync.get_emeter_realtime()
CURRENT_CONSUMPTION_SCHEMA(current_emeter)
@has_emeter
def test_get_emeter_daily(dev):
dev.sync.update()
if dev.is_strip:
pytest.skip("Disabled for HS300 temporarily")
assert dev.has_emeter
assert dev.sync.get_emeter_daily(year=1900, month=1) == {}
d = dev.sync.get_emeter_daily()
assert len(d) > 0
k, v = d.popitem()
assert isinstance(k, int)
assert isinstance(v, float)
# Test kwh (energy, energy_wh)
d = dev.sync.get_emeter_daily(kwh=False)
k2, v2 = d.popitem()
assert v * 1000 == v2
@has_emeter
def test_get_emeter_monthly(dev):
dev.sync.update()
if dev.is_strip:
pytest.skip("Disabled for HS300 temporarily")
assert dev.has_emeter
assert dev.sync.get_emeter_monthly(year=1900) == {}
d = dev.sync.get_emeter_monthly()
assert len(d) > 0
k, v = d.popitem()
assert isinstance(k, int)
assert isinstance(v, float)
# Test kwh (energy, energy_wh)
d = dev.sync.get_emeter_monthly(kwh=False)
k2, v2 = d.popitem()
assert v * 1000 == v2
@has_emeter
def test_emeter_status(dev):
dev.sync.update()
if dev.is_strip:
pytest.skip("Disabled for HS300 temporarily")
assert dev.has_emeter
d = dev.sync.get_emeter_realtime()
with pytest.raises(KeyError):
assert d["foo"]
assert d["power_mw"] == d["power"] * 1000
# bulbs have only power according to tplink simulator.
if not dev.is_bulb:
assert d["voltage_mv"] == d["voltage"] * 1000
assert d["current_ma"] == d["current"] * 1000
assert d["total_wh"] == d["total"] * 1000
@pytest.mark.skip("not clearing your stats..")
@has_emeter
def test_erase_emeter_stats(dev):
dev.sync.update()
assert dev.has_emeter
dev.sync.erase_emeter()
@has_emeter
def test_current_consumption(dev):
dev.sync.update()
if dev.is_strip:
pytest.skip("Disabled for HS300 temporarily")
if dev.has_emeter:
x = dev.sync.current_consumption()
assert isinstance(x, float)
assert x >= 0.0
else:
assert dev.sync.current_consumption() is None
def test_alias(dev):
dev.sync.update()
test_alias = "TEST1234"
original = dev.sync.alias
assert isinstance(original, str)
dev.sync.set_alias(test_alias)
assert dev.sync.alias == test_alias
dev.sync.set_alias(original)
assert dev.sync.alias == original
@plug
def test_led(dev):
dev.sync.update()
original = dev.led
dev.sync.set_led(False)
assert not dev.led
dev.sync.set_led(True)
assert dev.led
dev.sync.set_led(original)
@plug
def test_on_since(dev):
dev.sync.update()
assert isinstance(dev.on_since, datetime.datetime)
def test_icon(dev):
assert set(dev.sync.get_icon().keys()), {"icon", "hash"}
def test_time(dev):
assert isinstance(dev.sync.get_time(), datetime.datetime)
# TODO check setting?
def test_timezone(dev):
TZ_SCHEMA(dev.sync.get_timezone())
def test_hw_info(dev):
dev.sync.update()
PLUG_SCHEMA(dev.hw_info)
def test_location(dev):
dev.sync.update()
PLUG_SCHEMA(dev.location)
def test_rssi(dev):
dev.sync.update()
PLUG_SCHEMA({"rssi": dev.rssi}) # wrapping for vol
def test_mac(dev):
dev.sync.update()
PLUG_SCHEMA({"mac": dev.mac}) # wrapping for val
# TODO check setting?
@non_variable_temp
def test_temperature_on_nonsupporting(dev):
dev.sync.update()
assert dev.valid_temperature_range == (0, 0)
# TODO test when device does not support temperature range
with pytest.raises(SmartDeviceException):
dev.sync.set_color_temp(2700)
with pytest.raises(SmartDeviceException):
print(dev.sync.color_temp)
@variable_temp
def test_out_of_range_temperature(dev):
dev.sync.update()
with pytest.raises(ValueError):
dev.sync.set_color_temp(1000)
with pytest.raises(ValueError):
dev.sync.set_color_temp(10000)
@non_dimmable
def test_non_dimmable(dev):
dev.sync.update()
assert not dev.is_dimmable
with pytest.raises(SmartDeviceException):
assert dev.brightness == 0
with pytest.raises(SmartDeviceException):
dev.sync.set_brightness(100)
@dimmable
@turn_on
def test_dimmable_brightness(dev, turn_on):
handle_turn_on(dev, turn_on)
dev.sync.update()
assert dev.is_dimmable
dev.sync.set_brightness(50)
assert dev.brightness == 50
dev.sync.set_brightness(10)
assert dev.brightness == 10
with pytest.raises(ValueError):
dev.sync.set_brightness("foo")
@dimmable
def test_invalid_brightness(dev):
dev.sync.update()
assert dev.is_dimmable
with pytest.raises(ValueError):
dev.sync.set_brightness(110)
with pytest.raises(ValueError):
dev.sync.set_brightness(-100)
@color_bulb
@turn_on
def test_hsv(dev, turn_on):
handle_turn_on(dev, turn_on)
dev.sync.update()
assert dev.is_color
hue, saturation, brightness = dev.hsv
assert 0 <= hue <= 255
assert 0 <= saturation <= 100
assert 0 <= brightness <= 100
dev.sync.set_hsv(hue=1, saturation=1, value=1)
hue, saturation, brightness = dev.hsv
assert hue == 1
assert saturation == 1
assert brightness == 1
@color_bulb
@turn_on
def test_invalid_hsv(dev, turn_on):
handle_turn_on(dev, turn_on)
dev.sync.update()
assert dev.is_color
for invalid_hue in [-1, 361, 0.5]:
with pytest.raises(ValueError):
dev.sync.set_hsv(invalid_hue, 0, 0)
for invalid_saturation in [-1, 101, 0.5]:
with pytest.raises(ValueError):
dev.sync.set_hsv(0, invalid_saturation, 0)
for invalid_brightness in [-1, 101, 0.5]:
with pytest.raises(ValueError):
dev.sync.set_hsv(0, 0, invalid_brightness)
@non_color_bulb
def test_hsv_on_non_color(dev):
dev.sync.update()
assert not dev.is_color
with pytest.raises(SmartDeviceException):
dev.sync.set_hsv(0, 0, 0)
with pytest.raises(SmartDeviceException):
print(dev.hsv)
@variable_temp
@turn_on
def test_try_set_colortemp(dev, turn_on):
dev.sync.update()
handle_turn_on(dev, turn_on)
dev.sync.set_color_temp(2700)
assert dev.sync.color_temp == 2700
@non_variable_temp
def test_non_variable_temp(dev):
with pytest.raises(SmartDeviceException):
dev.sync.update()
dev.sync.set_color_temp(2700)
@strip
@turn_on
def test_children_change_state(dev, turn_on):
dev.sync.update()
handle_turn_on(dev, turn_on)
for plug in dev.plugs:
plug.sync.update()
orig_state = plug.is_on
if orig_state:
plug.turn_off()
plug.sync.update()
assert not plug.is_on
assert plug.is_off
plug.sync.turn_on()
plug.sync.update()
assert plug.is_on
assert not plug.is_off
else:
plug.sync.turn_on()
plug.sync.update()
assert plug.is_on
assert not plug.is_off
plug.sync.turn_off()
plug.sync.update()
assert not plug.is_on
assert plug.is_off
@strip
def test_children_alias(dev):
test_alias = "TEST1234"
for plug in dev.plugs:
plug.sync.update()
original = plug.alias
plug.sync.set_alias(alias=test_alias)
plug.sync.update()
assert plug.alias == test_alias
plug.sync.set_alias(alias=original)
plug.sync.update()
assert plug.alias == original
@strip
def test_children_on_since(dev):
for plug in dev.plugs:
plug.sync.update()
assert plug.on_since
@pytest.mark.skip("this test will wear out your relays")
def test_all_binary_states(dev):
# test every binary state
for state in range(2 ** dev.num_children):
# create binary state map
state_map = {}
for plug_index in range(dev.num_children):
state_map[plug_index] = bool((state >> plug_index) & 1)
if state_map[plug_index]:
dev.sync.turn_on(index=plug_index)
else:
dev.sync.turn_off(index=plug_index)
# check state map applied
for index, state in dev.is_on.items():
assert state_map[index] == state
# toggle each outlet with state map applied
for plug_index in range(dev.num_children):
# toggle state
if state_map[plug_index]:
dev.sync.turn_off(index=plug_index)
else:
dev.sync.turn_on(index=plug_index)
# only target outlet should have state changed
for index, state in dev.is_on.items():
if index == plug_index:
assert state != state_map[index]
else:
assert state == state_map[index]
# reset state
if state_map[plug_index]:
dev.sync.turn_on(index=plug_index)
else:
dev.sync.turn_off(index=plug_index)
# original state map should be restored
for index, state in dev.is_on.items():
assert state == state_map[index]
@strip
def test_children_get_emeter_realtime(dev):
dev.sync.update()
assert dev.has_emeter
# test with index
for plug in dev.plugs:
plug.sync.update()
emeter = plug.sync.get_emeter_realtime()
CURRENT_CONSUMPTION_SCHEMA(emeter)
# test without index
# TODO test that sum matches the sum of individiaul plugs.
# for index, emeter in dev.sync.get_emeter_realtime().items():
# CURRENT_CONSUMPTION_SCHEMA(emeter)
@strip
def test_children_get_emeter_daily(dev):
dev.sync.update()
assert dev.has_emeter
# test individual emeters
for plug in dev.plugs:
plug.sync.update()
emeter = plug.sync.get_emeter_daily(year=1900, month=1)
assert emeter == {}
emeter = plug.sync.get_emeter_daily()
assert len(emeter) > 0
k, v = emeter.popitem()
assert isinstance(k, int)
assert isinstance(v, float)
# test sum of emeters
all_emeter = dev.sync.get_emeter_daily(year=1900, month=1)
k, v = all_emeter.popitem()
assert isinstance(k, int)
assert isinstance(v, float)
@strip
def test_children_get_emeter_monthly(dev):
dev.sync.update()
assert dev.has_emeter
# test individual emeters
for plug in dev.plugs:
plug.sync.update()
emeter = plug.sync.get_emeter_monthly(year=1900)
assert emeter == {}
emeter = plug.sync.get_emeter_monthly()
assert len(emeter) > 0
k, v = emeter.popitem()
assert isinstance(k, int)
assert isinstance(v, float)
# test sum of emeters
all_emeter = dev.sync.get_emeter_monthly(year=1900)
k, v = all_emeter.popitem()
assert isinstance(k, int)
assert isinstance(v, float)
# def test_cache(dev):
# from datetime import timedelta
# dev.sync.cache_ttl = timedelta(seconds=3)
# with patch.object(
# FakeTransportProtocol, "query", wraps=dev.protocol.query
# ) as query_mock:
# CHECK_COUNT = 1
# # Smartstrip calls sysinfo in its __init__ to request children, so
# # the even first get call here will get its results from the cache.
# if dev.is_strip:
# CHECK_COUNT = 0
# dev.sys_info
# assert query_mock.call_count == CHECK_COUNT
# dev.sys_info
# assert query_mock.call_count == CHECK_COUNT
# def test_cache_invalidates(dev):
# from datetime import timedelta
# dev.sync.cache_ttl = timedelta(seconds=0)
# with patch.object(
# FakeTransportProtocol, "query", wraps=dev.protocol.query
# ) as query_mock:
# dev.sys_info
# assert query_mock.call_count == 1
# dev.sys_info
# assert query_mock.call_count == 2
# # assert query_mock.called_once()
def test_representation(dev):
import re
pattern = re.compile("<.* model .* at .* (.*), is_on: .* - dev specific: .*>")
assert pattern.match(str(dev))

View File

@@ -0,0 +1,73 @@
import json
from unittest import TestCase
from ..protocol import TPLinkSmartHomeProtocol
class TestTPLinkSmartHomeProtocol(TestCase):
def test_encrypt(self):
d = json.dumps({"foo": 1, "bar": 2})
encrypted = TPLinkSmartHomeProtocol.encrypt(d)
# encrypt adds a 4 byte header
encrypted = encrypted[4:]
self.assertEqual(d, TPLinkSmartHomeProtocol.decrypt(encrypted))
def test_encrypt_unicode(self):
d = "{'snowman': '\u2603'}"
e = bytes(
[
208,
247,
132,
234,
133,
242,
159,
254,
144,
183,
141,
173,
138,
104,
240,
115,
84,
41,
]
)
encrypted = TPLinkSmartHomeProtocol.encrypt(d)
# encrypt adds a 4 byte header
encrypted = encrypted[4:]
self.assertEqual(e, encrypted)
def test_decrypt_unicode(self):
e = bytes(
[
208,
247,
132,
234,
133,
242,
159,
254,
144,
183,
141,
173,
138,
104,
240,
115,
84,
41,
]
)
d = "{'snowman': '\u2603'}"
self.assertEqual(d, TPLinkSmartHomeProtocol.decrypt(e))