Skip to main content

pycrestron-cip

CI PyPI Python License: MIT Version

pycrestron-cip is a typed asyncio client for CIP (Crestron-over-IP), the protocol that touch panels and XPanel use to talk to a Crestron control processor. Your code connects as a panel IP ID, sees what that panel would see (digital, analog and serial joins) and can press its buttons.

It needs nothing from the processor's program: no SIMPL changes, no extra modules. If a panel IP ID is defined, you can connect to it. That makes it a good base for Home Assistant integrations and other home automation.

Status: alpha. Tested against a Crestron 3-Series processor. The API may still change.

Not affiliated with, endorsed by or supported by Crestron Electronics, Inc. "Crestron" is a trademark of Crestron Electronics, Inc.

Features

  • Pure asyncio, no runtime dependencies, fully typed (py.typed)
  • Registration, update request / end-of-query sync, heartbeats in both directions
  • Correct TCP framing: frames split across reads, several frames per read, several joins per sub-packet (processors pack their initial dump; older clients lose most of it)
  • Liveness timeout: a silent, half-open connection is noticed and reconnected
  • Automatic reconnect with backoff, and a long retry interval for an undefined IP ID
  • Input caches with callbacks; latched outputs are replayed after every reconnect
  • Buttons: press / release with auto-repeat, and pulse
  • Serial joins in UTF-8 and UTF-16, chunked long strings, short and extended forms, smart objects
  • Optional TLS (port 41796) and username/password authentication
  • FakeProcessor: a CIP server for your own tests, built from real captures
  • pycrestron-cip-probe: a read-only command that records everything a processor sends

Why pycrestron-cip

Most open-source CIP clients stop at "it connects". We read the source of every panel-side CIP client we could find, ran their parsers against a fake processor, and built this library to close the gaps. Then we verified it on a real processor.

pycrestron-cip Other open-source CIP clients¹
Reads every join in a packed sub-packet (processors send their initial dump this way) ✅ digital and analog, verified on a 3-Series none do both; 6 of 7 keep only the first digital join
Answers the processor's heartbeat requests ✅ 1 of 7
Notices a silent, half-open connection by itself ✅ liveness timeout 0 of 7
Reconnects with backoff and replays latched outputs ✅ both backoff: 2 of 7 · replay: 2 of 7
Frames split across TCP reads ✅ 2 of 6 TCP clients
Serial text: UTF-8, UTF-16 (unicode flag), long strings sent in chunks ✅ all three UTF-16: 0 of 7 · chunks: 0 of 7
A non-ASCII byte cannot break the receive path ✅ 3 of the 6 that decode serials
Smart-object joins, with the object ID ✅ inbound 0 of 7 complete
A fake processor you can use in your own tests ✅ pycrestron_cip.testing 0 of 7 ship one
Automated tests ✅ 41 tests, 94 % coverage, CI on Python 3.13 and 3.14 3 of 7

¹ Seven XPanel-style CIP clients in Python, Swift, JavaScript and Node, each reviewed at its latest commit in September 2026. Projects that emulate a processor, proxy TLS on the processor, or use a different protocol are not counted. Other projects were tested against a fake processor, not real hardware.

Why it matters

  • Correct state from the first second. After connecting, a processor packs several joins into one sub-packet of its initial dump. A client that reads only the first one starts with most values missing, and a paged panel may not resend them until they change. That is how a light ends up shown as off while it is on.
  • Connections that heal themselves. A client that never notices a half-open socket can sit "connected" while nothing arrives. This one sends and answers heartbeats, times out when the processor goes quiet, reconnects with backoff and restores its outputs.
  • Any text, any language. Room names and labels come through intact: UTF-8, UTF-16 and long chunked strings, with a safe fallback for stray bytes.
  • Built to be depended on. Pure asyncio, fully typed, no runtime dependencies, so it drops into Home Assistant and other asyncio apps. FakeProcessor and pycrestron-cip-probe let you test your integration without, and then against, real hardware.

Honest limits: sending to smart objects is not supported yet; TLS and username/password authentication are implemented but not yet verified on hardware (captures welcome); the WebSocket/CH5 transport used by some 4-Series web panels is not covered.

Requirements

  • Python 3.13 or newer
  • A Crestron processor reachable on TCP 41794 (or 41796 for TLS)
  • A panel IP ID defined in the processor's program that nothing else is using (see below)

Installation

pip install pycrestron-cip

For local development:

git clone https://github.com/Antonio112009/pycrestron-cip.git
cd pycrestron-cip
pip install -e ".[dev]"

Quick Start

import asyncio

from pycrestron_cip import CipClient, JoinUpdate


def on_join(update: JoinUpdate) -> None:
    print(f"{update.type}{update.join} = {update.value!r}")


async def main() -> None:
    async with CipClient("192.168.1.10", 0x03) as panel:
        panel.subscribe(on_join)  # everything the processor sends
        await panel.pulse(101)  # tap button d101
        print(panel.get_analog(361))  # last value of a361
        panel.set_serial(10, "hello")  # latched serial output
        await asyncio.sleep(30)


