4.3 KiB
Setup card generator
scripts/generate-setup-card.py produces printable 4x6 setup cards for fulfillment: drop one in the box with each ODS unit so the recipient can scan two QR codes without typing anything. The default card opens the first-run wizard; --mode factory-owner prints the owner-card handoff that opens ODS Talk.
What it generates
A portrait card with:
- Top band - "ODS" wordmark, the unit's mDNS name (e.g.
ods.local), and a one-line tagline. - Two QR codes side by side:
- Left - JOIN WI-FI. Encodes the standard
WIFI:T:WPA;S:<ssid>;P:<password>;;format that iOS Camera and Android auto-recognize. - Right - OPEN SETUP or OPEN ODS TALK. In default setup mode this is a setup URL such as
http://192.168.7.1/setup. In factory-owner mode this is the owner magic-link URL generated by Setup / Owner.
- Left - JOIN WI-FI. Encodes the standard
- Plain-text fallback block - SSID, password, and URL printed verbatim for phones that will not scan the QR.
- Footer - "ODS is open-source - light-heart-labs.com", plus an optional per-unit serial number.
Card is 1200x1800 px at 300 DPI = exactly 4x6 inches. PNG is the default output; PDF is available with --format pdf or a .pdf output path.
Requirements
pip install 'qrcode[pil]'
This is an operator-side tool, not a runtime service, so the deps are not bundled with any ODS container.
Usage
Setup wizard card:
python3 scripts/generate-setup-card.py \
--ssid 'ODS-Setup-A4F2' \
--password 'xxxxxxxx' \
--setup-url 'http://192.168.7.1/setup' \
--device-name 'ods-a4f2.local' \
--serial 'DRM-2026-A4F2' \
--output cards/card-A4F2.png
Factory owner card:
python3 scripts/generate-setup-card.py \
--mode factory-owner \
--ssid 'ODS-Setup-A4F2' \
--password 'xxxxxxxx' \
--owner-url 'http://auth.ods-a4f2.local/magic-link/OWNER_TOKEN' \
--device-name 'ods-a4f2.local' \
--serial 'DRM-2026-A4F2' \
--output cards/owner-card-A4F2.pdf
Flags:
| Flag | Required | Notes |
|---|---|---|
--mode |
no | setup by default; use factory-owner for Wi-Fi + owner ODS Talk QR cards. |
--ssid |
yes | Wi-Fi SSID of the device's setup AP. |
--password |
no | Wi-Fi password. Omit for open networks; the Wi-Fi QR will use T:nopass automatically. |
--security |
no | WPA (default), WEP, or nopass. |
--setup-url |
setup mode | URL the QR opens, e.g. http://192.168.7.1/setup. |
--owner-url |
factory-owner mode | Owner magic-link URL created from Setup / Owner. Treat it as a physical credential. |
--device-name |
no | mDNS hostname printed on the card. Defaults to ods.local. |
--serial |
no | Optional serial / batch ID printed in the footer right corner. |
--format |
no | png or pdf. If omitted, .pdf output paths write PDF; everything else writes PNG. |
--output / -o |
yes | Output path. Parent directory is created if missing. |
Exit codes: 0 on success, 2 for missing dependencies or invalid flags.
Batch generation
Looping the script over a list of pre-baked AP credentials is the recommended fulfillment flow:
while IFS=',' read -r ssid password serial owner_url; do
python3 scripts/generate-setup-card.py \
--mode factory-owner \
--ssid "$ssid" \
--password "$password" \
--owner-url "$owner_url" \
--device-name "ods-$(printf '%s' "$serial" | tr '[:upper:]' '[:lower:]').local" \
--serial "$serial" \
--output "cards/$serial.pdf"
done < unit-credentials.csv
Security notes
- The plaintext AP password is on the card by design. Treat the card as a physical credential.
- A factory owner URL is a permanent credential until revoked. Print only the intended card, do not publish screenshots, and revoke/reprint from Setup / Owner when a card is lost or replaced.
- Factory owner cards open the mobile ODS Talk portal. It is backed by Hermes, but it avoids the full advanced Hermes interface for the recipient.
- The setup URL does not carry a credential. It points at the device's setup-mode IP; if the recipient has not joined the AP yet, the URL times out.
- Serial / batch identifiers are optional and intended for fulfillment, not authentication.
Limitations / future work
- No splash logo. The wordmark is set in the system bold font.
- No multi-language. Text is English-only.
- No Hidden SSID flag. Standard Wi-Fi QR supports
H:true;. Today the script hardcodesH:false.