Python library for controlling DMP alarm systems
Project description
PyDMP
Python library for controlling DMP (Digital Monitoring Products) alarm systems
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 (forlisten, 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
- Panel Control:
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
Async API (Recommended)
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
DMPConnectionwithDMPTransport. - Replace
DMPConnectionSyncwithDMPTransportSync. - Use
DMPCommandNAKError(theDMPCommandNAKalias was removed). DMPPanelno longer falls back to a connection object that implementssend_command. Tests or callers that injected a stub connection should either:- Patch
panel._send_commanddirectly to your async stub, or - Provide a proper
DMPTransport+DMPProtocolif exercising the full path.
- Patch
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
--jsonto emit machine-readable JSON. The default output is a human-friendly table/message format. Thelistencommand streams newline-delimited JSON (NDJSON) when--jsonis 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 updateslatest.
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:
- Fork the repository
- Create a feature branch
- Write tests for new functionality
- Ensure all tests pass
- 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
- Issues: https://github.com/amattas/pydmp/issues
- Discussions: https://github.com/amattas/pydmp/discussions
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pydmp-1.1.0.tar.gz.
File metadata
- Download URL: pydmp-1.1.0.tar.gz
- Upload date:
- Size: 236.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
41f7933f49c4a5a0b56cee31d2180b91f9f1d55d5ac2ec5b9d540fe52b151d30
|
|
| MD5 |
7e8029acfaa50fd1af8c3109c21604c5
|
|
| BLAKE2b-256 |
9ffd8a2d0384c3a396c4eefc06ba2a837eaa212262fd878fc61769ceefc2f22c
|
Provenance
The following attestation bundles were made for pydmp-1.1.0.tar.gz:
Publisher:
aviato-ci.yml on amattas/pydmp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pydmp-1.1.0.tar.gz -
Subject digest:
41f7933f49c4a5a0b56cee31d2180b91f9f1d55d5ac2ec5b9d540fe52b151d30 - Sigstore transparency entry: 2209641954
- Sigstore integration time:
-
Permalink:
amattas/pydmp@cab26b31a93dac7d72411e5639d26d7d41c40f0a -
Branch / Tag:
refs/tags/1.1.0 - Owner: https://github.com/amattas
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
aviato-ci.yml@cab26b31a93dac7d72411e5639d26d7d41c40f0a -
Trigger Event:
push
-
Statement type:
File details
Details for the file pydmp-1.1.0-py3-none-any.whl.
File metadata
- Download URL: pydmp-1.1.0-py3-none-any.whl
- Upload date:
- Size: 51.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4e264b494def17fdbbc66a8a102bbdb822731e50829a2adb80681f1d840c8fd3
|
|
| MD5 |
d71330b771a9283f0757652fb6962443
|
|
| BLAKE2b-256 |
7ce6bc1d163517277db83c1cb576df6bc60d16759c218654236a3f3afd4377d2
|
Provenance
The following attestation bundles were made for pydmp-1.1.0-py3-none-any.whl:
Publisher:
aviato-ci.yml on amattas/pydmp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pydmp-1.1.0-py3-none-any.whl -
Subject digest:
4e264b494def17fdbbc66a8a102bbdb822731e50829a2adb80681f1d840c8fd3 - Sigstore transparency entry: 2209641971
- Sigstore integration time:
-
Permalink:
amattas/pydmp@cab26b31a93dac7d72411e5639d26d7d41c40f0a -
Branch / Tag:
refs/tags/1.1.0 - Owner: https://github.com/amattas
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
aviato-ci.yml@cab26b31a93dac7d72411e5639d26d7d41c40f0a -
Trigger Event:
push
-
Statement type: