Skip to main content

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

pyziggy-0.9.0.tar.gz (72.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyziggy-0.9.0-py3-none-any.whl (70.2 kB view details)

Uploaded Python 3

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

Hashes for pyziggy-0.9.0.tar.gz
Algorithm Hash digest
SHA256 ec83d40915e14b5744e47a7e1756a00017111ccb697d860c2b9cda244629561a
MD5 c7e06a476c07122e7d6c5c7f3a126f90
BLAKE2b-256 5075c8298b1594329de8e5277b6bf08dc24fcddad3e3aa261d6a9feba8399d99

See more details on using hashes here.

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

Hashes for pyziggy-0.9.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b521f97f3c1afc99245a751d7a82251c8979c00b2967f2e518461cb5e20203ff
MD5 65bde8f467c1c46fb7b6bdc976847ad3
BLAKE2b-256 4fa0611a7fadaefd59f20c9d23a9e5525bac60735747617fae754d8e2d2e20f3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page