From 5fd1b3a11f4ef21a3670afd28a9d5cd5a7fa0a41 Mon Sep 17 00:00:00 2001 From: portersky Date: Mon, 17 Aug 2026 20:53:40 +0200 Subject: [PATCH] feat: pairing mode and RX frame logging The dongle beacon carries a pairing flag in the Microsoft OUI IE; with it clear, controllers ignore the beacon. Port xone_dongle_toggle_pairing: xone_set_pairing() rewrites the beacon with the flag set and blinks the LED, the GUI button triggers it, EVT_BUTTON (dongle button) does too, and the LED follows upstream state (blink while pairing, on when a controller is connected, off otherwise). The CLI gains a pair command. Log every inbound frame (info word + first 16 bytes) so the RX path can be traced against hardware. Co-Authored-By: qwen3.8-27b@q2_k_xl: pairing mode and RX logging --- CMakeLists.txt | 2 +- include/app/xone_api.h | 3 ++ src/app/api.cpp | 70 +++++++++++++++++++++++++++++++++++++----- src/app/main.swift | 6 ++-- src/cli/main.cpp | 8 +++++ 5 files changed, 78 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e97717b..296ddd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if(NOT CMAKE_GENERATOR MATCHES "^(Ninja|Xcode)$") endif() cmake_minimum_required(VERSION 3.21) -project(xone_macos VERSION 0.1.10 LANGUAGES CXX Swift) +project(xone_macos VERSION 0.1.11 LANGUAGES CXX Swift) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) diff --git a/include/app/xone_api.h b/include/app/xone_api.h index 7ad3dd9..4e43736 100644 --- a/include/app/xone_api.h +++ b/include/app/xone_api.h @@ -72,6 +72,9 @@ int xone_controller_count(xone_dongle const *d); // success, -1 if index is out of range. int xone_controller_mac(xone_dongle const *d, int index, char *buf, int len); +// Enter or leave pairing mode (beacon pairing flag + LED blink). +void xone_set_pairing(xone_dongle *d, bool enable); + #ifdef __cplusplus } #endif diff --git a/src/app/api.cpp b/src/app/api.cpp index 8e88466..e7dc256 100644 --- a/src/app/api.cpp +++ b/src/app/api.cpp @@ -3,6 +3,7 @@ #include "app/xone_api.h" #include +#include #include #include #include @@ -46,6 +47,10 @@ struct xone_dongle { // Connected controllers (wcid 1..16). Guarded by lock. std::vector controllers; + // Pairing mode state (port of dongle->pairing). Shared between the UI + // thread and the transport reader thread. + std::atomic pairing{false}; + ~xone_dongle() { // Stop the reader thread before the chip is torn down so a late IN @@ -66,6 +71,9 @@ struct xone_dongle { auto handle_client_command(std::span payload, std::uint8_t wcid, std::span addr) -> void; + + // Enter or leave pairing mode (port of xone_dongle_toggle_pairing). + auto set_pairing(bool enable) -> void; }; namespace { @@ -109,6 +117,14 @@ auto xone_dongle::process_message(void const *data, std::size_t len) -> void // Strip the header + trailer. buf = buf.subspan(cmd_hdr_len, len - 2 * cmd_hdr_len); + char hex[64] = ""; + int off = 0; + for (std::size_t i = 0; i < buf.size() && i < 16 && off < 48; ++i) + off += std::snprintf(hex + off, sizeof(hex) - off, "%02x ", buf[i]); + xone::log_msg(xone::log_level::debug, + "api: rx info=0x%08x port=%d len=%zu %s", + (unsigned)info, (unsigned)port, buf.size(), hex); + if (port == dma_msg_port::wlan_port) { process_wlan(buf); return; @@ -119,7 +135,9 @@ auto xone_dongle::process_message(void const *data, std::size_t len) -> void switch (field_get(mt_rx_fce_info_evt_type, info)) { case cpu_evt::evt_button: - // Pairing mode is entered via the CLI for now. + // The dongle pairing button was pressed (port of + // xone_dongle_handle_button). + set_pairing(true); break; case cpu_evt::evt_packet_rx: process_wlan(buf); @@ -166,6 +184,25 @@ auto xone_dongle::process_wlan(std::span buf) -> void // DATA|QOS_DATA (GIP) is handled in a later increment. } +auto xone_dongle::set_pairing(bool enable) -> void +{ + if (!chip) + return; + pairing = enable; + chip->set_pairing(enable); + bool has_clients = false; + { + std::lock_guard guard(lock); + has_clients = !controllers.empty(); + } + auto led = enable ? xone::mt76::led_mode::led_blink + : (has_clients ? xone::mt76::led_mode::led_on + : xone::mt76::led_mode::led_off); + chip->set_led_mode(led); + xone::log_msg(xone::log_level::info, "api: pairing mode %s", + enable ? "enabled" : "disabled"); +} + auto xone_dongle::handle_association(std::span addr) -> void { // Find a free WCID slot (1..16). @@ -192,8 +229,12 @@ auto xone_dongle::handle_association(std::span addr) -> void return; } - std::lock_guard guard(lock); - controllers.push_back(controller{wcid, mac}); + { + std::lock_guard guard(lock); + controllers.push_back(controller{wcid, mac}); + } + if (!pairing) + chip->set_led_mode(xone::mt76::led_mode::led_on); xone::log_msg(xone::log_level::info, "api: controller associated (wcid=%d)", wcid); } @@ -206,13 +247,19 @@ auto xone_dongle::handle_disassociation(std::uint8_t wcid) -> void // Remove from the chip outside the lock. chip->remove_client(wcid); - std::lock_guard guard(lock); - for (auto it = controllers.begin(); it != controllers.end(); ++it) { - if (it->wcid == wcid) { - controllers.erase(it); - break; + bool last = false; + { + std::lock_guard guard(lock); + for (auto it = controllers.begin(); it != controllers.end(); ++it) { + if (it->wcid == wcid) { + controllers.erase(it); + break; + } } + last = controllers.empty(); } + if (last && !pairing) + chip->set_led_mode(xone::mt76::led_mode::led_off); xone::log_msg(xone::log_level::info, "api: controller removed (wcid=%d)", wcid); } @@ -400,3 +447,10 @@ extern "C" int xone_controller_mac(xone_dongle const *d, int index, char *buf, c.mac[0], c.mac[1], c.mac[2], c.mac[3], c.mac[4], c.mac[5]); return 0; } + +// Enter or leave pairing mode: sets the beacon pairing flag and blinks the +// LED while enabled (port of xone_dongle_toggle_pairing). +extern "C" void xone_set_pairing(xone_dongle *d, bool enable) +{ + d->set_pairing(enable); +} diff --git a/src/app/main.swift b/src/app/main.swift index 94579b4..25c5956 100644 --- a/src/app/main.swift +++ b/src/app/main.swift @@ -58,9 +58,11 @@ struct ContentView: View { HStack { Button("Pair Controller") { - // TODO(phase 5): pairing UI (LED blink + controller press) + if let s = session { + xone_set_pairing(s, true) + } } - .disabled(!donglePresent) + .disabled(radioState != XONE_STATE_READY) Spacer() diff --git a/src/cli/main.cpp b/src/cli/main.cpp index 523bcd3..ac3141d 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -25,6 +25,7 @@ auto usage() -> int " info Show dongle and chip state\n" " firmware Load the firmware image\n" " radio-init Initialize the radio\n" + " pair Enter pairing mode (beacon flag + LED blink)\n" " led Set LED mode (0 blink, 1 on, 2 off)\n" " func MCU function select (debug)\n" " radio-deinit Suspend the radio\n" @@ -68,6 +69,13 @@ auto run(int argc, char **argv, xone::usb::transport &transport, int ret = chip.init_radio(); if (ret != 0) std::fprintf(stderr, "xone_cli: radio init failed (%d)\n", ret); + } else if (!std::strcmp(cmd, "pair")) { + int ret = chip.set_pairing(true); + if (ret != 0) { + std::fprintf(stderr, "xone_cli: pair failed (%d)\n", ret); + return 1; + } + chip.set_led_mode(xone::mt76::led_mode::led_blink); } else if (!std::strcmp(cmd, "func")) { if (i + 2 > argc) return usage();