Skip to content

Commit

Permalink
Merge pull request #28 from gilesknap/kasa-merge
Browse files Browse the repository at this point in the history
Restore kasa support from @lbunge
  • Loading branch information
gilesknap committed Jun 8, 2024
2 parents 92647c2 + 8457400 commit fa873b9
Show file tree
Hide file tree
Showing 10 changed files with 286 additions and 37 deletions.
103 changes: 69 additions & 34 deletions docs/explanations/devicetypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,48 @@ Supported Types of Device Control
Examples
--------

This project has been tested against 3 power control devices. These specific
examples have their own documentation pages as follows:
This project has been tested against several power control devices. These specific examples have their own documentation pages as follows:

- `TP Link Tapo Smart Plug<tapo>` - Command line example
- `TP-Link Tapo Smart Plug<tapo>` - Command line example
- `UUGear MEGA4 USB Hub<mega4>` - SmartThings example
- `Netgear GS803EP PoE 8 port switch<netgear>` - Web GUI example
- `Cisco IOS PoE Switches<ciscoiospoehowto>` - Cisco IOS PoE switch example
- `TP-Link Kasa Smart Plug<kasa>` - Python-Kasa API example

Kasa Device Control
-------------------

:example:
`TP-Link Kasa Smart Plug<kasa>`

Kasa devices are controlled using the Python-Kasa library which interfaces
with TP-Link Kasa smart devices over the network. This method involves
asynchronous communication with devices to execute actions like turning
on or off and querying their current state.

An example configuration for controlling a Kasa smart plug is shown below:

:example yaml:

.. code-block:: yaml
- type: KasaDevice
name: server01_plug
ip_address: 192.168.1.100
on: "python -m kasa --host 192.168.1.100 on"
off: "python -m kasa --host 192.168.1.100 off"
query: "python -m kasa --host 192.168.1.100 state"
This configuration uses the Python-Kasa API to communicate with the plug.
The 'ip_address' should be replaced with the IP address of your Kasa device.
The 'on', 'off', and 'query' commands are examples of how you might invoke
device control through command line interfacing with the Python-Kasa library.

.. note::
Ensure that the 'python-kasa' library is installed in the environment
where your server is running, as it is required for communication
with Kasa devices.



Expand All @@ -20,13 +55,13 @@ Command Line Control
:example:
`TP Link Tapo Smart Plug<tapo>`

The simplest form of control is to execute a command line utility. Clearly
The simplest form of control is to execute a command line utility. Clearly
the command tool needs to be installed on the machine running the webhook
server, the server account needs permission to run it and the tool must
be able to contact the device from the server.

A common example for RPI power is a switchable USB hub. Most of these can be
controlled by the uhubctl command line tool.
controlled by the uhubctl command line tool.
(see https://github.com/mvp/uhubctl).

Using uhubctl implies that the hub in question is plugged into a USB port
Expand All @@ -53,7 +88,7 @@ SmartThings Control

SmartThings are IoT devices
which are controlled using a SmartThing api token and SmartThing device
ID. Once you have set up a SmartThing and tested it via the associated
ID. Once you have set up a SmartThing and tested it via the associated
App you will be able to discover your device ID via the App. You can get
your api token by logging in here: https://account.smartthings.com/login.

Expand All @@ -64,19 +99,19 @@ supply the IP of a single NIC or for best security use 127.0.0.1, meaning
that only processes on the same machine will have access (in this cases
you would run the webhook server on the maas rack server).

SmartThing commands for switch devices will usually take the exact same form
as in the above example. Command line devices will just take a command line
SmartThing commands for switch devices will usually take the exact same form
as in the above example. Command line devices will just take a command line
to execute in the shell.

In all cases the response from the query command is passed through a regex search
and will return 'on' if the the response matches query_on_regex and 'off' it
matches query_off_regex. Note that the defaults for these regex are 'on' and
'off' which is what SmartThing devices will return in the switch
status field by default. This is why the nuc1 example is not required to
'off' which is what SmartThing devices will return in the switch
status field by default. This is why the nuc1 example is not required to
specify query_on_regex, query_off_regex.

Note that the query response which goes to MAAS is converted to
MAAS default values and hence no configuration of regex in
Note that the query response which goes to MAAS is converted to
MAAS default values and hence no configuration of regex in
MAAS itself are required.

example yaml:
Expand All @@ -99,7 +134,7 @@ Web GUI Control
`Netgear GS803EP PoE 8 port switch<netgear>`

For those power control devices that are configured by a web UI only, I provide
a Web Scraping device type. This uses the python selenium library to
a Web Scraping device type. This uses the python selenium library to
click buttons and send text to forms on any Web UI. Defining the correct
sequence should allow you to login to any Web GUI and control it.

Expand All @@ -109,7 +144,7 @@ Per Control Device Configuration

For this device type there are two types of entry. First is the global entry
where you configure how to connect/disconnect to/from each device. (i.e.
if you had 2 PoE switches you would have 2 of these)
if you had 2 PoE switches you would have 2 of these)

