This commit is contained in:
zvecr 2022-09-26 18:09:36 +01:00
parent 76a45a4e24
commit 1dbb4c0f96
12 changed files with 359 additions and 8 deletions

View File

@ -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':'<HB61s'}, {'name':'XAPResponse', 'members': 'token flags length data', 'fmt':'<HBB60s'}] %}
{% for item in type_definitions -%}
class {{ item.name }}(namedtuple('{{ item.name }}', '{{ item.members }}')):
fmt = Struct('{{ item.fmt }}')
{% macro gen_struct(name, members, fmt) -%}
class {{ name }}(namedtuple('{{ name }}', '{{ members }}')):
fmt = Struct('{{ fmt }}')
def __new__(cls, *args):
return super().__new__(cls, *args)
@ -18,7 +17,29 @@ class {{ item.name }}(namedtuple('{{ item.name }}', '{{ item.members }}')):
return self.fmt.pack(*list(self))
{% endfor -%}
{% endmacro -%}
{% set type_definitions = [
{'name':'XAPRequest', 'members': 'token length data', 'fmt':'<HB61s'},
{'name':'XAPResponse', 'members': 'token flags length data', 'fmt':'<HBB60s'},
{'name':'XAPConfigRgblight', 'members': 'enable mode hue sat val speed', 'fmt':'<BBBBBB'}
] %}
{% for item in type_definitions -%}{{ gen_struct(item.name, item.members, item.fmt) }}{% endfor -%}
# Spec structs
{%- for item in xap.routes.values() recursive -%}
{%- if item.routes -%}
{{ loop(item.routes.values()) }}
{%- endif -%}
{% if item.request_struct_members %}
# TODO: gen inbound object for {{ to_snake(item.define) }}
{%- endif -%}
{% if item.return_struct_members %}
# TODO: gen outbound object for {{ to_snake(item.define) }}
{%- endif -%}
{%- endfor %}
class XAPSecureStatus(IntEnum):
LOCKED = 0x00
UNLOCKING = 0x01

View File

