Various documentation updates (#333)

* Add a note about socket sharing

* Show inherited members for apidocs

* Remove outdated note of emeters not being supported on smartstrips

* Describe emeter and usage modules, add note about NTP for time sync

* Describe lib design and modules

* Bump sphinx version, ignore d001 (line-length) for doc8

* demote energy & usage to 3rd level, promote api for 2nd
This commit is contained in:
Teemu R 2022-04-06 01:13:27 +02:00 committed by GitHub
parent 68038c93df
commit d848117384
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 140 additions and 28 deletions

View File

@ -37,3 +37,9 @@ repos:
hooks:
- id: mypy
additional_dependencies: [types-click]
- repo: https://github.com/PyCQA/doc8
rev: '0.11.1'
hooks:
- id: doc8
additional_dependencies: [tomli]

50
docs/source/design.rst Normal file
View File

@ -0,0 +1,50 @@
.. py:module:: kasa.modules
.. _library_design:
Library Design & Modules
========================
This page aims to provide some details on the design and internals of this library.
You might be interested in this if you want to improve this library,
or if you are just looking to access some information that is not currently exposed.
.. _update_cycle:
Update Cycle
************
When :meth:`~kasa.SmartDevice.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.
The returned data is cached internally to avoid I/O on property accesses.
All properties defined both in the device class and in the module classes follow this principle.
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.
.. _modules:
Modules
*******
The functionality provided by all :class:`~kasa.SmartDevice` 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`.
.. 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`.
API documentation for modules
*****************************
.. automodule:: kasa.modules
:members:
:inherited-members:
:undoc-members:

View File

@ -8,6 +8,7 @@
cli
discover
smartdevice
design
smartbulb
smartplug
smartdimmer

View File

@ -56,4 +56,5 @@ API documentation
.. autoclass:: kasa.SmartBulb
:members:
:inherited-members:
:undoc-members:

View File

@ -1,12 +1,20 @@
.. py:module:: kasa
Common API
======================
==========
The basic functionalities of all supported devices are accessible using the common :class:`SmartDevice` base class.
The property accesses use the data obtained before by awaiting :func:`update()`.
The property accesses use the data obtained before by awaiting :func:`SmartDevice.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.
Methods changing the state of the device do not invalidate the cache (i.e., there is no implicit `update()`).
.. note::
The device instances share the communication socket in background to optimize I/O accesses.
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).
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.
@ -22,28 +30,70 @@ Simple example script showing some functionality:
async def main():
p = SmartPlug("127.0.0.1")
await p.update()
print(p.alias)
await p.update() # Request the update
print(p.alias) # Print out the alias
print(p.emeter_realtime) # Print out current emeter status
await p.turn_off()
await p.turn_off() # Turn the device off
if __name__ == "__main__":
asyncio.run(main())
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
import asyncio
from kasa import SmartPlug
async def main():
dev = SmartPlug("127.0.0.1") # We create the instance inside the main loop
while True:
await dev.update() # Request an update
print(dev.emeter_realtime)
await asyncio.sleep(0.5) # Sleep some time between updates
if __name__ == "__main__":
asyncio.run(main())
Refer to device type specific classes for more examples:
:class:`SmartPlug`, :class:`SmartBulb`, :class:`SmartStrip`,
:class:`SmartDimmer`, :class:`SmartLightStrip`.
* :class:`SmartPlug`
* :class:`SmartBulb`
* :class:`SmartStrip`
* :class:`SmartDimmer`
* :class:`SmartLightStrip`
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:`~SmartDevice.has_emeter` to check for the availability.
Usage statistics
~~~~~~~~~~~~~~~~
You can use :attr:`~SmartDevice.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`):
.. code-block:: python
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}")
API documentation
~~~~~~~~~~~~~~~~~
*****************
.. autoclass:: kasa.SmartDevice
.. autoclass:: SmartDevice
:members:
:undoc-members:

View File

@ -10,4 +10,5 @@ API documentation
.. autoclass:: kasa.SmartDimmer
:members:
:inherited-members:
:undoc-members:

View File

@ -10,4 +10,5 @@ API documentation
.. autoclass:: kasa.SmartLightStrip
:members:
:inherited-members:
:undoc-members:

View File

@ -11,4 +11,5 @@ API documentation
.. autoclass:: kasa.SmartPlug
:members:
:inherited-members:
:undoc-members:

View File

@ -1,11 +1,6 @@
Smart strips
============
.. note::
The emeter feature is currently not implemented for smart strips. See https://github.com/python-kasa/python-kasa/issues/64 for details.
.. note::
Feel free to open a pull request to improve the documentation!
@ -34,4 +29,5 @@ API documentation
.. autoclass:: kasa.SmartStrip
:members:
:inherited-members:
:undoc-members:

19
poetry.lock generated
View File

@ -497,18 +497,19 @@ python-versions = "*"
[[package]]
name = "sphinx"
version = "3.5.4"
version = "4.5.0"
description = "Python documentation generator"
category = "main"
optional = true
python-versions = ">=3.5"
python-versions = ">=3.6"
[package.dependencies]
alabaster = ">=0.7,<0.8"
babel = ">=1.3"
colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""}
docutils = ">=0.12,<0.17"
docutils = ">=0.14,<0.18"
imagesize = "*"
importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
Jinja2 = ">=2.3"
packaging = "*"
Pygments = ">=2.0"
@ -516,14 +517,14 @@ requests = ">=2.5.0"
snowballstemmer = ">=1.1"
sphinxcontrib-applehelp = "*"
sphinxcontrib-devhelp = "*"
sphinxcontrib-htmlhelp = "*"
sphinxcontrib-htmlhelp = ">=2.0.0"
sphinxcontrib-jsmath = "*"
sphinxcontrib-qthelp = "*"
sphinxcontrib-serializinghtml = "*"
sphinxcontrib-serializinghtml = ">=1.1.5"
[package.extras]
docs = ["sphinxcontrib-websupport"]
lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"]
lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "docutils-stubs", "types-typed-ast", "types-requests"]
test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"]
[[package]]
@ -754,7 +755,7 @@ docs = ["sphinx", "sphinx_rtd_theme", "m2r", "mistune", "sphinxcontrib-programou
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "cbc8eb721e3b498c25eef73c95b2aa309419fa075b878c18cac0b148113c25f9"
content-hash = "6577513a016c329bc825369761eae9971cb6a18a13c96ac0669c1f51ab3de87d"
[metadata.files]
alabaster = [
@ -1074,8 +1075,8 @@ snowballstemmer = [
{file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
]
sphinx = [
{file = "Sphinx-3.5.4-py3-none-any.whl", hash = "sha256:2320d4e994a191f4b4be27da514e46b3d6b420f2ff895d064f52415d342461e8"},
{file = "Sphinx-3.5.4.tar.gz", hash = "sha256:19010b7b9fa0dc7756a6e105b2aacd3a80f798af3c25c273be64d7beeb482cb1"},
{file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"},
{file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"},
]
sphinx-rtd-theme = [
{file = "sphinx_rtd_theme-0.5.2-py2.py3-none-any.whl", hash = "sha256:4a05bdbe8b1446d77a01e20a23ebc6777c74f43237035e76be89699308987d6f"},

View File

@ -22,7 +22,7 @@ asyncclick = ">=8"
pydantic = "^1"
# required only for docs
sphinx = { version = "^3", optional = true }
sphinx = { version = "^4", optional = true }
m2r = { version = "^0", optional = true }
mistune = { version = "<2.0.0", optional = true }
sphinx_rtd_theme = { version = "^0", optional = true }
@ -81,6 +81,10 @@ markers = [
"requires_dummy: test requires dummy data to pass, skipped on real devices",
]
[tool.doc8]
paths = ["docs"]
ignore = ["D001"]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"