Skip to content

Latest commit

 

History

History
177 lines (131 loc) · 10 KB

Bluetooth-LE-TNC.md

File metadata and controls

177 lines (131 loc) · 10 KB

Bluetooth LE / Bluetooth Smart TNC Protocol Design

This is a protocol specification for the Bluetooth LE based communication channel between an APRS / Packet Radio client and a TNC.

The TNC is defined as a GATT service profile that provides the following functions via individual GATT characteristics:

  • TX - transmit AX.25 packets (via GATT write characteristic)
  • RX - receive AX.25 packets (via notify followed by read characteristic)
  • Diag - receive TNC diagnostic messages (via notify followed by read characteristic)
  • Vol - receive the audio volume level on the TNC input (via read characteristic or notify)

Over radio, AX.25 frames are sent and received in APRS and Packet Radio. In (mostly serial) host-to-TNC communication, these frames are usually encapsulated using the KISS protocol that adds multiplexing, serialization, and framing.

This protocol design is using the KISS framing inside of Bluetooth characteristics for the following reasons:

  • Backward Compatibility: a TNC is usually offering multiple client interfaces (USB serial; Bluetooth Serial Port Profile; Bluetooth Smart). Using the same encapsulation format on all interfaces reduces complexity.
  • Framing: it is possible to store multiple KISS frames in a single characteristic values, back-to-back, increasing the throughput on the slow Bluetooth LE channel.
  • Configuration: The KISS protocol allows to implement vendor-specific commands and status messages. These can be easily exchanged using the RX/TX characteristics.

Basic Usage Patterns

Bluetooth LE GATT is a client-driven protocol. All interactions are initiated by the LE client (also called central, most probably a smartphone), and the LE server (peripheral, the TNC) is the responder. The only means for the LE server to initiate a transmission is by means of the limited notify characteristic mechanism, to which the client must explicitly subscribe.

The basic usage pattern for the TNC is as follows:

  • The LE client connects to the TNC (LE server with well-known Service UUID)
  • Client subscribes to RX and Diag (and optionally Vol) characteristics
  • Client transmits packets by issuing write to the TX characteristic
  • TNC informs the client of incoming packets / diag messages via notify
  • Client reads packets / diagnostic messages via read on the respective characteristic
  • The client can either poll Vol from time to time, or subscribe to notifications

GATT Service and Characteristics

The TNC GATT service has the following well-defined UUID:

ca1060dc-6fb0-4d48-b931-073ed111081b

This has been generated by a magic incantation of uuidgen, and shall be the master UUID for Bluetooth LE based TNCs henceforth, by demand of the wise Bluetooth SIG wizards.

The TNC GATT service shall contain the following characteristics, with their UUIDs derived from the service UUID:

  • 00000001-6fb0-4d48-b931-073ed111081b TX - transmit packets (KISS format, write only)

  • 00000002-6fb0-4d48-b931-073ed111081b RX - receive packets (KISS format, read and notify)

  • 00000003-6fb0-4d48-b931-073ed111081b Diag - receive diagnostic messages from the TNC (UTF-8 strings, read and notify)

  • 00000004-6fb0-4d48-b931-073ed111081b Vol - receive the audio volume on the TNC input (16-bit Little Endian, read and notify)

  • 000000ff-6fb0-4d48-b931-073ed111081b MTU - enforce an MTU exchange initiated by the TNC

For RX and Diag, the notify packet only deliveres the MTU-sized prefix of the actual message. Therefore, a follow-up read must be performed by the client.

Transmitting AX.25 Packets

The client constructs a KISS frame and writes it to the 0001 characteristic (using 0001 here as short form for 00000001-6fb0-4d48-b931-073ed111081b). If the KISS frame exceeds the MTU, the client splits it into a series of BLE prepare-write commands, followed by an execute-write. While the BLE standard allows mixing prepare-writes to different characteristics, for the sake of protocol simplicity this protocol limits batch writes to a single characteristic, and enforces consecutiveness of the individual fragments.

After receiving the BLE write or execute-write, the TNC processes the KISS frame according to its frame type. AX.25 packets are excracted and sent via RF, control frames are processed by the TNC.

If the TNC's KISS transmission buffer is filled up, it can perform BLE-level flow control by delaying the BLE write response acknowledgement to the client until sufficient buffer space is freed up by processing/transmitting the frame obtained from the client.

