From 404389f1eaf4d2c99aa63917b090f31f4826c0c0 Mon Sep 17 00:00:00 2001 From: "CSBOOK\\44744" <447449914@qq.com> Date: Thu, 2 Jul 2026 14:02:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E5=8D=95=E5=87=BB?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E5=AF=B9=E8=AF=9D=E5=92=8C=E9=95=BF=E6=8C=89?= =?UTF-8?q?=E6=9C=9F=E9=97=B4=E5=AF=B9=E8=AF=9D=EF=BC=8C=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=9F=AD=E6=8C=89=E5=90=AF=E5=8A=A8=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/runtime/control_plane_key_input.c | 10 ++ .../runtime/control_plane_runtime_workers.c | 20 ++++ .../src/runtime/control_plane_system_events.c | 24 +++++ main/Kconfig.projbuild | 30 ++++++ sdkconfig.defaults | 3 + tools/png_to_rle.py | 94 ++++--------------- 6 files changed, 104 insertions(+), 77 deletions(-) diff --git a/components/control_plane/src/runtime/control_plane_key_input.c b/components/control_plane/src/runtime/control_plane_key_input.c index 09e3f83..aee1f6a 100644 --- a/components/control_plane/src/runtime/control_plane_key_input.c +++ b/components/control_plane/src/runtime/control_plane_key_input.c @@ -52,6 +52,10 @@ #define CONFIG_TQ_KEY_INPUT_POLL_MS 20 #endif +#if !defined(CONFIG_TQ_PTT_MODE_HOLD_TO_TALK) && !defined(CONFIG_TQ_PTT_MODE_CLICK_TO_TALK) +#define CONFIG_TQ_PTT_MODE_CLICK_TO_TALK 1 +#endif + #ifndef CONTROL_PLANE_PTT_INITIAL_INACTIVE_STABLE_MS #define CONTROL_PLANE_PTT_INITIAL_INACTIVE_STABLE_MS 120 #endif @@ -123,9 +127,15 @@ static const control_plane_key_config_t s_key_configs[] = { .long_press_ms = CONFIG_TQ_KEY_LONG_PRESS_MS, .double_click_ms = CONFIG_TQ_KEY_DOUBLE_CLICK_MS, .emit_press_release = true, +#if CONFIG_TQ_PTT_MODE_CLICK_TO_TALK + .enable_short_press = true, + .enable_long_press = false, + .enable_double_click = true, +#else .enable_short_press = false, .enable_long_press = false, .enable_double_click = false, +#endif }, { .key_id = CONTROL_PLANE_KEY_ID_SW2, diff --git a/components/control_plane/src/runtime/control_plane_runtime_workers.c b/components/control_plane/src/runtime/control_plane_runtime_workers.c index 27c5551..fea2122 100644 --- a/components/control_plane/src/runtime/control_plane_runtime_workers.c +++ b/components/control_plane/src/runtime/control_plane_runtime_workers.c @@ -80,6 +80,14 @@ #define CONFIG_TQ_TASK_MONITOR_MAX_TASKS 64 #endif +#if !defined(CONFIG_TQ_PTT_MODE_HOLD_TO_TALK) && !defined(CONFIG_TQ_PTT_MODE_CLICK_TO_TALK) +#define CONFIG_TQ_PTT_MODE_CLICK_TO_TALK 1 +#endif + +#ifndef CONFIG_TQ_PTT_CLICK_CAPTURE_TIMEOUT_MS +#define CONFIG_TQ_PTT_CLICK_CAPTURE_TIMEOUT_MS 8000 +#endif + #ifndef CONFIG_TQ_NETWORK_TASK_CORE #define CONFIG_TQ_NETWORK_TASK_CORE 0 #endif @@ -1189,6 +1197,18 @@ static void control_plane_drive_ptt_state_machine(TickType_t *last_ws_start_atte } TickType_t now = xTaskGetTickCount(); +#if CONFIG_TQ_PTT_MODE_CLICK_TO_TALK + TickType_t capture_timeout_ticks = pdMS_TO_TICKS(CONFIG_TQ_PTT_CLICK_CAPTURE_TIMEOUT_MS); + if (capture_timeout_ticks == 0) { + capture_timeout_ticks = 1; + } + if (ptt.press_tick != 0 && + (now - ptt.press_tick) >= capture_timeout_ticks) { + control_plane_set_ptt_active(false); + return; + } +#endif + TickType_t denied_ticks = pdMS_TO_TICKS(CONTROL_PLANE_PTT_DENIED_TIMEOUT_MS); if (denied_ticks == 0) { denied_ticks = 1; diff --git a/components/control_plane/src/runtime/control_plane_system_events.c b/components/control_plane/src/runtime/control_plane_system_events.c index 773387e..28db707 100644 --- a/components/control_plane/src/runtime/control_plane_system_events.c +++ b/components/control_plane/src/runtime/control_plane_system_events.c @@ -30,6 +30,10 @@ #define CONFIG_TQ_SYSTEM_EVENT_TASK_STACK 4096 #endif +#if !defined(CONFIG_TQ_PTT_MODE_HOLD_TO_TALK) && !defined(CONFIG_TQ_PTT_MODE_CLICK_TO_TALK) +#define CONFIG_TQ_PTT_MODE_CLICK_TO_TALK 1 +#endif + #define CONTROL_PLANE_PTT_HOLD_STEP_MS 80 #ifndef CONFIG_TQ_LOCAL_TASK_CORE @@ -153,12 +157,32 @@ static void control_plane_dispatch_key_event(const control_plane_key_event_t *ev switch (event->type) { case CONTROL_PLANE_KEY_EVENT_PRESSED: { +#if CONFIG_TQ_PTT_MODE_CLICK_TO_TALK + control_plane_multimodal_runtime_state_t state = {0}; + if (control_plane_multimodal_get_runtime_state(&state) == ESP_OK && + state.remote_responding) { + esp_err_t err = control_plane_multimodal_send_request_to_speak(); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGW(TAG, "PTT barge-in request failed: %s", esp_err_to_name(err)); + } + } +#else control_plane_set_ptt_active(true); +#endif break; } case CONTROL_PLANE_KEY_EVENT_RELEASED: { +#if !CONFIG_TQ_PTT_MODE_CLICK_TO_TALK control_plane_set_ptt_active(false); +#endif + break; + } + + case CONTROL_PLANE_KEY_EVENT_SHORT_PRESS: { +#if CONFIG_TQ_PTT_MODE_CLICK_TO_TALK + control_plane_set_ptt_active(true); +#endif break; } diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index fe20833..a0563b3 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -278,6 +278,36 @@ menu "Example Configuration" help Ignore raw GPIO chatter briefly after a double-click has been emitted. + choice TQ_PTT_INTERACTION_MODE + prompt "PTT interaction mode" + default TQ_PTT_MODE_CLICK_TO_TALK + depends on TQ_KEY_INPUT_ENABLE + help + Select how the PTT key controls multimodal speech input. + + config TQ_PTT_MODE_CLICK_TO_TALK + bool "Click-to-talk" + help + A short press opens one bounded speech-input window. Double-click + remains available for the PTT double-click expression and does not + start speech input. + + config TQ_PTT_MODE_HOLD_TO_TALK + bool "Hold-to-talk" + help + Preserve the original behavior: press and hold starts speech input, + release stops speech input. + endchoice + + config TQ_PTT_CLICK_CAPTURE_TIMEOUT_MS + int "PTT click-to-talk capture timeout (ms)" + default 8000 + range 1000 60000 + depends on TQ_KEY_INPUT_ENABLE && TQ_PTT_MODE_CLICK_TO_TALK + help + Maximum microphone capture window started by one PTT short press. + When it expires, uplink capture is stopped and StopSpeech is sent. + endmenu menu "TQ Screen" diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 2eab505..0d95989 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -56,6 +56,9 @@ CONFIG_TQ_KEY_DEBOUNCE_INTERVAL_MS=20 CONFIG_TQ_KEY_LONG_PRESS_MS=800 CONFIG_TQ_KEY_DOUBLE_CLICK_MS=450 CONFIG_TQ_KEY_POST_DOUBLE_CLICK_SUPPRESS_MS=450 +CONFIG_TQ_PTT_MODE_CLICK_TO_TALK=y +# CONFIG_TQ_PTT_MODE_HOLD_TO_TALK is not set +CONFIG_TQ_PTT_CLICK_CAPTURE_TIMEOUT_MS=8000 # Internal DMA-capable SRAM budget. TLS/auth/fortune flows, hardware AES DMA # bounce buffers, LCD DMA, and worker task stacks all need internal-only memory. diff --git a/tools/png_to_rle.py b/tools/png_to_rle.py index cc0a7e1..310467d 100644 --- a/tools/png_to_rle.py +++ b/tools/png_to_rle.py @@ -1,73 +1,9 @@ #!/usr/bin/env python3 -"""Convert PNG images to the binary RGB565 RLE format used by lcd_direct.""" +"""Convert PNG images to standard TGA RLE image files.""" import argparse -import struct from pathlib import Path -try: - from PIL import Image -except ImportError as exc: - raise SystemExit("Pillow is required. Install it with: python -m pip install Pillow") from exc - - -MAX_RUN_COUNT = 0xFFFF - - -def parse_rgb(value): - text = value.strip() - if text.startswith("#"): - text = text[1:] - if len(text) != 6: - raise argparse.ArgumentTypeError("expected RRGGBB or #RRGGBB") - try: - return tuple(int(text[index:index + 2], 16) for index in (0, 2, 4)) - except ValueError as exc: - raise argparse.ArgumentTypeError("background must be hexadecimal RGB") from exc - - -def rgb888_to_rgb565(red, green, blue): - return ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3) - - -def image_to_runs(path, background): - with Image.open(path) as image: - rgba = image.convert("RGBA") - width, height = rgba.size - bg_red, bg_green, bg_blue = background - - runs = [] - current_color = None - current_count = 0 - - for red, green, blue, alpha in rgba.getdata(): - if alpha < 255: - red = ((red * alpha) + (bg_red * (255 - alpha))) // 255 - green = ((green * alpha) + (bg_green * (255 - alpha))) // 255 - blue = ((blue * alpha) + (bg_blue * (255 - alpha))) // 255 - - color = rgb888_to_rgb565(red, green, blue) - if color == current_color and current_count < MAX_RUN_COUNT: - current_count += 1 - continue - - if current_color is not None: - runs.append((current_color, current_count)) - current_color = color - current_count = 1 - - if current_color is not None: - runs.append((current_color, current_count)) - - return width, height, runs - - -def write_rle(path, runs): - path.parent.mkdir(parents=True, exist_ok=True) - with path.open("wb") as output: - for color, count in runs: - output.write(struct.pack(" {out_path} ({width}x{height}, {len(runs)} runs)") + out_path.parent.mkdir(parents=True, exist_ok=True) + with Image.open(input_file) as image: + converted = image.convert("RGBA") + width, height = converted.size + converted.save(out_path, format="TGA", compression="tga_rle") + print(f"{input_file} -> {out_path} ({width}x{height})") def main(): parser = argparse.ArgumentParser( - description="Convert PNG files to lcd_direct RGB565 RLE binary assets.", + description="Convert PNG files to standard TGA RLE files using Pillow.", ) parser.add_argument("input", type=Path, help="PNG file or directory containing PNG files.") parser.add_argument( @@ -115,12 +61,6 @@ def main(): action="store_true", help="Recursively convert PNG files when input is a directory.", ) - parser.add_argument( - "--background", - type=parse_rgb, - default=(0, 0, 0), - help="RGB background for transparent pixels, as RRGGBB or #RRGGBB. Default: 000000.", - ) args = parser.parse_args() input_path = args.input.resolve() @@ -136,7 +76,7 @@ def main(): raise SystemExit("single-file output must use a .rle suffix") for input_file in inputs: - convert_file(input_file, input_path, output, args.background) + convert_file(input_file, input_path, output) if __name__ == "__main__":