mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-21 18:53:35 +00:00
Update docs for newer devices and DeviceConfig (#614)
* Update docs for newer devices and DeviceConfig * Tweak docs post review * Move sentence to newline * Update post review * Update following review
This commit is contained in:
parent
897db674c2
commit
3e0cd07b7c
1
.gitignore
vendored
1
.gitignore
vendored
@ -27,3 +27,4 @@ venv
|
||||
.venv
|
||||
|
||||
/build
|
||||
docs/build
|
||||
|
@ -7,6 +7,11 @@ To see what is being sent to and received from the device, specify option ``--de
|
||||
|
||||
To avoid discovering the devices when executing commands its type can be passed as an option (e.g., ``--type plug`` for plugs, ``--type bulb`` for bulbs, ..).
|
||||
If no type is manually given, its type will be discovered automatically which causes a short delay.
|
||||
Note that the ``--type`` parameter only works for legacy devices using port 9999.
|
||||
|
||||
To avoid discovering the devices for newer KASA or TAPO devices using port 20002 for discovery the ``--device-family``, ``-encrypt-type`` and optional
|
||||
``-login-version`` options can be passed and the devices will probably require authentication via ``--username`` and ``--password``.
|
||||
Refer to ``kasa --help`` for detailed usage.
|
||||
|
||||
If no command is given, the ``state`` command will be executed to query the device state.
|
||||
|
||||
@ -20,7 +25,12 @@ Discovery
|
||||
*********
|
||||
|
||||
The tool can automatically discover supported devices using a broadcast-based discovery protocol.
|
||||
This works by sending an UDP datagram on port 9999 to the broadcast address (defaulting to ``255.255.255.255``).
|
||||
This works by sending an UDP datagram on ports 9999 and 20002 to the broadcast address (defaulting to ``255.255.255.255``).
|
||||
|
||||
Newer devices that respond on port 20002 will require TP-Link cloud credentials to be passed (unless they have never been connected
|
||||
to the TP-Link cloud) or they will report as having failed authentication when trying to query the device.
|
||||
Use ``--username`` and ``--password`` options to specify credentials.
|
||||
These values can also be set as environment variables via ``KASA_USERNAME`` and ``KASA_PASSWORD``.
|
||||
|
||||
On multihomed systems, you can use ``--target`` option to specify the broadcast target.
|
||||
For example, if your devices reside in network ``10.0.0.0/24`` you can use ``kasa --target 10.0.0.255 discover`` to discover them.
|
||||
|
@ -58,6 +58,7 @@ html_theme = "sphinx_rtd_theme"
|
||||
html_static_path = ["_static"]
|
||||
|
||||
todo_include_todos = True
|
||||
myst_heading_anchors = 3
|
||||
|
||||
|
||||
def setup(app):
|
||||
|
@ -1,18 +0,0 @@
|
||||
DeviceConfig
|
||||
============
|
||||
|
||||
.. contents:: Contents
|
||||
:local:
|
||||
|
||||
.. note::
|
||||
|
||||
Feel free to open a pull request to improve the documentation!
|
||||
|
||||
|
||||
API documentation
|
||||
*****************
|
||||
|
||||
.. autoclass:: kasa.DeviceConfig
|
||||
:members:
|
||||
:inherited-members:
|
||||
:undoc-members:
|
@ -1,9 +1,59 @@
|
||||
.. py:module:: kasa.discover
|
||||
|
||||
Discovering devices
|
||||
===================
|
||||
|
||||
.. contents:: Contents
|
||||
:local:
|
||||
|
||||
Discovery
|
||||
*********
|
||||
|
||||
Discovery works by sending broadcast UDP packets to two known TP-link discovery ports, 9999 and 20002.
|
||||
Port 9999 is used for legacy devices that do not use strong encryption and 20002 is for newer devices that use different
|
||||
levels of encryption.
|
||||
If a device uses port 20002 for discovery you will obtain some basic information from the device via discovery, but you
|
||||
will need to await :func:`SmartDevice.update() <kasa.SmartDevice.update()>` to get full device information.
|
||||
Credentials will most likely be required for port 20002 devices although if the device has never been connected to the tplink
|
||||
cloud it may work without credentials.
|
||||
|
||||
To query or update the device requires authentication via :class:`Credentials <kasa.Credentials>` and if this is invalid or not provided it
|
||||
will raise an :class:`AuthenticationException <kasa.AuthenticationException>`.
|
||||
|
||||
If discovery encounters an unsupported device when calling via :meth:`Discover.discover_single() <kasa.Discover.discover_single>`
|
||||
it will raise a :class:`UnsupportedDeviceException <kasa.UnsupportedDeviceException>`.
|
||||
If discovery encounters a device when calling :meth:`Discover.discover() <kasa.Discover.discover>`,
|
||||
you can provide a callback to the ``on_unsupported`` parameter
|
||||
to handle these.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import asyncio
|
||||
from kasa import Discover, Credentials
|
||||
|
||||
async def main():
|
||||
device = await Discover.discover_single(
|
||||
"127.0.0.1",
|
||||
credentials=Credentials("myusername", "mypassword"),
|
||||
discovery_timeout=10
|
||||
)
|
||||
|
||||
await device.update() # Request the update
|
||||
print(device.alias) # Print out the alias
|
||||
|
||||
devices = await Discover.discover(
|
||||
credentials=Credentials("myusername", "mypassword"),
|
||||
discovery_timeout=10
|
||||
)
|
||||
for ip, device in devices.items():
|
||||
await device.update()
|
||||
print(device.alias)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
API documentation
|
||||
*****************
|
||||
|
||||
|
@ -15,4 +15,3 @@
|
||||
smartdimmer
|
||||
smartstrip
|
||||
smartlightstrip
|
||||
deviceconfig
|
||||
|
@ -67,7 +67,7 @@ API documentation
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
.. autoclass:: kasa.BehaviorMode
|
||||
.. autoclass:: kasa.smartbulb.BehaviorMode
|
||||
:members:
|
||||
|
||||
.. autoclass:: kasa.TurnOnBehaviors
|
||||
|
@ -26,7 +26,7 @@ These methods will return the device response, which can be useful for some use
|
||||
|
||||
Errors are raised as :class:`SmartDeviceException` instances for the library user to handle.
|
||||
|
||||
Simple example script showing some functionality:
|
||||
Simple example script showing some functionality for legacy devices:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@ -45,6 +45,31 @@ Simple example script showing some functionality:
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
If you are connecting to a newer KASA or TAPO device you can get the device via discovery or
|
||||
connect directly with :class:`DeviceConfig`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import asyncio
|
||||
from kasa import Discover, Credentials
|
||||
|
||||
async def main():
|
||||
device = await Discover.discover_single(
|
||||
"127.0.0.1",
|
||||
credentials=Credentials("myusername", "mypassword"),
|
||||
discovery_timeout=10
|
||||
)
|
||||
|
||||
config = device.config # DeviceConfig.to_dict() can be used to store for later
|
||||
|
||||
# To connect directly later without discovery
|
||||
|
||||
later_device = await SmartDevice.connect(config=config)
|
||||
|
||||
await later_device.update()
|
||||
|
||||
print(later_device.alias) # Print out the alias
|
||||
|
||||
If you want to perform updates in a loop, you need to make sure that the device accesses are done in the same event loop:
|
||||
|
||||
.. code-block:: python
|
||||
@ -67,6 +92,22 @@ Refer to device type specific classes for more examples:
|
||||
:class:`SmartPlug`, :class:`SmartBulb`, :class:`SmartStrip`,
|
||||
:class:`SmartDimmer`, :class:`SmartLightStrip`.
|
||||
|
||||
DeviceConfig class
|
||||
******************
|
||||
|
||||
The :class:`DeviceConfig` class can be used to initialise devices with parameters to allow them to be connected to without using
|
||||
discovery.
|
||||
This is required for newer KASA and TAPO devices that use different protocols for communication and will not respond
|
||||
on port 9999 but instead use different encryption protocols over http port 80.
|
||||
Currently there are three known types of encryption for TP-Link devices and two different protocols.
|
||||
Devices with automatic firmware updates enabled may update to newer versions of the encryption without separate notice,
|
||||
so discovery can be helpful to determine the correct config.
|
||||
|
||||
To connect directly pass a :class:`DeviceConfig` object to :meth:`SmartDevice.connect()`.
|
||||
|
||||
A :class:`DeviceConfig` can be constucted manually if you know the :attr:`DeviceConfig.connection_type` values for the device or
|
||||
alternatively the config can be retrieved from :attr:`SmartDevice.config` post discovery and then re-used.
|
||||
|
||||
Energy Consumption and Usage Statistics
|
||||
***************************************
|
||||
|
||||
@ -103,3 +144,25 @@ API documentation
|
||||
.. autoclass:: SmartDevice
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
.. autoclass:: DeviceConfig
|
||||
:members:
|
||||
:inherited-members:
|
||||
:undoc-members:
|
||||
:member-order: bysource
|
||||
|
||||
.. autoclass:: Credentials
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
.. autoclass:: SmartDeviceException
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
.. autoclass:: AuthenticationException
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
.. autoclass:: UnsupportedDeviceException
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
@ -126,20 +126,30 @@ class DeviceConfig:
|
||||
"""Class to represent paramaters that determine how to connect to devices."""
|
||||
|
||||
DEFAULT_TIMEOUT = 5
|
||||
|
||||
#: IP address or hostname
|
||||
host: str
|
||||
#: Timeout for querying the device
|
||||
timeout: Optional[int] = DEFAULT_TIMEOUT
|
||||
#: Override the default 9999 port to support port forwarding
|
||||
port_override: Optional[int] = None
|
||||
#: Credentials for devices requiring authentication
|
||||
credentials: Optional[Credentials] = None
|
||||
#: Credentials hash for devices requiring authentication.
|
||||
#: If credentials are also supplied they take precendence over credentials_hash.
|
||||
#: Credentials hash can be retrieved from :attr:`SmartDevice.credentials_hash`
|
||||
credentials_hash: Optional[str] = None
|
||||
#: The protocol specific type of connection. Defaults to the legacy type.
|
||||
connection_type: ConnectionType = field(
|
||||
default_factory=lambda: ConnectionType(
|
||||
DeviceFamilyType.IotSmartPlugSwitch, EncryptType.Xor, 1
|
||||
)
|
||||
)
|
||||
|
||||
#: True if the device uses http. Consumers should retrieve rather than set this
|
||||
#: in order to determine whether they should pass a custom http client if desired.
|
||||
uses_http: bool = False
|
||||
|
||||
# compare=False will be excluded from the serialization and object comparison.
|
||||
#: Set a custom http_client for the device to use.
|
||||
http_client: Optional[httpx.AsyncClient] = field(default=None, compare=False)
|
||||
|
||||
def __post_init__(self):
|
||||
@ -154,7 +164,7 @@ class DeviceConfig:
|
||||
credentials_hash: Optional[str] = None,
|
||||
exclude_credentials: bool = False,
|
||||
) -> Dict[str, Dict[str, str]]:
|
||||
"""Convert connection params to dict."""
|
||||
"""Convert device config to dict."""
|
||||
if credentials_hash or exclude_credentials:
|
||||
self.credentials = None
|
||||
if credentials_hash:
|
||||
@ -163,5 +173,5 @@ class DeviceConfig:
|
||||
|
||||
@staticmethod
|
||||
def from_dict(cparam_dict: Dict[str, Dict[str, str]]) -> "DeviceConfig":
|
||||
"""Return connection parameters from dict."""
|
||||
"""Return device config from dict."""
|
||||
return _dataclass_from_dict(DeviceConfig, cparam_dict)
|
||||
|
@ -171,10 +171,15 @@ class Discover:
|
||||
device object.
|
||||
|
||||
:func:`discover_single()` can be used to initialize a single device given its
|
||||
IP address. If the type of the device and its IP address is already known,
|
||||
you can initialize the corresponding device class directly without this.
|
||||
IP address. If the :class:`DeviceConfig` of the device is already known,
|
||||
you can initialize the corresponding device class directly without discovery.
|
||||
|
||||
The protocol uses UDP broadcast datagrams on port 9999 for discovery.
|
||||
The protocol uses UDP broadcast datagrams on port 9999 and 20002 for discovery.
|
||||
Legacy devices support discovery on port 9999 and newer devices on 20002.
|
||||
|
||||
Newer devices that respond on port 20002 will most likely require TP-Link cloud
|
||||
credentials to be passed if queries or updates are to be performed on the returned
|
||||
devices.
|
||||
|
||||
Examples:
|
||||
Discovery returns a list of discovered devices:
|
||||
@ -222,7 +227,8 @@ class Discover:
|
||||
) -> DeviceDict:
|
||||
"""Discover supported devices.
|
||||
|
||||
Sends discovery message to 255.255.255.255:9999 in order
|
||||
Sends discovery message to 255.255.255.255:9999 and
|
||||
255.255.255.255:20002 in order
|
||||
to detect available supported devices in the local network,
|
||||
and waits for given timeout for answers from devices.
|
||||
If you have multiple interfaces,
|
||||
@ -239,9 +245,13 @@ class Discover:
|
||||
:param target: The target address where to send the broadcast discovery
|
||||
queries if multi-homing (e.g. 192.168.xxx.255).
|
||||
:param on_discovered: coroutine to execute on discovery
|
||||
:param timeout: How long to wait for responses, defaults to 5
|
||||
:param discovery_timeout: Seconds to wait for responses, defaults to 5
|
||||
:param discovery_packets: Number of discovery packets to broadcast
|
||||
:param interface: Bind to specific interface
|
||||
:param on_unsupported: Optional callback when unsupported devices are discovered
|
||||
:param credentials: Credentials for devices requiring authentication
|
||||
:param port: Override the discovery port for devices listening on 9999
|
||||
:param timeout: Query timeout in seconds for devices returned by discovery
|
||||
:return: dictionary with discovered devices
|
||||
"""
|
||||
loop = asyncio.get_event_loop()
|
||||
@ -282,13 +292,14 @@ class Discover:
|
||||
"""Discover a single device by the given IP address.
|
||||
|
||||
It is generally preferred to avoid :func:`discover_single()` and
|
||||
use :func:`connect_single()` instead as it should perform better when
|
||||
use :meth:`SmartDevice.connect()` instead as it should perform better when
|
||||
the WiFi network is congested or the device is not responding
|
||||
to discovery requests.
|
||||
|
||||
:param host: Hostname of device to query
|
||||
:param port: Optionally set a different port for the device
|
||||
:param timeout: Timeout for discovery
|
||||
:param discovery_timeout: Timeout in seconds for discovery
|
||||
:param port: Optionally set a different port for legacy devices using port 9999
|
||||
:param timeout: Timeout in seconds device for devices queries
|
||||
:param credentials: Credentials for devices that require authentication
|
||||
:rtype: SmartDevice
|
||||
:return: Object for querying/controlling found device.
|
||||
|
@ -247,7 +247,7 @@ class SmartDevice:
|
||||
|
||||
@property
|
||||
def credentials_hash(self) -> Optional[str]:
|
||||
"""Return the connection parameters the device is using."""
|
||||
"""The protocol specific hash of the credentials the device is using."""
|
||||
return self.protocol._transport.credentials_hash
|
||||
|
||||
def add_module(self, name: str, module: Module):
|
||||
@ -804,7 +804,7 @@ class SmartDevice:
|
||||
|
||||
@property
|
||||
def config(self) -> DeviceConfig:
|
||||
"""Return the connection parameters the device is using."""
|
||||
"""Return the device configuration."""
|
||||
return self.protocol.config
|
||||
|
||||
@staticmethod
|
||||
|
Loading…
Reference in New Issue
Block a user