docs: Add Protocol.md with information on host / firmware communication
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
parent
884cee27eb
commit
9edf60ffc6
|
@ -4,6 +4,9 @@ installing, and running Klipper.
|
||||||
See [code overview](Code_Overview.md) for developer information on the
|
See [code overview](Code_Overview.md) for developer information on the
|
||||||
structure and layout of the Klipper code.
|
structure and layout of the Klipper code.
|
||||||
|
|
||||||
|
See [protocol](Protocol.md) for developer information on the messaging
|
||||||
|
protocol between host and firmware.
|
||||||
|
|
||||||
See [debugging](Debugging.md) for developer information on how to test
|
See [debugging](Debugging.md) for developer information on how to test
|
||||||
and debug Klipper.
|
and debug Klipper.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,296 @@
|
||||||
|
Klipper uses a binary protocol for communication between host and
|
||||||
|
firmware. This page provides a high-level description of that
|
||||||
|
protocol.
|
||||||
|
|
||||||
|
The goal of the protocol is to enable an error-free communication
|
||||||
|
channel between the host and firmware that is low-latency,
|
||||||
|
low-bandwidth, and low-complexity for the firmware.
|
||||||
|
|
||||||
|
The protocol acts as a
|
||||||
|
[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) mechanism
|
||||||
|
between firmware and host. The firmware declares the commands that the
|
||||||
|
host may invoke along with the response messages that it can
|
||||||
|
generate. The host uses that information to command the firmware to
|
||||||
|
perform actions and to interpret the results.
|
||||||
|
|
||||||
|
Firmware Interface
|
||||||
|
==================
|
||||||
|
|
||||||
|
Declaring commands
|
||||||
|
------------------
|
||||||
|
|
||||||
|
The firmware declares a "command" by using the DECL_COMMAND() macro in
|
||||||
|
the C code. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
DECL_COMMAND(command_set_digital_out, "set_digital_out pin=%u value=%c");
|
||||||
|
```
|
||||||
|
|
||||||
|
The above declares a command named "set_digital_out". This allows the
|
||||||
|
host to "invoke" this command which would cause the
|
||||||
|
command_set_digital_out() C function will be executed in the
|
||||||
|
firmware. The above also indicates that the command takes two integer
|
||||||
|
parameters. When the command_set_digital_out() C code is executed, it
|
||||||
|
will be passed an array containing these two integers - the first
|
||||||
|
corresponding to the 'pin' and the second corresponding to the
|
||||||
|
'value'.
|
||||||
|
|
||||||
|
In general, the parameters are described with printf() style
|
||||||
|
parameters (eg, "%u"). In the above example, "value=" is a parameter
|
||||||
|
name and "%c" indicates the parameter is an integer. The parameter
|
||||||
|
name is used as documentation. In this example, the "%c" is used as
|
||||||
|
documentation to indicate the expected integer is 1 byte in size (the
|
||||||
|
declared integer size does not impact the parsing or encoding).
|
||||||
|
|
||||||
|
At firmware compile time, the build will collect all commands declared
|
||||||
|
with DECL_COMMAND(), determine their parameters, and arrange for them
|
||||||
|
to be callable.
|
||||||
|
|
||||||
|
Declaring responses
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
To send information from the firmware to the host a "response" is
|
||||||
|
generated. These are both declared and transmitted using the sendf() C
|
||||||
|
macro. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
sendf("status clock=%u status=%c", sched_read_time(), sched_is_shutdown());
|
||||||
|
```
|
||||||
|
|
||||||
|
The above transmits a "status" response message that contains two
|
||||||
|
integer parameters ("clock" and "status"). At firmware compile time
|
||||||
|
the build automatically finds all sendf() calls and generates encoders
|
||||||
|
for them. The first parameter of the sendf() function describes the
|
||||||
|
response and it is in the same format as command declarations.
|
||||||
|
|
||||||
|
The host can arrange to register a callback function for each
|
||||||
|
response. So, in effect, commands allow the host to invoke C functions
|
||||||
|
in the firmware and responses allow the firmware to invoke code in the
|
||||||
|
host.
|
||||||
|
|
||||||
|
The firmware should only invoke sendf() from command or task handlers,
|
||||||
|
and it should not be invoked from interrupts or timers. The firmware
|
||||||
|
does not need to issue a sendf() from a command, it is not limited in
|
||||||
|
the number of times sendf() may be invoked, and it may invoke sendf()
|
||||||
|
at any time from a task handler.
|
||||||
|
|
||||||
|
### Output responses
|
||||||
|
|
||||||
|
To simplify debugging, the firmware also has an output() C
|
||||||
|
function. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
output("The value of %u is %s with size %u.", x, buf, buf_len);
|
||||||
|
```
|
||||||
|
|
||||||
|
The output() function is similar in usage to printf() - it is intended
|
||||||
|
to generate and format arbitrary messages for human consumption. It is
|
||||||
|
a wrapper around sendf() and as with sendf() it should not be called
|
||||||
|
from interrupts or timers.
|
||||||
|
|
||||||
|
Low-level message encoding
|
||||||
|
==========================
|
||||||
|
|
||||||
|
To accomplish the above RPC mechanism, each command and response is
|
||||||
|
encoded into a binary format for transmission. This section describes
|
||||||
|
the transmission system.
|
||||||
|
|
||||||
|
Message Blocks
|
||||||
|
--------------
|
||||||
|
|
||||||
|
All data sent from host to firmware and vice-versa are contained in
|
||||||
|
"message blocks". A message block has a two byte header and a three
|
||||||
|
byte trailer. The format of a message block is:
|
||||||
|
|
||||||
|
```
|
||||||
|
<1 byte length><1 byte sequence><n-byte content><2 byte crc><1 byte sync>
|
||||||
|
```
|
||||||
|
|
||||||
|
The length byte contains the number of bytes in the message block
|
||||||
|
including the header and trailer bytes (thus the minimum message
|
||||||
|
length is 5 bytes). The maximum message block length is currently 64
|
||||||
|
bytes. The sequence byte contains a 4 bit sequence number in the
|
||||||
|
low-order bits and the high-order bits always contain 0x10 (the
|
||||||
|
high-order bits are reserved for future use). The content bytes
|
||||||
|
contain arbitrary data and its format is described in the following
|
||||||
|
section. The crc bytes contain a 16bit CCITT
|
||||||
|
[CRC](https://en.wikipedia.org/wiki/Cyclic_redundancy_check) of the
|
||||||
|
message block including the header bytes but excluding the trailer
|
||||||
|
bytes. The sync byte is 0x7e.
|
||||||
|
|
||||||
|
The format of the message block is inspired by
|
||||||
|
[HDLC](https://en.wikipedia.org/wiki/High-Level_Data_Link_Control)
|
||||||
|
message frames. Like in HDLC, the message block may optionally contain
|
||||||
|
an additional sync character at the start of the block. Unlike in
|
||||||
|
HDLC, a sync character is not exclusive to the framing and may be
|
||||||
|
present in the message block content.
|
||||||
|
|
||||||
|
Message Block Contents
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Each message block sent from host to firmware contains a series of
|
||||||
|
zero or more message commands in its contents. Each command starts
|
||||||
|
with a Variable Length Quantity (VLQ) encoded command-id followed by
|
||||||
|
zero or more VLQ parameters for the given command. So, the contents of
|
||||||
|
an example message block might look like:
|
||||||
|
|
||||||
|
```
|
||||||
|
<id_cmd_a><param1><id_cmd_b><param1><param2><id_cmd_c><id_cmd_d>
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to encode and parse the message contents, both the host and
|
||||||
|
firmware must agree on a "data dictionary". The data dictionary
|
||||||
|
associates high-level commands with specific integer command-ids along
|
||||||
|
with the number of parameters that the command takes. When processing
|
||||||
|
the data, the parser will know to expect a specific number of VLQ
|
||||||
|
encoded parameters following a given command. So, in the above
|
||||||
|
example, both the host and firmware would know that "id_cmd_a" is
|
||||||
|
always followed by exactly one parameter, "id_cmd_b" two parameters,
|
||||||
|
and "id_cmd_c" / "id_cmd_d" zero parameters.
|
||||||
|
|
||||||
|
The message contents for blocks sent from firmware to host follow the
|
||||||
|
same format. The identifiers in these messages are "response ids", but
|
||||||
|
they serve the same purpose and follow the same encoding rules. In
|
||||||
|
practice, message blocks sent from the firmware to the host never
|
||||||
|
contain more than one response in the message block contents.
|
||||||
|
|
||||||
|
### Variable Length Quantities
|
||||||
|
|
||||||
|
See the [wikipedia article](https://en.wikipedia.org/wiki/Variable-length_quantity)
|
||||||
|
for more information on the general format of VLQ encoded
|
||||||
|
integers. Klipper uses an encoding scheme that supports both positive
|
||||||
|
and negative integers. Integers close to zero use less bytes to encode
|
||||||
|
and positive integers typically encode using less bytes than negative
|
||||||
|
integers. The following table shows the number of bytes each integer
|
||||||
|
takes to encode:
|
||||||
|
|
||||||
|
| Integer | Encoded size |
|
||||||
|
|---------------------------|--------------|
|
||||||
|
| -32 .. 95 | 1 |
|
||||||
|
| -4096 .. 12287 | 2 |
|
||||||
|
| -524288 .. 1572863 | 3 |
|
||||||
|
| -67108864 .. 201326591 | 4 |
|
||||||
|
| -2147483648 .. 4294967295 | 5 |
|
||||||
|
|
||||||
|
### Variable length strings
|
||||||
|
|
||||||
|
As an exception to the above encoding rules, if a parameter to a
|
||||||
|
command or response is a dynamic string then the parameter is not
|
||||||
|
encoded as a simple VLQ integer. Instead it is encoded by transmitting
|
||||||
|
the length as a VLQ encoded integer followed by the contents itself:
|
||||||
|
|
||||||
|
```
|
||||||
|
<VLQ encoded length><n-byte contents>
|
||||||
|
```
|
||||||
|
|
||||||
|
The data dictionary includes information on each parameter so that
|
||||||
|
both the host and firmware know which command parameters use simple
|
||||||
|
VLQ encoding and which parameters use string encoding.
|
||||||
|
|
||||||
|
Data Dictionary
|
||||||
|
===============
|
||||||
|
|
||||||
|
In order for meaningful communications to be established between
|
||||||
|
firmware and host, both sides must agree on a "data dictionary". This
|
||||||
|
data dictionary contains the integer identifiers for commands and
|
||||||
|
responses along with the number of parameters and the types of
|
||||||
|
parameters for each command/response.
|
||||||
|
|
||||||
|
At compile time the firmware build uses the contents of DECL_COMMAND()
|
||||||
|
and sendf() macros to generate the data dictionary. The build
|
||||||
|
automatically assigns unique identifiers to each command and
|
||||||
|
response. The data dictionary maps the high-level names (eg,
|
||||||
|
"get_status") to the integer command ids. This allows the host and
|
||||||
|
firmware to use descriptive ASCII names while still using minimal
|
||||||
|
bandwidth.
|
||||||
|
|
||||||
|
The host queries the data dictionary when it first connects to the
|
||||||
|
firmware. Once the host downloads the data dictionary from the
|
||||||
|
firmware, it uses that data dictionary to encode all commands and to
|
||||||
|
parse all responses from the firmware. The host must therefore handle
|
||||||
|
a dynamic data dictionary. However, to keep the firmware simple, the
|
||||||
|
firmware always uses its static (compiled in) data dictionary.
|
||||||
|
|
||||||
|
The data dictionary is queried by sending "identify" commands to the
|
||||||
|
firmware. The firmware will respond to each identify command with an
|
||||||
|
"identify_response" message. Since these two commands are needed prior
|
||||||
|
to obtaining the data dictionary, their integer ids and parameter
|
||||||
|
types are hard-coded in both the firmware and the host. The
|
||||||
|
"identify_response" response id is 0, the "identify" command id
|
||||||
|
is 1. Other than having hard-coded ids the identify command and its
|
||||||
|
response are declared and transmitted the same way as other commands
|
||||||
|
and responses. No other command or response is hard-coded.
|
||||||
|
|
||||||
|
The format of the transmitted data dictionary itself is a zlib
|
||||||
|
compressed JSON string. The firmware compile process generates the
|
||||||
|
string, compresses it, and stores it in the text section of the
|
||||||
|
firmware. The data dictionary can be much larger than the maximum
|
||||||
|
message block size - the host downloads it by sending multiple
|
||||||
|
identify commands requesting progressive chunks of the data
|
||||||
|
dictionary. Once all chunks are obtained the host will assemble the
|
||||||
|
chunks, uncompress the data, and parse the contents.
|
||||||
|
|
||||||
|
In addition to information on the communication protocol, the data
|
||||||
|
dictionary also contains firmware version, configuration, and other
|
||||||
|
useful information.
|
||||||
|
|
||||||
|
Static Strings
|
||||||
|
--------------
|
||||||
|
|
||||||
|
To reduce bandwidth the data dictionary also contains a set of static
|
||||||
|
strings known to the firmware. This is useful when sending messages
|
||||||
|
from firmware to host. For example, if the firmware were to run:
|
||||||
|
|
||||||
|
```
|
||||||
|
shutdown("Unable to handle command");
|
||||||
|
```
|
||||||
|
|
||||||
|
The error message can be encoded and sent using a single VLQ. The host
|
||||||
|
uses the data dictionary to resolve the VLQ encoded static string id
|
||||||
|
to the associated message in the data dictionary.
|
||||||
|
|
||||||
|
Message flow
|
||||||
|
============
|
||||||
|
|
||||||
|
Message commands sent from host to firmware are intended to be
|
||||||
|
error-free. The firmware will check the CRC and sequence numbers in
|
||||||
|
each message block to ensure the commands are accurate and
|
||||||
|
in-order. The firmware always processes message blocks in-order -
|
||||||
|
should it receive a block out-of-order it will discard it and any
|
||||||
|
other out-of-order blocks until it receives blocks with the correct
|
||||||
|
sequencing.
|
||||||
|
|
||||||
|
The low-level host code implements an automatic retransmission system
|
||||||
|
for lost and corrupt message blocks sent to the firmware. To
|
||||||
|
facilitate this, the firmware transmits an "ack message block" after
|
||||||
|
each successfully received message block. The host schedules a timeout
|
||||||
|
after sending each block and it will retransmit should the timeout
|
||||||
|
expire without receiving a corresponding "ack". In addition, if the
|
||||||
|
firmware detects a corrupt or out-of-order block it may transmit a
|
||||||
|
"nak message block" to facilitate fast retransmission.
|
||||||
|
|
||||||
|
An "ack" is a message block with empty content (ie, a 5 byte message
|
||||||
|
block) and a sequence number greater than the last received host
|
||||||
|
sequence number. A "nak" is a message block with empty content and a
|
||||||
|
sequence number less than the last received host sequence number.
|
||||||
|
|
||||||
|
The protocol facilitates a "window" transmission system so that the
|
||||||
|
host can have many outstanding message blocks in-flight at a
|
||||||
|
time. (This is in addition to the many commands that may be present in
|
||||||
|
a given message block.) This allows maximum bandwidth utilization even
|
||||||
|
in the event of transmission latency. The timeout, retransmit,
|
||||||
|
windowing, and ack mechanism are inspired by similar mechanisms in
|
||||||
|
[TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol).
|
||||||
|
|
||||||
|
In the other direction, message blocks sent from firmware to host are
|
||||||
|
designed to be error-free, but they do not have assured
|
||||||
|
transmission. (Responses should not be corrupt, but they may go
|
||||||
|
missing.) This is done to keep the implementation in the firmware
|
||||||
|
simple. There is no automatic retransmission system for responses -
|
||||||
|
the high-level code is expected to be capable of handling an
|
||||||
|
occasional missing response (usually by re-requesting the content or
|
||||||
|
setting up a recurring schedule of response transmission). The
|
||||||
|
sequence number field in message blocks sent to the host is always one
|
||||||
|
greater than the last received sequence number of message blocks
|
||||||
|
received from the host. It is not used to track sequences of response
|
||||||
|
message blocks.
|
Loading…
Reference in New Issue