ezsp
Actor-based host support for the EmberZNet Serial Protocol (EZSP).
EZSP is the command protocol used by a host application processor to control the EmberZNet PRO stack running on a Silicon Labs Network Co-Processor (NCP). This crate models typed command, response, and callback payloads; legacy and extended frame headers; transport-independent actors; high-level Zigbee workflows; and a transport API that external link implementations, including ASHv2, can use.
Documentation basis
The protocol model follows these Silicon Labs references:
UG100: EZSP Reference Guide, Rev. 5.1, for EmberZNet PRO 7.4.2.UG101: UART-EZSP Gateway Protocol Reference, Rev. 1.3, for ASHv2 over UART.- https://docs.silabs.com/zigbee/latest/sisdk-ezsp-reference-guide/, the current Simplicity SDK EZSP reference.
- https://docs.silabs.com/zigbee/6.6/em35x/, the older EmberZNet API reference used by several Ember type descriptions.
The implementation retains the legacy EZSP and Ember names where they are part of the crate API.
Features
apis-saltansimplementsapis_saltans_hw::DriverforNcpand supplies callback/event and data-model conversions.semverenablessemversupport in EZSP version APIs.
This crate does not depend on, re-export, or provide an implementation of
ASHv2. An ASHv2 crate can integrate by implementing the public Transmit and
Receive traits.
Actor model
The transport API separates outbound and inbound I/O:
Transmitsends a completeFrame<Commands>.Receiveyields decodedFrame<Parameters>values and accepts the negotiated EZSP version used by version-sensitive decoders.Client::runwraps those transport halves and returns a newly wiredClientplus the transmitter and receiver actor futures for the caller to spawn.- The returned
Clientrepresents the actor channels before protocol negotiation. Client::connectsends the initialversioncommand and returns a cloneableConnectionhandle together with the asynchronous callback stream.ConnectionimplementsCommunicate; all EZSP command-group traits are blanket-implemented for communicators.
Every Connection::communicate call sends an actor message and waits on its own
one-shot response. The transmitter actor assigns an EZSP sequence number,
serializes outbound access, and correlates inbound responses by that number.
Cloned handles can therefore be used by independent tasks without placing the
transport behind a mutex. Asynchronous callbacks bypass response correlation
and are delivered through a separate bounded channel.
flowchart LR
callers[Connection handle clones] --> commands[Actor inbox]
commands --> transmitter[Transmitter actor]
transmitter --> tx[Transmit implementation]
tx --> ncp[NCP]
ncp --> rx[Receive implementation]
rx --> receiver[Receiver task]
receiver --> commands
receiver --> callbacks[Callback stream]
Transport implementations supply independent Transmit and Receive halves.
Pass both halves to Client::run and spawn both returned actor futures before
version negotiation. The transport implementation remains responsible for
running any lower-level I/O tasks that feed those halves.
Typed protocol API
EZSP command methods are grouped into traits such as Configuration,
Messaging, Networking, Security, and Utilities. Each method creates a
typed command parameter, calls Communicate::communicate, and converts the
correlated response into its public return type. Ezsp is a convenience trait
combining the complete command surface.
The lower-level frame model remains public for transport implementations and protocol tooling:
Frame,Header,Legacy, andExtendedmodel the EZSP envelope.Commandsis the outbound aggregate consumed byTransmitimplementations.Parameters,Response, andCallbackclassify decoded inbound payloads.Parsableperforms frame-ID-directed parameter decoding, while the publicDecode,Status, andErrortypes let adapters report compatible failures.- Protocol data types are exposed through
ember,ezsp, and the typed parameter modules.
EZSP fields wider than one byte are encoded little-endian. Protocol versions
before 8 use the three-byte legacy header; versions 8 and newer use the
five-byte extended header. The generic receiver actor records a successful
version response and passes the negotiated version to subsequent transport
receive calls.
High-level NCP startup
Builder owns a pre-negotiation Client and the complete startup
configuration. Client::run returns the newly wired client together with the
futures that drive it. After the caller spawns those futures, start:
- validates that at least one application endpoint was supplied;
- negotiates the requested EZSP version through the running transport actors;
- applies concentrator, configuration, and policy settings;
- resumes the persisted network or forms an explicitly configured network;
- waits for
NetworkUp, applies runtime radio power, and sends a many-to-one route request; - registers the supplied endpoints;
- creates the callback bridge and event-handler futures used for translation, scan aggregation, APS defragmentation, and message-confirmation correlation;
- returns those futures with
Ncpin aBuildResult.
Builder::start does not spawn either returned future. Spawn bridge before
event_handler, and keep both tasks running while using the Ncp.
The event channel passed to Builder::start determines the application event
type. That type must implement TranslatableEvent, which is automatically
implemented for types that can be constructed from both Callback and
DefragmentedMessage.
Builder methods configure callback and actor channel capacities, the desired protocol version, EZSP policies and configuration values, concentrator parameters, radio transmit power, and baseline APS options. The named APS option methods enable route discovery, forced route discovery, source or destination EUI-64 inclusion, and address discovery for every outgoing frame. Per-message options supplied to the send methods are combined with this baseline.
Startup::Resume restores state persisted by the NCP through networkInit and
is the normal choice for restarts:
use Startup;
use InitBitmask;
let startup = Resume;
Startup::Initialize intentionally replaces the current network. It attempts
to leave the current network, installs the initial security state, and forms a
network from InitializationParameters. NetworkCredentials groups the
extended PAN ID, PAN ID, trust-center EUI-64, and network key; initialization
parameters add the preconfigured trust-center link key, channel, join method,
and initial security bitmask.
use ;
let credentials = new;
let parameters = new;
let startup = Initialize;
NetworkCredentials contains secret key material. Do not log its Debug
output, and protect persisted or copied credentials appropriately. Random
credentials can be sampled with rand, but the distribution accepts any RNG;
production callers are responsible for selecting a cryptographically secure
one.
High-level NCP operations
Ncp owns the connected communicator and endpoint metadata. It adds
workflows that span commands and asynchronous callbacks:
- active-network and energy scans, completed by
scanComplete; - unicast, multicast, and broadcast APS sends;
- outgoing message-tag correlation with
messageSentcallbacks; - incoming APS fragment reassembly;
- source-endpoint selection from registered output clusters; and
- event-handler shutdown through
Ncp::terminate.
Outgoing APS sends select the lowest-numbered registered local endpoint whose
output clusters contain the requested cluster ID. ZDP uses endpoint zero. A
missing match returns Error::NoMatchingSourceEndpoint before a send command is
issued.
Awaiting Ncp::unicast, Ncp::multicast, or Ncp::broadcast performs the EZSP
send transaction and returns a deferred StackResponse (multicast also returns
the assigned APS sequence). Await StackResponse separately to validate the
matching asynchronous messageSent callback. Dropping it discards only the
notification and does not cancel a message already accepted by the NCP.
Each send method takes a final aps_options: ember::aps::Options argument.
These per-message options are combined with the options configured on
Builder; pass Options::NONE when a message needs no additional flags. For
example, a caller can request APS encryption and retry for one unicast without
changing the baseline used by later sends:
use Options;
let options = ENCRYPTION.union;
let response = ncp
.unicast
.await?;
response.await?;
Oversized unicasts are split into APS fragments. Multicast and broadcast payloads must fit the maximum payload reported by the NCP. Fragmented unicasts enable APS retry in addition to the combined baseline and per-message options.
APS defragmentation
Defragmenter<T> reassembles fragmented incoming APS unicasts for any
T: Messaging. handle acknowledges each fragment with the required empty
sendReply and returns a DefragmentedMessage after the complete payload is
available. The high-level event handler owns a defragmenter using its clone of
the Connection actor handle and emits incoming-message events only for complete
payloads.
Reassembly keys messages by sender and APS sequence, enforces the fragment window and receive-buffer limits, and expires incomplete messages. Compile-time environment variables can override the defaults:
EZSP_DEFRAGMENTATION_MAX_INCOMING_PACKETSEZSP_DEFRAGMENTATION_DEFAULT_WINDOW_SIZEEZSP_DEFRAGMENTATION_RECEIVE_BUFFER_LENGTHEZSP_DEFRAGMENTATION_REASSEMBLY_TIMEOUT_MILLIS
External ASHv2 integration
ASHv2 support lives outside this crate. An adapter supplies an outbound type
implementing Transmit and an inbound type implementing Receive:
Transmit::transmitreceives a complete typedFrame<Commands>. An ASHv2 adapter serializes the header followed by the command parameters in little-endian order and sends the result as one ASHv2 DATA payload.Receive::receiveaccepts the currently negotiated version and obtains one complete ASHv2 DATA payload, decodes its EZSP header and parameters, and returnsFrame<Parameters>. It receivesNonebefore the initial EZSPversionresponse andSome(version)on subsequent calls; versions at leastMIN_NON_LEGACY_VERSIONuse extended headers. Because this method returnsOptionrather thanResult, the adapter owns its malformed-frame policy, such as logging and skipping a bad payload.
Once the ASHv2-specific halves exist, the generic EZSP wiring is:
use Builder;
const EZSP_CHANNEL_SIZE: usize = 128;
// `ash_transmit` and `ash_receive` are supplied by an external ASHv2 adapter
// and implement `ezsp::Transmit` and `ezsp::Receive`, respectively.
let =
client.run;
let _ezsp_transmitter = spawn;
let _ezsp_receiver = spawn;
let result = new
.start
.await?;
// Spawn the returned application services in producer-to-consumer order.
let _bridge = spawn;
let _event_handler = spawn;
let ncp = result.ncp;
The client input in this example must be supplied by another crate API. The
current ezsp API does not expose a public constructor for that initial value,
so an external adapter cannot yet initiate this sequence using ezsp alone.
Start any lower-level ASHv2 serial worker and ASHv2 protocol tasks before the
two EZSP actor futures. Both EZSP actors must be running before
Builder::start, because startup begins with Client::connect. The
channel_size passed to Client::run bounds the EZSP command/response and
callback channels. Builder::with_event_messages_capacity configures the
separate channel between the callback bridge and event handler.
ASHv2 remains responsible for reliability, CRC validation, byte stuffing, randomization, acknowledgements, reset handling, and retransmission. Neither EZSP nor ASHv2 fragments protocol frames: one complete EZSP frame must fit in one ASHv2 DATA payload.
apis-saltans integration
The apis-saltans feature adds implementations and conversions around the
normal actor-backed Ncp; it does not add a wrapper type or another transport.
Ncp implements apis_saltans_hw::Driver. The mapping provides:
- stored endpoint descriptors through
Driver::get_endpoints; - NCP identity and EZSP address-table lookup operations;
- active-network and energy scans through the existing callback aggregator;
- permit joining, with the requested duration truncated to whole seconds and clamped to 255 seconds;
- high-RAM many-to-one route requests; and
- unicast, broadcast, and multicast datagram transmission through the high-level NCP send helpers.
After extracting Ncp from the BuildResult, call Driver::run and spawn its
returned future to run the separate apis-saltans hardware actor:
use Driver;
let = ncp.run;
spawn;
// `hardware` is an apis_saltans_hw::NcpHandle.
The feature provides bidirectional endpoint conversion. Convert the ZDP simple
descriptors used by apis-saltans before passing them to the EZSP builder:
let endpoints: = simple_descriptors
.into_iter
.map
.collect;
Driver::get_endpoints performs the reverse conversion. Endpoints containing
an unsupported apis-saltans profile or a reserved endpoint number are logged
and omitted; descriptors originally converted from SimpleDescriptor round
trip without that loss.
Outgoing datagrams take their APS profile and cluster from
apis_saltans_hw::Datagram metadata. A device destination preserves its target
endpoint. A broadcast uses its target endpoint with radius zero. A group uses
the profile's broadcast endpoint with zero multicast hops and nonmember radius.
The local source endpoint is still selected from the registered EZSP output
clusters.
The integration also maps per-datagram transmission options into EZSP APS
options: ACKNOWLEDGED_TRANSMISSION controls Options::RETRY, and
SECURITY_ENABLED controls Options::ENCRYPTION. Those values are then
combined with the NCP's baseline options. Other TxOptions flags do not add an
EZSP APS option.
Driver::transmit returns HwResponse after the EZSP send transaction has been
accepted. The response contains the deferred StackResponse; awaiting it
reports the later messageSent callback status.
Event and message conversion
The feature converts these EZSP callbacks into apis_saltans_hw::Event values:
stackStatusfor network up, down, opened, and closed;childJoinfor child joins and leaves; andtrustCenterJoinfor unsecured joins, secured/unsecured rejoins, and leaves.
Complete incoming APS messages convert separately into
apis_saltans_hw::aps::Data<bytes::Bytes> and NWK envelopes. The conversion
preserves APS destination, profile, cluster, endpoints, sequence, and payload,
plus the sender short ID, link quality, RSSI, binding index, and source-route
overhead. The source IEEE address remains unknown.
The feature does not currently implement
TryFrom<DefragmentedMessage> directly for apis_saltans_hw::Event. Therefore
that event enum alone does not implement TranslatableEvent and cannot be used
as Builder::start's event type without an application wrapper that supplies
both required conversions.
EZSP errors cross the driver boundary as
apis_saltans_hw::Error::Implementation, retaining the original error in an
Arc.
Legal
This project is free software and is not affiliated with Silicon Labs. Silicon Labs documentation is cited only to describe the public protocol implemented by this crate.
Contributing
- Format with
cargo +nightly fmtand verify withcargo +nightly fmt --check. - Lint with
cargo clippy --all-features. - Verify documentation with
cargo +nightly doc --all-features --no-deps.