From 167927fb00c83f3e8bae5f9d10bcbb78fd81b420 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Tue, 14 Jul 2026 13:28:21 +1000 Subject: [PATCH] Add 0.3.1 with keycode version query. --- data/xap/xap_0.3.1.hjson | 21 ++++ docs/xap_0.3.1.md | 252 +++++++++++++++++++++++++++++++++++++++ docs/xap_protocol.md | 1 + 3 files changed, 274 insertions(+) create mode 100644 data/xap/xap_0.3.1.hjson create mode 100644 docs/xap_0.3.1.md diff --git a/data/xap/xap_0.3.1.hjson b/data/xap/xap_0.3.1.hjson new file mode 100644 index 00000000000..ab6dc34c50d --- /dev/null +++ b/data/xap/xap_0.3.1.hjson @@ -0,0 +1,21 @@ +{ + version: 0.3.1 + routes: { + 0x01: { + routes: { + 0x10: { + type: command + name: Keycodes version query + define: KEYCODES_VERSION_QUERY + description: + ''' + Query the version of the keycodes known by the firmware. + ''' + return_type: u32 + return_purpose: bcd-version + return_constant: QMK_KEYCODES_VERSION_BCD + } + } + } + } +} diff --git a/docs/xap_0.3.1.md b/docs/xap_0.3.1.md new file mode 100644 index 00000000000..16ccc64d4de --- /dev/null +++ b/docs/xap_0.3.1.md @@ -0,0 +1,252 @@ + + + + +# QMK Firmware XAP Specs + +This document describes the requirements of the QMK XAP ("extensible application protocol") API. + +## Types + +**All integral types are little-endian.** + +| Name | Definition | +| -- | -- | +| _bool_ | Data type that contains values 0 and 1. Implemented as an alias of `u8`. | +| _struct{}_ | A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a packed C-style `struct`. The order in which they're defined matches the order of the data in the response packet. | +| _type[n]_ | An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets. | +| _u16_ | An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_. | +| _u32_ | An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_. | +| _u64_ | An unsigned 64-bit integral, commonly seen as `uint64_t` from _stdint.h_. | +| _u8_ | An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_. | + +## Definitions + +This list defines the terms used across the entire set of XAP protocol documentation. + +| Name | Definition | +| -- | -- | +| _Capability_ | A way to determine if certain functionality is enabled in the firmware. Any _subsystem_ that provides build-time restriction of functionality must provide a _route_ for a _capabilities query_. | +| _Handler_ | A piece of code that is executed when a specific _route_ is received. | +| _Payload_ | Any received data appended to the _route_, which gets delivered to the _handler_ when received. | +| _Response_ | The data sent back to the host during execution of a _handler_. | +| _Route_ | A sequence of _IDs_ describing the route to invoke a _handler_. | +| _Secure Route_ | A _route_ which has potentially destructive consequences, necessitating prior approval by the user before executing. | +| _Subsystem_ | A high-level area of functionality within XAP. | +| _Unlock sequence_ | A physical sequence initiated by the user to enable execution of _secure routes_. | +| _Broadcast Header_ | Packet format for broadcast messages. Takes the format:
`token` - token
`u8` - type
`u8` - length | +| _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. | +| _Request Header_ | Packet format for inbound data. Takes the format:
`token` - token
`u8` - length | +| _Response Flags_ | An `u8` containing the status of the request. | +| _Response Header_ | Packet format for outbound data. Takes the format:
`token` - token
`response_flags` - flags
`u8` - length | +| _Token_ | A `u16` associated with a specific request as well as its corresponding response. Valid token values are within the range `0x0100`-`0xFFFF`. | + +## Requests and Responses + +Communication generally follows a request/response pattern. + +Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response. +This allows response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. +Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below. +To ensure host interoperability, valid token values are within the range `0x0100`-`0xFFFF`. + +This token is followed by a `u8` signifying the length of data in the request. + +Two token values are reserved: `0xFFFE` and `0xFFFF`: +* `0xFFFE`: A message sent by a host application may use this token if no response is to be sent -- a "fire and forget" message. +* `0xFFFF`: Signifies a "broadcast" message sent by the firmware without prompting from the host application. Broadcast messages are defined later in this document. + +When not using reserved tokens above, requests will always generate one corresponding response with a matching token. + +Maximum total message length is 64 bytes - a single HID report size, no fragmentation or reassembly needed. + +Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length: + +| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | +| -- | -- | -- | -- | -- | -- | -- | -- | +| `-` | `-` | `-` | `-` | `-` | `-` | `SECURE_FAILURE` | `SUCCESS` | + +* Bit 1 (`SECURE_FAILURE`): When this bit is set, the requested _route_ was marked _secure_ but an _unlock sequence_ has not completed. +* Bit 0 (`SUCCESS`): When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token). + +### Example "conversation": + +**Request** -- version query: + +| Byte | 0 | 1 | 2 | 3 | 4 | +| --- | --- | --- | --- | --- | --- | +| **Purpose** | Token | Token | Payload Length | Route | Route | +| **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` | + +**Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192: + +| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload | +| **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` | + +## Routes + +Subsystem validity should be queried through the “Enabled-in-firmware subsystem query” under the QMK subsystem (route=0x00,0x01). +This is the primary method for determining if a subsystem has been enabled in the running firmware. + +### XAP - `0x00` +This subsystem is always present, and provides the ability to query information about the XAP protocol of the connected device. + +| Name | Route | Tags | Payloads | Description | +| -- | -- | -- | -- | -- | +| Version Query | `0x00 0x00` | | __Response:__ `u32` | XAP protocol version query.

* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`
* e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.| +| Capabilities Query | `0x00 0x01` | | __Response:__ `u32` | XAP subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.| +| Enabled subsystem query | `0x00 0x02` | | __Response:__ `u32` | XAP protocol subsystem query. Each bit should be considered as a "usable" subsystem. For example, checking `(value & (1 << XAP_ROUTE_QMK) != 0)` means the QMK subsystem is enabled and available for querying.| +| Secure Status | `0x00 0x03` | | __Response:__ `u8` | Query secure route status

* 0 means secure routes are disabled
* 1 means unlock sequence initiated but incomplete
* 2 means secure routes are allowed
* any other value should be interpreted as disabled| +| Secure Unlock | `0x00 0x04` | | | Initiate secure route unlock sequence| +| Secure Lock | `0x00 0x05` | | | Disable secure routes| + +### QMK - `0x01` +This subsystem is always present, and provides the ability to address QMK-specific functionality. + +| Name | Route | Tags | Payloads | Description | +| -- | -- | -- | -- | -- | +| Version Query | `0x01 0x00` | | __Response:__ `u32` | QMK protocol version query.

* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`
* e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.| +| Capabilities Query | `0x01 0x01` | | __Response:__ `u32` | QMK subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.| +| Board identifiers | `0x01 0x02` | | __Response:__
    * Vendor ID: `u16`
    * Product ID: `u16`
    * Product Version: `u16`
    * QMK Unique Identifier: `u32` | Retrieves the set of identifying information for the board.| +| Board Manufacturer | `0x01 0x03` | | __Response:__ `string` | Retrieves the name of the manufacturer| +| Product Name | `0x01 0x04` | | __Response:__ `string` | Retrieves the product name| +| Config Blob Length | `0x01 0x05` | | __Response:__ `u16` | Retrieves the length of the configuration data bundled within the firmware| +| Config Blob Chunk | `0x01 0x06` | | __Request:__
    * offset: `u16`

__Response:__ `u8[32]` | Retrieves a chunk of the configuration data bundled within the firmware| +| Jump to bootloader | `0x01 0x07` | __Secure__ | __Response:__ `u8` | Jump to bootloader

May not be present - if QMK capabilities query returns “true”, then jump to bootloader is supported

* 0 means secure routes are disabled, and should be considered as a failure
* 1 means successful, board will jump to bootloader| +| Hardware Identifier | `0x01 0x08` | | __Response:__ `u32[4]` | Retrieves a unique identifier for the board.| +| Reinitialize EEPROM | `0x01 0x09` | __Secure__ | __Response:__ `u8` | Reinitializes the keyboard's EEPROM (persistent memory)

May not be present - if QMK capabilities query returns “true”, then reinitialize is supported