example WebGui yaml:

Expand All @@ -128,24 +163,24 @@ example WebGui yaml:
click/cls/icon-logout
click/id/modal_footer_button_primary
You will need the chromedriver for selenium for this to work. Download from
You will need the chromedriver for selenium for this to work. Download from
here (pick one that matches your Chrome version):

- https://chromedriver.storage.googleapis.com/index.html

The configuration 'driver:' needs to point at the downloaded file. 'timeout:'
should be the max time taken for any transitions in the click sequences you
specify.
should be the max time taken for any transitions in the click sequences you
specify.

'login:' and 'logout': specify the sequence of clicks to perform these two
'login:' and 'logout': specify the sequence of clicks to perform these two
tasks.

The configuration strings are '/' separated fields as follows:

Commands
@@@@@@@@

:click/...
:click/...
clicks on the specified HTML Element

send/.../text_to_send
Expand All @@ -154,10 +189,10 @@ send/.../text_to_send
sendcr/.../text_to_send
sends text plus carriage return to a specified HTML Element

get/...
get/...
gets the text from a specified HTML Element

delay/n
delay/n
pauses for n seconds (floating point supported)

Field Identifiers
Expand Down Expand Up @@ -186,11 +221,11 @@ NOTE: in all cases if more than one field is matched you can index it like this:
Where n is the 0 based index into the list of fields.

NOTE: in all cases the code will wait for the specified field to be seen
if it cannot immediately be seen.
if it cannot immediately be seen.
'timeout' specifies the maximum wait time before an error.

When the web scraper detects an error it will execute the logout script
(ignoring errors) followed by the login script and try again. This has
(ignoring errors) followed by the login script and try again. This has
been shown to successfully recover on the GS308EP when it has timed out
due to inactivity and gone back to the login screen.

Expand All @@ -200,16 +235,16 @@ Per Control Device Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For each computer that you want to power control you will need an additional
entry like the following example. On, Off and Query use the same syntax as
described above.
entry like the following example. On, Off and Query use the same syntax as
described above.

Note that the name has the entry above's name followed by a computer name
separated by '-' this is important as it associates this entry with the
correct Web Device. Only one instance of the selenium driver is loaded
Note that the name has the entry above's name followed by a computer name
separated by '-' this is important as it associates this entry with the
correct Web Device. Only one instance of the selenium driver is loaded
per web device and can control any number of target PCs.

The _regex parameters are regular expressions which will try to match
the text returned from the final get in the query sequence. If there is
the text returned from the final get in the query sequence. If there is
a match it will return on or off to MAAS, or an error if there is none.

example WebDevice yaml:
Expand Down Expand Up @@ -241,19 +276,19 @@ Working with another Web GUI
----------------------------

This has been tested with the Netgear GS3008EP. I have tried to make a generic
DSL that allows for most possible sequences of Web Element interactions, but
YMMV.
DSL that allows for most possible sequences of Web Element interactions, but
YMMV.

To experiment with the approach and develop your own command sequences for
this device type see the python script here:
this device type see the python script here:
https://github.com/gilesknap/maaspower/blob/main/utils/webuitest.py
You can launch this script interactively with iPython and experiment with
your device to get the right sequence of commands to turn devices on/off and
query their state. See the comments in the file for details.

If you are brave enough to create your own config for a new device, please
report any problems here https://github.com/gilesknap/maaspower/issues. Also
post any working configurations too (you can do a PR to the docs or
post any working configurations too (you can do a PR to the docs or
report in issues).

.. _ciscoiospoeconfig:
Expand Down Expand Up @@ -291,7 +326,7 @@ is shown below:
#enable_password: "123qwe"
#port_poe_watts: 30
The above example pertains to a Raspberry Pi 4B, labeled '**rpi_4b_1**'' (with
The above example pertains to a Raspberry Pi 4B, labeled '**rpi_4b_1**'' (with
PoE HAT), connected to a Cisco Catalyst 2960X PoE switch at switchport
'**gigabitEthernet 1/0/22**'. The Cisco switch hosts SSH accessible at IP
'**10.0.0.1**'. Maaspower will establish an SSH connection to the switch
Expand Down
83 changes: 83 additions & 0 deletions docs/how-to/kasasmartplug.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
.. _kasa:

