diff --git a/data/templates/xap/client/python/types.py.j2 b/data/templates/xap/client/python/types.py.j2 index 2509688abfd..8ed0e624da9 100644 --- a/data/templates/xap/client/python/types.py.j2 +++ b/data/templates/xap/client/python/types.py.j2 @@ -2,10 +2,9 @@ from collections import namedtuple from enum import IntFlag, IntEnum from struct import Struct -{% set type_definitions = [{'name':'XAPRequest', 'members': 'token length data', 'fmt':'`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. + +Any request will generate at least one corresponding response, with the exception of messages using reserved tokens. Maximum total message length is 128 bytes due to RAM constraints. + +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:__ `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.| + +### 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.| + +#### 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.| + + +## 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, with 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 a `u8` signifying the length of the text, followed by the `u8[Length]` containing the text itself. + +**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 | +| --- | --- | --- | --- | --- | +| **Purpose** | Token | Token | Broadcast Type | Secure Status | +| **Value** | `0xFF` | `0xFF` | `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 08390580bbe..f70f605bb5b 100644 --- a/docs/xap_protocol.md +++ b/docs/xap_protocol.md @@ -1,4 +1,5 @@ +* [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) * [XAP Version 0.0.1](xap_0.0.1.md) diff --git a/lib/python/qmk/cli/xap/xap.py b/lib/python/qmk/cli/xap/xap.py index c050a4f2e75..767ccf7b251 100644 --- a/lib/python/qmk/cli/xap/xap.py +++ b/lib/python/qmk/cli/xap/xap.py @@ -7,7 +7,7 @@ from milc import cli from qmk.keyboard import render_layout from qmk.xap.common import get_xap_keycodes -from xap_client import XAPClient, XAPEventType, XAPSecureStatus +from xap_client import XAPClient, XAPEventType, XAPSecureStatus, XAPConfigRgblight KEYCODE_MAP = get_xap_keycodes('latest') @@ -187,6 +187,14 @@ class XAPShell(cmd.Cmd): print('^C') return False + def do_dump(self, line): + ret = self.device.transaction(b'\x06\x03\x03') + ret = XAPConfigRgblight.from_bytes(ret) + print(ret) + + ret = self.device.int_transaction(b'\x06\x03\x02') + print(f'XAPEffectRgblight(enabled={bin(ret)})') + @cli.argument('-v', '--verbose', arg_only=True, action='store_true', help='Turns on verbose output.') @cli.argument('-d', '--device', help='device to select - uses format :.') diff --git a/lib/python/qmk/xap/common.py b/lib/python/qmk/xap/common.py index 64372db804e..da888b09c8c 100755 --- a/lib/python/qmk/xap/common.py +++ b/lib/python/qmk/xap/common.py @@ -7,6 +7,7 @@ from pathlib import Path from typing import OrderedDict from jinja2 import Environment, FileSystemLoader, select_autoescape +from qmk.casing import to_snake from qmk.constants import QMK_FIRMWARE from qmk.json_schema import json_load, validate from qmk.decorators import lru_cache @@ -24,7 +25,7 @@ def _get_jinja2_env(data_templates_xap_subdir: str): def render_xap_output(data_templates_xap_subdir, file_to_render, defs): j2 = _get_jinja2_env(data_templates_xap_subdir) - return j2.get_template(file_to_render).render(xap=defs, xap_str=hjson.dumps(defs)) + return j2.get_template(file_to_render).render(xap=defs, xap_str=hjson.dumps(defs), to_snake=to_snake) def _find_kb_spec(kb): diff --git a/lib/python/xap_client/client.py b/lib/python/xap_client/client.py index 598241548f4..f4631b87122 100644 --- a/lib/python/xap_client/client.py +++ b/lib/python/xap_client/client.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-2.0-or-later from typing import List + class XAPClient: """XAP device discovery """ diff --git a/lib/python/xap_client/routes.py b/lib/python/xap_client/routes.py index 13f8f3cf6f5..e5a0891bced 100644 --- a/lib/python/xap_client/routes.py +++ b/lib/python/xap_client/routes.py @@ -61,3 +61,7 @@ class XAPRoutes(): REMAPPING_SET_ENCODER_KEYCODE = b'\x05\x04' # LIGHTING LIGHTING_CAPABILITIES_QUERY = b'\x06\x01' + LIGHTING_RGBLIGHT = b'\x06\x03' + LIGHTING_RGBLIGHT_CAPABILITIES_QUERY = b'\x06\x03\x01' + LIGHTING_RGBLIGHT_GET_ENABLED_EFFECTS = b'\x06\x03\x02' + LIGHTING_RGBLIGHT_GET_CONFIG = b'\x06\x03\x03' diff --git a/lib/python/xap_client/types.py b/lib/python/xap_client/types.py index 626830a475c..edb19dc62a3 100644 --- a/lib/python/xap_client/types.py +++ b/lib/python/xap_client/types.py @@ -59,6 +59,29 @@ class XAPResponse(namedtuple('XAPResponse', 'token flags length data')): return self.fmt.pack(*list(self)) +class XAPConfigRgblight(namedtuple('XAPConfigRgblight', 'enable mode hue sat val speed')): + fmt = Struct('