mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-08-09 20:24:02 +00:00
Merge remote-tracking branch 'upstream/master' into feat/device_update
This commit is contained in:
@@ -109,7 +109,7 @@ DIMMERS = {
|
||||
}
|
||||
|
||||
HUBS_SMART = {"H100", "KH100"}
|
||||
SENSORS_SMART = {"T310", "T315", "T300"}
|
||||
SENSORS_SMART = {"T310", "T315", "T300", "T110"}
|
||||
THERMOSTATS_SMART = {"KE100"}
|
||||
|
||||
WITH_EMETER_IOT = {"HS110", "HS300", "KP115", "KP125", *BULBS_IOT}
|
||||
@@ -203,14 +203,14 @@ wallswitch_iot = parametrize(
|
||||
"wall switches iot", model_filter=SWITCHES, protocol_filter={"IOT"}
|
||||
)
|
||||
strip = parametrize("strips", model_filter=STRIPS, protocol_filter={"SMART", "IOT"})
|
||||
dimmer = parametrize("dimmers", model_filter=DIMMERS, protocol_filter={"IOT"})
|
||||
lightstrip = parametrize(
|
||||
dimmer_iot = parametrize("dimmers", model_filter=DIMMERS, protocol_filter={"IOT"})
|
||||
lightstrip_iot = parametrize(
|
||||
"lightstrips", model_filter=LIGHT_STRIPS, protocol_filter={"IOT"}
|
||||
)
|
||||
|
||||
# bulb types
|
||||
dimmable = parametrize("dimmable", model_filter=DIMMABLE, protocol_filter={"IOT"})
|
||||
non_dimmable = parametrize(
|
||||
dimmable_iot = parametrize("dimmable", model_filter=DIMMABLE, protocol_filter={"IOT"})
|
||||
non_dimmable_iot = parametrize(
|
||||
"non-dimmable", model_filter=BULBS - DIMMABLE, protocol_filter={"IOT"}
|
||||
)
|
||||
variable_temp = parametrize(
|
||||
@@ -292,12 +292,12 @@ device_iot = parametrize(
|
||||
def check_categories():
|
||||
"""Check that every fixture file is categorized."""
|
||||
categorized_fixtures = set(
|
||||
dimmer.args[1]
|
||||
dimmer_iot.args[1]
|
||||
+ strip.args[1]
|
||||
+ plug.args[1]
|
||||
+ bulb.args[1]
|
||||
+ wallswitch.args[1]
|
||||
+ lightstrip.args[1]
|
||||
+ lightstrip_iot.args[1]
|
||||
+ bulb_smart.args[1]
|
||||
+ dimmers_smart.args[1]
|
||||
+ hubs_smart.args[1]
|
||||
|
@@ -189,6 +189,11 @@ class FakeSmartTransport(BaseTransport):
|
||||
if "current_rule_id" in info["get_dynamic_light_effect_rules"]:
|
||||
del info["get_dynamic_light_effect_rules"]["current_rule_id"]
|
||||
|
||||
def _set_led_info(self, info, params):
|
||||
"""Set or remove values as per the device behaviour."""
|
||||
info["get_led_info"]["led_status"] = params["led_rule"] != "never"
|
||||
info["get_led_info"]["led_rule"] = params["led_rule"]
|
||||
|
||||
def _send_request(self, request_dict: dict):
|
||||
method = request_dict["method"]
|
||||
params = request_dict["params"]
|
||||
@@ -218,7 +223,9 @@ class FakeSmartTransport(BaseTransport):
|
||||
# SMART fixtures started to be generated
|
||||
missing_result := self.FIXTURE_MISSING_MAP.get(method)
|
||||
) and missing_result[0] in self.components:
|
||||
result = copy.deepcopy(missing_result[1])
|
||||
# Copy to info so it will work with update methods
|
||||
info[method] = copy.deepcopy(missing_result[1])
|
||||
result = copy.deepcopy(info[method])
|
||||
retval = {"result": result, "error_code": 0}
|
||||
else:
|
||||
# PARAMS error returned for KS240 when get_device_usage called
|
||||
@@ -234,11 +241,14 @@ class FakeSmartTransport(BaseTransport):
|
||||
pytest.fixtures_missing_methods[self.fixture_name] = set()
|
||||
pytest.fixtures_missing_methods[self.fixture_name].add(method)
|
||||
return retval
|
||||
elif method == "set_qs_info":
|
||||
elif method in ["set_qs_info", "fw_download"]:
|
||||
return {"error_code": 0}
|
||||
elif method == "set_dynamic_light_effect_rule_enable":
|
||||
self._set_light_effect(info, params)
|
||||
return {"error_code": 0}
|
||||
elif method == "set_led_info":
|
||||
self._set_led_info(info, params)
|
||||
return {"error_code": 0}
|
||||
elif method[:4] == "set_":
|
||||
target_method = f"get_{method[4:]}"
|
||||
info[target_method].update(params)
|
||||
|
547
kasa/tests/fixtures/smart/H100(EU)_1.0_1.5.10.json
vendored
Normal file
547
kasa/tests/fixtures/smart/H100(EU)_1.0_1.5.10.json
vendored
Normal file
@@ -0,0 +1,547 @@
|
||||
{
|
||||
"component_nego": {
|
||||
"component_list": [
|
||||
{
|
||||
"id": "device",
|
||||
"ver_code": 2
|
||||
},
|
||||
{
|
||||
"id": "firmware",
|
||||
"ver_code": 2
|
||||
},
|
||||
{
|
||||
"id": "quick_setup",
|
||||
"ver_code": 3
|
||||
},
|
||||
{
|
||||
"id": "inherit",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "time",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "wireless",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "account",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "synchronize",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "sunrise_sunset",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "led",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "cloud_connect",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "iot_cloud",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "child_device",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "child_quick_setup",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "child_inherit",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "control_child",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "alarm",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "device_load",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "device_local_time",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "alarm_logs",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "localSmart",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "matter",
|
||||
"ver_code": 3
|
||||
},
|
||||
{
|
||||
"id": "chime",
|
||||
"ver_code": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"discovery_result": {
|
||||
"device_id": "00000000000000000000000000000000",
|
||||
"device_model": "H100(EU)",
|
||||
"device_type": "SMART.TAPOHUB",
|
||||
"factory_default": false,
|
||||
"ip": "127.0.0.123",
|
||||
"is_support_iot_cloud": true,
|
||||
"mac": "3C-52-A1-00-00-00",
|
||||
"mgt_encrypt_schm": {
|
||||
"encrypt_type": "AES",
|
||||
"http_port": 80,
|
||||
"is_support_https": false,
|
||||
"lv": 2
|
||||
},
|
||||
"obd_src": "tplink",
|
||||
"owner": "00000000000000000000000000000000"
|
||||
},
|
||||
"get_alarm_configure": {
|
||||
"duration": 10,
|
||||
"type": "Alarm 1",
|
||||
"volume": "high"
|
||||
},
|
||||
"get_auto_update_info": {
|
||||
"enable": false,
|
||||
"random_range": 120,
|
||||
"time": 180
|
||||
},
|
||||
"get_child_device_component_list": {
|
||||
"child_component_list": [
|
||||
{
|
||||
"component_list": [
|
||||
{
|
||||
"id": "device",
|
||||
"ver_code": 2
|
||||
},
|
||||
{
|
||||
"id": "quick_setup",
|
||||
"ver_code": 3
|
||||
},
|
||||
{
|
||||
"id": "time",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "device_local_time",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "schedule",
|
||||
"ver_code": 2
|
||||
},
|
||||
{
|
||||
"id": "countdown",
|
||||
"ver_code": 2
|
||||
},
|
||||
{
|
||||
"id": "account",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "synchronize",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "sunrise_sunset",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "cloud_connect",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "iot_cloud",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "frost_protection",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "child_protection",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "temperature",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "temp_control",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "remove_scale",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "progress_calibration",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "early_start",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "temp_record",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "screen_setting",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "night_mode",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "smart_control_schedule",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "firmware",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "battery_detect",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "temperature_correction",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "window_open_detect",
|
||||
"ver_code": 2
|
||||
},
|
||||
{
|
||||
"id": "shutdown_mode",
|
||||
"ver_code": 1
|
||||
}
|
||||
],
|
||||
"device_id": "SCRUBBED_CHILD_DEVICE_ID_1"
|
||||
},
|
||||
{
|
||||
"component_list": [
|
||||
{
|
||||
"id": "device",
|
||||
"ver_code": 2
|
||||
},
|
||||
{
|
||||
"id": "quick_setup",
|
||||
"ver_code": 3
|
||||
},
|
||||
{
|
||||
"id": "trigger_log",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "time",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "device_local_time",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "account",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "synchronize",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "cloud_connect",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "iot_cloud",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "firmware",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "localSmart",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "battery_detect",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "temperature",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "humidity",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "temp_humidity_record",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "comfort_temperature",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "comfort_humidity",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "report_mode",
|
||||
"ver_code": 1
|
||||
}
|
||||
],
|
||||
"device_id": "SCRUBBED_CHILD_DEVICE_ID_2"
|
||||
}
|
||||
],
|
||||
"start_index": 0,
|
||||
"sum": 2
|
||||
},
|
||||
"get_child_device_list": {
|
||||
"child_device_list": [
|
||||
{
|
||||
"at_low_battery": false,
|
||||
"avatar": "",
|
||||
"battery_percentage": 100,
|
||||
"bind_count": 5,
|
||||
"category": "subg.trv",
|
||||
"child_protection": false,
|
||||
"current_temp": 22.9,
|
||||
"device_id": "SCRUBBED_CHILD_DEVICE_ID_1",
|
||||
"frost_protection_on": false,
|
||||
"fw_ver": "2.4.0 Build 230804 Rel.193040",
|
||||
"hw_id": "00000000000000000000000000000000",
|
||||
"hw_ver": "1.0",
|
||||
"jamming_rssi": -113,
|
||||
"jamming_signal_level": 1,
|
||||
"location": "",
|
||||
"mac": "A842A1000000",
|
||||
"max_control_temp": 30,
|
||||
"min_control_temp": 5,
|
||||
"model": "KE100",
|
||||
"nickname": "I01BU0tFRF9OQU1FIw==",
|
||||
"oem_id": "00000000000000000000000000000000",
|
||||
"parent_device_id": "0000000000000000000000000000000000000000",
|
||||
"region": "Europe/Berlin",
|
||||
"rssi": -7,
|
||||
"signal_level": 3,
|
||||
"specs": "EU",
|
||||
"status": "online",
|
||||
"status_follow_edge": false,
|
||||
"target_temp": 23.0,
|
||||
"temp_offset": 0,
|
||||
"temp_unit": "celsius",
|
||||
"trv_states": [
|
||||
"heating"
|
||||
],
|
||||
"type": "SMART.KASAENERGY"
|
||||
},
|
||||
{
|
||||
"at_low_battery": false,
|
||||
"avatar": "",
|
||||
"battery_percentage": 100,
|
||||
"bind_count": 1,
|
||||
"category": "subg.trigger.temp-hmdt-sensor",
|
||||
"current_humidity": 62,
|
||||
"current_humidity_exception": 2,
|
||||
"current_temp": 24.0,
|
||||
"current_temp_exception": 0,
|
||||
"device_id": "SCRUBBED_CHILD_DEVICE_ID_2",
|
||||
"fw_ver": "1.7.0 Build 230424 Rel.170332",
|
||||
"hw_id": "00000000000000000000000000000000",
|
||||
"hw_ver": "1.0",
|
||||
"jamming_rssi": -115,
|
||||
"jamming_signal_level": 1,
|
||||
"lastOnboardingTimestamp": 1706990901,
|
||||
"mac": "F0A731000000",
|
||||
"model": "T315",
|
||||
"nickname": "I01BU0tFRF9OQU1FIw==",
|
||||
"oem_id": "00000000000000000000000000000000",
|
||||
"parent_device_id": "0000000000000000000000000000000000000000",
|
||||
"region": "Europe/Berlin",
|
||||
"report_interval": 16,
|
||||
"rssi": -38,
|
||||
"signal_level": 3,
|
||||
"specs": "EU",
|
||||
"status": "online",
|
||||
"status_follow_edge": false,
|
||||
"temp_unit": "celsius",
|
||||
"type": "SMART.TAPOSENSOR"
|
||||
}
|
||||
],
|
||||
"start_index": 0,
|
||||
"sum": 2
|
||||
},
|
||||
"get_connect_cloud_state": {
|
||||
"status": 0
|
||||
},
|
||||
"get_device_info": {
|
||||
"avatar": "hub",
|
||||
"device_id": "0000000000000000000000000000000000000000",
|
||||
"fw_id": "00000000000000000000000000000000",
|
||||
"fw_ver": "1.5.10 Build 240207 Rel.175759",
|
||||
"has_set_location_info": true,
|
||||
"hw_id": "00000000000000000000000000000000",
|
||||
"hw_ver": "1.0",
|
||||
"in_alarm": false,
|
||||
"in_alarm_source": "",
|
||||
"ip": "127.0.0.123",
|
||||
"lang": "de_DE",
|
||||
"latitude": 0,
|
||||
"longitude": 0,
|
||||
"mac": "3C-52-A1-00-00-00",
|
||||
"model": "H100",
|
||||
"nickname": "I01BU0tFRF9OQU1FIw==",
|
||||
"oem_id": "00000000000000000000000000000000",
|
||||
"overheated": false,
|
||||
"region": "Europe/Berlin",
|
||||
"rssi": -60,
|
||||
"signal_level": 2,
|
||||
"specs": "EU",
|
||||
"ssid": "I01BU0tFRF9TU0lEIw==",
|
||||
"time_diff": 60,
|
||||
"type": "SMART.TAPOHUB"
|
||||
},
|
||||
"get_device_load_info": {
|
||||
"cur_load_num": 4,
|
||||
"load_level": "light",
|
||||
"max_load_num": 64,
|
||||
"total_memory": 4352,
|
||||
"used_memory": 1451
|
||||
},
|
||||
"get_device_time": {
|
||||
"region": "Europe/Berlin",
|
||||
"time_diff": 60,
|
||||
"timestamp": 1714669215
|
||||
},
|
||||
"get_device_usage": {},
|
||||
"get_fw_download_state": {
|
||||
"auto_upgrade": false,
|
||||
"download_progress": 0,
|
||||
"reboot_time": 5,
|
||||
"status": 0,
|
||||
"upgrade_time": 5
|
||||
},
|
||||
"get_inherit_info": null,
|
||||
"get_latest_fw": {
|
||||
"fw_size": 0,
|
||||
"fw_ver": "1.5.10 Build 240207 Rel.175759",
|
||||
"hw_id": "",
|
||||
"need_to_upgrade": false,
|
||||
"oem_id": "",
|
||||
"release_date": "",
|
||||
"release_note": "",
|
||||
"type": 0
|
||||
},
|
||||
"get_led_info": {
|
||||
"led_rule": "never",
|
||||
"led_status": false,
|
||||
"night_mode": {
|
||||
"end_time": 358,
|
||||
"night_mode_type": "sunrise_sunset",
|
||||
"start_time": 1259,
|
||||
"sunrise_offset": 0,
|
||||
"sunset_offset": 0
|
||||
}
|
||||
},
|
||||
"get_matter_setup_info": {
|
||||
"setup_code": "00000000000",
|
||||
"setup_payload": "00:0000000000000000000"
|
||||
},
|
||||
"get_support_alarm_type_list": {
|
||||
"alarm_type_list": [
|
||||
"Doorbell Ring 1",
|
||||
"Doorbell Ring 2",
|
||||
"Doorbell Ring 3",
|
||||
"Doorbell Ring 4",
|
||||
"Doorbell Ring 5",
|
||||
"Doorbell Ring 6",
|
||||
"Doorbell Ring 7",
|
||||
"Doorbell Ring 8",
|
||||
"Doorbell Ring 9",
|
||||
"Doorbell Ring 10",
|
||||
"Phone Ring",
|
||||
"Alarm 1",
|
||||
"Alarm 2",
|
||||
"Alarm 3",
|
||||
"Alarm 4",
|
||||
"Dripping Tap",
|
||||
"Alarm 5",
|
||||
"Connection 1",
|
||||
"Connection 2"
|
||||
]
|
||||
},
|
||||
"get_support_child_device_category": {
|
||||
"device_category_list": [
|
||||
{
|
||||
"category": "subg.trv"
|
||||
},
|
||||
{
|
||||
"category": "subg.trigger"
|
||||
},
|
||||
{
|
||||
"category": "subg.plugswitch"
|
||||
}
|
||||
]
|
||||
},
|
||||
"get_wireless_scan_info": {
|
||||
"ap_list": [],
|
||||
"start_index": 0,
|
||||
"sum": 0,
|
||||
"wep_supported": false
|
||||
},
|
||||
"qs_component_nego": {
|
||||
"component_list": [
|
||||
{
|
||||
"id": "quick_setup",
|
||||
"ver_code": 3
|
||||
},
|
||||
{
|
||||
"id": "sunrise_sunset",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "iot_cloud",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "inherit",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "firmware",
|
||||
"ver_code": 2
|
||||
},
|
||||
{
|
||||
"id": "matter",
|
||||
"ver_code": 3
|
||||
}
|
||||
],
|
||||
"extra_info": {
|
||||
"device_model": "H100",
|
||||
"device_type": "SMART.TAPOHUB",
|
||||
"is_klap": false
|
||||
}
|
||||
}
|
||||
}
|
170
kasa/tests/fixtures/smart/child/KE100(EU)_1.0_2.4.0.json
vendored
Normal file
170
kasa/tests/fixtures/smart/child/KE100(EU)_1.0_2.4.0.json
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"component_nego": {
|
||||
"component_list": [
|
||||
{
|
||||
"id": "device",
|
||||
"ver_code": 2
|
||||
},
|
||||
{
|
||||
"id": "quick_setup",
|
||||
"ver_code": 3
|
||||
},
|
||||
{
|
||||
"id": "time",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "device_local_time",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "schedule",
|
||||
"ver_code": 2
|
||||
},
|
||||
{
|
||||
"id": "countdown",
|
||||
"ver_code": 2
|
||||
},
|
||||
{
|
||||
"id": "account",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "synchronize",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "sunrise_sunset",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "cloud_connect",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "iot_cloud",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "frost_protection",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "child_protection",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "temperature",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "temp_control",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "remove_scale",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "progress_calibration",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "early_start",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "temp_record",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "screen_setting",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "night_mode",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "smart_control_schedule",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "firmware",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "battery_detect",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "temperature_correction",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "window_open_detect",
|
||||
"ver_code": 2
|
||||
},
|
||||
{
|
||||
"id": "shutdown_mode",
|
||||
"ver_code": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"get_connect_cloud_state": {
|
||||
"status": 0
|
||||
},
|
||||
"get_device_info": {
|
||||
"at_low_battery": false,
|
||||
"avatar": "",
|
||||
"battery_percentage": 100,
|
||||
"bind_count": 5,
|
||||
"category": "subg.trv",
|
||||
"child_protection": false,
|
||||
"current_temp": 22.9,
|
||||
"device_id": "SCRUBBED_CHILD_DEVICE_ID_1",
|
||||
"frost_protection_on": false,
|
||||
"fw_ver": "2.4.0 Build 230804 Rel.193040",
|
||||
"hw_id": "00000000000000000000000000000000",
|
||||
"hw_ver": "1.0",
|
||||
"jamming_rssi": -113,
|
||||
"jamming_signal_level": 1,
|
||||
"lastOnboardingTimestamp": 1713888871,
|
||||
"location": "",
|
||||
"mac": "A842A1000000",
|
||||
"max_control_temp": 30,
|
||||
"min_control_temp": 5,
|
||||
"model": "KE100",
|
||||
"nickname": "I01BU0tFRF9OQU1FIw==",
|
||||
"oem_id": "00000000000000000000000000000000",
|
||||
"parent_device_id": "0000000000000000000000000000000000000000",
|
||||
"region": "Europe/Berlin",
|
||||
"rssi": -7,
|
||||
"signal_level": 3,
|
||||
"specs": "EU",
|
||||
"status": "online",
|
||||
"target_temp": 23.0,
|
||||
"temp_offset": 0,
|
||||
"temp_unit": "celsius",
|
||||
"trv_states": [
|
||||
"heating"
|
||||
],
|
||||
"type": "SMART.KASAENERGY"
|
||||
},
|
||||
"get_fw_download_state": {
|
||||
"cloud_cache_seconds": 1,
|
||||
"download_progress": 0,
|
||||
"reboot_time": 5,
|
||||
"status": 0,
|
||||
"upgrade_time": 5
|
||||
},
|
||||
"get_latest_fw": {
|
||||
"fw_ver": "2.8.0 Build 240202 Rel.135229",
|
||||
"hw_id": "00000000000000000000000000000000",
|
||||
"need_to_upgrade": true,
|
||||
"oem_id": "00000000000000000000000000000000",
|
||||
"release_date": "2024-02-05",
|
||||
"release_note": "Modifications and Bug Fixes:\n1. Optimized the noise issue in some cases.\n2. Fixed some minor bugs.",
|
||||
"type": 2
|
||||
}
|
||||
}
|
526
kasa/tests/fixtures/smart/child/T110(EU)_1.0_1.8.0.json
vendored
Normal file
526
kasa/tests/fixtures/smart/child/T110(EU)_1.0_1.8.0.json
vendored
Normal file
@@ -0,0 +1,526 @@
|
||||
{
|
||||
"component_nego": {
|
||||
"component_list": [
|
||||
{
|
||||
"id": "device",
|
||||
"ver_code": 2
|
||||
},
|
||||
{
|
||||
"id": "quick_setup",
|
||||
"ver_code": 3
|
||||
},
|
||||
{
|
||||
"id": "trigger_log",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "time",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "device_local_time",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "account",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "synchronize",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "cloud_connect",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "iot_cloud",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "firmware",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "localSmart",
|
||||
"ver_code": 1
|
||||
},
|
||||
{
|
||||
"id": "battery_detect",
|
||||
"ver_code": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"get_connect_cloud_state": {
|
||||
"status": 0
|
||||
},
|
||||
"get_device_info": {
|
||||
"at_low_battery": false,
|
||||
"avatar": "sensor_t110",
|
||||
"bind_count": 1,
|
||||
"category": "subg.trigger.contact-sensor",
|
||||
"device_id": "SCRUBBED_CHILD_DEVICE_ID_2",
|
||||
"fw_ver": "1.8.0 Build 220728 Rel.160024",
|
||||
"hw_id": "00000000000000000000000000000000",
|
||||
"hw_ver": "1.0",
|
||||
"jamming_rssi": -113,
|
||||
"jamming_signal_level": 1,
|
||||
"lastOnboardingTimestamp": 1714661626,
|
||||
"mac": "E4FAC4000000",
|
||||
"model": "T110",
|
||||
"nickname": "I01BU0tFRF9OQU1FIw==",
|
||||
"oem_id": "00000000000000000000000000000000",
|
||||
"open": false,
|
||||
"parent_device_id": "0000000000000000000000000000000000000000",
|
||||
"region": "Europe/Berlin",
|
||||
"report_interval": 16,
|
||||
"rssi": -54,
|
||||
"signal_level": 3,
|
||||
"specs": "EU",
|
||||
"status": "online",
|
||||
"status_follow_edge": false,
|
||||
"type": "SMART.TAPOSENSOR"
|
||||
},
|
||||
"get_fw_download_state": {
|
||||
"cloud_cache_seconds": 1,
|
||||
"download_progress": 30,
|
||||
"reboot_time": 5,
|
||||
"status": 4,
|
||||
"upgrade_time": 5
|
||||
},
|
||||
"get_latest_fw": {
|
||||
"fw_ver": "1.9.0 Build 230704 Rel.154531",
|
||||
"hw_id": "00000000000000000000000000000000",
|
||||
"need_to_upgrade": true,
|
||||
"oem_id": "00000000000000000000000000000000",
|
||||
"release_date": "2023-10-30",
|
||||
"release_note": "Modifications and Bug Fixes:\n1. Reduced power consumption.\n2. Fixed some minor bugs.",
|
||||
"type": 2
|
||||
},
|
||||
"get_temp_humidity_records": {
|
||||
"local_time": 1714681046,
|
||||
"past24h_humidity": [
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000
|
||||
],
|
||||
"past24h_humidity_exception": [
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000
|
||||
],
|
||||
"past24h_temp": [
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000
|
||||
],
|
||||
"past24h_temp_exception": [
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000,
|
||||
-1000
|
||||
],
|
||||
"temp_unit": "celsius"
|
||||
},
|
||||
"get_trigger_logs": {
|
||||
"logs": [
|
||||
{
|
||||
"event": "close",
|
||||
"eventId": "8140289c-c66b-bdd6-63b9-542299442299",
|
||||
"id": 4,
|
||||
"timestamp": 1714661714
|
||||
},
|
||||
{
|
||||
"event": "open",
|
||||
"eventId": "fb4e1439-2f2c-a5e1-c35a-9e7c0d35a1e3",
|
||||
"id": 3,
|
||||
"timestamp": 1714661710
|
||||
},
|
||||
{
|
||||
"event": "close",
|
||||
"eventId": "ddee7733-1180-48ac-56a3-512018048ac5",
|
||||
"id": 2,
|
||||
"timestamp": 1714661657
|
||||
},
|
||||
{
|
||||
"event": "open",
|
||||
"eventId": "ab80951f-da38-49f9-21c5-bf025c7b606d",
|
||||
"id": 1,
|
||||
"timestamp": 1714661638
|
||||
}
|
||||
],
|
||||
"start_id": 4,
|
||||
"sum": 4
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@ import pytest
|
||||
|
||||
from kasa.iot import IotDevice
|
||||
from kasa.smart import SmartDevice
|
||||
from kasa.tests.conftest import dimmable, parametrize
|
||||
from kasa.tests.conftest import dimmable_iot, parametrize
|
||||
|
||||
brightness = parametrize("brightness smart", component_filter="brightness")
|
||||
|
||||
@@ -10,13 +10,13 @@ brightness = parametrize("brightness smart", component_filter="brightness")
|
||||
@brightness
|
||||
async def test_brightness_component(dev: SmartDevice):
|
||||
"""Test brightness feature."""
|
||||
brightness = dev.get_module("Brightness")
|
||||
brightness = dev.modules.get("Brightness")
|
||||
assert brightness
|
||||
assert isinstance(dev, SmartDevice)
|
||||
assert "brightness" in dev._components
|
||||
|
||||
# Test getting the value
|
||||
feature = brightness._module_features["brightness"]
|
||||
feature = dev.features["brightness"]
|
||||
assert isinstance(feature.value, int)
|
||||
assert feature.value > 1 and feature.value <= 100
|
||||
|
||||
@@ -32,7 +32,7 @@ async def test_brightness_component(dev: SmartDevice):
|
||||
await feature.set_value(feature.maximum_value + 10)
|
||||
|
||||
|
||||
@dimmable
|
||||
@dimmable_iot
|
||||
async def test_brightness_dimmable(dev: IotDevice):
|
||||
"""Test brightness feature."""
|
||||
assert isinstance(dev, IotDevice)
|
||||
|
28
kasa/tests/smart/modules/test_contact.py
Normal file
28
kasa/tests/smart/modules/test_contact.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import pytest
|
||||
|
||||
from kasa import Module, SmartDevice
|
||||
from kasa.tests.device_fixtures import parametrize
|
||||
|
||||
contact = parametrize(
|
||||
"is contact sensor", model_filter="T110", protocol_filter={"SMART.CHILD"}
|
||||
)
|
||||
|
||||
|
||||
@contact
|
||||
@pytest.mark.parametrize(
|
||||
"feature, type",
|
||||
[
|
||||
("is_open", bool),
|
||||
],
|
||||
)
|
||||
async def test_contact_features(dev: SmartDevice, feature, type):
|
||||
"""Test that features are registered and work as expected."""
|
||||
contact = dev.modules.get(Module.ContactSensor)
|
||||
assert contact is not None
|
||||
|
||||
prop = getattr(contact, feature)
|
||||
assert isinstance(prop, type)
|
||||
|
||||
feat = dev.features[feature]
|
||||
assert feat.value == prop
|
||||
assert isinstance(feat.value, type)
|
@@ -1,8 +1,8 @@
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import Module
|
||||
from kasa.smart import SmartDevice
|
||||
from kasa.smart.modules import FanModule
|
||||
from kasa.tests.device_fixtures import parametrize
|
||||
|
||||
fan = parametrize("has fan", component_filter="fan_control", protocol_filter={"SMART"})
|
||||
@@ -11,10 +11,10 @@ fan = parametrize("has fan", component_filter="fan_control", protocol_filter={"S
|
||||
@fan
|
||||
async def test_fan_speed(dev: SmartDevice, mocker: MockerFixture):
|
||||
"""Test fan speed feature."""
|
||||
fan = dev.get_module(FanModule)
|
||||
fan = dev.modules.get(Module.Fan)
|
||||
assert fan
|
||||
|
||||
level_feature = fan._module_features["fan_speed_level"]
|
||||
level_feature = dev.features["fan_speed_level"]
|
||||
assert (
|
||||
level_feature.minimum_value
|
||||
<= level_feature.value
|
||||
@@ -36,9 +36,9 @@ async def test_fan_speed(dev: SmartDevice, mocker: MockerFixture):
|
||||
@fan
|
||||
async def test_sleep_mode(dev: SmartDevice, mocker: MockerFixture):
|
||||
"""Test sleep mode feature."""
|
||||
fan = dev.get_module(FanModule)
|
||||
fan = dev.modules.get(Module.Fan)
|
||||
assert fan
|
||||
sleep_feature = fan._module_features["fan_sleep_mode"]
|
||||
sleep_feature = dev.features["fan_sleep_mode"]
|
||||
assert isinstance(sleep_feature.value, bool)
|
||||
|
||||
call = mocker.spy(fan, "call")
|
||||
@@ -52,29 +52,28 @@ async def test_sleep_mode(dev: SmartDevice, mocker: MockerFixture):
|
||||
|
||||
|
||||
@fan
|
||||
async def test_fan_interface(dev: SmartDevice, mocker: MockerFixture):
|
||||
async def test_fan_module(dev: SmartDevice, mocker: MockerFixture):
|
||||
"""Test fan speed on device interface."""
|
||||
assert isinstance(dev, SmartDevice)
|
||||
fan = dev.get_module(FanModule)
|
||||
fan = dev.modules.get(Module.Fan)
|
||||
assert fan
|
||||
device = fan._device
|
||||
assert device.is_fan
|
||||
|
||||
await device.set_fan_speed_level(1)
|
||||
await fan.set_fan_speed_level(1)
|
||||
await dev.update()
|
||||
assert device.fan_speed_level == 1
|
||||
assert fan.fan_speed_level == 1
|
||||
assert device.is_on
|
||||
|
||||
await device.set_fan_speed_level(4)
|
||||
await fan.set_fan_speed_level(4)
|
||||
await dev.update()
|
||||
assert device.fan_speed_level == 4
|
||||
assert fan.fan_speed_level == 4
|
||||
|
||||
await device.set_fan_speed_level(0)
|
||||
await fan.set_fan_speed_level(0)
|
||||
await dev.update()
|
||||
assert not device.is_on
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await device.set_fan_speed_level(-1)
|
||||
await fan.set_fan_speed_level(-1)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await device.set_fan_speed_level(5)
|
||||
await fan.set_fan_speed_level(5)
|
||||
|
113
kasa/tests/smart/modules/test_firmware.py
Normal file
113
kasa/tests/smart/modules/test_firmware.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import Module
|
||||
from kasa.smart import SmartDevice
|
||||
from kasa.smart.modules.firmware import DownloadState, Firmware
|
||||
from kasa.tests.device_fixtures import parametrize
|
||||
|
||||
firmware = parametrize(
|
||||
"has firmware", component_filter="firmware", protocol_filter={"SMART"}
|
||||
)
|
||||
|
||||
|
||||
@firmware
|
||||
@pytest.mark.parametrize(
|
||||
"feature, prop_name, type, required_version",
|
||||
[
|
||||
("auto_update_enabled", "auto_update_enabled", bool, 2),
|
||||
("update_available", "update_available", bool, 1),
|
||||
("update_available", "update_available", bool, 1),
|
||||
("current_firmware_version", "current_firmware", str, 1),
|
||||
("available_firmware_version", "latest_firmware", str, 1),
|
||||
],
|
||||
)
|
||||
async def test_firmware_features(
|
||||
dev: SmartDevice, feature, prop_name, type, required_version, mocker: MockerFixture
|
||||
):
|
||||
"""Test light effect."""
|
||||
fw = dev.modules.get(Module.Firmware)
|
||||
assert fw
|
||||
if not isinstance(fw, Firmware): # TODO needed while common interface still TBD
|
||||
return
|
||||
if not dev.is_cloud_connected:
|
||||
pytest.skip("Device is not cloud connected, skipping test")
|
||||
|
||||
if fw.supported_version < required_version:
|
||||
pytest.skip("Feature %s requires newer version" % feature)
|
||||
|
||||
prop = getattr(fw, prop_name)
|
||||
assert isinstance(prop, type)
|
||||
|
||||
feat = dev.features[feature]
|
||||
assert feat.value == prop
|
||||
assert isinstance(feat.value, type)
|
||||
|
||||
|
||||
@firmware
|
||||
async def test_update_available_without_cloud(dev: SmartDevice):
|
||||
"""Test that update_available returns None when disconnected."""
|
||||
fw = dev.modules.get(Module.Firmware)
|
||||
assert fw
|
||||
if not isinstance(fw, Firmware): # TODO needed while common interface still TBD
|
||||
return
|
||||
|
||||
if dev.is_cloud_connected:
|
||||
assert isinstance(fw.update_available, bool)
|
||||
else:
|
||||
assert fw.update_available is None
|
||||
|
||||
|
||||
@firmware
|
||||
async def test_firmware_update(
|
||||
dev: SmartDevice, mocker: MockerFixture, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
"""Test updating firmware."""
|
||||
caplog.set_level(logging.INFO)
|
||||
|
||||
fw = dev.modules.get(Module.Firmware)
|
||||
assert fw
|
||||
if not isinstance(fw, Firmware): # TODO needed while common interface still TBD
|
||||
return
|
||||
|
||||
upgrade_time = 5
|
||||
extras = {"reboot_time": 5, "upgrade_time": upgrade_time, "auto_upgrade": False}
|
||||
update_states = [
|
||||
# Unknown 1
|
||||
DownloadState(status=1, download_progress=0, **extras),
|
||||
# Downloading
|
||||
DownloadState(status=2, download_progress=10, **extras),
|
||||
DownloadState(status=2, download_progress=100, **extras),
|
||||
# Flashing
|
||||
DownloadState(status=3, download_progress=100, **extras),
|
||||
DownloadState(status=3, download_progress=100, **extras),
|
||||
# Done
|
||||
DownloadState(status=0, download_progress=100, **extras),
|
||||
]
|
||||
|
||||
asyncio_sleep = asyncio.sleep
|
||||
sleep = mocker.patch("asyncio.sleep")
|
||||
mocker.patch.object(fw, "get_update_state", side_effect=update_states)
|
||||
|
||||
cb_mock = mocker.AsyncMock()
|
||||
|
||||
await fw.update(progress_cb=cb_mock)
|
||||
|
||||
# This is necessary to allow the eventloop to process the created tasks
|
||||
await asyncio_sleep(0)
|
||||
|
||||
assert "Unhandled state code" in caplog.text
|
||||
assert "Downloading firmware, progress: 10" in caplog.text
|
||||
assert "Flashing firmware, sleeping" in caplog.text
|
||||
assert "Update idle" in caplog.text
|
||||
|
||||
for state in update_states:
|
||||
cb_mock.assert_any_await(state)
|
||||
|
||||
# sleep based on the upgrade_time
|
||||
sleep.assert_any_call(upgrade_time)
|
@@ -23,6 +23,6 @@ async def test_humidity_features(dev, feature, type):
|
||||
prop = getattr(humidity, feature)
|
||||
assert isinstance(prop, type)
|
||||
|
||||
feat = humidity._module_features[feature]
|
||||
feat = dev.features[feature]
|
||||
assert feat.value == prop
|
||||
assert isinstance(feat.value, type)
|
||||
|
@@ -1,13 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from itertools import chain
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import Device, Feature
|
||||
from kasa.smart.modules import LightEffectModule
|
||||
from kasa import Device, Feature, Module
|
||||
from kasa.smart.modules import LightEffect
|
||||
from kasa.tests.device_fixtures import parametrize
|
||||
|
||||
light_effect = parametrize(
|
||||
@@ -18,10 +17,10 @@ light_effect = parametrize(
|
||||
@light_effect
|
||||
async def test_light_effect(dev: Device, mocker: MockerFixture):
|
||||
"""Test light effect."""
|
||||
light_effect = cast(LightEffectModule, dev.modules.get("LightEffectModule"))
|
||||
assert light_effect
|
||||
light_effect = dev.modules.get(Module.LightEffect)
|
||||
assert isinstance(light_effect, LightEffect)
|
||||
|
||||
feature = light_effect._module_features["light_effect"]
|
||||
feature = dev.features["light_effect"]
|
||||
assert feature.type == Feature.Type.Choice
|
||||
|
||||
call = mocker.spy(light_effect, "call")
|
||||
@@ -29,7 +28,7 @@ async def test_light_effect(dev: Device, mocker: MockerFixture):
|
||||
assert feature.choices
|
||||
for effect in chain(reversed(feature.choices), feature.choices):
|
||||
await light_effect.set_effect(effect)
|
||||
enable = effect != LightEffectModule.LIGHT_EFFECTS_OFF
|
||||
enable = effect != LightEffect.LIGHT_EFFECTS_OFF
|
||||
params: dict[str, bool | str] = {"enable": enable}
|
||||
if enable:
|
||||
params["id"] = light_effect._scenes_names_to_id[effect]
|
||||
|
@@ -29,7 +29,7 @@ async def test_temperature_features(dev, feature, type):
|
||||
prop = getattr(temp_module, feature)
|
||||
assert isinstance(prop, type)
|
||||
|
||||
feat = temp_module._module_features[feature]
|
||||
feat = dev.features[feature]
|
||||
assert feat.value == prop
|
||||
assert isinstance(feat.value, type)
|
||||
|
||||
@@ -42,6 +42,6 @@ async def test_temperature_warning(dev):
|
||||
assert hasattr(temp_module, "temperature_warning")
|
||||
assert isinstance(temp_module.temperature_warning, bool)
|
||||
|
||||
feat = temp_module._module_features["temperature_warning"]
|
||||
feat = dev.features["temperature_warning"]
|
||||
assert feat.value == temp_module.temperature_warning
|
||||
assert isinstance(feat.value, bool)
|
||||
|
@@ -28,7 +28,7 @@ async def test_temperature_control_features(dev, feature, type):
|
||||
prop = getattr(temp_module, feature)
|
||||
assert isinstance(prop, type)
|
||||
|
||||
feat = temp_module._module_features[feature]
|
||||
feat = dev.features[feature]
|
||||
assert feat.value == prop
|
||||
assert isinstance(feat.value, type)
|
||||
|
||||
|
@@ -25,7 +25,7 @@ async def test_waterleak_properties(dev, feature, prop_name, type):
|
||||
prop = getattr(waterleak, prop_name)
|
||||
assert isinstance(prop, type)
|
||||
|
||||
feat = waterleak._module_features[feature]
|
||||
feat = dev.features[feature]
|
||||
assert feat.value == prop
|
||||
assert isinstance(feat.value, type)
|
||||
|
||||
|
@@ -7,19 +7,18 @@ from voluptuous import (
|
||||
Schema,
|
||||
)
|
||||
|
||||
from kasa import Bulb, BulbPreset, Device, DeviceType, KasaException
|
||||
from kasa import Device, DeviceType, KasaException, LightPreset, Module
|
||||
from kasa.iot import IotBulb, IotDimmer
|
||||
from kasa.smart import SmartDevice
|
||||
|
||||
from .conftest import (
|
||||
bulb,
|
||||
bulb_iot,
|
||||
color_bulb,
|
||||
color_bulb_iot,
|
||||
dimmable,
|
||||
dimmable_iot,
|
||||
handle_turn_on,
|
||||
non_color_bulb,
|
||||
non_dimmable,
|
||||
non_dimmable_iot,
|
||||
non_variable_temp,
|
||||
turn_on,
|
||||
variable_temp,
|
||||
@@ -65,19 +64,20 @@ async def test_get_light_state(dev: IotBulb):
|
||||
@color_bulb
|
||||
@turn_on
|
||||
async def test_hsv(dev: Device, turn_on):
|
||||
assert isinstance(dev, Bulb)
|
||||
light = dev.modules.get(Module.Light)
|
||||
assert light
|
||||
await handle_turn_on(dev, turn_on)
|
||||
assert dev.is_color
|
||||
assert light.is_color
|
||||
|
||||
hue, saturation, brightness = dev.hsv
|
||||
hue, saturation, brightness = light.hsv
|
||||
assert 0 <= hue <= 360
|
||||
assert 0 <= saturation <= 100
|
||||
assert 0 <= brightness <= 100
|
||||
|
||||
await dev.set_hsv(hue=1, saturation=1, value=1)
|
||||
await light.set_hsv(hue=1, saturation=1, value=1)
|
||||
|
||||
await dev.update()
|
||||
hue, saturation, brightness = dev.hsv
|
||||
hue, saturation, brightness = light.hsv
|
||||
assert hue == 1
|
||||
assert saturation == 1
|
||||
assert brightness == 1
|
||||
@@ -96,57 +96,64 @@ async def test_set_hsv_transition(dev: IotBulb, mocker):
|
||||
|
||||
@color_bulb
|
||||
@turn_on
|
||||
async def test_invalid_hsv(dev: Bulb, turn_on):
|
||||
async def test_invalid_hsv(dev: Device, turn_on):
|
||||
light = dev.modules.get(Module.Light)
|
||||
assert light
|
||||
await handle_turn_on(dev, turn_on)
|
||||
assert dev.is_color
|
||||
assert light.is_color
|
||||
|
||||
for invalid_hue in [-1, 361, 0.5]:
|
||||
with pytest.raises(ValueError):
|
||||
await dev.set_hsv(invalid_hue, 0, 0) # type: ignore[arg-type]
|
||||
await light.set_hsv(invalid_hue, 0, 0) # type: ignore[arg-type]
|
||||
|
||||
for invalid_saturation in [-1, 101, 0.5]:
|
||||
with pytest.raises(ValueError):
|
||||
await dev.set_hsv(0, invalid_saturation, 0) # type: ignore[arg-type]
|
||||
await light.set_hsv(0, invalid_saturation, 0) # type: ignore[arg-type]
|
||||
|
||||
for invalid_brightness in [-1, 101, 0.5]:
|
||||
with pytest.raises(ValueError):
|
||||
await dev.set_hsv(0, 0, invalid_brightness) # type: ignore[arg-type]
|
||||
await light.set_hsv(0, 0, invalid_brightness) # type: ignore[arg-type]
|
||||
|
||||
|
||||
@color_bulb
|
||||
@pytest.mark.skip("requires color feature")
|
||||
async def test_color_state_information(dev: Device):
|
||||
assert isinstance(dev, Bulb)
|
||||
light = dev.modules.get(Module.Light)
|
||||
assert light
|
||||
assert "HSV" in dev.state_information
|
||||
assert dev.state_information["HSV"] == dev.hsv
|
||||
assert dev.state_information["HSV"] == light.hsv
|
||||
|
||||
|
||||
@non_color_bulb
|
||||
async def test_hsv_on_non_color(dev: Bulb):
|
||||
assert not dev.is_color
|
||||
async def test_hsv_on_non_color(dev: Device):
|
||||
light = dev.modules.get(Module.Light)
|
||||
assert light
|
||||
assert not light.is_color
|
||||
|
||||
with pytest.raises(KasaException):
|
||||
await dev.set_hsv(0, 0, 0)
|
||||
await light.set_hsv(0, 0, 0)
|
||||
with pytest.raises(KasaException):
|
||||
print(dev.hsv)
|
||||
print(light.hsv)
|
||||
|
||||
|
||||
@variable_temp
|
||||
@pytest.mark.skip("requires colortemp module")
|
||||
async def test_variable_temp_state_information(dev: Device):
|
||||
assert isinstance(dev, Bulb)
|
||||
light = dev.modules.get(Module.Light)
|
||||
assert light
|
||||
assert "Color temperature" in dev.state_information
|
||||
assert dev.state_information["Color temperature"] == dev.color_temp
|
||||
assert dev.state_information["Color temperature"] == light.color_temp
|
||||
|
||||
|
||||
@variable_temp
|
||||
@turn_on
|
||||
async def test_try_set_colortemp(dev: Device, turn_on):
|
||||
assert isinstance(dev, Bulb)
|
||||
light = dev.modules.get(Module.Light)
|
||||
assert light
|
||||
await handle_turn_on(dev, turn_on)
|
||||
await dev.set_color_temp(2700)
|
||||
await light.set_color_temp(2700)
|
||||
await dev.update()
|
||||
assert dev.color_temp == 2700
|
||||
assert light.color_temp == 2700
|
||||
|
||||
|
||||
@variable_temp_iot
|
||||
@@ -166,36 +173,42 @@ async def test_unknown_temp_range(dev: IotBulb, monkeypatch, caplog):
|
||||
|
||||
|
||||
@variable_temp_smart
|
||||
async def test_smart_temp_range(dev: SmartDevice):
|
||||
assert dev.valid_temperature_range
|
||||
async def test_smart_temp_range(dev: Device):
|
||||
light = dev.modules.get(Module.Light)
|
||||
assert light
|
||||
assert light.valid_temperature_range
|
||||
|
||||
|
||||
@variable_temp
|
||||
async def test_out_of_range_temperature(dev: Bulb):
|
||||
async def test_out_of_range_temperature(dev: Device):
|
||||
light = dev.modules.get(Module.Light)
|
||||
assert light
|
||||
with pytest.raises(ValueError):
|
||||
await dev.set_color_temp(1000)
|
||||
await light.set_color_temp(1000)
|
||||
with pytest.raises(ValueError):
|
||||
await dev.set_color_temp(10000)
|
||||
await light.set_color_temp(10000)
|
||||
|
||||
|
||||
@non_variable_temp
|
||||
async def test_non_variable_temp(dev: Bulb):
|
||||
async def test_non_variable_temp(dev: Device):
|
||||
light = dev.modules.get(Module.Light)
|
||||
assert light
|
||||
with pytest.raises(KasaException):
|
||||
await dev.set_color_temp(2700)
|
||||
await light.set_color_temp(2700)
|
||||
|
||||
with pytest.raises(KasaException):
|
||||
print(dev.valid_temperature_range)
|
||||
print(light.valid_temperature_range)
|
||||
|
||||
with pytest.raises(KasaException):
|
||||
print(dev.color_temp)
|
||||
print(light.color_temp)
|
||||
|
||||
|
||||
@dimmable
|
||||
@dimmable_iot
|
||||
@turn_on
|
||||
async def test_dimmable_brightness(dev: Device, turn_on):
|
||||
assert isinstance(dev, (Bulb, IotDimmer))
|
||||
async def test_dimmable_brightness(dev: IotBulb, turn_on):
|
||||
assert isinstance(dev, (IotBulb, IotDimmer))
|
||||
await handle_turn_on(dev, turn_on)
|
||||
assert dev.is_dimmable
|
||||
assert dev._is_dimmable
|
||||
|
||||
await dev.set_brightness(50)
|
||||
await dev.update()
|
||||
@@ -229,9 +242,9 @@ async def test_dimmable_brightness_transition(dev: IotBulb, mocker):
|
||||
set_light_state.assert_called_with({"brightness": 10}, transition=1000)
|
||||
|
||||
|
||||
@dimmable
|
||||
async def test_invalid_brightness(dev: Bulb):
|
||||
assert dev.is_dimmable
|
||||
@dimmable_iot
|
||||
async def test_invalid_brightness(dev: IotBulb):
|
||||
assert dev._is_dimmable
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await dev.set_brightness(110)
|
||||
@@ -240,9 +253,9 @@ async def test_invalid_brightness(dev: Bulb):
|
||||
await dev.set_brightness(-100)
|
||||
|
||||
|
||||
@non_dimmable
|
||||
async def test_non_dimmable(dev: Bulb):
|
||||
assert not dev.is_dimmable
|
||||
@non_dimmable_iot
|
||||
async def test_non_dimmable(dev: IotBulb):
|
||||
assert not dev._is_dimmable
|
||||
|
||||
with pytest.raises(KasaException):
|
||||
assert dev.brightness == 0
|
||||
@@ -291,7 +304,7 @@ async def test_modify_preset(dev: IotBulb, mocker):
|
||||
"saturation": 0,
|
||||
"color_temp": 0,
|
||||
}
|
||||
preset = BulbPreset(**data)
|
||||
preset = LightPreset(**data)
|
||||
|
||||
assert preset.index == 0
|
||||
assert preset.brightness == 10
|
||||
@@ -305,7 +318,7 @@ async def test_modify_preset(dev: IotBulb, mocker):
|
||||
|
||||
with pytest.raises(KasaException):
|
||||
await dev.save_preset(
|
||||
BulbPreset(index=5, hue=0, brightness=0, saturation=0, color_temp=0)
|
||||
LightPreset(index=5, hue=0, brightness=0, saturation=0, color_temp=0)
|
||||
)
|
||||
|
||||
|
||||
@@ -314,11 +327,11 @@ async def test_modify_preset(dev: IotBulb, mocker):
|
||||
("preset", "payload"),
|
||||
[
|
||||
(
|
||||
BulbPreset(index=0, hue=0, brightness=1, saturation=0),
|
||||
LightPreset(index=0, hue=0, brightness=1, saturation=0),
|
||||
{"index": 0, "hue": 0, "brightness": 1, "saturation": 0},
|
||||
),
|
||||
(
|
||||
BulbPreset(index=0, brightness=1, id="testid", mode=2, custom=0),
|
||||
LightPreset(index=0, brightness=1, id="testid", mode=2, custom=0),
|
||||
{"index": 0, "brightness": 1, "id": "testid", "mode": 2, "custom": 0},
|
||||
),
|
||||
],
|
||||
@@ -380,7 +393,7 @@ SYSINFO_SCHEMA_BULB = SYSINFO_SCHEMA.extend(
|
||||
|
||||
|
||||
@bulb
|
||||
def test_device_type_bulb(dev):
|
||||
def test_device_type_bulb(dev: Device):
|
||||
if dev.is_light_strip:
|
||||
pytest.skip("bulb has also lightstrips to test the api")
|
||||
assert dev.device_type == DeviceType.Bulb
|
||||
|
@@ -13,6 +13,7 @@ from kasa import (
|
||||
DeviceError,
|
||||
EmeterStatus,
|
||||
KasaException,
|
||||
Module,
|
||||
UnsupportedDeviceError,
|
||||
)
|
||||
from kasa.cli import (
|
||||
@@ -21,11 +22,15 @@ from kasa.cli import (
|
||||
brightness,
|
||||
cli,
|
||||
cmd_command,
|
||||
effect,
|
||||
emeter,
|
||||
hsv,
|
||||
led,
|
||||
raw_command,
|
||||
reboot,
|
||||
state,
|
||||
sysinfo,
|
||||
temperature,
|
||||
toggle,
|
||||
update_credentials,
|
||||
wifi,
|
||||
@@ -34,7 +39,6 @@ from kasa.discover import Discover, DiscoveryResult
|
||||
from kasa.iot import IotDevice
|
||||
|
||||
from .conftest import (
|
||||
device_iot,
|
||||
device_smart,
|
||||
get_device_for_fixture_protocol,
|
||||
handle_turn_on,
|
||||
@@ -78,11 +82,10 @@ async def test_update_called_by_cli(dev, mocker, runner):
|
||||
update.assert_called()
|
||||
|
||||
|
||||
@device_iot
|
||||
async def test_sysinfo(dev, runner):
|
||||
async def test_sysinfo(dev: Device, runner):
|
||||
res = await runner.invoke(sysinfo, obj=dev)
|
||||
assert "System info" in res.output
|
||||
assert dev.alias in res.output
|
||||
assert dev.model in res.output
|
||||
|
||||
|
||||
@turn_on
|
||||
@@ -108,7 +111,6 @@ async def test_toggle(dev, turn_on, runner):
|
||||
assert dev.is_on != turn_on
|
||||
|
||||
|
||||
@device_iot
|
||||
async def test_alias(dev, runner):
|
||||
res = await runner.invoke(alias, obj=dev)
|
||||
assert f"Alias: {dev.alias}" in res.output
|
||||
@@ -308,15 +310,14 @@ async def test_emeter(dev: Device, mocker, runner):
|
||||
daily.assert_called_with(year=1900, month=12)
|
||||
|
||||
|
||||
@device_iot
|
||||
async def test_brightness(dev, runner):
|
||||
async def test_brightness(dev: Device, runner):
|
||||
res = await runner.invoke(brightness, obj=dev)
|
||||
if not dev.is_dimmable:
|
||||
if not (light := dev.modules.get(Module.Light)) or not light.is_dimmable:
|
||||
assert "This device does not support brightness." in res.output
|
||||
return
|
||||
|
||||
res = await runner.invoke(brightness, obj=dev)
|
||||
assert f"Brightness: {dev.brightness}" in res.output
|
||||
assert f"Brightness: {light.brightness}" in res.output
|
||||
|
||||
res = await runner.invoke(brightness, ["12"], obj=dev)
|
||||
assert "Setting brightness" in res.output
|
||||
@@ -326,7 +327,110 @@ async def test_brightness(dev, runner):
|
||||
assert "Brightness: 12" in res.output
|
||||
|
||||
|
||||
@device_iot
|
||||
async def test_color_temperature(dev: Device, runner):
|
||||
res = await runner.invoke(temperature, obj=dev)
|
||||
if not (light := dev.modules.get(Module.Light)) or not light.is_variable_color_temp:
|
||||
assert "Device does not support color temperature" in res.output
|
||||
return
|
||||
|
||||
res = await runner.invoke(temperature, obj=dev)
|
||||
assert f"Color temperature: {light.color_temp}" in res.output
|
||||
valid_range = light.valid_temperature_range
|
||||
assert f"(min: {valid_range.min}, max: {valid_range.max})" in res.output
|
||||
|
||||
val = int((valid_range.min + valid_range.max) / 2)
|
||||
res = await runner.invoke(temperature, [str(val)], obj=dev)
|
||||
assert "Setting color temperature to " in res.output
|
||||
await dev.update()
|
||||
|
||||
res = await runner.invoke(temperature, obj=dev)
|
||||
assert f"Color temperature: {val}" in res.output
|
||||
assert res.exit_code == 0
|
||||
|
||||
invalid_max = valid_range.max + 100
|
||||
# Lights that support the maximum range will not get past the click cli range check
|
||||
# So can't be tested for the internal range check.
|
||||
if invalid_max < 9000:
|
||||
res = await runner.invoke(temperature, [str(invalid_max)], obj=dev)
|
||||
assert res.exit_code == 1
|
||||
assert isinstance(res.exception, ValueError)
|
||||
|
||||
res = await runner.invoke(temperature, [str(9100)], obj=dev)
|
||||
assert res.exit_code == 2
|
||||
|
||||
|
||||
async def test_color_hsv(dev: Device, runner: CliRunner):
|
||||
res = await runner.invoke(hsv, obj=dev)
|
||||
if not (light := dev.modules.get(Module.Light)) or not light.is_color:
|
||||
assert "Device does not support colors" in res.output
|
||||
return
|
||||
|
||||
res = await runner.invoke(hsv, obj=dev)
|
||||
assert f"Current HSV: {light.hsv}" in res.output
|
||||
|
||||
res = await runner.invoke(hsv, ["180", "50", "50"], obj=dev)
|
||||
assert "Setting HSV: 180 50 50" in res.output
|
||||
assert res.exit_code == 0
|
||||
await dev.update()
|
||||
|
||||
res = await runner.invoke(hsv, ["180", "50"], obj=dev)
|
||||
assert "Setting a color requires 3 values." in res.output
|
||||
assert res.exit_code == 2
|
||||
|
||||
|
||||
async def test_light_effect(dev: Device, runner: CliRunner):
|
||||
res = await runner.invoke(effect, obj=dev)
|
||||
if not (light_effect := dev.modules.get(Module.LightEffect)):
|
||||
assert "Device does not support effects" in res.output
|
||||
return
|
||||
|
||||
# Start off with a known state of off
|
||||
await light_effect.set_effect(light_effect.LIGHT_EFFECTS_OFF)
|
||||
await dev.update()
|
||||
assert light_effect.effect == light_effect.LIGHT_EFFECTS_OFF
|
||||
|
||||
res = await runner.invoke(effect, obj=dev)
|
||||
msg = (
|
||||
"Setting an effect requires a named built-in effect: "
|
||||
+ f"{light_effect.effect_list}"
|
||||
)
|
||||
assert msg in res.output
|
||||
assert res.exit_code == 2
|
||||
|
||||
res = await runner.invoke(effect, [light_effect.effect_list[1]], obj=dev)
|
||||
assert f"Setting Effect: {light_effect.effect_list[1]}" in res.output
|
||||
assert res.exit_code == 0
|
||||
await dev.update()
|
||||
assert light_effect.effect == light_effect.effect_list[1]
|
||||
|
||||
res = await runner.invoke(effect, ["foobar"], obj=dev)
|
||||
assert f"Effect must be one of: {light_effect.effect_list}" in res.output
|
||||
assert res.exit_code == 2
|
||||
|
||||
|
||||
async def test_led(dev: Device, runner: CliRunner):
|
||||
res = await runner.invoke(led, obj=dev)
|
||||
if not (led_module := dev.modules.get(Module.Led)):
|
||||
assert "Device does not support led" in res.output
|
||||
return
|
||||
|
||||
res = await runner.invoke(led, obj=dev)
|
||||
assert f"LED state: {led_module.led}" in res.output
|
||||
assert res.exit_code == 0
|
||||
|
||||
res = await runner.invoke(led, ["on"], obj=dev)
|
||||
assert "Turning led to True" in res.output
|
||||
assert res.exit_code == 0
|
||||
await dev.update()
|
||||
assert led_module.led is True
|
||||
|
||||
res = await runner.invoke(led, ["off"], obj=dev)
|
||||
assert "Turning led to False" in res.output
|
||||
assert res.exit_code == 0
|
||||
await dev.update()
|
||||
assert led_module.led is False
|
||||
|
||||
|
||||
async def test_json_output(dev: Device, mocker, runner):
|
||||
"""Test that the json output produces correct output."""
|
||||
mocker.patch("kasa.Discover.discover", return_value={"127.0.0.1": dev})
|
||||
@@ -375,7 +479,6 @@ async def test_credentials(discovery_mock, mocker, runner):
|
||||
assert "Username:foo Password:bar\n" in res.output
|
||||
|
||||
|
||||
@device_iot
|
||||
async def test_without_device_type(dev, mocker, runner):
|
||||
"""Test connecting without the device type."""
|
||||
discovery_mock = mocker.patch(
|
||||
@@ -737,7 +840,7 @@ async def test_feature_set(mocker, runner):
|
||||
dummy_device = await get_device_for_fixture_protocol(
|
||||
"P300(EU)_1.0_1.0.13.json", "SMART"
|
||||
)
|
||||
led_setter = mocker.patch("kasa.smart.modules.ledmodule.LedModule.set_led")
|
||||
led_setter = mocker.patch("kasa.smart.modules.led.Led.set_led")
|
||||
mocker.patch("kasa.discover.Discover.discover_single", return_value=dummy_device)
|
||||
|
||||
res = await runner.invoke(
|
||||
|
125
kasa/tests/test_common_modules.py
Normal file
125
kasa/tests/test_common_modules.py
Normal file
@@ -0,0 +1,125 @@
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import Device, Module
|
||||
from kasa.tests.device_fixtures import (
|
||||
dimmable_iot,
|
||||
dimmer_iot,
|
||||
lightstrip_iot,
|
||||
parametrize,
|
||||
parametrize_combine,
|
||||
plug_iot,
|
||||
)
|
||||
|
||||
led_smart = parametrize(
|
||||
"has led smart", component_filter="led", protocol_filter={"SMART"}
|
||||
)
|
||||
led = parametrize_combine([led_smart, plug_iot])
|
||||
|
||||
light_effect_smart = parametrize(
|
||||
"has light effect smart", component_filter="light_effect", protocol_filter={"SMART"}
|
||||
)
|
||||
light_effect = parametrize_combine([light_effect_smart, lightstrip_iot])
|
||||
|
||||
dimmable_smart = parametrize(
|
||||
"dimmable smart", component_filter="brightness", protocol_filter={"SMART"}
|
||||
)
|
||||
dimmable = parametrize_combine([dimmable_smart, dimmer_iot, dimmable_iot])
|
||||
|
||||
|
||||
@led
|
||||
async def test_led_module(dev: Device, mocker: MockerFixture):
|
||||
"""Test fan speed feature."""
|
||||
led_module = dev.modules.get(Module.Led)
|
||||
assert led_module
|
||||
feat = dev.features["led"]
|
||||
|
||||
call = mocker.spy(led_module, "call")
|
||||
await led_module.set_led(True)
|
||||
assert call.call_count == 1
|
||||
await dev.update()
|
||||
assert led_module.led is True
|
||||
assert feat.value is True
|
||||
|
||||
await led_module.set_led(False)
|
||||
assert call.call_count == 2
|
||||
await dev.update()
|
||||
assert led_module.led is False
|
||||
assert feat.value is False
|
||||
|
||||
await feat.set_value(True)
|
||||
assert call.call_count == 3
|
||||
await dev.update()
|
||||
assert feat.value is True
|
||||
assert led_module.led is True
|
||||
|
||||
|
||||
@light_effect
|
||||
async def test_light_effect_module(dev: Device, mocker: MockerFixture):
|
||||
"""Test fan speed feature."""
|
||||
light_effect_module = dev.modules[Module.LightEffect]
|
||||
assert light_effect_module
|
||||
feat = dev.features["light_effect"]
|
||||
|
||||
call = mocker.spy(light_effect_module, "call")
|
||||
effect_list = light_effect_module.effect_list
|
||||
assert "Off" in effect_list
|
||||
assert effect_list.index("Off") == 0
|
||||
assert len(effect_list) > 1
|
||||
assert effect_list == feat.choices
|
||||
|
||||
assert light_effect_module.has_custom_effects is not None
|
||||
|
||||
await light_effect_module.set_effect("Off")
|
||||
assert call.call_count == 1
|
||||
await dev.update()
|
||||
assert light_effect_module.effect == "Off"
|
||||
assert feat.value == "Off"
|
||||
|
||||
second_effect = effect_list[1]
|
||||
await light_effect_module.set_effect(second_effect)
|
||||
assert call.call_count == 2
|
||||
await dev.update()
|
||||
assert light_effect_module.effect == second_effect
|
||||
assert feat.value == second_effect
|
||||
|
||||
last_effect = effect_list[len(effect_list) - 1]
|
||||
await light_effect_module.set_effect(last_effect)
|
||||
assert call.call_count == 3
|
||||
await dev.update()
|
||||
assert light_effect_module.effect == last_effect
|
||||
assert feat.value == last_effect
|
||||
|
||||
# Test feature set
|
||||
await feat.set_value(second_effect)
|
||||
assert call.call_count == 4
|
||||
await dev.update()
|
||||
assert light_effect_module.effect == second_effect
|
||||
assert feat.value == second_effect
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await light_effect_module.set_effect("foobar")
|
||||
assert call.call_count == 4
|
||||
|
||||
|
||||
@dimmable
|
||||
async def test_light_brightness(dev: Device):
|
||||
"""Test brightness setter and getter."""
|
||||
assert isinstance(dev, Device)
|
||||
light = dev.modules.get(Module.Light)
|
||||
assert light
|
||||
|
||||
# Test getting the value
|
||||
feature = dev.features["brightness"]
|
||||
assert feature.minimum_value == 0
|
||||
assert feature.maximum_value == 100
|
||||
|
||||
await light.set_brightness(10)
|
||||
await dev.update()
|
||||
assert light.brightness == 10
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await light.set_brightness(feature.minimum_value - 10)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await light.set_brightness(feature.maximum_value + 10)
|
@@ -9,7 +9,7 @@ from unittest.mock import Mock, patch
|
||||
import pytest
|
||||
|
||||
import kasa
|
||||
from kasa import Credentials, Device, DeviceConfig
|
||||
from kasa import Credentials, Device, DeviceConfig, DeviceType
|
||||
from kasa.iot import IotDevice
|
||||
from kasa.smart import SmartChildDevice, SmartDevice
|
||||
|
||||
@@ -25,6 +25,7 @@ def _get_subclasses(of_class):
|
||||
inspect.isclass(obj)
|
||||
and issubclass(obj, of_class)
|
||||
and module.__package__ != "kasa"
|
||||
and module.__package__ != "kasa.interfaces"
|
||||
):
|
||||
subclasses.add((module.__package__ + "." + name, obj))
|
||||
return subclasses
|
||||
@@ -120,3 +121,56 @@ def test_deprecated_exceptions(exceptions_class, use_class):
|
||||
with pytest.deprecated_call(match=msg):
|
||||
getattr(kasa, exceptions_class)
|
||||
getattr(kasa, use_class.__name__)
|
||||
|
||||
|
||||
deprecated_is_device_type = {
|
||||
"is_bulb": DeviceType.Bulb,
|
||||
"is_plug": DeviceType.Plug,
|
||||
"is_dimmer": DeviceType.Dimmer,
|
||||
"is_light_strip": DeviceType.LightStrip,
|
||||
"is_wallswitch": DeviceType.WallSwitch,
|
||||
"is_strip": DeviceType.Strip,
|
||||
"is_strip_socket": DeviceType.StripSocket,
|
||||
}
|
||||
deprecated_is_light_function_smart_module = {
|
||||
"is_color": "Color",
|
||||
"is_dimmable": "Brightness",
|
||||
"is_variable_color_temp": "ColorTemperature",
|
||||
}
|
||||
|
||||
|
||||
def test_deprecated_attributes(dev: SmartDevice):
|
||||
"""Test deprecated attributes on all devices."""
|
||||
tested_keys = set()
|
||||
|
||||
def _test_attr(attribute):
|
||||
tested_keys.add(attribute)
|
||||
msg = f"{attribute} is deprecated"
|
||||
if module := Device._deprecated_attributes[attribute][0]:
|
||||
msg += f", use: {module} in device.modules instead"
|
||||
with pytest.deprecated_call(match=msg):
|
||||
val = getattr(dev, attribute)
|
||||
return val
|
||||
|
||||
for attribute in deprecated_is_device_type:
|
||||
val = _test_attr(attribute)
|
||||
expected_val = dev.device_type == deprecated_is_device_type[attribute]
|
||||
assert val == expected_val
|
||||
|
||||
for attribute in deprecated_is_light_function_smart_module:
|
||||
val = _test_attr(attribute)
|
||||
if isinstance(dev, SmartDevice):
|
||||
expected_val = (
|
||||
deprecated_is_light_function_smart_module[attribute] in dev.modules
|
||||
)
|
||||
elif hasattr(dev, f"_{attribute}"):
|
||||
expected_val = getattr(dev, f"_{attribute}")
|
||||
else:
|
||||
expected_val = False
|
||||
assert val == expected_val
|
||||
|
||||
assert len(tested_keys) == len(Device._deprecated_attributes)
|
||||
untested_keys = [
|
||||
key for key in Device._deprecated_attributes if key not in tested_keys
|
||||
]
|
||||
assert len(untested_keys) == 0
|
||||
|
@@ -3,10 +3,10 @@ import pytest
|
||||
from kasa import DeviceType
|
||||
from kasa.iot import IotDimmer
|
||||
|
||||
from .conftest import dimmer, handle_turn_on, turn_on
|
||||
from .conftest import dimmer_iot, handle_turn_on, turn_on
|
||||
|
||||
|
||||
@dimmer
|
||||
@dimmer_iot
|
||||
@turn_on
|
||||
async def test_set_brightness(dev, turn_on):
|
||||
await handle_turn_on(dev, turn_on)
|
||||
@@ -22,7 +22,7 @@ async def test_set_brightness(dev, turn_on):
|
||||
assert dev.is_on == turn_on
|
||||
|
||||
|
||||
@dimmer
|
||||
@dimmer_iot
|
||||
@turn_on
|
||||
async def test_set_brightness_transition(dev, turn_on, mocker):
|
||||
await handle_turn_on(dev, turn_on)
|
||||
@@ -44,7 +44,7 @@ async def test_set_brightness_transition(dev, turn_on, mocker):
|
||||
assert dev.brightness == 1
|
||||
|
||||
|
||||
@dimmer
|
||||
@dimmer_iot
|
||||
async def test_set_brightness_invalid(dev):
|
||||
for invalid_brightness in [-1, 101, 0.5]:
|
||||
with pytest.raises(ValueError):
|
||||
@@ -55,7 +55,7 @@ async def test_set_brightness_invalid(dev):
|
||||
await dev.set_brightness(1, transition=invalid_transition)
|
||||
|
||||
|
||||
@dimmer
|
||||
@dimmer_iot
|
||||
async def test_turn_on_transition(dev, mocker):
|
||||
query_helper = mocker.spy(IotDimmer, "_query_helper")
|
||||
original_brightness = dev.brightness
|
||||
@@ -72,7 +72,7 @@ async def test_turn_on_transition(dev, mocker):
|
||||
assert dev.brightness == original_brightness
|
||||
|
||||
|
||||
@dimmer
|
||||
@dimmer_iot
|
||||
async def test_turn_off_transition(dev, mocker):
|
||||
await handle_turn_on(dev, True)
|
||||
query_helper = mocker.spy(IotDimmer, "_query_helper")
|
||||
@@ -90,7 +90,7 @@ async def test_turn_off_transition(dev, mocker):
|
||||
)
|
||||
|
||||
|
||||
@dimmer
|
||||
@dimmer_iot
|
||||
@turn_on
|
||||
async def test_set_dimmer_transition(dev, turn_on, mocker):
|
||||
await handle_turn_on(dev, turn_on)
|
||||
@@ -108,7 +108,7 @@ async def test_set_dimmer_transition(dev, turn_on, mocker):
|
||||
assert dev.brightness == 99
|
||||
|
||||
|
||||
@dimmer
|
||||
@dimmer_iot
|
||||
@turn_on
|
||||
async def test_set_dimmer_transition_to_off(dev, turn_on, mocker):
|
||||
await handle_turn_on(dev, turn_on)
|
||||
@@ -127,7 +127,7 @@ async def test_set_dimmer_transition_to_off(dev, turn_on, mocker):
|
||||
)
|
||||
|
||||
|
||||
@dimmer
|
||||
@dimmer_iot
|
||||
async def test_set_dimmer_transition_invalid(dev):
|
||||
for invalid_brightness in [-1, 101, 0.5]:
|
||||
with pytest.raises(ValueError):
|
||||
@@ -138,6 +138,6 @@ async def test_set_dimmer_transition_invalid(dev):
|
||||
await dev.set_dimmer_transition(1, invalid_transition)
|
||||
|
||||
|
||||
@dimmer
|
||||
@dimmer_iot
|
||||
def test_device_type_dimmer(dev):
|
||||
assert dev.device_type == DeviceType.Dimmer
|
||||
|
@@ -26,8 +26,8 @@ from kasa.xortransport import XorEncryption
|
||||
|
||||
from .conftest import (
|
||||
bulb_iot,
|
||||
dimmer,
|
||||
lightstrip,
|
||||
dimmer_iot,
|
||||
lightstrip_iot,
|
||||
new_discovery,
|
||||
plug_iot,
|
||||
strip_iot,
|
||||
@@ -86,14 +86,14 @@ async def test_type_detection_strip(dev: Device):
|
||||
assert d.device_type == DeviceType.Strip
|
||||
|
||||
|
||||
@dimmer
|
||||
@dimmer_iot
|
||||
async def test_type_detection_dimmer(dev: Device):
|
||||
d = Discover._get_device_class(dev._last_update)("localhost")
|
||||
assert d.is_dimmer
|
||||
assert d.device_type == DeviceType.Dimmer
|
||||
|
||||
|
||||
@lightstrip
|
||||
@lightstrip_iot
|
||||
async def test_type_detection_lightstrip(dev: Device):
|
||||
d = Discover._get_device_class(dev._last_update)("localhost")
|
||||
assert d.is_light_strip
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
@@ -180,11 +181,10 @@ async def test_feature_setters(dev: Device, mocker: MockerFixture):
|
||||
|
||||
async def _test_features(dev):
|
||||
exceptions = []
|
||||
query = mocker.patch.object(dev.protocol, "query")
|
||||
for feat in dev.features.values():
|
||||
query.reset_mock()
|
||||
try:
|
||||
await _test_feature(feat, query)
|
||||
with patch.object(feat.device.protocol, "query") as query:
|
||||
await _test_feature(feat, query)
|
||||
# we allow our own exceptions to avoid mocking valid responses
|
||||
except KasaException:
|
||||
pass
|
||||
|
@@ -16,7 +16,7 @@ from voluptuous import (
|
||||
Schema,
|
||||
)
|
||||
|
||||
from kasa import KasaException
|
||||
from kasa import KasaException, Module
|
||||
from kasa.iot import IotDevice
|
||||
|
||||
from .conftest import get_device_for_fixture_protocol, handle_turn_on, turn_on
|
||||
@@ -261,27 +261,26 @@ async def test_modules_not_supported(dev: IotDevice):
|
||||
|
||||
|
||||
async def test_get_modules():
|
||||
"""Test get_modules for child and parent modules."""
|
||||
"""Test getting modules for child and parent modules."""
|
||||
dummy_device = await get_device_for_fixture_protocol(
|
||||
"HS100(US)_2.0_1.5.6.json", "IOT"
|
||||
)
|
||||
from kasa.iot.modules import Cloud
|
||||
from kasa.smart.modules import CloudModule
|
||||
|
||||
# Modules on device
|
||||
module = dummy_device.get_module("Cloud")
|
||||
module = dummy_device.modules.get("cloud")
|
||||
assert module
|
||||
assert module._device == dummy_device
|
||||
assert isinstance(module, Cloud)
|
||||
|
||||
module = dummy_device.get_module(Cloud)
|
||||
module = dummy_device.modules.get(Module.IotCloud)
|
||||
assert module
|
||||
assert module._device == dummy_device
|
||||
assert isinstance(module, Cloud)
|
||||
|
||||
# Invalid modules
|
||||
module = dummy_device.get_module("DummyModule")
|
||||
module = dummy_device.modules.get("DummyModule")
|
||||
assert module is None
|
||||
|
||||
module = dummy_device.get_module(CloudModule)
|
||||
module = dummy_device.modules.get(Module.Cloud)
|
||||
assert module is None
|
||||
|
@@ -1,29 +1,28 @@
|
||||
import pytest
|
||||
|
||||
from kasa import DeviceType
|
||||
from kasa.exceptions import KasaException
|
||||
from kasa.iot import IotLightStrip
|
||||
|
||||
from .conftest import lightstrip
|
||||
from .conftest import lightstrip_iot
|
||||
|
||||
|
||||
@lightstrip
|
||||
@lightstrip_iot
|
||||
async def test_lightstrip_length(dev: IotLightStrip):
|
||||
assert dev.is_light_strip
|
||||
assert dev.device_type == DeviceType.LightStrip
|
||||
assert dev.length == dev.sys_info["length"]
|
||||
|
||||
|
||||
@lightstrip
|
||||
@lightstrip_iot
|
||||
async def test_lightstrip_effect(dev: IotLightStrip):
|
||||
assert isinstance(dev.effect, dict)
|
||||
for k in ["brightness", "custom", "enable", "id", "name"]:
|
||||
assert k in dev.effect
|
||||
|
||||
|
||||
@lightstrip
|
||||
@lightstrip_iot
|
||||
async def test_effects_lightstrip_set_effect(dev: IotLightStrip):
|
||||
with pytest.raises(KasaException):
|
||||
with pytest.raises(ValueError):
|
||||
await dev.set_effect("Not real")
|
||||
|
||||
await dev.set_effect("Candy Cane")
|
||||
@@ -31,7 +30,7 @@ async def test_effects_lightstrip_set_effect(dev: IotLightStrip):
|
||||
assert dev.effect["name"] == "Candy Cane"
|
||||
|
||||
|
||||
@lightstrip
|
||||
@lightstrip_iot
|
||||
@pytest.mark.parametrize("brightness", [100, 50])
|
||||
async def test_effects_lightstrip_set_effect_brightness(
|
||||
dev: IotLightStrip, brightness, mocker
|
||||
@@ -49,7 +48,7 @@ async def test_effects_lightstrip_set_effect_brightness(
|
||||
assert payload["brightness"] == brightness
|
||||
|
||||
|
||||
@lightstrip
|
||||
@lightstrip_iot
|
||||
@pytest.mark.parametrize("transition", [500, 1000])
|
||||
async def test_effects_lightstrip_set_effect_transition(
|
||||
dev: IotLightStrip, transition, mocker
|
||||
@@ -67,12 +66,12 @@ async def test_effects_lightstrip_set_effect_transition(
|
||||
assert payload["transition"] == transition
|
||||
|
||||
|
||||
@lightstrip
|
||||
@lightstrip_iot
|
||||
async def test_effects_lightstrip_has_effects(dev: IotLightStrip):
|
||||
assert dev.has_effects is True
|
||||
assert dev.effect_list
|
||||
|
||||
|
||||
@lightstrip
|
||||
@lightstrip_iot
|
||||
def test_device_type_lightstrip(dev):
|
||||
assert dev.device_type == DeviceType.LightStrip
|
||||
|
@@ -9,12 +9,11 @@ from unittest.mock import patch
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import KasaException
|
||||
from kasa import KasaException, Module
|
||||
from kasa.exceptions import SmartErrorCode
|
||||
from kasa.smart import SmartDevice
|
||||
|
||||
from .conftest import (
|
||||
bulb_smart,
|
||||
device_smart,
|
||||
get_device_for_fixture_protocol,
|
||||
)
|
||||
@@ -123,65 +122,42 @@ async def test_update_module_queries(dev: SmartDevice, mocker: MockerFixture):
|
||||
|
||||
|
||||
async def test_get_modules():
|
||||
"""Test get_modules for child and parent modules."""
|
||||
"""Test getting modules for child and parent modules."""
|
||||
dummy_device = await get_device_for_fixture_protocol(
|
||||
"KS240(US)_1.0_1.0.5.json", "SMART"
|
||||
)
|
||||
from kasa.iot.modules import AmbientLight
|
||||
from kasa.smart.modules import CloudModule, FanModule
|
||||
from kasa.smart.modules import Cloud
|
||||
|
||||
# Modules on device
|
||||
module = dummy_device.get_module("CloudModule")
|
||||
module = dummy_device.modules.get("Cloud")
|
||||
assert module
|
||||
assert module._device == dummy_device
|
||||
assert isinstance(module, CloudModule)
|
||||
assert isinstance(module, Cloud)
|
||||
|
||||
module = dummy_device.get_module(CloudModule)
|
||||
module = dummy_device.modules.get(Module.Cloud)
|
||||
assert module
|
||||
assert module._device == dummy_device
|
||||
assert isinstance(module, CloudModule)
|
||||
assert isinstance(module, Cloud)
|
||||
|
||||
# Modules on child
|
||||
module = dummy_device.get_module("FanModule")
|
||||
module = dummy_device.modules.get("Fan")
|
||||
assert module
|
||||
assert module._device != dummy_device
|
||||
assert module._device._parent == dummy_device
|
||||
|
||||
module = dummy_device.get_module(FanModule)
|
||||
module = dummy_device.modules.get(Module.Fan)
|
||||
assert module
|
||||
assert module._device != dummy_device
|
||||
assert module._device._parent == dummy_device
|
||||
|
||||
# Invalid modules
|
||||
module = dummy_device.get_module("DummyModule")
|
||||
module = dummy_device.modules.get("DummyModule")
|
||||
assert module is None
|
||||
|
||||
module = dummy_device.get_module(AmbientLight)
|
||||
module = dummy_device.modules.get(Module.IotAmbientLight)
|
||||
assert module is None
|
||||
|
||||
|
||||
@bulb_smart
|
||||
async def test_smartdevice_brightness(dev: SmartDevice):
|
||||
"""Test brightness setter and getter."""
|
||||
assert isinstance(dev, SmartDevice)
|
||||
assert "brightness" in dev._components
|
||||
|
||||
# Test getting the value
|
||||
feature = dev.features["brightness"]
|
||||
assert feature.minimum_value == 1
|
||||
assert feature.maximum_value == 100
|
||||
|
||||
await dev.set_brightness(10)
|
||||
await dev.update()
|
||||
assert dev.brightness == 10
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await dev.set_brightness(feature.minimum_value - 10)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await dev.set_brightness(feature.maximum_value + 10)
|
||||
|
||||
|
||||
@device_smart
|
||||
async def test_smartdevice_cloud_connection(dev: SmartDevice, mocker: MockerFixture):
|
||||
"""Test is_cloud_connected property."""
|
||||
|
Reference in New Issue
Block a user