Documentation

Osprio IO

Osprio IO is an OSDP peripheral device (PD). It exposes opto-isolated voltage inputs and relay outputs to an access control unit over RS-485, and it is commissioned from a configuration page the module serves over its own WiFi access point.

Two variants share this firmware and everything described here: IO4 with four channels in each direction, and IO8 with eight. The number in the name is the count per direction, so an IO4 carries eight points in total. Nothing else differs — same protocol behaviour, same portal, same output model.

It is the one piece of Osprio hardware that gets installed rather than carried. Osprio Mini and Osprio Pro are instruments you bring to a bus; Osprio IO joins a bus as a node and stays there.

Platform overview

  • Role: peripheral device only. Osprio IO never acts as an ACU.
  • Points: four inputs and four outputs on IO4; eight of each on IO8.
  • Bus: RS-485, half duplex, A/B screw terminal, multi-drop alongside the readers already on the segment.
  • Protocol stack: LibOSDP — the same implementation used in Osprio's own tooling.
  • Configuration: an onboard WiFi portal, plus osdp_COMSET and osdp_KEYSET from the ACU.
  • Power: USB-C, or 9–24 V DC on the power-in terminal.

Everything the module does is visible on the OSDP bus. There is no companion app, no cloud service, and no second control channel.

OSDP interface

Identity

The reply to osdp_ID carries a vendor code, a model number, a hardware version, a serial number derived from the module's factory MAC address, and the firmware version. The serial number is also what the default WiFi network name is derived from, so the label on the enclosure, the network you connect to, and the device the ACU sees all agree.

Address and baud rate

SettingValues
PD address0126 (0x000x7E)
Baud rate9600, 19200, 38400, 57600, 115200, 230400

Both are set in the WiFi portal and both are stored in non-volatile memory. An unprovisioned module comes up at address 1 and 9600 baud, the rate every ACU is required to support.

The ACU can change them at runtime with osdp_COMSET. The new values are written to non-volatile memory only after the reply has actually gone out — if the reply were lost and the settings had already been committed, the module would come back on an address the ACU is not using.

Capabilities

Osprio IO advertises exactly what it can do:

CapabilityLevelItems
Contact status monitoring014 (IO4) or 8 (IO8)
Output control044 (IO4) or 8 (IO8)

Contact status level 01 describes a circuit monitored without supervision, encoded to active/inactive per the PD's default interpretation. That is what a debounced input does. End-of-line resistor supervision needs analogue sensing this hardware does not have, so a higher level is not claimed.

Output control level 04 covers direct activation and deactivation, timed operation, and configuration of each output's inactive state. Only hardware drive-current reporting sits above it.

These counts are also a contract for the status replies: a report whose entry count does not match the advertised capability is rejected outright, so the advertised count and the number of points the firmware is built for can never disagree. That is also why the variant is a build-time property of the firmware rather than something set in the portal.

Commands

CommandBehaviour
osdp_OUTApplies an output control code to one output. An unknown output number or control code is NAKed rather than silently ignored.
osdp_ISTATReturns one entry per input, active or inactive.
osdp_OSTATReturns one entry per output, reflecting the level actually being driven.
osdp_LSTATReturns exactly two entries, tamper then power, both reported normal. Neither is sensed on the current hardware revision, and a short report would be rejected, so the module answers honestly rather than omitting them.
osdp_COMSETAccepts a new address and baud rate; persisted on completion.
osdp_KEYSETStores a new Secure Channel Base Key.

Unsolicited input reporting

The module does not wait to be asked. When a debounced input settles at a new value, it submits an input status report as an OSDP event, so the ACU learns about a door contact or a request-to-exit on the next poll rather than on its own polling schedule for osdp_ISTAT.

An input report always carries the complete picture of every input on the board. If another input changes while a report is still in flight, the change is folded into the next report instead of being queued behind it — the ACU never receives a stale partial view.

Secure channel

Osprio IO runs full OSDP secure channel with a 16-byte SCBK. There are two ways to get the key onto the module:

  • From the portal. Type or generate the key on the configuration page before installation.
  • From the ACU, in install mode. The module accepts the specification's published default key so the ACU can write the real one over osdp_KEYSET.

The two are mutually exclusive. Submitting a key with install mode enabled is refused with an explanation rather than resolved by guessing which instruction to honour.

Install mode is a commissioning state for a one-to-one connection between the ACU and this PD. A channel opened with the published default key is only as private as the wire it runs on, so anyone else on that bus can read the exchange in which the real key is delivered. Turn it off once the module is keyed.

The stored key is write-only from the firmware's point of view: it is read back once on the way into the OSDP stack, it is never logged, and the portal will only ever tell you whether a key is set — never what it is.

Inputs

Four opto-isolated voltage inputs on IO4, eight on IO8. The field device applies a voltage across an input's + and pins; the optocoupler keeps the module's logic side off your field wiring, with no shared return.

  • Inputs are sampled in the OSDP refresh loop, every 10 ms.
  • An input must read the same value for 30 ms before the change is accepted. The debounce window is rounded up to a whole number of sample periods.
  • Once settled, the new state is reported to the ACU as contact status and included in every subsequent osdp_ISTAT reply.
  • Input state is seeded from the actual pin level at startup, so a module that boots with a contact already closed does not report a phantom change on its first cycle.

