Autogenerates Python classes for communicating with Zigbee2MQTT
Project description
pyziggy
https://github.com/bebump/pyziggy
This project helps with writing home automation scripts in Python.
It aims to eliminate as much of the boilerplate as possible by providing autogenerated typed interfaces for interacting with Zigbee2MQTT.
This is what a minimal Python automation script looks like. It turns on a light at 50% brightness when a switch is pressed.
from pyziggy_autogenerate.available_devices import AvailableDevices
devices = AvailableDevices()
def light_switch_action_listener():
ActionEnum = devices.light_switch.action.enum_type
if action == ActionEnum.button_1_press:
devices.light.state.set(1)
devices.light.brightness.set_normalized(0.5)
devices.light_switch.action.add_listener(light_switch_action_listener)
If this program is saved as automation.py you can run it with the pyziggy run automation.py command. This will regenerate the AvailableDevices class based on information queried from Zigbee2MQTT, then validate the script using mypy, and start an infinite message loop. Sending SIGINT (pressing CTRL+C) to the running program will cleanly terminate it.
Why not Home Assistant?
Pyziggy isn't meant to replace Home Assistant. Home Assistant provides nice graphical controls and dashboards, something that pyziggy isn't aiming to do.
At the same time pyziggy comes with flask integration, so it's easy to create custom web interfaces that hook into the automation scripts.
Getting started
Pyziggy requires Python 3.12 at minimum. It has also been tested with 3.13.
pip install pyziggy
You can bootstrap an empty directory to a home automation project by navigating to it, and then issuing
pyziggy run automation.py
If the directory is completely empty it will first create an empty configuration file where you can specify how to connect to the MQTT server.
Running the command again will generate the automation.py file containing the minimum necessary code to have a continuously running script that communicates with MQTT. This is about three lines and will already refer to an autogenerated AvailableDevices object that exposes typed interfaces to your devices discoverable through MQTT.
Pressing CTRL+C cleanly terminates the automation. You can open this directory as a regular Python project in PyCharm, for example, edit automation.py, and then run pyziggy run automation.py again to test your changes.
Debugging your automations
You can pass the -v or --verbose parameter to run to set the logging level to DEBUG.
For actual debugging you could open your home automation project in PyCharm. Create a run configuration, choose module from the drop-down menu, specify pyziggy as the module name, and use run automation.py as script parameters. This is equivalent to running pyziggy run automation.py from the command line, but now you can place breakpoints in your code and inspect them.
A full example
Our complete home automation project is available at https://github.com/bebump/pyziggy-example.
Deploying automations to remote machines over SSH
This chapter is about using the util/pyziggy-setup.sh script.
It can automate setting up a directory for development by installing the right Python version and creating a virtual environment referring to it. And it can do this on a remote machine as well, and sync between your development environment and the remote.
On MacOS it can also install your automation as a service, and make it easy to start and stop it on the remote to switch between development and operation.
You could jump straight in and run the script and its help should walk you through all steps and inform you of all prerequisites, or the following paragraphs can orient you a bit more about what to expect.
As a prerequisite it's recommended to have pyenv installed. If you want to deploy your automation to a remote machine using SSH, you'll need to have rsync with version 3.2.0+ installed too.
On MacOS brew can install both
brew install pyenv rsync
Now you can download pyziggy-setup.sh and place it in the directory that you want to use for development. Make it executable:
chmod u+x pyziggy-setup.sh
The help displayed by executing ./pyziggy-setup.sh should walk you through setting up your environment, and inform you about how to then deploy your automations to a remote machine over SSH.
About threading
User code should generally assume that it's running on the message thread. The message thread is the thread that belongs to the pyziggy message_loop.
The concept of the message_loop is necessary because it allows us to synchronize with the paho-mqtt thread, which is responsible for communicating with the MQTT server, and the flask thread, which is optionally present if an HTTP interface is provided using flask. And it is useful, because it allows us to execute certain operations asynchronously. This allows pyziggy to collate multiple parameter updates into one and only communicate the changes to the MQTT server.
Because of this approach it is generally fine to make lots of parameter changes in automation code. Only parameters that are changed will result in communication with the MQTT server, and when many parameters change at once, they will often be communicated in as few messages as possible.
The key takeaway here, is that callbacks exposed by pyziggy will be called on the message thread. And parameter value changes should also be initiated from the message thread. So it's generally safe to access any public parameter function from any pyziggy callback without the need for additional synchronization.
The callback of pyziggy.message_loop.MessageLoopTimer is called on the message thread as well. But the callback of the built in threading.Timer is not, so don't use it without synchronization to access your devices.
Flask service callbacks e.g. will be called on the flask thread and consequently they should be synchronized if they need to acces device parameters. You can use the message_loop.post_message function for this synchronization. Here's an example for this technique.
from flask import Flask, request
from automation import turn_off_all_lights
from pyziggy.message_loop import message_loop
app = Flask(__name__)
def http_message_handler(payload):
if "action" in payload:
action = payload["action"]
if action == "turn_off_all_lights":
turn_off_all_lights()
@app.route("/pyziggy/post", methods=["POST"])
def http_pyziggy_post():
payload = request.get_json()
def message_callback():
http_message_handler(payload)
message_loop.post_message(message_callback)
return "", 200
The message_callback function and consequently the http_message_handler function will be called on the message thread, so no synchronization is necessary beyond that point.
Another class that can be used either for synchronization, or for making a function call asynchronous, is pyziggy.message_loop.AsyncUpdater. You can call AsyncUpdater._trigger_async_update() from any thread and it will result in a call to AsyncUpdater._handle_async_update() on the message thread.
Under the hood
This section is meant to provide some information that can help with understanding failure cases.
The automation file you pass to run will be imported using importlib The imported module's dictionary will be scanned for a DevicesClient object. An AvailableDevices object meets this criteria because it inherits from DevicesClient. This object will be attached to a message loop and will carry out all communications with the MQTT server.
A consequence is that you should only have one AvailableDevices(DevicesClient) object in your automation module. The instantiation doesn't have to happen inside automation.py. You could do it in another module, and just import the object in automation.py. The important thing is that the AvailableDevices object must be visible through the module that you pass to run.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyziggy-0.9.0.tar.gz.
File metadata
- Download URL: pyziggy-0.9.0.tar.gz
- Upload date:
- Size: 72.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ec83d40915e14b5744e47a7e1756a00017111ccb697d860c2b9cda244629561a
|
|
| MD5 |
c7e06a476c07122e7d6c5c7f3a126f90
|
|
| BLAKE2b-256 |
5075c8298b1594329de8e5277b6bf08dc24fcddad3e3aa261d6a9feba8399d99
|
File details
Details for the file pyziggy-0.9.0-py3-none-any.whl.
File metadata
- Download URL: pyziggy-0.9.0-py3-none-any.whl
- Upload date:
- Size: 70.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b521f97f3c1afc99245a751d7a82251c8979c00b2967f2e518461cb5e20203ff
|
|
| MD5 |
65bde8f467c1c46fb7b6bdc976847ad3
|
|
| BLAKE2b-256 |
4fa0611a7fadaefd59f20c9d23a9e5525bac60735747617fae754d8e2d2e20f3
|