Documentation

Python Getting Started

The libosdp Python package wraps the C library so you can build and test OSDP devices without writing any C. Install it from PyPI:

pip install libosdp

The package exposes two modules:

  • osdp — the recommended, Python-friendly API documented here.
  • osdp_sys — a thin wrapper directly over the C API. It is a low-level building block for osdp and is not recommended for direct use.

A Channel to talk over

LibOSDP does not own the transport. You implement a Channel that moves bytes over your wire (serial, TCP, etc.) and hand it to the device. A pyserial-backed channel looks like this:

import serial
from osdp import Channel

class SerialChannel(Channel):
    def __init__(self, device: str, speed: int = 115200):
        self.dev = serial.Serial(device, speed, timeout=0)

    def read(self, max_read: int) -> bytes:
        return self.dev.read(max_read)

    def write(self, data: bytes) -> int:
        return self.dev.write(data)

    def flush(self):
        self.dev.flush()

    def __del__(self):
        self.dev.close()

Applications must call into the library frequently (the device runs a background thread that does this for you when you call start()); see Secure Channel for how keying works.

Control Panel mode

A Control Panel (CP) manages one or more Peripheral Devices (PDs). Each PD is described by a PDInfo.

from osdp import ControlPanel, PDInfo, KeyStore, LogLevel, Command, CommandLEDColor

channel = SerialChannel("/dev/ttyUSB0")

# Setting scbk=None puts the PD in install mode; KeyStore.gen_key() provisions
# a random Secure Channel Base Key (SCBK) instead.
pd_info = [
    PDInfo(101, channel, scbk=KeyStore.gen_key()),
]

cp = ControlPanel(pd_info, log_level=LogLevel.Debug)
cp.start()
cp.sc_wait_all()   # wait until the secure channel is established

led_cmd = {
    'command': Command.LED,
    'reader': 1,
    'led_number': 0,
    'control_code': 1,
    'on_count': 10,
    'off_count': 10,
    'on_color': CommandLEDColor.Red,
    'off_color': CommandLEDColor.Black,
    'temporary': False,
}

while True:
    # Pull any event the PD reported
    event = cp.get_event(pd_info[0].address)
    if event:
        print(f"CP: Received event {event}")

    # Push a command to PD-101
    cp.send_command(pd_info[0].address, led_cmd)

The dictionaries you pass to send_command() are described in Commands; the ones you read from get_event() are described in Events.

Command completion callback (optional)

from osdp import CompletionStatus

def on_command_complete(address, command, status):
    if status == CompletionStatus.Ok:
        print("command completed")

cp.set_command_completion_handler(on_command_complete)

Peripheral Device mode

A Peripheral Device (PD) answers a CP. Describe it with PDInfo and declare its capabilities.

from osdp import PeripheralDevice, PDInfo, PDCapabilities, Capability, Event, CardFormat

channel = SerialChannel("/dev/ttyUSB0")

# scbk=None puts the PD in install mode (accepts a key from the CP)
pd_info = PDInfo(101, channel, scbk=None)

pd_cap = PDCapabilities([
    (Capability.OutputControl, 1, 1),
    (Capability.LEDControl, 2, 1),
    (Capability.AudibleControl, 1, 1),
    (Capability.TextOutput, 1, 1),
])

pd = PeripheralDevice(pd_info, pd_cap)
pd.start()
pd.sc_wait()

card_event = {
    'event': Event.CardRead,
    'reader_no': 1,
    'direction': 1,
    'format': CardFormat.ASCII,
    'data': bytes([9, 1, 9, 2, 6, 3, 1, 7, 7, 0]),
}

while True:
    pd.notify_event(card_event)

    cmd = pd.get_command()
    if cmd:
        print(f"PD: Received command: {cmd}")

Event completion callback (optional)

from osdp import CompletionStatus

def on_event_complete(event, status):
    if status == CompletionStatus.Flushed:
        print("event removed by flush")

pd.set_event_completion_handler(on_event_complete)

Full examples

Runnable command-line versions of both apps live in the LibOSDP repo: