ASH (Asynchronous Serial Host) protocol codec for the EmberZNet NCP-UART interface, per Silicon Labs UG101.
ASH is the reliable data-link layer that sits directly on top of the raw
serial byte stream and carries EZSP frames. This module is pure: it turns
EZSP payloads (and control frames) into on-the-wire byte sequences and back,
with no I/O and no connection state. The stateful concerns (reset handshake,
frame/ack numbering, retransmission) live in Zigbee.EZSP.ASH.Connection.
Frame anatomy (on the wire)
[ control byte ][ data field ][ CRC-16 hi ][ CRC-16 lo ][ 0x7E flag ]
\_____________ byte-stuffed ________________/- The control byte identifies the frame type and carries frame/ack numbers.
- The data field is present only on DATA frames; it is the (randomized) EZSP payload.
- The CRC (CRC-16-CCITT, init 0xFFFF) is computed over control byte + data field, transmitted high byte first.
- Everything except the trailing flag is then byte-stuffed to escape reserved control bytes.
Summary
Functions
Encode an ACK frame acknowledging up to (but not including) ack_num.
The cancel byte (0x1A); sent before RST to flush a partial frame.
CRC-16-CCITT (a.k.a. CRC-16/CCITT-FALSE): initial value 0xFFFF, polynomial 0x1021, no reflection, no final XOR. Returned as a 16-bit integer.
Encode a DATA frame carrying an EZSP payload.
Decode a single raw frame (the bytes between flags, without the trailing 0x7E) into a structured map.
The frame delimiter byte (0x7E).
Encode a NAK frame requesting retransmission from ack_num.
Randomize (or de-randomize, the operation is its own inverse) a DATA field by XORing it with an LFSR pseudo-random sequence seeded at 0x42.
Encode the RST (reset) frame, prefixed with a Cancel byte to flush any
partial frame the NCP may be mid-way through. The result is always the fixed
sequence 1A C0 38 BC 7E.
Escape reserved bytes. Each reserved byte is replaced by the escape byte (0x7D) followed by the original byte XORed with 0x20.
Reverse of stuff/1. Returns {:ok, binary} or {:error, :bad_escape}.
Functions
Encode an ACK frame acknowledging up to (but not including) ack_num.
The cancel byte (0x1A); sent before RST to flush a partial frame.
@spec crc(binary()) :: 0..65535
CRC-16-CCITT (a.k.a. CRC-16/CCITT-FALSE): initial value 0xFFFF, polynomial 0x1021, no reflection, no final XOR. Returned as a 16-bit integer.
For example crc(<<0xC0>>) is 0x38BC, which is why the RST frame ends in
38 BC before the flag.
Encode a DATA frame carrying an EZSP payload.
frame_num: this frame's sequence number (0..7)ack_num: the next frame number we expect from the NCP (0..7)payload: the EZSP frame bytes (will be randomized)retransmit?: set on retransmissions so the NCP can detect duplicates
Decode a single raw frame (the bytes between flags, without the trailing 0x7E) into a structured map.
Returns {:ok, frame} where frame is one of:
%{type: :data, frame_num: 0..7, ack_num: 0..7, retransmit?: bool, payload: binary}
%{type: :ack, ack_num: 0..7, not_ready?: bool}
%{type: :nak, ack_num: 0..7, not_ready?: bool}
%{type: :rstack, version: byte, reset_code: byte}
%{type: :error, version: byte, error_code: byte}
%{type: :rst}or {:error, reason} on a CRC mismatch, bad escape, or truncated frame.
The frame delimiter byte (0x7E).
Encode a NAK frame requesting retransmission from ack_num.
Randomize (or de-randomize, the operation is its own inverse) a DATA field by XORing it with an LFSR pseudo-random sequence seeded at 0x42.
Only the EZSP payload of DATA frames is randomized; control bytes and CRC are never randomized.
@spec rst_frame() :: binary()
Encode the RST (reset) frame, prefixed with a Cancel byte to flush any
partial frame the NCP may be mid-way through. The result is always the fixed
sequence 1A C0 38 BC 7E.
Escape reserved bytes. Each reserved byte is replaced by the escape byte (0x7D) followed by the original byte XORed with 0x20.
Reverse of stuff/1. Returns {:ok, binary} or {:error, :bad_escape}.