Giter VIP home page Giter VIP logo

python-kasa's Introduction

python-kasa

PyPI version Build Status codecov Documentation Status

python-kasa is a Python library to control TPLink's smart home devices (plugs, wall switches, power strips, and bulbs).

This is a voluntary, community-driven effort and is not affiliated, sponsored, or endorsed by TPLink.

Contributions in any form (adding missing features, reporting issues, fixing or triaging existing ones, improving the documentation, or device donations) are more than welcome!


Getting started

You can install the most recent release using pip:

pip install python-kasa

For enhanced cli tool support (coloring, embedded shell) install with [shell]:

pip install python-kasa[shell]

If you are using cpython, it is recommended to install with [speedups] to enable orjson (faster json support):

pip install python-kasa[speedups]

or for both:

pip install python-kasa[speedups, shell]

With [speedups], the protocol overhead is roughly an order of magnitude lower (benchmarks available in devtools).

Alternatively, you can clone this repository and use poetry to install the development version:

git clone https://github.com/python-kasa/python-kasa.git
cd python-kasa/
poetry install

If you have not yet provisioned your device, you can do so using the cli tool.

Discovering devices

Running kasa discover will send discovery packets to the default broadcast address (255.255.255.255) to discover supported devices. If your system has multiple network interfaces, you can specify the broadcast address using the --target option.

The discover command will automatically execute the state command on all the discovered devices:

$ kasa discover
Discovering devices on 255.255.255.255 for 3 seconds

