From 0d84d8785e0e4f545dae5bbe8e6cf05c2f767ff2 Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Wed, 19 Jun 2024 09:53:40 +0100 Subject: [PATCH] Update docs with more howto examples (#968) Co-authored-by: Teemu R. --- README.md | 11 +-- docs/source/codeinfo.md | 26 ++++++++ docs/source/guides.md | 50 +++----------- docs/source/guides/connect.md | 10 +++ docs/source/guides/device.md | 10 +++ docs/source/guides/discover.md | 11 +++ docs/source/guides/energy.md | 27 ++++++++ docs/source/guides/feature.md | 10 +++ docs/source/guides/light.md | 26 ++++++++ docs/source/guides/module.md | 10 +++ docs/source/guides/strip.md | 10 +++ docs/source/reference.md | 35 +++++----- docs/source/tutorial.md | 3 + docs/tutorial.py | 11 --- kasa/device.py | 104 ++++++++++++++++++++++++++++- kasa/feature.py | 65 +++++++++++++++++- kasa/interfaces/light.py | 62 ++++++++++++++++- kasa/interfaces/lighteffect.py | 42 +++++++++++- kasa/interfaces/lightpreset.py | 70 ++++++++++++++++++- kasa/module.py | 40 ++++++++++- kasa/smart/modules/childdevice.py | 40 ++++++++++- kasa/tests/test_readme_examples.py | 59 ++++++++++++++++ 22 files changed, 646 insertions(+), 86 deletions(-) create mode 100644 docs/source/codeinfo.md create mode 100644 docs/source/guides/connect.md create mode 100644 docs/source/guides/device.md create mode 100644 docs/source/guides/discover.md create mode 100644 docs/source/guides/energy.md create mode 100644 docs/source/guides/feature.md create mode 100644 docs/source/guides/light.md create mode 100644 docs/source/guides/module.md create mode 100644 docs/source/guides/strip.md diff --git a/README.md b/README.md index 78cddac7..1ef24953 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/source/codeinfo.md b/docs/source/codeinfo.md new file mode 100644 index 00000000..3ee91b36 --- /dev/null +++ b/docs/source/codeinfo.md @@ -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.* +::: diff --git a/docs/source/guides.md b/docs/source/guides.md index f45412d1..75b1424b 100644 --- a/docs/source/guides.md +++ b/docs/source/guides.md @@ -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 ``` diff --git a/docs/source/guides/connect.md b/docs/source/guides/connect.md new file mode 100644 index 00000000..9336a1c1 --- /dev/null +++ b/docs/source/guides/connect.md @@ -0,0 +1,10 @@ +(connect_target)= +# Connect without discovery + +:::{include} ../codeinfo.md +::: + +```{eval-rst} +.. automodule:: kasa.deviceconfig + :noindex: +``` diff --git a/docs/source/guides/device.md b/docs/source/guides/device.md new file mode 100644 index 00000000..c2fbfb74 --- /dev/null +++ b/docs/source/guides/device.md @@ -0,0 +1,10 @@ +(device_target)= +# Interact with devices + +:::{include} ../codeinfo.md +::: + +```{eval-rst} +.. automodule:: kasa.device + :noindex: +``` diff --git a/docs/source/guides/discover.md b/docs/source/guides/discover.md new file mode 100644 index 00000000..2d50c4c6 --- /dev/null +++ b/docs/source/guides/discover.md @@ -0,0 +1,11 @@ +(discover_target)= +# Discover devices + +:::{include} ../codeinfo.md +::: + + +```{eval-rst} +.. automodule:: kasa.discover + :noindex: +``` diff --git a/docs/source/guides/energy.md b/docs/source/guides/energy.md new file mode 100644 index 00000000..d7b5727c --- /dev/null +++ b/docs/source/guides/energy.md @@ -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}") +``` diff --git a/docs/source/guides/feature.md b/docs/source/guides/feature.md new file mode 100644 index 00000000..307f52a6 --- /dev/null +++ b/docs/source/guides/feature.md @@ -0,0 +1,10 @@ +(feature_target)= +# Interact with features + +:::{include} ../codeinfo.md +::: + +```{eval-rst} +.. automodule:: kasa.feature + :noindex: +``` diff --git a/docs/source/guides/light.md b/docs/source/guides/light.md new file mode 100644 index 00000000..c8b72a99 --- /dev/null +++ b/docs/source/guides/light.md @@ -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: +``` diff --git a/docs/source/guides/module.md b/docs/source/guides/module.md new file mode 100644 index 00000000..a001cf50 --- /dev/null +++ b/docs/source/guides/module.md @@ -0,0 +1,10 @@ +(module_target)= +# Interact with modules + +:::{include} ../codeinfo.md +::: + +```{eval-rst} +.. automodule:: kasa.module + :noindex: +``` diff --git a/docs/source/guides/strip.md b/docs/source/guides/strip.md new file mode 100644 index 00000000..d1377eab --- /dev/null +++ b/docs/source/guides/strip.md @@ -0,0 +1,10 @@ +(child_target)= +# Interact with child devices + +:::{include} ../codeinfo.md +::: + +```{eval-rst} +.. automodule:: kasa.smart.modules.childdevice + :noindex: +``` diff --git a/docs/source/reference.md b/docs/source/reference.md index ffbfab47..c1bc4662 100644 --- a/docs/source/reference.md +++ b/docs/source/reference.md @@ -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} diff --git a/docs/source/tutorial.md b/docs/source/tutorial.md index ee704289..30944dd5 100644 --- a/docs/source/tutorial.md +++ b/docs/source/tutorial.md @@ -1,5 +1,8 @@ # Getting started +:::{include} codeinfo.md +::: + ```{eval-rst} .. automodule:: tutorial :members: diff --git a/docs/tutorial.py b/docs/tutorial.py index f963ac42..5dc768c7 100644 --- a/docs/tutorial.py +++ b/docs/tutorial.py @@ -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: diff --git a/kasa/device.py b/kasa/device.py index 53b71d85..dde2e97e 100644 --- a/kasa/device.py +++ b/kasa/device.py @@ -1,4 +1,106 @@ -"""Module for Device base class.""" +"""Interact with TPLink Smart Home devices. + +Once you have a device via :ref:`Discovery ` or +:ref:`Connect ` 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 ` 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 ` +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 diff --git a/kasa/feature.py b/kasa/feature.py index b992789a..d0c83a3d 100644 --- a/kasa/feature.py +++ b/kasa/feature.py @@ -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 diff --git a/kasa/interfaces/light.py b/kasa/interfaces/light.py index 207014ca..5d206d1a 100644 --- a/kasa/interfaces/light.py +++ b/kasa/interfaces/light.py @@ -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 diff --git a/kasa/interfaces/lighteffect.py b/kasa/interfaces/lighteffect.py index 0eb11b5b..e4efa2c2 100644 --- a/kasa/interfaces/lighteffect.py +++ b/kasa/interfaces/lighteffect.py @@ -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 diff --git a/kasa/interfaces/lightpreset.py b/kasa/interfaces/lightpreset.py index 84a374db..95f02946 100644 --- a/kasa/interfaces/lightpreset.py +++ b/kasa/interfaces/lightpreset.py @@ -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 diff --git a/kasa/module.py b/kasa/module.py index 177c2baa..3a090782 100644 --- a/kasa/module.py +++ b/kasa/module.py @@ -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 diff --git a/kasa/smart/modules/childdevice.py b/kasa/smart/modules/childdevice.py index 5713eff4..4c3b99de 100644 --- a/kasa/smart/modules/childdevice.py +++ b/kasa/smart/modules/childdevice.py @@ -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 diff --git a/kasa/tests/test_readme_examples.py b/kasa/tests/test_readme_examples.py index 7a5f8e19..f024c672 100644 --- a/kasa/tests/test_readme_examples.py +++ b/kasa/tests/test_readme_examples.py @@ -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"]