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

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

  1. Check that the config.toml file is correct and connection to the MQTT broker is possible.

  2. Connect to the MQTT broker and wait for a message on the bridge/devices topic.

    1. 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.

    2. 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.

  3. Checks the correctness of automation.py by running mypy. For example, if automation.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 code 1.

  4. Imports the automation.py module. Checks that it has exactly one public member object that is an instance of the DevicesClient class. AvailableDevices inherits from DevicesClient so the devices object will pass this check. If this check fails, the program exits with code 1.

  5. Checks whether the automation.py module defines a member object that is an instance of the flask.Flask class. This is optional and failing this check has no consequence.

    1. If a Flask object was found, a web server is started listening on the port specified by flask_port in config.toml. Requests sent to this port are routed to the Flask object.

  6. 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.

  7. Upon receiving the SIGINT signal, the program stops the infinite loop and cleanly exits with code 0. You can use CTRL+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