Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

labgrid/driver/power: Add Tapo(Kasa) smart plug support #1555

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
7 changes: 7 additions & 0 deletions doc/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,13 @@ Currently available are:
<https://github.com/labgrid-project/labgrid/blob/master/labgrid/driver/power/simplerest.py>`__
for details.

``tapoplug``
Controls *Tapo Smart Plugs* via `python-kasa
<https://github.com/python-kasa/python-kasa>`_.
Requires *KASA_USERNAME* and *KASA_USERNAME* environment variables to be set.
Tested on *Tapo P100* smart plug.
See the `list of supported TAPO devices <https://python-kasa.readthedocs.io/en/stable/SUPPORTED.html#tapo-devices>`_.

``tplink``
Controls *TP-Link power strips* via `python-kasa
<https://github.com/python-kasa/python-kasa>`_.
Expand Down
77 changes: 77 additions & 0 deletions labgrid/driver/power/tapoplug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""Tested with TAPO P100, and should be compatible with any TAPO smart plug supported by kasa

There is a list of supported devices in python-kasa package official documentation.
https://python-kasa.readthedocs.io/en/stable/SUPPORTED.html#tapo-devices

"""

import asyncio
import os

from kasa import (
Credentials,
DeviceConfig,
DeviceConnectionParameters,
DeviceEncryptionType,
DeviceFamily,
SmartProtocol,
smart,
transports,
)

connection_type = DeviceConnectionParameters(
device_family=DeviceFamily.SmartTapoPlug, encryption_type=DeviceEncryptionType.Klap, login_version=2
)


def get_credentials():
username = os.environ.get("KASA_USERNAME")
password = os.environ.get("KASA_PASSWORD")
if username is None or password is None:
raise ValueError(
"Username and password cannot be None.Set KASA_USERNAME and KASA_PASSWORD environment variables"
)
return Credentials(username=username, password=password)


async def _power_set(host, port, index, value):
assert port is None
config = DeviceConfig(host=host, credentials=get_credentials(), connection_type=connection_type)
protocol = SmartProtocol(transport=transports.KlapTransportV2(config=config))

device = smart.SmartDevice(host=host, config=config, protocol=protocol)

try:
await device.update()

if value:
await device.turn_on()
else:
await device.turn_off()
except Exception as e:
print(f"An error occurred while setting power: {e}")
finally:
await device.disconnect()


def power_set(host, port, index, value):
asyncio.run(_power_set(host, port, index, value))


async def _power_get(host, port, index):
assert port is None
config = DeviceConfig(host=host, credentials=get_credentials(), connection_type=connection_type)
protocol = SmartProtocol(transport=transports.KlapTransportV2(config=config))

device = smart.SmartDevice(host=host, config=config, protocol=protocol)
try:
await device.update()
return device.is_on
except Exception as e:
print(f"An error occurred while getting power: {e}")
finally:
await device.disconnect()


def power_get(host, port, index):
return asyncio.run(_power_get(host, port, index))
2 changes: 1 addition & 1 deletion labgrid/resource/power.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class NetworkPowerPort(Resource):
"""
model = attr.ib(validator=attr.validators.instance_of(str))
host = attr.ib(validator=attr.validators.instance_of(str))
index = attr.ib(validator=attr.validators.instance_of(str),
index = attr.ib(default='0',validator=attr.validators.instance_of(str),
converter=lambda x: str(int(x)))


Expand Down
4 changes: 4 additions & 0 deletions tests/test_powerdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ def test_import_backend_tplink(self):
pytest.importorskip("kasa")
import labgrid.driver.power.tplink

def test_import_backend_tapoplug(self):
pytest.importorskip("kasa")
import labgrid.driver.power.tapoplug

def test_import_backend_siglent(self):
pytest.importorskip("vxi11")
import labgrid.driver.power.siglent
Expand Down