Optional: allow multiple KISS frames to be sent together (up to 512 bytes) by putting them back-to-back into the characteristic.

Receiving over BLE

Bluetooth LE has only one way of server-initiated communication: notify. A client must "subscribe" to a characteristic to obtain notifys for it. Therefore, to receive packets, the BLE client subscribes to the RX/notify characteristic 0002.

Generally, up to 512 Bytes can be delivered in a characteristic. However, a notify is truncated to the MTU size. As the client can not determine if a notify was truncated, it needs to perform a series of read requests with increasing offsets to obtain the full charcteristic value. Generally, the client starts with a read at offset 0, and performs follow-up reads with the offset increased by the MTU, until it reaches 512 Bytes or receives a read response that is smaller than the MTU. During this series of read requests, the characteristic value should be kept constant by the server.

Receiving KISS Frames

When the TNC receives a valid AX.25 frame over RF, it encapsulates it into a KISS frame, and sends a notify with the first MTU bytes of that KISS frame. The TNC stores the full KISS frame in the 0002 characteristic buffer. The BLE client, upon getting the notify, performs a series of reads on 0002 until it obtains the complete KISS frame. If the TNC obtains an additional frame in the meantime, it caches that frame in a separate buffer, and delays the notify until the client has finished reading the characteristic. After the client has finished reading, the old KISS frame is discarded and the new one is stored in the 0002 characteristic buffer.

Optional: allow multiple KISS frames to be sent together (up to 512 bytes) by putting them back-to-back into the characteristic. Additional KISS frames can be added to the characteristic buffer until the client's read has reached the buffer end, or the 512 byte characteristic size would be exceeded. Only one notify needs to be sent for the aggregated KISS frames.

Diagnostics

The "Receiving over BLE" considerations also apply for the Diagnostics characteristic 0003. Diagnostic messages are UTF-8 strings (they may also contain newlines). There is no aggregation of multiple messages, each message causes a notify and must be read by the client.

Volume Level

The volume level (characteristic 0004) is a 16-bit value ranging from 0x0000 (silence) to 0xffff (maximum value) encoded in Little Endian byte order. The application may either read the characteristic value whenever needed, or subscribe to it and receive notifys with the current value. The BLE server should balance its energy consumption with the user's demand for up-to-date information, e.g. by only sending changes above a certain threshold, or by averaging the volume level over a certain time (i.e. 0.5 seconds).

MTU Exchange (Optional)

By default, Android clients do not perform an MTU exchange (and there is no client API to enforce it prior to Android 5), thus leaving the default MTU of 22 bytes. To work around that, it is possible for the BLE server to initiate an MTU exchange. However, having the server do that at a server-controlled time can lead to race conditions (the MTU exchange changes the client's expectations on the data amount for read/writes).

To allow for a serialized MTU exchange, an additional read characteristic 00ff is proposed, which is used by the client to let the server initiate an MTU exchange as follows:

  1. Client sends read request on 00ff
  2. Server sends MTU request (with the maximum 512 Bytes value)
  3. Client sends MTU response (hopefully also with 512 Bytes)
  4. Server sends read response with a single data byte 0x00

Discussion points

  • Use a different framing instead of KISS? I'm really open here, as long as it is practical to implement and has benefits. I could imagine some other mechanism, i.e. send a "packet count X" via notify and let the client read the RX characteristic X times, with each characteristic value representing one packet.

  • Multi-packet aggregation: With a large MTU (512 Bytes), we can significantly increase Android throughput by putting multiple packets into one characteristic. KISS comes handy here, but we could use another encoding as well (i.e: <count><size><data><size><data>). The MTU on iOS is fixed to 157 Bytes, but they generally have a faster and more robust stack.

  • Timestamps: if you have some kind of RTC, you could add the packet's actual RX timestamp into the frame, so we could cleanly handle "old" packets. If you have no RTC, but a deep-sleep independent timer, we could use a relative timestamp (e.g. the packet's age in milliseconds at the time of querying the characteristic). The BLE client could then do the math to convert it into an absolute time.

  • Flow control: the TNC could perform flow-control by delaying the write response for each outgoing AX.25 packet until the packet has been transmitted OTA. That would allow the host to have very fine-grained feedback about the timing of transmissions, however it would prevent the back-to-back transmission of multiple packets queued in the TNC.