use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use anyhow::Result;
use tokio::sync::broadcast;
use crate::kernel::event::{
BoardEvent, ConnectionEvent, DeviceInfo, DisconnectReason, ModeChangeEvent, ModeSource,
};
use crate::kernel::protocol_hid::*;
use crate::kernel::sink::{AudioFrameSink, PcmSink};
#[cfg(feature = "ble")]
use crate::kernel::sink::{CountingSink, MsbcDecoderSink};
use crate::kernel::types::ConnectionType;
use crate::runtime::hotplug::{spawn_blocking_with_runloop, HotplugConfig, HotplugManager};
use crate::tool::parse::{parse_device_info_from_buf, parse_device_info_from_gatt};
#[cfg(feature = "usb")]
use {
crate::runtime::usb::device_manager::DeviceConnection,
crate::runtime::usb_capture::UsbAudioCapture,
};
#[cfg(feature = "ble")]
use crate::runtime::ble::gatt_client::VendorGattClient;
#[cfg(feature = "ble")]
use btleplug::platform::Adapter;
pub struct BoardDeviceCore {
event_tx: broadcast::Sender<BoardEvent>,
hotplug_config: HotplugConfig,
hotplug_stop: Mutex<Option<Arc<AtomicBool>>>,
started: AtomicBool,
connection_type: Mutex<Option<ConnectionType>>,
#[cfg(feature = "usb")]
config_conn: Mutex<Option<Arc<Mutex<Option<DeviceConnection>>>>>,
#[cfg(feature = "usb")]
monitor_paused: Mutex<Option<Arc<AtomicBool>>>,
#[cfg(feature = "ble")]
cached_adapter: Arc<Mutex<Option<Adapter>>>,
#[cfg(feature = "ble")]
ble_target: Arc<Mutex<Option<String>>>,
#[cfg(feature = "ble")]
ble_auto_connect: Arc<AtomicBool>,
#[cfg(feature = "ble")]
vendor_gatt_client: Arc<Mutex<Option<Arc<VendorGattClient>>>>,
audio_frame_sink: Mutex<Option<Arc<dyn AudioFrameSink>>>,
pcm_sink: Mutex<Option<Arc<dyn PcmSink>>>,
#[cfg(feature = "usb")]
usb_capture: Mutex<Option<UsbAudioCapture>>,
}
impl BoardDeviceCore {
pub fn new(config: HotplugConfig) -> Result<Self> {
let (event_tx, _) = broadcast::channel(256);
Ok(Self {
event_tx,
hotplug_config: config,
hotplug_stop: Mutex::new(None),
started: AtomicBool::new(false),
connection_type: Mutex::new(None),
#[cfg(feature = "usb")]
config_conn: Mutex::new(None),
#[cfg(feature = "usb")]
monitor_paused: Mutex::new(None),
#[cfg(feature = "ble")]
cached_adapter: Arc::new(Mutex::new(None)),
#[cfg(feature = "ble")]
ble_target: Arc::new(Mutex::new(None)),
#[cfg(feature = "ble")]
ble_auto_connect: Arc::new(AtomicBool::new(false)),
#[cfg(feature = "ble")]
vendor_gatt_client: Arc::new(Mutex::new(None)),
audio_frame_sink: Mutex::new(None),
pcm_sink: Mutex::new(None),
#[cfg(feature = "usb")]
usb_capture: Mutex::new(None),
})
}
pub fn event_sender(&self) -> &broadcast::Sender<BoardEvent> {
&self.event_tx
}
pub fn set_pcm_sink(&self, sink: Arc<dyn PcmSink>) {
*self.pcm_sink.lock().unwrap() = Some(sink);
}
pub fn set_audio_frame_sink(&self, sink: Arc<dyn AudioFrameSink>) {
*self.audio_frame_sink.lock().unwrap() = Some(sink);
}
#[cfg(feature = "ble")]
pub fn set_ble_target(&self, name: Option<&str>) {
*self.ble_target.lock().unwrap() = name.map(String::from);
}
#[cfg(feature = "ble")]
pub fn ble_target(&self) -> Option<String> {
self.ble_target.lock().unwrap().clone()
}
#[cfg(feature = "ble")]
pub fn set_auto_reconnect(&self, on: bool) {
self.ble_auto_connect.store(on, Ordering::SeqCst);
}
#[cfg(feature = "ble")]
pub async fn scan_ble_devices(
&self,
timeout: std::time::Duration,
) -> Result<Vec<crate::runtime::ble::gatt_client::BleDeviceInfo>> {
let client = self.ensure_vendor_gatt_client().await?;
client.scan_all_vendor_devices(timeout).await
}
pub fn connection(&self) -> Option<ConnectionType> {
*self.connection_type.lock().unwrap()
}
pub fn is_connected(&self) -> bool {
self.connection().is_some()
}
pub fn auto_reconnect(&self) -> bool {
#[cfg(feature = "ble")]
{
self.ble_auto_connect.load(Ordering::SeqCst)
}
#[cfg(not(feature = "ble"))]
{
true
}
}
pub async fn start(self: &Arc<Self>) -> Result<()> {
if self.started.swap(true, Ordering::SeqCst) {
return Ok(());
}
let stop_flag = Arc::new(AtomicBool::new(true));
*self.hotplug_stop.lock().unwrap() = Some(stop_flag.clone());
let mut hotplug = HotplugManager::new(self.event_tx.clone(), self.hotplug_config.clone());
let inner = self.clone();
hotplug = hotplug.on_connection_change(Box::new(move |ct| {
*inner.connection_type.lock().unwrap() = ct;
match ct {
Some(ConnectionType::Usb) => {
#[cfg(feature = "usb")]
{
if !inner.started.load(Ordering::SeqCst) {
return;
}
let pcm = inner.pcm_sink.lock().unwrap().clone();
if let Some(pcm) = pcm {
let mut slot = inner.usb_capture.lock().unwrap();
if slot.is_none() {
let cap = UsbAudioCapture::new(pcm);
if let Err(e) = cap.start() {
log::warn!(target: "audio", "USB Audio 采集启动失败: {}", e);
} else {
*slot = Some(cap);
}
}
}
}
let inner = inner.clone();
tokio::spawn(async move {
match inner.get_work_mode().await {
Ok(mode) => {
log::info!(target: "board", "[work-mode] USB 初始工作模式: {:?}", mode);
let _ = inner.event_tx.send(BoardEvent::ModeChange(ModeChangeEvent {
mode: mode.display_name().to_string(),
mode_value: mode as u8,
source: ModeSource::Connection,
}));
}
Err(e) => {
log::warn!(target: "board", "[work-mode] USB 查询初始工作模式失败: {}", e);
}
}
if let Err(e) = inner.notify_app_online(true).await {
log::warn!(target: "board", "[app-online] USB 自动上报上线失败: {}", e);
}
});
}
Some(ConnectionType::Ble) => {
let inner = inner.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(500)).await;
match inner.read_device_info().await {
Ok(info) => {
log::info!(
target: "board",
"[device-info] BLE 初始设备信息: fw={} battery={}%",
info.firmware_version,
info.battery_level
);
let _ = inner.event_tx.send(BoardEvent::DeviceInfo(info));
}
Err(e) => {
log::warn!(
target: "board",
"[device-info] BLE 查询初始设备信息失败: {}",
e
);
}
}
match inner.get_work_mode().await {
Ok(mode) => {
log::info!(target: "board", "[work-mode] BLE 初始工作模式: {:?}", mode);
let _ = inner.event_tx.send(BoardEvent::ModeChange(ModeChangeEvent {
mode: mode.display_name().to_string(),
mode_value: mode as u8,
source: ModeSource::Connection,
}));
}
Err(e) => {
log::warn!(target: "board", "[work-mode] BLE 查询初始工作模式失败: {}", e);
}
}
if let Err(e) = inner.notify_app_online(true).await {
log::warn!(target: "board", "[app-online] BLE 自动上报上线失败: {}", e);
}
});
}
_ => {
#[cfg(feature = "usb")]
{
if let Some(cap) = inner.usb_capture.lock().unwrap().take() {
cap.stop();
}
}
}
}
}));
#[cfg(feature = "usb")]
{
let inner = self.clone();
hotplug = hotplug.on_monitor_ready(Box::new(move |conn, paused| {
*inner.config_conn.lock().unwrap() = Some(conn);
*inner.monitor_paused.lock().unwrap() = Some(paused);
}));
}
#[cfg(feature = "ble")]
{
let inner = self.clone();
hotplug = hotplug
.with_vendor_gatt_client_slot(self.vendor_gatt_client.clone())
.with_ble_ensure_client(Box::new(move || {
let inner = inner.clone();
Box::pin(async move { inner.ensure_vendor_gatt_client().await })
}))
.with_ble_target_device_name(self.ble_target.clone())
.with_ble_auto_connect(self.ble_auto_connect.clone());
}
hotplug = hotplug.with_running_flag(stop_flag);
tokio::spawn(async move {
hotplug.run().await;
});
Ok(())
}
pub fn shutdown(&self) {
self.started.store(false, Ordering::SeqCst);
if let Some(stop) = self.hotplug_stop.lock().unwrap().as_ref() {
stop.store(false, Ordering::SeqCst);
}
#[cfg(feature = "usb")]
{
if let Some(cap) = self.usb_capture.lock().unwrap().take() {
cap.stop();
}
}
}
pub async fn disconnect(&self) -> Result<()> {
match self.connection() {
None => Ok(()),
Some(ConnectionType::Usb) => Err(anyhow::anyhow!(
"USB 为物理连接,无法主动断开(请拔出线缆)"
)),
Some(ConnectionType::Ble) => {
#[cfg(feature = "ble")]
{
self.set_auto_reconnect(false);
self.set_ble_target(None);
let client = { self.vendor_gatt_client.lock().unwrap().clone() };
if let Some(client) = client {
let _ = client.disconnect().await;
}
*self.connection_type.lock().unwrap() = None;
let _ = self.event_tx.send(BoardEvent::Connection(ConnectionEvent {
connected: false,
connection_type: None,
reason: Some(DisconnectReason::UserAction),
}));
}
#[cfg(not(feature = "ble"))]
{
return Err(anyhow::anyhow!("BLE feature 未启用,无法断开 BLE 连接"));
}
Ok(())
}
}
}
pub async fn read_device_info(&self) -> Result<DeviceInfo> {
let conn_type = self
.connection()
.ok_or_else(|| anyhow::anyhow!("设备未连接"))?;
match conn_type {
#[cfg(feature = "usb")]
ConnectionType::Usb => {
let (len, buf) = self.cmd_via_fresh_usb(HidPacket::get_device_info()).await?;
if len < 24 {
return Err(anyhow::anyhow!("设备信息响应长度不足: {}", len));
}
if buf[1] != CMD_GET_DEVICE_INFO {
return Err(anyhow::anyhow!("响应 CMD 不匹配: 0x{:02X}", buf[1]));
}
if buf[3] != 0x00 {
return Err(anyhow::anyhow!("查询失败: result=0x{:02X}", buf[3]));
}
parse_device_info_from_buf(&buf, 4, conn_type)
}
#[cfg(not(feature = "usb"))]
ConnectionType::Usb => Err(anyhow::anyhow!("usb feature 未启用")),
ConnectionType::Ble => {
#[cfg(feature = "ble")]
{
let resp = self
.cmd_via_gatt(&HidPacket::get_device_info(), CMD_GET_DEVICE_INFO)
.await?;
parse_device_info_from_gatt(&resp, conn_type)
}
#[cfg(not(feature = "ble"))]
{
let _ = conn_type;
Err(anyhow::anyhow!("BLE feature 未启用"))
}
}
}
}
pub async fn read_key_config(&self) -> Result<KeyConfig> {
let conn_type = self
.connection()
.ok_or_else(|| anyhow::anyhow!("设备未连接"))?;
match conn_type {
#[cfg(feature = "usb")]
ConnectionType::Usb => {
let (len, buf) = self.cmd_via_fresh_usb(HidPacket::get_key_config()).await?;
if len < 64 {
return Err(anyhow::anyhow!("按键配置响应长度不足: {}", len));
}
if buf[3] != 0x00 {
return Err(anyhow::anyhow!("读取失败: result=0x{:02X}", buf[3]));
}
let mut key_data = [0u8; KEY_DATA_LEN];
key_data.copy_from_slice(&buf[4..64]);
Ok(KeyConfig::from_bytes(&key_data))
}
#[cfg(not(feature = "usb"))]
ConnectionType::Usb => Err(anyhow::anyhow!("usb feature 未启用")),
ConnectionType::Ble => {
#[cfg(feature = "ble")]
{
let resp = self
.cmd_via_gatt(&HidPacket::get_key_config(), CMD_GET_KEY_SETTING)
.await?;
if resp.len() < 3 + KEY_DATA_LEN {
return Err(anyhow::anyhow!("按键配置数据长度不足: {}", resp.len()));
}
if resp[2] != 0x00 {
return Err(anyhow::anyhow!("读取失败: result=0x{:02X}", resp[2]));
}
let mut key_data = [0u8; KEY_DATA_LEN];
key_data.copy_from_slice(&resp[3..3 + KEY_DATA_LEN]);
Ok(KeyConfig::from_bytes(&key_data))
}
#[cfg(not(feature = "ble"))]
{
let _ = conn_type;
Err(anyhow::anyhow!("BLE feature 未启用"))
}
}
}
}
pub async fn write_key_config(&self, config: &KeyConfig) -> Result<()> {
let conn_type = self
.connection()
.ok_or_else(|| anyhow::anyhow!("设备未连接"))?;
match conn_type {
#[cfg(feature = "usb")]
ConnectionType::Usb => {
let (len, buf) = self
.cmd_via_fresh_usb(HidPacket::set_key_config(&config.to_bytes()))
.await?;
if len < 4 {
return Err(anyhow::anyhow!("写入响应长度不足: {}", len));
}
if buf[3] != 0x00 {
return Err(anyhow::anyhow!("写入失败: result=0x{:02X}", buf[3]));
}
Ok(())
}
#[cfg(not(feature = "usb"))]
ConnectionType::Usb => Err(anyhow::anyhow!("usb feature 未启用")),
ConnectionType::Ble => {
#[cfg(feature = "ble")]
{
let resp = self
.cmd_via_gatt(
&HidPacket::set_key_config(&config.to_bytes()),
CMD_SET_KEY_SETTING,
)
.await?;
if resp.len() < 3 {
return Err(anyhow::anyhow!("写入响应长度不足"));
}
if resp[2] != 0x00 {
return Err(anyhow::anyhow!("写入失败: result=0x{:02X}", resp[2]));
}
Ok(())
}
#[cfg(not(feature = "ble"))]
{
let _ = conn_type;
Err(anyhow::anyhow!("BLE feature 未启用"))
}
}
}
}
pub async fn get_silent_record(&self) -> Result<bool> {
self.silent_record_command(HidPacket::get_silent_record(), CMD_GET_SILENT_RECORD)
.await
}
pub async fn set_silent_record(&self, enable: bool) -> Result<bool> {
self.silent_record_command(HidPacket::set_silent_record(enable), CMD_SET_SILENT_RECORD)
.await
}
#[cfg(feature = "test-mode")]
pub async fn set_factory_key_test(
&self,
enable: bool,
session: u16,
) -> Result<FactoryKeyControlAck> {
let conn_type = self
.connection()
.ok_or_else(|| anyhow::anyhow!("设备未连接"))?;
let packet = HidPacket::factory_key_test_control(enable, session)?;
match conn_type {
#[cfg(feature = "usb")]
ConnectionType::Usb => {
let (len, response) = self.cmd_via_fresh_usb(packet).await?;
parse_factory_key_control_ack(&response[..len], session)
.map_err(anyhow::Error::from)
}
#[cfg(not(feature = "usb"))]
ConnectionType::Usb => Err(anyhow::anyhow!("usb feature 未启用")),
ConnectionType::Ble => {
#[cfg(feature = "ble")]
{
let response = self
.cmd_via_gatt(&packet, CMD_AI_FACTORY_KEY_TEST_CONTROL)
.await?;
parse_factory_key_control_ack(&response, session).map_err(anyhow::Error::from)
}
#[cfg(not(feature = "ble"))]
{
let _ = packet;
Err(anyhow::anyhow!("BLE feature 未启用"))
}
}
}
}
async fn silent_record_command(&self, packet: [u8; 64], expected_cmd: u8) -> Result<bool> {
let conn_type = self
.connection()
.ok_or_else(|| anyhow::anyhow!("设备未连接"))?;
match conn_type {
#[cfg(feature = "usb")]
ConnectionType::Usb => {
let (len, response) = self.cmd_via_fresh_usb(packet).await?;
parse_silent_record_hid_response(&response[..len], expected_cmd)
.ok_or_else(|| anyhow::anyhow!("静默录音响应无效或命令失败"))
}
#[cfg(not(feature = "usb"))]
ConnectionType::Usb => Err(anyhow::anyhow!("usb feature 未启用")),
ConnectionType::Ble => {
#[cfg(feature = "ble")]
{
let response = self.cmd_via_gatt(&packet, expected_cmd).await?;
parse_silent_record_gatt_response(&response, expected_cmd)
.ok_or_else(|| anyhow::anyhow!("静默录音 GATT 响应无效或命令失败"))
}
#[cfg(not(feature = "ble"))]
{
let _ = (packet, expected_cmd);
Err(anyhow::anyhow!("BLE feature 未启用"))
}
}
}
}
pub async fn read_bindings_blob(&self) -> Result<crate::kernel::bindings_blob::BlobRead> {
let mut link = self.blob_link()?;
crate::kernel::bindings_blob::read_blob(&mut link)
.await
.map_err(anyhow::Error::msg)
}
pub async fn write_bindings_blob(
&self,
payload: &[u8],
) -> std::result::Result<(), crate::kernel::bindings_blob::BlobWriteError> {
let mut link = self
.blob_link()
.map_err(|e| crate::kernel::bindings_blob::BlobWriteError::Transport(e.to_string()))?;
crate::kernel::bindings_blob::write_blob(&mut link, payload).await
}
fn blob_link(&self) -> Result<DeviceBlobLink<'_>> {
let conn_type = self
.connection()
.ok_or_else(|| anyhow::anyhow!("设备未连接"))?;
Ok(DeviceBlobLink {
core: self,
conn_type,
})
}
pub async fn get_work_mode(&self) -> Result<WorkMode> {
let conn_type = self
.connection()
.ok_or_else(|| anyhow::anyhow!("设备未连接"))?;
log::debug!(target: "board", "[work-mode] GET (CMD 0x12/0xC9) 经 {:?}", conn_type);
match conn_type {
#[cfg(feature = "usb")]
ConnectionType::Usb => {
let (len, response) = self.cmd_via_fresh_usb(HidPacket::get_work_mode()).await?;
let parsed = parse_work_mode_hid_response(&response[..len]);
if parsed.is_none() {
log::warn!(target: "board", "[work-mode] HID 响应解析失败,原始响应: {}", fmt_hex_prefix_len(&response, len));
}
parsed.ok_or_else(|| anyhow::anyhow!("工作模式响应无效或命令失败"))
}
#[cfg(not(feature = "usb"))]
ConnectionType::Usb => Err(anyhow::anyhow!("usb feature 未启用")),
ConnectionType::Ble => {
#[cfg(feature = "ble")]
{
let response = self
.cmd_via_gatt(&HidPacket::get_work_mode(), CMD_STATUS)
.await?;
let parsed = parse_work_mode_gatt_response(&response);
if parsed.is_none() {
log::warn!(target: "board", "[work-mode] GATT 响应解析失败,原始响应: {}", fmt_hex_prefix(&response));
}
parsed.ok_or_else(|| anyhow::anyhow!("工作模式 GATT 响应无效或命令失败"))
}
#[cfg(not(feature = "ble"))]
{
let _ = conn_type;
Err(anyhow::anyhow!("BLE feature 未启用"))
}
}
}
}
pub async fn get_sleep_timeout(&self) -> Result<crate::kernel::types::SleepTimeout> {
log::info!(target: "board", "[sleep-timeout] GET (CMD 0x63) 开始");
let res = self
.sleep_timeout_command(HidPacket::get_sleep_timeout(), CMD_GET_SLEEP_TIMEOUT)
.await;
match &res {
Ok(t) => {
log::info!(target: "board", "[sleep-timeout] GET 成功: disconnected={}s connected={}s", t.disconnected, t.connected)
}
Err(e) => log::warn!(target: "board", "[sleep-timeout] GET 失败: {}", e),
}
res
}
pub async fn set_sleep_timeout(
&self,
timeout: crate::kernel::types::SleepTimeout,
) -> Result<crate::kernel::types::SleepTimeout> {
log::info!(target: "board", "[sleep-timeout] SET (CMD 0x64) 请求: disconnected={}s connected={}s", timeout.disconnected, timeout.connected);
let res = self
.sleep_timeout_command(HidPacket::set_sleep_timeout(timeout), CMD_SET_SLEEP_TIMEOUT)
.await;
match &res {
Ok(t) => {
log::info!(target: "board", "[sleep-timeout] SET 成功,固件生效值: disconnected={}s connected={}s", t.disconnected, t.connected)
}
Err(e) => log::warn!(target: "board", "[sleep-timeout] SET 失败: {}", e),
}
res
}
async fn sleep_timeout_command(
&self,
packet: [u8; 64],
expected_cmd: u8,
) -> Result<crate::kernel::types::SleepTimeout> {
let conn_type = self
.connection()
.ok_or_else(|| anyhow::anyhow!("设备未连接"))?;
log::debug!(target: "board", "[sleep-timeout] 经 {:?} 下发 CMD 0x{:02X}", conn_type, expected_cmd);
match conn_type {
#[cfg(feature = "usb")]
ConnectionType::Usb => {
let (len, response) = self.cmd_via_fresh_usb(packet).await?;
let parsed = parse_sleep_timeout_hid_response(&response[..len], expected_cmd);
if parsed.is_none() {
log::warn!(target: "board", "[sleep-timeout] HID 响应解析失败,原始响应: {}", fmt_hex_prefix_len(&response, len));
}
parsed.ok_or_else(|| anyhow::anyhow!("软休眠超时响应无效或命令失败"))
}
#[cfg(not(feature = "usb"))]
ConnectionType::Usb => Err(anyhow::anyhow!("usb feature 未启用")),
ConnectionType::Ble => {
#[cfg(feature = "ble")]
{
let response = self.cmd_via_gatt(&packet, expected_cmd).await?;
let parsed = parse_sleep_timeout_gatt_response(&response, expected_cmd);
if parsed.is_none() {
log::warn!(target: "board", "[sleep-timeout] GATT 响应解析失败,原始响应: {}", fmt_hex_prefix(&response));
}
parsed.ok_or_else(|| anyhow::anyhow!("软休眠超时 GATT 响应无效或命令失败"))
}
#[cfg(not(feature = "ble"))]
{
let _ = (packet, expected_cmd);
Err(anyhow::anyhow!("BLE feature 未启用"))
}
}
}
}
pub async fn notify_app_online(&self, online: bool) -> Result<()> {
let conn_type = self
.connection()
.ok_or_else(|| anyhow::anyhow!("设备未连接"))?;
let packet = HidPacket::app_online_notify(online);
log::debug!(target: "board", "[app-online] 经 {:?} 上报 online={}", conn_type, online);
match conn_type {
#[cfg(feature = "usb")]
ConnectionType::Usb => {
let (_len, _response) = self.cmd_via_fresh_usb(packet).await?;
}
#[cfg(not(feature = "usb"))]
ConnectionType::Usb => {
let _ = conn_type;
return Err(anyhow::anyhow!("usb feature 未启用"));
}
ConnectionType::Ble => {
#[cfg(feature = "ble")]
{
let _ = self.cmd_via_gatt(&packet, CMD_AI_APP_ONLINE_NOTIFY).await?;
}
#[cfg(not(feature = "ble"))]
{
let _ = conn_type;
return Err(anyhow::anyhow!("BLE feature 未启用"));
}
}
}
Ok(())
}
pub async fn get_app_online(&self) -> Result<bool> {
self.app_online_query_command(HidPacket::get_app_online(), CMD_AI_GET_APP_ONLINE)
.await
}
pub async fn get_open_url(&self) -> Result<String> {
self.open_url_command(HidPacket::get_open_url(), CMD_AI_GET_OPEN_URL)
.await
}
pub async fn set_open_url(&self, url: &str) -> Result<String> {
self.open_url_command(HidPacket::set_open_url(url), CMD_AI_SET_OPEN_URL)
.await
}
async fn app_online_query_command(&self, packet: [u8; 64], expected_cmd: u8) -> Result<bool> {
let conn_type = self
.connection()
.ok_or_else(|| anyhow::anyhow!("设备未连接"))?;
match conn_type {
#[cfg(feature = "usb")]
ConnectionType::Usb => {
let (len, response) = self.cmd_via_fresh_usb(packet).await?;
parse_app_online_hid_response(&response[..len], expected_cmd)
.ok_or_else(|| anyhow::anyhow!("App 在线状态响应无效或命令失败"))
}
#[cfg(not(feature = "usb"))]
ConnectionType::Usb => Err(anyhow::anyhow!("usb feature 未启用")),
ConnectionType::Ble => {
#[cfg(feature = "ble")]
{
let response = self.cmd_via_gatt(&packet, expected_cmd).await?;
parse_app_online_gatt_response(&response, expected_cmd)
.ok_or_else(|| anyhow::anyhow!("App 在线状态 GATT 响应无效或命令失败"))
}
#[cfg(not(feature = "ble"))]
{
let _ = (packet, expected_cmd);
Err(anyhow::anyhow!("BLE feature 未启用"))
}
}
}
}
async fn open_url_command(&self, packet: [u8; 64], expected_cmd: u8) -> Result<String> {
let conn_type = self
.connection()
.ok_or_else(|| anyhow::anyhow!("设备未连接"))?;
match conn_type {
#[cfg(feature = "usb")]
ConnectionType::Usb => {
let (len, response) = self.cmd_via_fresh_usb(packet).await?;
let parsed = parse_open_url_hid_response(&response[..len], expected_cmd);
if parsed.is_none() {
log::warn!(target: "board", "[open-url] HID 响应解析失败,原始响应: {}", fmt_hex_prefix_len(&response, len));
}
parsed.ok_or_else(|| anyhow::anyhow!("开网页 URL 响应无效或命令失败"))
}
#[cfg(not(feature = "usb"))]
ConnectionType::Usb => Err(anyhow::anyhow!("usb feature 未启用")),
ConnectionType::Ble => {
#[cfg(feature = "ble")]
{
let response = self.cmd_via_gatt(&packet, expected_cmd).await?;
let parsed = parse_open_url_gatt_response(&response, expected_cmd);
if parsed.is_none() {
log::warn!(target: "board", "[open-url] GATT 响应解析失败,原始响应: {}", fmt_hex_prefix(&response));
}
parsed.ok_or_else(|| anyhow::anyhow!("开网页 URL GATT 响应无效或命令失败"))
}
#[cfg(not(feature = "ble"))]
{
let _ = (packet, expected_cmd);
Err(anyhow::anyhow!("BLE feature 未启用"))
}
}
}
}
#[cfg(feature = "usb")]
pub async fn dfu_upgrade(
self: &Arc<Self>,
firmware_path: std::path::PathBuf,
on_progress: crate::dfu::client::ProgressCallback,
cancel_flag: Arc<AtomicBool>,
) -> Result<()> {
use crate::dfu::{build_enter_dfu_hid_command, client::DfuClient};
let conn_type = self
.connection()
.ok_or_else(|| anyhow::anyhow!("设备未连接"))?;
if conn_type != ConnectionType::Usb {
return Err(anyhow::anyhow!(
"固件升级仅支持 USB 连接(当前 {:?})",
conn_type
));
}
let firmware = std::fs::read(&firmware_path)
.map_err(|e| anyhow::anyhow!("读取固件文件失败 {firmware_path:?}: {e}"))?;
let total_len = firmware.len() as u32;
if total_len == 0 {
return Err(anyhow::anyhow!("固件文件为空"));
}
log::info!(target: "board", "[dfu] runtime: 开始升级 {firmware_path:?} ({} bytes)", total_len);
log::info!(target: "board", "[dfu] runtime: 发送 CMD 0xEF 进入 DFU 模式");
let enter_cmd = build_enter_dfu_hid_command();
if let Err(e) = self.cmd_via_fresh_usb(enter_cmd).await {
log::warn!(target: "board", "[dfu] runtime: CMD 0xEF 响应未收到(设备重启中),继续等待 DFU 设备: {e}");
}
let paused_arc = self.monitor_paused.lock().unwrap().clone();
let firmware_arc = Arc::new(firmware);
let progress_clone = on_progress.clone();
let cancel_clone = cancel_flag.clone();
let thread_result = spawn_blocking_with_runloop::<_, Result<()>>(move || {
let _pause_guard = PauseGuard::new(paused_arc);
let client = DfuClient::new(cancel_clone, progress_clone);
client.upgrade(&firmware_arc, || Ok(()))
})
.await;
match thread_result {
Ok(inner) => inner,
Err(panic) => Err(anyhow::anyhow!("DFU 线程崩溃: {panic:?}")),
}
}
#[cfg(feature = "usb")]
pub async fn is_stuck_in_dfu(&self) -> Result<bool> {
spawn_blocking_with_runloop(crate::dfu::recover::scan_stuck_device)
.await
.map_err(|e| anyhow::anyhow!("HID 线程崩溃: {e:?}"))?
}
#[cfg(feature = "usb")]
pub async fn recover_stuck_dfu(&self) -> Result<crate::dfu::RecoveryOutcome> {
use crate::dfu::recover::{
scan_normal_device, scan_stuck_device, send_recovery_sequence,
RECOVERY_POLL_INTERVAL_MS, RECOVERY_WAIT_TIMEOUT_SECS,
};
use crate::dfu::RecoveryOutcome;
let stuck = spawn_blocking_with_runloop(scan_stuck_device)
.await
.map_err(|e| anyhow::anyhow!("HID 线程崩溃: {e:?}"))??;
if !stuck {
log::info!(target: "board", "[dfu-recover] 未发现 DFU 设备,无需恢复");
return Ok(RecoveryOutcome::NotStuck);
}
let paused_arc = self.monitor_paused.lock().unwrap().clone();
let _pause_guard = tokio::task::spawn_blocking(move || PauseGuard::new(paused_arc))
.await
.map_err(|e| anyhow::anyhow!("暂停 monitor 失败: {e}"))?;
log::info!(target: "board", "[dfu-recover] 发送恢复序列 PREPARE+END...");
let delivered = spawn_blocking_with_runloop(send_recovery_sequence)
.await
.map_err(|e| anyhow::anyhow!("恢复线程崩溃: {e:?}"))??;
if !delivered {
log::warn!(target: "board", "[dfu-recover] 恢复包未能送达设备,跳过等待");
return Ok(RecoveryOutcome::StillStuck);
}
let deadline =
tokio::time::Instant::now() + Duration::from_secs(RECOVERY_WAIT_TIMEOUT_SECS);
while tokio::time::Instant::now() < deadline {
tokio::time::sleep(Duration::from_millis(RECOVERY_POLL_INTERVAL_MS)).await;
let scan = spawn_blocking_with_runloop(scan_normal_device)
.await
.map_err(|e| anyhow::anyhow!("HID 线程崩溃: {e:?}"))?;
let back = match scan {
Ok(found) => found,
Err(e) => {
log::debug!(target: "board", "[dfu-recover] 等待期间枚举失败: {e}(继续轮询)");
false
}
};
if back {
log::info!(target: "board", "[dfu-recover] 设备已回到正常模式");
return Ok(RecoveryOutcome::Recovered);
}
}
log::warn!(
target: "board",
"[dfu-recover] 等待 {RECOVERY_WAIT_TIMEOUT_SECS}s 后设备仍未回到正常模式(需物理重插 USB)"
);
Ok(RecoveryOutcome::StillStuck)
}
#[cfg(feature = "test-mode")]
pub async fn shutdown_device(&self, keep_pair: bool) -> Result<()> {
let conn_type = self
.connection()
.ok_or_else(|| anyhow::anyhow!("设备未连接"))?;
let cmd = HidPacket::shutdown(keep_pair);
match conn_type {
#[cfg(feature = "usb")]
ConnectionType::Usb => {
self.cmd_via_fresh_usb(cmd).await?;
}
#[cfg(not(feature = "usb"))]
ConnectionType::Usb => {
let _ = (conn_type, cmd);
return Err(anyhow::anyhow!("usb feature 未启用"));
}
ConnectionType::Ble => {
#[cfg(feature = "ble")]
{
self.cmd_via_gatt(&cmd, CMD_AI_SHUTDOWN).await?;
}
#[cfg(not(feature = "ble"))]
{
let _ = conn_type;
return Err(anyhow::anyhow!("BLE feature 未启用"));
}
}
}
Ok(())
}
#[cfg(feature = "usb")]
async fn cmd_via_fresh_usb(&self, cmd: [u8; 64]) -> Result<(usize, [u8; 64])> {
let paused_arc = self.monitor_paused.lock().unwrap().clone();
log::debug!(target: "board", "[USB→] 发送 HID (CMD=0x{:02X}): {}", cmd[1], fmt_hex_prefix(&cmd));
let result = spawn_blocking_with_runloop::<_, Result<(usize, [u8; 64])>>(move || {
let _guard = PauseGuard::new(paused_arc);
let api = hidapi::HidApi::new()?;
let dev_info = api
.device_list()
.find(|d| {
d.vendor_id() == VID
&& is_target_pid(d.product_id())
&& d.usage_page() == USAGE_PAGE_CONFIG
&& d.usage() == 0x0002
})
.or_else(|| {
api.device_list().find(|d| {
d.vendor_id() == VID
&& is_target_pid(d.product_id())
&& d.usage_page() == USAGE_PAGE_CONFIG
})
})
.ok_or_else(|| anyhow::anyhow!("未找到 Config 接口"))?;
let device = api.open_path(dev_info.path())?;
device.write(&cmd)?;
let mut buf = [0u8; 64];
let len = device.read_timeout(&mut buf, 3000)?;
Ok((len, buf))
});
let res = result
.await
.map_err(|e| anyhow::anyhow!("HID 线程崩溃: {:?}", e))?;
match &res {
Ok((len, buf)) => {
log::debug!(target: "board", "[USB←] 收到 HID (len={}, CMD=0x{:02X}): {}", len, buf.get(1).copied().unwrap_or(0), fmt_hex_prefix_len(buf, *len))
}
Err(e) => log::warn!(target: "board", "[USB✗] HID 命令失败: {}", e),
}
res
}
#[cfg(feature = "ble")]
async fn cmd_via_gatt(&self, hid_packet: &[u8; 64], expected_cmd: u8) -> Result<Vec<u8>> {
use crate::kernel::protocol_gatt::hid_to_gatt_command;
let client = self
.vendor_gatt_client
.lock()
.unwrap()
.clone()
.ok_or_else(|| anyhow::anyhow!("VendorGattClient 未就绪"))?;
let gatt_cmd = hid_to_gatt_command(hid_packet);
log::debug!(target: "board", "[GATT→] 发送 (CMD=0x{:02X}): {}", hid_packet[1], fmt_hex_prefix(hid_packet));
let res = client
.send_command_and_read_response(expected_cmd, &gatt_cmd, 3000)
.await;
match &res {
Ok(resp) => {
log::debug!(target: "board", "[GATT←] 收到 (len={}, CMD=0x{:02X}): {}", resp.len(), resp.first().copied().unwrap_or(0), fmt_hex_prefix(resp))
}
Err(e) => {
log::warn!(target: "board", "[GATT✗] 命令失败 (CMD=0x{:02X}): {}", expected_cmd, e)
}
}
res
}
#[cfg(feature = "ble")]
pub(crate) async fn ensure_vendor_gatt_client(&self) -> Result<Arc<VendorGattClient>> {
{
let guard = self.vendor_gatt_client.lock().unwrap();
if let Some(c) = guard.as_ref() {
return Ok(c.clone());
}
}
let adapter = self.get_or_create_adapter().await?;
let audio_sink = self.build_ble_audio_sink();
let client = Arc::new(VendorGattClient::new(
adapter,
audio_sink,
self.event_tx.clone(),
));
*self.vendor_gatt_client.lock().unwrap() = Some(client.clone());
Ok(client)
}
#[cfg(feature = "ble")]
async fn get_or_create_adapter(&self) -> Result<Adapter> {
{
let guard = self.cached_adapter.lock().unwrap();
if let Some(a) = guard.as_ref() {
return Ok(a.clone());
}
}
let adapter = Self::create_adapter().await?;
*self.cached_adapter.lock().unwrap() = Some(adapter.clone());
Ok(adapter)
}
#[cfg(feature = "ble")]
async fn create_adapter() -> Result<Adapter> {
use btleplug::api::Manager as _;
let manager = btleplug::platform::Manager::new()
.await
.map_err(|e| anyhow::anyhow!("BLE Manager 失败: {}", e))?;
let adapters = manager
.adapters()
.await
.map_err(|e| anyhow::anyhow!("BLE adapters 失败: {}", e))?;
adapters
.into_iter()
.next()
.ok_or_else(|| anyhow::anyhow!("无 BLE adapter"))
}
#[cfg(feature = "ble")]
fn build_ble_audio_sink(&self) -> Arc<dyn AudioFrameSink> {
if let Some(frame_sink) = self.audio_frame_sink.lock().unwrap().clone() {
return frame_sink;
}
if let Some(pcm) = self.pcm_sink.lock().unwrap().clone() {
return Arc::new(MsbcDecoderSink::new(pcm));
}
Arc::new(CountingSink::new())
}
}
#[cfg(feature = "usb")]
struct PauseGuard {
paused: Option<Arc<AtomicBool>>,
}
#[cfg(feature = "usb")]
impl PauseGuard {
fn new(paused: Option<Arc<AtomicBool>>) -> Self {
if let Some(p) = &paused {
p.store(true, Ordering::SeqCst);
std::thread::sleep(std::time::Duration::from_millis(20));
}
Self { paused }
}
}
#[cfg(feature = "usb")]
impl Drop for PauseGuard {
fn drop(&mut self) {
if let Some(p) = &self.paused {
p.store(false, Ordering::SeqCst);
}
}
}
fn fmt_hex_prefix(data: &[u8]) -> String {
const PREFIX: usize = 16;
if data.len() <= PREFIX {
data.iter()
.map(|b| format!("{:02X}", b))
.collect::<Vec<_>>()
.join(" ")
} else {
let head: String = data[..PREFIX]
.iter()
.map(|b| format!("{:02X}", b))
.collect::<Vec<_>>()
.join(" ");
format!("{} … (共 {} 字节)", head, data.len())
}
}
fn fmt_hex_prefix_len(data: &[u8], len: usize) -> String {
let end = len.min(data.len());
fmt_hex_prefix(&data[..end])
}
struct DeviceBlobLink<'a> {
core: &'a BoardDeviceCore,
conn_type: ConnectionType,
}
impl DeviceBlobLink<'_> {
async fn exchange(
&mut self,
request: &[u8; 64],
expected_cmd: u8,
) -> Result<Option<Vec<u8>>, String> {
match self.conn_type {
#[cfg(feature = "usb")]
ConnectionType::Usb => {
let (len, buf) = self
.core
.cmd_via_fresh_usb(*request)
.await
.map_err(|e| e.to_string())?;
if len == 0 {
return Ok(None); }
Ok(Some(buf[..len].to_vec()))
}
#[cfg(not(feature = "usb"))]
ConnectionType::Usb => Err("usb feature 未启用".to_string()),
ConnectionType::Ble => {
#[cfg(feature = "ble")]
{
match self.core.cmd_via_gatt(request, expected_cmd).await {
Ok(resp) => Ok(Some(resp)),
Err(e) if e.to_string().contains("超时") => Ok(None),
Err(e) => Err(e.to_string()),
}
}
#[cfg(not(feature = "ble"))]
{
let _ = (request, expected_cmd);
Err("BLE feature 未启用".to_string())
}
}
}
}
}
impl crate::kernel::bindings_blob::BlobLink for DeviceBlobLink<'_> {
async fn read_chunk(&mut self, offset: u16) -> Result<Option<(u16, u16, Vec<u8>)>, String> {
use crate::kernel::bindings_blob as blob;
let request = blob::read_bindings_blob_packet(offset);
let Some(resp) = self
.exchange(&request, blob::CMD_AI_READ_BINDINGS_BLOB)
.await?
else {
return Ok(None);
};
let parsed = match self.conn_type {
ConnectionType::Usb => {
blob::parse_blob_read_hid_response(&resp).map(|(o, t, c)| (o, t, c.to_vec()))
}
ConnectionType::Ble => {
blob::parse_blob_read_gatt_response(&resp).map(|(o, t, c)| (o, t, c.to_vec()))
}
};
parsed
.map(Some)
.ok_or_else(|| "blob 读应答无效或命令失败".to_string())
}
async fn write_chunk(
&mut self,
offset: u16,
chunk: &[u8],
) -> Result<Option<crate::kernel::bindings_blob::BlobWriteAck>, String> {
use crate::kernel::bindings_blob as blob;
let request = blob::write_bindings_blob_packet(offset, chunk);
let Some(resp) = self
.exchange(&request, blob::CMD_AI_WRITE_BINDINGS_BLOB)
.await?
else {
return Ok(None);
};
let parsed = match self.conn_type {
ConnectionType::Usb => blob::parse_blob_write_ack_hid_response(&resp),
ConnectionType::Ble => blob::parse_blob_write_ack_gatt_response(&resp),
};
parsed
.map(Some)
.ok_or_else(|| "blob 写应答无效".to_string())
}
async fn commit(
&mut self,
total_len: u16,
crc16: u16,
) -> Result<Option<crate::kernel::bindings_blob::BlobWriteAck>, String> {
use crate::kernel::bindings_blob as blob;
let request = blob::commit_bindings_blob_packet(total_len, crc16);
let Some(resp) = self
.exchange(&request, blob::CMD_AI_WRITE_BINDINGS_BLOB)
.await?
else {
return Ok(None);
};
let parsed = match self.conn_type {
ConnectionType::Usb => blob::parse_blob_write_ack_hid_response(&resp),
ConnectionType::Ble => blob::parse_blob_write_ack_gatt_response(&resp),
};
parsed
.map(Some)
.ok_or_else(|| "blob commit 应答无效".to_string())
}
}