Skip to main content

PyDMP

Python library for controlling DMP (Digital Monitoring Products) alarm systems

CI Docs Workflow Pages Python License

PyDMP is a standalone, platform-agnostic Python library for interfacing with DMP alarm panels via TCP/IP. It provides both asynchronous and synchronous APIs with full type hints, comprehensive error handling, and automatic rate limiting.

Status: Work in progress — under active development. Interfaces and CLI commands may change between releases.

Features

  • Low-level protocol communication with DMP panels
  • High-level abstractions (panels, areas, zones, outputs)
  • Both sync and async APIs with automatic rate limiting
  • Full type hints and comprehensive error handling
  • LFSR encryption for user codes
  • CLI tool for command-line control
  • Platform-independent — not tied to any home automation system

Panel Compatibility

PyDMP has been fully tested with the DMP XR150 panel. Other DMP panels using the same TCP/IP protocol should work, but have not been extensively tested.

For detailed compatibility information, see the Panel Compatibility documentation, which includes:

  • Tested panel models and firmware versions
  • Feature compatibility matrices
  • Network topology compatibility
  • Community compatibility reports
  • How to report compatibility with your panel

Installation

pip install pydmp

CLI Tool Installation

For the command-line interface:

pip install pydmp[cli]

Development Installation

For development with tests:

pip install -e ".[dev]"

CLI Overview

PyDMP includes a CLI for common operations. See the full guide.

  • Global options

    • --config, -c PATH — path to YAML file (default: config.yaml)
    • --quiet, -q — reduced logging (WARNING)
    • --debug, -d — debug logging (overrides other flags)
    • --version, -v — show version and exit
    • --help, -h — show help
  • Common option

    • --json, -j — JSON output where supported (for listen, outputs NDJSON)
  • Command sections

    • Panel Control: arm, disarm, sensor-reset
    • Status & Query: get-areas, get-zones, get-outputs, get-users, get-profiles, check-code
    • Zones: set-zone-bypass, set-zone-restore
    • Outputs: output, set-output
    • Realtime: listen

Development: Formatting with pre-commit

This repository uses Black for code formatting. To avoid formatting failures in CI, install and enable pre‑commit hooks:

pip install -e ".[dev]"           # installs black and pre-commit
pre-commit install                 # installs the git hook
pre-commit run -a                  # optional: format all files once

Black reads configuration from pyproject.toml (line-length = 100, target-version = py310).

Quick Start

import asyncio
from pydmp import DMPPanel

async def main():
    panel = DMPPanel()
    await panel.connect("192.168.1.100", "00001", "YOUR_KEY")

    # Arm/disarm area
    areas = await panel.get_areas()
    await areas[0].arm(bypass_faulted=True)  # Optional flags; instant can be set too
    await areas[0].disarm()

    # Check status (raw codes: A/D/S)
    state = await areas[0].get_state()
    if state == 'A':
        print("Armed (Away)")

    # Check zones (raw codes: N/O/S/X/L/M)
    zones = await panel.get_zones()
    for zone in zones:
        if zone.is_open:
            print(f"Open zone: {zone.number} - {zone.name}")

    await panel.disconnect()

asyncio.run(main())

Sync API (Simple Scripts)

from pydmp import DMPPanelSync

panel = DMPPanelSync()
panel.connect("192.168.1.100", "00001", "YOUR_KEY")

areas = panel.get_areas()
areas[0].arm_sync()

state = areas[0].get_state_sync()
if state == 'A':
    print("Armed (Away)")

panel.disconnect()

API Documentation

Panel Control

# Connect to panel (side-effect free)
await panel.connect(host, account, remote_key)

# Get entities
areas = await panel.get_areas()  # List[Area]
zones = await panel.get_zones()  # List[Zone]
outputs = await panel.get_outputs()  # List[Output]

# Update status (explicit)
await panel.update_status()

# Disconnect
await panel.disconnect()

Area Control

area = await panel.get_area(1)

