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.
Files changed (141) hide show
  1. plugp100-5.0.0.dev6/PKG-INFO +189 -0
  2. plugp100-5.0.0.dev6/README.md +167 -0
  3. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/__init__.py +1 -1
  4. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/discovery/__init__.py +2 -2
  5. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/discovery/discovered_device.py +34 -12
  6. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/discovery/tapo_discovery.py +0 -1
  7. plugp100-5.0.0.dev6/plugp100/example.py +81 -0
  8. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/energy_component.py +2 -2
  9. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/device_factory.py +5 -1
  10. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/child_device_list.py +1 -1
  11. plugp100-5.0.0.dev6/plugp100.egg-info/PKG-INFO +189 -0
  12. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/test_power_strip.py +2 -2
  13. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/test_tapo_discovery.py +2 -2
  14. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/test_hub.py +6 -1
  15. plugp100-5.0.0.dev4/PKG-INFO +0 -88
  16. plugp100-5.0.0.dev4/README.md +0 -66
  17. plugp100-5.0.0.dev4/plugp100/example.py +0 -53
  18. plugp100-5.0.0.dev4/plugp100.egg-info/PKG-INFO +0 -88
  19. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/LICENSE +0 -0
  20. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/MANIFEST.in +0 -0
  21. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/__init__.py +0 -0
  22. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/light_effect.py +0 -0
  23. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/light_effect_preset.py +0 -0
  24. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/__init__.py +0 -0
  25. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/handshake_params.py +0 -0
  26. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/internal/__init__.py +0 -0
  27. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/internal/snowflake_id.py +0 -0
  28. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/login_device.py +0 -0
  29. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/secure_passthrough_params.py +0 -0
  30. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/set_device_info/__init__.py +0 -0
  31. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/set_device_info/play_alarm_params.py +0 -0
  32. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/set_device_info/set_light_color_info_params.py +0 -0
  33. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/set_device_info/set_light_info_params.py +0 -0
  34. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/set_device_info/set_plug_info_params.py +0 -0
  35. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/set_device_info/set_trv_info_params.py +0 -0
  36. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/tapo_request.py +0 -0
  37. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/requests/trigger_logs_params.py +0 -0
  38. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/api/tapo_client.py +0 -0
  39. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/common/__init__.py +0 -0
  40. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/common/credentials.py +0 -0
  41. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/common/functional/__init__.py +0 -0
  42. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/common/functional/tri.py +0 -0
  43. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/common/utils/__init__.py +0 -0
  44. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/common/utils/http_client.py +0 -0
  45. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/common/utils/json_utils.py +0 -0
  46. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/discovery/cloud_client.py +0 -0
  47. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/discovery/rsa_session.py +0 -0
  48. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/encryption/__init__.py +0 -0
  49. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/encryption/helpers.py +0 -0
  50. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/encryption/key_pair.py +0 -0
  51. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/encryption/tp_link_cipher.py +0 -0
  52. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/__init__.py +0 -0
  53. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/child/__init__.py +0 -0
  54. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/child/tapohubchildren.py +0 -0
  55. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/child/tapostripsocket.py +0 -0
  56. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/__init__.py +0 -0
  57. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/alarm_component.py +0 -0
  58. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/battery_component.py +0 -0
  59. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/device_component.py +0 -0
  60. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/hub_children_component.py +0 -0
  61. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/humidity_component.py +0 -0
  62. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/light_component.py +0 -0
  63. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/light_effect_component.py +0 -0
  64. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/motion_sensor_component.py +0 -0
  65. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/on_off_component.py +0 -0
  66. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/overheat_component.py +0 -0
  67. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/report_mode_component.py +0 -0
  68. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/smart_door_component.py +0 -0
  69. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/socket_children_component.py +0 -0
  70. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/temperature_component.py +0 -0
  71. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/temperature_humidity_records.py +0 -0
  72. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/trigger_log_component.py +0 -0
  73. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/components/water_leak_component.py +0 -0
  74. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/device_type.py +0 -0
  75. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/errors/__init__.py +0 -0
  76. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/errors/invalid_authentication.py +0 -0
  77. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/event_polling/__init__.py +0 -0
  78. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/event_polling/event_subscription.py +0 -0
  79. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/event_polling/poll_tracker.py +0 -0
  80. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/event_polling/state_tracker.py +0 -0
  81. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/hub_device_tracker.py +0 -0
  82. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/tapobulb.py +0 -0
  83. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/tapodevice.py +0 -0
  84. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/tapohub.py +0 -0
  85. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/new/tapoplug.py +0 -0
  86. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/protocol/__init__.py +0 -0
  87. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/protocol/klap/__init__.py +0 -0
  88. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/protocol/klap/klap_handshake_revision.py +0 -0
  89. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/protocol/klap/klap_protocol.py +0 -0
  90. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/protocol/passthrough_protocol.py +0 -0
  91. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/protocol/securepassthrough_transport.py +0 -0
  92. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/protocol/tapo_protocol.py +0 -0
  93. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/__init__.py +0 -0
  94. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/alarm_type_list.py +0 -0
  95. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/components.py +0 -0
  96. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/device_state.py +0 -0
  97. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/device_usage_info.py +0 -0
  98. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/energy_info.py +0 -0
  99. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/__init__.py +0 -0
  100. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/hub_child_base_info.py +0 -0
  101. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/ke100_device_state.py +0 -0
  102. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/leak_device_state.py +0 -0
  103. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/s200b_device_state.py +0 -0
  104. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/switch_child_device_state.py +0 -0
  105. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/t100_device_state.py +0 -0
  106. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/t110_device_state.py +0 -0
  107. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/t31x_device_state.py +0 -0
  108. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/hub_childs/trigger_log_response.py +0 -0
  109. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/power_info.py +0 -0
  110. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/tapo_exception.py +0 -0
  111. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/tapo_response.py +0 -0
  112. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/temperature_unit.py +0 -0
  113. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100/responses/time_info.py +0 -0
  114. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100.egg-info/SOURCES.txt +0 -0
  115. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100.egg-info/dependency_links.txt +0 -0
  116. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100.egg-info/requires.txt +0 -0
  117. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/plugp100.egg-info/top_level.txt +0 -0
  118. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/pyproject.toml +0 -0
  119. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/requirements-dev.txt +0 -0
  120. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/requirements.txt +0 -0
  121. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/setup.cfg +0 -0
  122. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/setup.py +0 -0
  123. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/__init__.py +0 -0
  124. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/tapo_test_helper.py +0 -0
  125. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/test_button_t310.py +0 -0
  126. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/test_hub.py +0 -0
  127. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/test_ledstrip.py +0 -0
  128. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/test_light.py +0 -0
  129. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/test_plug.py +0 -0
  130. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/integration/test_sensor_s200b.py +0 -0
  131. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/__init__.py +0 -0
  132. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/hub_child/__init__.py +0 -0
  133. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/hub_child/test_button.py +0 -0
  134. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/hub_child/test_temp_hum_sensor.py +0 -0
  135. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/hub_child/test_trv_ke100.py +0 -0
  136. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/test_bulb.py +0 -0
  137. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/test_discovery.py +0 -0
  138. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/test_klap_protocol.py +0 -0
  139. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/test_plug.py +0 -0
  140. {plugp100-5.0.0.dev4 → plugp100-5.0.0.dev6}/tests/unit/test_plug_strip.py +0 -0
  141. {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)
