Skip to content

Consolidate emote/camera buttons to analog enums, simplify navigation, add binary button_hints with single-byte device_type#14

Draft
Copilot wants to merge 7 commits intomainfrom
copilot/update-button-mapping-and-docs
Draft

Consolidate emote/camera buttons to analog enums, simplify navigation, add binary button_hints with single-byte device_type#14
Copilot wants to merge 7 commits intomainfrom
copilot/update-button-mapping-and-docs

Conversation

Copy link
Contributor

Copilot AI commented Dec 11, 2025

Replaces discrete emote (0x20-0x24) and camera (0x40-0x43) buttons with single analog enum inputs using state values 0-31. Simplifies navigation button descriptions and removes Training Controls section. Adds device_type (single-byte enum) and button_hints fields to App Information (0x04) for flexible button semantics communication using a compact binary format.

Protocol Changes

Button Mapping (PROTOCOL.md)

  • Emote: 0x20 with state values 0-31 (0=none, 1=wave, 2=thumbs up, etc.)
  • Camera: 0x40 with state values 0-31 (0=camera 1, 1=camera 2, etc.)
  • Generic ranges: 0x50-0x5F (digital), 0x60-0x6F (analog) for app-defined actions
  • Navigation simplified:
    • 0x10: Navigate up
    • 0x11: Navigate down
    • 0x12: Navigate left / Steer left
    • 0x13: Navigate right / Steer right
    • 0x17: Removed (Home button)
  • Training Controls removed: Entire section (0x30-0x3F) removed. Apps can use generic ranges for training-specific features.

App Information Format (BLE.md, MDNS.md)

[Message_Type] [Version] [Device_Type] [App_ID_Length] [App_ID...] 
[App_Version_Length] [App_Version...] [Button_Count] [Button_IDs...] 
[Hint_Count] [Hint_1...] [Hint_2...] ...

New fields:

  • device_type (1 byte): Device type enum
    • 0x01 = Controller (physical game controller)
    • 0x02 = App (software application)
  • button_hints: Binary format mapping button IDs to labels

Binary Button Hints Format:
Each hint consists of:

  • Button_ID (1 byte): The button this hint applies to
  • Label_Length (1 byte): Length of label string (0-32 characters)
  • Label (variable): UTF-8 encoded label for the button

Example message:

// App (0x02): "zwift", Version: "1.52.0", Buttons: [0x01, 0x02, 0x10, 0x14]
[0x04, 0x01, 0x02, 0x05, 'z', 'w', 'i', 'f', 't', 0x06, '1', '.', '5', '2', '.', '0', 0x04, 0x01, 0x02, 0x10, 0x14, 0x00]

// Controller (0x01) with 2 button hints
[0x04, 0x01, 0x01, ..., 0x02, 0x20, 0x05, 'E', 'm', 'o', 't', 'e', 0x40, 0x06, 'C', 'a', 'm', 'e', 'r', 'a']

Size Efficiency:

  • Binary button_hints: ~16 bytes for 2 hints (70% reduction vs JSON)
  • Single-byte device_type: 3 bytes saved vs variable-length string

Implementation

device.json

  • Replaced discrete buttons with analog enum entries for 0x20/0x40
  • Added button_hints structure for all buttons

protocol_parser.py

  • Updated BUTTON_NAMES mappings
  • Simplified navigation button names
  • Removed Training Controls entries (0x30-0x35)
  • Removed Home button (0x17)
  • Enhanced format_button_state() to recognize enum semantics for 0x20/0x40
  • Extended encode_app_info()/parse_app_info() with single-byte device_type and binary button_hints

mock_device.py*

  • Emit analog enum values: encode_button_state([(0x20, 1)]) for "Wave"

Example Usage

# App sends button hints on connect
hints = {0x20: "Emote", 0x40: "Camera"}
app_info = encode_app_info("zwift", "1.52.0", device_type="app", button_hints=hints)

# Device sends enum value
emote_wave = encode_button_state([(0x20, 1)])  # Not press/release, but enum ID

Migration

Apps should provide button_hints to communicate button labels. During transition, devices may accept legacy discrete button IDs (0x21-0x24, 0x41-0x43).

Original prompt

— Keep Gear Shifting (0x01–0x03) and Navigation (0x10–0x17) as-is.

  • Replace multiple emote buttons with a single analog Emote input (0x20)
  • consolidate Camera 1–3 into one analog Camera View input (0x40). Use a small enum (0–31) for both analog values.

