py-pcbu
This library automates interactions with PC Bio Unlock by implementing its protocol.
Installation
pip install py-pcbu
and if you plan on using the CLI, you also need to install the cli extra:
pip install py-pcbu[cli]
Usage
Pairing
- First, install PC Bio Unlock normally. Currently, versions >2.0.0 are supported, probably lower versions too.
- In the desktop app, click on
Pair device. Go through the wizard steps, until you reach the QR Code. - Get the JSON string out of the QR Code. On Android, I can only recommend using the FOSS app https://gitlab.com/Atharok/BarcodeScanner
Then we need to run the pair client using py-pcbu:
Option 1: py-pcbu as CLI
With a conf.local.json file similar to the conf.template.json, one can directly call:
python -m pcbu pair-client
Option 2: py-pcbu as library
Write a TCPPairClient as follows:
from pcbu.tcp.pair_client import TCPPairClient
from pcbu.models import PairingQRData
pairing_data = PairingQRData.from_json(your_qr_json_string_here)
client = TCPPairClient(pairing_qr_data=pairing_data, device_name="My unlock program")
await pair_response = client.pair()
print(pair_response)
This snippet prints in plaintext the payload received from the desktop. It contains sensitive informations such as your account password! So make sure to store it somewhere safe.
Unlocking
Option 1: py-pcbu as CLI
With a conf.local.json file similar to the conf.template.json, one can directly call:
python -m pcbu unlock-server
Option 2 py-pcbu as library
py-pcbu gives a simple TCPUnlockServer to run out of the box. It will automatically unlock any authenticated request matching a stored pairing_id:
from pcbu.models import PCPairing, PCPairingSecret
from pcbu.tcp.unlock_server import TCPUnlockServer
pairings_dicts = [
{
"server_ip_address": "192.168.1.Y",
"server_port": 43296,
"pairing_id": "abcdef",
"desktop_ip_address": "192.168.1.Y",
"encryption_key": "some_super_long_key",
"username": "user1@desktop",
"password": "pwd1",
},
{
"server_ip_address": "192.168.1.Y",
"server_port": 43297,
"pairing_id": "ghijk",
"desktop_ip_address": "192.168.2.Z",
"encryption_key": "another_super_long_key",
"username": "user2@desktop",
"password": "pwd2",
},
]
pc_pairings = [PCPairingSecret.from_dict(d) for d in pairing_dicts]
async with TCPUnlockServer(pc_pairings) as server:
await server.start()
This snippet will start a TCPUnlockServer listening on each of the server_ip_address:server_port from the PCPairingSecret list (i.e. on both ports 43296 and 43297).
When the server receives an unlocking request, it will validate that the requesting's ip address matches one of the PCPairingSecret instances, decrypt the unlock request, and call the callback method TCPUnlockServerBase.on_valid_unlock_request which automatically accepts the unlocking request in this TCPUnlockServer implementation.
For more advanced use cases, one can also extend TCPUnlockServerBase from pcbu.tcp.unlock_server. The following methods can be overriden to react to events on the unlock server:
on_enter(self): called when the server's context is entered.on_start(self): called upon starting the server.on_valid_unlock_request(self, pairing: PCPairing): called when an unlock request has been authenticated and matches one of the registered PC pairings. The match is passed to the method. The basicTCPUnlockServerimplementation directly callsself.unlock(pairing)in this method, but one can add other checks or defer the unlocking to a later time.on_invalid_unlock_request(self, ip: str): called when an unlock request could not be authenticated or matched against the registerd pairings. The requesting ip address is passed.on_exit(self): called when the server's context is exited, but just before exiting the individual TCP servers.
Development
For easier development, we include several VSCode launch configuration to allow easier debugging:
Pair Server: runs thepair-serverCLI command. Emulates a pairing server (i.e. emulates the desktop's PCBU app showing you the QR Code). Automatically binds to the host's ip address, ignoring the.confone.Pair Client: runs thepair-clientCLI command. Expects a pairing server to be up, and initiates the pairing process.Unlock Server: runs theunlock-serverCLI command. Waits for unlock requests. Automatically binds to the host's ip address, ignoring the.confone.
All commands expect a gitignored conf.local.json file at the root of the repository. You can cp conf.template.json conf.local.json to get a base file to start from. Some conf options can be overwritten via CLI options, see
python -m pcbu --help
for more info.
Tests, lint and formatting run with uv:
uv sync
uv run pytest
uv run ruff check
uv run ruff format --check
TODOs
- Rewrite
TCPPairServerwithasyncio - Rewrite
TCPPairClientwithasyncio - Rewrite
TCPUnlockServerwithasyncio - Write a
TCPUnlockClient(i.e. emulating a Desktop requesting an unlock) withasyncio - Add
pair-clientcommand (removingscripts/test_pair.py) - Add
unlock-clientcommand - Handle more failure cases (e.g. rejected UnlockRequest on the client)
Releasing
A release is published whenever main carries a version in pyproject.toml that has no git tag yet: the CI runs, then the package is built, signed with Sigstore, attached to a GitHub Release tagged with the version, and uploaded to TestPyPI then PyPI. A failed release can be retried by pushing a fix, or by running the Release workflow manually.
To release:
- Bump
versioninpyproject.tomland runuv lock - Move the
Unreleasedentries ofCHANGELOG.mdunder a new## [x.y.z] - YYYY-MM-DDsection (its content becomes the release notes) - Open a PR and merge it into
main
Metadata
Release files for py-pcbu 0.8.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| py_pcbu-0.8.0.tar.gz | 27.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| py_pcbu-0.8.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 55.9 kB
Release files / py_pcbu-0.8.0.tar.gz
| Download URL | py_pcbu-0.8.0.tar.gz |
|---|---|
| Size | 27.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1f9386b20e94ce2d040b75ed8508088d2e952e3da15282ded5ebce3a8bfb8562
|
|
BLAKE2b-256 checksum How to use checksums |
a1b4c53705ebdc1ec3afc151d550a88826a60ab9abd029c14e6461c26614012c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / py_pcbu-0.8.0-py3-none-any.whl
| Download URL | py_pcbu-0.8.0-py3-none-any.whl |
|---|---|
| Size | 28.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6778d55c6cc2081dec722e9ea4e10ef6d0237ec0aa0dc1297af5b74cfe5b3651
|
|
BLAKE2b-256 checksum How to use checksums |
66487a6f33b2f7ab81e92b0246b772c67a3ec8a222084e06d47aba1f1137ac7f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency log