Update docs with more howto examples (#968)

Co-authored-by: Teemu R. <tpr@iki.fi>
This commit is contained in:
Steven B 2024-06-19 09:53:40 +01:00 committed by GitHub
parent 6b46773609
commit 0d84d8785e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 646 additions and 86 deletions

View File

@ -173,16 +173,9 @@ Current state: {'total': 133.105, 'power': 108.223577, 'current': 0.54463, 'volt
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).
You can find several code examples in the API documentation [How to guides](https://python-kasa.readthedocs.io/en/latest/guides.html).
[The library design and module structure is described in a separate page](https://python-kasa.readthedocs.io/en/latest/design.html).
The device type specific documentation can be found in their separate pages:
* [Plugs](https://python-kasa.readthedocs.io/en/latest/smartplug.html)
* [Bulbs](https://python-kasa.readthedocs.io/en/latest/smartbulb.html)
* [Dimmers](https://python-kasa.readthedocs.io/en/latest/smartdimmer.html)
* [Power strips](https://python-kasa.readthedocs.io/en/latest/smartstrip.html)
* [Light strips](https://python-kasa.readthedocs.io/en/latest/smartlightstrip.html)
Information about the library design and the way the devices work can be found in the [topics section](https://python-kasa.readthedocs.io/en/latest/topics.html).
## Contributing

26
docs/source/codeinfo.md Normal file
View File

@ -0,0 +1,26 @@
:::{note}
The library is fully async and methods that perform IO need to be run inside an async coroutine.
Code examples assume you are following them inside `asyncio REPL`:
```
$ python -m asyncio
```
Or the code is running inside an async function:
```py
import asyncio
from kasa import Discover
async def main():
dev = await Discover.discover_single("127.0.0.1",username="un@example.com",password="pw")
await dev.turn_on()
await dev.update()
if __name__ == "__main__":
asyncio.run(main())
```
**All of your code needs to run inside the same event loop so only call `asyncio.run` once.**
*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.*
:::

View File

@ -1,44 +1,16 @@
# How-to Guides
This page contains guides of how to perform common actions using the library.
Guides of how to perform common actions using the library.
## Discover devices
```{toctree}
:maxdepth: 2
```{eval-rst}
.. automodule:: kasa.discover
:noindex:
```
## Connect without discovery
```{eval-rst}
.. automodule:: kasa.deviceconfig
:noindex:
```
## Get Energy Consumption and Usage Statistics
:::{note}
In order to use the helper methods to calculate the statistics correctly, your devices need to have correct time set.
The devices use NTP and public servers from [NTP Pool Project](https://www.ntppool.org/) to synchronize their time.
:::
### Energy Consumption
The availability of energy consumption sensors depend on the device.
While most of the bulbs support it, only specific switches (e.g., HS110) or strips (e.g., HS300) support it.
You can use {attr}`~Device.has_emeter` to check for the availability.
### Usage statistics
You can use {attr}`~Device.on_since` to query for the time the device has been turned on.
Some devices also support reporting the usage statistics on daily or monthly basis.
You can access this information using through the usage module ({class}`kasa.modules.Usage`):
```py
dev = SmartPlug("127.0.0.1")
usage = dev.modules["usage"]
print(f"Minutes on this month: {usage.usage_this_month}")
print(f"Minutes on today: {usage.usage_today}")
guides/discover
guides/connect
guides/device
guides/module
guides/feature
guides/light
guides/strip
guides/energy
```

View File

@ -0,0 +1,10 @@
(connect_target)=
# Connect without discovery
:::{include} ../codeinfo.md
:::
```{eval-rst}
.. automodule:: kasa.deviceconfig
:noindex:
```

View File

@ -0,0 +1,10 @@
(device_target)=
# Interact with devices
:::{include} ../codeinfo.md
:::
```{eval-rst}
.. automodule:: kasa.device
:noindex:
```

View File

@ -0,0 +1,11 @@
(discover_target)=
# Discover devices
:::{include} ../codeinfo.md
:::
```{eval-rst}
.. automodule:: kasa.discover
:noindex:
```

View File

@ -0,0 +1,27 @@
# Get Energy Consumption and Usage Statistics
:::{note}
In order to use the helper methods to calculate the statistics correctly, your devices need to have correct time set.
The devices use NTP (123/UDP) and public servers from [NTP Pool Project](https://www.ntppool.org/) to synchronize their time.
:::
## Energy Consumption
The availability of energy consumption sensors depend on the device.
While most of the bulbs support it, only specific switches (e.g., HS110) or strips (e.g., HS300) support it.
You can use {attr}`~Device.has_emeter` to check for the availability.
## Usage statistics
You can use {attr}`~Device.on_since` to query for the time the device has been turned on.
Some devices also support reporting the usage statistics on daily or monthly basis.
You can access this information using through the usage module ({class}`kasa.modules.Usage`):
```py
dev = SmartPlug("127.0.0.1")
usage = dev.modules["usage"]
print(f"Minutes on this month: {usage.usage_this_month}")
print(f"Minutes on today: {usage.usage_today}")
```

View File

@ -0,0 +1,10 @@
(feature_target)=
# Interact with features
:::{include} ../codeinfo.md
:::
```{eval-rst}
.. automodule:: kasa.feature
:noindex:
```

View File

@ -0,0 +1,26 @@
(light_target)=
# Interact with lights
:::{include} ../codeinfo.md
:::
```{eval-rst}
.. automodule:: kasa.interfaces.light
:noindex:
```
(lightpreset_target)=
## Presets
```{eval-rst}
.. automodule:: kasa.interfaces.lightpreset
:noindex:
```
(lighteffect_target)=
## Effects
```{eval-rst}
.. automodule:: kasa.interfaces.lighteffect
:noindex:
```

View File

@ -0,0 +1,10 @@
(module_target)=
# Interact with modules
:::{include} ../codeinfo.md
:::
```{eval-rst}
.. automodule:: kasa.module
:noindex:
```

View File

@ -0,0 +1,10 @@
(child_target)=
# Interact with child devices
:::{include} ../codeinfo.md
:::
```{eval-rst}
.. automodule:: kasa.smart.modules.childdevice
:noindex:
```

View File

@ -3,18 +3,16 @@
## Discover
```{module} kasa.discover
```{module} kasa
```
```{eval-rst}
.. autoclass:: kasa.Discover
.. autoclass:: Discover
:members:
```
## Device
```{module} kasa.device
```
```{eval-rst}
.. autoclass:: Device
@ -25,17 +23,14 @@
## Device Config
```{module} kasa.credentials
```
```{eval-rst}
.. autoclass:: Credentials
:members:
:undoc-members:
:noindex:
```
```{module} kasa.deviceconfig
```
```{eval-rst}
.. autoclass:: DeviceConfig
@ -45,19 +40,19 @@
```{eval-rst}
.. autoclass:: kasa.DeviceFamily
.. autoclass:: DeviceFamily
:members:
:undoc-members:
```
```{eval-rst}
.. autoclass:: kasa.DeviceConnection
.. autoclass:: DeviceConnectionParameters
:members:
:undoc-members:
```
```{eval-rst}
.. autoclass:: kasa.DeviceEncryption
.. autoclass:: DeviceEncryptionType
:members:
:undoc-members:
```
@ -65,7 +60,15 @@
## Modules and Features
```{eval-rst}
.. autoclass:: kasa.Module
.. autoclass:: Module
:noindex:
:members:
:inherited-members:
:undoc-members:
```
```{eval-rst}
.. autoclass:: Feature
:noindex:
:members:
:inherited-members:
@ -80,14 +83,6 @@
:undoc-members:
```
```{eval-rst}
.. autoclass:: kasa.Feature
:noindex:
:members:
:inherited-members:
:undoc-members:
```
## Protocols and transports
```{eval-rst}

View File

@ -1,5 +1,8 @@
# Getting started
:::{include} codeinfo.md
:::
```{eval-rst}
.. automodule:: tutorial
:members:

View File

@ -1,16 +1,5 @@
# 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 Discover
:func:`~kasa.Discover.discover` returns a dict[str,Device] of devices on your network:

View File

@ -1,4 +1,106 @@
"""Module for Device base class."""
"""Interact with TPLink Smart Home devices.
Once you have a device via :ref:`Discovery <discover_target>` or
:ref:`Connect <connect_target>` you can start interacting with a device.
>>> from kasa import Discover
>>>
>>> dev = await Discover.discover_single(
>>> "127.0.0.2",
>>> username="user@example.com",
>>> password="great_password"
>>> )
>>>
Most devices can be turned on and off
>>> await dev.turn_on()
>>> await dev.update()
>>> print(dev.is_on)
True
>>> await dev.turn_off()
>>> await dev.update()
>>> print(dev.is_on)
False
All devices provide several informational properties:
>>> dev.alias
Bedroom Lamp Plug
>>> dev.model
HS110(EU)
>>> dev.rssi
-71
>>> dev.mac
50:C7:BF:00:00:00
Some information can also be changed programmatically:
>>> await dev.set_alias("new alias")
>>> await dev.update()
>>> dev.alias
new alias
Devices support different functionality that are exposed via
:ref:`modules <module_target>` that you can access via :attr:`~kasa.Device.modules`:
>>> for module_name in dev.modules:
>>> print(module_name)
Energy
schedule
usage
anti_theft
time
cloud
Led
>>> led_module = dev.modules["Led"]
>>> print(led_module.led)
False
>>> await led_module.set_led(True)
>>> await dev.update()
>>> print(led_module.led)
True
Individual pieces of functionality are also exposed via :ref:`features <feature_target>`
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.
>>> for feature_name in dev.features:
>>> print(feature_name)
state
rssi
on_since
current_consumption
consumption_today
consumption_this_month
consumption_total
voltage
current
cloud_connection
led
>>> led_feature = dev.features["led"]
>>> print(led_feature.value)
True
>>> await led_feature.set_value(False)
>>> await dev.update()
>>> print(led_feature.value)
False
"""
from __future__ import annotations

View File

@ -1,4 +1,67 @@
"""Generic interface for defining device features."""
"""Interact with feature.
Features are implemented by devices to represent individual pieces of functionality like
state, time, firmware.
>>> from kasa import Discover, Module
>>>
>>> dev = await Discover.discover_single(
>>> "127.0.0.3",
>>> username="user@example.com",
>>> password="great_password"
>>> )
>>> await dev.update()
>>> print(dev.alias)
Living Room Bulb
Features allow for instrospection and can be interacted with as new features are added
to the API:
>>> for feature_id, feature in dev.features.items():
>>> print(f"{feature.name} ({feature_id}): {feature.value}")
Device ID (device_id): 0000000000000000000000000000000000000000
State (state): True
Signal Level (signal_level): 2
RSSI (rssi): -52
SSID (ssid): #MASKED_SSID#
Overheated (overheated): False
Brightness (brightness): 100
Cloud connection (cloud_connection): True
HSV (hsv): HSV(hue=0, saturation=100, value=100)
Color temperature (color_temperature): 2700
Auto update enabled (auto_update_enabled): False
Update available (update_available): False
Current firmware version (current_firmware_version): 1.1.6 Build 240130 Rel.173828
Available firmware version (available_firmware_version): 1.1.6 Build 240130 Rel.173828
Light effect (light_effect): Off
Light preset (light_preset): Not set
Smooth transition on (smooth_transition_on): 2
Smooth transition off (smooth_transition_off): 2
Time (time): 2024-02-23 02:40:15+01:00
To see whether a device supports a feature, check for the existence of it:
>>> if feature := dev.features.get("brightness"):
>>> print(feature.value)
100
You can update the value of a feature
>>> await feature.set_value(50)
>>> await dev.update()
>>> print(feature.value)
50
Features have types that can be used for introspection:
>>> feature = dev.features["light_preset"]
>>> print(feature.type)
Type.Choice
>>> print(feature.choices)
['Not set', 'Light preset 1', 'Light preset 2', 'Light preset 3',\
'Light preset 4', 'Light preset 5', 'Light preset 6', 'Light preset 7']
"""
from __future__ import annotations

View File

@ -1,4 +1,64 @@
"""Module for Device base class."""
"""Interact with a TPLink Light.
>>> from kasa import Discover, Module
>>>
>>> dev = await Discover.discover_single(
>>> "127.0.0.3",
>>> username="user@example.com",
>>> password="great_password"
>>> )
>>> await dev.update()
>>> print(dev.alias)
Living Room Bulb
Lights, like any other supported devices, can be turned on and off:
>>> print(dev.is_on)
>>> await dev.turn_on()
>>> await dev.update()
>>> print(dev.is_on)
True
Get the light module to interact:
>>> light = dev.modules[Module.Light]
You can use the ``is_``-prefixed properties to check for supported features:
>>> light.is_dimmable
True
>>> light.is_color
True
>>> light.is_variable_color_temp
True
All known bulbs support changing the brightness:
>>> light.brightness
100
>>> await light.set_brightness(50)
>>> await dev.update()
>>> light.brightness
50
Bulbs supporting color temperature can be queried for the supported range:
>>> light.valid_temperature_range
ColorTempRange(min=2500, max=6500)
>>> await light.set_color_temp(3000)
>>> await dev.update()
>>> light.color_temp
3000
Color bulbs can be adjusted by passing hue, saturation and value:
>>> await light.set_hsv(180, 100, 80)
>>> await dev.update()
>>> light.hsv
HSV(hue=180, saturation=100, value=80)
"""
from __future__ import annotations

View File

@ -1,4 +1,44 @@
"""Module for base light effect module."""
"""Interact with a TPLink Light Effect.
>>> from kasa import Discover, Module, LightState
>>>
>>> dev = await Discover.discover_single(
>>> "127.0.0.3",
>>> username="user@example.com",
>>> password="great_password"
>>> )
>>> await dev.update()
>>> print(dev.alias)
Living Room Bulb
Light effects are accessed via the LightPreset module. To list available presets
>>> if dev.modules[Module.Light].has_effects:
>>> light_effect = dev.modules[Module.LightEffect]
>>> light_effect.effect_list
['Off', 'Party', 'Relax']
To view the currently selected effect:
>>> light_effect.effect
Off
To activate a light effect:
>>> await light_effect.set_effect("Party")
>>> await dev.update()
>>> light_effect.effect
Party
If the device supports it you can set custom effects:
>>> if light_effect.has_custom_effects:
>>> effect_list = { "brightness", 50 }
>>> await light_effect.set_custom_effect(effect_list)
>>> light_effect.has_custom_effects # The device in this examples does not support \
custom effects
False
"""
from __future__ import annotations

View File

@ -1,4 +1,72 @@
"""Module for LightPreset base class."""
"""Interact with TPLink Light Presets.
>>> from kasa import Discover, Module, LightState
>>>
>>> dev = await Discover.discover_single(
>>> "127.0.0.3",
>>> username="user@example.com",
>>> password="great_password"
>>> )
>>> await dev.update()
>>> print(dev.alias)
Living Room Bulb
Light presets are accessed via the LightPreset module. To list available presets
>>> light_preset = dev.modules[Module.LightPreset]
>>> light_preset.preset_list
['Not set', 'Light preset 1', 'Light preset 2', 'Light preset 3',\
'Light preset 4', 'Light preset 5', 'Light preset 6', 'Light preset 7']
To view the currently selected preset:
>>> light_preset.preset
Not set
To view the actual light state for the presets:
>>> len(light_preset.preset_states_list)
7
>>> light_preset.preset_states_list[0]
LightState(light_on=None, brightness=50, hue=0,\
saturation=100, color_temp=2700, transition=None)
To set a preset as active:
>>> dev.modules[Module.Light].state # This is only needed to show the example working
LightState(light_on=True, brightness=100, hue=0,\
saturation=100, color_temp=2700, transition=None)
>>> await light_preset.set_preset("Light preset 1")
>>> await dev.update()
>>> light_preset.preset
Light preset 1
>>> dev.modules[Module.Light].state # This is only needed to show the example working
LightState(light_on=True, brightness=50, hue=0,\
saturation=100, color_temp=2700, transition=None)
You can save a new preset state if the device supports it:
>>> if light_preset.has_save_preset:
>>> new_preset_state = LightState(light_on=True, brightness=75, hue=0,\
saturation=100, color_temp=2700, transition=None)
>>> await light_preset.save_preset("Light preset 1", new_preset_state)
>>> await dev.update()
>>> light_preset.preset # Saving updates the preset state for the preset, it does not \
set the preset
Not set
>>> light_preset.preset_states_list[0]
LightState(light_on=None, brightness=75, hue=0,\
saturation=100, color_temp=2700, transition=None)
If you manually set the light state to a preset state it will show that preset as \
active:
>>> await dev.modules[Module.Light].set_brightness(75)
>>> await dev.update()
>>> light_preset.preset
Light preset 1
"""
from __future__ import annotations

View File

@ -1,4 +1,42 @@
"""Base class for all module implementations."""
"""Interact with modules.
Modules are implemented by devices to encapsulate sets of functionality like
Light, AutoOff, Firmware etc.
>>> from kasa import Discover, Module
>>>
>>> dev = await Discover.discover_single(
>>> "127.0.0.3",
>>> username="user@example.com",
>>> password="great_password"
>>> )
>>> await dev.update()
>>> print(dev.alias)
Living Room Bulb
To see whether a device supports functionality check for the existence of the module:
>>> if light := dev.modules.get("Light"):
>>> print(light.hsv)
HSV(hue=0, saturation=100, value=100)
If you know or expect the module to exist you can access by index:
>>> light_preset = dev.modules["LightPreset"]
>>> print(light_preset.preset_list)
['Not set', 'Light preset 1', 'Light preset 2', 'Light preset 3',\
'Light preset 4', 'Light preset 5', 'Light preset 6', 'Light preset 7']
Modules support typing via the Module names in Module:
>>> from typing_extensions import reveal_type, TYPE_CHECKING
>>> light_effect = dev.modules.get("LightEffect")
>>> light_effect_typed = dev.modules.get(Module.LightEffect)
>>> if TYPE_CHECKING:
>>> reveal_type(light_effect) # Static checker will reveal: str
>>> reveal_type(light_effect_typed) # Static checker will reveal: LightEffect
"""
from __future__ import annotations

View File

@ -1,4 +1,42 @@
"""Implementation for child devices."""
"""Interact with child devices.
>>> from kasa import Discover
>>>
>>> dev = await Discover.discover_single(
>>> "127.0.0.1",
>>> username="user@example.com",
>>> password="great_password"
>>> )
>>> await dev.update()
>>> print(dev.alias)
Bedroom Power Strip
All methods act on the whole strip:
>>> for plug in dev.children:
>>> print(f"{plug.alias}: {plug.is_on}")
Plug 1: True
Plug 2: False
Plug 3: False
>>> dev.is_on
True
>>> await dev.turn_off()
>>> await dev.update()
Accessing individual plugs can be done using the `children` property:
>>> len(dev.children)
3
>>> for plug in dev.children:
>>> print(f"{plug.alias}: {plug.is_on}")
Plug 1: False
Plug 2: False
Plug 3: False
>>> await dev.children[1].turn_on()
>>> await dev.update()
>>> dev.is_on
True
"""
from ..smartmodule import SmartModule

View File

@ -69,6 +69,7 @@ def test_discovery_examples(readmes_mock):
"""Test discovery examples."""
res = xdoctest.doctest_module("kasa.discover", "all")
assert res["n_passed"] > 0
assert res["n_warned"] == 0
assert not res["failed"]
@ -76,6 +77,63 @@ def test_deviceconfig_examples(readmes_mock):
"""Test discovery examples."""
res = xdoctest.doctest_module("kasa.deviceconfig", "all")
assert res["n_passed"] > 0
assert res["n_warned"] == 0
assert not res["failed"]
def test_device_examples(readmes_mock):
"""Test device examples."""
res = xdoctest.doctest_module("kasa.device", "all")
assert res["n_passed"] > 0
assert res["n_warned"] == 0
assert not res["failed"]
def test_light_examples(readmes_mock):
"""Test device examples."""
res = xdoctest.doctest_module("kasa.interfaces.light", "all")
assert res["n_passed"] > 0
assert res["n_warned"] == 0
assert not res["failed"]
def test_light_preset_examples(readmes_mock):
"""Test device examples."""
res = xdoctest.doctest_module("kasa.interfaces.lightpreset", "all")
assert res["n_passed"] > 0
assert res["n_warned"] == 0
assert not res["failed"]
def test_light_effect_examples(readmes_mock):
"""Test device examples."""
res = xdoctest.doctest_module("kasa.interfaces.lighteffect", "all")
assert res["n_passed"] > 0
assert res["n_warned"] == 0
assert not res["failed"]
def test_child_examples(readmes_mock):
"""Test device examples."""
res = xdoctest.doctest_module("kasa.smart.modules.childdevice", "all")
assert res["n_passed"] > 0
assert res["n_warned"] == 0
assert not res["failed"]
def test_module_examples(readmes_mock):
"""Test device examples."""
res = xdoctest.doctest_module("kasa.module", "all")
assert res["n_passed"] > 0
assert res["n_warned"] == 0
assert not res["failed"]
def test_feature_examples(readmes_mock):
"""Test device examples."""
res = xdoctest.doctest_module("kasa.feature", "all")
assert res["n_passed"] > 0
assert res["n_warned"] == 0
assert not res["failed"]
@ -83,6 +141,7 @@ def test_tutorial_examples(readmes_mock):
"""Test discovery examples."""
res = xdoctest.doctest_module("docs/tutorial.py", "all")
assert res["n_passed"] > 0
assert res["n_warned"] == 0
assert not res["failed"]