mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 11:13:34 +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
|
.venv
|
||||||
|
|
||||||
/build
|
/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, ..).
|
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.
|
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.
|
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.
|
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.
|
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.
|
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"]
|
html_static_path = ["_static"]
|
||||||
|
|
||||||
todo_include_todos = True
|
todo_include_todos = True
|
||||||
|
myst_heading_anchors = 3
|
||||||
|
|
||||||
|
|
||||||
def setup(app):
|
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
|
Discovering devices
|
||||||
===================
|
===================
|
||||||
|
|
||||||
.. contents:: Contents
|
.. contents:: Contents
|
||||||
:local:
|
: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
|
API documentation
|
||||||
*****************
|
*****************
|
||||||
|
|
||||||
|
@ -15,4 +15,3 @@
|
|||||||
smartdimmer
|
smartdimmer
|
||||||
smartstrip
|
smartstrip
|
||||||
smartlightstrip
|
smartlightstrip
|
||||||
deviceconfig
|
|
||||||
|
@ -67,7 +67,7 @@ API documentation
|
|||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
|
|
||||||
.. autoclass:: kasa.BehaviorMode
|
.. autoclass:: kasa.smartbulb.BehaviorMode
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. autoclass:: kasa.TurnOnBehaviors
|
.. 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.
|
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
|
.. code-block:: python
|
||||||
|
|
||||||
@ -45,6 +45,31 @@ Simple example script showing some functionality:
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(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:
|
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
|
.. code-block:: python
|
||||||
@ -67,6 +92,22 @@ Refer to device type specific classes for more examples:
|
|||||||
:class:`SmartPlug`, :class:`SmartBulb`, :class:`SmartStrip`,
|
:class:`SmartPlug`, :class:`SmartBulb`, :class:`SmartStrip`,
|
||||||
:class:`SmartDimmer`, :class:`SmartLightStrip`.
|
: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
|
Energy Consumption and Usage Statistics
|
||||||
***************************************
|
***************************************
|
||||||
|
|
||||||
@ -103,3 +144,25 @@ API documentation
|
|||||||
.. autoclass:: SmartDevice
|
.. autoclass:: SmartDevice
|
||||||
:members:
|
:members:
|
||||||
:undoc-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."""
|
"""Class to represent paramaters that determine how to connect to devices."""
|
||||||
|
|
||||||
DEFAULT_TIMEOUT = 5
|
DEFAULT_TIMEOUT = 5
|
||||||
|
#: IP address or hostname
|
||||||
host: str
|
host: str
|
||||||
|
#: Timeout for querying the device
|
||||||
timeout: Optional[int] = DEFAULT_TIMEOUT
|
timeout: Optional[int] = DEFAULT_TIMEOUT
|
||||||
|
#: Override the default 9999 port to support port forwarding
|
||||||
port_override: Optional[int] = None
|
port_override: Optional[int] = None
|
||||||
|
#: Credentials for devices requiring authentication
|
||||||
credentials: Optional[Credentials] = None
|
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
|
credentials_hash: Optional[str] = None
|
||||||
|
#: The protocol specific type of connection. Defaults to the legacy type.
|
||||||
connection_type: ConnectionType = field(
|
connection_type: ConnectionType = field(
|
||||||
default_factory=lambda: ConnectionType(
|
default_factory=lambda: ConnectionType(
|
||||||
DeviceFamilyType.IotSmartPlugSwitch, EncryptType.Xor, 1
|
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
|
uses_http: bool = False
|
||||||
|
|
||||||
# compare=False will be excluded from the serialization and object comparison.
|
# 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)
|
http_client: Optional[httpx.AsyncClient] = field(default=None, compare=False)
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
@ -154,7 +164,7 @@ class DeviceConfig:
|
|||||||
credentials_hash: Optional[str] = None,
|
credentials_hash: Optional[str] = None,
|
||||||
exclude_credentials: bool = False,
|
exclude_credentials: bool = False,
|
||||||
) -> Dict[str, Dict[str, str]]:
|
) -> Dict[str, Dict[str, str]]:
|
||||||
"""Convert connection params to dict."""
|
"""Convert device config to dict."""
|
||||||
if credentials_hash or exclude_credentials:
|
if credentials_hash or exclude_credentials:
|
||||||
self.credentials = None
|
self.credentials = None
|
||||||
if credentials_hash:
|
if credentials_hash:
|
||||||
@ -163,5 +173,5 @@ class DeviceConfig:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_dict(cparam_dict: Dict[str, Dict[str, str]]) -> "DeviceConfig":
|
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)
|
return _dataclass_from_dict(DeviceConfig, cparam_dict)
|
||||||
|
@ -171,10 +171,15 @@ class Discover:
|
|||||||
device object.
|
device object.
|
||||||
|
|
||||||
:func:`discover_single()` can be used to initialize a single device given its
|
: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,
|
IP address. If the :class:`DeviceConfig` of the device is already known,
|
||||||
you can initialize the corresponding device class directly without this.
|
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:
|
Examples:
|
||||||
Discovery returns a list of discovered devices:
|
Discovery returns a list of discovered devices:
|
||||||
@ -222,7 +227,8 @@ class Discover:
|
|||||||
) -> DeviceDict:
|
) -> DeviceDict:
|
||||||
"""Discover supported devices.
|
"""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,
|
to detect available supported devices in the local network,
|
||||||
and waits for given timeout for answers from devices.
|
and waits for given timeout for answers from devices.
|
||||||
If you have multiple interfaces,
|
If you have multiple interfaces,
|
||||||
@ -239,9 +245,13 @@ class Discover:
|
|||||||
:param target: The target address where to send the broadcast discovery
|
:param target: The target address where to send the broadcast discovery
|
||||||
queries if multi-homing (e.g. 192.168.xxx.255).
|
queries if multi-homing (e.g. 192.168.xxx.255).
|
||||||
:param on_discovered: coroutine to execute on discovery
|
: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 discovery_packets: Number of discovery packets to broadcast
|
||||||
:param interface: Bind to specific interface
|
: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
|
:return: dictionary with discovered devices
|
||||||
"""
|
"""
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
@ -282,13 +292,14 @@ class Discover:
|
|||||||
"""Discover a single device by the given IP address.
|
"""Discover a single device by the given IP address.
|
||||||
|
|
||||||
It is generally preferred to avoid :func:`discover_single()` and
|
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
|
the WiFi network is congested or the device is not responding
|
||||||
to discovery requests.
|
to discovery requests.
|
||||||
|
|
||||||
:param host: Hostname of device to query
|
:param host: Hostname of device to query
|
||||||
:param port: Optionally set a different port for the device
|
:param discovery_timeout: Timeout in seconds for discovery
|
||||||
:param timeout: Timeout 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
|
:param credentials: Credentials for devices that require authentication
|
||||||
:rtype: SmartDevice
|
:rtype: SmartDevice
|
||||||
:return: Object for querying/controlling found device.
|
:return: Object for querying/controlling found device.
|
||||||
|
@ -247,7 +247,7 @@ class SmartDevice:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def credentials_hash(self) -> Optional[str]:
|
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
|
return self.protocol._transport.credentials_hash
|
||||||
|
|
||||||
def add_module(self, name: str, module: Module):
|
def add_module(self, name: str, module: Module):
|
||||||
@ -804,7 +804,7 @@ class SmartDevice:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def config(self) -> DeviceConfig:
|
def config(self) -> DeviceConfig:
|
||||||
"""Return the connection parameters the device is using."""
|
"""Return the device configuration."""
|
||||||
return self.protocol.config
|
return self.protocol.config
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
Loading…
Reference in New Issue
Block a user