Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ Current state: {'total': 133.105, 'power': 108.223577, 'current': 0.54463, 'volt

# Library usage

If you want to use this library in your own project, a good starting point is to check [the documentation on discovering devices](https://python-kasa.readthedocs.io/en/latest/discover.html).
If you want to use this library in your own project, a good starting point is [the tutorial in the documentation](https://python-kasa.readthedocs.io/en/latest/tutorial.html).

You can find several code examples in the API documentation of each of the implementation base classes, check out the [documentation for the base class shared by all supported devices](https://python-kasa.readthedocs.io/en/latest/smartdevice.html).

[The library design and module structure is described in a separate page](https://python-kasa.readthedocs.io/en/latest/design.html).
Expand Down
7 changes: 4 additions & 3 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
import os
import sys

sys.path.insert(0, os.path.abspath("..")) # Will find modules in the docs parent


# -- Project information -----------------------------------------------------
Expand Down
36 changes: 24 additions & 12 deletions docs/source/design.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,21 @@ Use :func:`~kasa.Discover.discover` to perform udp-based broadcast discovery on
This will return you a list of device instances based on the discovery replies.

If the device's host is already known, you can use to construct a device instance with
:meth:`~kasa.SmartDevice.connect()`.
:meth:`~kasa.Device.connect()`.

The :meth:`~kasa.SmartDevice.connect()` also enables support for connecting to new
The :meth:`~kasa.Device.connect()` also enables support for connecting to new
KASA SMART protocol and TAPO devices directly using the parameter :class:`~kasa.DeviceConfig`.
Simply serialize the :attr:`~kasa.SmartDevice.config` property via :meth:`~kasa.DeviceConfig.to_dict()`
Simply serialize the :attr:`~kasa.Device.config` property via :meth:`~kasa.DeviceConfig.to_dict()`
and then deserialize it later with :func:`~kasa.DeviceConfig.from_dict()`
and then pass it into :meth:`~kasa.SmartDevice.connect()`.
and then pass it into :meth:`~kasa.Device.connect()`.


.. _update_cycle:

Update Cycle
************

When :meth:`~kasa.SmartDevice.update()` is called,
When :meth:`~kasa.Device.update()` is called,
the library constructs a query to send to the device based on :ref:`supported modules <modules>`.
Internally, each module defines :meth:`~kasa.modules.Module.query()` to describe what they want query during the update.

Expand All @@ -45,23 +45,23 @@ All properties defined both in the device class and in the module classes follow

While the properties are designed to provide a nice API to use for common use cases,
you may sometimes want to access the raw, cached data as returned by the device.
This can be done using the :attr:`~kasa.SmartDevice.internal_state` property.
This can be done using the :attr:`~kasa.Device.internal_state` property.


.. _modules:

Modules
*******

The functionality provided by all :class:`~kasa.SmartDevice` instances is (mostly) done inside separate modules.
The functionality provided by all :class:`~kasa.Device` instances is (mostly) done inside separate modules.
While the individual device-type specific classes provide an easy access for the most import features,
you can also access individual modules through :attr:`kasa.SmartDevice.modules`.
You can get the list of supported modules for a given device instance using :attr:`~kasa.SmartDevice.supported_modules`.
You can get the list of supported modules for a given device instance using :attr:`~kasa.Device.supported_modules`.

.. note::

If you only need some module-specific information,
you can call the wanted method on the module to avoid using :meth:`~kasa.SmartDevice.update`.
you can call the wanted method on the module to avoid using :meth:`~kasa.Device.update`.

Protocols and Transports
************************
Expand Down Expand Up @@ -112,10 +112,22 @@ The base exception for all library errors is :class:`KasaException <kasa.excepti
- If the device fails to respond within a timeout the library raises a :class:`TimeoutError <kasa.exceptions.TimeoutError>`.
- All other failures will raise the base :class:`KasaException <kasa.exceptions.KasaException>` class.

API documentation for modules
*****************************
API documentation for modules and features
******************************************

.. automodule:: kasa.modules
.. autoclass:: kasa.Module
:noindex:
:members:
:inherited-members:
:undoc-members:

.. automodule:: kasa.interfaces
:noindex:
:members:
:inherited-members:
:undoc-members:

.. autoclass:: kasa.Feature
:noindex:
:members:
:inherited-members:
Expand Down
16 changes: 8 additions & 8 deletions docs/source/smartdevice.rst → docs/source/device.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ Common API
.. contents:: Contents
:local:

SmartDevice class
*****************
Device class
************

The basic functionalities of all supported devices are accessible using the common :class:`SmartDevice` base class.
The basic functionalities of all supported devices are accessible using the common :class:`Device` base class.

The property accesses use the data obtained before by awaiting :func:`SmartDevice.update()`.
The property accesses use the data obtained before by awaiting :func:`Device.update()`.
The values are cached until the next update call. In practice this means that property accesses do no I/O and are dependent, while I/O producing methods need to be awaited.
See :ref:`library_design` for more detailed information.

Expand All @@ -20,7 +20,7 @@ See :ref:`library_design` for more detailed information.
This means that you need to use the same event loop for subsequent requests.
The library gives a warning ("Detected protocol reuse between different event loop") to hint if you are accessing the device incorrectly.

Methods changing the state of the device do not invalidate the cache (i.e., there is no implicit :func:`SmartDevice.update()` call made by the library).
Methods changing the state of the device do not invalidate the cache (i.e., there is no implicit :func:`Device.update()` call made by the library).
You can assume that the operation has succeeded if no exception is raised.
These methods will return the device response, which can be useful for some use cases.

Expand Down Expand Up @@ -103,10 +103,10 @@ Currently there are three known types of encryption for TP-Link devices and two
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()`.
To connect directly pass a :class:`DeviceConfig` object to :meth:`Device.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.
alternatively the config can be retrieved from :attr:`Device.config` post discovery and then re-used.

Energy Consumption and Usage Statistics
***************************************
Expand Down Expand Up @@ -141,7 +141,7 @@ You can access this information using through the usage module (:class:`kasa.mod
API documentation
*****************

.. autoclass:: SmartDevice
.. autoclass:: Device
:members:
:undoc-members:

Expand Down
2 changes: 1 addition & 1 deletion docs/source/discover.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Discovery works by sending broadcast UDP packets to two known TP-link discovery
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.
will need to await :func:`Device.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.

Expand Down
3 changes: 2 additions & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@

Home <self>
cli
tutorial
discover
smartdevice
device
design
contribute
smartbulb
Expand Down
6 changes: 3 additions & 3 deletions docs/source/smartbulb.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ API documentation
:members:
:undoc-members:

.. autoclass:: kasa.smartbulb.BehaviorMode
.. autoclass:: kasa.iot.iotbulb.BehaviorMode
:members:

.. autoclass:: kasa.TurnOnBehaviors
.. autoclass:: kasa.iot.iotbulb.TurnOnBehaviors
:members:


.. autoclass:: kasa.TurnOnBehavior
.. autoclass:: kasa.iot.iotbulb.TurnOnBehavior
:undoc-members:
:members:
8 changes: 8 additions & 0 deletions docs/source/tutorial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Tutorial

```{eval-rst}
.. automodule:: tutorial
:members:
:inherited-members:
:undoc-members:
```
103 changes: 103 additions & 0 deletions docs/tutorial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# ruff: noqa
"""
The kasa library is fully async and methods that perform IO need to be run inside an async couroutine.

These examples assume you are following the tutorial inside `asyncio REPL` (python -m asyncio) or the code
is running inside an async function (`async def`).


The main entry point for the API is :meth:`~kasa.Discover.discover` and
:meth:`~kasa.Discover.discover_single` which return Device objects.

Most newer devices require your TP-Link cloud username and password, but this can be omitted for older devices.

>>> from kasa import Device, Discover, Credentials

:func:`~kasa.Discover.discover` returns a list of devices on your network:

>>> devices = await Discover.discover(credentials=Credentials("user@example.com", "great_password"))
>>> for dev in devices:
>>> await dev.update()
>>> print(dev.host)
127.0.0.1
127.0.0.2

:meth:`~kasa.Discover.discover_single` returns a single device by hostname:

>>> dev = await Discover.discover_single("127.0.0.1", credentials=Credentials("user@example.com", "great_password"))
>>> await dev.update()
>>> dev.alias
Living Room
>>> dev.model
L530
>>> dev.rssi
-52
>>> dev.mac
5C:E9:31:00:00:00

You can update devices by calling different methods (e.g., ``set_``-prefixed ones).
Note, that these do not update the internal state, but you need to call :meth:`~kasa.Device.update()` to query the device again.
back to the device.

>>> await dev.set_alias("Dining Room")
>>> await dev.update()
>>> dev.alias
Dining Room

Different groups of functionality are supported by modules which you can access via :attr:`~kasa.Device.modules` with a typed
key from :class:`~kasa.Module`.

Modules will only be available on the device if they are supported but some individual features of a module may not be available for your device.
You can check the availability using ``is_``-prefixed properties like `is_color`.

>>> from kasa import Module
>>> Module.Light in dev.modules
True
>>> light = dev.modules[Module.Light]
>>> light.brightness
100
>>> await light.set_brightness(50)
>>> await dev.update()
>>> light.brightness
50
>>> light.is_color
True
>>> if light.is_color:
>>> print(light.hsv)
HSV(hue=0, saturation=100, value=50)

You can test if a module is supported by using `get` to access it.

>>> if effect := dev.modules.get(Module.LightEffect):
>>> print(effect.effect)
>>> print(effect.effect_list)
>>> if effect := dev.modules.get(Module.LightEffect):
>>> await effect.set_effect("Party")
>>> await dev.update()
>>> print(effect.effect)
Off
['Off', 'Party', 'Relax']
Party

Individual pieces of functionality are also exposed via features which you can access via :attr:`~kasa.Device.features` and will only be present if they are supported.

Features are similar to modules in that they provide functionality that may or may not be present.

Whereas modules group functionality into a common interface, features expose a single function that may or may not be part of a module.

The advantage of features is that they have a simple common interface of `id`, `name`, `value` and `set_value` so no need to learn the module API.

They are useful if you want write code that dynamically adapts as new features are added to the API.

>>> if auto_update := dev.features.get("auto_update_enabled"):
>>> print(auto_update.value)
False
>>> if auto_update:
>>> await auto_update.set_value(True)
>>> await dev.update()
>>> print(auto_update.value)
True
>>> for feat in dev.features.values():
>>> print(f"{feat.name}: {feat.value}")
Device ID: 0000000000000000000000000000000000000000\nState: True\nSignal Level: 2\nRSSI: -52\nSSID: #MASKED_SSID#\nOverheated: False\nBrightness: 50\nCloud connection: True\nHSV: HSV(hue=0, saturation=100, value=50)\nColor temperature: 2700\nAuto update enabled: True\nUpdate available: False\nCurrent firmware version: 1.1.6 Build 240130 Rel.173828\nAvailable firmware version: 1.1.6 Build 240130 Rel.173828\nLight effect: Party\nSmooth transition on: 2\nSmooth transition off: 2\nTime: 2024-02-23 02:40:15+01:00
"""
10 changes: 5 additions & 5 deletions kasa/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""Python interface for TP-Link's smart home devices.

All common, shared functionalities are available through `SmartDevice` class::
All common, shared functionalities are available through `Device` class::

x = SmartDevice("192.168.1.1")
print(x.sys_info)
>>> from kasa import Discover
>>> x = await Discover.discover_single("192.168.1.1")
>>> print(x.model)

For device type specific actions `SmartBulb`, `SmartPlug`, or `SmartStrip`
should be used instead.
For device type specific actions `modules` and `features` should be used instead.

Module-specific errors are raised as `KasaException` and are expected
to be handled by the user of the library.
Expand Down
2 changes: 1 addition & 1 deletion kasa/deviceconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ class DeviceConfig:
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 can be retrieved from :attr:`Device.credentials_hash`
credentials_hash: Optional[str] = None
#: The protocol specific type of connection. Defaults to the legacy type.
batch_size: Optional[int] = None
Expand Down
6 changes: 3 additions & 3 deletions kasa/discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,10 +270,10 @@ async def discover(
you can use *target* parameter to specify the network for discovery.

If given, `on_discovered` coroutine will get awaited with
a :class:`SmartDevice`-derived object as parameter.
a :class:`Device`-derived object as parameter.

The results of the discovery are returned as a dict of
:class:`SmartDevice`-derived objects keyed with IP addresses.
:class:`Device`-derived objects keyed with IP addresses.
The devices are already initialized and all but emeter-related properties
can be accessed directly.

Expand Down Expand Up @@ -332,7 +332,7 @@ async def discover_single(
"""Discover a single device by the given IP address.

It is generally preferred to avoid :func:`discover_single()` and
use :meth:`SmartDevice.connect()` instead as it should perform better when
use :meth:`Device.connect()` instead as it should perform better when
the WiFi network is congested or the device is not responding
to discovery requests.

Expand Down
2 changes: 1 addition & 1 deletion kasa/iot/iotbulb.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ class IotBulb(IotDevice):
>>> bulb.presets
[LightPreset(index=0, brightness=50, hue=0, saturation=0, color_temp=2700, custom=None, id=None, mode=None), LightPreset(index=1, brightness=100, hue=0, saturation=75, color_temp=0, custom=None, id=None, mode=None), LightPreset(index=2, brightness=100, hue=120, saturation=75, color_temp=0, custom=None, id=None, mode=None), LightPreset(index=3, brightness=100, hue=240, saturation=75, color_temp=0, custom=None, id=None, mode=None)]

To modify an existing preset, pass :class:`~kasa.smartbulb.LightPreset`
To modify an existing preset, pass :class:`~kasa.interfaces.light.LightPreset`
instance to :func:`save_preset` method:

>>> preset = bulb.presets[0]
Expand Down
2 changes: 1 addition & 1 deletion kasa/iot/iotplug.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class IotPlug(IotDevice):
>>> plug.led
True

For more examples, see the :class:`SmartDevice` class.
For more examples, see the :class:`Device` class.
"""

def __init__(
Expand Down
2 changes: 1 addition & 1 deletion kasa/iot/iotstrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class IotStrip(IotDevice):
>>> strip.is_on
True

For more examples, see the :class:`SmartDevice` class.
For more examples, see the :class:`Device` class.
"""

def __init__(
Expand Down
2 changes: 1 addition & 1 deletion kasa/tests/fixtures/smart/L530E(EU)_3.0_1.1.6.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@
"longitude": 0,
"mac": "5C-E9-31-00-00-00",
"model": "L530",
"nickname": "I01BU0tFRF9OQU1FIw==",
"nickname": "TGl2aW5nIFJvb20=",
"oem_id": "00000000000000000000000000000000",
"overheated": false,
"region": "Europe/Berlin",
Expand Down
Loading