Steps

  1. Edit PROTOCOL.md Button Mapping to:
  • Keep Gear Shifting (0x01/0x02/0x03) and Navigation (0x10–0x17) unchanged.
  • Replace Social/Emotes section with 0x20 = Emote (analog enum 0–31) and document encoding (0..N = emote id).
  • Replace Camera controls with 0x40 = Switch Camera View (analog enum 0–31) and document encoding (0 = camera 1, 2 = camera 1, 2 = camera 3, etc.).
  • Define generic ranges for app-driven mapping: digital 0x50-0x5F, analog 0x60-0x6F.
  1. Add fields to the App Information part of BLE.md and MDNS.md:
  • add device_type ("remote"|"controller"|"app")
  • add optional button_hints (map button_id -> { role_hint, label }).
  1. Update transport docs cross-references:
  • Update BLE.md and MDNS.md to mention Emote (0x20) and Camera (0x40) analog enums and how to read button_hints from App Info characteristic.
  1. Edit sample device file:
  • Update devices/example-corp/sc-100/device.json: remove multiple emote/camera entries, add analog entries 0x20 (Emote) and 0x40 (Camera), and include button_hints for existing buttons.
  1. Update examples and parser:
  • Modify examples/python/mock_device*.py and examples/python/protocol_parser.py to emit/parse 0x20 and 0x40 using small-enum semantics; add comments showing app-side mapping and legacy acceptance.

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 3 commits December 11, 2025 11:15
…hints

- Replace multiple emote buttons (0x20-0x24) with single analog Emote (0x20)
- Replace multiple camera buttons (0x40-0x43) with single analog Camera View (0x40)
- Define generic ranges: digital 0x50-0x5F, analog 0x60-0x6F for app-driven mapping
- Add device_type and button_hints to App Information in BLE.md and MDNS.md
- Update device.json with analog entries and button_hints
- Update protocol_parser.py with new button mappings and enum semantics
- Update mock_device*.py files to emit analog enum values for 0x20 and 0x40
- Update tests to reflect new button structure

Co-authored-by: jonasbark <1151304+jonasbark@users.noreply.github.com>
Ensure that state values 0 and 1 are treated as enum values (not RELEASED/PRESSED)
for analog enum buttons like Emote (0x20) and Camera View (0x40).

Co-authored-by: jonasbark <1151304+jonasbark@users.noreply.github.com>
- Move json import to top of protocol_parser.py
- Add comments to device.json clarifying decimal vs hex button_id notation
- Add note about hardcoded enum mappings being defaults for debugging/logging

Co-authored-by: jonasbark <1151304+jonasbark@users.noreply.github.com>
Copilot AI changed the title [WIP] Update button mapping in PROTOCOL.md and BLE.md Consolidate emote/camera buttons to analog enums, add button_hints for app-driven mapping Dec 11, 2025
Copilot AI requested a review from jonasbark December 11, 2025 11:26
@jonasbark
Copy link
Collaborator

@copilot BLE.md:193 the protocol should be binary - do not integrate JSON. Otherwise the payload would be too large for BLE transmission. Same applies to the same part in MDNS.md

- Remove JSON encoding for button_hints to reduce payload size
- Implement compact binary format: [Hint_Count] [Button_ID] [Label_Length] [Label]...
- Update BLE.md and MDNS.md with binary format specification
- Update protocol_parser.py encode/parse functions for binary hints
- Remove json import (no longer needed)
- Update tests to use new button_id -> label format
- Add comment to device.json clarifying wire protocol vs config format

Binary format provides ~70% size reduction vs JSON, making it suitable for BLE MTU constraints.

Co-authored-by: jonasbark <1151304+jonasbark@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 11, 2025

@copilot BLE.md:193 the protocol should be binary - do not integrate JSON. Otherwise the payload would be too large for BLE transmission. Same applies to the same part in MDNS.md

Changed to binary format in commit ed847db. Replaced JSON with compact binary structure: [Hint_Count] [Button_ID] [Label_Length] [Label]... which provides ~70% size reduction and is BLE-friendly.

Copilot AI changed the title Consolidate emote/camera buttons to analog enums, add button_hints for app-driven mapping Consolidate emote/camera buttons to analog enums, add binary button_hints for app-driven mapping Dec 11, 2025
@jonasbark
Copy link
Collaborator

@copilot adjust the following buttons:

  • 0x10: Navigate up
  • 0x11: Navigate down
  • 0x12: Navigate left / Steer left
  • 0x13: Navigate right / Steer right
  • remove 0x17
  • Remove the "Training Controls" section

…ction

- 0x10: "Navigate up" (was "Navigate up / Steer left")
- 0x11: "Navigate down" (was "Navigate down / Steer right")
- 0x12: "Navigate left / Steer left" (was "Navigate left / Look left")
- 0x13: "Navigate right / Steer right" (was "Navigate right / Look right")
- Remove 0x17 (Home button)
- Remove Training Controls section (0x30-0x35: ERG Up/Down, Skip Interval, Pause, Resume, Lap)
- Update protocol_parser.py BUTTON_NAMES accordingly
- Update tests to reflect removed buttons

Co-authored-by: jonasbark <1151304+jonasbark@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 11, 2025

@copilot adjust the following buttons:

  • 0x10: Navigate up
  • 0x11: Navigate down...

Updated in commit 041f387. Navigation buttons simplified (0x10-0x13), removed 0x17 (Home), and removed Training Controls section (0x30-0x35). All tests pass.

Copilot AI changed the title Consolidate emote/camera buttons to analog enums, add binary button_hints for app-driven mapping Consolidate emote/camera buttons to analog enums, simplify navigation, add binary button_hints Dec 11, 2025
@jonasbark jonasbark marked this pull request as ready for review December 11, 2025 12:05
Copilot AI review requested due to automatic review settings December 11, 2025 12:05
MDNS.md Outdated
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot use a single byte instead of device_type_length + device_type, called "device_type" with values 0x01 = controller, 0x02 = app

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to single-byte enum in commit 1e1e16e. Device_type now uses: 0x01 = controller, 0x02 = app. Saves 3 bytes per message.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request consolidates the button mapping protocol by replacing discrete emote and camera buttons with single analog enum inputs, simplifying navigation button descriptions, and adding a binary button_hints format to the App Information message for flexible button semantics communication.

Key changes:

  • Replaced discrete emote buttons (0x21-0x24) with single analog enum Emote button (0x20) using state values 0-31
  • Replaced discrete camera buttons (0x41-0x43) with single analog enum Camera View button (0x40) using state values 0-31
  • Removed Training Controls section (0x30-0x3F) and Home button (0x17) from the protocol
  • Added device_type and button_hints fields to App Information (0x04) using compact binary format for 70% size reduction

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
PROTOCOL.md Updated button mapping tables to consolidate emote/camera buttons into analog enums, simplified navigation descriptions, removed Training Controls section, and documented generic button ranges
BLE.md Extended App Information characteristic format with device_type field and binary button_hints encoding specification
MDNS.md Extended App Information message format with device_type field and binary button_hints encoding specification (matching BLE.md)
examples/python/protocol_parser.py Updated BUTTON_NAMES mappings, added enum formatting logic for 0x20/0x40, and implemented binary button_hints encoding/decoding in app info functions
examples/python/test_examples.py Updated tests to verify new button mappings, check removed buttons are absent, and validate device_type and button_hints encoding/decoding
examples/python/mock_device.py Modified button simulation to emit analog enum values for emote/camera buttons instead of press/release sequences
examples/python/mock_device_tcp.py Modified button simulation to emit analog enum values for emote/camera buttons instead of press/release sequences
examples/python/mock_device_ble.py Modified button simulation to emit analog enum values for emote/camera buttons instead of press/release sequences
devices/example-corp/sc-100/device.json Replaced discrete emote/camera button entries with analog enum entries and added comprehensive button_hints structure with enum value mappings
Comments suppressed due to low confidence (1)

examples/python/test_examples.py:89

  • The test function test_format_button_state does not include test cases for the new enum button handling for buttons 0x20 (Emote) and 0x40 (Camera View). These buttons have special enum semantics that should be tested to ensure the format_button_state function correctly displays enum values like "Wave", "Thumbs Up", "Camera 1", etc. Consider adding test cases such as:
  • format_button_state(0x20, 1) should return "Wave"
  • format_button_state(0x20, 2) should return "Thumbs Up"
  • format_button_state(0x40, 0) should return "Camera 1"
  • format_button_state(0x40, 1) should return "Camera 2"
def test_format_button_state():
    """Test button state formatting."""
    print("Testing format_button_state...")
    
    # Known button pressed
    result = format_button_state(0x01, 1)
    assert "Shift Up" in result and "PRESSED" in result, f"Unexpected format: {result}"
    
    # Known button released
    result = format_button_state(0x01, 0)
    assert "Shift Up" in result and "RELEASED" in result, f"Unexpected format: {result}"
    
    # Unknown button
    result = format_button_state(0xFF, 1)
    assert "0xFF" in result and "PRESSED" in result, f"Unexpected format: {result}"
    
    # Analog value
    result = format_button_state(0x10, 0x80)
    assert "Up" in result and "ANALOG" in result, f"Unexpected format: {result}"
    
    # Analog min (2)
    result = format_button_state(0x10, 2)
    assert "ANALOG" in result and "0%" in result, f"Unexpected format: {result}"
    
    # Analog max (255)
    result = format_button_state(0x10, 255)
    assert "ANALOG" in result and "100%" in result, f"Unexpected format: {result}"
    
    print("  ✓ All format_button_state tests passed")

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