== Bulb McBulby - KL130(EU) ==
        Host: 192.168.xx.xx
        Port: 9999
        Device state: True
        == Generic information ==
        Time:         2023-12-05 14:33:23 (tz: {'index': 6, 'err_code': 0}
        Hardware:     1.0
        Software:     1.8.8 Build 190613 Rel.123436
        MAC (rssi):   1c:3b:f3:xx:xx:xx (-56)
        Location:     {'latitude': None, 'longitude': None}

        == Device specific information ==
        Brightness: 16
        Is dimmable: True
        Color temperature: 2500
        Valid temperature range: ColorTempRange(min=2500, max=9000)
        HSV: HSV(hue=0, saturation=0, value=16)
        Presets:
                index=0 brightness=50 hue=0 saturation=0 color_temp=2500 custom=None id=None mode=None
                index=1 brightness=100 hue=299 saturation=95 color_temp=0 custom=None id=None mode=None
                index=2 brightness=100 hue=120 saturation=75 color_temp=0 custom=None id=None mode=None
                index=3 brightness=100 hue=240 saturation=75 color_temp=0 custom=None id=None mode=None

        == Current State ==
        <EmeterStatus power=2.4 voltage=None current=None total=None>

        == Modules ==
        + <Module Schedule (smartlife.iot.common.schedule) for 192.168.xx.xx>
        + <Module Usage (smartlife.iot.common.schedule) for 192.168.xx.xx>
        + <Module Antitheft (smartlife.iot.common.anti_theft) for 192.168.xx.xx>
        + <Module Time (smartlife.iot.common.timesetting) for 192.168.xx.xx>
        + <Module Emeter (smartlife.iot.common.emeter) for 192.168.xx.xx>
        - <Module Countdown (countdown) for 192.168.xx.xx>
        + <Module Cloud (smartlife.iot.common.cloud) for 192.168.xx.xx>

If your device requires authentication to control it, you need to pass the credentials using --username and --password options.

Basic functionalities

All devices support a variety of common commands, including:

  • state which returns state information
  • on and off for turning the device on or off
  • emeter (where applicable) to return energy consumption information
  • sysinfo to return raw system information

The syntax to control device is kasa --host <ip address> <command>. Use kasa --help (or consult the documentation) to get a list of all available commands and options. Some examples of available options include JSON output (--json), defining timeouts (--timeout and --discovery-timeout).

Each individual command may also have additional options, which are shown when called with the --help option. For example, --transition on bulbs requests a smooth state change, while --name and --index are used on power strips to select the socket to act on:

$ kasa on --help

Usage: kasa on [OPTIONS]

  Turn the device on.

Options:
  --index INTEGER
  --name TEXT
  --transition INTEGER
  --help                Show this message and exit.

Bulbs

Common commands for bulbs and light strips include:

  • brightness to control the brightness
  • hsv to control the colors
  • temperature to control the color temperatures

When executed without parameters, these commands will report the current state.

Some devices support --transition option to perform a smooth state change. For example, the following turns the light to 30% brightness over a period of five seconds:

$ kasa --host <addr> brightness --transition 5000 30

See --help for additional options and the documentation for more details about supported features and limitations.

Power strips

Each individual socket can be controlled separately by passing --index or --name to the command. If neither option is defined, the commands act on the whole power strip.

For example:

$ kasa --host <addr> off  # turns off all sockets
$ kasa --host <addr> off --name 'Socket1'  # turns off socket named 'Socket1'

See --help for additional options and the documentation for more details about supported features and limitations.

Energy meter

Running kasa emeter command will return the current consumption. Possible options include --year and --month for retrieving historical state, and reseting the counters can be done with --erase.

$ kasa emeter
== Emeter ==
Current state: {'total': 133.105, 'power': 108.223577, 'current': 0.54463, 'voltage': 225.296283}

Library usage

If you want to use this library in your own project, a good starting point is the tutorial in the documentation.

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.

The library design and module structure is described in a separate page.

The device type specific documentation can be found in their separate pages:

Contributing

Contributions are very welcome! The easiest way to contribute is by creating a fixture file for the automated test suite if your device hardware and firmware version is not currently listed as supported. Please refer to our contributing guidelines.

Supported devices

The following devices have been tested and confirmed as working. If your device is unlisted but working, please consider contributing a fixture file.

Supported Kasa devices

  • Plugs: EP10, EP25*, HS100**, HS103, HS105, HS110, KP100, KP105, KP115, KP125, KP125M*, KP401
  • Power Strips: EP40, HS107, HS300, KP200, KP303, KP400
  • Wall Switches: ES20M, HS200, HS210, HS220, KP405, KS200M, KS205*, KS220M, KS225*, KS230, KS240*
  • Bulbs: KL110, KL120, KL125, KL130, KL135, KL50, KL60, LB110
  • Light Strips: KL400L5, KL420L5, KL430
  • Hubs: KH100*
  • Hub-Connected Devices***: KE100*

Supported Tapo* devices

  • Plugs: P100, P110, P125M, P135, TP15
  • Power Strips: P300, TP25
  • Wall Switches: S500D, S505
  • Bulbs: L510B, L510E, L530E
  • Light Strips: L900-10, L900-5, L920-5, L930-5
  • Hubs: H100
  • Hub-Connected Devices***: T110, T300, T310, T315

*ย ย  Model requires authentication
**ย  Newer versions require authentication
*** Devices may work across TAPO/KASA branded hubs

See supported devices in our documentation for more detailed information about tested hardware and software versions.

Resources

Developer Resources

Library Users

TP-Link Tapo support

This library has recently added a limited supported for devices that carry Tapo branding. That support is currently limited to the cli. The package kasa.smart is in flux and if you use it directly you should expect it could break in future releases until this statement is removed.

Other TAPO libraries are:

python-kasa's People

Contributors

acmay avatar adriandorr avatar amelchio avatar annikajayuk avatar appleguru avatar aricforrest avatar basnijholt avatar bdraco avatar bmoregeo avatar brianthedavis avatar buongiornotexas avatar epicalex avatar flavio-fernandes avatar gadgetreactor avatar gollo avatar gritstub avatar hankb avatar jules43 avatar khenriks avatar kirichkov avatar lschweiss avatar mweinelt avatar newam avatar rytilahti avatar sbytnar avatar sdb9696 avatar shifty35 avatar smaggard avatar thegardenmonkey avatar trainman419 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

python-kasa's Issues

not able to pip install the library

pip3 install python-kasa
is giving me error "Could not find a version that satisfies the requirement python-kasa (from versions: )
No matching distribution found for python-kasa"
how do i resolve it?
i am using python 3.6 on ubuntu.

HS300 Children plugs have emeter

In SmartStrip.py at line 294

def has_emeter(self) -> bool: """Children have no emeter to my knowledge.""" #return False

They do though. Changing this from False to True works fine.

Improve HS220 support

Now that SmartDimmer is in its own class and easier to work on, the current support for HS220 could use some improvements:

  • Defining transition periods, maybe set_brightness(brightness, *, transition=1234)
    • Side note: the bulbs support transitions, too. these two should share the same method signature for brightness setting.
  • Turning the bulb on to specified brightness set_brightness(brightness, *, turn_on=True)
  • Allow changing the brightness even when the device is offline? (turn_on boolean?)

Information about the API: plasticrake/tplink-smarthome-api#49 (comment)
Earlier pyhs100 issue: GadgetReactor/pyHS100#143
Related downstream issue: home-assistant/core#32755

7.1.2 Update to asyncclick breaks github install of python-kasa

I've just installed the latest master python-kasa in clean environment on Windows using pip install git+https://github.com/python-kasa/python-kasa.git, which installs asyncclick 7.1.2 (consistent with pyproject.toml (^7), but not poetry.lock (7.0.9)).

When I run kasa, I get the following import error:

    from ._winconsole import _get_windows_console_stream, _wrap_std_stream
ImportError: cannot import name '_wrap_std_stream' from 'asyncclick._winconsole' (d:\python envs\kasatest\lib\site-packages\asyncclick\_winconsole.py)

As a short term fix, I've reverted to 7.0.9 by using pip install asyncclick==7.0.9.

Isssue #8 for asyncclick covers the same problem - I'm not sure that the fix is relevant though.

I presume this is due to pyproject.toml taking precedence over poetry.lock - as I haven't got to grips with recent changes to installation methods, I don't have any useful suggestions for a proper fix (unless it is as simple as updating pyproject.toml?).

Update readme

  • Add historical information about the origins of the project

New firmware for HS103 blocking local access?

Has anyone ever actually gotten this to work with an HS103? I'm trying it now, and just getting timeout errors with no response...

~> kasa --host 192.168.1.43 --plug --debug
DEBUG:kasa.smartdevice:Initializing 192.168.1.43
DEBUG:asyncio:Using selector: KqueueSelector
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/kasa/smartdevice.py", line 136, in _query_helper
    response = await self.protocol.query(host=self.host, request=request)
  File "/usr/local/lib/python3.7/site-packages/kasa/protocol.py", line 47, in query
    reader, writer = await asyncio.wait_for(task, timeout=timeout)
  File "/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/tasks.py", line 449, in wait_for
    raise futures.TimeoutError()
concurrent.futures._base.TimeoutError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/local/bin/kasa", line 8, in <module>
    sys.exit(cli())
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 1236, in invoke
    return Command.invoke(self, ctx)
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/kasa/cli.py", line 81, in cli
    ctx.invoke(state)
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/click/decorators.py", line 73, in new_func
    return ctx.invoke(f, obj, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/kasa/cli.py", line 183, in state
    asyncio.run(dev.update())
  File "/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 587, in run_until_complete
    return future.result()
  File "/usr/local/lib/python3.7/site-packages/kasa/smartdevice.py", line 184, in update
    self._sys_info = await self.get_sys_info()
  File "/usr/local/lib/python3.7/site-packages/kasa/smartdevice.py", line 177, in get_sys_info
    return await self._query_helper("system", "get_sysinfo")
  File "/usr/local/lib/python3.7/site-packages/kasa/smartdevice.py", line 138, in _query_helper
    raise SmartDeviceException(f"Communication error on {target}:{cmd}") from ex
kasa.smartdevice.SmartDeviceException: Communication error on system:get_sysinfo

problem controlling the smartplug through a controller

i am trying to control a lamp using a controller. i am sending the controller button data through a udp socket to my processor. everything will work fine for first 15minutes, then the connection between the controller and the smartplug is lost. this happens everytime. i have enclosed my code here, is this the problem with the udp socket? or with the program?

import socket
import asyncio
from smartplug import SmartPlug

PORT = xxxx
BUFFER_SIZE = 1024
HOST = '192.xxx.x.xx'

sock = socket.socket(socket.AF_INET, type=socket.SOCK_DGRAM)
locaddr = (HOST, PORT)
sock.bind(locaddr)

plug = SmartPlug("192.xxx.x.xx") 
                                                          
async def uv_plug_init():
    await plug.update()

async def uv_plug_on():
    await plug.turn_on()

async def uv_plug_off():
    await plug.turn_off()

asyncio.run(uv_plug_init())

def uvlight():
    asyncio.run(uv_plug_off())
    data, addr = sock.recvfrom(1024)
    print("data: {}".format(data))
    plug_init = data[-1]
    while True:      
        data, addr = sock.recvfrom(1024)
        print("data: {}".format(data))
        if data[-1] == plug_init:
            asyncio.run(uv_plug_off())
        
        else:
            asyncio.run(uv_plug_on()) 

uvlight()

(edited to use triple-backticks)

cli emeter year and month functions fail

The emeter year and month function fail. I'm not good with diffs, but the relevant code in method emeter of cli.py is:

   if year:
        click.echo(f"== For year {year.year} ==")
        emeter_status = await dev.get_emeter_monthly(year.year)
    elif month:
        click.echo(f"== For month {month.month} of {month.year} ==")
        emeter_status = await dev.get_emeter_daily(year=month.year, month=month.month)
    else:
        emeter_status = dev.emeter_realtime

    if isinstance(emeter_status, list):
        for plug in emeter_status:
            index = emeter_status.index(plug) + 1
            click.echo(f"Plug {index}: {plug}")
    else:
        click.echo("Current: %s A" % emeter_status["current"])
        click.echo("Voltage: %s V" % emeter_status["voltage"])
        click.echo("Power: %s W" % emeter_status["power"])
        click.echo("Total consumption: %s kWh" % emeter_status["total"])

        click.echo("Today: %s kWh" % dev.emeter_today)
        click.echo("This month: %s kWh" % dev.emeter_this_month)

This code correctly returns a dict of either yearly or monthly data as emeter_status, but this data is not handled correctly in the second if. Modifying the first if as follows should address the problem:

    if year:
        click.echo(f"== For year {year.year} ==")
        click.echo(await dev.get_emeter_monthly(year.year))
        return
    elif month:
        click.echo(f"== For month {month.month} of {month.year} ==")
        click.echo(await dev.get_emeter_daily(year=month.year, month=month.month))
        return
    else:
        emeter_status = dev.emeter_realtime

Individual commands do not work on discovered devices

No host name given, trying discovery..
Discovering devices for 3 seconds
== hs100a - HS100(US) ==
    Host: 192.168.10.127
    Device state: ON

    == Generic information ==
    Time:         2020-06-11 17:13:12
    Hardware:     2.0
    Software:     1.5.6 Build 191118 Rel.140307
    MAC (rssi):   XX:XX:XX:XX:XX:XX (-48)
    Location:     {'latitude': 304306, 'longitude': -978273}

    == Device specific information ==
    LED state: True
    On since: 2020-06-11 16:58:33.087466


Traceback (most recent call last):
  File "/home/userx/tmp/python/venv8/bin/kasa", line 8, in <module>
    sys.exit(cli())
  File "/home/userx/tmp/python/venv8/lib/python3.8/site-packages/asyncclick/core.py", line 799, in __call__
    return anyio.run(self._main, main, args, kwargs, backend=_anyio_backend)
  File "/home/userx/tmp/python/venv8/lib/python3.8/site-packages/anyio/__init__.py", line 68, in run
    return asynclib.run(func, *args, **backend_options)  # type: ignore
  File "/home/userx/tmp/python/venv8/lib/python3.8/site-packages/anyio/_backends/_asyncio.py", line 99, in run
    raise exception
  File "/home/userx/tmp/python/venv8/lib/python3.8/site-packages/anyio/_backends/_asyncio.py", line 67, in wrapper
    retval = await func(*args)
  File "/home/userx/tmp/python/venv8/lib/python3.8/site-packages/asyncclick/core.py", line 802, in _main
    return await main(*args, **kwargs)
  File "/home/userx/tmp/python/venv8/lib/python3.8/site-packages/asyncclick/core.py", line 748, in main
    rv = await self.invoke(ctx)
  File "/home/userx/tmp/python/venv8/lib/python3.8/site-packages/asyncclick/core.py", line 1175, in invoke
    return await _process_result(await sub_ctx.command.invoke(sub_ctx))
  File "/home/userx/tmp/python/venv8/lib/python3.8/site-packages/asyncclick/core.py", line 994, in invoke
    return await ctx.invoke(self.callback, **ctx.params)
  File "/home/userx/tmp/python/venv8/lib/python3.8/site-packages/asyncclick/core.py", line 586, in invoke
    rv = callback(*args, **kwargs)
  File "/home/userx/tmp/python/venv8/lib/python3.8/site-packages/asyncclick/decorators.py", line 60, in new_func
    raise RuntimeError('Managed to invoke callback without a '
RuntimeError: Managed to invoke callback without a context object of type 'SmartDevice' existing

Pull request #54 broke installer?

Hi,

how to install this software, now the setup.py file is deleted? How can I use this library in my software?

Example code at: https://github.com/python-kasa/python-kasa#discovering-devices-1 is not working.

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    from kasa import Discover
  File "/tmp/TerrariumPI/python-kasa/kasa/__init__.py", line 15, in <module>
    from kasa.discover import Discover
  File "/tmp/TerrariumPI/python-kasa/kasa/discover.py", line 9, in <module>
    from kasa.smartbulb import SmartBulb
  File "/tmp/TerrariumPI/python-kasa/kasa/smartbulb.py", line 5, in <module>
    from kasa.smartdevice import (
  File "/tmp/TerrariumPI/python-kasa/kasa/smartdevice.py", line 17, in <module>
    from dataclasses import dataclass
ModuleNotFoundError: No module named 'dataclasses'

How to fix this?

Can we donate?

This project is really well done, good job.

I would like to make a donation to the project, is there a way I could do so?

@rytilahti you're the top contributor, I assume this is your project? Would it make sense if I would donate directly to you through paypal maybe?

There's that new thing called GitHub sponsors, I'm not sure if I could donate to the project through that.

Can the HS105 be controlled without internet?

Hello, this is more of a question rather than an issue. Will this repository allow me to turn on/off a device connected to a TP-Link HS105 Smart Plug with just a local network setup (i.e. a hotspot without internet connection)?

Improve poetry usage documentation

Greatly appreciate the work done in this project, I had recently gotten a build of this to work on May 2nd, but it seems like the installation process has changed dramatically in this short time. Haivng some problems with the new flow, and would appreciate some help in resolving if possible.

Running on a Raspberry Pi Model 3B+ on Buster. Python version is 3.7. After running the newly updated install process using:

poetry install
pre-commit install

The installation appears to be successful with the following output:

pi@raspberrypi2:~/www/python/python-kasa $ sudo poetry install
Creating virtualenv python-kasa-BwJufRoA-py3.7 in /root/.cache/pypoetry/virtualenvs
Installing dependencies from lock file

Package operations: 40 installs, 0 updates, 0 removals

  • Installing zipp (3.1.0)
  • Installing importlib-metadata (1.6.0)
  • Installing pyparsing (2.4.7)
  • Installing six (1.14.0)
  • Installing appdirs (1.4.4)
  • Installing async-generator (1.10)
  • Installing attrs (19.3.0)
  • Installing certifi (2020.4.5.1)
  • Installing chardet (3.0.4)
  • Installing distlib (0.3.0)
  • Installing filelock (3.0.12)
  • Installing idna (2.9)
  • Installing more-itertools (8.3.0)
  • Installing packaging (20.3)
  • Installing pluggy (0.13.1)
  • Installing py (1.8.1)
  • Installing sniffio (1.1.0)
  • Installing urllib3 (1.25.9)
  • Installing wcwidth (0.1.9)
  • Installing anyio (1.3.0)
  • Installing cfgv (3.1.0)
  • Installing coverage (5.1)
  • Installing identify (1.4.15)
  • Installing nodeenv (1.3.5)
  • Installing pytest (5.4.2)
  • Installing pyyaml (5.3.1)
  • Installing requests (2.23.0)
  • Installing termcolor (1.1.0)
  • Installing toml (0.10.1)
  • Installing virtualenv (20.0.20)
  • Installing asyncclick (7.0.9)
  • Installing codecov (2.0.22)
  • Installing pre-commit (2.4.0)
  • Installing pytest-asyncio (0.11.0)
  • Installing pytest-azurepipelines (0.8.0)
  • Installing pytest-cov (2.8.1)
  • Installing pytest-mock (3.1.0)
  • Installing pytest-sugar (0.9.3)
  • Installing tox (3.15.0)
  • Installing voluptuous (0.11.7)
  • Installing python-kasa (0.4.0.dev0)

pi@raspberrypi2:~/www/python/python-kasa $ sudo pre-commit install
pre-commit installed at .git/hooks/pre-commit

However after this is done, there is no kasa module in my python3.7 and the previously working "kasa" CLI program is not found on the path.

With the previous setup.py based flow, the kasa libraries were installed at a location in the $PATH but this is no longer the case.

Question is where is the kasa.py CLI program expected to be? And why is the kasa module not linking to my python environment?

I originally thought running this as root with sudo would possibly work, but no luck there either.

Thanks!

Discover.discover() add selecting network interface [pull request]

Hi, thanks for the cool product.

On my dd-wrt router, the broadcast socket does not work without specifying the network interface name. Please add to the code possibility to specify the network interface name during the discovery process.

I am too lazy to create a full pull-request, so, just a code:

async def discover(*, target="255.255.255.255",
                      on_discovered=None,
                      timeout=5,
                      discovery_packets=3,
                      return_raw=False,
                      interface=None) -> Mapping[str, Union[SmartDevice, Dict]]:
    loop = asyncio.get_event_loop()
    transport, protocol = await loop.create_datagram_endpoint(
        lambda: __DiscoverProtocol(
            target=target,
            on_discovered=on_discovered,
            timeout=timeout,
            discovery_packets=discovery_packets,
            interface=interface
        ),
        local_addr=("0.0.0.0", 0),
    )
    protocol = cast(__DiscoverProtocol, protocol)

    try:
        await asyncio.sleep(5)
    finally:
        transport.close()

    if return_raw:
        return protocol.discovered_devices_raw

    return protocol.discovered_devices


class _DiscoverProtocol(asyncio.DatagramProtocol):
    discovered_devices: Dict[str, SmartDevice]
    discovered_devices_raw: Dict[str, Dict]

    def __init__(
        self,
        *,
        on_discovered: OnDiscoveredCallable = None,
        target: str = "255.255.255.255",
        timeout: int = 5,
        discovery_packets: int = 3,
        interface: bytes = b""
    ):
        self.transport = None
        self.tries = discovery_packets
        self.timeout = timeout
        self.interface = interface
        self.on_discovered = on_discovered
        self.protocol = TPLinkSmartHomeProtocol()
        self.target = (target, Discover.DISCOVERY_PORT)
        self.discovered_devices = {}
        self.discovered_devices_raw = {}

    def connection_made(self, transport) -> None:
        """Set socket options for broadcasting."""
        self.transport = transport
        sock = transport.get_extra_info("socket")
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        if self.interface is not None and len(self.interface) > 0:
            sock.setsockopt(socket.SOL_SOCKET, 25, self.interface)

        self.do_discover()

Usage on my dd-wrt router:

devices = asyncio.run(Discover.discover(interface=b"br0"))

Running via Crontab

This repo is awesome btw. I have a script that reads the on time and turns off a device (Kitchen Cabinet - Glass Cabinet) if the on time exceeds a threshold. The code runs fine when initiated by the command line, and sends the below error message when run via the crontab scheduler. Running ubuntu 18.04.4 LTS (Bionic Beaver). Any help would be appreciated, thank you!

Trying to discover Kitchen Cabinet - Glass Cabinet using 3 attempts of 1 seconds
Attempt 1 of 3
Traceback (most recent call last):
File "/usr/local/bin/kasa", line 8, in
sys.exit(cli())
File "/usr/local/lib/python3.7/dist-packages/asyncclick/core.py", line 799, in call
return anyio.run(self._main, main, args, kwargs, backend=_anyio_backend)
File "/usr/local/lib/python3.7/dist-packages/anyio/init.py", line 72, in run
return asynclib.run(func, *args, **backend_options) # type: ignore
File "/usr/local/lib/python3.7/dist-packages/anyio/_backends/_asyncio.py", line 114, in run
raise exception
File "/usr/local/lib/python3.7/dist-packages/anyio/_backends/_asyncio.py", line 76, in wrapper
retval = await func(*args)
File "/usr/local/lib/python3.7/dist-packages/asyncclick/core.py", line 802, in _main
return await main(*args, **kwargs)
File "/usr/local/lib/python3.7/dist-packages/asyncclick/core.py", line 748, in main
rv = await self.invoke(ctx)
File "/usr/local/lib/python3.7/dist-packages/asyncclick/core.py", line 1172, in invoke
await Command.invoke(self, ctx)
File "/usr/local/lib/python3.7/dist-packages/asyncclick/core.py", line 994, in invoke
return await ctx.invoke(self.callback, **ctx.params)
File "/usr/local/lib/python3.7/dist-packages/asyncclick/core.py", line 588, in invoke
rv = await rv
File "/usr/local/lib/python3.7/dist-packages/kasa/cli.py", line 56, in cli
host = await find_host_from_alias(alias=alias, target=target)
File "/usr/local/lib/python3.7/dist-packages/kasa/cli.py", line 195, in find_host_from_alias
if dev.alias.lower() == alias.lower():
File "/usr/local/lib/python3.7/dist-packages/kasa/smartdevice.py", line 112, in wrapped
"You need to await update() to access the data"
kasa.exceptions.SmartDeviceException: You need to await update() to access the data

HS107 Support

https://www.kasasmart.com/us/products/smart-plugs/kasa-smart-wi-fi-plug-2-outlets-hs107

I have one of these devices and the library crashes on the get_realtime emeter.

Here are some trimmed down logs.

DEBUG:kasa.protocol:> (33) {"system": {"get_sysinfo": null}}
DEBUG:kasa.protocol:< (705) {"system":{"get_sysinfo":{"sw_ver":"1.0.8 Build 190103 Rel.163436","hw_ver":"1.0","model":"HS107(US)","deviceId":"XXX","oemId":"XXX","hwId":"XX","rssi":-56,"longitude_i":XX,"latitude_i":XXX,"alias":"TP-LINK_Smart Plug_C119","mic_type":"IOT.SMARTPLUGSWITCH","feature":"TIM","mac":"XXX","updating":0,"led_off":0,"children":[{"id":"XXX","state":0,"alias":"Two head","on_time":0,"next_action":{"type":-1}},{"id":"XXX","state":0,"alias":"Tree","on_time":0,"next_action":{"type":-1}}],"child_num":2,"err_code":0}}}
DEBUG:kasa.smartstrip:Initializing 2 child sockets
DEBUG:kasa.smartdevice:Initializing XXX with cache ttl 0:00:03
DEBUG:kasa.smartdevice:Initializing XXX with cache ttl 0:00:03
DEBUG:asyncio:Using selector: EpollSelector
DEBUG:kasa.smartdevice:Checking cache for system get_sysinfo
DEBUG:kasa.smartdevice:Got cached system get_sysinfo
== TP-LINK_Smart Plug_C119 - HS107(US) ==
Device state: OFF
DEBUG:asyncio:Using selector: EpollSelector
WARNING:kasa.smartstrip:You called update() on a child device, which has no effect.Call update() on the parent device instead.
  * Two head state: OFF
DEBUG:asyncio:Using selector: EpollSelector
WARNING:kasa.smartstrip:You called update() on a child device, which has no effect.Call update() on the parent device instead.
  * Tree state: OFF
Host/IP: XXXXXXXXX
LED state: True
== Generic information ==
DEBUG:asyncio:Using selector: EpollSelector
DEBUG:kasa.smartdevice:Checking cache for time get_time
DEBUG:kasa.smartdevice:Got no result from cache, querying the device.
DEBUG:kasa.protocol:> (28) {"time": {"get_time": null}}
DEBUG:kasa.protocol:< (96) {"time":{"get_time":{"year":2020,"month":4,"mday":10,"hour":14,"min":49,"sec":49,"err_code":0}}}
Time:         2020-04-10 14:49:49
Hardware:     1.0
Software:     1.0.8 Build 190103 Rel.163436
MAC (rssi):   XXXXXX (XX)
Location:     {'latitude': XXXXXXX, 'longitude': XXXXXXXX}
== Emeter ==
DEBUG:asyncio:Using selector: EpollSelector
DEBUG:kasa.smartdevice:Checking cache for system get_sysinfo
DEBUG:kasa.smartdevice:Got cached system get_sysinfo
DEBUG:asyncio:Using selector: EpollSelector
DEBUG:kasa.smartdevice:Checking cache for emeter get_realtime
DEBUG:kasa.smartdevice:Got no result from cache, querying the device.
DEBUG:kasa.protocol:> (34) {"emeter": {"get_realtime": null}}
DEBUG:kasa.protocol:< (57) {"emeter":{"err_code":-1,"err_msg":"module not support"}}
Traceback (most recent call last):
  File "kasa/cli.py", line 390, in <module>
    cli()
  File "/home/acmay/.local/lib/python3.7/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/core.py", line 1236, in invoke
    return Command.invoke(self, ctx)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "kasa/cli.py", line 63, in cli
    ctx.invoke(discover)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "kasa/cli.py", line 146, in discover
    ctx.invoke(state)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/decorators.py", line 73, in new_func
    return ctx.invoke(f, obj, *args, **kwargs)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "kasa/cli.py", line 214, in state
    ctx.invoke(emeter)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/decorators.py", line 73, in new_func
    return ctx.invoke(f, obj, *args, **kwargs)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "kasa/cli.py", line 272, in emeter
    emeter_status = asyncio.run(dev.get_emeter_realtime())
  File "/usr/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.7/asyncio/base_events.py", line 584, in run_until_complete
    return future.result()
  File "/home/acmay/src/hass/python-kasa/kasa/smartdevice.py", line 89, in wrapped
    return await f(*args, **kwargs)
  File "/home/acmay/src/hass/python-kasa/kasa/smartdevice.py", line 456, in get_emeter_realtime
    return EmeterStatus(await self._query_helper(self.emeter_type, "get_realtime"))
  File "/home/acmay/src/hass/python-kasa/kasa/smartdevice.py", line 188, in _query_helper
    raise SmartDeviceException(f"Error on {target}.{cmd}: {result}")
kasa.smartdevice.SmartDeviceException: Error on emeter.get_realtime: {'err_code': -1, 'err_msg': 'module not support'}

unable to install

hello i am trying to install this library on debian and have python version 3.8.0
when tryin to run pip install python-kasa --pre
i get this error message
Could not find a version that satisfies the requirement python-kasa (from versions: )
No matching distribution found for python-kasa

when trying to clone the repository after getting the file and running poetry install
i get this error message
[SolverProblemError]
The current project's Python requirement (2.7.13) is not compatible with some of the required packages Python requirement:

  • pytest-mock requires Python >=3.5

Because pytest-mock (3.2.0) requires Python >=3.5
and no versions of pytest-mock match >=3,<3.2.0 || >3.2.0,<4, pytest-mock is forbidden.
So, because python-kasa depends on pytest-mock (^3), version solving failed.

everything on the system seems to be up to date so i am not sure why there is a version problem.
please help me understand and resolve this issue
thanks

Support for P100 Smart Plug

I am interested integrating my TP Link Tapo P100 plugs into homeassistant. Currently homeassistant/core uses pyHS100, but hopefully can be updated to use this module instead / as well.

Has any effort been made to integrate the P100?

In homeassistant the discovery fails, so my starting point will be to try manually discovering the devices with this module and see what happens and if the errors are useful. I am hoping it is just the discovery that needs updating, maybe.

Issues with setup

Ran kasa discovery and wifi scan with no result, I keep getting such errors. I'm new to Python so I'm uncertain if I've set this up properly, I've installed via pip install python-kasa --pre then attempted to run commands via Kasa in powershell. KP303 powerstrip is connected to my windows desktop at 192.168.0.100 as expected.

PS C:\Windows\system32> kasa wifi scan
No host name given, trying discovery..
Discovering devices for 3 seconds
Traceback (most recent call last):
  File "b:\python\python38\lib\runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "b:\python\python38\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "B:\Python\python38\Scripts\kasa.exe\__main__.py", line 7, in <module>
  File "b:\python\python38\lib\site-packages\asyncclick\core.py", line 799, in __call__
    return anyio.run(self._main, main, args, kwargs, backend=_anyio_backend)
  File "b:\python\python38\lib\site-packages\anyio\__init__.py", line 72, in run
    return asynclib.run(func, *args, **backend_options)  # type: ignore
  File "b:\python\python38\lib\site-packages\anyio\_backends\_asyncio.py", line 114, in run
    raise exception
  File "b:\python\python38\lib\site-packages\anyio\_backends\_asyncio.py", line 76, in wrapper
    retval = await func(*args)
  File "b:\python\python38\lib\site-packages\asyncclick\core.py", line 802, in _main
    return await main(*args, **kwargs)
  File "b:\python\python38\lib\site-packages\asyncclick\core.py", line 748, in main
    rv = await self.invoke(ctx)
  File "b:\python\python38\lib\site-packages\asyncclick\core.py", line 1175, in invoke
    return await _process_result(await sub_ctx.command.invoke(sub_ctx))
  File "b:\python\python38\lib\site-packages\asyncclick\core.py", line 1172, in invoke
    await Command.invoke(self, ctx)
  File "b:\python\python38\lib\site-packages\asyncclick\core.py", line 994, in invoke
    return await ctx.invoke(self.callback, **ctx.params)
  File "b:\python\python38\lib\site-packages\asyncclick\core.py", line 586, in invoke
    rv = callback(*args, **kwargs)
  File "b:\python\python38\lib\site-packages\asyncclick\decorators.py", line 60, in new_func
    raise RuntimeError('Managed to invoke callback without a '
RuntimeError: Managed to invoke callback without a context object of type 'SmartDevice' existing

LB100 unable to turn on or off the lights

I have installed all the required libraries, it's maybe because i don't know how the async and await works.. so first thing

import asyncio
from kasa import SmartPlug
from pprint import pformat as pf

plug = SmartPlug("ip of the device")
asyncio.run(plug.update())
await plug.turn_on()

if i do this it says await outside async

if i do this

import asyncio
from kasa import SmartPlug
from pprint import pformat as pf


async def main():
    plug = SmartPlug("ip of the device")
    asyncio.run(plug.update())
    await plug.turn_on()

asyncio.run(main())

it says this

Traceback (most recent call last):
  File "d:/Documents/8sem/Project Phase II/new/let's do this/demo.py", line 11, in <module>
    asyncio.run(main())
  File "C:\Users\ReNew\AppData\Local\Programs\Python\Python38-32\lib\asyncio\runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "C:\Users\ReNew\AppData\Local\Programs\Python\Python38-32\lib\asyncio\base_events.py", line 612, in run_until_complete
    return future.result()
  File "d:/Documents/8sem/Project Phase II/new/let's do this/demo.py", line 8, in main
    asyncio.run(plug.update())
  File "C:\Users\ReNew\AppData\Local\Programs\Python\Python38-32\lib\asyncio\runners.py", line 33, in run
    raise RuntimeError(
RuntimeError: asyncio.run() cannot be called from a running event loop
sys:1: RuntimeWarning: coroutine 'SmartDevice.update' was never awaited

can someone help me on how to move forward

issue with installation

hello. i am new here, and new to python.
i am havin trouble installing the package. when i run the command
pip install pyhton-kasa --pre
i get the following error message

Could not find a version that satisfies the requirement python-kasa (from versions: )
No matching distribution found for python-kasa

i am trying to insatll on my raspberry pi running the desbian os
python version - Python 2.7.13

any help would be much appreciated
thank you

How to install?

I am feeling silly as I can't find this information anywhere. The pypi link on the readme throws 404. Any help?

RFC: remove implicit updates after state changes?

It would make sense to avoid automatic await on update() after changing the device state, as this will create unnecessary I/O especially when changing multiple settings (e.g., turning the bulb on while adjusting its settings).

The question is, does it make sense to complicate the code by adding a device setting implicit_updates (or alike), that allows turning this behavior on if wanted?

The current API would be pretty clear without such:

  • All property accesses depend on update() to get the latest state. Property accesses do no I/O.
  • All state changes will likewise require update() to refresh the internal state. All coroutines do I/O operations.

What do you think?

Python Version?

What Python Versions is this targeted to support? I was testing moving from pyHS100 and discover failed when using Python 3.6.5 since wait_closed is only available in 3.7

+ python3 python-kasa/kasa/cli.py
No host name given, trying discovery..
Discovering devices for 3 seconds
ERROR:kasa.discover:Got exception Communication error on system:get_sysinfo
Traceback (most recent call last):
  File "/home/pi/development/udi-poly-kasa/python-kasa/kasa/smartdevice.py", line 200, in _query_helper
    response = await self.protocol.query(host=self.host, request=request)
  File "/home/pi/development/udi-poly-kasa/python-kasa/kasa/protocol.py", line 67, in query
    await writer.wait_closed()
AttributeError: 'StreamWriter' object has no attribute 'wait_closed'

sys_info not None fails assertion

I apologize if this clutters up your issue system, but I couldn't find any better place to ask a question. I'm working under windows and would like to send commands to an HS100 smartplug. I didn't quite see how to install the module under windows (using pip for example), so I just downloaded and put the kasa directory in the same directory as my script and that seems to let the import happen fine.

But, in my code I have:

from kasa import SmartPlug
from pprint import pformat as pf

# Define the connection to the TPLink HS100 smart plug using the kasa module
plug = SmartPlug("192.168.0.99")
#print("Hardware: %s" % pf(plug.hw_info))
#print("Full sysinfo: %s" % pf(plug.get_sysinfo())) # this prints lots of information about the device

print("Alias is", plug.alias)

As soon as I try to do anything with plug, it fails assertion saying:

  File "bla\kasa\smartdevice.py", line 110, in wrapped
    assert self._sys_info is not None

I'm guessing this means the library isn't connecting to the plug. Am I doing anything wrong? Is there anything under windows that might be breaking this?

Stopping Kasa SmartDevices from phoning home

I seem to recall some capability to stop Kasa devices from trying to contact their mothership.

However, I cannot find any threads on this topic.

Did I imagine it?

I'd rather not put a block on my router.

Request all necessary information during update()

It is possible to request data from multiple targets (and functions) using a single request, as is done for the discovery already. The regular update() cycle should be optimized to request all the necessary data to avoid consecutive calls to obtain some extra information.

This is necessary as some devices (mainly bulbs?) are known to have rate limits before they start dropping requests, and some downstreams (esp. homeassistant) want to obtain different sorts of data (status, current consumption, historical consumption) during each update cycle.

This combination will lead to unhappy users (and/or various hacks to go around the issue), so this should be fixed before the release.

Install on Ubuntu 18.04 no luck

I tried all of your install options and failed miserably each time. pip3 can't find it, poetry becomes a circular dependency nightmare.. Anyone ever get this installed on Ubuntu?

Separate dimmer-related code from smartplug class

Dimmers (such as HS220) provide interface similar to smart plugs, but provide also brightness controls.

This code should be moved out from SmartPlug into a SmartDimmer class (which can extend from SmartPlug if it makes sense).

TP Link Dimmer switch (HS220) hardware version 2.0 not being discovered

TP Link Dimmer switch (HS220) hardware version 2.0 not being discovered. All other TP Link switches, bulbs, and plugs are successfully discovered.

Relates to home-assistant/core#39395

While I don't currently have a good environment to test python-kasa in currently, I was able to fix this on home assistant by manually editing the old pyHS100/discover.py that it uses.

Specifically, I changed
class Discover: DISCOVERY_QUERY = {"system": {"get_sysinfo": None}, "emeter": {"get_realtime": None}}
to
class Discover: DISCOVERY_QUERY = {"system": {"get_sysinfo": None}}

and then was able to successfully discover and use the version 2.0 dimmers.

I suspect a similar modification to python-kasa's discover.py will fix it here too:

DISCOVERY_QUERY = {

Concurrency performance question

I am converting my home brew web app from pyHS100 to Kasa. I thought the asyncio interface for Kasa would speed it up, but so far I do not see much improvement and in some cases it is slower.

In the initial page render, I get updates from a list of all devices by IP address. The pyHS100 code has to do each one sequentially. With Kasa I am using asyncio.gather() to get the updates concurrently, or so I think. The klights() function below is run from the main thread using asyncio.run(). The question is whether the asyncio.gather is really concurrent or am I mistaken? It does not appear to be much faster than using pyHS100 in a similar sequential fashion.

from kasa import SmartDevice, SmartPlug, SmartStrip, SmartDeviceException
async def update_wrapper(smartdevice):
    global logger
    try:
        await smartdevice.update()
    except BaseException as bx:
        logger.error("Unable to initialize SmartDevice {}: {}".format(smartdevice.host,str(bx)))
        print(bx)
        pass

async def klights():
    devices = db(db.devices).select(db.devices.host_ip_addr,db.devices.device_type,distinct=True)
    smartdevs = []
    keys = []
    dev_names = []
    for device in devices:
        if device.device_type == 'plug':
            smartdevs.append(SmartPlug(device.host_ip_addr))
        else:
            smartdevs.append(SmartStrip(device.host_ip_addr))
    sdaws = []
    for sdev in smartdevs:
        sdaws.append(update_wrapper(sdev))
    devs = asyncio.gather(*sdaws)
    return smartdevs

Lastly, the same web app takes ajax calls to the server to turn devices on/off. In this case the server has to connect to a device, change its state, and query its resulting state. Here kasa definitely seems slower as it must perform extra update()'s to perform the same function with pyHS100. The question is how to reduce the multiple round trips to the device?

pyHS100:
dev = SmartPlug(ip_addr)
dev.turn_on()
state = dev.is_on

Kasa:
dev = SmartPlug(ip_addr)
asyncio.run(dev.update())
asyncio.run(dev.turn_on())
asyncio.run(devupdate())
state = dev.is_on

Hyper-V (and probably virtualbox) break UDP discovery

I'm not sure if this a strictly a bug, but I'm reporting here so people are aware of it and can work around the issue. I do have a workaround, but I haven't done enough testing to see if the workaround breaks Hyper-V, so YMMV.

I'm running Windows 10 with Hyper-V installed and have an HS110 on my network. When I run kasa or kasa discover from this machine I get nothing.

Direct IP connection also works fine, as does connect by hostname.

If I disable the Hyper-V vEthernet virtual adapters, discovery works fine.

I'm not confident on terminology, but it looks as though the problem arises due Hyper-V virtual adapters having higher priority than the physical ethernet card - more precisely, on my machine the interface metrics for both the vEthernet adapter and the vEthernet Default switch are than lower interface metrics than the physical card.

I fixed problem this by changing the interface metrics on all of the virtual adapters and switches to have higher metrics than the physical adapter. After this discovery works.

Notes:

  1. Grab ifIndex for adapter with:

Get-NetIPInterface

  1. With admin enabled powershell, set the interface metric for adapter 12 to 45.

Set-NetIPInterface -InterfaceIndex 12 -InterfaceMetric 45

Fix failing tests: KeyError: 'relay_state'

In the CI's build log I see:

2019-12-18T08:07:23.5192675Z 
2019-12-18T08:07:23.5192766Z =================================== FAILURES ===================================
2019-12-18T08:07:23.5192838Z _ test_state_info[/home/vsts/work/1/s/kasa/tests/fixtures/HS300(US)_1.0.json] __
2019-12-18T08:07:23.5193293Z 
2019-12-18T08:07:23.5194008Z dev = <[KeyError('relay_state') raised in repr()] SmartStrip object at 0x7f5dd51ed290>
2019-12-18T08:07:23.5194070Z 
2019-12-18T08:07:23.5194611Z     def test_state_info(dev):
2019-12-18T08:07:23.5194668Z         dev.sync.update()
2019-12-18T08:07:23.5194738Z >       assert isinstance(dev.sync.state_information, dict)
2019-12-18T08:07:23.5194781Z 
2019-12-18T08:07:23.5194830Z kasa/tests/test_fixtures.py:59: 
2019-12-18T08:07:23.5194904Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2019-12-18T08:07:23.5194968Z kasa/smartdevice.py:674: in __getattr__
2019-12-18T08:07:23.5195381Z     method = getattr(self.async_device, attr)
2019-12-18T08:07:23.5195440Z kasa/smartdevice.py:98: in wrapped
2019-12-18T08:07:23.5195599Z     return f(*args, **kwargs)
2019-12-18T08:07:23.5195668Z kasa/smartstrip.py:120: in state_information
2019-12-18T08:07:23.5195725Z     if plug.is_on:
2019-12-18T08:07:23.5195786Z kasa/smartdevice.py:98: in wrapped
2019-12-18T08:07:23.5195842Z     return f(*args, **kwargs)
2019-12-18T08:07:23.5195906Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2019-12-18T08:07:23.5195959Z 
2019-12-18T08:07:23.5198186Z self = <[KeyError('relay_state') raised in repr()] SmartPlug object at 0x7f5dd51ed1d0>
2019-12-18T08:07:23.5198257Z 
2019-12-18T08:07:23.5198322Z     @property  # type: ignore
2019-12-18T08:07:23.5198369Z     @requires_update
2019-12-18T08:07:23.5198606Z     def is_on(self) -> bool:
2019-12-18T08:07:23.5198660Z         """Return whether device is on.
2019-12-18T08:07:23.5198707Z     
2019-12-18T08:07:23.5198775Z         :return: True if device is on, False otherwise
2019-12-18T08:07:23.5198825Z         """
2019-12-18T08:07:23.5198883Z         sys_info = self.sys_info
2019-12-18T08:07:23.5198933Z >       return bool(sys_info["relay_state"])
2019-12-18T08:07:23.5199132Z E       KeyError: 'relay_state'

@rytilahti, this was after you simplified the socket strip. Do you perhaps know why this happens?

Improve retry logic for discovery, messaging (was: Handle empty responses)

I have a device outside with an RSSI of -78 that sometimes returns data and sometimes doesn't. There is also multiple wireless video feeds that are going on at the same time as well that probably don't help.

I did this to get past the first exception on the unpack of empty data but it still fails later on. Not sure the best spot to put some extra retry logic.

diff --git a/kasa/protocol.py b/kasa/protocol.py
index f29b4be..60368f0 100755
--- a/kasa/protocol.py
+++ b/kasa/protocol.py
@@ -57,6 +57,10 @@ class TPLinkSmartHomeProtocol:
             while True:
                 chunk = await reader.read(4096)
                 if length == -1:
+                    if len(chunk) < 4:
+                        _LOGGER.error("Request %s:%s Failed to get response",
+                                      host, request)
+                        break
                     length = struct.unpack(">I", chunk[0:4])[0]
                 buffer += chunk
                 if (length > 0 and len(buffer) >= length + 4) or not chunk:
@@ -66,7 +70,10 @@ class TPLinkSmartHomeProtocol:
                 writer.close()
                 await writer.wait_closed()
 
-        response = TPLinkSmartHomeProtocol.decrypt(buffer[4:])
+        if len(buffer) > 4:
+            response = TPLinkSmartHomeProtocol.decrypt(buffer[4:])
+        else:
+            response = ""
         _LOGGER.debug("< (%i) %s", len(response), response)
 
         return json.loads(response)

Here are the logs of a good and failed run.

acmay@mud:~/src/hass/python-kasa$ PYTHONPATH=`pwd` python3 kasa/cli.py --host 192.168.1.28 sysinfo
No --strip nor --bulb nor --plug given, discovering..
== System info ==
{'alias': 'TP-LINK_Smart Plug_2ECE',
 'child_num': 2,
 'children': [{'alias': 'Rope',
               'id': 'XXXX',
               'next_action': {'action': 1, 'schd_sec': 69240, 'type': 1},
               'on_time': 0,
               'state': 0},
              {'alias': 'Plug 2',
               'id': 'XXXX',
               'next_action': {'type': -1},
               'on_time': 0,
               'state': 0}],
 'deviceId': 'XXX',
 'feature': 'TIM',
 'hwId': 'XXXX',
 'hw_ver': '1.0',
 'latitude_i': XXXX,
 'led_off': 0,
 'longitude_i': XXXXX,
 'mac': 'XXXX',
 'mic_type': 'IOT.SMARTPLUGSWITCH',
 'model': 'KP400(US)',
 'ntc_state': 0,
 'oemId': 'XXXX',
 'rssi': -78,
 'status': 'new',
 'sw_ver': '1.0.10 Build 190716 Rel.095026',
 'updating': 0}
acmay@mud:~/src/hass/python-kasa$ PYTHONPATH=`pwd` python3 kasa/cli.py --host 192.168.1.28 sysinfo
No --strip nor --bulb nor --plug given, discovering..
ERROR:kasa.protocol:Request 192.168.1.28:{"system": {"get_sysinfo": null}, "emeter": {"get_realtime": null}, "smartlife.iot.dimmer": {"get_dimmer_parameters": null}, "smartlife.iot.common.emeter": {"get_realtime": null}, "smartlife.iot.smartbulb.lightingservice": {"get_light_state": null}} Failed to get response
Traceback (most recent call last):
  File "kasa/cli.py", line 391, in <module>
    cli()
  File "/home/acmay/.local/lib/python3.7/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/core.py", line 1256, in invoke
    Command.invoke(self, ctx)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/home/acmay/.local/lib/python3.7/site-packages/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "kasa/cli.py", line 68, in cli
    dev = asyncio.run(Discover.discover_single(host))
  File "/usr/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.7/asyncio/base_events.py", line 584, in run_until_complete
    return future.result()
  File "/home/acmay/src/hass/python-kasa/kasa/discover.py", line 176, in discover_single
    info = await protocol.query(host, Discover.DISCOVERY_QUERY)
  File "/home/acmay/src/hass/python-kasa/kasa/protocol.py", line 79, in query
    return json.loads(response)
  File "/usr/lib/python3.7/json/__init__.py", line 348, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python3.7/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python3.7/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Handle discovery where multiple LAN interfaces exist

My network arrangement:
modem->wireless bridge a->wireless bridge b->rpi qos & automation->wifi/lan bridge->clients, including hs300.

For the purposes of this discussion, we can simplify that to:
modem->rpi->wifi->hs300

When discovery occurs, from what I can tell, an arbitrary interface (the first interface, linked to the modem) on the rpi is selected. This means the wrong network gets the broadcast packet (which would need to go out via wifi).

This is a known issue, as discussed here, and it can be resolved as discussed in the previous link and here. Also useful is this article about selecting interfaces in linux vs windows

The crux of it is basically:

sockets = []

if windows:
    for address in AF_INET addresses:
        sockets.append(<a new socket, bound to that address>)

if linux/osx:
    for index, interface in socket.if_nameindex():
        sockets.append(<a new socket, bound to that interface>)

for socket in sockets:
    <do discovery>

Obviously, there are additional considerations in the actual coding. Also, there are some ugly convolutions on Windows -- specifically, although you can select the interface to use by binding one of its IP addresses, there's no nice way to get a list of IP addresses. This may mean a module dependency such as netifaces.

Thank you!

Trying to get extended lightstrip functionality

following from conversation in #67 , making a lot of assumptions, looking for assistance on gathering further information.

  1. I can now capture packet dumps from my android emulator.
    Kasa in emulator is controlling lights just fine.
    all tplink traffic uses 9999?
    Only thing I see on 9999 is UDP (outgoing to 9999), immediately replied to with a Network unreachable.
    Then a bunch of DNS to tplink related names, and some TLS.

  2. From talk in #67, inferring there's a separate cloud-control-protocol, which is TLS wrapped and unusable to us, and that's what I'm seeing.

  3. So I logout of the Kasa app in emulator which I guess should force local protocol, and now 2 problems:
    a) it can no longer find my KL130 Light. I'm guessing I need to reconfigure the emulator networking, but no idea what I'd be looking to do.
    b) trying to add a lightstrip shows "A Kasa account is required to setup and control Light Strip" - so I'm not sure that even if I solved 3a I'd be able to progress any further. Was non-cloud control of them possible with some previous apk?

HSV cli command not working

Really awesome project!!

I got some TP-Link KL130 for my birthday and I was looking for a way to control them via python. I seem to be running into a weird issue (this could totally be python configuration for me so don't rule that out)

I am able to discover all of my devices and toggle them with the on and off commands so I believe most of this is working correctly; however, when I try to set hsv with this command

kasa --host 10.0.0.98 --bulb hsv 100 100 100

I get this:

$ kasa --host 10.0.0.98 --bulb hsv 100 100 100
Setting HSV: 100 100 100
Traceback (most recent call last):
  File "/usr/local/bin/kasa", line 11, in <module>
    load_entry_point('python-kasa==0.4.0.dev0', 'console_scripts', 'kasa')()
  File "/home/ethan/.local/lib/python3.7/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/home/ethan/.local/lib/python3.7/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/home/ethan/.local/lib/python3.7/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/ethan/.local/lib/python3.7/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/ethan/.local/lib/python3.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/home/ethan/.local/lib/python3.7/site-packages/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/home/ethan/.local/lib/python3.7/site-packages/click/decorators.py", line 73, in new_func
    return ctx.invoke(f, obj, *args, **kwargs)
  File "/home/ethan/.local/lib/python3.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/home/ethan/.local/lib/python3.7/site-packages/kasa/cli.py", line 334, in hsv
    asyncio.run(dev.set_hsv(h, s, v))
  File "/usr/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.7/asyncio/base_events.py", line 583, in run_until_complete
    return future.result()
  File "/home/ethan/.local/lib/python3.7/site-packages/kasa/smartdevice.py", line 88, in wrapped
    assert self._sys_info is not None
AssertionError

I'm using Python 3.7.6

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.