app.ndtp.protocol
NDTP wire codec: stream framing, CRC, and decoding of what the telematics terminals send.
Pure functions and dataclasses only — no sockets, no asyncio, no app state — so the codec is unit-testable on raw bytes and reusable by the TCP server, replay tools and fake-device clients.
Wire format (all integers little-endian, packed). Verified against the emulator’s own bytecode
(ndtp-telemetry-emulator:1.0, CellFactory.wrapNphPacket), not just the spec:
frame = NPL header (15 B) | NPH header (10 B) | body
NPL = signature u16 (0x7E7E) | data_size u16 (= len(NPH + body)) | flags u16
| crc u16 | type u8 (2 = NPH) | peer_address u32 (= unitId) | request_id u16
NPH = service_id u16 | type u16 | flags u16 (bit0 = request) | request_id u32
CRC is CRC-16/MODBUS over NPH + body. The emulator byte-swaps it before writing it into the
little-endian field, so on the wire it reads as big-endian. The NPL crc flag bit is
always 0, yet the CRC is always filled in — so we always verify it.
Realtime bodies are a sequence of cells [type u8][number u8][payload] with no length
prefix: the payload size is implied by the type. A cell type we don’t know the size of
therefore ends parsing of that packet (see Realtime.unparsed_tail). G6CellNav00 is
always first, so navigation is never lost to this.
Functions
|
CRC-16/MODBUS: init 0xFFFF, reflected poly 0xA001, no final xor. |
|
Interpret a frame's NPH header and body. |
|
|
|
|
|
A complete |
|
Classes
|
|
|
One CRC-verified NPL frame. |
|
Incremental decoder for one TCP connection: |
|
|
|
|
|
|
|
|
|
A cell of known size whose fields we don't decode. |
|
|
|
Exceptions
A CRC-valid frame whose content doesn't match its declared message type. |
- class app.ndtp.protocol.DecoderStats(frames: 'int' = 0, bytes_discarded: 'int' = 0, crc_errors: 'int' = 0, bad_headers: 'int' = 0, unsupported_type: 'int' = 0)[исходный код]
Базовые классы:
object- __init__(frames: int = 0, bytes_discarded: int = 0, crc_errors: int = 0, bad_headers: int = 0, unsupported_type: int = 0) None
- bad_headers: int
- bytes_discarded: int
- crc_errors: int
- frames: int
- unsupported_type: int
- class app.ndtp.protocol.Frame(npl: NplHeader, payload: bytes)[исходный код]
Базовые классы:
objectOne CRC-verified NPL frame.
payloadis the NPH header + body.- payload: bytes
- class app.ndtp.protocol.FrameDecoder(*, max_data_size: int = 4096)[исходный код]
Базовые классы:
objectIncremental decoder for one TCP connection:
feed()raw bytes, get complete frames.TCP is a byte stream, so a read may hold half a frame or several frames. Framing errors never raise: garbage is skipped, a bad frame is dropped and the decoder resyncs on the next signature, with everything counted in
stats.- __init__(*, max_data_size: int = 4096) None[исходный код]
- feed(data: bytes | bytearray | memoryview) list[Frame][исходный код]
- class app.ndtp.protocol.Handshake(nph: NphHeader, proto_version: tuple[int, int], flags: int, peer_address: int, max_packet_size: int)[исходный код]
Базовые классы:
objectNPH_SGC_CONN_REQUEST— the first packet on every (re)connection.- __init__(nph: NphHeader, proto_version: tuple[int, int], flags: int, peer_address: int, max_packet_size: int) None
- flags: int
- max_packet_size: int
- peer_address: int
- proto_version: tuple[int, int]
Базовые классы:
objectG6CellNav00: one navigation fix.
- exception app.ndtp.protocol.NdtpDecodeError[исходный код]
Базовые классы:
ValueErrorA CRC-valid frame whose content doesn’t match its declared message type.
- class app.ndtp.protocol.NphHeader(service_id: 'int', type: 'int', flags: 'int', request_id: 'int')[исходный код]
Базовые классы:
object- __init__(service_id: int, type: int, flags: int, request_id: int) None
- flags: int
- property is_request: bool
- request_id: int
- service_id: int
- type: int
- class app.ndtp.protocol.NplHeader(data_size: 'int', flags: 'int', crc: 'int', type: 'int', peer_address: 'int', request_id: 'int')[исходный код]
Базовые классы:
object- __init__(data_size: int, flags: int, crc: int, type: int, peer_address: int, request_id: int) None
- crc: int
- data_size: int
- flags: int
- peer_address: int
- request_id: int
- type: int
- class app.ndtp.protocol.RawCell(type: int, number: int, payload: bytes)[исходный код]
Базовые классы:
objectA cell of known size whose fields we don’t decode.
- __init__(type: int, number: int, payload: bytes) None
- number: int
- payload: bytes
- type: int
- class app.ndtp.protocol.Realtime(nph: NphHeader, nav: NavCell | None, cells: tuple[NavCell | RawCell, ...], unparsed_tail: bytes = b'')[исходный код]
Базовые классы:
objectNPH_SND_REALTIME— a telemetry packet.- __init__(nph: NphHeader, nav: NavCell | None, cells: tuple[NavCell | RawCell, ...], unparsed_tail: bytes = b'') None
- unparsed_tail: bytes
- class app.ndtp.protocol.UnknownMessage(nph: 'NphHeader', body: 'bytes')[исходный код]
Базовые классы:
object- body: bytes
- app.ndtp.protocol.crc16_modbus(data: bytes | bytearray | memoryview) int[исходный код]
CRC-16/MODBUS: init 0xFFFF, reflected poly 0xA001, no final xor.
- app.ndtp.protocol.decode_frame(frame: Frame) Handshake | Realtime | UnknownMessage[исходный код]
Interpret a frame’s NPH header and body. Raises
NdtpDecodeErroron malformed content.
- app.ndtp.protocol.encode_frame(service_id: int, nph_type: int, body: bytes, *, peer_address: int, nph_request_id: int = 0, request: bool = True) bytes[исходный код]
- app.ndtp.protocol.encode_handshake(unit_id: int, *, request_id: int = 1, max_packet_size: int = 65535) bytes[исходный код]
A complete
G6CellNav00cell (2-byte cell header + 26-byte payload).
- app.ndtp.protocol.encode_realtime(unit_id: int, cells: bytes, *, request_id: int) bytes[исходный код]