Sampling in the refresh loop is deliberate rather than a shortcut. A mechanical contact needs tens of milliseconds of debounce regardless, which is several sample periods; driving it from a GPIO interrupt would still need the same filter, with a queue and an ordering problem added on top.

There is no end-of-line supervision. See the capability note above.

Outputs

Four changeover relays on IO4, eight on IO8, each bringing out NC, C and NO. The relay contact is isolated from the module's logic, and the coil is driven through an optocoupler.

The seven control codes

osdp_OUT carries an output number, a control code, and a timer count in units of 100 ms. Two independent pieces of state are involved — a latched level and a temporary override — because the control codes manipulate them separately:

Control codeEffect
NOPNothing.
PERMANENT_OFFSet the latched level off and abandon any timed operation.
PERMANENT_ONSet the latched level on and abandon any timed operation.
PERMANENT_OFF_ALLOW_TIMEDSet the latched level off; a running timed operation continues.
PERMANENT_ON_ALLOW_TIMEDSet the latched level on; a running timed operation continues.
TEMPORARY_ONDrive on for the timer duration, then fall back to the latched level.
TEMPORARY_OFFDrive off for the timer duration, then fall back to the latched level.

The level actually driven is the temporary state while a timed operation is running, and the latched state otherwise.

A timer count of zero means forever, as the specification says — not "expire immediately". A TEMPORARY_ON with a zero timer holds until another command changes it. This is the behaviour that lets an ACU hold a strike or a hold-open output without re-arming a timer.

Inactive state: fail-safe and fail-secure

Each output carries its own inactive-state setting, configured per output in the WiFi portal. An output marked inverted is energised while inactive and de-energised while active — which is what makes a circuit fail-safe, because losing power drops the relay into the active state.

This is the "accept configuration of the output driver" half of output capability level 04. OSDP defines no command for it: osdp_OUT carries only an output number, a control code and a timer, so the setting is accepted locally and stored.

Two things compose here and are worth keeping apart:

  • How the board is wired — which GPIO level energises the driver. A property of the hardware; it never changes.
  • Which level energises the load — the inactive-state setting. A property of your installation, per output.

Because the second is per output rather than per board, a fail-safe maglock and a fail-secure gate can share one module.

The inactive-state configuration is applied as the outputs are initialised, before the OSDP stack starts answering polls. An output configured fail-safe therefore never pulses through its active state on the way to being ready, and an ACU that is already polling never sees an undriven output.

One hardware caveat. Output 1 shares a pin that the ESP32 drives briefly during boot, before any firmware runs. That pulse cannot be suppressed in software. If the load on output 1 cannot tolerate a brief pulse at power-up, the output stage has to hold itself off until it is enabled.

Manual override from the portal

The configuration page shows live tiles for every point on the board — inputs and outputs — and lets you tap an output to toggle it. A tap sets the output to the inverse of what is actually being driven and cancels any running timed operation — otherwise a timer would revert the change a moment after you made it. The ACU can of course change it again on its next command.

WiFi configuration portal

The module raises its own WPA2 access point and serves a single self-contained configuration page. No site network, no app, no laptop: a phone is enough.

The WiFi passphrase is the only credential. There is no login page, and that is a deliberate choice — requiring WPA2 to associate encrypts the RF link, whereas a login form over an open access point would send its credentials in reversible base64 and leave the SCBK in the same session equally readable.

Network identity

PropertyValue
SSIDosprio- followed by a suffix you choose
Default suffixfive digits derived from the module's serial number
SecurityWPA2-PSK only
Shipped passphraseosprioio — the claim phase exists to force this out of use
Passphrase rules8–63 characters
Suffix rules1–25 characters, letters, digits, - or _
Addresshttp://192.168.4.1/
Maximum clients2

Only the suffix is stored; the osprio- prefix is added when the access point starts. There is no code path that can produce a network name without it.

The two phases

The portal serves one of two pages, and which one you get depends on whether the shipped passphrase is still in force.

Claim phase. Until you set your own passphrase, the page offers nothing but a network name and a new password. It refuses the factory default as your choice. Nothing else is even reachable: in this phase the OSDP endpoints are not registered at all, so a request for them is redirected rather than being turned away by a check that could be inverted or forgotten.

The reasoning is worth stating plainly: the SCBK is entered in the next phase. An access point keyed with a published passphrase would leave that key readable by anyone in radio range who captured the handshake.

Configuration phase. Once your passphrase is stored, the full page appears, in three parts:

  • Points — live tiles for every input and output, refreshed once a second, with tappable outputs.
  • OSDP — PD address, baud rate, secure channel key, install mode, and per-output inactive state.
  • Network — network name, WiFi password, and lockdown.

Each form saves independently. Changing a baud rate does not mean retyping a WiFi password.

Field validation

Everything is validated before anything is written, so a bad field cannot leave the module half-configured:

FieldRule
PD addressinteger 0–126
Baud rateone of the six rates OSDP defines; anything else is refused rather than accepted and left unable to communicate
Secure channel keyexactly 32 hex characters; blank leaves the stored key untouched
Install modecannot be combined with a key in the same submission
Network name1–25 characters from letters, digits, - and _
WiFi password8–63 characters; blank leaves the stored one untouched

The Random button generates a key in the browser, not on the module — a phone has a better entropy source than an ESP32 whose RNG quality depends on the radio being active, and a key generated there need never cross the link at all. Copy it before you save: the stored key is never displayed again, and the ACU needs the same value.

Saving anything that affects the radio or the OSDP stack restarts the module. The response reaches your browser first, then the access point and HTTP server are shut down in order, and only then does the module reset.

Captive portal behaviour

Connecting a phone should raise the configuration page by itself. Three mechanisms cover the ways different clients look for one:

  • The DHCP lease carries a captive-portal URI (option 114), the standards-compliant signal, which also survives HSTS where a redirected lookup does not.
  • A DNS server answers every lookup with the module's own address, for clients that ignore option 114.
  • Every unrecognised HTTP request is redirected to the portal root. An operating system probing a known URL and getting a redirect instead of the expected empty response is what makes it raise a sign-in sheet.

Activation and deactivation

The access point is not left running indefinitely, and it does not have to come up at all.

The idle window

The access point shuts down after 5 minutes with nobody using it. Three things reset the countdown:

  • a station is associated,
  • an HTTP request arrives,
  • the config button is pressed.

Association alone is not treated as proof of life — a phone will silently drop an access point with no internet and re-join later, which can empty the association table at exactly the wrong moment. A request in flight is proof somebody is there. On disconnect the countdown restarts from the full window, so an installer who briefly drops off is not punished by a stopwatch that has been running the whole time.

While the access point is down, no SSID is broadcast and nothing can associate.

The config button

One button, two gestures, told apart by how long you hold it.

GestureEffect
Short pressRequest an access point session, or restart the idle countdown of one already running. This is how you bring the portal back on a locked-down module.
Hold 10 seconds, then releaseFactory reset.

The factory reset arms at the ten-second mark — both LEDs start blinking — and fires when you let go. That ordering is the point: the installer gets confirmation before the only destructive action in the product, and releasing the button is an explicit decision rather than the absence of one. Let go before ten seconds and nothing is lost.

The button cannot be held through a power-up. It shares a pin with the boot-mode strap, so holding it while power is applied puts the chip into serial download mode and no firmware runs at all. Press it after the module has booted.

Lockdown

Lockdown is the setting for a module that is finished being configured. With it enabled, the access point does not come up at boot: no SSID is broadcast, nothing is associable, and the portal exists only when someone with physical access presses the config button.

The portal itself is best-effort in a different sense too. If WiFi fails to start, the module logs it and carries on to OSDP — an installed access-control device staying on the bus matters more than a commissioning convenience.

Persistence and factory reset

Stored in non-volatile memory: PD address, baud rate, SCBK, WiFi network name suffix, WiFi passphrase, lockdown, install mode, and the per-output inactive states. Everything not yet provisioned falls back to a built-in default, so the module always has a usable configuration.

A factory reset erases every one of those and returns the module to default address and baud, no SCBK, the default network name and passphrase, and lockdown off.

The SCBK goes with it deliberately. A module being factory reset is a module leaving its old trust relationship; leaving the key behind would hand the next installer a credential they have no reason to hold. Plan to re-key after a reset.

Only this firmware's own storage is erased, not the whole partition, so radio calibration data stored alongside it survives.

Power and wiring

ConnectionNotes
USB-CPower and console. Convenient on the bench and for configuration.
Power in9–24 V DC, wide range, no jumper to set. Runs off whatever rail the panel already has.
RS-485A and B on a screw terminal. Land it alongside the readers already on the segment.
InputsFour + / pairs on IO4, eight on IO8.
OutputsFour NC / C / NO groups on IO4, eight on IO8.

Address, baud rate and secure channel key are all set over WiFi, so a module can be fully commissioned before it is wired to anything but power.

Firmware updates

A module already wired into a panel can be updated in place, two ways:

  • Over WiFi. Bring up the configuration portal and upload the new image from the same page you commission the module on.
  • Over OSDP. Send the image down the bus as an OSDP file transfer from the ACU, with no need to go near the enclosure at all.

The flash carries two application slots. The new image is written to the slot that is not running, so the working firmware stays intact for the whole transfer, and the module only switches over on the reboot that follows.

USB-C remains available for a first flash on the bench.

Security notes

Three things are worth stating plainly rather than leaving to be discovered:

  • The SCBK is stored in plaintext. Flash encryption and secure boot are not enabled on the current firmware. Someone with physical possession of a module and the will to read its flash can recover the key. This is the next security milestone; doing half of it now would be worse than documenting the gap.
  • Install mode is not a setting to leave on. It accepts the published default key. Use it on a one-to-one connection, then turn it off.
  • The portal is served over plain HTTP. WPA2 is what protects the session, which is why the shipped passphrase must be replaced before the module will accept a key at all.