TP Link Kasa Smart Plug
=======================

MaasPower is compatible with TP-Link Kasa Smart Plugs, which provide remote switchable
power sockets that can be used to control power switching on any machine.

.. image:: ../images/kasa.png
:width: 400
:alt: TP-Link Kasa Smart Plug

Setting Up the Kasa Smart Plug
------------------------------

To use a Kasa Smart Plug, follow these steps to set it up and integrate it with MaasPower:

1. **Installation and Registration:**

- Install the Kasa Smart App on your smartphone.
- Create an account and follow the app instructions to connect your smart plug to your WiFi network.

2. **Device Control Setup:**

- Once the device is set up in the Kasa app, you can control it directly via the app.
However, to control it through MaasPower, you will need to interact with it via the python-kasa library.

3. **Installing python-kasa:**

- Ensure your system where MaasPower is installed has Python installed. MAASPower will install this
as part of it's dependendencies.
- If you prefer to install this yourself, you can install the python-kasa library using pip:

.. code-block:: bash
pip install python-kasa
4. **Discovering Your Device:**

- To discover your Kasa devices and get their IP addresses, you can use the 'discover' command from python-kasa:

.. code-block:: bash
python -m kasa discover
5. **Configure MaasPower:**

- Add a new device entry for each of your Kasa Smart Plugs in the MaasPower configuration file.
You need the IP address found in the previous step.

.. code-block:: yaml
- type: KasaDevice # Device Type, needs to be KasaDevice
name: server01_plug # Friendly name, will be used in MAAS webhook url
ip_address: 192.168.x.xxx # Whatever the ip address is of your smart plug
Testing Your Setup
------------------

Once you have configured your Kasa Smart Plug in MaasPower, you can test controlling the device
through a simple command line request. For example:

.. code-block:: bash
curl --user username:password http://your.server.ip:port/maaspower/office-plug/query
This command should return the current power state of the smart plug, verifying that your setup is correct and operational.

Note: Replace 'username:password', 'your.server.ip', and 'port' with your actual MaasPower server credentials and address.

Documentation and Support
-------------------------

For more information on configuring and troubleshooting TP-Link Kasa devices with python-kasa, refer to the official python-kasa documentation:
https://python-kasa.readthedocs.io/

If you encounter issues or have questions regarding the setup with MaasPower, consider visiting the MaasPower GitHub repository or contacting support through the project's channels.

Known Issues
-------------------------
As of this writing, there are known errors with the implementation of the kasa smartdevice.py package in the python-kasa project.
This does not affect the actual control of the device like turning it on and off as well as the query function. However, the devices
won't auto update their power status when turning it on or off. Simply query the power state after toggling the power to update it.
Binary file added docs/images/kasa.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dependencies = [
"importlib_resources",
"selenium",
"netmiko==4.3.0",
"python-kasa>=0.4.0",
] # Add project dependencies here, e.g. ["click", "numpy"]
dynamic = ["version"]
license.file = "LICENSE"
Expand Down
2 changes: 2 additions & 0 deletions src/maaspower/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

# import all subclasses of SwitchDevice so ApiSchema sees them
from .devices.cisco_ios_poe_switch import CiscoIOSPOESwitch
from .devices.kasa_device import KasaDevice
from .devices.shell_cmd import CommandLine
from .devices.smart_thing import SmartThing
from .devices.web_device import WebDevice
Expand All @@ -25,6 +26,7 @@
WebGui,
WebDevice,
CiscoIOSPOESwitch,
KasaDevice,
]

cli = typer.Typer()
Expand Down
9 changes: 8 additions & 1 deletion src/maaspower/devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,11 @@

# __all__ defines the public API for the package.
# Each module also defines its own __all__.
__all__ = ["shell_cmd", "smart_thing", "web_ui", "web_device", "cisco_ios_poe_switch"]
__all__ = [
"shell_cmd",
"smart_thing",
"web_ui",
"web_device",
"cisco_ios_poe_switch",
"kasa_device",
]
Loading

0 comments on commit fa873b9

Please sign in to comment.