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.
- 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
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
pip install pydmpFor the command-line interface:
pip install pydmp[cli]For development with tests:
pip install -e ".[dev]"PyDMP includes a CLI for common operations. See the full guide: docs/guide/cli.md
-
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:
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 onceBlack reads configuration from pyproject.toml (line-length = 100, target-version = py310).
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())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()# 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 = 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_disarmedzone = 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_faultoutput = 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_onRecent 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 fakeCreate 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.
# 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- 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
- [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)
| 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 |
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
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
)- 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)
# 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/Online docs (MkDocs + mkdocstrings) include concepts, realtime status (S3), encryption details, and full API reference. Hosted: https://amattas.github.io/pydmp/
- Build locally:
pip install -e ".[docs]"
mkdocs serve- CI deploys docs to GitHub Pages on push to main.
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-missingContributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Write tests for new functionality
- Ensure all tests pass
- Submit a pull request
Apache License 2.0 - see LICENSE file for details.
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.
This is an independent project and is not affiliated with, endorsed by, or associated with Digital Monitoring Products (DMP).
- Issues: https://github.com/amattas/pydmp/issues
- Discussions: https://github.com/amattas/pydmp/discussions