Skip to main content

Blocking Python facade for driving Extron SIS devices over SSH

Project description

sismatic

A blocking Python facade for driving Extron devices over SSH using their Simple Instruction Set (SIS). The name is SIS + automatic: it handles the SIS machinery — connection pooling, the SSH handshake, byte-level framing — behind the scenes so you address a pool of devices by id and send plain instructions.

The package is a thin Python layer over a compiled Rust extension (sismatic-core), shipped as an abi3 wheel that covers CPython 3.9+ with a single build.

Install

pip install sismatic

Quickstart

from sismatic import Sis

# No connections are opened here; devices connect lazily on first use.
sis = Sis.from_file("devices.toml")

for device_id in sorted(sis.ids()):
    print(device_id)

sis.query("atrium-101", "firmware")          # read a built-in field
sis.register("atrium-101", "title", "Wk 4")  # write a metadata register
sis.command("atrium-101", "start")           # run a recorder command

Use optionally as a context manager to control the teardown of the session deterministically:

with Sis.from_file("devices.toml") as sis:
    sis.command("atrium-101", "start")
# every SSH connection is closed on exit

Configuration

from_file

from_file picks the deserializer from the extension (.toml, .json, .yaml/.yml). A config is a defaults table plus a list of devices:

TOML:

[defaults]
port = 22023
connect_secs = 5
command_secs = 3
eager = true              # open connections up front instead of on first use
sis_keepalive_secs = 120  # while warm, probe idle devices so connections stay warm
eager_retry_secs = 30     # while cold, retry connecting to unreachable eager devices

[[device]]
id = "atrium-101"
host = "10.0.0.7"
username = "admin"
password = "extron"

[[device]]
id = "annex-far"
host = "10.0.0.8"
username = "admin"
password = "extron"
connect_secs = 10  # per-device override
command_secs = 8

YAML:

---
defaults:
  username: "joe"
  password: "schmoe"
  eager: true
  sis_keepalive_secs: 120
  eager_retry_secs: 30

devices:
  - id: Hall A
    host: 10.0.150.1
  - id: Hall B
    host: 10.0.150.2
  - id: Hall C
    host: 10.0.150.3

Device groups

A [[group]] names one or more devices so they act as one — the case being more than one recorder in a room that must start together. Addressing the group id sends the instruction to every member at once, over the members' own warm connections:

[[device]]
id = "room-5-front"
host = "10.0.0.7"

[[device]]
id = "room-5-back"
host = "10.0.0.8"

[[group]]
id = "room-5"
devices = ["room-5-front", "room-5-back"]

A group id is accepted anywhere a device id is. Because a group has many members, a group call returns a dict keyed by member id (a single device still returns its scalar value):

sis.command("room-5", "start")
# {'room-5-front': 'RcdrY1', 'room-5-back': 'RcdrY1'}

sis.groups()          # -> ['room-5']

