增加了单击启动对话和长按期间对话,默认使用短按启动对话

This commit is contained in:
CSBOOK\44744
2026-07-02 14:02:51 +08:00
parent bc74db61a9
commit 404389f1ea
6 changed files with 104 additions and 77 deletions
@@ -52,6 +52,10 @@
#define CONFIG_TQ_KEY_INPUT_POLL_MS 20 #define CONFIG_TQ_KEY_INPUT_POLL_MS 20
#endif #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 #ifndef CONTROL_PLANE_PTT_INITIAL_INACTIVE_STABLE_MS
#define CONTROL_PLANE_PTT_INITIAL_INACTIVE_STABLE_MS 120 #define CONTROL_PLANE_PTT_INITIAL_INACTIVE_STABLE_MS 120
#endif #endif
@@ -123,9 +127,15 @@ static const control_plane_key_config_t s_key_configs[] = {
.long_press_ms = CONFIG_TQ_KEY_LONG_PRESS_MS, .long_press_ms = CONFIG_TQ_KEY_LONG_PRESS_MS,
.double_click_ms = CONFIG_TQ_KEY_DOUBLE_CLICK_MS, .double_click_ms = CONFIG_TQ_KEY_DOUBLE_CLICK_MS,
.emit_press_release = true, .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_short_press = false,
.enable_long_press = false, .enable_long_press = false,
.enable_double_click = false, .enable_double_click = false,
#endif
}, },
{ {
.key_id = CONTROL_PLANE_KEY_ID_SW2, .key_id = CONTROL_PLANE_KEY_ID_SW2,
@@ -80,6 +80,14 @@
#define CONFIG_TQ_TASK_MONITOR_MAX_TASKS 64 #define CONFIG_TQ_TASK_MONITOR_MAX_TASKS 64
#endif #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 #ifndef CONFIG_TQ_NETWORK_TASK_CORE
#define CONFIG_TQ_NETWORK_TASK_CORE 0 #define CONFIG_TQ_NETWORK_TASK_CORE 0
#endif #endif
@@ -1189,6 +1197,18 @@ static void control_plane_drive_ptt_state_machine(TickType_t *last_ws_start_atte
} }
TickType_t now = xTaskGetTickCount(); 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); TickType_t denied_ticks = pdMS_TO_TICKS(CONTROL_PLANE_PTT_DENIED_TIMEOUT_MS);
if (denied_ticks == 0) { if (denied_ticks == 0) {
denied_ticks = 1; denied_ticks = 1;
@@ -30,6 +30,10 @@
#define CONFIG_TQ_SYSTEM_EVENT_TASK_STACK 4096 #define CONFIG_TQ_SYSTEM_EVENT_TASK_STACK 4096
#endif #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 #define CONTROL_PLANE_PTT_HOLD_STEP_MS 80
#ifndef CONFIG_TQ_LOCAL_TASK_CORE #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) { switch (event->type) {
case CONTROL_PLANE_KEY_EVENT_PRESSED: { 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); control_plane_set_ptt_active(true);
#endif
break; break;
} }
case CONTROL_PLANE_KEY_EVENT_RELEASED: { case CONTROL_PLANE_KEY_EVENT_RELEASED: {
#if !CONFIG_TQ_PTT_MODE_CLICK_TO_TALK
control_plane_set_ptt_active(false); 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; break;
} }
+30
View File
@@ -278,6 +278,36 @@ menu "Example Configuration"
help help
Ignore raw GPIO chatter briefly after a double-click has been emitted. 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 endmenu
menu "TQ Screen" menu "TQ Screen"
+3
View File
@@ -56,6 +56,9 @@ CONFIG_TQ_KEY_DEBOUNCE_INTERVAL_MS=20
CONFIG_TQ_KEY_LONG_PRESS_MS=800 CONFIG_TQ_KEY_LONG_PRESS_MS=800
CONFIG_TQ_KEY_DOUBLE_CLICK_MS=450 CONFIG_TQ_KEY_DOUBLE_CLICK_MS=450
CONFIG_TQ_KEY_POST_DOUBLE_CLICK_SUPPRESS_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 # 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. # bounce buffers, LCD DMA, and worker task stacks all need internal-only memory.
+17 -77
View File
@@ -1,73 +1,9 @@
#!/usr/bin/env python3 #!/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 argparse
import struct
from pathlib import Path 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("<HH", color, count))
def iter_inputs(input_path, recursive): def iter_inputs(input_path, recursive):
if input_path.is_file(): if input_path.is_file():
@@ -91,16 +27,26 @@ def output_path_for(input_file, input_root, output):
return (output / relative).with_suffix(".rle") return (output / relative).with_suffix(".rle")
def convert_file(input_file, input_root, output, background): def convert_file(input_file, input_root, output):
try:
from PIL import Image
except ImportError as exc:
raise SystemExit(
"Pillow is required. Install it with: python -m pip install Pillow"
) from exc
out_path = output_path_for(input_file, input_root, output) out_path = output_path_for(input_file, input_root, output)
width, height, runs = image_to_runs(input_file, background) out_path.parent.mkdir(parents=True, exist_ok=True)
write_rle(out_path, runs) with Image.open(input_file) as image:
print(f"{input_file} -> {out_path} ({width}x{height}, {len(runs)} runs)") 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(): def main():
parser = argparse.ArgumentParser( 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("input", type=Path, help="PNG file or directory containing PNG files.")
parser.add_argument( parser.add_argument(
@@ -115,12 +61,6 @@ def main():
action="store_true", action="store_true",
help="Recursively convert PNG files when input is a directory.", 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() args = parser.parse_args()
input_path = args.input.resolve() input_path = args.input.resolve()
@@ -136,7 +76,7 @@ def main():
raise SystemExit("single-file output must use a .rle suffix") raise SystemExit("single-file output must use a .rle suffix")
for input_file in inputs: for input_file in inputs:
convert_file(input_file, input_path, output, args.background) convert_file(input_file, input_path, output)
if __name__ == "__main__": if __name__ == "__main__":