增加了单击启动对话和长按期间对话,默认使用短按启动对话
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
+17
-77
@@ -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("<HH", color, count))
|
||||
|
||||
|
||||
def iter_inputs(input_path, recursive):
|
||||
if input_path.is_file():
|
||||
@@ -91,16 +27,26 @@ def output_path_for(input_file, input_root, output):
|
||||
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)
|
||||
width, height, runs = image_to_runs(input_file, background)
|
||||
write_rle(out_path, runs)
|
||||
print(f"{input_file} -> {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__":
|
||||
|
||||
Reference in New Issue
Block a user