diff options
| -rw-r--r-- | data/mappings/info_rules.json | 1 | ||||
| -rw-r--r-- | data/schemas/keyboard.jsonschema | 35 | ||||
| -rw-r--r-- | docs/reference_info_json.md | 36 | ||||
| -rwxr-xr-x | lib/python/qmk/cli/generate/config_h.py | 29 | ||||
| -rw-r--r-- | lib/python/qmk/info.py | 58 | 
5 files changed, 159 insertions, 0 deletions
| diff --git a/data/mappings/info_rules.json b/data/mappings/info_rules.json index d4eec37ba0..5cdae962a5 100644 --- a/data/mappings/info_rules.json +++ b/data/mappings/info_rules.json @@ -13,6 +13,7 @@      "BOOTLOADER": {"info_key": "bootloader", "warn_duplicate": false},      "BLUETOOTH": {"info_key": "bluetooth.driver"},      "CAPS_WORD_ENABLE": {"info_key": "caps_word.enabled", "value_type": "bool"}, +    "ENCODER_ENABLE": {"info_key": "encoder.enabled", "value_type": "bool"},      "FIRMWARE_FORMAT": {"info_key": "build.firmware_format"},      "KEYBOARD_SHARED_EP": {"info_key": "usb.shared_endpoint.keyboard", "value_type": "bool"},      "MOUSE_SHARED_EP": {"info_key": "usb.shared_endpoint.mouse", "value_type": "bool"}, diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema index 52a66cc132..8d9fc4754d 100644 --- a/data/schemas/keyboard.jsonschema +++ b/data/schemas/keyboard.jsonschema @@ -2,6 +2,26 @@      "$schema": "https://json-schema.org/draft/2020-12/schema#",      "$id": "qmk.keyboard.v1",      "title": "Keyboard Information", +    "definitions": { +        "encoder_config": { +            "type": "object", +            "properties": { +                "rotary": { +                    "type": "array", +                    "items": { +                        "type": "object", +                        "additionalProperties": false, +                        "required": ["pin_a", "pin_b"], +                        "properties": { +                            "pin_a": {"$ref": "qmk.definitions.v1#/mcu_pin"}, +                            "pin_b": {"$ref": "qmk.definitions.v1#/mcu_pin"}, +                            "resolution": {"$ref": "qmk.definitions.v1#/unsigned_int"}  +                        } +                    } +                } +            } +        } +    },      "type": "object",      "properties": {          "keyboard_name": {"$ref": "qmk.definitions.v1#/text_identifier"}, @@ -113,6 +133,12 @@              "type": "array",              "items": {"$ref": "qmk.definitions.v1#/filename"}          }, +        "encoder": { +            "$ref": "#/definitions/encoder_config", +            "properties": { +                "enabled": {"type": "boolean"} +            } +        },          "features": {"$ref": "qmk.definitions.v1#/boolean_array"},          "indicators": {              "type": "object", @@ -363,6 +389,15 @@                          }                      }                  }, +                "encoder": { +                    "type": "object", +                    "additionalProperties": false, +                    "properties": { +                        "right": { +                            "$ref": "#/definitions/encoder_config" +                        } +                    } +                },                  "main": {                      "type": "string",                      "enum": ["eeprom", "left", "matrix_grid", "pin", "right"] diff --git a/docs/reference_info_json.md b/docs/reference_info_json.md index 90b28689d0..d2e9346ee3 100644 --- a/docs/reference_info_json.md +++ b/docs/reference_info_json.md @@ -187,3 +187,39 @@ Example:  ```  The device version is a BCD (binary coded decimal) value, in the format `MMmr`, so the below value would look like `0x0100` in the generated code. This also means the maximum valid values for each part are `99.9.9`, despite it being a hexadecimal value under the hood. + +### Encoders + +This section controls the basic [rotary encoder](feature_encoders.md) support. + +The following items can be set. Not every value is required. + +* `pin_a` +  * __Required__. A pad definition +* `pin_b` +  * __Required__. B pad definition +* `resolution` +  * How many pulses the encoder registers between each detent + +Examples: + +```json +{ +    "encoder": { +        "rotary": [ +            { "pin_a": "B5", "pin_b": "A2" } +        ] +    } +} +``` + +```json +{ +    "encoder": { +        "rotary": [ +            { "pin_a": "B5", "pin_b": "A2", "resolution": 4 } +            { "pin_a": "B6", "pin_b": "A3", "resolution": 2 } +        ] +    } +} +``` diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index 893892c479..9d50368aba 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -134,6 +134,29 @@ def generate_config_items(kb_info_json, config_h_lines):              config_h_lines.append(f'#endif // {config_key}') +def generate_encoder_config(encoder_json, config_h_lines, postfix=''): +    """Generate the config.h lines for encoders.""" +    a_pads = [] +    b_pads = [] +    resolutions = [] +    for encoder in encoder_json.get("rotary", []): +        a_pads.append(encoder["pin_a"]) +        b_pads.append(encoder["pin_b"]) +        resolutions.append(str(encoder.get("resolution", 4))) + +    config_h_lines.append(f'#ifndef ENCODERS_PAD_A{postfix}') +    config_h_lines.append(f'#   define ENCODERS_PAD_A{postfix} {{ { ", ".join(a_pads) } }}') +    config_h_lines.append(f'#endif // ENCODERS_PAD_A{postfix}') + +    config_h_lines.append(f'#ifndef ENCODERS_PAD_B{postfix}') +    config_h_lines.append(f'#   define ENCODERS_PAD_B{postfix} {{ { ", ".join(b_pads) } }}') +    config_h_lines.append(f'#endif // ENCODERS_PAD_B{postfix}') + +    config_h_lines.append(f'#ifndef ENCODER_RESOLUTIONS{postfix}') +    config_h_lines.append(f'#   define ENCODER_RESOLUTIONS{postfix} {{ { ", ".join(resolutions) } }}') +    config_h_lines.append(f'#endif // ENCODER_RESOLUTIONS{postfix}') + +  def generate_split_config(kb_info_json, config_h_lines):      """Generate the config.h lines for split boards."""      if 'primary' in kb_info_json['split']: @@ -173,6 +196,9 @@ def generate_split_config(kb_info_json, config_h_lines):      if 'right' in kb_info_json['split'].get('matrix_pins', {}):          config_h_lines.append(matrix_pins(kb_info_json['split']['matrix_pins']['right'], '_RIGHT')) +    if 'right' in kb_info_json['split'].get('encoder', {}): +        generate_encoder_config(kb_info_json['split']['encoder']['right'], config_h_lines, '_RIGHT') +  @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')  @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") @@ -198,6 +224,9 @@ def generate_config_h(cli):      if 'matrix_pins' in kb_info_json:          config_h_lines.append(matrix_pins(kb_info_json['matrix_pins'])) +    if 'encoder' in kb_info_json: +        generate_encoder_config(kb_info_json['encoder'], config_h_lines) +      if 'split' in kb_info_json:          generate_split_config(kb_info_json, config_h_lines) diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 23761d71b7..d308de9db8 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -218,6 +218,62 @@ def _extract_audio(info_data, config_c):          info_data['audio'] = {'pins': audio_pins} +def _extract_encoders_values(config_c, postfix=''): +    """Common encoder extraction logic +    """ +    a_pad = config_c.get(f'ENCODERS_PAD_A{postfix}', '').replace(' ', '')[1:-1] +    b_pad = config_c.get(f'ENCODERS_PAD_B{postfix}', '').replace(' ', '')[1:-1] +    resolutions = config_c.get(f'ENCODER_RESOLUTIONS{postfix}', '').replace(' ', '')[1:-1] + +    default_resolution = config_c.get('ENCODER_RESOLUTION', '4') + +    if a_pad and b_pad: +        a_pad = list(filter(None, a_pad.split(','))) +        b_pad = list(filter(None, b_pad.split(','))) +        resolutions = list(filter(None, resolutions.split(','))) +        resolutions += [default_resolution] * (len(a_pad) - len(resolutions)) + +        encoders = [] +        for index in range(len(a_pad)): +            encoders.append({'pin_a': a_pad[index], 'pin_b': b_pad[index], "resolution": int(resolutions[index])}) + +        return encoders + + +def _extract_encoders(info_data, config_c): +    """Populate data about encoder pins +    """ +    encoders = _extract_encoders_values(config_c) +    if encoders: +        if 'encoder' not in info_data: +            info_data['encoder'] = {} + +        if 'rotary' in info_data['encoder']: +            _log_warning(info_data, 'Encoder config is specified in both config.h and info.json (encoder.rotary) (Value: %s), the config.h value wins.' % info_data['encoder']['rotary']) + +        info_data['encoder']['rotary'] = encoders + + +def _extract_split_encoders(info_data, config_c): +    """Populate data about split encoder pins +    """ +    encoders = _extract_encoders_values(config_c, '_RIGHT') +    if encoders: +        if 'split' not in info_data: +            info_data['split'] = {} + +        if 'encoder' not in info_data['split']: +            info_data['split']['encoder'] = {} + +        if 'right' not in info_data['split']['encoder']: +            info_data['split']['encoder']['right'] = {} + +        if 'rotary' in info_data['split']['encoder']['right']: +            _log_warning(info_data, 'Encoder config is specified in both config.h and info.json (encoder.rotary) (Value: %s), the config.h value wins.' % info_data['split']['encoder']['right']['rotary']) + +        info_data['split']['encoder']['right']['rotary'] = encoders + +  def _extract_secure_unlock(info_data, config_c):      """Populate data about the secure unlock sequence      """ @@ -506,6 +562,8 @@ def _extract_config_h(info_data, config_c):      _extract_split_main(info_data, config_c)      _extract_split_transport(info_data, config_c)      _extract_split_right_pins(info_data, config_c) +    _extract_encoders(info_data, config_c) +    _extract_split_encoders(info_data, config_c)      _extract_device_version(info_data)      return info_data | 
