Skip to main content

Trinnov Altitude Python Library

CI PyPI Python Version

Async Trinnov Altitude client for long-running integrations (Home Assistant primary target).

Version 2.0

Version 2.x is a clean break from 1.x.

  • No compatibility shims
  • New lifecycle (start / wait_synced / stop)
  • New state model (client.state)
  • Optional command ACK handling

Read the migration guide: docs/MIGRATION_V2.md

Installation

pip install trinnov-altitude

Quick Start

import asyncio

from trinnov_altitude.client import TrinnovAltitudeClient


async def main() -> None:
    client = TrinnovAltitudeClient(host="192.168.1.90")

    try:
        await client.start()
        await client.wait_synced(timeout=10)

        await client.volume_set(-30.0)
        await client.mute_on()

        print(client.state.volume)
        print(client.state.source)
    finally:
        await client.stop()


asyncio.run(main())

Lifecycle

  • await client.start() connects, bootstraps, and starts the read loop.
  • await client.wait_synced() waits until welcome + catalogs + current indices are observed.
  • await client.stop() stops listener and disconnects cleanly.

Liveness & reconnect

The control connection is a long-lived TCP push session, so a silent read is ambiguous: a healthy link is quiet whenever nothing is changing, but a dead link (an idle-killed half-open socket, or a processor whose control thread has wedged while its TCP stack still ACKs) looks identical. To tell them apart, when the link has been quiet for heartbeat_interval seconds the client sends a read-only probe (get_current_state); if no traffic arrives within heartbeat_timeout, the link is treated as dead and auto_reconnect kicks in. TcpTransport also enables SO_KEEPALIVE as an OS-level backstop.

client = TrinnovAltitudeClient(
    host="192.168.1.90",
    heartbeat_interval=20.0,  # seconds of quiet before a liveness probe (None disables)
    heartbeat_timeout=5.0,    # seconds to wait for the probe response before reconnecting
)

Protocol Semantics

The client parses raw messages first, then normalizes them into canonical state events. This keeps protocol quirks isolated and keeps the state reducer deterministic.

  • Canonical identity:
    • CURRENT_PRESET <n>
    • CURRENT_PROFILE <n> or index-only PROFILE <n>
    • DECODER ... UPMIXER <mode>
  • Optional catalogs:
    • Presets via LABELS_CLEAR + LABEL <n>: <name>
    • Sources via PROFILES_CLEAR + PROFILE <n>: <name>
  • Quirk profiles:
    • altitude_ci is selected when IDENTS includes altitude_ci
    • In that profile, META_PRESET_LOADED <n> is normalized as a source-change signal

Catalog messages may arrive late, be refreshed, or be absent. Consumers should not assume labels are always present.

Events

def on_event(event, message):
    if event == "connected":
        ...
    elif event == "disconnected":
        ...
    elif event == "received_message":
        ...

client.register_callback(on_event)

Callback exceptions are isolated and logged (they do not crash the listener).

HA Adapter

Use trinnov_altitude.adapter.AltitudeStateAdapter to convert mutable runtime state into immutable snapshots plus typed deltas/events:

  • snapshot: stable full-state view for coordinator data
  • deltas: field-level changes since previous snapshot
  • events: integration-friendly event stream (volume, mute, source, preset, etc.)

You can wire this directly through the client:

from trinnov_altitude.adapter import AltitudeStateAdapter

adapter = AltitudeStateAdapter()

def on_update(snapshot, deltas, events):
    ...

handle = client.register_adapter_callback(adapter, on_update)
# later: client.deregister_adapter_callback(handle)

For Home Assistant coordinator/event-bus integration, use trinnov_altitude.ha_bridge:

  • coordinator_payload(snapshot)
  • to_ha_events(events)
  • build_bridge_update(snapshot, deltas, events)

Command ACKs

You can use fire-and-forget commands (default) or explicit ACK waiting:

await client.volume_set(-20.0)
await client.command("volume -20", wait_for_ack=True, ack_timeout=2.0)

Development

uv sync --group dev
uv run ruff check .
uv run ruff format --check .
uv run ty check trinnov_altitude
uv run pytest -v

Or use task wrappers:

task dev
task check

