pyziggy¶
Source@Github — Documentation — PyPI
Pyziggy is a code generator and runtime framework for creating Zigbee2MQTT automation projects, without jumping through hoops, like having to remember device or parameter names, and citing them correctly as strings. This information is already available through Zigbee2MQTT, so it can be encoded in class hierarchies and the type system.
Pyziggy automatically generates a typed package in your project directory containing information about devices and parameters that Zigbee2MQTT knows about. Your automation project can import this package, and use simple setters and getters on Python objects to effect changes in your Zigbee network. Upon restart, the device package is regenerated and the correctness of your project is checked with mypy.
An IDE should give you full code completion, and take the guesswork out of interacting with your smart devices.
# Your IDE should offer valid choices after each press of .
devices.color_bulb.brightness.set_normalized(0.5)
There will be a section later detailing what pyziggy exactly does.
The optional Flask integration makes it easy for scripts to expose an HTTP interface that interacts with the device objects.
Getting started¶
First you need a working Zigbee2MQTT installation. You should be able to reach the MQTT broker from the machine that’s meant to run pyziggy. Please refer to the Zigbee2MQTT documentation for details about this.
If you have a Python 3.12+ environment, you can install pyziggy with:
pip install pyziggy
Create an empty directory for your automation scripts. Navigate to it and issue
pyziggy run automation.py
and follow the instructions.
A minimal automation project¶
Side note: I like to set up a virtual environment in the .venv
subdirectory of my project directory. I do this with the pyziggy-setup script.
The rest of the documentation will assume, that you set up an automation project by issuing the following commands.
mkdir my_automation
cd my_automation
pyziggy run automation.py
Pyziggy will create a default config.toml
file in the directory, instruct you to edit it and exit. After specifying your MQTT connection details, you need to issue the command again:
pyziggy run automation.py
This would result in the following directory contents.
my_automation/
├─ pyziggy_autogenerate/
│ └─ available_devices.py
├─ automation.py
└─ config.toml
The contents of the initially autogenerated (but editable) automation.py
file would be as follows
from pyziggy_autogenerate.available_devices import AvailableDevices
devices = AvailableDevices()
Although it won’t do much, this is a fully formed automation module file that’s ready to go.
Next steps¶
The way to get the most out of pyziggy is to use an IDE with code completion features. PyCharm is my favourite, which is free for non-professional use. If your automation directory contains a virtual environment in .venv
, all you need to do is open it in PyCharm, and it should be correctly configured.
Open your automation.py
file and start editing it. Typing devices.
should show you suggestions for all the devices that Zigbee2MQTT knows about. Going further and typing e.g. devices.color_bulb.
will show suggestions for all the parameters of color_bulb
that can be queried, set or both. Not every parameter is settable, in which case it will not have setter functions.
All public members of devices
are actual devices that Zigbee2MQTT knows about and pyziggy could successfully parse. The public members of each device are parameters, such as brightness
or color_temp
in case of a smart light bulb.
Most home automation tasks can be implemented by
calling setters and getters on the parameters in
pyziggy.parameters
attaching change listeners to…
your parameters:
pyziggy.parameters.ParameterBase
the global
on_connect
event on thedevices
variable:pyziggy.devices_client.DevicesClient.on_connect
creating
pyziggy.message_loop.MessageLoopTimer
objects that can interact with the parameters in a thread-safe way
A full example¶
A complete automation project running in our home is available at https://github.com/bebump/pyziggy-example.
For simpler examples with more explanation, see the Cookbook chapter.
Startup sequence¶
Understanding the inner workings of pyziggy can be helpful for debugging or supporting advanced use-cases, but for getting started it isn’t necessary. For the curious, there are even more explanations spread out over the API reference.
At this point, after issuing the pyziggy run automation.py
command in the my_automation
directory, pyziggy will
Check that the
config.toml
file is correct and connection to the MQTT broker is possible.Connect to the MQTT broker and wait for a message on the
bridge/devices
topic.If it receives the message, it regenerates the
pyziggy_autogenerate/available_devices.py
file based on its contents. The file should now only contain devices and parameters of devices that the MQTT broker presently knows about.If the message isn’t received for 5 seconds, the program times out and exits with code
1
. This allows service management daemons to detect the error condition, and retry later or take some other action.
Checks the correctness of
automation.py
by running mypy. For example, ifautomation.py
is trying to access a device or a parameter of the device that doesn’t exist, or is misspelled, that will generate an error at this point and the program will exit with code1
.Imports the
automation.py
module. Checks that it has exactly one public member object that is an instance of theDevicesClient
class.AvailableDevices
inherits fromDevicesClient
so thedevices
object will pass this check. If this check fails, the program exits with code1
.Checks whether the
automation.py
module defines a member object that is an instance of theflask.Flask
class. This is optional and failing this check has no consequence.If a
Flask
object was found, a web server is started listening on the port specified byflask_port
inconfig.toml
. Requests sent to this port are routed to theFlask
object.
The program initiates a connection to the MQTT broker and enters an infinite message loop. On sucessful connection the listeners of the
DevicesClient.on_connect
pyziggy.broadcasters.Broadcaster
are called.Upon receiving the SIGINT signal, the program stops the infinite loop and cleanly exits with code
0
. You can useCTRL+C
to send SIGINT if running pyziggy in the terminal.
Running as a service¶
Pyziggy was designed with running as a service in mind. I only know how to do this on MacOS, but this information can probably be adapted to other platforms as well.
MacOS¶
You need to add the following .plist
file to $HOME/Library/LaunchAgents
.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>net.bebump.pyziggy/string>
<key>ProgramArguments</key>
<array>
<string>/Users/ExampleUser/my_automation/.venv/bin/python</string>
<string>-m</string>
<string>pyziggy</string>
<string>run</string>
<string>/Users/ExampleUser/my_automation/automation.py</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/ExampleUser/my_automation</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/net.bebump.pyziggy/stdout.log</string>
<key>StandardErrorPath</key>
<string>/tmp/net.bebump.pyziggy/stderr.log</string>
</dict>
</plist>
You can start the service by issuing
launchctl load ~/Library/LaunchAgents/net.bebump.pyziggy.plist
and stop and uninstall it by
launchctl unload net.bebump.pyziggy.plist && rm ~/Library/LaunchAgents/net.bebump.pyziggy.plist