dump_devinfo improvements (#657)

* dump_devinfo improvements

* Scrub only the last three bytes for mac addresses
* Add --target to allow creating fixtures based on discovery
* Save fixtures directly to correct location, add --basedir to allow defining the location of repository root
* Add --autosave to disable prompting for saving

* Update fixtures for devices I have

* Add fixture for HS110 hw 4.0 fw 1.0.4

* Improve help strings

* Fix tests

* Update devtools README

* Default to discovery if no host/target given
This commit is contained in:
Teemu R 2024-01-20 14:20:08 +01:00 committed by GitHub
parent 49cfef087c
commit f77e87dc5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 307 additions and 165 deletions

View File

@ -3,18 +3,24 @@
This directory contains some simple scripts that can be useful for developers.
## dump_devinfo
* Queries the device and returns a fixture that can be added to the test suite
* Queries the device (if --host is given) or discover devices and creates fixture files that can be added to the test suite.
```shell
Usage: dump_devinfo.py [OPTIONS] HOST
Usage: python -m devtools.dump_devinfo [OPTIONS]
Generate devinfo file for given device.
Generate devinfo files for devices.
Use --host (for a single device) or --target (for a complete network).
Options:
--host TEXT Target host.
--target TEXT Target network for discovery.
--username TEXT Username/email address to authenticate to device.
--password TEXT Password to use to authenticate to device.
--basedir TEXT Base directory for the git repository
--autosave Save without prompting
-d, --debug
--help Show this message and exit.
--username For authenticating devices.
--password
```
## create_module_fixtures

View File

@ -13,6 +13,7 @@ import json
import logging
import re
from collections import defaultdict, namedtuple
from pathlib import Path
from pprint import pprint
from typing import Dict, List
@ -65,7 +66,17 @@ def scrub(res):
res[k] = [scrub(vi) for vi in v]
else:
if k in keys_to_scrub:
if k in ["latitude", "latitude_i", "longitude", "longitude_i"]:
if k in ["mac", "mic_mac"]:
# Some macs have : or - as a separator and others do not
if len(v) == 12:
v = f"{v[:6]}000000"
else:
delim = ":" if ":" in v else "-"
rest = delim.join(
format(s, "02x") for s in bytes.fromhex("000000")
)
v = f"{v[:8]}{delim}{rest}"
elif k in ["latitude", "latitude_i", "longitude", "longitude_i"]:
v = 0
elif k in ["ip"]:
v = "127.0.0.123"
@ -95,8 +106,40 @@ def default_to_regular(d):
return d
async def handle_device(basedir, autosave, device: SmartDevice):
"""Create a fixture for a single device instance."""
if isinstance(device, TapoDevice):
filename, copy_folder, final = await get_smart_fixture(device)
else:
filename, copy_folder, final = await get_legacy_fixture(device)
save_filename = Path(basedir) / copy_folder / filename
pprint(scrub(final))
if autosave:
save = "y"
else:
save = click.prompt(
f"Do you want to save the above content to {save_filename} (y/n)"
)
if save == "y":
click.echo(f"Saving info to {save_filename}")
with open(save_filename, "w") as f:
json.dump(final, f, sort_keys=True, indent=4)
f.write("\n")
else:
click.echo("Not saving.")
@click.command()
@click.argument("host")
@click.option("--host", required=False, help="Target host.")
@click.option(
"--target",
required=False,
default="255.255.255.255",
help="Target network for discovery.",
)
@click.option(
"--username",
default="",
@ -111,37 +154,31 @@ def default_to_regular(d):
envvar="KASA_PASSWORD",
help="Password to use to authenticate to device.",
)
@click.option("--basedir", help="Base directory for the git repository", default=".")
@click.option("--autosave", is_flag=True, default=False, help="Save without prompting")
@click.option("-d", "--debug", is_flag=True)
async def cli(host, debug, username, password):
"""Generate devinfo file for given device."""
async def cli(host, target, basedir, autosave, debug, username, password):
"""Generate devinfo files for devices.
Use --host (for a single device) or --target (for a complete network).
"""
if debug:
logging.basicConfig(level=logging.DEBUG)
credentials = Credentials(username=username, password=password)
if host is not None:
click.echo("Host given, performing discovery on %s." % host)
device = await Discover.discover_single(host, credentials=credentials)
if isinstance(device, TapoDevice):
save_filename, copy_folder, final = await get_smart_fixture(device)
await handle_device(basedir, autosave, device)
else:
save_filename, copy_folder, final = await get_legacy_fixture(device)
pprint(scrub(final))
save = click.prompt(
f"Do you want to save the above content to {save_filename} (y/n)"
)
if save == "y":
click.echo(f"Saving info to {save_filename}")
with open(save_filename, "w") as f:
json.dump(final, f, sort_keys=True, indent=4)
f.write("\n")
click.echo(
f"Saved. Copy/Move {save_filename} to "
+ f"{copy_folder} to add it to the test suite"
"No --host given, performing discovery on %s. Use --target to override."
% target
)
else:
click.echo("Not saving.")
devices = await Discover.discover(target=target, credentials=credentials)
click.echo("Detected %s devices" % len(devices))
for dev in devices.values():
await handle_device(basedir, autosave, dev)
async def get_legacy_fixture(device):

View File

@ -125,7 +125,7 @@ class SmartDevice:
>>> dev.rssi
-71
>>> dev.mac
50:C7:BF:01:F8:CD
50:C7:BF:00:00:00
Some information can also be changed programmatically:
@ -155,9 +155,9 @@ class SmartDevice:
'hw_ver': '1.0',
'mac': '01:23:45:67:89:ab',
'type': 'IOT.SMARTPLUGSWITCH',
'hwId': '45E29DA8382494D2E82688B52A0B2EB5',
'hwId': '00000000000000000000000000000000',
'fwId': '00000000000000000000000000000000',
'oemId': '3D341ECE302C0642C99E31CE2430544B',
'oemId': '00000000000000000000000000000000',
'dev_name': 'Wi-Fi Smart Plug With Energy Monitoring'}
>>> dev.sys_info
@ -175,7 +175,7 @@ class SmartDevice:
>>> dev.has_emeter
True
>>> dev.emeter_realtime
<EmeterStatus power=0.983971 voltage=235.595234 current=0.015342 total=32.448>
<EmeterStatus power=0.928511 voltage=231.067823 current=0.014937 total=55.139>
>>> dev.emeter_today
>>> dev.emeter_this_month

