Skip to main content

sblcp

PyPI CI Lint Ruff Python

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_YOU broadcast discovery (hash-based IP↔breaker identification via Coordinator.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-endian u32 sequence number, a big-endian u16 message 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_YOU kind and the keyless ARP kind, see sblcp-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:

  1. Bump __version__ in src/sblcp/__init__.py (the only place).
  2. Move the CHANGELOG.md [Unreleased] entries into a new version section.
  3. Commit (git commit -am "release X.Y.Z").
  4. Build the artifacts: python -m build produces dist/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

sblcp-0.1.0.tar.gz (43.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sblcp-0.1.0-py3-none-any.whl (32.7 kB view details)

Uploaded Python 3

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

Hashes for sblcp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 8e61fb33462a817322caef26c878c7971c5b6a8eef41caf6cd2169201bc4a6bb
MD5 2e8a57963f026e6cdab7c4ce0b67aa0f
BLAKE2b-256 db84f3314737897d20b219a07129862a192cc6ef84b3a392e70a09f9d7bdc0ae

See more details on using hashes here.

Provenance

The following attestation bundles were made for sblcp-0.1.0.tar.gz:

Publisher: release.yml on electrification-bus/sblcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for sblcp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ef172b11064fd400fda867c66a9ef7ca3ec6e3f9a5a166c38be44459a62f05ba
MD5 f47df7f4660c43775c637fa0366c3b41
BLAKE2b-256 3b152dfe45bb6db7f38276e19fd68dd6459145a88162adac0737f3aa37e7a1db

See more details on using hashes here.

Provenance

The following attestation bundles were made for sblcp-0.1.0-py3-none-any.whl:

Publisher: release.yml on electrification-bus/sblcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page