plugp100 5.0.0.dev4__tar.gz → 5.0.0.dev6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plugp100-5.0.0.dev6/PKG-INFO +189 -0
- plugp100-5.0.0.dev6/README.md +167 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/__init__.py +1 -1
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/discovery/__init__.py +2 -2
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/discovery/discovered_device.py +34 -12
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/discovery/tapo_discovery.py +0 -1
- plugp100-5.0.0.dev6/plugp100/example.py +81 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/energy_component.py +2 -2
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/device_factory.py +5 -1
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/child_device_list.py +1 -1
- plugp100-5.0.0.dev6/plugp100.egg-info/PKG-INFO +189 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/test_power_strip.py +2 -2
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/test_tapo_discovery.py +2 -2
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/test_hub.py +6 -1
- plugp100-5.0.0.dev4/PKG-INFO +0 -88
- plugp100-5.0.0.dev4/README.md +0 -66
- plugp100-5.0.0.dev4/plugp100/example.py +0 -53
- plugp100-5.0.0.dev4/plugp100.egg-info/PKG-INFO +0 -88
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/LICENSE +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/MANIFEST.in +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/light_effect.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/light_effect_preset.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/handshake_params.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/internal/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/internal/snowflake_id.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/login_device.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/secure_passthrough_params.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/set_device_info/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/set_device_info/play_alarm_params.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/set_device_info/set_light_color_info_params.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/set_device_info/set_light_info_params.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/set_device_info/set_plug_info_params.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/set_device_info/set_trv_info_params.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/tapo_request.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/trigger_logs_params.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/tapo_client.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/common/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/common/credentials.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/common/functional/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/common/functional/tri.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/common/utils/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/common/utils/http_client.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/common/utils/json_utils.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/discovery/cloud_client.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/discovery/rsa_session.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/encryption/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/encryption/helpers.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/encryption/key_pair.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/encryption/tp_link_cipher.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/child/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/child/tapohubchildren.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/child/tapostripsocket.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/alarm_component.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/battery_component.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/device_component.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/hub_children_component.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/humidity_component.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/light_component.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/light_effect_component.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/motion_sensor_component.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/on_off_component.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/overheat_component.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/report_mode_component.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/smart_door_component.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/socket_children_component.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/temperature_component.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/temperature_humidity_records.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/trigger_log_component.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/water_leak_component.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/device_type.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/errors/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/errors/invalid_authentication.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/event_polling/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/event_polling/event_subscription.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/event_polling/poll_tracker.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/event_polling/state_tracker.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/hub_device_tracker.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/tapobulb.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/tapodevice.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/tapohub.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/tapoplug.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/protocol/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/protocol/klap/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/protocol/klap/klap_handshake_revision.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/protocol/klap/klap_protocol.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/protocol/passthrough_protocol.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/protocol/securepassthrough_transport.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/protocol/tapo_protocol.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/alarm_type_list.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/components.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/device_state.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/device_usage_info.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/energy_info.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/hub_child_base_info.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/ke100_device_state.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/leak_device_state.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/s200b_device_state.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/switch_child_device_state.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/t100_device_state.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/t110_device_state.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/t31x_device_state.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/trigger_log_response.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/power_info.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/tapo_exception.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/tapo_response.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/temperature_unit.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/time_info.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100.egg-info/SOURCES.txt +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100.egg-info/dependency_links.txt +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100.egg-info/requires.txt +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100.egg-info/top_level.txt +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/pyproject.toml +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/requirements-dev.txt +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/requirements.txt +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/setup.cfg +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/setup.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/tapo_test_helper.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/test_button_t310.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/test_hub.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/test_ledstrip.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/test_light.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/test_plug.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/test_sensor_s200b.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/hub_child/__init__.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/hub_child/test_button.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/hub_child/test_temp_hum_sensor.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/hub_child/test_trv_ke100.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/test_bulb.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/test_discovery.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/test_klap_protocol.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/test_plug.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/test_plug_strip.py +0 -0
- {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/test_utils.py +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: plugp100
|
|
3
|
+
Version: 5.0.0.dev6
|
|
4
|
+
Summary: Controller for TP-Link Tapo P100 and other devices
|
|
5
|
+
Home-page: https://github.com/petretiandrea/plugp100
|
|
6
|
+
Download-URL: https://github.com/petretiandrea/plugp100
|
|
7
|
+
Author: @petretiandrea
|
|
8
|
+
Author-email: petretiandrea@gmail.com
|
|
9
|
+
License: GPL3
|
|
10
|
+
Keywords: Tapo,P100
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: certifi>=2021.5.30
|
|
15
|
+
Requires-Dist: jsons>=1.6.3
|
|
16
|
+
Requires-Dist: requests>=2.27.1
|
|
17
|
+
Requires-Dist: aiohttp>=3.8.1
|
|
18
|
+
Requires-Dist: semantic-version==2.10.0
|
|
19
|
+
Requires-Dist: cryptography>=38.0.3
|
|
20
|
+
Requires-Dist: scapy>=2.5.0
|
|
21
|
+
Requires-Dist: urllib3<2,>=1.26.5
|
|
22
|
+
|
|
23
|
+
# Plug P100
|
|
24
|
+
This is a fork of original work of [@K4CZP3R](https://github.com/K4CZP3R/tapo-p100-python)
|
|
25
|
+
|
|
26
|
+
The purpose of this fork is to provide the library as PyPi package.
|
|
27
|
+
|
|
28
|
+
# How to install
|
|
29
|
+
```pip install plugp100```
|
|
30
|
+
|
|
31
|
+
## Library Architecture
|
|
32
|
+
The library was rewritten by taking inspiration from [Component Gaming Design Pattern](https://gameprogrammingpatterns.com/component.html) to achieve better decoupling from device and its capabilities.
|
|
33
|
+
Each Tapo Device, now, is something like a container of Device Component. A Device Component represent a specific feature, so a Tapo Device can be composed by multiple device component.
|
|
34
|
+
e.g. `EnergyComponent`, `OverheatComponent`, `OnOffComponent` and so on.
|
|
35
|
+
|
|
36
|
+
There 3 main Tapo Device class family, which simplify access to underlying components:
|
|
37
|
+
- TapoBulb
|
|
38
|
+
- TapoPlug
|
|
39
|
+
- TapoHub
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## Supported Devices
|
|
43
|
+
|
|
44
|
+
This library supports a wide range of Tapo devices, including:
|
|
45
|
+
|
|
46
|
+
- Tapo Smart Plugs
|
|
47
|
+
- Tapo Smart Plug Strip
|
|
48
|
+
- Tapo Smart Led Strip
|
|
49
|
+
- Tapo Smart Bulb
|
|
50
|
+
- Tapo HUB H100
|
|
51
|
+
- Water Leak
|
|
52
|
+
- Trigger Button (like S200)
|
|
53
|
+
- Switch
|
|
54
|
+
- Smart Door
|
|
55
|
+
- Temperature Humidity Sensor
|
|
56
|
+
|
|
57
|
+
Every device class has more than one component which enrich the basic capability of Tapo Device.
|
|
58
|
+
You can see the supported components inside `plugp100/new/components` package.
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
Replace `<tapo_username>`, `<tapo_password>`, and `<tapo_device_ip>` with your Tapo account credentials and device IP address.
|
|
64
|
+
|
|
65
|
+
### Authentication
|
|
66
|
+
|
|
67
|
+
Before using the library, make sure to have your Tapo credentials ready:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from plugp100.common.credentials import AuthCredential
|
|
71
|
+
|
|
72
|
+
credentials = AuthCredential("<tapo_username>", "<tapo_password>")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Example: Discovery
|
|
76
|
+
|
|
77
|
+
Use the library to discover Tapo devices on the network:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import asyncio
|
|
81
|
+
import logging
|
|
82
|
+
from plugp100.common.credentials import AuthCredential
|
|
83
|
+
from plugp100.discovery.tapo_discovery import TapoDiscovery
|
|
84
|
+
|
|
85
|
+
async def example_discovery(credentials: AuthCredential):
|
|
86
|
+
discovered = await TapoDiscovery.scan(timeout=5)
|
|
87
|
+
for discovered_device in discovered:
|
|
88
|
+
try:
|
|
89
|
+
device = await discovered_device.get_tapo_device(credentials)
|
|
90
|
+
await device.update()
|
|
91
|
+
print({
|
|
92
|
+
'type': type(device),
|
|
93
|
+
'protocol': device.protocol_version,
|
|
94
|
+
'raw_state': device.raw_state
|
|
95
|
+
})
|
|
96
|
+
await device.client.close()
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logging.error(f"Failed to update {discovered_device.ip} {discovered_device.device_type}", exc_info=e)
|
|
99
|
+
|
|
100
|
+
async def main():
|
|
101
|
+
credentials = AuthCredential("<tapo_username>", "<tapo_password>")
|
|
102
|
+
await example_discovery(credentials)
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
loop = asyncio.new_event_loop()
|
|
106
|
+
loop.run_until_complete(main())
|
|
107
|
+
loop.run_until_complete(asyncio.sleep(0.1))
|
|
108
|
+
loop.close()
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Example: Connecting by only ip address
|
|
112
|
+
|
|
113
|
+
Connect to a Tapo device without knowing its device type and protocol. The library will try to guess:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
import asyncio
|
|
117
|
+
from plugp100.common.credentials import AuthCredential
|
|
118
|
+
from plugp100.new.device_factory import connect, DeviceConnectConfiguration
|
|
119
|
+
|
|
120
|
+
async def example_connect_by_guessing(credentials: AuthCredential, host: str):
|
|
121
|
+
device_configuration = DeviceConnectConfiguration(
|
|
122
|
+
host=host,
|
|
123
|
+
credentials=credentials
|
|
124
|
+
)
|
|
125
|
+
device = await connect(device_configuration)
|
|
126
|
+
await device.update()
|
|
127
|
+
print({
|
|
128
|
+
'type': type(device),
|
|
129
|
+
'protocol': device.protocol_version,
|
|
130
|
+
'raw_state': device.raw_state,
|
|
131
|
+
'components': device.get_device_components
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
async def main():
|
|
135
|
+
credentials = AuthCredential("<tapo_username>", "<tapo_password>")
|
|
136
|
+
await example_connect_by_guessing(credentials, "<tapo_device_ip>")
|
|
137
|
+
|
|
138
|
+
if __name__ == "__main__":
|
|
139
|
+
loop = asyncio.new_event_loop()
|
|
140
|
+
loop.run_until_complete(main())
|
|
141
|
+
loop.run_until_complete(asyncio.sleep(0.1))
|
|
142
|
+
loop.close()
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Example: Connecting by knowing Protocol
|
|
146
|
+
|
|
147
|
+
Connect to a Tapo device with known device type and protocol details:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
import asyncio
|
|
151
|
+
from plugp100.common.credentials import AuthCredential
|
|
152
|
+
from plugp100.new.device_factory import connect, DeviceConnectConfiguration
|
|
153
|
+
|
|
154
|
+
async def example_connect_knowing_device_and_protocol(credentials: AuthCredential, host: str):
|
|
155
|
+
device_configuration = DeviceConnectConfiguration(
|
|
156
|
+
host=host,
|
|
157
|
+
credentials=credentials,
|
|
158
|
+
device_type="SMART.TAPOPLUG",
|
|
159
|
+
encryption_type="klap",
|
|
160
|
+
encryption_version=2
|
|
161
|
+
)
|
|
162
|
+
device = await connect(device_configuration)
|
|
163
|
+
await device.update()
|
|
164
|
+
print({
|
|
165
|
+
'type': type(device),
|
|
166
|
+
'protocol': device.protocol_version,
|
|
167
|
+
'raw_state': device.raw_state,
|
|
168
|
+
'components': device.get_device_components
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
async def main():
|
|
172
|
+
credentials = AuthCredential("<tapo_username>", "<tapo_password>")
|
|
173
|
+
await example_connect_knowing_device_and_protocol(credentials, "<tapo_device_ip>")
|
|
174
|
+
|
|
175
|
+
if __name__ == "__main__":
|
|
176
|
+
loop = asyncio.new_event_loop()
|
|
177
|
+
loop.run_until_complete(main())
|
|
178
|
+
loop.run_until_complete(asyncio.sleep(0.1))
|
|
179
|
+
loop.close()
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
## Supported Protocols
|
|
185
|
+
|
|
186
|
+
- Klap v1
|
|
187
|
+
- Klap v2
|
|
188
|
+
- Passthorugh
|
|
189
|
+
- Ipcamera-like?! (work in progress hub H200)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Plug P100
|
|
2
|
+
This is a fork of original work of [@K4CZP3R](https://github.com/K4CZP3R/tapo-p100-python)
|
|
3
|
+
|
|
4
|
+
The purpose of this fork is to provide the library as PyPi package.
|
|
5
|
+
|
|
6
|
+
# How to install
|
|
7
|
+
```pip install plugp100```
|
|
8
|
+
|
|
9
|
+
## Library Architecture
|
|
10
|
+
The library was rewritten by taking inspiration from [Component Gaming Design Pattern](https://gameprogrammingpatterns.com/component.html) to achieve better decoupling from device and its capabilities.
|
|
11
|
+
Each Tapo Device, now, is something like a container of Device Component. A Device Component represent a specific feature, so a Tapo Device can be composed by multiple device component.
|
|
12
|
+
e.g. `EnergyComponent`, `OverheatComponent`, `OnOffComponent` and so on.
|
|
13
|
+
|
|
14
|
+
There 3 main Tapo Device class family, which simplify access to underlying components:
|
|
15
|
+
- TapoBulb
|
|
16
|
+
- TapoPlug
|
|
17
|
+
- TapoHub
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## Supported Devices
|
|
21
|
+
|
|
22
|
+
This library supports a wide range of Tapo devices, including:
|
|
23
|
+
|
|
24
|
+
- Tapo Smart Plugs
|
|
25
|
+
- Tapo Smart Plug Strip
|
|
26
|
+
- Tapo Smart Led Strip
|
|
27
|
+
- Tapo Smart Bulb
|
|
28
|
+
- Tapo HUB H100
|
|
29
|
+
- Water Leak
|
|
30
|
+
- Trigger Button (like S200)
|
|
31
|
+
- Switch
|
|
32
|
+
- Smart Door
|
|
33
|
+
- Temperature Humidity Sensor
|
|
34
|
+
|
|
35
|
+
Every device class has more than one component which enrich the basic capability of Tapo Device.
|
|
36
|
+
You can see the supported components inside `plugp100/new/components` package.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
Replace `<tapo_username>`, `<tapo_password>`, and `<tapo_device_ip>` with your Tapo account credentials and device IP address.
|
|
42
|
+
|
|
43
|
+
### Authentication
|
|
44
|
+
|
|
45
|
+
Before using the library, make sure to have your Tapo credentials ready:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from plugp100.common.credentials import AuthCredential
|
|
49
|
+
|
|
50
|
+
credentials = AuthCredential("<tapo_username>", "<tapo_password>")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Example: Discovery
|
|
54
|
+
|
|
55
|
+
Use the library to discover Tapo devices on the network:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import asyncio
|
|
59
|
+
import logging
|
|
60
|
+
from plugp100.common.credentials import AuthCredential
|
|
61
|
+
from plugp100.discovery.tapo_discovery import TapoDiscovery
|
|
62
|
+
|
|
63
|
+
async def example_discovery(credentials: AuthCredential):
|
|
64
|
+
discovered = await TapoDiscovery.scan(timeout=5)
|
|
65
|
+
for discovered_device in discovered:
|
|
66
|
+
try:
|
|
67
|
+
device = await discovered_device.get_tapo_device(credentials)
|
|
68
|
+
await device.update()
|
|
69
|
+
print({
|
|
70
|
+
'type': type(device),
|
|
71
|
+
'protocol': device.protocol_version,
|
|
72
|
+
'raw_state': device.raw_state
|
|
73
|
+
})
|
|
74
|
+
await device.client.close()
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logging.error(f"Failed to update {discovered_device.ip} {discovered_device.device_type}", exc_info=e)
|
|
77
|
+
|
|
78
|
+
async def main():
|
|
79
|
+
credentials = AuthCredential("<tapo_username>", "<tapo_password>")
|
|
80
|
+
await example_discovery(credentials)
|
|
81
|
+
|
|
82
|
+
if __name__ == "__main__":
|
|
83
|
+
loop = asyncio.new_event_loop()
|
|
84
|
+
loop.run_until_complete(main())
|
|
85
|
+
loop.run_until_complete(asyncio.sleep(0.1))
|
|
86
|
+
loop.close()
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Example: Connecting by only ip address
|
|
90
|
+
|
|
91
|
+
Connect to a Tapo device without knowing its device type and protocol. The library will try to guess:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
import asyncio
|
|
95
|
+
from plugp100.common.credentials import AuthCredential
|
|
96
|
+
from plugp100.new.device_factory import connect, DeviceConnectConfiguration
|
|
97
|
+
|
|
98
|
+
async def example_connect_by_guessing(credentials: AuthCredential, host: str):
|
|
99
|
+
device_configuration = DeviceConnectConfiguration(
|
|
100
|
+
host=host,
|
|
101
|
+
credentials=credentials
|
|
102
|
+
)
|
|
103
|
+
device = await connect(device_configuration)
|
|
104
|
+
await device.update()
|
|
105
|
+
print({
|
|
106
|
+
'type': type(device),
|
|
107
|
+
'protocol': device.protocol_version,
|
|
108
|
+
'raw_state': device.raw_state,
|
|
109
|
+
'components': device.get_device_components
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
async def main():
|
|
113
|
+
credentials = AuthCredential("<tapo_username>", "<tapo_password>")
|
|
114
|
+
await example_connect_by_guessing(credentials, "<tapo_device_ip>")
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
loop = asyncio.new_event_loop()
|
|
118
|
+
loop.run_until_complete(main())
|
|
119
|
+
loop.run_until_complete(asyncio.sleep(0.1))
|
|
120
|
+
loop.close()
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Example: Connecting by knowing Protocol
|
|
124
|
+
|
|
125
|
+
Connect to a Tapo device with known device type and protocol details:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
import asyncio
|
|
129
|
+
from plugp100.common.credentials import AuthCredential
|
|
130
|
+
from plugp100.new.device_factory import connect, DeviceConnectConfiguration
|
|
131
|
+
|
|
132
|
+
async def example_connect_knowing_device_and_protocol(credentials: AuthCredential, host: str):
|
|
133
|
+
device_configuration = DeviceConnectConfiguration(
|
|
134
|
+
host=host,
|
|
135
|
+
credentials=credentials,
|
|
136
|
+
device_type="SMART.TAPOPLUG",
|
|
137
|
+
encryption_type="klap",
|
|
138
|
+
encryption_version=2
|
|
139
|
+
)
|
|
140
|
+
device = await connect(device_configuration)
|
|
141
|
+
await device.update()
|
|
142
|
+
print({
|
|
143
|
+
'type': type(device),
|
|
144
|
+
'protocol': device.protocol_version,
|
|
145
|
+
'raw_state': device.raw_state,
|
|
146
|
+
'components': device.get_device_components
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
async def main():
|
|
150
|
+
credentials = AuthCredential("<tapo_username>", "<tapo_password>")
|
|
151
|
+
await example_connect_knowing_device_and_protocol(credentials, "<tapo_device_ip>")
|
|
152
|
+
|
|
153
|
+
if __name__ == "__main__":
|
|
154
|
+
loop = asyncio.new_event_loop()
|
|
155
|
+
loop.run_until_complete(main())
|
|
156
|
+
loop.run_until_complete(asyncio.sleep(0.1))
|
|
157
|
+
loop.close()
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
## Supported Protocols
|
|
163
|
+
|
|
164
|
+
- Klap v1
|
|
165
|
+
- Klap v2
|
|
166
|
+
- Passthorugh
|
|
167
|
+
- Ipcamera-like?! (work in progress hub H200)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .discovered_device import DiscoveredDevice,
|
|
1
|
+
from .discovered_device import DiscoveredDevice, EncryptionSchema
|
|
2
2
|
from .tapo_discovery import TapoDiscovery
|
|
3
3
|
|
|
4
|
-
__all__ = ["TapoDiscovery", "DiscoveredDevice", "
|
|
4
|
+
__all__ = ["TapoDiscovery", "DiscoveredDevice", "EncryptionSchema"]
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
+
import logging
|
|
2
3
|
from typing import Optional, Any
|
|
3
4
|
|
|
4
5
|
import aiohttp
|
|
@@ -14,7 +15,7 @@ class DiscoveredDevice:
|
|
|
14
15
|
device_model: str
|
|
15
16
|
ip: str
|
|
16
17
|
mac: str
|
|
17
|
-
mgt_encrypt_schm: "
|
|
18
|
+
mgt_encrypt_schm: Optional["EncryptionSchema"]
|
|
18
19
|
|
|
19
20
|
device_id: Optional[str] = None
|
|
20
21
|
owner: Optional[str] = None
|
|
@@ -25,6 +26,10 @@ class DiscoveredDevice:
|
|
|
25
26
|
|
|
26
27
|
@staticmethod
|
|
27
28
|
def from_dict(values: dict[str, Any]) -> "DiscoveredDevice":
|
|
29
|
+
if enc_schema_info := values.get("mgt_encrypt_schm", None):
|
|
30
|
+
encryption_schema = EncryptionSchema(**enc_schema_info)
|
|
31
|
+
else:
|
|
32
|
+
encryption_schema = None
|
|
28
33
|
return DiscoveredDevice(
|
|
29
34
|
device_type=values.get("device_type", values.get("device_type_text")),
|
|
30
35
|
device_model=values.get("device_model", values.get("model")),
|
|
@@ -36,7 +41,7 @@ class DiscoveredDevice:
|
|
|
36
41
|
is_support_iot_cloud=values.get("is_support_iot_cloud", None),
|
|
37
42
|
obd_src=values.get("obd_src", None),
|
|
38
43
|
factory_default=values.get("factory_default", None),
|
|
39
|
-
mgt_encrypt_schm=
|
|
44
|
+
mgt_encrypt_schm=encryption_schema,
|
|
40
45
|
)
|
|
41
46
|
|
|
42
47
|
@property
|
|
@@ -56,25 +61,42 @@ class DiscoveredDevice:
|
|
|
56
61
|
"encrypt_type": self.mgt_encrypt_schm.encrypt_type,
|
|
57
62
|
"http_port": self.mgt_encrypt_schm.http_port,
|
|
58
63
|
"lv": self.mgt_encrypt_schm.lv,
|
|
59
|
-
}
|
|
64
|
+
}
|
|
65
|
+
if self.mgt_encrypt_schm is not None
|
|
66
|
+
else None,
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
async def get_tapo_device(
|
|
63
70
|
self, credentials: AuthCredential, session: Optional[aiohttp.ClientSession] = None
|
|
64
71
|
) -> TapoDevice:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
if encrypt_schema := self.mgt_encrypt_schm:
|
|
73
|
+
port = (
|
|
74
|
+
encrypt_schema.http_port or 443
|
|
75
|
+
if encrypt_schema.is_support_https
|
|
76
|
+
else encrypt_schema.http_port
|
|
77
|
+
)
|
|
78
|
+
config = DeviceConnectConfiguration(
|
|
79
|
+
host=self.ip,
|
|
80
|
+
port=port,
|
|
81
|
+
credentials=credentials,
|
|
82
|
+
device_type=self.device_type,
|
|
83
|
+
encryption_type=encrypt_schema.encrypt_type,
|
|
84
|
+
encryption_version=encrypt_schema.lv,
|
|
85
|
+
)
|
|
86
|
+
else:
|
|
87
|
+
logging.warning(
|
|
88
|
+
"No encryption schema found for discovered device {}, {}",
|
|
89
|
+
self.ip,
|
|
90
|
+
self.device_type,
|
|
91
|
+
)
|
|
92
|
+
config = DeviceConnectConfiguration(
|
|
93
|
+
host=self.ip, port=80, device_type=self.device_type
|
|
94
|
+
)
|
|
73
95
|
return await connect(config, session)
|
|
74
96
|
|
|
75
97
|
|
|
76
98
|
@dataclasses.dataclass
|
|
77
|
-
class
|
|
99
|
+
class EncryptionSchema:
|
|
78
100
|
"""Base model for encryption scheme of discovery result."""
|
|
79
101
|
|
|
80
102
|
is_support_https: Optional[bool] = None
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from plugp100.common.credentials import AuthCredential
|
|
5
|
+
from plugp100.discovery.tapo_discovery import TapoDiscovery
|
|
6
|
+
from plugp100.new.device_factory import connect, DeviceConnectConfiguration
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Example get device from discovery
|
|
10
|
+
async def example_discovery(credentials: AuthCredential):
|
|
11
|
+
discovered = await TapoDiscovery.scan(timeout=5)
|
|
12
|
+
for discovered_device in discovered:
|
|
13
|
+
try:
|
|
14
|
+
device = await discovered_device.get_tapo_device(credentials)
|
|
15
|
+
await device.update()
|
|
16
|
+
print(
|
|
17
|
+
{
|
|
18
|
+
"type": type(device),
|
|
19
|
+
"protocol": device.protocol_version,
|
|
20
|
+
"raw_state": device.raw_state,
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
await device.client.close()
|
|
24
|
+
except Exception as e:
|
|
25
|
+
logging.error(
|
|
26
|
+
f"Failed to update {discovered_device.ip} {discovered_device.device_type}",
|
|
27
|
+
exc_info=e,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Example by knowing protocol details and device class
|
|
32
|
+
async def example_connect_knowing_device_and_protocol(
|
|
33
|
+
credentials: AuthCredential, host: str
|
|
34
|
+
):
|
|
35
|
+
device_configuration = DeviceConnectConfiguration(
|
|
36
|
+
host=host,
|
|
37
|
+
credentials=credentials,
|
|
38
|
+
device_type="SMART.TAPOPLUG",
|
|
39
|
+
encryption_type="klap",
|
|
40
|
+
encryption_version=2,
|
|
41
|
+
)
|
|
42
|
+
device = await connect(device_configuration)
|
|
43
|
+
await device.update()
|
|
44
|
+
print(
|
|
45
|
+
{
|
|
46
|
+
"type": type(device),
|
|
47
|
+
"protocol": device.protocol_version,
|
|
48
|
+
"raw_state": device.raw_state,
|
|
49
|
+
"components": device.get_device_components,
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Example without knowing device class and protocol. The library will try
|
|
55
|
+
# to get info to establish protocol and device class
|
|
56
|
+
async def example_connect_by_guessing(credentials: AuthCredential, host: str):
|
|
57
|
+
device_configuration = DeviceConnectConfiguration(host=host, credentials=credentials)
|
|
58
|
+
device = await connect(device_configuration)
|
|
59
|
+
await device.update()
|
|
60
|
+
print(
|
|
61
|
+
{
|
|
62
|
+
"type": type(device),
|
|
63
|
+
"protocol": device.protocol_version,
|
|
64
|
+
"raw_state": device.raw_state,
|
|
65
|
+
"components": device.get_device_components,
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def main():
|
|
71
|
+
credentials = AuthCredential("<tapo_username>", "<tapo_password>")
|
|
72
|
+
await example_discovery(credentials)
|
|
73
|
+
await example_connect_knowing_device_and_protocol(credentials, "<tapo_device_ip>")
|
|
74
|
+
await example_connect_by_guessing(credentials, "<tapo_device_ip>")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
loop = asyncio.new_event_loop()
|
|
79
|
+
loop.run_until_complete(main())
|
|
80
|
+
loop.run_until_complete(asyncio.sleep(0.1))
|
|
81
|
+
loop.close()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Optional
|
|
1
|
+
from typing import Optional, Any
|
|
2
2
|
|
|
3
3
|
from plugp100.api.tapo_client import TapoClient
|
|
4
4
|
from plugp100.new.components.device_component import DeviceComponent
|
|
@@ -12,7 +12,7 @@ class EnergyComponent(DeviceComponent):
|
|
|
12
12
|
self._energy_usage = None
|
|
13
13
|
self._power_info = None
|
|
14
14
|
|
|
15
|
-
async def update(self,
|
|
15
|
+
async def update(self, current_state: dict[str, Any] | None = None):
|
|
16
16
|
energy_usage = await self._client.get_energy_usage()
|
|
17
17
|
power_info = await self._client.get_current_power()
|
|
18
18
|
self._energy_usage = energy_usage.value if energy_usage.is_success() else None
|
|
@@ -39,8 +39,8 @@ class DeviceConnectConfiguration:
|
|
|
39
39
|
async def connect(
|
|
40
40
|
config: DeviceConnectConfiguration, session: Optional[aiohttp.ClientSession] = None
|
|
41
41
|
):
|
|
42
|
-
protocol = await _get_or_guess_protocol(config, session)
|
|
43
42
|
if config.device_type is None:
|
|
43
|
+
protocol = await _get_or_guess_protocol(config, session)
|
|
44
44
|
_LOGGER.info(
|
|
45
45
|
"Not enough information to detected device type and model, trying to fetching from device..."
|
|
46
46
|
)
|
|
@@ -52,6 +52,8 @@ async def connect(
|
|
|
52
52
|
factory = _get_device_class_from_model_type(device_info.type)
|
|
53
53
|
else:
|
|
54
54
|
factory = _get_device_class_from_model_type(config.device_type)
|
|
55
|
+
protocol = await _get_or_guess_protocol(config, session)
|
|
56
|
+
|
|
55
57
|
client = TapoClient(config.credentials, config.url, protocol, session)
|
|
56
58
|
return factory(config.host, config.port, client)
|
|
57
59
|
|
|
@@ -108,4 +110,6 @@ def _get_device_class_from_model_type(device_type: str) -> Type[TapoDevice]:
|
|
|
108
110
|
return TapoBulb
|
|
109
111
|
elif device_type == "SMART.TAPOHUB":
|
|
110
112
|
return TapoHub
|
|
113
|
+
elif device_type == "SMART.IPCAMERA":
|
|
114
|
+
raise Exception(f"Device of type {device_type} not supported!")
|
|
111
115
|
return TapoDevice
|
|
@@ -52,7 +52,7 @@ class ChildDeviceList(object):
|
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
def get_next_index(self) -> int:
|
|
55
|
-
return self.start_index + len(self.child_device_list)
|
|
55
|
+
return self.start_index + len(self.child_device_list)
|
|
56
56
|
|
|
57
57
|
def has_next(self) -> bool:
|
|
58
58
|
return self.get_next_index() < self.sum
|