@ -181,7 +181,6 @@
'''
This subsystem allows for control over the lighting subsystem.
'''
enable_if_preprocessor: defined(RGBLIGHT_ENABLE) || defined(RGB_MATRIX_ENABLE)
routes: {
0x01: {
type: command

78
data/xap/xap_0.3.0.hjson Normal file
View File

@ -0,0 +1,78 @@
{
version: 0.3.0
routes: {
0x06: {
routes: {
0x03: {
type: router
name: rgblight
define: RGBLIGHT
description:
'''
This subsystem allows for control over the rgblight subsystem.
'''
enable_if_preprocessor: defined(RGBLIGHT_ENABLE)
routes: {
0x01: {
type: command
name: Capabilities Query
define: CAPABILITIES_QUERY
description:
'''
rgblight subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
'''
return_type: u32
return_purpose: capabilities
return_constant: XAP_ROUTE_LIGHTING_RGBLIGHT_CAPABILITIES
}
0x02: {
type: command
name: Get Enabled Effects
define: GET_ENABLED_EFFECTS
description: Each bit should be considered as a "usable" effect id
return_type: u64
return_constant: ENABLED_RGBLIGHT_EFFECTS
}
0x03: {
type: command
name: Get Config
define: GET_CONFIG
description: Query the current config.
return_type: struct
return_struct_length: 6
return_struct_members: [
{
type: u8
name: enable
},
{
type: u8
name: mode
},
{
type: u8
name: hue
},
{
type: u8
name: sat
},
{
type: u8
name: val
},
{
type: u8
name: speed
},
]
return_execute: get_rgblight_config
}
}
}
}
}
}
}

194
docs/xap_0.3.0.md Normal file
View File

@ -0,0 +1,194 @@
# 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 |
| -- | -- |
| _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:<br>`token` - token<br>`u8` - type<br>`u8` - length |
| _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. |
| _Request Header_ | Packet format for inbound data. Takes the format:<br>`token` - token<br>`u8` - length |
| _Response Flags_ | An `u8` containing the status of the request. |
| _Response Header_ | Packet format for outbound data. Takes the format:<br>`token` - token<br>`response_flags` - flags<br>`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.<br><br>* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`<br> * 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<br><br>* 0 means secure routes are disabled<br>* 1 means unlock sequence initiated but incomplete<br>* 2 means secure routes are allowed<br>* 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.<br><br>* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`<br> * 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:__<br>&nbsp;&nbsp;&nbsp;&nbsp;* Vendor ID: `u16`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Product ID: `u16`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Product Version: `u16`<br>&nbsp;&nbsp;&nbsp;&nbsp;* 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`<br><br>__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<br><br>May not be present if QMK capabilities query returns “true”, then jump to bootloader is supported<br><br>* 0 means secure routes are disabled, and should be considered as a failure<br>* 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:__<br>&nbsp;&nbsp;&nbsp;&nbsp;* Layer: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Row: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Column: `u8`<br><br>__Response:__ `u16`| Query the Keycode at the requested location.|
| Get Encoder Keycode | `0x04 0x04` | |__Request:__<br>&nbsp;&nbsp;&nbsp;&nbsp;* Layer: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Encoder: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Clockwise: `u8`<br><br>__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:__<br>&nbsp;&nbsp;&nbsp;&nbsp;* Layer: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Row: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Column: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Keycode: `u16`| Modify the Keycode at the requested location.|
| Set Encoder Keycode | `0x05 0x04` | __Secure__ |__Request:__<br>&nbsp;&nbsp;&nbsp;&nbsp;* Layer: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Encoder: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Clockwise: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* 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:__<br>&nbsp;&nbsp;&nbsp;&nbsp;* enable: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* mode: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* hue: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* sat: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* val: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* 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`(&nbsp;) | `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.

View File

@ -1,4 +1,5 @@
<!-- This file is generated -->
* [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)

View File

@ -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 <pid>:<vid>.')

View File

@ -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):

View File

@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later
from typing import List
class XAPClient:
"""XAP device discovery
"""

View File

@ -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'

View File

@ -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('<BBBBBB')
def __new__(cls, *args):
return super().__new__(cls, *args)
@classmethod
def from_bytes(cls, data):
return cls._make(cls.fmt.unpack(data))
def to_bytes(self):
return self.fmt.pack(*list(self))
# Spec structs
# TODO: gen outbound object for board_identifiers
# TODO: gen inbound object for get_keymap_keycode
# TODO: gen inbound object for get_encoder_keycode
# TODO: gen inbound object for set_keymap_keycode
# TODO: gen inbound object for set_encoder_keycode
# TODO: gen outbound object for get_config
class XAPSecureStatus(IntEnum):
LOCKED = 0x00
UNLOCKING = 0x01

View File

@ -28,6 +28,10 @@ bool get_config_blob_chunk(uint16_t offset, uint8_t *data, uint8_t data_len) {
return true;
}
// TODO: return actual values
#define ENABLED_RGBLIGHT_EFFECTS 0xFFFFFFFF
#define ENABLED_RGB_MATRIX_EFFECTS 0xFFFFFFFF
#define QSTR2(z) #z
#define QSTR(z) QSTR2(z)

View File

@ -181,3 +181,20 @@ bool xap_respond_dynamic_encoder_set_keycode(xap_token_t token, const void *data
return true;
}
#endif
#if ((defined(RGBLIGHT_ENABLE)))
extern rgblight_config_t rgblight_config;
bool xap_respond_get_rgblight_config(xap_token_t token, const void *data, size_t length) {
xap_route_lighting_rgblight_get_config_t ret;
ret.enable = rgblight_config.enable;
ret.mode = rgblight_config.mode;
ret.hue = rgblight_config.hue;
ret.sat = rgblight_config.sat;
ret.val = rgblight_config.val;
ret.speed = rgblight_config.speed;
return xap_respond_data(token, &ret, sizeof(ret));
}
#endif