@@ -16,4 +16,4 @@ from plugp100.api.requests import *
16
16
  from plugp100.api import *
17
17
  from plugp100.common import *
18
18
 
19
- __version__ = "5.0.0-dev.4"
19
+ __version__ = "5.0.0-dev.6"
@@ -1,4 +1,4 @@
1
- from .discovered_device import DiscoveredDevice, EncryptionScheme
1
+ from .discovered_device import DiscoveredDevice, EncryptionSchema
2
2
  from .tapo_discovery import TapoDiscovery
3
3
 
4
- __all__ = ["TapoDiscovery", "DiscoveredDevice", "EncryptionScheme"]
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: "EncryptionScheme"
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=EncryptionScheme(**values.get("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
- config = DeviceConnectConfiguration(
66
- host=self.ip,
67
- port=self.mgt_encrypt_schm.http_port,
68
- credentials=credentials,
69
- device_type=self.device_type,
70
- encryption_type=self.mgt_encrypt_schm.encrypt_type,
71
- encryption_version=self.mgt_encrypt_schm.lv,
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 EncryptionScheme:
99
+ class EncryptionSchema:
78
100
  """Base model for encryption scheme of discovery result."""
79
101
 
80
102
  is_support_https: Optional[bool] = None
@@ -82,7 +82,6 @@ class TapoDiscovery:
82
82
  None,
83
83
  lambda: list(TapoDiscovery(broadcast, port, timeout)._scan()),
84
84
  )
85
- print(devices_found)
86
85
  return [DiscoveredDevice.from_dict(x) for x in devices_found]
87
86
 
88
87
  @staticmethod
@@ -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, **kwargs):
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) + 1
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