Each member must name a device defined in the same config, and group ids share the device id namespace (a group may not reuse a device's id). If any member fails, the call raises, naming which members failed.

from_config

Not using a file? Sis.from_config(mapping) takes an already-parsed dict shaped the same way, so you own the parsing. You can have a source config in any format like INI, XML, a database row, or environment variables with any parser that can produce a dictionary in the expected format.

Minimal Example with a configuration literal (no additonal parsing):

from sismatic import Sis
import logging
logging.basicConfig(level=logging.DEBUG)

devices = {
  "defaults": {
    "username": "joe",
    "password": "schmoe",
    "eager": True,
    "sis_keepalive_secs": 120,
    "eager_retry_secs": 30,
    "port": 22023,
  },
  "devices": [
      {"id": "Hall A", "host": "https://10.0.150.1"},
      {"id": "Hall B", "host": "https://10.0.150.2"},
  ],
}
sis = Sis.from_config(devices)
print(sis.ids())

Connections

Connections are lazy by default — the expensive SSH handshake is paid on a device's first instruction. Set eager to pay it up front, and sis_keepalive_secs to send a benign probe on an interval so a device's inactivity timer never lets the connection go cold. Because eager means "hold a warm connection over time," eager_retry_secs sets how often to re-attempt the handshake for an eager device that is unreachable at startup or has since dropped (0 gives up after the first failure). See the design note on eager connections and SIS keepalive for the full rationale.

API at a glance

>>> from sismatic import Sis
>>> [m for m in dir(Sis) if not m.startswith('_')]
['close', 'command', 'from_config', 'from_file', 'from_toml', 'groups', 'ids', 'query', 'register']

The wheel ships a PEP 561 py.typed marker and a type stub, so editors and mypy see full signatures, including the exact accepted instruction names. Full API docs: https://metzenseifner.github.io/sismatic/.

Example: start a recording across a batch of devices

# control_recording.py
# Starts recording and stamps a title across a batch of devices.

from dataclasses import dataclass
from sismatic import Sis

@dataclass(frozen=True)
class RecordingJob:
    """(device_id × title) as a product type. A job is exactly a pairing of
    the two — never one without the other — so the type says that, instead
    of the two strings being threaded separately through every call site as
    two positional arguments that happen to always travel together."""
    device_id: str
    title: str


def run_job(sis: Sis, job: RecordingJob) -> None:
    sis.register(job.device_id, "title", job.title)  # "title" — see Register::Title
    sis.command(job.device_id, "start")              # "start" — see Command::Start


def main() -> None:
    with Sis.from_file("devices.toml") as sis:
        jobs = [
            RecordingJob(device_id="atrium-101", title="Week 4 — Lecture"),
            RecordingJob(device_id="annex-far", title="Week 4 — Overflow Room"),
        ]
        for job in jobs:
            run_job(sis, job)


if __name__ == "__main__":
    main()

License

ECL-2.0. Part of the sismatic workspace.

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

sismatic-0.2.19.tar.gz (86.0 kB view details)

Uploaded Source

Built Distributions

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

sismatic-0.2.19-cp39-abi3-manylinux_2_28_x86_64.whl (2.3 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.28+ x86-64

sismatic-0.2.19-cp39-abi3-macosx_11_0_arm64.whl (2.5 MB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

sismatic-0.2.19-cp39-abi3-macosx_10_12_x86_64.whl (2.7 MB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

File details

Details for the file sismatic-0.2.19.tar.gz.

File metadata

  • Download URL: sismatic-0.2.19.tar.gz
  • Upload date:
  • Size: 86.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for sismatic-0.2.19.tar.gz
Algorithm Hash digest
SHA256 fde121046cc6ec9ed76ad93dab825cb9730e39cc19646f6b4f871fc1b474ae20
MD5 a4e9942df973dfc38e0631f9a176f0f7
BLAKE2b-256 5728712cae216acf0c084b90418d851dbfd9812f00d20bffc0197e0d0f7b54f1

See more details on using hashes here.

Provenance

The following attestation bundles were made for sismatic-0.2.19.tar.gz:

Publisher: pipeline.yml on metzenseifner/sismatic

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sismatic-0.2.19-cp39-abi3-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for sismatic-0.2.19-cp39-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 07a2ea2853bab7e88717afba26288e69d3a7304ff5f6bf2014bc61b3c81844c1
MD5 1704451fda053e35b3601df479fbb14e
BLAKE2b-256 8695ce1ba8fbe532a599886293ef8b55f9046c5dce66dfe1ac839ffb28abaf09

See more details on using hashes here.

Provenance

The following attestation bundles were made for sismatic-0.2.19-cp39-abi3-manylinux_2_28_x86_64.whl:

Publisher: pipeline.yml on metzenseifner/sismatic

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sismatic-0.2.19-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for sismatic-0.2.19-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 8d88c4cdfe0465290078435dacfe9952dc849fb888aab8e49d77036f20151332
MD5 9d5ad6e505898787e16509f795eade0f
BLAKE2b-256 8cee03eb621210dd4c66816e0c73e60fde081413f135c6bea1812ca9673bd7d6

See more details on using hashes here.

Provenance

The following attestation bundles were made for sismatic-0.2.19-cp39-abi3-macosx_11_0_arm64.whl:

Publisher: pipeline.yml on metzenseifner/sismatic

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sismatic-0.2.19-cp39-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for sismatic-0.2.19-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 f90f1307bf7035e8d60bc88a169136467256337b4fbc4189fbbde88468901002
MD5 175141822a91d71b48cfaa9a0ce9fcb2
BLAKE2b-256 dd9765595f9494fd153939fa86957d00c3cec7f52b56d8105e46f8f78de16561

See more details on using hashes here.

Provenance

The following attestation bundles were made for sismatic-0.2.19-cp39-abi3-macosx_10_12_x86_64.whl:

Publisher: pipeline.yml on metzenseifner/sismatic

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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