Real Device Integration Tests (Read-Only)

The test suite includes a manual, read-only integration tier for validating behavior against real hardware.

  • Marker: integration_real
  • Opt-in gate: TRINNOV_ITEST=1
  • Target host: TRINNOV_HOST=<ip-or-hostname>
  • Optional port override: TRINNOV_PORT=44100
  • If the device is offline/unreachable, tests are skipped.

These tests intentionally avoid mutating commands (no power/preset/source/volume state changes).

TRINNOV_ITEST=1 TRINNOV_HOST=192.168.30.3 task test:integration-real

Pyx (optional)

Pyx is optional in this repo. You can keep publishing to PyPI/TestPyPI only.

  • Install via Pyx: authenticate uv with PYX_API_KEY and configure your Pyx index URL in uv (uv add --index ... / uv sync).
  • Publish to Pyx: run the Release workflow manually with target=pyx after setting repository secrets PYX_API_KEY and PYX_PUBLISH_URL.
  • No dual-publish requirement: use Pyx when you need private/internal package distribution or policy control.

Release

  1. Merge conventional-commit changes to master.
  2. Wait for the release-please workflow to open/update a release PR.
  3. Review and merge the release PR (this updates CHANGELOG.md and __version__).
  4. Release Please creates the GitHub Release and tag.
  5. The Release workflow publishes artifacts to PyPI automatically for published releases.
  6. For TestPyPI or Pyx-only publishing, run Release manually with workflow_dispatch.

Maintenance

Release files for trinnov-altitude 3.3.6

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

Source distribution (sdist)

Source distribution for trinnov-altitude 3.3.6
File Size Uploaded
trinnov_altitude-3.3.6.tar.gz 257.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for trinnov-altitude 3.3.6
File Interpreter ABI Platform
trinnov_altitude-3.3.6-py3-none-any.whl Python 3 none any Details

Total release size: 289.3 kB

Release files / trinnov_altitude-3.3.6.tar.gz

Download URL trinnov_altitude-3.3.6.tar.gz
Size 257.6 kB
Tags Source
SHA-256 checksum
How to use checksums
3db5990e2812e17f1fad56b21629e981289cfd1f6963fa8a7515686752635e4a
BLAKE2b-256 checksum
How to use checksums
791da82fcda96c6722b017d55970e1aa6c7ac921e9d9a3f6797161babcd50824
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

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 Jun 28, 2026.

Transparency log

Release files / trinnov_altitude-3.3.6-py3-none-any.whl

Download URL trinnov_altitude-3.3.6-py3-none-any.whl
Size 31.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
09b3a2ca978ec550324b4eb3664d242fb000058394f130fdfd52242a05075504
BLAKE2b-256 checksum
How to use checksums
3f7433730f72ecef5d26bba9a50014a6d368195bdda650f9956b6e444ee6c4b6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

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 Jun 28, 2026.

Transparency log

Release history Release notifications | RSS feed

3.4.1

2 release files

3.4.0

2 release files

3.3.12

2 release files

3.3.11

2 release files

3.3.10

2 release files

3.3.9

2 release files

3.3.8

2 release files

3.3.7

2 release files

This release

3.3.6 This release

2 release files

3.3.5

2 release files

3.3.4

2 release files

3.3.3

2 release files

3.3.2

2 release files

3.3.0

2 release files

3.2.5

2 release files

3.2.4

2 release files

3.2.3

2 release files

3.2.2

2 release files

3.2.1

2 release files

3.2.0

2 release files

3.1.3

2 release files

3.1.2

2 release files

3.1.1

2 release files

3.1.0

2 release files

3.0.0

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.2.8

2 release files

1.2.6

2 release files

1.2.5

2 release files

1.2.4

2 release files

1.2.3

2 release files

1.2.2

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.1.27

2 release files

0.1.26

2 release files

0.1.24

2 release files

0.1.23

2 release files

0.1.22

2 release files

0.1.21

2 release files

0.1.20

2 release files

0.1.18

2 release files

0.1.17

2 release files

0.1.16

2 release files

0.1.15

2 release files

0.1.14

2 release files

0.1.13

2 release files

0.1.12

2 release files

0.1.11

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

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