Documentation
Control Panel
A Control Panel (CP) is the bus master: it polls one or more Peripheral Devices, sends them commands, and receives their events. In Python that role is the ControlPanel class. Each managed PD is described by a PDInfo and addressed by its bus address throughout the API.
Lifecycle
A CP moves through the same phases every time:
- Construct —
ControlPanel(pd_info_list, ...), optionally passing an event handler and a command-completion handler up front. - Register handlers (optional) — or set them later with
set_event_handler/set_command_completion_handler. - Start —
start()spawns a background thread that services the bus. This is what satisfies OSDP's requirement to refresh the library at least every 50ms; your application never has to poll on a timer. - Wait for the link —
online_wait_all()/sc_wait_all()block until the PDs are online and (if keyed) the secure channel is established. - Run — submit commands with
submit_command()and drain events withget_event()(or a registered handler). - Tear down —
teardown()stops the thread and releases the context.
cp = ControlPanel(pd_info, log_level=LogLevel.Debug)
cp.start()
cp.sc_wait_all()
...
cp.teardown()
Event and command-completion handlers are invoked from the background refresh thread, not the thread that called start(). Guard any state they share with your application, and keep them short — blocking in a handler stalls the bus. The queue-based get_event() is the alternative when you would rather pull events on your own thread.
ControlPanel
Manages one or more PDs over a shared channel.
Runs a background thread that services the bus; nothing happens until `start()` is called.
Example:
cp = ControlPanel([PDInfo(address=101, channel=my_channel)])
cp.start()
cp.online_wait(101)
cp.submit_command(101, commands.Buzzer(rep_count=3))__init__(self, list[PDInfo] pd_info_list, LogLevel log_level=LogLevel.Info, EventHandler|None event_handler=None, CommandCompletionHandler|None command_completion_handler=None)ctx(self) -> "_sys.ControlPanel"The library context. Raises once this CP has been torn down.
set_event_handler(self, EventHandler|None handler)Install the handler called for every event from every PD.
set_command_completion_handler(self, CommandCompletionHandler|None handler)Install the handler called when a submitted command settles.
submit_command(self, int address, Command command) -> boolQueue a command for a PD. Returns False if it could not be queued.
The command is copied on the way in, so it may be discarded or reused as soon as this returns.
flush_commands(self, int address) -> intDrop every queued command for a PD. Returns how many were dropped.
Each one is still reported to the completion handler, as Flushed.
get_event(self, int address, float timeout=5) -> Event|NonePop the next event from a PD, or None if none arrives in time.
A negative timeout does not block.
status(self) -> intBitmask of which PDs are online, indexed by their position.
is_online(self, int address) -> boolWhether a PD is answering.
get_num_online(self) -> intHow many PDs are answering.
sc_status(self) -> intBitmask of which PDs have a secure channel up.
is_sc_active(self, int address) -> boolWhether a PD's secure channel is up.
get_num_sc_active(self) -> intHow many PDs have a secure channel up.
get_pd_id(self, int address) -> PdIdThe identity a PD reported during setup.
check_capability(self, int address, Capability cap) -> tuple[int, int]A PD's (compliance_level, num_items) for one capability.
Both are zero when the PD does not claim the capability.
set_flag(self, int address, LibFlag flag) -> boolTurn a per-PD flag on.
clear_flag(self, int address, LibFlag flag) -> boolTurn a per-PD flag off.
enable_pd(self, int address) -> boolResume polling a PD.
disable_pd(self, int address) -> boolStop polling a PD without tearing down the CP.
is_pd_enabled(self, int address) -> boolWhether a PD is being polled.
register_file_ops(self, int address, FileOps fops) -> boolSupply the file a subsequent FileTransfer command will send.
get_file_tx_status(self, int address) -> FileTxStatus|NoneHow far the running transfer has got, or None if none is running.
get_metrics(self, int address) -> Metrics|NonePacket and command counters for a PD.
start(self)Start servicing the bus.
stop(self)Stop servicing the bus.
teardown(self)Stop the bus and release the library context.
The CP cannot be used afterwards.
online_wait(self, int address, float timeout=8) -> boolBlock until a PD comes online, or the timeout elapses.
online_wait_all(self, float timeout=10) -> boolBlock until every PD is online, or the timeout elapses.
offline_wait(self, int address, float timeout=8) -> boolBlock until a PD goes offline, or the timeout elapses.
sc_wait(self, int address, float timeout=5) -> boolBlock until a PD's secure channel comes up.
sc_wait_all(self, float timeout=5) -> boolBlock until every PD's secure channel comes up.
Events
Events are generated by a PD and delivered to the CP. You can consume them two ways, and may mix them:
- Pull — call
get_event(address)in your own loop; it returns the next queued event orNone. - Push — register an event handler (constructor argument or
set_event_handler) that the library calls as events arrive.
def on_event(address: int, event) -> int:
match event:
case events.CardRead(data=data):
print(f"PD {address}: card {data.hex()}")
return 0
cp = ControlPanel(pd_info, event_handler=on_event)
The event payload types are documented on the Events page.
Command completion
Every command you submit settles exactly once. A command-completion handler is told the outcome — acknowledged, failed, flushed before it reached the wire, or aborted at teardown — as a CompletionStatus. The library hands your handler its own re-marshalled copy of the original command, so you can tell which one completed.
from osdp import CompletionStatus
def on_command_complete(address, command, status):
if status == CompletionStatus.Ok:
print(f"{type(command).__name__} completed on PD {address}")
cp.set_command_completion_handler(on_command_complete)
Commands
Build a command dataclass and submit it to a PD by address. See Commands for every command type and its fields.
cp.submit_command(101, commands.Buzzer(reader=0, control_code=BuzzerControlCode.DefaultTone))
flush_commands(address) drops any commands still queued for a PD; each dropped command settles with CompletionStatus.Flushed.
Capabilities, IDs, and flags
get_pd_id(address) returns the PD's PdId, and check_capability(address, capability) reports whether a PD advertises a given Capability and at what compliance level. set_flag / clear_flag toggle per-PD LibFlag options at runtime.
File transfer
To send a file to a PD, register a FileOps implementation with register_file_ops(address, fops), then submit a commands.FileTransfer. Progress is readable via get_file_tx_status(address).
See also
- Peripheral Device — the other end of the link.
- Commands / Events — the message payloads.
- Secure Channel — how keying and install mode work.