Documentation

Events

Events travel from a PD to a Control Panel. In Python each event is a frozen dataclass in the osdp.events module. A PD reports one with PeripheralDevice.submit_event; a CP receives it from ControlPanel.get_event or a registered event handler.

Because each event is its own type, a match statement gives you exactly the right fields with no dictionary lookups:

match cp.get_event(101):
    case events.CardRead(data=data, format=fmt):
        print(f"card {data.hex()} ({fmt.name})")
    case events.KeyPress(data=keys):
        print(f"keys {keys!r}")
    case events.Status(report=report):
        print(f"status {report!r}")

Card and keypad

CardRead

A card was presented to a reader. For the raw formats, bits is the exact bit count and data is the packed payload; for ASCII, data is the character bytes. format is a CardFormat:

classCardRead

A card was presented to a reader.

For the raw formats the card is a bit string whose length is not necessarily a multiple of eight, so `bits` carries the real length and `data` is that many bits padded out to whole bytes. For the ASCII format the card is bytes and `bits` does not apply.

Example:

A 26-bit Wiegand card needs four bytes to hold its 26 bits:

>>> event = CardRead(format=CardFormat.Wiegand,
...                  data=bytes([0x01, 0x02, 0x03, 0x40]), bits=26)
>>> event.bits
26

Leave `bits` out and it is taken to be all of the data:

>>> event = CardRead(format=CardFormat.Wiegand, data=b"\\x01\\x02")
>>> event.bits
16
Fields
int
Zero-based reader number.
CardFormat
How to read data.
bytes
The card data, at most 64 bytes.
None
Length of the card data in BITS.
enumCardFormat

Encoding of the data in a `CardRead` event.

Values
Unspecified
Raw bits in an unspecified format; bits is meaningful.
Wiegand
Raw Wiegand bits; bits is meaningful.
ASCII
Deprecated.

KeyPress

Keys were entered on a reader's keypad.

classKeyPress

Keys were pressed on a reader's keypad.

Example:

>>> event = KeyPress(data=b"1234")
>>> event.data
b'1234'
Fields
int
Zero-based reader number.
bytes
One byte per key, at most 64.

Biometrics

BioRead

A PD's answer to a commands.BioRead — the captured template and its quality. status is a BioStatus:

classBioRead

A PD's answer to a `commands.BioRead`.

The template and quality are only meaningful when `status` is Success. Note there is no format field: the reply does not restate the encoding that was asked for.

Example:

>>> event = BioRead(status=BioStatus.Success,
...                 type=BioType.RightThumbPrint, quality=200,
...                 data=b"template")
>>> event.status.name
'Success'

A failed scan carries no template:

>>> BioRead(status=BioStatus.Timeout).data
b''
Fields
int
Zero-based reader number.
BioStatus
Whether the scan succeeded.
BioType
Which biometric was captured.
bytes
The captured template.

A template larger than one packet is delivered only when both roles enable the opt-in multi-part flag; see Multi-part Messages.

enumBioStatus

Outcome of a biometric read or match.

The values are sparse; UnknownError is 0xFF.

Values
Success
The scan or match completed; the reply's other fields are valid.
Timeout
The reader gave up waiting for the subject.
UnknownError

BioMatch

A PD's answer to a commands.BioMatch — whether the scan matched and the score.

classBioMatch

A PD's answer to a `commands.BioMatch`.

The score is only meaningful when `status` is Success.

Example:

>>> event = BioMatch(status=BioStatus.Success, score=250)
>>> event.score
250
Fields
int
Zero-based reader number.
BioStatus
Whether the match ran.

Smartcard

A PD's answers to the smartcard commands, carrying the payload reassembled from the multi-part reply. On the PD side, submit these to answer the corresponding command — from inside the command handler for an inline reply, or later for delivery on a subsequent poll.

classPivData

A PD's answer to a `commands.PivData`: the PIV object contents.

On the CP, the event carries the payload reassembled from the multi-part reply. On the PD, submit this event (from within the command callback for an inline reply, or later for delivery on a subsequent poll) and libosdp fragments it on the wire.

Fields
bytes
Reassembled reply payload; non-empty.
classGenAuth

A PD's answer to a `commands.GenAuth`: the authenticate response.

Delivery semantics match `PivData`.

Fields
bytes
Reassembled reply payload; non-empty.
classCrAuth

A PD's answer to a `commands.CrAuth`: the challenge response.

Delivery semantics match `PivData`.

Fields
bytes
Reassembled reply payload; non-empty.

Status and manufacturer

Status

A status report from the PD (inputs, outputs, tamper, power). type is a StatusReportType.

classStatus

A PD's status report.

Example:

>>> event = Status(type=StatusReportType.Input, report=bytes([0, 1, 0]))
>>> list(event.report)
[0, 1, 0]
Fields
StatusReportType
Which set of status bits this carries.
bytes
One byte per entry, at most 64.

Manufacturer

Vendor-specific replies and status/error notifications.

classManufacturerReply

A vendor-specific reply from the PD.

Example:

>>> event = ManufacturerReply(vendor_code=0x00030201, data=b"\\x01")
>>> event.vendor_code
197121
Fields
int
The vendor's 3-byte IEEE OUI.
bytes
Vendor-defined payload, at most 128 bytes.
classManufacturerStatus

A vendor-specific status reply from the PD.

Unlike `ManufacturerReply` this carries no vendor code; interpret it using the vendor code from the PD's id.

Example:

>>> event = ManufacturerStatus(data=b"\\x01\\x02")
>>> ManufacturerStatus().data
b''
Fields
bytes
Vendor-defined payload, at most 128 bytes.
classManufacturerError

A vendor-specific error reply from the PD.

Carries the same payload as `ManufacturerStatus` but is a distinct event, so a handler can tell an error apart from a status without inspecting the bytes.

Example:

>>> event = ManufacturerError(data=b"\\xff")
>>> event.ID.name
'ManufacturerError'
Fields
bytes
Vendor-defined payload, at most 128 bytes.

Notification

A library-generated notification, delivered as an event when notifications are enabled. Beyond the raw type/arg0/arg1 fields, Notification exposes computed properties (succeeded, sc_active, pd_online, file_id, file_tx_outcome) that decode the arguments for the notification kind. type is a NotificationType.

classNotification

A library notification delivered to a CP app.

Produced by the library, not by a peer, when `LibFlag.EnableNotification` is set. Apps never submit this.

Each `type` carries its own typed fields; only those relevant to the type are meaningful. The properties below name the common readings.

Example:

>>> note = Notification(type=NotificationType.Command,
...                     command=int(CommandId.LED), success=True)
>>> note.command_id.name
'LED'
>>> note.succeeded
True
Fields
NotificationType
What happened.
int
Which command completed (CommandId).
bool
Whether that command succeeded.
Methods
command_id(self) -> CommandId

Which command completed. Only for NotificationType.Command.

succeeded(self) -> bool

Whether that command succeeded. Only for NotificationType.Command.

sc_active(self) -> bool

Whether the secure channel is up.

Only for NotificationType.SecureChannelStatus.

pd_online(self) -> bool

Whether the PD is reachable.

Only for NotificationType.PeripheralDeviceStatus.

file_id(self) -> int

Which file was transferred.

Only for NotificationType.Multipart* (reads object_id).

pd_id(self) -> PdId

The collected PD identity. Only for NotificationType.PdId.

mp_outcome(self) -> MpOutcome

How the transfer ended.

Only for NotificationType.MultipartDone (reads outcome).

See also