sblcp
A Python coordinator library and breaker simulator for the Smart Breaker Local Communication Protocol (SBLCP), the local UDP protocol spoken by Eaton AbleEdge Smart Breaker 2.0 devices on the LAN. SBLCP is the AbleEdge instantiation of Eaton's EMLCP family (the same lineage as the first-generation EMCB and Eaton's emcb-udp-coordinator).
Status: early, working. The wire codec (framing, HMAC-SHA256 signing, sequence-number handling), the coordinator client, and a breaker simulator are implemented and tested against spec-derived vectors, including a coordinator↔simulator integration test over UDP loopback. Payload codecs cover every fixed message code: sequence sync, meter telemetry, device status, primary/remote handle read + write, trip log, energy reset, factory reset, production details, time sync, debug log + level, WiFi RSSI, identify-me, the Local-OTA HTTP server, UDP key management (TTL query + rotation), and
WHO_ARE_YOUbroadcast discovery (hash-based IP↔breaker identification viaCoordinator.who_are_you).
Rotating a key requires encrypting it under the device's secondary unicast key (AES-256-GCM); that lives in sblcp.crypto behind an optional dependency — pip install sblcp[crypto]. The core library is standard-library-only.
This library is deliberately standalone: it implements the SBLCP wire protocol and nothing more. It has no runtime dependencies beyond the Python standard library and no coupling to any particular application that consumes it, so it can back a coordinator daemon, a test harness, or a higher-level integration equally well.
What SBLCP is
- Transport: UDP on port 32866 over a 2.4 GHz WiFi LAN, IPv4 unicast and subnet broadcast.
- Model: coordinator → node. The coordinator (this library) always initiates; the breaker (node) validates and responds.
- Security: every message carries a 32-byte HMAC-SHA256 signature computed with a shared UDP key, plus a sequence number to defeat replays. Keys are 32 bytes, come in unicast and broadcast sets (each with a primary and secondary), and expire after 7 days.
- Framing: a
ETNM(coordinator) /ETNS(node) start marker, a little-endianu32sequence number, a big-endianu16message code, a little-endian message body, and the 32-byte hash footer. Minimum 42 bytes.
Scope
- In scope: the SBLCP fixed message codes (device status, meter telemetry, remote handle position, trip log, energy reset, time sync, RSSI, UDP-key management, sequence-number sync, ...), the coordinator transport, key management, node discovery (both the signed
WHO_ARE_YOUkind and the keyless ARP kind, seesblcp-arp), and a software simulator for hardware-free development and CI. - Out of scope (intentionally): a vendor extension envelope that carries a separate, non-public message model. This library implements the fixed message codes only; a raw payload for such a code can still be sent over the same transport via the low-level
request()API.
Interface
See doc/INTERFACE.md for the full Python API: the Coordinator client and its operations, the KeySet key model, the typed payloads and enums, the low-level request() escape hatch, and the BreakerNode simulator.
Install
pip install -e ".[dev]"
Run the simulator
sblcp-sim # binds a fake breaker node on :32866
Finding breakers before they have keys (sblcp-arp)
SBLCP's own discovery signs every datagram, so Coordinator.discover() and who_are_you() are useless until a device has been provisioned. That is a real gap: a factory-fresh breaker is on your network and answering ARP, but nothing at the application layer.
sblcp-arp closes it by working purely from the host's ARP cache, which needs no keys and no root:
sblcp-arp # scan the local /24, list everything
sblcp-arp 192.168.1.0/24 --oui 2c:bc:bb # only devices from one vendor
sblcp-arp --no-populate --mac-suffix ab:cd:ef
nudging 192.168.1.0/24 ...
192.168.1.200 2c:bc:bb:ab:cd:ef on en0
Two filters, for two different situations:
| Filter | Use it when |
|---|---|
--mac-suffix ab:cd:ef |
You know part of one device's MAC and want its IP and full address. Reliable |
--oui 2c:bc:bb |
You want a rough sweep for one vendor prefix. Incomplete by nature, see below |
Prefer the suffix. A device that advertises over Bluetooth gives you only its low three octets, and a suffix match turns that into a full MAC and an IP without needing to know the vendor prefix at all.
An OUI filter finds only devices using the prefix you name, and a vendor may well use several. Two AbleEdge breakers observed on one network reported 2C:BC:BB and F0:24:F9: enumerating by either prefix alone would have found one and silently missed the other. Treat --oui as a starting point for exploration, never as a complete inventory.
How the scan works
By default the target network is nudged first: one empty UDP datagram is sent to every address, on the discard port. The kernel has to resolve each address to send, which populates the ARP cache as a side effect. Hosts ignore the datagram, as they should, but their network stack still answers the ARP.
That approach is deliberate. It needs no elevated privileges, crafts no raw frames, and shells out to nothing, so the package stays standard-library-only and works the same on macOS, BSD and Linux. Pass --no-populate to read the cache as it stands, which is faster and completely silent on the wire.
Reading the cache tries arp -an first and falls back to ip neigh, so hosts without net-tools are covered. Unresolved neighbours are skipped: an incomplete entry means something was asked about, not that anything answered.
From Python
from sblcp import find_hosts
# Resolve one device from the low-order octets in its BLE advertising name.
for entry in find_hosts("192.168.1.0/24", mac_suffix="ab:cd:ef"):
print(entry.ip, entry.mac, entry.interface)
find_hosts() returns ArpEntry records sorted numerically by address. Also exported: read_arp_table(), populate_arp_cache(), and parse_arp_output() if you already have output to parse.
No OUI is built into this package. It implements a protocol, not a vendor's product, so pass the prefix you care about.
Releasing
The version has a single source of truth: __version__ in src/sblcp/__init__.py. pyproject.toml reads it dynamically (dynamic = ["version"] plus [tool.setuptools.dynamic]), so there is exactly one line to bump. This repo is modern-build only: there is no setup.py shim and no second copy to keep in sync.
To cut a release:
- Bump
__version__insrc/sblcp/__init__.py(the only place). - Move the
CHANGELOG.md[Unreleased]entries into a new version section. - Commit (
git commit -am "release X.Y.Z"). - Build the artifacts:
python -m buildproducesdist/sblcp-X.Y.Z*carrying the version from__init__.py.
There is no automated PyPI publish workflow yet (the package is not on PyPI). When one is added (on: push: tags: v*), it should also carry the "verify the tag equals v$__version__" guard step from the version-single-source convention so a tag can never disagree with the packaged version.
License
MIT © Clark Communications Corporation
Metadata
Release files for sblcp 0.1.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 | Provenance | Actions |
|---|---|---|---|---|
| sblcp-0.1.0.tar.gz | 43.3 kB | With provenance | Details for sblcp-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| sblcp-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for sblcp-0.1.0-py3-none-any.whl |
Total release size: 75.9 kB
Release files / sblcp-0.1.0.tar.gz
| Download URL | sblcp-0.1.0.tar.gz |
|---|---|
| Size | 43.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8e61fb33462a817322caef26c878c7971c5b6a8eef41caf6cd2169201bc4a6bb
|
|
BLAKE2b-256 checksum How to use checksums |
db84f3314737897d20b219a07129862a192cc6ef84b3a392e70a09f9d7bdc0ae
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
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 Aug 14, 2026.
Transparency logRelease files / sblcp-0.1.0-py3-none-any.whl
| Download URL | sblcp-0.1.0-py3-none-any.whl |
|---|---|
| Size | 32.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ef172b11064fd400fda867c66a9ef7ca3ec6e3f9a5a166c38be44459a62f05ba
|
|
BLAKE2b-256 checksum How to use checksums |
3b152dfe45bb6db7f38276e19fd68dd6459145a88162adac0737f3aa37e7a1db
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
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 Aug 14, 2026.
Transparency logSecurity for sblcp 0.1.0
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.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for sblcp-0.1.0.tar.gz
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 Aug 14, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.