# Arming (no user code required)
await area.arm()
await area.arm(bypass_faulted=True)  # Bypass faulted zones
await area.arm(force_arm=True)  # Force arm bad zones
await area.disarm()  # No user code sent to panel

# Status
state = area.state  # AreaState enum
is_armed = area.is_armed
is_disarmed = area.is_disarmed

Zone Control

zone = await panel.get_zone(1)

# Zone operations
await zone.bypass()
await zone.restore()

# Status
state = zone.state  # ZoneState enum
is_open = zone.is_open
is_bypassed = zone.is_bypassed
has_fault = zone.has_fault

Output Control

output = await panel.get_output(1)

# Output operations
await output.turn_on()
await output.turn_off()
await output.pulse()
await output.toggle()

# Status
state = output.state  # OutputState enum
is_on = output.is_on

Breaking Changes

Recent cleanup removed temporary compatibility aliases and shims. Update your code as follows:

  • Replace DMPConnection with DMPTransport.
  • Replace DMPConnectionSync with DMPTransportSync.
  • Use DMPCommandNAKError (the DMPCommandNAK alias was removed).
  • DMPPanel no longer falls back to a connection object that implements send_command. Tests or callers that injected a stub connection should either:
    • Patch panel._send_command directly to your async stub, or
    • Provide a proper DMPTransport + DMPProtocol if exercising the full path.

Examples (tests):

panel = DMPPanel()
panel._connection = FakeConnection()
panel._send_command = panel._connection.send_command  # route through fake

CLI Usage

Configuration

Create a config.yaml file:

panel:
  host: 192.168.1.100
  account: "00001"
  remote_key: "YOUR_KEY"

JSON output

  • Most commands support --json to emit machine-readable JSON. The default output is a human-friendly table/message format. The listen command streams newline-delimited JSON (NDJSON) when --json is provided.

Commands

# Get areas and zones
pydmp get-areas
pydmp get-zones

# Arm area(s)
pydmp arm "1,2"

# Disarm area (no user code needed)
pydmp disarm 1

# Zone control
pydmp set-zone-bypass 5
pydmp set-zone-restore 5

# Output control
pydmp output 1 on
pydmp output 2 off
pydmp output 3 pulse
pydmp output 4 toggle

# JSON output examples
pydmp get-areas --json
pydmp get-users --json
pydmp get-profiles --json
pydmp listen --json --duration 10 | jq

DMP Protocol Details

Connection

  • Protocol: TCP/IP
  • Port: 2011 (default)
  • Format: @[ACCOUNT][COMMAND]\r
  • Account: 5 digits, left-padded (e.g., 00001)
  • Rate Limit: 0.3s minimum between commands

Command Format Notes

  • [AA]: 2-digit area number (01-08)
  • [ZZZ]: 3-digit zone number (001-999)
  • [NNN]: 3-digit output number (001-999)
  • [B]: Bypass faulted zones (Y/N)
  • [F]: Force arm (Y/N)
  • [M]: Output mode (O=Off, P=Pulse, S=Steady, M=Momentary)

Core Commands

Command Description Format
!V2[KEY] Authenticate @[ACCT]!V2[KEY]\r
!V0 Drop connection @[ACCT]!V0\r
!S Get status @[ACCT]!S\r
?WB**Y001 Get zone/area status @[ACCT]?WB**Y001\r
!O[AA] Disarm area @[ACCT]!O01\r
!C[AA],[B][F] Arm area @[ACCT]!C01,NN\r
!X[ZZZ] Bypass zone @[ACCT]!X005\r
!Y[ZZZ] Restore zone @[ACCT]!Y005\r
!Q[NNN][M] Set output @[ACCT]!Q001P\r

Architecture

pydmp/
├── transport.py         # Async TCP transport (raw bytes I/O)
├── transport_sync.py    # Sync wrapper (transport + protocol)
├── protocol.py          # DMP protocol encoder/decoder
├── crypto.py            # LFSR encryption
├── panel.py             # Async panel controller
├── panel_sync.py        # Sync panel controller
├── status_server.py     # Serial 3 (S3) realtime listener
├── status_parser.py     # Parse S3 Z-frames to typed events
├── user.py              # User code model
├── profile.py           # User profile model
├── area.py              # Area abstraction
├── zone.py              # Zone abstraction
├── output.py            # Output abstraction
├── const/               # Constants (states, types, commands)
├── exceptions.py        # Exception hierarchy
└── cli.py               # CLI tool

Error Handling

PyDMP provides a comprehensive exception hierarchy:

from pydmp.exceptions import (
    DMPError,                # Base exception
    DMPConnectionError,      # Connection issues
    DMPAuthenticationError,  # Auth failed
    DMPTimeoutError,         # Operation timeout
    DMPProtocolError,        # Protocol errors
    DMPCommandNAKError,      # Command rejected
    DMPAreaError,            # Area-specific errors
    DMPZoneError,            # Zone-specific errors
    DMPOutputError,          # Output-specific errors
)

Security Considerations

  • Never log credentials or keys
  • Validate all inputs (account: 5 digits, zone: 1-999, code: 4-6 digits)
  • Don't expose sensitive info in errors
  • Support TLS if panel allows
  • Rate limit commands (0.3s minimum)

Development

# Clone repository
git clone https://github.com/amattas/pydmp.git
cd pydmp

# Install dev dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Run tests with coverage
pytest --cov=pydmp --cov-report=html

# Format code
black src/ tests/

# Type checking
mypy src/

# Linting
ruff check src/ tests/

Documentation

Online docs (Zensical) include concepts, realtime status (S3), encryption details, and full API reference. Hosted: https://amattas.github.io/pydmp/

  • Build locally:
pip install -e ".[docs]"
pydoc-markdown -I src -p pydmp > docs/api/reference.md
zensical serve
  • CI deploys versioned docs to GitHub Pages: every push to main updates dev, every release publishes its version and updates latest.

Testing

PyDMP includes comprehensive unit tests for core functionality:

# Run all tests
pytest

# Run specific test file
pytest tests/test_protocol.py

# Run with coverage
pytest --cov=pydmp --cov-report=term-missing

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Write tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

License

Apache License 2.0 - see LICENSE file for details.

Future Enhancements

Emergency Triggers

Emergency events (Panic, Fire, Police, Medical) are not yet implemented. In the reference implementation, these are triggered by pulsing configurable output relays rather than sending dedicated panel commands. For example:

  • Configure which output number corresponds to each emergency type
  • Trigger emergency by pulsing that output: !Q[output]P

This functionality may be added in a future version.

Disclaimer

This is an independent project and is not affiliated with, endorsed by, or associated with Digital Monitoring Products (DMP).

Support

Metadata

Release files for pydmp 1.1.0

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

Source distribution (sdist)

Source distribution for pydmp 1.1.0
File Size Uploaded
pydmp-1.1.0.tar.gz 236.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pydmp 1.1.0
File Interpreter ABI Platform
pydmp-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 287.3 kB

Release files / pydmp-1.1.0.tar.gz

Download URL pydmp-1.1.0.tar.gz
Size 236.0 kB
Tags Source
SHA-256 checksum
How to use checksums
41f7933f49c4a5a0b56cee31d2180b91f9f1d55d5ac2ec5b9d540fe52b151d30
BLAKE2b-256 checksum
How to use checksums
9ffd8a2d0384c3a396c4eefc06ba2a837eaa212262fd878fc61769ceefc2f22c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.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 Jul 21, 2026.

Transparency log

Release files / pydmp-1.1.0-py3-none-any.whl

Download URL pydmp-1.1.0-py3-none-any.whl
Size 51.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4e264b494def17fdbbc66a8a102bbdb822731e50829a2adb80681f1d840c8fd3
BLAKE2b-256 checksum
How to use checksums
7ce6bc1d163517277db83c1cb576df6bc60d16759c218654236a3f3afd4377d2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.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 Jul 21, 2026.

Transparency log
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