asyncio.run(main())

API

Call What it does
await connect() / async with CipClient(...) Connect, register, wait for the initial sync. Raises IpidNotDefinedError, AuthError, CipTimeoutError or CipConnectionError
await close() Say goodbye to the processor and stop all tasks
get_digital/analog/serial(join) Last value the processor sent (the default when never sent)
has_input(join_type, join) Whether the processor has sent this join at all
subscribe(cb, join_type=None, join=None) Callback for incoming joins; returns an unsubscribe function
subscribe_state(cb) Callback for connection state changes (ConnectionState)
set_digital/analog/serial(join, value) Latched outputs, replayed after every reconnect
press(join) / release(join) / await pulse(join) Buttons (held buttons auto-repeat)
request_update() Ask the processor to send its values again
available, state, await wait_ready() Connection status
snapshot(), stats, last_error Diagnostics (no credentials)

Smart-object joins: pass smart_object= to the getters; JoinUpdate.smart_object tells you where an update came from.

Options

Option Default Meaning
port 41794 (41796 with TLS) TCP port
ssl None True for TLS without certificate checks, or an ssl.SSLContext
username, password None Credentials, if the processor requires authentication
connect_timeout 10 s Connect, register and sync must finish within this time
heartbeat_interval 15 s How often we send a heartbeat (processors answer, they do not ask)
liveness_timeout 35 s Nothing received for this long means the connection is dead
reconnect True Reconnect by itself after a successful first connect
backoff_min, backoff_max 1 s, 60 s Reconnect backoff
button_repeat 0.5 s Repeat interval for held buttons

Things to know about processors

  • The processor sends a panel only changes relative to what it believes the panel already shows. After a (re)connect, the initial dump contains non-default values only, and a "paged" panel may not resend a value until it changes.
  • Button-style digitals are released by the processor unless repeated within about 0.5 s. press() repeats for you until release().
  • Two clients on one IP ID share one panel. On paged panels that means one shared page: one client's navigation changes what the other sees. Give automation its own IP ID where you can.
  • Directly setting an analog that the program drives (for example a dimmer level) often does nothing: the program expects the panel's raise/lower buttons.

Testing Your Code

FakeProcessor is a small CIP server that behaves like a real processor as far as observed: it greets, registers panels, answers heartbeats, packs its initial dump and ends it with end-of-query. It can also misbehave: wrong IP ID, silence, dropped connections, split or coalesced frames.

from pycrestron_cip import CipClient
from pycrestron_cip.testing import FakeProcessor


async def test_scene_indicator():
    async with FakeProcessor(ipids={0x03}) as proc:
        proc.digital[350] = True
        async with CipClient("127.0.0.1", 0x03, port=proc.port) as panel:
            assert panel.get_digital(350)
            await panel.pulse(101)
        assert [u.join for u in proc.joins_received()] == [101, 101]  # press, release

Probing a Processor

pycrestron-cip-probe (or python -m pycrestron_cip.probe) connects as a panel, records everything the processor sends and disconnects. It sends only what the protocol requires (no button presses, no join values):

pycrestron-cip-probe 192.168.1.10 0x03 --seconds 120 --log probe.log --json probe.json

Stop anything else that uses the same IP ID first. Please do not publish probe logs from a real installation: they contain room names, addresses and whatever the panel shows.

Protocol

docs/protocol.md describes the wire format as implemented, what was verified on hardware, other implementations and the open questions.

Contributing

See CONTRIBUTING.md. Captures from other processors (4-Series, TLS, authentication) are especially welcome.

License

MIT

Release files for pycrestron-cip 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pycrestron-cip 0.1.1
File Size Uploaded
pycrestron_cip-0.1.1.tar.gz 31.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pycrestron-cip 0.1.1
File Interpreter ABI Platform
pycrestron_cip-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 53.9 kB

Release files / pycrestron_cip-0.1.1.tar.gz

Download URL pycrestron_cip-0.1.1.tar.gz
Size 31.0 kB
Tags Source
SHA-256 checksum
How to use checksums
16c5d05bbc4f2ab70fde9c5daba60ccd1adbf54f3a4defbd2954f1269b143e0c
BLAKE2b-256 checksum
How to use checksums
c0eb07b0e9e3735e813ea7be32609dd55b07b0d4abc24ee11915bb6607b2e593
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.

Transparency log

Release files / pycrestron_cip-0.1.1-py3-none-any.whl

Download URL pycrestron_cip-0.1.1-py3-none-any.whl
Size 22.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8a9003b9bc1ac8398fbd53b8be2cbd6483279696aa676437902423f6944b223a
BLAKE2b-256 checksum
How to use checksums
c4eb3b2c044ed83e3f334abae8dbe7a815a3b4af5f48829a7dd83bb0089a1607
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.2

2 release files

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page