Package: tplink-cloud-api
tplink-cloud-apiA Python library to remotely control TP-Link smart home devices using their cloud service - no need to be on the same network as your devicesThis started as a Python port of Adumont's Node.js module:https://github.com/adumont/tplink-cloud-apiIntroductionThetplinkcloudPython module allows you to remotely control your TP-Link smartplugs (HS100, HS103, HS105, HS110, HS300, KP115) using the TP-Link cloud web service, from anywhere, without the need to be on the same wifi/lan.It's especially useful in scenarios where you want to control your devices from public web services, likeIFTTT,Thinger.io,Webtask.io,Glitch.com, Tasker (Android)...Device CompatibilityThe following devices areofficiallysupported by the library at this time:HS300 (Smart Plug Power Strip with 6 Smart Outlets)HS100 (Older Smart Plug - Blocks two outlets as a single outlet)HS103 (Smaller Single-Socket Smart Plug - 12 Amp)HS105 (Smaller Single-Socket Smart Plug - 15 Amp)HS110 (Older Smart Plug - Blocks two outlets as a single outlet)KP115 (Small Single-Socket Smart Plug - 15 Amp; replacement for HS110)KP303 (Smart Plug Power Strip with 3 Outlets)InstallationThe package is availble via PyPi and can be installed with the following command:pip3 install tplink-cloud-apiTo install it from the repo, clone the repo and cd into the directory:git clone https://github.com/piekstra/tplink-cloud-api.git
cd tplink-cloud-apiYou can install this library withpip:pip3 install .UsageAuthenticateInstantiating a TP Link Device Manager automatically logs in with your TP-Link credentials, caches the login token, and fetches your devices. The current TP-Link Cloud API Url (https://wap.tplinkcloud.com) is assumed if not provided explicitly.fromtplinkcloudimportTPLinkDeviceManagerusername='kasa@email.com'password='secure'device_manager=TPLinkDeviceManager(username,password)Note that the device manager can also be constructed usingawaitif desired and running in anasynccontextAsync ContextIn order to run the async methods, you will need an async context. For a simple Python script, you can simply use the following:importasyncioasyncio.run(example_async_method())For more advanced usage, you can gather tasks so they all run at once such as in the following example which can fetch a large number of devices' system info very quickly:fromtplinkcloudimportTPLinkDeviceManagerimportasyncioimportjsonusername='kasa@email.com'password='secure'device_manager=TPLinkDeviceManager(username,password)asyncdeffetch_all_devices_sys_info():devices=awaitdevice_manager.get_devices()fetch_tasks=[]fordeviceindevices:asyncdefget_info(device):print(f'Found{device.model_type.name}device:{device.get_alias()}')print("SYS INFO")print(json.dumps(device.device_info,indent=2,default=lambdax:vars(x)ifhasattr(x,"__dict__")elsex.nameifhasattr(x,"name")elseNone))print(json.dumps(awaitdevice.get_sys_info(),indent=2,default=lambdax:vars(x)ifhasattr(x,"__dict__")elsex.nameifhasattr(x,"name")elseNone))fetch_tasks.append(get_info(device))awaitasyncio.gather(*fetch_tasks)asyncio.run(fetch_all_devices_sys_info())Retrieve devicesTo view your devices, you can run the following:devices=awaitdevice_manager.get_devices()ifdevices:print(f'Found{len(devices)}devices')fordeviceindevices:print(f'{device.model_type.name}device called{device.get_alias()}')Control your devicesSmart Power Strips (HS300, KP303)Toggle a plug:device_name="My Smart Plug"device=awaitdevice_manager.find_device(device_name)ifdevice:print(f'Found{device.model_type.name}device:{device.get_alias()}')awaitdevice.toggle()else:print(f'Could not find{device_name}')ReplaceMy Smart Plugwith the alias you gave to your plug in the Kasa app (be sure to give a different alias to each device). Instead oftoggle(), you can also usepower_on()orpower_off().To retrieve power consumption data for one of the individual plugs on an HS300 power strip (KP303 does not support power usage data):importjsondevice=awaitdevice_manager.find_device("My Smart Plug")power_usage=awaitdevice.get_power_usage_realtime()print(json.dumps(power_usage,indent=2,default=lambdax:x.__dict__))If you want to get multiple devices with a name including a certain substring, you can use the following:device_names_like="plug"devices=awaitdevice_manager.find_devices(device_names_like)ifdevices:print(f'Found{len(devices)}matching devices')fordeviceindevices:print(f'{device.model_type.name}device called{device.get_alias()}')Smart Plugs (Not Power Strips) (HS100, HS103, HS105, HS110, KP115)These have the same functionality as the Smart Power Strips, though the HS100, HS103 and HS105 do not have the power usage features.Add and modify schedule rules for your devicesEdit an existing schedule ruledevice_name="My Smart Plug"device=awaitdevice_manager.find_device(device_name)ifdevice:print(f'Found{device.model_type.name}device:{device.get_alias()}')print(f'Modifying schedule rule')schedule=awaitdevice.get_schedule_rules()original_rule=schedule.rules[0]rule_edit=TPLinkDeviceScheduleRuleBuilder(original_rule).with_enable_status(False)awaitdevice.edit_schedule_rule(rule_edit.to_json())else:print(f'Could not find{device_name}')Add a new schedule ruledevice_name="My Smart Plug"device=awaitdevice_manager.find_device(device_name)ifdevice:print(f'Found{device.model_type.name}device:{device.get_alias()}')print(f'Adding schedule rule')new_rule=TPLinkDeviceScheduleRuleBuilder().with_action(turn_on=True).with_name('My Schedule Rule').with_enable_status(True).with_sunset_start().with_repeat_on_days([0,0,0,0,0,1,1]).build()awaitdevice.add_schedule_rule(new_rule.to_json())else:print(f'Could not find{device_name}')Delete a schedule ruledevice_name="My Smart Plug"device=awaitdevice_manager.find_device(device_name)ifdevice:print(f'Found{device.model_type.name}device:{device.get_alias()}')print(f'Deleting schedule rule')schedule=awaitdevice.get_schedule_rules()rule=schedule.rules[0]awaitdevice.delete_schedule_rule(rule.id)else:print(f'Could not find{device_name}')TestingThis project leverageswiremockto test the code to some extent. Note this will not protect the project from changes that TP-Link makes to their API, but instead verifies that the existing code functions consistently as written.Local TestingNote that the tests setup leverages thelocal_env_vars.pyfile. The values for those environment variables need to be set based on the following:TPLINK_KASA_USERNAME:kasa_docker- This must have parity with theloginbodyspecified intests/wiremock/mappings/login_request.jsonTPLINK_KASA_PASSWORD:kasa_password- This must have parity with theloginbodyspecified intests/wiremock/mappings/login_request.jsonTPLINK_KASA_TERM_ID:2a8ced52-f200-4b79-a1fe-2f6b58193c4c- This must be a UUID V4 string and must have parity with theloginbodyspecified intests/wiremock/mappings/login_request.json. It must also match thetermIDquery parameter in all mocked requests foundhereTPLINK_KASA_API_URL:http://127.0.0.1:8080- This URL is simplyhttp://127.0.0.1but the url port must have parity with thedocker-compose.yamlwiremock service's exposed httpport.To run tests, you will first need to start the wiremock service by running:docker-compose up -dThen, you can run the actual tests with the following command:pytest --verboseGitHub TestingThis project leverages GitHub Actions and has aworkflowthat will run these tests. The environment configuration for the tests must have parity with thelocal_env_vars.pyfile from thelocal testing.