idx += label_len

button_hints[button_id] = label

Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parse_app_info function silently breaks out of the loop when parsing button hints encounters malformed data (lines 386-401). While this provides robustness against corrupted messages, it can lead to partial data loss without notification. For example, if hint_count is 5 but only 3 complete hints are present in the buffer, the function will return only 3 hints without raising an error or logging a warning. This silent failure could make debugging difficult. Consider either:

  1. Raising a ValueError when hint data is incomplete, OR
  2. Adding a comment explaining this is intentional for robustness and partial data is acceptable
Suggested change
# Raise an error if not all hints were parsed (data incomplete)
if 'hint_count' in locals() and len(button_hints) < hint_count:
raise ValueError(f"Incomplete button_hints: expected {hint_count}, got {len(button_hints)}")

Copilot uses AI. Check for mistakes.
"device_type": "controller",
"app_id": "myapp",
"app_version": "2.1.3",
"supported_buttons": [0x01, 0x20, 0x30],
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test uses button ID 0x30 in the supported_buttons list, which has been removed from the protocol (Training Controls section was removed). While this doesn't break functionality since the encode/decode functions should handle any button ID, using a removed/deprecated button ID in tests is confusing and could mislead developers. Consider using a valid button ID like 0x50 (Power-up 1) or 0x14 (Select/Confirm) instead to better reflect the current protocol.

Suggested change
"supported_buttons": [0x01, 0x20, 0x30],
"supported_buttons": [0x01, 0x20, 0x50],

Copilot uses AI. Check for mistakes.
Comment on lines +139 to +146
emote_map = {0: "None", 1: "Wave", 2: "Thumbs Up", 3: "Hammer Time", 4: "Bell"}
emote_name = emote_map.get(state, f"Emote {state}")
state_str = f"ENUM: {emote_name}"
elif button_id == 0x40: # Camera View (analog enum 0-31)
# Map common camera values
camera_map = {0: "Camera 1", 1: "Camera 2", 2: "Camera 3"}
camera_name = camera_map.get(state, f"Camera View {state}")
state_str = f"ENUM: {camera_name}"
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The enum handling for buttons 0x20 and 0x40 doesn't check if the state value is within the valid range (0-31) as documented in PROTOCOL.md. Values >= 32 will be formatted as "Emote {state}" or "Camera View {state}", but according to the protocol specification these buttons should only use state values 0-31. Consider adding a check or comment to clarify expected behavior when state values exceed 31, or add validation to warn about out-of-range values.

Suggested change
emote_map = {0: "None", 1: "Wave", 2: "Thumbs Up", 3: "Hammer Time", 4: "Bell"}
emote_name = emote_map.get(state, f"Emote {state}")
state_str = f"ENUM: {emote_name}"
elif button_id == 0x40: # Camera View (analog enum 0-31)
# Map common camera values
camera_map = {0: "Camera 1", 1: "Camera 2", 2: "Camera 3"}
camera_name = camera_map.get(state, f"Camera View {state}")
state_str = f"ENUM: {camera_name}"
if 0 <= state <= 31:
emote_map = {0: "None", 1: "Wave", 2: "Thumbs Up", 3: "Hammer Time", 4: "Bell"}
emote_name = emote_map.get(state, f"Emote {state}")
state_str = f"ENUM: {emote_name}"
else:
state_str = f"INVALID ENUM (out of range 0-31): {state}"
elif button_id == 0x40: # Camera View (analog enum 0-31)
# Map common camera values
if 0 <= state <= 31:
camera_map = {0: "Camera 1", 1: "Camera 2", 2: "Camera 3"}
camera_name = camera_map.get(state, f"Camera View {state}")
state_str = f"ENUM: {camera_name}"
else:
state_str = f"INVALID ENUM (out of range 0-31): {state}"

Copilot uses AI. Check for mistakes.
- Use single byte for device_type instead of length + string
- 0x01 = controller (physical game controller)
- 0x02 = app (software application)
- Update BLE.md and MDNS.md specifications
- Update protocol_parser.py encode/decode functions
- Update tests to verify single-byte format
- 3 bytes saved per message (more BLE-friendly)

Co-authored-by: jonasbark <1151304+jonasbark@users.noreply.github.com>
Copilot AI changed the title Consolidate emote/camera buttons to analog enums, simplify navigation, add binary button_hints Consolidate emote/camera buttons to analog enums, simplify navigation, add binary button_hints with single-byte device_type Dec 11, 2025
@jonasbark jonasbark mentioned this pull request Dec 11, 2025
@jonasbark jonasbark marked this pull request as draft December 13, 2025 09:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments