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
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sblcp-0.1.0.tar.gz.
File metadata
- Download URL: sblcp-0.1.0.tar.gz
- Upload date:
- Size: 43.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e61fb33462a817322caef26c878c7971c5b6a8eef41caf6cd2169201bc4a6bb
|
|
| MD5 |
2e8a57963f026e6cdab7c4ce0b67aa0f
|
|
| BLAKE2b-256 |
db84f3314737897d20b219a07129862a192cc6ef84b3a392e70a09f9d7bdc0ae
|
Provenance
The following attestation bundles were made for sblcp-0.1.0.tar.gz:
Publisher:
release.yml on electrification-bus/sblcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sblcp-0.1.0.tar.gz -
Subject digest:
8e61fb33462a817322caef26c878c7971c5b6a8eef41caf6cd2169201bc4a6bb - Sigstore transparency entry: 2468176396
- Sigstore integration time:
-
Permalink:
electrification-bus/sblcp@644fd3af881db869b3a75a42840d14b2f25f1c14 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/electrification-bus
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@644fd3af881db869b3a75a42840d14b2f25f1c14 -
Trigger Event:
push
-
Statement type:
File details
Details for the file sblcp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: sblcp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 32.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ef172b11064fd400fda867c66a9ef7ca3ec6e3f9a5a166c38be44459a62f05ba
|
|
| MD5 |
f47df7f4660c43775c637fa0366c3b41
|
|
| BLAKE2b-256 |
3b152dfe45bb6db7f38276e19fd68dd6459145a88162adac0737f3aa37e7a1db
|
Provenance
The following attestation bundles were made for sblcp-0.1.0-py3-none-any.whl:
Publisher:
release.yml on electrification-bus/sblcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sblcp-0.1.0-py3-none-any.whl -
Subject digest:
ef172b11064fd400fda867c66a9ef7ca3ec6e3f9a5a166c38be44459a62f05ba - Sigstore transparency entry: 2468176409
- Sigstore integration time:
-
Permalink:
electrification-bus/sblcp@644fd3af881db869b3a75a42840d14b2f25f1c14 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/electrification-bus
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@644fd3af881db869b3a75a42840d14b2f25f1c14 -
Trigger Event:
push
-
Statement type: