kasad
v0.1.0First pre-release

Switch and schedule your Kasa plug without the cloud.

kasad is one small program that talks to TP-Link Kasa plugs over your own network. It runs your schedule, keeps the plug's clock right, and serves a web app for your phone and desktop. No account, and no internet connection.

For Kasa plugs that speak the local protocol on TCP port 9999 · Linux on x86-64 and ARM · MIT license

The web app is part of kasad: open its address in any browser. Shown here with a made-up plug and rules.

kasad in a desktop browser: the week's schedule drawn as bars, the rules kasad runs, and the rules stored on the plug. kasad in a desktop browser, in dark mode: the week's schedule drawn as bars, the rules kasad runs, and the rules stored on the plug.
kasad on a phone: a large power dial shows the desk lamp is on, with today's schedule as a bar and the next event, off at 22:15. kasad on a phone, in dark mode: a large power dial shows the desk lamp is on, with today's schedule as a bar and the next event, off at 22:15.

Why kasad

The schedule was never the problem. The clock was.

A Kasa plug stores its schedule and runs it by its own clock. That clock is reported to reset when the plug loses power, and the plug sets it again from the internet. Keep the plug off the internet and, after a power cut, nothing sets it.

kasad sets the clock from your own network when it starts and every hour. It can also run the schedule itself, so the plug's clock stops mattering.

One day, one power cut A rule turns the plug on at 18:30. The power drops for a moment at 03:10.
The plug on its own, offline The 18:30 rule doesn't run
With kasad The 18:30 rule runs
  • Power cut
  • kasad sets the clock (it does so hourly)
  • Plug on
  • A rule that can't run

What it does

One small program does the whole job.

kasad is a command-line tool, a scheduler and a web server in a single binary, with nothing to install beside it.

Switch it from any screen

The web app is built into kasad. It shows whether the plug is on and when a rule will next act, and switches it with a tap, on a phone or a desktop, in light or dark.

A schedule that catches up

Weekly on and off rules, run by kasad. A plug that doesn't answer is tried again every 30 seconds, and after downtime kasad applies the latest rule it missed. Clock changes are handled too.

Rules on the plug, too

List, add, change and delete the rules the plug keeps itself, including ones made in the vendor's app, and move a rule between the plug and kasad.

The plug's clock, kept right

kasad sets the plug's clock from your machine when it starts and every hour, so rules stored on the plug have the right time to go by. No NTP tricks on your network.

A JSON API

Everything the web app does is a plain JSON request: the plug's state, switching it, and both kinds of schedule. Scripts send a bearer token.

Sign-in when you want it

Set a password and the web app asks for it. People stay signed in for 30 days, even across restarts. It is plain HTTP, so it is meant for a network you trust.

One binary, web app included
Under 8 MB
Of memory while serving the web app
About 9 MB
Third-party dependencies
None
x86-64, ARM64, ARMv7 and ARMv6
4 Linux builds

Sizes are of the v0.1.0 release binaries. Memory was measured on x86-64 Linux with the web app open.

Two kinds of rule

Choose where each rule runs.

A rule can be run by kasad or stored on the plug, and the editor lets you pick for each one. Change your mind and the rule moves: it is stored in its new place before it is removed from the old one.

For a rule that is…Run by kasadStored on the plug
Works while kasad is not runningNoYes
Depends on the plug's clockNoYes
Retried, and caught up after an outageYesNo

Rules timed by sunrise or sunset, or that run only once, are listed and can be deleted, but not changed.

The rule editor on a phone, with a Runs on choice between kasad and Plug above the action, time and days. The rule editor on a phone, in dark mode, with a Runs on choice between kasad and Plug above the action, time and days.

Install

Three ways to run it.

kasad runs on Linux, from a Raspberry Pi to a server. Tell it where the plug is. For the web app, give it an address to listen on and a password.

Release + systemd

Shell
tar -xzf kasad-v0.1.0-linux-arm64.tar.gz
cd kasad-v0.1.0-linux-arm64
sudo install kasad /usr/local/bin/
sudo install -m 600 kasad.env.example /etc/kasad.env
sudo install -m 644 kasad.service /etc/systemd/system/

# Set KASAD_HOST to the plug's address. For the web app,
# set KASAD_LISTEN and KASAD_PASSWORD as well.
sudoedit /etc/kasad.env

sudo systemctl daemon-reload
sudo systemctl enable --now kasad

Archives for x86-64, ARM64, ARMv7 (Raspberry Pi 2 and later) and ARMv6 (Pi Zero and the first Pi), with SHA256SUMS, on the Releases page. The unit runs kasad as a user of its own and logs to the journal.

Container

Shell
docker run -d --name kasad --restart unless-stopped \
  -e KASAD_HOST=192.0.2.10 \
  -e KASAD_PASSWORD='a phrase of your own' \
  -e TZ=Europe/Lisbon \
  -p 8080:8080 -v kasad-data:/data \
  ghcr.io/gureetk/kasad:latest

Images for the same four kinds of machine. kasad runs as an unprivileged user and keeps its files in /data. The web app is then on port 8080, and the README has the same as a Compose file.

From source

Shell
# Go 1.22 or newer
go install github.com/gureetk/kasad/cmd/kasad@latest

kasad -host 192.0.2.10 state

No third-party dependencies, and the web app is compiled in. Node.js is only needed to run the web app's tests.

Then talk to the plug

Terminal
$ export KASAD_HOST=192.0.2.10
$ kasad state
Desk lamp (HS103(US))
  State:     on for 1h5m48s
  Clock:     2026-11-13 19:35:49, in step with this machine
  …
$ kasad off
Desk lamp is off
$ kasad sync
Desk lamp clock set to 2026-11-13 19:47:50 (was in step with this machine)

Output from a test plug. kasad serve runs the schedule; give it -listen and it serves the web app as well.

Under the hood

How kasad talks to the plug

These plugs answer on TCP port 9999 without asking for an account. Each message is JSON, sent after a 4-byte length and scrambled with an autokey XOR: every byte is XORed with the byte that came out before it, starting from 171.

kasad's protocol code is written from that description, with Go's standard library alone. Pick a command to see what goes over the wire.

It scrambles; it doesn't protect. Anything that can reach port 9999 can control the plug, so keep plugs on a network that only trusted machines can reach.

JSON
{"system":{"get_sysinfo":{}}}
Length, 4 bytes
00 00 00 1d29 bytes of JSON follow
On the wire
d0 f2 81 f8 8b ff 9a f7 d5 ef 94 b6 d1 b4 c0 9f ec 95 e6 8f e1 87 e8 ca f0 8b f6 8b f6

The first three bytes

  1. {0x7bXOR0xab=0xd0
  2. "0x22XOR0xd0=0xf2
  3. s0x73XOR0xf2=0x81

The key starts at 0xab, which is 171. After that, each byte's key is the byte that came out before it, so a reply is unscrambled the same way.

kasad is young.

v0.1.0 is a pre-release. The README says what has and hasn't been tried on real plugs yet.

Read the README

Thanks

Built on other people's groundwork

kasad's code is its own, written from the protocol's public description. Working that description out, and writing the first clients, was the work of these projects.