View File

@ -1,44 +1,32 @@
{
"emeter": {
"get_realtime": {
"current": 0.015342,
"current": 0.014937,
"err_code": 0,
"power": 0.983971,
"total": 32.448,
"voltage": 235.595234
"power": 0.928511,
"total": 55.139,
"voltage": 231.067823
}
},
"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",
"deviceId": "0000000000000000000000000000000000000000",
"err_code": 0,
"feature": "TIM:ENE",
"fwId": "00000000000000000000000000000000",
"hwId": "45E29DA8382494D2E82688B52A0B2EB5",
"hwId": "00000000000000000000000000000000",
"hw_ver": "1.0",
"icon_hash": "",
"latitude": 51.476938,
"latitude": 0,
"led_off": 1,
"longitude": 7.216849,
"mac": "50:C7:BF:01:F8:CD",
"longitude": 0,
"mac": "50:C7:BF:00:00:00",
"model": "HS110(EU)",
"oemId": "3D341ECE302C0642C99E31CE2430544B",
"on_time": 512874,
"oemId": "00000000000000000000000000000000",
"on_time": 6023162,
"relay_state": 1,
"rssi": -71,
"sw_ver": "1.2.5 Build 171213 Rel.101523",

View File

@ -0,0 +1,40 @@
{
"emeter": {
"get_realtime": {
"current_ma": 451,
"err_code": 0,
"power_mw": 61753,
"total_wh": 16323,
"voltage_mv": 230837
}
},
"system": {
"get_sysinfo": {
"active_mode": "none",
"alias": "#MASKED_NAME#",
"dev_name": "Smart Wi-Fi Plug With Energy Monitoring",
"deviceId": "0000000000000000000000000000000000000000",
"err_code": 0,
"feature": "TIM:ENE",
"hwId": "00000000000000000000000000000000",
"hw_ver": "4.0",
"icon_hash": "",
"latitude_i": 0,
"led_off": 1,
"longitude_i": 0,
"mac": "B0:95:75:00:00:00",
"mic_type": "IOT.SMARTPLUGSWITCH",
"model": "HS110(EU)",
"next_action": {
"type": -1
},
"oemId": "00000000000000000000000000000000",
"on_time": 1484778,
"relay_state": 1,
"rssi": -60,
"status": "new",
"sw_ver": "1.0.4 Build 191111 Rel.143500",
"updating": 0
}
}
}

View File

@ -2,24 +2,24 @@
"smartlife.iot.common.emeter": {
"get_realtime": {
"err_code": 0,
"power_mw": 1300
"power_mw": 2500
}
},
"smartlife.iot.smartbulb.lightingservice": {
"get_light_state": {
"brightness": 5,
"color_temp": 2700,
"brightness": 17,
"color_temp": 2500,
"err_code": 0,
"hue": 1,
"hue": 0,
"mode": "normal",
"on_off": 1,
"saturation": 1
"saturation": 0
}
},
"system": {
"get_sysinfo": {
"active_mode": "schedule",
"alias": "bedroom",
"active_mode": "none",
"alias": "#MASKED_NAME#",
"ctrl_protocols": {
"name": "Linkie",
"version": "1.0"
@ -29,7 +29,7 @@
"deviceId": "0000000000000000000000000000000000000000",
"disco_ver": "1.0",
"err_code": 0,
"heapsize": 332316,
"heapsize": 334708,
"hwId": "00000000000000000000000000000000",
"hw_ver": "1.0",
"is_color": 1,
@ -37,20 +37,20 @@
"is_factory": false,
"is_variable_color_temp": 1,
"light_state": {
"brightness": 5,
"color_temp": 2700,
"hue": 1,
"brightness": 17,
"color_temp": 2500,
"hue": 0,
"mode": "normal",
"on_off": 1,
"saturation": 1
"saturation": 0
},
"mic_mac": "000000000000",
"mic_mac": "1C3BF3000000",
"mic_type": "IOT.SMARTBULB",
"model": "KL130(EU)",
"oemId": "00000000000000000000000000000000",
"preferred_state": [
{
"brightness": 10,
"brightness": 50,
"color_temp": 2500,
"hue": 0,
"index": 0,
@ -78,7 +78,7 @@
"saturation": 75
}
],
"rssi": -62,
"rssi": -60,
"sw_ver": "1.8.8 Build 190613 Rel.123436"
}
}

View File

@ -1,17 +1,17 @@
{
"emeter": {
"get_realtime": {
"current_ma": 0,
"current_ma": 296,
"err_code": 0,
"power_mw": 0,
"total_wh": 0,
"voltage_mv": 231561
"power_mw": 63499,
"total_wh": 12068,
"voltage_mv": 230577
}
},
"system": {
"get_sysinfo": {
"active_mode": "none",
"alias": "TP-LINK_Smart Plug_330B",
"alias": "#MASKED_NAME#",
"dev_name": "Smart Wi-Fi Plug Mini",
"deviceId": "0000000000000000000000000000000000000000",
"err_code": 0,
@ -20,9 +20,9 @@
"hw_ver": "1.0",
"icon_hash": "",
"latitude_i": 0,
"led_off": 0,
"led_off": 1,
"longitude_i": 0,
"mac": "00:00:00:00:00:00",
"mac": "C0:06:C3:00:00:00",
"mic_type": "IOT.SMARTPLUGSWITCH",
"model": "KP115(EU)",
"next_action": {
@ -31,9 +31,9 @@
"ntc_state": 0,
"obd_src": "tplink",
"oemId": "00000000000000000000000000000000",
"on_time": 197,
"on_time": 2078998,
"relay_state": 1,
"rssi": -70,
"rssi": -49,
"status": "new",
"sw_ver": "1.0.16 Build 210205 Rel.163735",
"updating": 0

View File

@ -1,20 +1,4 @@
{
"emeter": {
"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": {
"alias": "TP-LINK_Power Strip_CF69",
@ -22,29 +6,29 @@
"children": [
{
"alias": "Plug 1",
"id": "00",
"id": "8006E9854025B67C3F9D99BA1E66223D1C9A8A7700",
"next_action": {
"type": -1
},
"on_time": 302,
"on_time": 79701,
"state": 1
},
{
"alias": "Plug 2",
"id": "01",
"id": "8006E9854025B67C3F9D99BA1E66223D1C9A8A7701",
"next_action": {
"type": -1
},
"on_time": 0,
"on_time": 79700,
"state": 0
},
{
"alias": "Plug 3",
"id": "02",
"id": "8006E9854025B67C3F9D99BA1E66223D1C9A8A7702",
"next_action": {
"type": -1
},
"on_time": 0,
"on_time": 1484408,
"state": 0
}
],
@ -54,14 +38,14 @@
"hwId": "00000000000000000000000000000000",
"hw_ver": "1.0",
"latitude_i": 0,
"led_off": 0,
"led_off": 1,
"longitude_i": 0,
"mac": "00:00:00:00:00:00",
"mac": "1C:3B:F3:00:00:00",
"mic_type": "IOT.SMARTPLUGSWITCH",
"model": "KP303(UK)",
"ntc_state": 0,
"oemId": "00000000000000000000000000000000",
"rssi": -63,
"rssi": -68,
"status": "new",
"sw_ver": "1.0.3 Build 191105 Rel.113122",
"updating": 0

View File

@ -106,7 +106,7 @@
"factory_default": false,
"ip": "127.0.0.123",
"is_support_iot_cloud": true,
"mac": "00-00-00-00-00-00",
"mac": "5C-E9-31-00-00-00",
"mgt_encrypt_schm": {
"encrypt_type": "AES",
"http_port": 80,
@ -141,7 +141,7 @@
"get_device_info": {
"avatar": "bulb",
"brightness": 100,
"color_temp": 2500,
"color_temp": 6500,
"color_temp_range": [
2500,
6500
@ -150,33 +150,33 @@
"re_power_type": "always_on",
"state": {
"brightness": 100,
"color_temp": 2500,
"hue": 31,
"saturation": 88
"color_temp": 6500,
"hue": 9,
"saturation": 67
},
"type": "last_states"
},
"device_id": "0000000000000000000000000000000000000000",
"device_on": false,
"device_on": true,
"dynamic_light_effect_enable": false,
"fw_id": "00000000000000000000000000000000",
"fw_ver": "1.0.6 Build 230509 Rel.195312",
"has_set_location_info": true,
"hue": 31,
"hue": 9,
"hw_id": "00000000000000000000000000000000",
"hw_ver": "3.0",
"ip": "127.0.0.123",
"lang": "en_US",
"latitude": 0,
"longitude": 0,
"mac": "00-00-00-00-00-00",
"mac": "5C-E9-31-00-00-00",
"model": "L530",
"nickname": "I01BU0tFRF9OQU1FIw==",
"oem_id": "00000000000000000000000000000000",
"overheated": false,
"region": "Europe/Berlin",
"rssi": -48,
"saturation": 88,
"saturation": 67,
"signal_level": 3,
"specs": "",
"ssid": "I01BU0tFRF9TU0lEIw==",
@ -186,23 +186,23 @@
"get_device_time": {
"region": "Europe/Berlin",
"time_diff": 60,
"timestamp": 1704212283
"timestamp": 1705700000
},
"get_device_usage": {
"power_usage": {
"past30": 53,
"past7": 12,
"today": 0
"past30": 19,
"past7": 3,
"today": 3
},
"saved_power": {
"past30": 494,
"past7": 112,
"today": 0
"past30": 179,
"past7": 20,
"today": 20
},
"time_usage": {
"past30": 547,
"past7": 124,
"today": 0
"past30": 198,
"past7": 23,
"today": 23
}
},
"get_dynamic_light_effect_rules": {

View File

@ -110,7 +110,7 @@
"factory_default": false,
"ip": "127.0.0.123",
"is_support_iot_cloud": true,
"mac": "00-00-00-00-00-00",
"mac": "A8-42-A1-00-00-00",
"mgt_encrypt_schm": {
"encrypt_type": "AES",
"http_port": 80,
@ -141,7 +141,7 @@
"get_device_info": {
"avatar": "",
"brightness": 100,
"color_temp": 9000,
"color_temp": 0,
"color_temp_range": [
9000,
9000
@ -149,18 +149,18 @@
"default_states": {
"state": {
"brightness": 100,
"color_temp": 9000,
"hue": 0,
"saturation": 100
"color_temp": 0,
"hue": 354,
"saturation": 93
},
"type": "last_states"
},
"device_id": "0000000000000000000000000000000000000000",
"device_on": true,
"device_on": false,
"fw_id": "00000000000000000000000000000000",
"fw_ver": "1.0.17 Build 230426 Rel.153230",
"has_set_location_info": false,
"hue": 0,
"hue": 354,
"hw_id": "00000000000000000000000000000000",
"hw_ver": "1.0",
"ip": "127.0.0.123",
@ -175,7 +175,7 @@
"name": "station"
},
"longitude": 0,
"mac": "00-00-00-00-00-00",
"mac": "A8-42-A1-00-00-00",
"model": "L900",
"music_rhythm_enable": false,
"music_rhythm_mode": "single_lamp",
@ -183,8 +183,8 @@
"oem_id": "00000000000000000000000000000000",
"overheated": false,
"region": "Europe/Berlin",
"rssi": -50,
"saturation": 100,
"rssi": -55,
"saturation": 93,
"signal_level": 2,
"specs": "",
"ssid": "I01BU0tFRF9TU0lEIw==",
@ -194,23 +194,23 @@
"get_device_time": {
"region": "Europe/Berlin",
"time_diff": 0,
"timestamp": 1704389578
"timestamp": 1705699997
},
"get_device_usage": {
"power_usage": {
"past30": 5,
"past7": 5,
"today": 5
"past30": 15,
"past7": 0,
"today": 0
},
"saved_power": {
"past30": 17,
"past7": 17,
"today": 17
"past30": 81,
"past7": 1,
"today": 0
},
"time_usage": {
"past30": 22,
"past7": 22,
"today": 22
"past30": 96,
"past7": 1,
"today": 0
}
},
"get_fw_download_state": {
@ -221,6 +221,84 @@
"upgrade_time": 5
},
"get_inherit_info": null,
"get_lighting_effect": {
"brightness": 50,
"custom": 0,
"direction": 1,
"display_colors": [],
"duration": 0,
"enable": 0,
"expansion_strategy": 2,
"id": "",
"name": "station",
"repeat_times": 1,
"segment_length": 1,
"sequence": [
[
300,
100,
50
],
[
240,
100,
50
],
[
180,
100,
50
],
[
120,
100,
50
],
[
60,
100,
50
],
[
0,
100,
50
],
[
0,
0,
50
],
[
0,
0,
50
],
[
0,
0,
50
],
[
0,
0,
50
],
[
0,
0,
50
],
[
0,
0,
50
]
],
"spread": 16,
"transition": 400,
"type": "sequence"
},
"get_next_event": {},
"get_on_off_gradually_info": {
"enable": false

View File

@ -86,7 +86,7 @@
"factory_default": false,
"ip": "127.0.0.123",
"is_support_iot_cloud": true,
"mac": "00-00-00-00-00-00",
"mac": "48-22-54-00-00-00",
"mgt_encrypt_schm": {
"encrypt_type": "AES",
"http_port": 80,
@ -140,7 +140,7 @@
"lang": "en_US",
"latitude": 0,
"longitude": 0,
"mac": "00-00-00-00-00-00",
"mac": "48-22-54-00-00-00",
"model": "P110",
"nickname": "I01BU0tFRF9OQU1FIw==",
"oem_id": "00000000000000000000000000000000",
@ -158,23 +158,23 @@
"get_device_time": {
"region": "Europe/Berlin",
"time_diff": 60,
"timestamp": 1704212614
"timestamp": 1705699998
},
"get_device_usage": {
"power_usage": {
"past30": 896,
"past7": 259,
"today": 0
"past30": 1010,
"past7": 368,
"today": 46
},
"saved_power": {
"past30": 23138,
"past7": 6684,
"today": 0
"past30": 26144,
"past7": 9536,
"today": 1218
},
"time_usage": {
"past30": 24034,
"past7": 6943,
"today": 0
"past30": 27154,
"past7": 9904,
"today": 1264
}
},
"get_electricity_price_config": {
@ -316,11 +316,11 @@
0,
0
],
"local_time": "2024-01-02 17:23:34",
"month_energy": 0,
"month_runtime": 0,
"today_energy": 0,
"today_runtime": 0
"local_time": "2024-01-19 22:33:18",
"month_energy": 430,
"month_runtime": 11571,
"today_energy": 46,
"today_runtime": 1264
},
"get_fw_download_state": {
"auto_upgrade": false,
@ -341,7 +341,7 @@
}
},
"get_max_power": {
"max_power": 3847
"max_power": 3904
},
"get_next_event": {},
"get_protection_power": {
@ -356,7 +356,16 @@
"sum": 0
},
"get_wireless_scan_info": {
"ap_list": [],
"ap_list": [
{
"bssid": "000000000000",
"channel": 0,
"cipher_type": 2,
"key_type": "wpa_psk",
"signal_level": 3,
"ssid": "I01BU0tFRF9TU0lEIw=="
}
],
"wep_supported": false
},
"qs_component_nego": {