* 0 means secure routes are disabled, and should be considered as a failure
* 1 means successful, board will reinitialize and then reboot| +| Keycodes version query | `0x01 0x10` | | __Response:__ `u32` | Query the version of the keycodes known by the firmware.| + +### Keyboard - `0x02` +This subsystem is always present, and reserved for vendor-specific functionality. No routes are defined by XAP. + +### User - `0x03` +This subsystem is always present, and reserved for user-specific functionality. No routes are defined by XAP. + +### Keymap - `0x04` +This subsystem allows for query of currently configured keycodes. + +| Name | Route | Tags | Payloads | Description | +| -- | -- | -- | -- | -- | +| Capabilities Query | `0x04 0x01` | | __Response:__ `u32` | Keymap subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.| +| Get Layer Count | `0x04 0x02` | | __Response:__ `u8` | Query maximum number of layers that can be addressed within the keymap.| +| Get Keycode | `0x04 0x03` | | __Request:__
    * Layer: `u8`
    * Row: `u8`
    * Column: `u8`

__Response:__ `u16` | Query the Keycode at the requested location.| +| Get Encoder Keycode | `0x04 0x04` | | __Request:__
    * Layer: `u8`
    * Encoder: `u8`
    * Clockwise: `u8`

__Response:__ `u16` | Query the Keycode at the requested location.| + +### Remapping - `0x05` +This subsystem allows for live reassignment of keycodes without rebuilding the firmware. + +| Name | Route | Tags | Payloads | Description | +| -- | -- | -- | -- | -- | +| Capabilities Query | `0x05 0x01` | | __Response:__ `u32` | Remapping subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.| +| Get Layer Count | `0x05 0x02` | | __Response:__ `u8` | Query maximum number of layers that can be addressed within the keymap.| +| Set Keycode | `0x05 0x03` | __Secure__ | __Request:__
    * Layer: `u8`
    * Row: `u8`
    * Column: `u8`
    * Keycode: `u16` | Modify the Keycode at the requested location.| +| Set Encoder Keycode | `0x05 0x04` | __Secure__ | __Request:__
    * Layer: `u8`
    * Encoder: `u8`
    * Clockwise: `u8`
    * Keycode: `u16` | Modify the Keycode at the requested location.| + +### Lighting - `0x06` +This subsystem allows for control over the lighting subsystem. + +| Name | Route | Tags | Payloads | Description | +| -- | -- | -- | -- | -- | +| Capabilities Query | `0x06 0x01` | | __Response:__ `u32` | Lighting subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.| + + +#### backlight - `0x06 0x02` +This subsystem allows for control over the backlight subsystem. + +| Name | Route | Tags | Payloads | Description | +| -- | -- | -- | -- | -- | +| Capabilities Query | `0x06 0x02 0x01` | | __Response:__ `u32` | backlight subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.| +| Get Enabled Effects | `0x06 0x02 0x02` | | __Response:__ `u8` | Each bit should be considered as a "usable" effect id| +| Get Config | `0x06 0x02 0x03` | | __Response:__
    * enable: `u8`
    * mode: `u8`
    * val: `u8` | Query the current config.| +| Set Config | `0x06 0x02 0x04` | | __Request:__
    * enable: `u8`
    * mode: `u8`
    * val: `u8` | Set the current config.| +| Save Config | `0x06 0x02 0x05` | | | Save the current config.| + + +#### rgblight - `0x06 0x03` +This subsystem allows for control over the rgblight subsystem. + +| Name | Route | Tags | Payloads | Description | +| -- | -- | -- | -- | -- | +| Capabilities Query | `0x06 0x03 0x01` | | __Response:__ `u32` | rgblight subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.| +| Get Enabled Effects | `0x06 0x03 0x02` | | __Response:__ `u64` | Each bit should be considered as a "usable" effect id| +| Get Config | `0x06 0x03 0x03` | | __Response:__
    * enable: `u8`
    * mode: `u8`
    * hue: `u8`
    * sat: `u8`
    * val: `u8`
    * speed: `u8` | Query the current config.| +| Set Config | `0x06 0x03 0x04` | | __Request:__
    * enable: `u8`
    * mode: `u8`
    * hue: `u8`
    * sat: `u8`
    * val: `u8`
    * speed: `u8` | Set the current config.| +| Save Config | `0x06 0x03 0x05` | | | Save the current config.| + + +#### rgbmatrix - `0x06 0x04` +This subsystem allows for control over the rgb matrix subsystem. + +| Name | Route | Tags | Payloads | Description | +| -- | -- | -- | -- | -- | +| Capabilities Query | `0x06 0x04 0x01` | | __Response:__ `u32` | rgb matrix subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.| +| Get Enabled Effects | `0x06 0x04 0x02` | | __Response:__ `u64` | Each bit should be considered as a "usable" effect id| +| Get Config | `0x06 0x04 0x03` | | __Response:__
    * enable: `u8`
    * mode: `u8`
    * hue: `u8`
    * sat: `u8`
    * val: `u8`
    * speed: `u8`
    * flags: `u8` | Query the current config.| +| Set Config | `0x06 0x04 0x04` | | __Request:__
    * enable: `u8`
    * mode: `u8`
    * hue: `u8`
    * sat: `u8`
    * val: `u8`
    * speed: `u8`
    * flags: `u8` | Set the current config.| +| Save Config | `0x06 0x04 0x05` | | | Save the current config.| + +### Audio - `0x07` + This subsystem allows for control over the audio subsystem. + +| Name | Route | Tags | Payloads | Description | +| -- | -- | -- | -- | -- | +| Capabilities Query | `0x07 0x01` | | __Response:__ `u32` | Audio subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.| +| Get Config | `0x07 0x03` | | __Response:__
    * enable: `u8`
    * clicky_enable: `u8` | Query the current config.| +| Set Config | `0x07 0x04` | | __Request:__
    * enable: `u8`
    * clicky_enable: `u8` | Set the current config.| +| Save Config | `0x07 0x05` | | | Save the current config.| + + +## Broadcast messages + +Broadcast messages may be sent by the firmware to the host, without a corresponding inbound request. Each broadcast message uses the token `0xFFFF`, and does not expect a response from the host. Tokens are followed by an _ID_ signifying the type of broadcast, then the response _payload_ length, and finally the corresponding _payload_. + +### Log message - `0x00` +Replicates and replaces the same functionality as if using the standard QMK `CONSOLE_ENABLE = yes` in `rules.mk`. Normal prints within the firmware will manifest as log messages broadcast to the host. `hid_listen` will not be functional with XAP enabled. + +Log message payloads include `u8[Length]` containing the text, where the length of the text is the _broadcast_header.length_ field. + +**Example Log Broadcast** -- log message "Hello QMK!" + +| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| **Purpose** | Token | Token | Broadcast Type | Length | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | +| **Value** | `0xFF` | `0xFF` | `0x00` | `0x0A`(10) | `0x48`(H) | `0x65`(e) | `0x6C`(l) | `0x6C`(l) | `0x6F`(o) | `0x20`( ) | `0x51`(Q) | `0x4D`(M) | `0x4B`(K) | `0x21`(!) | +### Secure Status - `0x01` +Secure status has changed. Payloads include a `u8` matching a 'Secure Status' request. + +**Example Secure Status Broadcast** -- secure "Unlocking" + +| Byte | 0 | 1 | 2 | 3 | 4 | +| --- | --- | --- | --- | --- | --- | +| **Purpose** | Token | Token | Broadcast Type | Length | Secure Status | +| **Value** | `0xFF` | `0xFF` | `0x01` | `0x01` | `0x01` | +### Keyboard - `0x02` +Reserved for vendor-specific functionality. No messages are defined by XAP. +### User - `0x03` +Reserved for user-specific functionality. No messages are defined by XAP. + diff --git a/docs/xap_protocol.md b/docs/xap_protocol.md index 2d37b9c3b10..f2f6d336921 100644 --- a/docs/xap_protocol.md +++ b/docs/xap_protocol.md @@ -24,6 +24,7 @@ "Y88P" ******************************************************************************* ---> +* [XAP Version 0.3.1](xap_0.3.1.md) * [XAP Version 0.3.0](xap_0.3.0.md) * [XAP Version 0.2.0](xap_0.2.0.md) * [XAP Version 0.1.0](xap_0.1.0.md)