From 323d6c571570e0a8323a2e1d6daf7110f9f5c796 Mon Sep 17 00:00:00 2001 From: Gregory Mallios Date: Sun, 31 Mar 2024 15:43:59 +0300 Subject: [PATCH] feat: Remove legacy code and add new DeviceFeatureSet --- manager-app/src/device.rs | 192 -------- manager-app/src/frontend_types.rs | 65 --- manager-app/src/main.rs | 94 ++-- manager-app/src/server.rs | 33 -- manager-app/src/utils.rs | 32 -- manager-ui/src/types/soundcore-lib.d.ts | 37 +- manager-ui/src/types/tauri-backend.d.ts | 36 -- soundcore-lib/examples/basic.rs | 15 - soundcore-lib/src/api.rs | 2 + soundcore-lib/src/api/feature_set.rs | 31 ++ .../src/api/feature_set/equalizer_features.rs | 11 + soundcore-lib/src/api/feature_set/flags.rs | 26 ++ .../api/feature_set/sound_mode_features.rs | 250 +++++++++++ soundcore-lib/src/api/state/state.rs | 6 +- soundcore-lib/src/devices.rs | 13 + soundcore-lib/src/devices/A3027Device.rs | 253 ----------- soundcore-lib/src/devices/A3040Device.rs | 278 ------------ soundcore-lib/src/devices/A3935Device.rs | 247 ----------- soundcore-lib/src/devices/A3951Device.rs | 409 ------------------ soundcore-lib/src/devices/a3027.rs | 3 + soundcore-lib/src/devices/a3027/features.rs | 16 + soundcore-lib/src/devices/a3028.rs | 3 + soundcore-lib/src/devices/a3028/features.rs | 16 + soundcore-lib/src/devices/a3029.rs | 3 + soundcore-lib/src/devices/a3029/features.rs | 16 + soundcore-lib/src/devices/a3040.rs | 3 + soundcore-lib/src/devices/a3040/features.rs | 23 + soundcore-lib/src/devices/a3930.rs | 3 + soundcore-lib/src/devices/a3930/features.rs | 17 + soundcore-lib/src/devices/a3951.rs | 3 + soundcore-lib/src/devices/a3951/features.rs | 22 + soundcore-lib/src/devices/mod.rs | 9 - soundcore-lib/src/models/anc_mode.rs | 20 +- .../src/packets/response/state/a3027.rs | 35 +- .../src/packets/response/state/a3028.rs | 22 +- .../src/packets/response/state/a3029.rs | 12 +- .../src/packets/response/state/a3040.rs | 28 +- .../src/packets/response/state/a3930.rs | 19 +- .../src/packets/response/state/a3951.rs | 62 +-- soundcore-lib/src/parsers/sound_mode.rs | 2 +- 40 files changed, 592 insertions(+), 1775 deletions(-) delete mode 100644 manager-app/src/device.rs delete mode 100644 manager-app/src/frontend_types.rs delete mode 100644 manager-app/src/server.rs delete mode 100644 manager-app/src/utils.rs delete mode 100644 soundcore-lib/examples/basic.rs create mode 100644 soundcore-lib/src/api/feature_set.rs create mode 100644 soundcore-lib/src/api/feature_set/equalizer_features.rs create mode 100644 soundcore-lib/src/api/feature_set/flags.rs create mode 100644 soundcore-lib/src/api/feature_set/sound_mode_features.rs create mode 100644 soundcore-lib/src/devices.rs delete mode 100644 soundcore-lib/src/devices/A3027Device.rs delete mode 100644 soundcore-lib/src/devices/A3040Device.rs delete mode 100644 soundcore-lib/src/devices/A3935Device.rs delete mode 100644 soundcore-lib/src/devices/A3951Device.rs create mode 100644 soundcore-lib/src/devices/a3027.rs create mode 100644 soundcore-lib/src/devices/a3027/features.rs create mode 100644 soundcore-lib/src/devices/a3028.rs create mode 100644 soundcore-lib/src/devices/a3028/features.rs create mode 100644 soundcore-lib/src/devices/a3029.rs create mode 100644 soundcore-lib/src/devices/a3029/features.rs create mode 100644 soundcore-lib/src/devices/a3040.rs create mode 100644 soundcore-lib/src/devices/a3040/features.rs create mode 100644 soundcore-lib/src/devices/a3930.rs create mode 100644 soundcore-lib/src/devices/a3930/features.rs create mode 100644 soundcore-lib/src/devices/a3951.rs create mode 100644 soundcore-lib/src/devices/a3951/features.rs delete mode 100644 soundcore-lib/src/devices/mod.rs diff --git a/manager-app/src/device.rs b/manager-app/src/device.rs deleted file mode 100644 index 2ae7546..0000000 --- a/manager-app/src/device.rs +++ /dev/null @@ -1,192 +0,0 @@ -use crate::{ - frontend_types::ANCModes, - utils::{anc_mode_to_profile, anc_profile_to_mode}, - SoundcoreAppState, -}; -use log::debug; -use soundcore_lib::{ - base::SoundcoreDevice, - devices::{A3027, A3040, A3935, A3951}, - types::{ - BatteryCharging, BatteryLevel, DeviceInfo, DeviceStatus, EQWave, SupportedModels, - SOUNDCORE_NAME_MODEL_MAP, - }, - BluetoothAdrr, -}; -use tauri::State; - -#[tauri::command] -pub(crate) async fn is_connected(state: State<'_, SoundcoreAppState>) -> Result { - let device = state.device.lock().await; - let device = device.as_ref().ok_or("No device connected")?; - let _status = device.get_info().await.map_err(|e| e.to_string())?; - Ok(true) -} - -#[tauri::command] -pub(crate) async fn close(state: State<'_, SoundcoreAppState>) -> Result<(), String> { - debug!("Closing device"); - let mut device = state.device.lock().await; - if device.is_some() { - device.as_ref().unwrap().close().await.unwrap(); - } - *device = None; - *state.model.write().await = None; - Ok(()) -} - -#[tauri::command] -pub(crate) async fn get_model( - state: State<'_, SoundcoreAppState>, -) -> Result { - let model = state.model.read().await; - model.clone().ok_or("No device connected".to_string()) -} - -#[tauri::command] -pub(crate) async fn connect( - app_state: State<'_, SoundcoreAppState>, - bt_name: String, - bt_addr: String, -) -> Result<(), String> { - /* Check if device is connected */ - { - let mut device = app_state.device.lock().await; - if device.is_some() { - device.as_ref().unwrap().close().await.unwrap(); - *device = None; - } - } - let key = SOUNDCORE_NAME_MODEL_MAP - .keys() - .find(|name| bt_name.contains(*name)) - .ok_or_else(|| format!("No Model ID found for given bluetooth name: {}", bt_name))?; - - let device_model = SOUNDCORE_NAME_MODEL_MAP - .get(key) - .ok_or_else(|| format!("No device model found for key: {}", key))?; - - let mut device_state = app_state.device.lock().await; - match device_model { - SupportedModels::A3951 => { - let device = A3951::default() - .init(BluetoothAdrr::from(bt_addr)) - .await - .map_err(|e| e.to_string())?; - *device_state = Some(device); - } - SupportedModels::A3935 => { - let device = A3935::default() - .init(BluetoothAdrr::from(bt_addr)) - .await - .map_err(|e| e.to_string())?; - *device_state = Some(device); - } - SupportedModels::A3027 | SupportedModels::A3028 | SupportedModels::A3029 => { - let device = A3027::default() - .init(BluetoothAdrr::from(bt_addr)) - .await - .map_err(|e| e.to_string())?; - *device_state = Some(device); - } - SupportedModels::A3040 => { - let device = A3040::default() - .init(BluetoothAdrr::from(bt_addr)) - .await - .map_err(|e| e.to_string())?; - *device_state = Some(device); - }, - _ => { - return Err(format!( - "Device model {:?} is not supported yet", - device_model - )) - } - }; - - *app_state.model.write().await = Some(device_model.clone()); - debug!("Connected to device: {}", bt_name); - Ok(()) -} - -#[tauri::command] -pub(crate) async fn get_info(state: State<'_, SoundcoreAppState>) -> Result { - let device = state.device.lock().await; - let device = device.as_ref().ok_or("No device connected")?; - let info = device.get_info().await.map_err(|e| e.to_string())?; - Ok(info) -} - -#[tauri::command] -pub(crate) async fn get_status( - state: State<'_, SoundcoreAppState>, -) -> Result { - let device = state.device.lock().await; - let device = device.as_ref().ok_or("No device connected")?; - let status = device.get_status().await.map_err(|e| e.to_string())?; - Ok(status) -} - -#[tauri::command] -pub(crate) async fn get_battery_level( - state: State<'_, SoundcoreAppState>, -) -> Result { - let device = state.device.lock().await; - let device = device.as_ref().ok_or("No device connected")?; - let battery_level = device - .get_battery_level() - .await - .map_err(|e| e.to_string())?; - Ok(battery_level) -} - -#[tauri::command] -pub(crate) async fn get_battery_charging( - state: State<'_, SoundcoreAppState>, -) -> Result { - let device = state.device.lock().await; - let device = device.as_ref().ok_or("No device connected")?; - let battery_charging = device - .get_battery_charging() - .await - .map_err(|e| e.to_string())?; - Ok(battery_charging) -} - -#[tauri::command] -pub(crate) async fn get_anc(state: State<'_, SoundcoreAppState>) -> Result { - let device = state.device.lock().await; - let device = device.as_ref().ok_or("No device connected")?; - let anc = device.get_anc().await.map_err(|e| e.to_string())?; - Ok(anc_profile_to_mode(anc)) -} - -#[tauri::command] -pub(crate) async fn set_anc( - state: State<'_, SoundcoreAppState>, - mode: ANCModes, -) -> Result<(), String> { - let device = state.device.lock().await; - let device = device.as_ref().ok_or("No device connected")?; - device - .set_anc(anc_mode_to_profile(mode)) - .await - .map_err(|e| e.to_string())?; - Ok(()) -} - -#[tauri::command] -pub(crate) async fn get_eq(state: State<'_, SoundcoreAppState>) -> Result { - let device = state.device.lock().await; - let device = device.as_ref().ok_or("No device connected")?; - let eq = device.get_eq().await.map_err(|e| e.to_string())?; - Ok(eq) -} - -#[tauri::command] -pub(crate) async fn set_eq(state: State<'_, SoundcoreAppState>, eq: EQWave) -> Result<(), String> { - let device = state.device.lock().await; - let device = device.as_ref().ok_or("No device connected")?; - device.set_eq(eq).await.map_err(|e| e.to_string())?; - Ok(()) -} diff --git a/manager-app/src/frontend_types.rs b/manager-app/src/frontend_types.rs deleted file mode 100644 index ba2f800..0000000 --- a/manager-app/src/frontend_types.rs +++ /dev/null @@ -1,65 +0,0 @@ -use serde::{Deserialize, Serialize}; -use soundcore_lib::types::{BatteryCharging, BatteryLevel}; -use typeshare::typeshare; - - - -#[typeshare] -#[derive(Serialize, Deserialize, Clone, Copy, Debug)] -#[serde(tag = "mode", content = "value")] -pub(crate) enum ANCModes { - NormalMode, - AncTransportMode, - AncOutdoorMode, - AncIndoorMode, - AncCustomValue(u8), - TransparencyFullyTransparentMode, - TransparencyVocalMode, -} - - -#[typeshare] -#[derive(Serialize, Deserialize)] -pub(crate) struct BthScanResult { - name: String, - address: String, - is_connected: bool, -} - -impl From for BthScanResult { - fn from(device: bluetooth_lib::BluetoothDevice) -> Self { - Self { - name: device.name, - address: device.address.to_string(), - is_connected: device.connected, - } - } -} - -#[typeshare] -#[derive(Serialize, Deserialize, Debug)] -pub(crate) struct NewTrayDeviceStatus { - pub is_connected: bool, - pub charging: BatteryCharging, - pub level: BatteryLevel, - pub anc_mode: ANCModes, -} - - -#[typeshare] -#[derive(Serialize, Deserialize)] -pub struct DeviceFeatures { - pub profiles: Vec, -} - -#[typeshare] -#[derive(Serialize, Deserialize)] -pub enum SupportedANCProfiles { - Normal, - AncTransportMode, - AncOutdoorMode, - AncIndoorMode, - AncCustomValue, - TransparencyFullyTransparentMode, - TransparencyVocalMode, -} diff --git a/manager-app/src/main.rs b/manager-app/src/main.rs index 6f4b72c..124e777 100644 --- a/manager-app/src/main.rs +++ b/manager-app/src/main.rs @@ -3,54 +3,33 @@ windows_subsystem = "windows" )] +use std::collections::HashMap; use std::sync::Arc; use log::{info, trace}; use mpsc::channel; +use soundcore_lib::api::SoundcoreDeviceState; +use soundcore_lib::btaddr::BluetoothAdrr; +use tauri::api::notification::Notification; use tauri::async_runtime::{Mutex, RwLock}; -use tauri::Manager; +use tauri::{AppHandle, Manager}; use tauri_plugin_log::LogTarget; use tokio::sync::mpsc; -use bluetooth_lib::platform::BthScanner; -use bluetooth_lib::Scanner; -use frontend_types::BthScanResult; -use soundcore_lib::base::SoundcoreDevice; -use soundcore_lib::types::{SupportedModels, SOUNDCORE_NAME_MODEL_MAP}; +use soundcore_lib::types::SupportedModels; use crate::async_bridge::{async_bridge, BridgeCommand, BridgeResponse}; pub(crate) mod async_bridge; -mod device; -pub(crate) mod frontend_types; -mod tray; -pub(crate) mod utils; - -#[cfg(target_os = "macos")] -mod server; - -#[tauri::command] -async fn scan_for_devices() -> Vec { - let res = BthScanner::new().scan().await; - let mut scan_res: Vec = vec![]; - res.into_iter().for_each(|btdevice| { - if !btdevice.connected - || !SOUNDCORE_NAME_MODEL_MAP - .keys() - .any(|name| btdevice.name.contains(name)) - { - return; - } - scan_res.push(BthScanResult::from(btdevice)); - }); - scan_res -} +// Remove for now since this uses legacy code +// TODO: Migrate it to the new async system +// mod tray; struct SoundcoreAppState { - device: Arc>>>, model: Arc>>, bridge_tx: Mutex>, scan_in_progress: Arc>, + last_states: Arc>>, } #[tokio::main] @@ -82,30 +61,17 @@ async fn main() { }); Ok(()) }) - .system_tray(tray::get_system_tray()) - .on_system_tray_event(tray::handle_tray_event) + // .system_tray(tray::get_system_tray()) + // .on_system_tray_event(tray::handle_tray_event) .manage(SoundcoreAppState { - device: Arc::new(Mutex::new(None)), model: Arc::new(RwLock::new(None)), bridge_tx: Mutex::new(input_tx), scan_in_progress: Arc::new(Mutex::new(false)), + last_states: Arc::new(Mutex::new(HashMap::new())) }) .invoke_handler(tauri::generate_handler![ - tray::set_tray_device_status, - tray::set_tray_menu, - device::connect, - device::close, - device::get_model, - device::is_connected, - device::get_info, - device::get_status, - device::get_battery_level, - device::get_battery_charging, - device::set_anc, - device::get_anc, - device::set_eq, - device::get_eq, - scan_for_devices, + // tray::set_tray_device_status, + // tray::set_tray_menu, send_bridge_command ]) .plugin(tauri_plugin_log::Builder::default().targets([ @@ -148,6 +114,16 @@ async fn handle_bridge_output(resp: BridgeResponse, manager: let state = manager.state::(); let mut scan_in_progress = state.scan_in_progress.lock().await; *scan_in_progress = false; + } else if let BridgeResponse::NewState(new_state) = resp.clone() { + let state = manager.state::(); + let mut device_states = state.last_states.lock().await; + let last_state = device_states.insert(new_state.addr, new_state.state.clone()); + handle_state_update(last_state, new_state.state, manager.app_handle()); + } else if let BridgeResponse::ConnectionEstablished(conn) = resp.clone() { + // This is the initial state so we don't want to show anything to the user + let state = manager.state::(); + let mut device_states = state.last_states.lock().await; + device_states.insert(conn.addr, conn.state.clone()); } manager.emit_all("async-bridge-event", resp).unwrap(); } @@ -168,3 +144,23 @@ async fn send_bridge_command( let tx = app_state.bridge_tx.lock().await; tx.send(payload).await.map_err(|e| e.to_string()) } + +fn handle_state_update( + last_state: Option, + new_state: SoundcoreDeviceState, + app_handle: AppHandle, +) { + if let Some(last_state) = last_state { + if last_state == new_state { + return; + } + Notification::new("soundcore-manager") + .title("Device state update") + .body(format!( + "Device state update: {:?} -> {:?}", + last_state, new_state + )) + .show() + .expect("Failed to show notification"); + } +} diff --git a/manager-app/src/server.rs b/manager-app/src/server.rs deleted file mode 100644 index 6641581..0000000 --- a/manager-app/src/server.rs +++ /dev/null @@ -1,33 +0,0 @@ -pub fn launch_server() { - #[cfg(debug_assertions)] - let mut cmd = std::process::Command::new("killall") - .arg("soundcoremanager-iobtserver") - .spawn() - .expect("failed to spawn killall command"); - let _res = cmd.wait().expect("failed to wait for killall command"); - - use tauri::api::process::CommandEvent::{Error, Stderr, Stdout}; - let (mut rx, mut _tx) = - tauri::api::process::Command::new_sidecar("soundcoremanager-iobtserver") - .expect("failed to create server command") - .spawn() - .expect("failed to spawn server command"); - tauri::async_runtime::spawn(async move { - // read events such as stdout - while let Some(event) = rx.recv().await { - match event { - Stdout(data) => { - log::debug!("{}", data); - } - Stderr(data) => { - log::debug!("{}", data); - } - Error(err) => { - log::debug!("{}", err); - } - _ => {} - } - } - }); - std::thread::sleep(std::time::Duration::from_secs(1)); -} diff --git a/manager-app/src/utils.rs b/manager-app/src/utils.rs deleted file mode 100644 index 003175a..0000000 --- a/manager-app/src/utils.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::frontend_types::ANCModes; -use soundcore_lib::types::ANCProfile; - -// TODO: ANCModes should carry the saved CustomValue as well in order to send it -// TODO: Cleanup ANC types -pub(crate) fn anc_mode_to_profile(mode: ANCModes) -> ANCProfile { - match mode { - ANCModes::NormalMode => ANCProfile::NORMAL_MODE, - ANCModes::AncTransportMode => ANCProfile::ANC_TRANSPORT_MODE, - ANCModes::AncOutdoorMode => ANCProfile::ANC_OUTDOOR_MODE, - ANCModes::AncIndoorMode => ANCProfile::ANC_INDOOR_MODE, - ANCModes::AncCustomValue(value) => ANCProfile::anc_custom_value(value), - ANCModes::TransparencyFullyTransparentMode => { - ANCProfile::TRANSPARENCY_FULLY_TRANSPARENT_MODE - } - ANCModes::TransparencyVocalMode => ANCProfile::TRANSPARENCY_VOCAL_MODE, - } -} - - -pub(crate) fn anc_profile_to_mode(profile: ANCProfile) -> ANCModes { - match profile { - ANCProfile{ option: 0, anc_option: 0, .. } => ANCModes::AncTransportMode, - ANCProfile{ option: 0, anc_option: 1, .. } => ANCModes::AncOutdoorMode, - ANCProfile{ option: 0, anc_option: 2, .. } => ANCModes::AncIndoorMode, - ANCProfile{ option: 0, anc_option: 3, .. } => ANCModes::AncCustomValue(profile.anc_custom), - ANCProfile{ option: 1, transparency_option: 0, .. } => ANCModes::TransparencyFullyTransparentMode, - ANCProfile{ option: 1, transparency_option: 1, .. } => ANCModes::TransparencyVocalMode, - ANCProfile{ option: 2, .. } => ANCModes::NormalMode, - _ => unreachable!(), - } -} diff --git a/manager-ui/src/types/soundcore-lib.d.ts b/manager-ui/src/types/soundcore-lib.d.ts index 20b02fb..20a18ea 100644 --- a/manager-ui/src/types/soundcore-lib.d.ts +++ b/manager-ui/src/types/soundcore-lib.d.ts @@ -37,6 +37,23 @@ export type AgeRange = number; export type AmbientSoundNotice = boolean; +export interface EqualizerFeatures { + bands: number; + channels: number; +} + +export interface SoundModeFeatures { + allowedAncModes: ANCMode[]; + allowedTransparencyModes: TransparencyMode[]; + hasNormalMode: boolean; +} + +export interface DeviceFeatureSet { + soundModeFeatures?: SoundModeFeatures; + equalizerFeatures?: EqualizerFeatures; + flags: FeatureFlags[]; +} + export type Battery = | { type: 'single'; value: SingleBattery } | { type: 'dual'; value: DualBattery }; @@ -84,12 +101,11 @@ export type HearID = { type: 'BASE'; value: BaseHearID } | { type: 'CUSTOM'; val /** This is a generalized version of the state for all devices */ export interface SoundcoreDeviceState { - featureFlags: BitFlags; + featureSet: DeviceFeatureSet; battery: Battery; soundMode: SoundMode; serial?: SerialNumber; fw?: FirmwareVer; - drcFw?: FirmwareVer; hostDevice?: number; twsStatus?: TwsStatus; buttonModel?: ButtonModel; @@ -220,6 +236,23 @@ export interface DeviceStatus { right_hearid_customdata: EQWave; } +export enum FeatureFlags { + DRC = 'DRC', + HEARID = 'HEARID', + WEAR_DETECTION = 'WEAR_DETECTION', + CUSTOM_BUTTONS = 'CUSTOM_BUTTONS', + TOUCH_TONE = 'TOUCH_TONE', + GAME_MODE = 'GAME_MODE', + AUTO_POWER_OFF_ON = 'AUTO_POWER_OFF_ON', + IN_EAR_BEEP = 'IN_EAR_BEEP', + LANG_PROMPT = 'LANG_PROMPT', + HEARING_PROTECTION = 'HEARING_PROTECTION', + AMBIENT_SOUND_NOTICE = 'AMBIENT_SOUND_NOTICE', + POWER_ON_BATTERY_NOTICE = 'POWER_ON_BATTERY_NOTICE', + SUPPORT_TWO_CONNECTIONS = 'SUPPORT_TWO_CONNECTIONS', + MULTIPLE_DEVICE_LIST = 'MULTIPLE_DEVICE_LIST' +} + export type BLEAdapterEvent = | { kind: 'deviceConnected'; value: BluetoothAdrr } | { kind: 'deviceDisconnected'; value: BluetoothAdrr }; diff --git a/manager-ui/src/types/tauri-backend.d.ts b/manager-ui/src/types/tauri-backend.d.ts index 0c2e55c..b6f4d64 100644 --- a/manager-ui/src/types/tauri-backend.d.ts +++ b/manager-ui/src/types/tauri-backend.d.ts @@ -12,42 +12,6 @@ export interface ConnectionFailedResponse { reason: string; } -export interface BthScanResult { - name: string; - address: string; - is_connected: boolean; -} - -export type ANCModes = - | { mode: 'NormalMode'; value?: undefined } - | { mode: 'AncTransportMode'; value?: undefined } - | { mode: 'AncOutdoorMode'; value?: undefined } - | { mode: 'AncIndoorMode'; value?: undefined } - | { mode: 'AncCustomValue'; value: number } - | { mode: 'TransparencyFullyTransparentMode'; value?: undefined } - | { mode: 'TransparencyVocalMode'; value?: undefined }; - -export interface NewTrayDeviceStatus { - is_connected: boolean; - charging: BatteryCharging; - level: BatteryLevel; - anc_mode: ANCModes; -} - -export enum SupportedANCProfiles { - Normal = 'Normal', - AncTransportMode = 'AncTransportMode', - AncOutdoorMode = 'AncOutdoorMode', - AncIndoorMode = 'AncIndoorMode', - AncCustomValue = 'AncCustomValue', - TransparencyFullyTransparentMode = 'TransparencyFullyTransparentMode', - TransparencyVocalMode = 'TransparencyVocalMode' -} - -export interface DeviceFeatures { - profiles: SupportedANCProfiles[]; -} - export type BridgeCommand = | { command: 'scan'; payload?: undefined } | { command: 'connect'; payload: DiscoveredDevice } diff --git a/soundcore-lib/examples/basic.rs b/soundcore-lib/examples/basic.rs deleted file mode 100644 index 19a38f7..0000000 --- a/soundcore-lib/examples/basic.rs +++ /dev/null @@ -1,15 +0,0 @@ -use bluetooth_lib::BluetoothAdrr; -use soundcore_lib::{base::SoundcoreDevice, devices::A3951}; - -const BT_ADDR: &str = "E8:EE:CC:56:C3:EA"; - -#[tokio::main] -async fn main() { - let addr = BluetoothAdrr::from(BT_ADDR); - let dev = A3951::default().init(addr).await.unwrap(); - let info = dev.get_info().await.unwrap(); - println!("Device info: {:?}", info); - // println!("Device status: {:?}", info); - // let charging = dev.get_battery_charging().await.unwrap(); - // println!("Charging: {:?}", charging); -} diff --git a/soundcore-lib/src/api.rs b/soundcore-lib/src/api.rs index 06b8a47..bb08533 100644 --- a/soundcore-lib/src/api.rs +++ b/soundcore-lib/src/api.rs @@ -1,3 +1,5 @@ mod state; +mod feature_set; pub use state::*; +pub use feature_set::*; \ No newline at end of file diff --git a/soundcore-lib/src/api/feature_set.rs b/soundcore-lib/src/api/feature_set.rs new file mode 100644 index 0000000..47c6575 --- /dev/null +++ b/soundcore-lib/src/api/feature_set.rs @@ -0,0 +1,31 @@ +use std::sync::Arc; + +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +mod flags; +mod sound_mode_features; +mod equalizer_features; + +pub use flags::*; +pub use sound_mode_features::*; +pub use equalizer_features::*; + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[typeshare] +#[serde(rename_all = "camelCase")] +pub struct DeviceFeatureSet { + pub sound_mode_features: Option, + pub equalizer_features: Option, + pub flags: Arc<[FeatureFlags]>, +} + +impl Default for DeviceFeatureSet { + fn default() -> Self { + Self { + sound_mode_features: None, + equalizer_features: None, + flags: Arc::new([]), + } + } +} \ No newline at end of file diff --git a/soundcore-lib/src/api/feature_set/equalizer_features.rs b/soundcore-lib/src/api/feature_set/equalizer_features.rs new file mode 100644 index 0000000..3cd6e32 --- /dev/null +++ b/soundcore-lib/src/api/feature_set/equalizer_features.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +#[derive( + Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash, +)] +#[typeshare] +pub struct EqualizerFeatures { + pub bands: u8, + pub channels: u8, +} \ No newline at end of file diff --git a/soundcore-lib/src/api/feature_set/flags.rs b/soundcore-lib/src/api/feature_set/flags.rs new file mode 100644 index 0000000..f80c7c0 --- /dev/null +++ b/soundcore-lib/src/api/feature_set/flags.rs @@ -0,0 +1,26 @@ +#![allow(non_camel_case_types)] +use serde::{Deserialize, Serialize}; +use strum::EnumIter; +use typeshare::typeshare; + +#[derive( + Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash, EnumIter, +)] +#[typeshare] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum FeatureFlags { + DRC, + HEARID, + WEAR_DETECTION, + CUSTOM_BUTTONS, + TOUCH_TONE, + GAME_MODE, + AUTO_POWER_OFF_ON, + IN_EAR_BEEP, + LANG_PROMPT, + HEARING_PROTECTION, + AMBIENT_SOUND_NOTICE, + POWER_ON_BATTERY_NOTICE, + SUPPORT_TWO_CONNECTIONS, + MULTIPLE_DEVICE_LIST, +} diff --git a/soundcore-lib/src/api/feature_set/sound_mode_features.rs b/soundcore-lib/src/api/feature_set/sound_mode_features.rs new file mode 100644 index 0000000..27bb598 --- /dev/null +++ b/soundcore-lib/src/api/feature_set/sound_mode_features.rs @@ -0,0 +1,250 @@ +use std::sync::Arc; + +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +use crate::models::{ + ANCMode, AdaptiveANCMode, CustomizableTransparencyMode, NonCustomizableTransparencyMode, + SceneBasedANCMode, TransparencyMode, +}; + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[typeshare] +#[serde(rename_all = "camelCase")] +pub struct SoundModeFeatures { + allowed_anc_modes: Arc<[ANCMode]>, + allowed_transparency_modes: Arc<[TransparencyMode]>, + has_normal_mode: bool, +} + +impl SoundModeFeatures { + const ANC_MODES_WITH_CUSTOM_VALUE: [ANCMode; 2] = [ + ANCMode::Adaptive(AdaptiveANCMode::Custom), + ANCMode::SceneBased(SceneBasedANCMode::Custom), + ]; + + const TRANS_MODES_WITH_CUSTOM_VALUE: [TransparencyMode; 1] = [TransparencyMode::Customizable( + CustomizableTransparencyMode::Custom, + )]; + + pub fn new( + anc_modes: &[ANCMode], + transparency_modes: &[TransparencyMode], + normal_mode: bool, + ) -> Self { + Self { + allowed_anc_modes: anc_modes.into(), + allowed_transparency_modes: transparency_modes.into(), + has_normal_mode: normal_mode, + } + } + + pub fn adaptive_customizable_anc_customizable_transparency() -> SoundModeFeatures { + SoundModeFeatures { + allowed_anc_modes: [ + ANCMode::Adaptive(AdaptiveANCMode::Custom), + ANCMode::Adaptive(AdaptiveANCMode::Adaptive), + ] + .into(), + allowed_transparency_modes: [ + TransparencyMode::Customizable(CustomizableTransparencyMode::Custom), + TransparencyMode::Customizable(CustomizableTransparencyMode::TalkMode), + ] + .into(), + has_normal_mode: true, + } + } + + pub fn scene_based_customizable_anc_non_customizable_transparency() -> SoundModeFeatures { + SoundModeFeatures { + allowed_anc_modes: [ + ANCMode::SceneBased(SceneBasedANCMode::Custom), + ANCMode::SceneBased(SceneBasedANCMode::Indoor), + ANCMode::SceneBased(SceneBasedANCMode::Outdoor), + ANCMode::SceneBased(SceneBasedANCMode::Transport), + ] + .into(), + allowed_transparency_modes: [ + TransparencyMode::NonCustomizable( + NonCustomizableTransparencyMode::FullyTransparent, + ), + TransparencyMode::NonCustomizable(NonCustomizableTransparencyMode::Vocal), + ] + .into(), + has_normal_mode: true, + } + } + + pub fn scene_based_non_customizable_anc_non_customizable_transparency() -> SoundModeFeatures { + SoundModeFeatures { + allowed_anc_modes: [ + ANCMode::SceneBased(SceneBasedANCMode::Indoor), + ANCMode::SceneBased(SceneBasedANCMode::Outdoor), + ANCMode::SceneBased(SceneBasedANCMode::Transport), + ] + .into(), + allowed_transparency_modes: [ + TransparencyMode::NonCustomizable( + NonCustomizableTransparencyMode::FullyTransparent, + ), + TransparencyMode::NonCustomizable(NonCustomizableTransparencyMode::Vocal), + ] + .into(), + has_normal_mode: true, + } + } + + pub fn has_normal_mode(&self) -> bool { + self.has_normal_mode + } + + pub fn allowed_anc_modes(&self) -> &[ANCMode] { + &self.allowed_anc_modes + } + pub fn allowed_transparency_modes(&self) -> &[TransparencyMode] { + &self.allowed_transparency_modes + } + + pub fn has_customizable_transparency(&self) -> Option { + self.allowed_transparency_modes + .iter() + .find(|mode| Self::TRANS_MODES_WITH_CUSTOM_VALUE.contains(mode)) + .cloned() + } + + pub fn has_customizable_anc(&self) -> Option { + self.allowed_anc_modes + .iter() + .find(|mode| Self::ANC_MODES_WITH_CUSTOM_VALUE.contains(mode)) + .cloned() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn should_return_true_when_has_normal_mode() { + let sound_mode_features = SoundModeFeatures::new( + &[ANCMode::Adaptive(AdaptiveANCMode::Adaptive)], + &[TransparencyMode::NonCustomizable( + NonCustomizableTransparencyMode::FullyTransparent, + )], + true, + ); + + assert!(sound_mode_features.has_normal_mode()); + } + + #[test] + fn should_return_false_when_does_not_have_normal_mode() { + let sound_mode_features = SoundModeFeatures::new( + &[ANCMode::Adaptive(AdaptiveANCMode::Adaptive)], + &[TransparencyMode::NonCustomizable( + NonCustomizableTransparencyMode::FullyTransparent, + )], + false, + ); + + assert!(!sound_mode_features.has_normal_mode()); + } + + #[test] + fn should_return_allowed_anc_modes() { + let sound_mode_features = SoundModeFeatures::new( + &[ANCMode::Adaptive(AdaptiveANCMode::Adaptive)], + &[TransparencyMode::NonCustomizable( + NonCustomizableTransparencyMode::FullyTransparent, + )], + true, + ); + + assert_eq!( + sound_mode_features.allowed_anc_modes(), + &vec![ANCMode::Adaptive(AdaptiveANCMode::Adaptive)] + ); + } + + #[test] + fn should_return_allowed_transparency_modes() { + let sound_mode_features = SoundModeFeatures::new( + &[ANCMode::Adaptive(AdaptiveANCMode::Adaptive)], + &[TransparencyMode::NonCustomizable( + NonCustomizableTransparencyMode::FullyTransparent, + )], + true, + ); + + assert_eq!( + sound_mode_features.allowed_transparency_modes(), + &vec![TransparencyMode::NonCustomizable( + NonCustomizableTransparencyMode::FullyTransparent + )] + ); + } + + #[test] + fn should_return_customizable_transparency_mode() { + let sound_mode_features = SoundModeFeatures::new( + &[ANCMode::Adaptive(AdaptiveANCMode::Adaptive)], + &[ + TransparencyMode::Customizable(CustomizableTransparencyMode::TalkMode), + TransparencyMode::Customizable(CustomizableTransparencyMode::Custom), + ], + true, + ); + + assert_eq!( + sound_mode_features.has_customizable_transparency(), + Some(TransparencyMode::Customizable( + CustomizableTransparencyMode::Custom + )) + ); + } + + #[test] + fn should_return_none_when_does_not_have_customizable_transparency_mode() { + let sound_mode_features = SoundModeFeatures::new( + &[ANCMode::Adaptive(AdaptiveANCMode::Adaptive)], + &[TransparencyMode::NonCustomizable( + NonCustomizableTransparencyMode::FullyTransparent, + )], + true, + ); + + assert_eq!(sound_mode_features.has_customizable_transparency(), None); + } + + #[test] + fn should_return_customizable_anc_mode() { + let sound_mode_features = SoundModeFeatures::new( + &[ + ANCMode::Adaptive(AdaptiveANCMode::Adaptive), + ANCMode::Adaptive(AdaptiveANCMode::Custom), + ], + &[TransparencyMode::NonCustomizable( + NonCustomizableTransparencyMode::FullyTransparent, + )], + true, + ); + + assert_eq!( + sound_mode_features.has_customizable_anc(), + Some(ANCMode::Adaptive(AdaptiveANCMode::Custom)) + ); + } + + #[test] + fn should_return_none_when_does_not_have_customizable_anc_mode() { + let sound_mode_features = SoundModeFeatures::new( + &[ANCMode::Adaptive(AdaptiveANCMode::Adaptive)], + &[TransparencyMode::NonCustomizable( + NonCustomizableTransparencyMode::FullyTransparent, + )], + true, + ); + + assert_eq!(sound_mode_features.has_customizable_anc(), None); + } +} diff --git a/soundcore-lib/src/api/state/state.rs b/soundcore-lib/src/api/state/state.rs index 8ecff6c..8e41c7c 100644 --- a/soundcore-lib/src/api/state/state.rs +++ b/soundcore-lib/src/api/state/state.rs @@ -2,19 +2,18 @@ use enumflags2::BitFlags; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::models::{AgeRange, Battery, ButtonModel, CustomHearID, FirmwareVer, HearID, SerialNumber, SideTone, SoundcoreFeatureFlags, SoundMode, TwsStatus, WearDetection}; +use crate::{api::DeviceFeatureSet, models::{AgeRange, Battery, ButtonModel, CustomHearID, FirmwareVer, HearID, SerialNumber, SideTone, SoundMode, SoundcoreFeatureFlags, TwsStatus, WearDetection}}; /// This is a generalized version of the state for all devices #[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Hash, Default)] #[serde(rename_all = "camelCase")] #[typeshare] pub struct SoundcoreDeviceState { - pub feature_flags: BitFlags, + pub feature_set: DeviceFeatureSet, pub battery: Battery, pub sound_mode: SoundMode, pub serial: Option, pub fw: Option, - pub drc_fw: Option, pub host_device: Option, pub tws_status: Option, pub button_model: Option, @@ -24,3 +23,4 @@ pub struct SoundcoreDeviceState { pub hear_id: Option, pub age_range: Option, } + diff --git a/soundcore-lib/src/devices.rs b/soundcore-lib/src/devices.rs new file mode 100644 index 0000000..cf2f116 --- /dev/null +++ b/soundcore-lib/src/devices.rs @@ -0,0 +1,13 @@ +mod a3027; +mod a3028; +mod a3029; +mod a3040; +mod a3930; +mod a3951; + +pub use a3027::*; +pub use a3028::*; +pub use a3029::*; +pub use a3040::*; +pub use a3930::*; +pub use a3951::*; diff --git a/soundcore-lib/src/devices/A3027Device.rs b/soundcore-lib/src/devices/A3027Device.rs deleted file mode 100644 index 5f8c29b..0000000 --- a/soundcore-lib/src/devices/A3027Device.rs +++ /dev/null @@ -1,253 +0,0 @@ -use async_trait::async_trait; -use bluetooth_lib::{platform::RFCOMM, BluetoothAdrr, RFCOMMClient}; -use tokio::time::sleep; - -use crate::{ - base::{SoundcoreANC, SoundcoreDevice, SoundcoreEQ, SoundcoreHearID, SoundcoreLDAC}, - error::SoundcoreLibError, - statics::*, - types::{ - ANCProfile, BatteryCharging, BatteryLevel, DeviceInfo, DeviceStatus, EQWave, EQWaveInt, - ResponseDecoder, - }, - utils::{ - build_command_array_with_options_toggle_enabled, i8_to_u8vec, remove_padding, verify_resp, - Clamp, - }, -}; -use std::time::Duration; - -static SLEEP_DURATION: Duration = std::time::Duration::from_millis(30); - -#[derive(Default)] -pub struct A3027 { - _btaddr: Option, - rfcomm: Option, -} - -#[async_trait] -impl SoundcoreDevice for A3027 { - async fn init( - &self, - btaddr: BluetoothAdrr, - ) -> Result, SoundcoreLibError> { - let mut rfcomm = RFCOMM::new().await?; - rfcomm - .connect_uuid(btaddr.clone(), A3951_RFCOMM_UUID) - .await?; - Ok(Box::new(A3027 { - _btaddr: Some(btaddr), - rfcomm: Some(rfcomm), - })) - } - - async fn close(&self) -> Result<(), SoundcoreLibError> { - match &self.rfcomm { - Some(rfcomm) => { - rfcomm.close().await; - Ok(()) - } - None => Err(SoundcoreLibError::NotConnected), - } - } - - async fn send(&self, data: &[u8]) -> Result<(), SoundcoreLibError> { - match &self.rfcomm { - Some(rfcomm) => { - rfcomm.send(data).await?; - Ok(()) - } - None => Err(SoundcoreLibError::NotConnected), - } - } - async fn recv(&self) -> Result, SoundcoreLibError> { - match &self.rfcomm { - Some(rfcomm) => Ok(remove_padding(rfcomm.recv(300).await?.as_slice())), - None => Err(SoundcoreLibError::BthError { - source: bluetooth_lib::error::BthError::InvalidSocketError, - }), - } - } - - async fn build_and_send_cmd( - &self, - cmd: [i8; 7], - data: Option<&[u8]>, - ) -> Result<(), SoundcoreLibError> { - let to_send = build_command_array_with_options_toggle_enabled(&i8_to_u8vec(&cmd), data); - let _ = &self.send(&to_send).await?; - sleep(SLEEP_DURATION).await; - Ok(()) - } - - async fn get_status(&self) -> Result { - self.build_and_send_cmd(A3951_CMD_DEVICE_STATUS, None) - .await?; - let resp = self.recv().await?; - if A3027_RESPONSE_VERIFICATION { - verify_resp(&resp)?; - } - Ok(Self::decode(self, &resp)?) - } - - async fn get_info(&self) -> Result { - self.build_and_send_cmd(A3951_CMD_DEVICE_INFO, None).await?; - let resp = self.recv().await?; - if A3027_RESPONSE_VERIFICATION { - verify_resp(&resp)?; - } - Ok(Self::decode(self, &resp)?) - } - async fn get_battery_level(&self) -> Result { - Ok(self.get_status().await?.battery_level) - } - - async fn get_battery_charging(&self) -> Result { - Ok(self.get_status().await?.battery_charging) - } -} - -#[async_trait] -impl SoundcoreANC for A3027 { - async fn set_anc(&self, profile: ANCProfile) -> Result<(), crate::error::SoundcoreLibError> { - self.build_and_send_cmd(A3951_CMD_DEVICE_SETANC, Some(&profile.to_bytes())) - .await?; - let _resp = self.recv().await?; /* No response validation - Need more info */ - Ok(()) - } - - async fn get_anc(&self) -> Result { - self.build_and_send_cmd(A3951_CMD_DEVICE_GETANC, None) - .await?; - let resp = self.recv().await?; - if A3027_RESPONSE_VERIFICATION { - verify_resp(&resp)?; - } - Ok(ANCProfile::decode(&resp[9..13])?) - } -} - -#[async_trait] -impl SoundcoreEQ for A3027 { - async fn set_eq(&self, wave: EQWave) -> Result<(), SoundcoreLibError> { - /* Original Java method name: SendEQ_NoDrc_Not_A3951_A3930 */ - let mut wave_out = vec![0; 10]; - let eq_index: i32 = 65278; /* Custom EQ Index */ - let eq_wave = EQWaveInt::from_eq_wave(wave).to_8bytes(); - wave_out[0] = eq_index as u8; - wave_out[1] = (eq_index >> 8) as u8; - wave_out[2..10].copy_from_slice(&eq_wave); - - /* A3027 Doesn't appear to be using DRC */ - self.build_and_send_cmd(A3027_CMD_DEVICE_SETEQ, Some(&wave_out)) - .await?; - let _resp = self.recv().await?; - Ok(()) - } - - async fn get_eq(&self) -> Result { - Ok(self.get_status().await?.left_eq) /* Return both left and right? */ - } -} - -#[async_trait] -impl SoundcoreLDAC for A3027 { - async fn get_ldac(&self) -> Result { - self.build_and_send_cmd(A3951_CMD_DEVICE_GETLDAC, None) - .await?; - let resp = self.recv().await?; - if A3027_RESPONSE_VERIFICATION { - verify_resp(&resp)?; - } - Ok(resp[9] == 1) - } - - async fn set_ldac(&self, _enabled: bool) -> Result<(), SoundcoreLibError> { - unimplemented!() - } -} -impl SoundcoreHearID for A3027 {} - -impl ResponseDecoder for A3027 { - fn decode(&self, arr: &[u8]) -> Result { - if arr.len() < 35 { - return Err(SoundcoreLibError::InvalidResponseLength { - expected: 35, - got: arr.len(), - data: arr.to_vec(), - }); - } - Ok(DeviceInfo { - left_fw: String::from_utf8(arr[9..14].to_vec())?, - right_fw: String::from_utf8(arr[14..19].to_vec())?, - sn: String::from_utf8(arr[19..35].to_vec())?, - }) - } -} - -impl ResponseDecoder for A3027 { - fn decode(&self, arr: &[u8]) -> Result { - if arr.len() < 70 { - return Err(SoundcoreLibError::InvalidResponseLength { - expected: 70, - got: arr.len(), - data: arr.to_vec(), - }); - } - - let charge_arr = vec![arr[10], arr[10]]; - let level_arr = vec![arr[9], arr[9]]; - - Ok(DeviceStatus { - host_device: arr[9], - tws_status: arr[10] == 1, - battery_level: Self::decode(self, &level_arr)?, - battery_charging: Self::decode(self, &charge_arr)?, - left_eq: EQWave::decode(&arr[13..21])?, - right_eq: EQWave::decode(&arr[13..21])?, - hearid_enabled: arr[23] == 1, - left_hearid: EQWave::decode(&arr[24..32])?, - right_hearid: EQWave::decode(&arr[32..40])?, - left_hearid_customdata: EQWave::default(), - right_hearid_customdata: EQWave::default(), - anc_status: ANCProfile::decode(&arr[44..48])?, - side_tone_enabled: false, - wear_detection_enabled: arr[69] == 1, - touch_tone_enabled: false, - }) - } -} - -impl ResponseDecoder for A3027 { - fn decode(&self, arr: &[u8]) -> Result { - if arr.len() < 2 { - return Err(SoundcoreLibError::InvalidResponseLength { - expected: 2, - got: arr.len(), - data: arr.to_vec(), - }); - } - - Ok(BatteryLevel { - left: Clamp::clamp(arr[0], 0, 5), - right: Clamp::clamp(arr[1], 0, 5), - }) - } -} - -impl ResponseDecoder for A3027 { - fn decode(&self, arr: &[u8]) -> Result { - if arr.len() < 2 { - return Err(SoundcoreLibError::InvalidResponseLength { - expected: 2, - got: arr.len(), - data: arr.to_vec(), - }); - } - - Ok(BatteryCharging { - left: arr[0] == 1, - right: arr[1] == 1, - }) - } -} diff --git a/soundcore-lib/src/devices/A3040Device.rs b/soundcore-lib/src/devices/A3040Device.rs deleted file mode 100644 index c863f33..0000000 --- a/soundcore-lib/src/devices/A3040Device.rs +++ /dev/null @@ -1,278 +0,0 @@ -use std::{slice::from_ref, time::Duration}; - -use crate::{ - base::{SoundcoreANC, SoundcoreDevice, SoundcoreEQ, SoundcoreHearID, SoundcoreLDAC}, - error::SoundcoreLibError, - statics::{ - A3040_CMD_DEVICE_BATTERYLEVEL, A3040_CMD_DEVICE_CHARGINSTATUS, A3040_CMD_DEVICE_INFO, - A3040_CMD_DEVICE_SETCUSTOMEQ, A3040_CMD_DEVICE_SETLDAC, A3040_RESPONSE_VERIFICATION, - A3040_RFCOMM_UUID, EQ_INDEX_CUSTOM, - }, - types::{ - ANCProfile, BatteryCharging, BatteryLevel, DeviceInfo, DeviceStatus, EQWave, EQWaveInt, - ResponseDecoder, - }, - utils::{build_command_with_options, i8_to_u8vec, remove_padding, verify_resp, Clamp}, -}; -use async_trait::async_trait; -use bluetooth_lib::{platform::RFCOMM, BluetoothAdrr, RFCOMMClient}; -use tokio::time::sleep; - -static SLEEP_DURATION: Duration = std::time::Duration::from_millis(30); - -#[derive(Default)] -pub struct A3040 { - _btaddr: Option, - rfcomm: Option, -} - -#[async_trait] -impl SoundcoreDevice for A3040 { - async fn init( - &self, - btaddr: BluetoothAdrr, - ) -> Result, SoundcoreLibError> { - let mut rfcomm = RFCOMM::new().await?; - rfcomm - .connect_uuid(btaddr.clone(), A3040_RFCOMM_UUID) - .await?; - Ok(Box::new(A3040 { - _btaddr: Some(btaddr), - rfcomm: Some(rfcomm), - })) - } - async fn close(&self) -> Result<(), SoundcoreLibError> { - match &self.rfcomm { - Some(rfcomm) => { - rfcomm.close().await; - Ok(()) - } - None => Err(SoundcoreLibError::NotConnected), - } - } - - async fn send(&self, data: &[u8]) -> Result<(), SoundcoreLibError> { - match &self.rfcomm { - Some(rfcomm) => { - rfcomm.send(data).await?; - Ok(()) - } - None => Err(SoundcoreLibError::NotConnected), - } - } - async fn recv(&self) -> Result, SoundcoreLibError> { - match &self.rfcomm { - Some(rfcomm) => Ok(remove_padding(rfcomm.recv(1000).await?.as_slice())), - None => Err(SoundcoreLibError::BthError { - source: bluetooth_lib::error::BthError::InvalidSocketError, - }), - } - } - - async fn build_and_send_cmd( - &self, - cmd: [i8; 7], - data: Option<&[u8]>, - ) -> Result<(), SoundcoreLibError> { - let to_send = build_command_with_options(&i8_to_u8vec(&cmd), data); - let _ = &self.send(&to_send).await?; - sleep(SLEEP_DURATION).await; - Ok(()) - } - - /* DeviceInfo and Status is the same command and response */ - async fn get_status(&self) -> Result { - self.build_and_send_cmd(A3040_CMD_DEVICE_INFO, None).await?; - let resp = self.recv().await?; - if A3040_RESPONSE_VERIFICATION { - verify_resp(&resp)? - } - Ok(Self::decode(self, &resp)?) - } - - async fn get_info(&self) -> Result { - self.build_and_send_cmd(A3040_CMD_DEVICE_INFO, None).await?; - let resp = self.recv().await?; - if A3040_RESPONSE_VERIFICATION { - verify_resp(&resp)? - } - Ok(Self::decode(self, &resp)?) - } - - async fn get_battery_level(&self) -> Result { - self.build_and_send_cmd(A3040_CMD_DEVICE_BATTERYLEVEL, None) - .await?; - let resp = self.recv().await?; - if A3040_RESPONSE_VERIFICATION { - verify_resp(&resp)? - } - Ok(Self::decode(self, &resp)?) - } - - async fn get_battery_charging(&self) -> Result { - self.build_and_send_cmd(A3040_CMD_DEVICE_CHARGINSTATUS, None) - .await?; - let resp = self.recv().await?; - if A3040_RESPONSE_VERIFICATION { - verify_resp(&resp)? - } - Ok(Self::decode(self, &resp)?) - } -} - -impl ResponseDecoder for A3040 { - fn decode(&self, arr: &[u8]) -> Result { - if arr.len() < 85 { - return Err(SoundcoreLibError::InvalidResponseLength { - expected: 2, - got: arr.len(), - data: arr.to_vec(), - }); - } - - Ok(DeviceInfo { - left_fw: String::from_utf8(arr[11..16].to_vec())?, - right_fw: String::from_utf8(arr[11..16].to_vec())?, - sn: String::from_utf8(arr[16..32].to_vec())?, - }) - } -} -impl ResponseDecoder for A3040 { - fn decode(&self, arr: &[u8]) -> Result { - if arr.len() < 85 { - return Err(SoundcoreLibError::InvalidResponseLength { - expected: 2, - got: arr.len(), - data: arr.to_vec(), - }); - } - - let base = arr[54] as usize; - let anc_current_mode_idx = (base + 54) - 1; - let anc_model_idx = anc_current_mode_idx + 1; - let touch_tone_idx = anc_model_idx + 6; - let wear_detection_idx = touch_tone_idx + 1; - /* The rest of the indexes are on JADX->A3040AnalysisService */ - - Ok(DeviceStatus { - host_device: 0, // Not available - tws_status: true, - battery_level: Self::decode(self, from_ref(&arr[9]))?, - battery_charging: Self::decode(self, from_ref(&arr[10]))?, - anc_status: Self::decode(self, from_ref(&arr[anc_current_mode_idx]))?, - side_tone_enabled: false, - wear_detection_enabled: arr[wear_detection_idx] == 1, - touch_tone_enabled: arr[touch_tone_idx] == 1, - left_eq: Self::decode(self, &arr[34..44])?, - right_eq: Self::decode(self, &arr[34..44])?, - hearid_enabled: false, // Doesn't seem to be supported - left_hearid: EQWave::default(), - right_hearid: EQWave::default(), - left_hearid_customdata: EQWave::default(), - right_hearid_customdata: EQWave::default(), - }) - } -} - -impl ResponseDecoder for A3040 { - fn decode(&self, arr: &[u8]) -> Result { - Ok(BatteryLevel { - left: Clamp::clamp(arr[9], 0, 5), - right: Clamp::clamp(arr[9], 0, 5), - }) - } -} - -impl ResponseDecoder for A3040 { - /* Might need to use DeviceInfo cmd and charging_case_idx */ - fn decode(&self, arr: &[u8]) -> Result { - Ok(BatteryCharging { - left: arr[9] == 1, - right: arr[10] == 1, - }) - } -} - -impl ResponseDecoder for A3040 { - fn decode(&self, arr: &[u8]) -> Result { - match arr.first() { - Some(&byte) if get_nth_bit_value(byte, 1) == 1 => Ok(ANCProfile::ANC_OUTDOOR_MODE), - Some(&byte) if get_nth_bit_value(byte, 2) == 1 => { - Ok(ANCProfile::TRANSPARENCY_FULLY_TRANSPARENT_MODE) - } - Some(&byte) if get_nth_bit_value(byte, 3) == 1 => Ok(ANCProfile::NORMAL_MODE), - _ => Err(SoundcoreLibError::InvalidResponse), - } - } -} - -impl ResponseDecoder for A3040 { - fn decode(&self, arr: &[u8]) -> Result { - Ok(EQWave { - pos0: Clamp::clamp(arr[0], 60, 180) as f32 / 10.0, - pos1: Clamp::clamp(arr[1], 60, 180) as f32 / 10.0, - pos2: Clamp::clamp(arr[2], 60, 180) as f32 / 10.0, - pos3: Clamp::clamp(arr[3], 60, 180) as f32 / 10.0, - pos4: Clamp::clamp(arr[4], 60, 180) as f32 / 10.0, - pos5: Clamp::clamp(arr[5], 60, 180) as f32 / 10.0, - pos6: Clamp::clamp(arr[6], 60, 180) as f32 / 10.0, - pos7: Clamp::clamp(arr[7], 60, 180) as f32 / 10.0, - pos8: Clamp::clamp(arr[8], 60, 180) as f32 / 10.0, - pos9: Clamp::clamp(arr[9], 60, 180) as f32 / 10.0, - }) - } -} - -#[async_trait] -impl SoundcoreANC for A3040 { - async fn get_anc(&self) -> Result { - Ok(self.get_status().await?.anc_status) - } - - /* set_anc needs a litle more investigation - maybe some wireshark captures? */ -} - -#[async_trait] -impl SoundcoreLDAC for A3040 { - async fn set_ldac(&self, toggle: bool) -> Result<(), SoundcoreLibError> { - self.build_and_send_cmd(A3040_CMD_DEVICE_SETLDAC, Some(&[toggle as u8])) - .await?; - let _resp = self.recv().await?; - Ok(()) - } - - async fn get_ldac(&self) -> Result { - self.build_and_send_cmd(A3040_CMD_DEVICE_INFO, None).await?; - let resp = self.recv().await?; - let base_idx = resp[54] as usize; - let ldac_idx = base_idx + 66; - Ok(resp[ldac_idx] == 1) - } -} - -#[async_trait] -impl SoundcoreEQ for A3040 { - async fn set_eq(&self, wave: EQWave) -> Result<(), SoundcoreLibError> { - let int_wave = EQWaveInt::from(wave).to_dualch_bytes(); - let mut out_wave = vec![0; 22]; - out_wave[0] = (EQ_INDEX_CUSTOM & 255) as u8; - out_wave[1] = (EQ_INDEX_CUSTOM >> 8) as u8; - // Copy the EQWaveInt bytes into the out_wave - out_wave[2..].copy_from_slice(&int_wave); - self.build_and_send_cmd(A3040_CMD_DEVICE_SETCUSTOMEQ, Some(&out_wave)) - .await?; - let _resp = self.recv().await?; - Ok(()) - } - - async fn get_eq(&self) -> Result { - Ok(self.get_status().await?.left_eq) - } -} - -impl SoundcoreHearID for A3040 {} - -fn get_nth_bit_value(b: u8, n: u8) -> u8 { - // shift the byte n-1 bits to the right and bitwise AND it with 1 to get the nth bit value - (b >> (n - 1)) & 1 -} diff --git a/soundcore-lib/src/devices/A3935Device.rs b/soundcore-lib/src/devices/A3935Device.rs deleted file mode 100644 index 6d574c5..0000000 --- a/soundcore-lib/src/devices/A3935Device.rs +++ /dev/null @@ -1,247 +0,0 @@ -use async_trait::async_trait; -use bluetooth_lib::{platform::RFCOMM, BluetoothAdrr, RFCOMMClient}; - -use tokio::time::sleep; - -use crate::{ - base::{SoundcoreANC, SoundcoreDevice, SoundcoreEQ, SoundcoreHearID, SoundcoreLDAC}, - error::SoundcoreLibError, - statics::*, - types::{ - ANCProfile, BatteryCharging, BatteryLevel, DeviceInfo, DeviceStatus, EQWave, EQWaveInt, - ResponseDecoder, - }, - utils::{ - build_command_array_with_options_toggle_enabled, i8_to_u8vec, remove_padding, verify_resp, - Clamp, - }, -}; -use std::time::Duration; - -static SLEEP_DURATION: Duration = std::time::Duration::from_millis(30); - -#[derive(Default)] -pub struct A3935 { - _btaddr: Option, - rfcomm: Option, -} - -#[async_trait] -impl SoundcoreDevice for A3935 { - async fn init( - &self, - btaddr: BluetoothAdrr, - ) -> Result, SoundcoreLibError> { - let mut rfcomm = RFCOMM::new().await?; - rfcomm - .connect_uuid(btaddr.clone(), A3951_RFCOMM_UUID) - .await?; - Ok(Box::new(A3935 { - _btaddr: Some(btaddr), - rfcomm: Some(rfcomm), - })) - } - - async fn close(&self) -> Result<(), SoundcoreLibError> { - match &self.rfcomm { - Some(rfcomm) => { - rfcomm.close().await; - Ok(()) - } - None => Err(SoundcoreLibError::NotConnected), - } - } - - async fn send(&self, data: &[u8]) -> Result<(), SoundcoreLibError> { - match &self.rfcomm { - Some(rfcomm) => { - rfcomm.send(data).await?; - Ok(()) - } - None => Err(SoundcoreLibError::NotConnected), - } - } - async fn recv(&self) -> Result, SoundcoreLibError> { - match &self.rfcomm { - Some(rfcomm) => Ok(remove_padding(rfcomm.recv(300).await?.as_slice())), - None => Err(SoundcoreLibError::BthError { - source: bluetooth_lib::error::BthError::InvalidSocketError, - }), - } - } - - async fn build_and_send_cmd( - &self, - cmd: [i8; 7], - data: Option<&[u8]>, - ) -> Result<(), SoundcoreLibError> { - let to_send = build_command_array_with_options_toggle_enabled(&i8_to_u8vec(&cmd), data); - let _ = &self.send(&to_send).await?; - sleep(SLEEP_DURATION).await; - Ok(()) - } - - async fn get_status(&self) -> Result { - self.build_and_send_cmd(A3951_CMD_DEVICE_STATUS, None) - .await?; - let resp = self.recv().await?; - if A3935_RESPONSE_VERIFICATION { - verify_resp(&resp)?; - } - Ok(Self::decode(self, &resp)?) - } - - async fn get_info(&self) -> Result { - self.build_and_send_cmd(A3951_CMD_DEVICE_INFO, None).await?; - let resp = self.recv().await?; - if A3951_RESPONSE_VERIFICATION { - verify_resp(&resp)?; - } - Ok(Self::decode(self, &resp)?) - } - async fn get_battery_level(&self) -> Result { - Ok(self.get_status().await?.battery_level) - } - - async fn get_battery_charging(&self) -> Result { - Ok(self.get_status().await?.battery_charging) - } -} - -#[async_trait] -impl SoundcoreANC for A3935 { - async fn set_anc(&self, profile: ANCProfile) -> Result<(), crate::error::SoundcoreLibError> { - self.build_and_send_cmd(A3951_CMD_DEVICE_SETANC, Some(&profile.to_bytes())) - .await?; - let _resp = self.recv().await?; /* No response validation - Need more info */ - Ok(()) - } - - async fn get_anc(&self) -> Result { - self.build_and_send_cmd(A3951_CMD_DEVICE_GETANC, None) - .await?; - let resp = self.recv().await?; - if A3951_RESPONSE_VERIFICATION { - verify_resp(&resp)?; - } - Ok(ANCProfile::decode(&resp[9..13])?) - } -} - -#[async_trait] -impl SoundcoreEQ for A3935 { - async fn set_eq(&self, wave: EQWave) -> Result<(), SoundcoreLibError> { - /* Original Java method name: SendEQ_NoDrc_Not_A3951_A3930 */ - let mut wave_out = vec![0; 10]; - let eq_index: i32 = 65278; /* Custom EQ Index */ - let eq_wave = EQWaveInt::from_eq_wave(wave).to_8bytes(); - wave_out[0] = eq_index as u8; - wave_out[1] = (eq_index >> 8) as u8; - wave_out[2..10].copy_from_slice(&eq_wave); - - /* A3027 Doesn't appear to be using DRC */ - self.build_and_send_cmd(A3027_CMD_DEVICE_SETEQ, Some(&wave_out)) - .await?; - let _resp = self.recv().await?; - Ok(()) - } - - async fn get_eq(&self) -> Result { - Ok(self.get_status().await?.left_eq) /* Return both left and right? */ - } -} - -#[async_trait] -impl SoundcoreLDAC for A3935 { - async fn get_ldac(&self) -> Result { - self.build_and_send_cmd(A3951_CMD_DEVICE_GETLDAC, None) - .await?; - let resp = self.recv().await?; - if A3951_RESPONSE_VERIFICATION { - verify_resp(&resp)?; - } - Ok(resp[9] == 1) - } - - async fn set_ldac(&self, _enabled: bool) -> Result<(), SoundcoreLibError> { - unimplemented!() - } -} -impl SoundcoreHearID for A3935 {} - -impl ResponseDecoder for A3935 { - fn decode(&self, arr: &[u8]) -> Result { - Ok(DeviceInfo { - left_fw: String::from_utf8(arr[9..14].to_vec())?, - right_fw: String::from_utf8(arr[14..19].to_vec())?, - sn: String::from_utf8(arr[19..35].to_vec())?, - }) - } -} - -impl ResponseDecoder for A3935 { - fn decode(&self, arr: &[u8]) -> Result { - if arr.len() < 50 { - return Err(SoundcoreLibError::InvalidResponseLength { - expected: 50, - got: arr.len(), - data: arr.to_vec(), - }); - } - - Ok(DeviceStatus { - host_device: arr[9], - tws_status: arr[10] == 1, - battery_level: Self::decode(self, &arr[11..13])?, - battery_charging: Self::decode(self, &arr[13..15])?, - left_eq: EQWave::decode(&arr[17..25])?, - right_eq: EQWave::decode(&arr[25..33])?, - hearid_enabled: false, - left_hearid: EQWave::default(), /* A3935 Doesn't Seem to support hearID */ - right_hearid: EQWave::default(), - left_hearid_customdata: EQWave::default(), - right_hearid_customdata: EQWave::default(), - anc_status: ANCProfile::decode(&arr[45..49])?, - side_tone_enabled: arr[49] == 1, - touch_tone_enabled: arr[50] == 1, - wear_detection_enabled: false, /* Doesn't seem to support it? */ - // TODO: This device supports AutoPowerOff - // TODO: arr[51] == 1, enable auto power off - // TODO: arr[52] is the index of the auto power off time - }) - } -} - -impl ResponseDecoder for A3935 { - fn decode(&self, arr: &[u8]) -> Result { - if arr.len() < 2 { - return Err(SoundcoreLibError::InvalidResponseLength { - expected: 2, - got: arr.len(), - data: arr.to_vec(), - }); - } - - Ok(BatteryLevel { - left: Clamp::clamp(arr[0], 0, 5), - right: Clamp::clamp(arr[1], 0, 5), - }) - } -} - -impl ResponseDecoder for A3935 { - fn decode(&self, arr: &[u8]) -> Result { - if arr.len() < 2 { - return Err(SoundcoreLibError::InvalidResponseLength { - expected: 2, - got: arr.len(), - data: arr.to_vec(), - }); - } - - Ok(BatteryCharging { - left: arr[0] == 1, - right: arr[1] == 1, - }) - } -} diff --git a/soundcore-lib/src/devices/A3951Device.rs b/soundcore-lib/src/devices/A3951Device.rs deleted file mode 100644 index d097d3e..0000000 --- a/soundcore-lib/src/devices/A3951Device.rs +++ /dev/null @@ -1,409 +0,0 @@ -use crate::packets::ResponsePacket; -use crate::{ - base::{SoundcoreANC, SoundcoreDevice, SoundcoreEQ, SoundcoreHearID, SoundcoreLDAC}, - error::SoundcoreLibError, - statics::*, - types::{ - ANCProfile, BatteryCharging, BatteryLevel, DeviceInfo, DeviceStatus, EQWave, EQWaveInt, - ResponseDecoder, - }, - utils::{ - build_command_array_with_options_toggle_enabled, i8_to_u8vec, remove_padding, verify_resp, - Clamp, - }, -}; -use async_trait::async_trait; -use bluetooth_lib::{platform::RFCOMM, BluetoothAdrr, RFCOMMClient}; -use log::debug; -use std::time::Duration; -use tokio::time::sleep; - -static SLEEP_DURATION: Duration = std::time::Duration::from_millis(30); - -pub static A3951_RFCOMM_UUID: &str = crate::statics::A3951_RFCOMM_UUID; - -#[derive(Default)] -pub struct A3951 { - _btaddr: Option, - rfcomm: Option, -} - -#[async_trait] -impl SoundcoreDevice for A3951 { - async fn init( - &self, - btaddr: BluetoothAdrr, - ) -> Result, SoundcoreLibError> { - let mut rfcomm = RFCOMM::new().await?; - rfcomm - .connect_uuid(btaddr.clone(), A3951_RFCOMM_UUID) - .await?; - Ok(Box::new(A3951 { - _btaddr: Some(btaddr), - rfcomm: Some(rfcomm), - })) - } - - async fn close(&self) -> Result<(), SoundcoreLibError> { - match &self.rfcomm { - Some(rfcomm) => { - rfcomm.close().await; - Ok(()) - } - None => Err(SoundcoreLibError::NotConnected), - } - } - - async fn send(&self, data: &[u8]) -> Result<(), SoundcoreLibError> { - match &self.rfcomm { - Some(rfcomm) => { - rfcomm.send(data).await?; - Ok(()) - } - None => Err(SoundcoreLibError::NotConnected), - } - } - async fn recv(&self) -> Result, SoundcoreLibError> { - match &self.rfcomm { - Some(rfcomm) => Ok(remove_padding(rfcomm.recv(300).await?.as_slice())), - None => Err(SoundcoreLibError::BthError { - source: bluetooth_lib::error::BthError::InvalidSocketError, - }), - } - } - - async fn build_and_send_cmd( - &self, - cmd: [i8; 7], - data: Option<&[u8]>, - ) -> Result<(), SoundcoreLibError> { - let to_send = build_command_array_with_options_toggle_enabled(&i8_to_u8vec(&cmd), data); - let _ = &self.send(&to_send).await?; - sleep(SLEEP_DURATION).await; - Ok(()) - } - - async fn get_status(&self) -> Result { - self.build_and_send_cmd(A3951_CMD_DEVICE_STATUS, None) - .await?; - let resp = self.recv().await?; - Ok(Self::decode(self, &resp)?) - } - - async fn get_info(&self) -> Result { - self.build_and_send_cmd(A3951_CMD_DEVICE_INFO, None).await?; - let resp = self.recv().await?; - if A3951_RESPONSE_VERIFICATION && A3951_RESPONSE_VERIFICATION { - verify_resp(&resp)?; - } - Ok(Self::decode(self, &resp)?) - } - async fn get_battery_level(&self) -> Result { - self.build_and_send_cmd(A3951_CMD_DEVICE_BATTERYLEVEL, None) - .await?; - let resp = self.recv().await?; - - if A3951_RESPONSE_VERIFICATION { - verify_resp(&resp)?; - } - - if resp[6] == 4 { - debug!("Device battery level blink: {:?}", resp); - // Case battery level. Ignore for now, more debugging needed. - // Battery charging "blinks" when this event is triggered. - return Err(SoundcoreLibError::Unknown); - } - - Ok(Self::decode(self, &resp[9..11])?) - } - - async fn get_battery_charging(&self) -> Result { - self.build_and_send_cmd(A3951_CMD_DEVICE_BATTERYCHARGING, None) - .await?; - let resp = self.recv().await?; - if A3951_RESPONSE_VERIFICATION { - verify_resp(&resp)?; - } - // https://prnt.sc/yze5IvvUtYlq Case battery "blink" - if resp.len() >= 13 && resp[13] == 255 { - debug!("Device battery charging blink: {:?}", resp); - // When "blinking" resp[13] is 255 afaik. - return Err(SoundcoreLibError::Unknown); - } - - Ok(Self::decode(self, &resp[9..11])?) - } -} - -#[async_trait] -impl SoundcoreANC for A3951 { - async fn set_anc(&self, profile: ANCProfile) -> Result<(), crate::error::SoundcoreLibError> { - self.build_and_send_cmd(A3951_CMD_DEVICE_SETANC, Some(&profile.to_bytes())) - .await?; - let _resp = self.recv().await?; /* No response validation - Need more info */ - Ok(()) - } - - async fn get_anc(&self) -> Result { - self.build_and_send_cmd(A3951_CMD_DEVICE_GETANC, None) - .await?; - let resp = self.recv().await?; - if A3951_RESPONSE_VERIFICATION { - verify_resp(&resp)?; - } - Ok(ANCProfile::decode(&resp[9..13])?) - } -} - -#[async_trait] -impl SoundcoreEQ for A3951 { - async fn set_eq(&self, wave: EQWave) -> Result<(), SoundcoreLibError> { - let drc_supported = true; - let eq_index: i32 = 65278; /* Custom EQ Index */ - let eq_hindex = 0; /* I don't know what this is, doesn't seem to change across EQ Indexes and EQ values and is constant */ - let arr_len = match drc_supported { - /* 76: DRC 74: No DRC */ - true => 76, - false => 74, - }; - let drc_offset = match drc_supported { - true => 4, - false => 2, - }; - let mut wave_out: Vec = vec![0; arr_len]; - - wave_out[0] = eq_index as u8; - wave_out[1] = ((eq_index >> 8) & 0xFF) as u8; - - if drc_supported { - /* hindex is used on DRC models */ - wave_out[2] = eq_hindex as u8; - wave_out[3] = ((eq_hindex >> 8) & 0xFF) as u8; - } - - /* used for both left and right EQs */ - let corrected_eq_wave = EQWave::transform_to_realeq(wave); - let eq_wave_bytes = EQWaveInt::from_eq_wave(wave).to_bytes(); - let corrected_eq_wave_bytes = EQWaveInt::from_eq_wave(corrected_eq_wave).to_bytes(); - let hearid_wave_bytes = EQWaveInt::from_eq_wave(EQWave::HEARD_ID_DEFAULT).to_bytes(); - - /* drc_offset - drc_offset + 16 EQ Wave */ - wave_out[drc_offset..drc_offset + 8].copy_from_slice(&eq_wave_bytes[0..8]); - wave_out[drc_offset + 8..drc_offset + 16].copy_from_slice(&eq_wave_bytes[0..8]); - /* Straight from Soundcore spaghetti */ - wave_out[drc_offset + 16] = 255_u8; - wave_out[drc_offset + 17] = 255_u8; - wave_out[drc_offset + 18] = 0; - /* drc_offset + 19-35 HearID EQ Wave */ - wave_out[drc_offset + 19..drc_offset + 27].copy_from_slice(&hearid_wave_bytes[0..8]); - wave_out[drc_offset + 27..drc_offset + 35].copy_from_slice(&hearid_wave_bytes[0..8]); - - wave_out[drc_offset + 35..drc_offset + 39].copy_from_slice(&[0, 0, 0, 0]); - wave_out[drc_offset + 39] = 0; /* HearID type */ - - /* drc_offset + 40-56 HearID Customer EQ Wave (IDK what this means, hearid data is not reversed atm) */ - wave_out[drc_offset + 40..drc_offset + 48].copy_from_slice(&hearid_wave_bytes[0..8]); - wave_out[drc_offset + 48..drc_offset + 56].copy_from_slice(&hearid_wave_bytes[0..8]); - - /* drc_offset + 56-72 "Corrected" EQ Wave */ - wave_out[drc_offset + 56..drc_offset + 64].copy_from_slice(&corrected_eq_wave_bytes[0..8]); - wave_out[drc_offset + 64..drc_offset + 72].copy_from_slice(&corrected_eq_wave_bytes[0..8]); - self.build_and_send_cmd(A3951_CMD_DEVICE_SETEQ_DRC, Some(&wave_out)) - .await?; - let _resp = self.recv().await?; - Ok(()) - } - - async fn get_eq(&self) -> Result { - Ok(self.get_status().await?.left_eq) /* Return both left and right? */ - } -} - -#[async_trait] -impl SoundcoreLDAC for A3951 { - async fn get_ldac(&self) -> Result { - self.build_and_send_cmd(A3951_CMD_DEVICE_GETLDAC, None) - .await?; - let resp = self.recv().await?; - if A3951_RESPONSE_VERIFICATION { - verify_resp(&resp)?; - } - Ok(resp[9] == 1) - } - - async fn set_ldac(&self, _enabled: bool) -> Result<(), SoundcoreLibError> { - unimplemented!() - } -} -impl SoundcoreHearID for A3951 {} - -impl ResponseDecoder for A3951 { - fn decode(&self, arr: &[u8]) -> Result { - Ok(DeviceInfo { - left_fw: String::from_utf8(arr[9..14].to_vec())?, - right_fw: String::from_utf8(arr[14..19].to_vec())?, - sn: String::from_utf8(arr[19..35].to_vec())?, - }) - } -} - -impl ResponseDecoder for A3951 { - fn decode(&self, arr: &[u8]) -> Result { - if arr.len() < 93 { - return Err(SoundcoreLibError::RecvError); - } - Ok(DeviceStatus { - host_device: arr[9], - tws_status: arr[10] == 1, - battery_level: Self::decode(self, &arr[11..13])?, - battery_charging: Self::decode(self, &arr[13..15])?, - left_eq: EQWave::decode(&arr[17..25])?, - right_eq: EQWave::decode(&arr[25..33])?, - hearid_enabled: arr[35] == 1, - left_hearid: EQWave::decode(&arr[36..44])?, - right_hearid: EQWave::decode(&arr[44..52])?, - left_hearid_customdata: EQWave::decode(&arr[58..66])?, - right_hearid_customdata: EQWave::decode(&arr[66..74])?, - anc_status: ANCProfile::decode(&arr[86..90])?, - side_tone_enabled: arr[90] == 1, - wear_detection_enabled: arr[91] == 1, - touch_tone_enabled: arr[92] == 1, - }) - } -} - -impl ResponseDecoder for A3951 { - fn decode(&self, arr: &[u8]) -> Result { - if arr.len() < 2 { - return Err(SoundcoreLibError::InvalidResponseLength { - expected: 2, - got: arr.len(), - data: arr.to_vec(), - }); - } - - Ok(BatteryLevel { - left: Clamp::clamp(arr[0], 0, 5), - right: Clamp::clamp(arr[1], 0, 5), - }) - } -} - -impl ResponseDecoder for A3951 { - fn decode(&self, arr: &[u8]) -> Result { - if arr.len() < 2 { - return Err(SoundcoreLibError::InvalidResponseLength { - expected: 2, - got: arr.len(), - data: arr.to_vec(), - }); - } - - Ok(BatteryCharging { - left: arr[0] == 1, - right: arr[1] == 1, - }) - } -} - -impl ANCProfile { - pub const NORMAL_MODE: ANCProfile = ANCProfile { - option: 2, - anc_option: 0, - transparency_option: 0, - anc_custom: 6, - }; - - pub const ANC_TRANSPORT_MODE: ANCProfile = ANCProfile { - option: 0, - anc_option: 0, - transparency_option: 1, - anc_custom: 6, - }; - - pub const ANC_OUTDOOR_MODE: ANCProfile = ANCProfile { - option: 0, - anc_option: 1, - transparency_option: 1, - anc_custom: 6, - }; - - pub const ANC_INDOOR_MODE: ANCProfile = ANCProfile { - option: 0, - anc_option: 2, - transparency_option: 1, - anc_custom: 6, - }; - - pub const TRANSPARENCY_FULLY_TRANSPARENT_MODE: ANCProfile = ANCProfile { - option: 1, - anc_option: 0, - transparency_option: 0, - anc_custom: 6, - }; - - pub const TRANSPARENCY_VOCAL_MODE: ANCProfile = ANCProfile { - option: 1, - anc_option: 0, - transparency_option: 1, - anc_custom: 6, - }; - - pub fn anc_custom_value(val: u8) -> ANCProfile { - ANCProfile { - option: 0, - anc_option: 3, - transparency_option: 1, - anc_custom: Clamp::clamp(val, 0, 10), - } - } - - pub fn decode(arr: &[u8]) -> Result { - let anc_custom: u8; - - if arr[3] == 255 { - anc_custom = 255; - } else { - anc_custom = Clamp::clamp(arr[3], 0, 10); - } - - Ok(ANCProfile { - option: Clamp::clamp(arr[0], 0, 2), - anc_option: Clamp::clamp(arr[1], 0, 3), - transparency_option: arr[2], - anc_custom, - }) - } - - pub fn to_bytes(&self) -> [u8; 4] { - let anc_custom: u8; - - if self.anc_custom == 255 { - anc_custom = 255; - } else { - anc_custom = Clamp::clamp(self.anc_custom, 0, 10); - } - - [ - Clamp::clamp(self.option, 0, 2), - Clamp::clamp(self.anc_option, 0, 3), - self.transparency_option, - anc_custom, - ] - } -} - -#[cfg(test)] -impl A3951 { - /* Used for comparing in testing */ - pub fn device_status(bytes: &[u8]) -> DeviceStatus { - Self::decode( - &A3951 { - _btaddr: None, - rfcomm: None, - }, - bytes, - ) - .unwrap() - } -} diff --git a/soundcore-lib/src/devices/a3027.rs b/soundcore-lib/src/devices/a3027.rs new file mode 100644 index 0000000..02c6244 --- /dev/null +++ b/soundcore-lib/src/devices/a3027.rs @@ -0,0 +1,3 @@ +mod features; + +pub use features::*; \ No newline at end of file diff --git a/soundcore-lib/src/devices/a3027/features.rs b/soundcore-lib/src/devices/a3027/features.rs new file mode 100644 index 0000000..df4adaa --- /dev/null +++ b/soundcore-lib/src/devices/a3027/features.rs @@ -0,0 +1,16 @@ +use std::sync::Arc; + +use crate::api::{DeviceFeatureSet, EqualizerFeatures, FeatureFlags, SoundModeFeatures}; + +pub fn a3027_features() -> DeviceFeatureSet { + DeviceFeatureSet { + sound_mode_features: Some( + SoundModeFeatures::scene_based_non_customizable_anc_non_customizable_transparency(), + ), + equalizer_features: Some(EqualizerFeatures { + bands: 8, + channels: 1, + }), + flags: Arc::new([]), + } +} diff --git a/soundcore-lib/src/devices/a3028.rs b/soundcore-lib/src/devices/a3028.rs new file mode 100644 index 0000000..02c6244 --- /dev/null +++ b/soundcore-lib/src/devices/a3028.rs @@ -0,0 +1,3 @@ +mod features; + +pub use features::*; \ No newline at end of file diff --git a/soundcore-lib/src/devices/a3028/features.rs b/soundcore-lib/src/devices/a3028/features.rs new file mode 100644 index 0000000..41720d2 --- /dev/null +++ b/soundcore-lib/src/devices/a3028/features.rs @@ -0,0 +1,16 @@ +use std::sync::Arc; + +use crate::api::{DeviceFeatureSet, EqualizerFeatures, FeatureFlags, SoundModeFeatures}; + +pub fn a3028_features() -> DeviceFeatureSet { + DeviceFeatureSet { + sound_mode_features: Some( + SoundModeFeatures::scene_based_non_customizable_anc_non_customizable_transparency(), + ), + equalizer_features: Some(EqualizerFeatures { + bands: 8, + channels: 1, + }), + flags: Arc::new([]), + } +} diff --git a/soundcore-lib/src/devices/a3029.rs b/soundcore-lib/src/devices/a3029.rs new file mode 100644 index 0000000..02c6244 --- /dev/null +++ b/soundcore-lib/src/devices/a3029.rs @@ -0,0 +1,3 @@ +mod features; + +pub use features::*; \ No newline at end of file diff --git a/soundcore-lib/src/devices/a3029/features.rs b/soundcore-lib/src/devices/a3029/features.rs new file mode 100644 index 0000000..2b11461 --- /dev/null +++ b/soundcore-lib/src/devices/a3029/features.rs @@ -0,0 +1,16 @@ +use std::sync::Arc; + +use crate::api::{DeviceFeatureSet, EqualizerFeatures, FeatureFlags, SoundModeFeatures}; + +pub fn a3029_features() -> DeviceFeatureSet { + DeviceFeatureSet { + sound_mode_features: Some( + SoundModeFeatures::scene_based_non_customizable_anc_non_customizable_transparency(), + ), + equalizer_features: Some(EqualizerFeatures { + bands: 8, + channels: 1, + }), + flags: Arc::new([]), + } +} diff --git a/soundcore-lib/src/devices/a3040.rs b/soundcore-lib/src/devices/a3040.rs new file mode 100644 index 0000000..02c6244 --- /dev/null +++ b/soundcore-lib/src/devices/a3040.rs @@ -0,0 +1,3 @@ +mod features; + +pub use features::*; \ No newline at end of file diff --git a/soundcore-lib/src/devices/a3040/features.rs b/soundcore-lib/src/devices/a3040/features.rs new file mode 100644 index 0000000..8cd60ec --- /dev/null +++ b/soundcore-lib/src/devices/a3040/features.rs @@ -0,0 +1,23 @@ +use std::sync::Arc; + +use crate::api::{DeviceFeatureSet, EqualizerFeatures, FeatureFlags, SoundModeFeatures}; + +pub fn a3040_features() -> DeviceFeatureSet { + DeviceFeatureSet { + sound_mode_features: Some( + SoundModeFeatures::adaptive_customizable_anc_customizable_transparency(), + ), + equalizer_features: Some(EqualizerFeatures { + bands: 8, + channels: 2, + }), + flags: Arc::new([ + FeatureFlags::CUSTOM_BUTTONS, + FeatureFlags::AUTO_POWER_OFF_ON, + FeatureFlags::POWER_ON_BATTERY_NOTICE, + FeatureFlags::MULTIPLE_DEVICE_LIST, + FeatureFlags::HEARING_PROTECTION, + FeatureFlags::AMBIENT_SOUND_NOTICE, + ]), + } +} diff --git a/soundcore-lib/src/devices/a3930.rs b/soundcore-lib/src/devices/a3930.rs new file mode 100644 index 0000000..02c6244 --- /dev/null +++ b/soundcore-lib/src/devices/a3930.rs @@ -0,0 +1,3 @@ +mod features; + +pub use features::*; \ No newline at end of file diff --git a/soundcore-lib/src/devices/a3930/features.rs b/soundcore-lib/src/devices/a3930/features.rs new file mode 100644 index 0000000..8891ce0 --- /dev/null +++ b/soundcore-lib/src/devices/a3930/features.rs @@ -0,0 +1,17 @@ +use std::sync::Arc; + +use crate::api::{DeviceFeatureSet, EqualizerFeatures, FeatureFlags, SoundModeFeatures}; + +pub fn a3930_features() -> DeviceFeatureSet { + DeviceFeatureSet { + // A3030 Seems to have no sound modes + sound_mode_features: Some( + SoundModeFeatures::new(&[], &[], true), + ), + equalizer_features: Some(EqualizerFeatures { + bands: 8, + channels: 1, + }), + flags: Arc::new([]), + } +} diff --git a/soundcore-lib/src/devices/a3951.rs b/soundcore-lib/src/devices/a3951.rs new file mode 100644 index 0000000..02c6244 --- /dev/null +++ b/soundcore-lib/src/devices/a3951.rs @@ -0,0 +1,3 @@ +mod features; + +pub use features::*; \ No newline at end of file diff --git a/soundcore-lib/src/devices/a3951/features.rs b/soundcore-lib/src/devices/a3951/features.rs new file mode 100644 index 0000000..fac2253 --- /dev/null +++ b/soundcore-lib/src/devices/a3951/features.rs @@ -0,0 +1,22 @@ +use std::sync::Arc; + +use crate::api::{DeviceFeatureSet, EqualizerFeatures, FeatureFlags, SoundModeFeatures}; + +pub fn a3951_features() -> DeviceFeatureSet { + DeviceFeatureSet { + sound_mode_features: Some( + SoundModeFeatures::adaptive_customizable_anc_customizable_transparency(), + ), + equalizer_features: Some(EqualizerFeatures { + bands: 8, + channels: 2, + }), + flags: Arc::new([ + FeatureFlags::CUSTOM_BUTTONS, + FeatureFlags::DRC, + FeatureFlags::HEARID, + FeatureFlags::TOUCH_TONE, + FeatureFlags::WEAR_DETECTION, + ]), + } +} \ No newline at end of file diff --git a/soundcore-lib/src/devices/mod.rs b/soundcore-lib/src/devices/mod.rs deleted file mode 100644 index 3e61adf..0000000 --- a/soundcore-lib/src/devices/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![allow(non_snake_case)] -mod A3027Device; -mod A3040Device; -mod A3935Device; -mod A3951Device; -pub use A3027Device::A3027; -pub use A3040Device::A3040; -pub use A3935Device::A3935; -pub use A3951Device::{A3951, A3951_RFCOMM_UUID}; diff --git a/soundcore-lib/src/models/anc_mode.rs b/soundcore-lib/src/models/anc_mode.rs index 1d782ea..d48a728 100644 --- a/soundcore-lib/src/models/anc_mode.rs +++ b/soundcore-lib/src/models/anc_mode.rs @@ -4,25 +4,13 @@ use crate::models::AdaptiveANCMode; use crate::models::SceneBasedANCMode; #[repr(u8)] -#[derive( -Debug, -Serialize, -Deserialize, -Eq, -PartialEq, -Ord, -PartialOrd, -Clone, -Copy, -Hash -)] +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash)] #[serde(rename_all = "camelCase")] pub enum ANCMode { SceneBased(SceneBasedANCMode), Adaptive(AdaptiveANCMode), } - impl ANCMode { pub fn as_u8(&self) -> u8 { match self { @@ -30,11 +18,11 @@ impl ANCMode { ANCMode::Adaptive(mode) => mode.as_u8(), } } - + pub fn from_u8_scene_based(value: u8) -> Option { SceneBasedANCMode::from_u8(value).map(ANCMode::SceneBased) } - + pub fn from_u8_adaptive(value: u8) -> Option { AdaptiveANCMode::from_u8(value).map(ANCMode::Adaptive) } @@ -53,5 +41,3 @@ impl Default for ANCMode { ANCMode::SceneBased(Default::default()) } } - - diff --git a/soundcore-lib/src/packets/response/state/a3027.rs b/soundcore-lib/src/packets/response/state/a3027.rs index 76807e9..5effa32 100644 --- a/soundcore-lib/src/packets/response/state/a3027.rs +++ b/soundcore-lib/src/packets/response/state/a3027.rs @@ -7,18 +7,20 @@ use nom::{ use serde::{Deserialize, Serialize}; use crate::{ - models::{ + devices::a3027_features, models::{ AgeRange, BaseHearID, DeviceFirmware, EQConfiguration, Gender, HearID, SerialNumber, - SingleBattery, SoundMode, SoundcoreFeatureFlags, StereoEQConfiguration, - TwsStatus, WearDetection, - }, - parsers::{ - parse_base_hear_id, parse_bool, parse_dual_fw, parse_serial_number, - parse_single_battery, u8_parser, - }, + SingleBattery, SoundMode, SoundcoreFeatureFlags, StereoEQConfiguration, TwsStatus, + WearDetection, + }, parsers::{ + parse_base_hear_id, parse_bool, parse_dual_fw, parse_serial_number, parse_single_battery, + u8_parser, + } }; -use crate::parsers::{bool_parser, parse_gender, parse_sound_mode, parse_stereo_eq_configuration, ParseError, TaggedData, TaggedParseResult}; +use crate::parsers::{ + bool_parser, parse_gender, parse_sound_mode, parse_stereo_eq_configuration, ParseError, + TaggedData, TaggedParseResult, +}; use crate::types::SupportedModels; use super::DeviceStateResponse; @@ -38,21 +40,10 @@ pub struct A3027StateResponse { pub touch_func: bool, } -const A3027_FEATURE_FLAGS: BitFlags = make_bitflags!(SoundcoreFeatureFlags::{ - SOUND_MODE - | ANC_MODE - | TRANS_MODE - | CUSTOM_ANC - | WEAR_DETECTION - | EQ - | STEREO_EQ - | HEARID -}); - impl From for DeviceStateResponse { fn from(value: A3027StateResponse) -> Self { DeviceStateResponse { - feature_flags: A3027_FEATURE_FLAGS, + feature_set: a3027_features(), battery: value.battery.into(), sound_mode: value.sound_mode, eq: value.eq.into(), @@ -106,7 +97,7 @@ pub fn parse_a3027_state_response<'a, E: ParseError<'a>>( sn, touch_func: touch_func.unwrap_or(false), }, - } + }, )) }), )(bytes) diff --git a/soundcore-lib/src/packets/response/state/a3028.rs b/soundcore-lib/src/packets/response/state/a3028.rs index d25b5e7..b98027b 100644 --- a/soundcore-lib/src/packets/response/state/a3028.rs +++ b/soundcore-lib/src/packets/response/state/a3028.rs @@ -3,18 +3,17 @@ use nom::{combinator::all_consuming, error::context, sequence::tuple}; use serde::{Deserialize, Serialize}; use crate::{ - models::{ + devices::a3028_features, models::{ AgeRange, BaseHearID, DeviceFirmware, EQConfiguration, Gender, HearID, SerialNumber, SingleBattery, SoundMode, SoundcoreFeatureFlags, StereoEQConfiguration, TwsStatus, - }, - parsers::{ + }, parsers::{ parse_base_hear_id, parse_dual_fw, parse_serial_number, parse_single_battery, u8_parser, - }, + } }; use crate::parsers::{ - parse_gender, parse_sound_mode, parse_stereo_eq_configuration, ParseError, - TaggedData, TaggedParseResult, + parse_gender, parse_sound_mode, parse_stereo_eq_configuration, ParseError, TaggedData, + TaggedParseResult, }; use crate::types::SupportedModels; @@ -33,19 +32,10 @@ pub struct A3028StateResponse { pub sn: SerialNumber, } -const A3028_FEATURE_FLAGS: BitFlags = make_bitflags!(SoundcoreFeatureFlags::{ - SOUND_MODE - | ANC_MODE - | TRANS_MODE - | EQ - | STEREO_EQ - | HEARID -}); - impl From for DeviceStateResponse { fn from(value: A3028StateResponse) -> Self { DeviceStateResponse { - feature_flags: A3028_FEATURE_FLAGS, + feature_set: a3028_features(), battery: value.battery.into(), sound_mode: value.sound_mode, eq: value.eq.into(), diff --git a/soundcore-lib/src/packets/response/state/a3029.rs b/soundcore-lib/src/packets/response/state/a3029.rs index 4ee12d8..29f001b 100644 --- a/soundcore-lib/src/packets/response/state/a3029.rs +++ b/soundcore-lib/src/packets/response/state/a3029.rs @@ -2,6 +2,7 @@ use enumflags2::{make_bitflags, BitFlags}; use nom::{combinator::all_consuming, error::context, number::complete::le_u8, sequence::tuple}; use serde::{Deserialize, Serialize}; +use crate::devices::a3029_features; use crate::parsers::{ parse_gender, parse_sound_mode, parse_stereo_eq_configuration, ParseError, TaggedData, TaggedParseResult, @@ -33,19 +34,10 @@ pub struct A3029StateResponse { pub hear_id_has_data: bool, } -const A3029_FEATURE_FLAGS: BitFlags = make_bitflags!(SoundcoreFeatureFlags::{ - SOUND_MODE - | ANC_MODE - | TRANS_MODE - | EQ - | STEREO_EQ - | HEARID -}); - impl From for DeviceStateResponse { fn from(value: A3029StateResponse) -> Self { DeviceStateResponse { - feature_flags: A3029_FEATURE_FLAGS, + feature_set: a3029_features(), battery: value.battery.into(), sound_mode: value.sound_mode, eq: value.eq.into(), diff --git a/soundcore-lib/src/packets/response/state/a3040.rs b/soundcore-lib/src/packets/response/state/a3040.rs index 76472fa..527c0bc 100644 --- a/soundcore-lib/src/packets/response/state/a3040.rs +++ b/soundcore-lib/src/packets/response/state/a3040.rs @@ -6,10 +6,16 @@ use nom::{bytes::complete::take, number::complete::le_u8}; use serde::{Deserialize, Serialize}; use crate::devices::a3040_features; -use crate::models::{A3040ButtonModel, ButtonModel, EQConfiguration, PromptLanguage, SoundMode, StereoEQConfiguration, TwsStatus}; +use crate::models::{ + A3040ButtonModel, ButtonModel, EQConfiguration, PromptLanguage, SoundMode, + StereoEQConfiguration, TwsStatus, +}; use crate::packets::DeviceStateResponse; use crate::parsers::{ - bool_parser, parse_a3040_button_model, parse_adaptive_sound_mode_customizable_trans, parse_auto_power_off_on, parse_fw, parse_hearing_protect, parse_mono_eq, parse_prompt_language, parse_single_battery, parse_sound_mode, parse_stereo_eq, parse_stereo_eq_configuration, u8_parser, TaggedData, TaggedParseResult + bool_parser, parse_a3040_button_model, parse_adaptive_sound_mode_customizable_trans, + parse_auto_power_off_on, parse_fw, parse_hearing_protect, parse_mono_eq, parse_prompt_language, + parse_single_battery, parse_sound_mode, parse_stereo_eq, parse_stereo_eq_configuration, + u8_parser, TaggedData, TaggedParseResult, }; use crate::types::SupportedModels; use crate::{ @@ -51,15 +57,14 @@ pub fn parse_a3040_state_response<'a, E: ParseError<'a>>( bytes: &'a [u8], ) -> TaggedParseResult { context("a3040_state_response", |bytes: &'a [u8]| { - let (bytes, (battery, fw, sn,eq, offset, button_model)) = - tuple(( - parse_single_battery, - parse_fw, - parse_serial_number, - parse_stereo_eq_configuration(10), // TODO: We have mono eq here, but it appears to be duplicated (bytes are not 1-1, DRC?) - le_u8, - parse_a3040_button_model, - ))(bytes)?; + let (bytes, (battery, fw, sn, eq, offset, button_model)) = tuple(( + parse_single_battery, + parse_fw, + parse_serial_number, + parse_stereo_eq_configuration(10), // TODO: We have mono eq here, but it appears to be duplicated (bytes are not 1-1, DRC?) + le_u8, + parse_a3040_button_model, + ))(bytes)?; let (bytes, _ignored) = take(offset as usize - 3)(bytes)?; let ( @@ -99,7 +104,6 @@ pub fn parse_a3040_state_response<'a, E: ParseError<'a>>( bool_parser::, ))(bytes)?; - println!("Remaining bytes: {:x?}", bytes); Ok(( bytes, TaggedData { diff --git a/soundcore-lib/src/packets/response/state/a3930.rs b/soundcore-lib/src/packets/response/state/a3930.rs index 52f1694..03e7829 100644 --- a/soundcore-lib/src/packets/response/state/a3930.rs +++ b/soundcore-lib/src/packets/response/state/a3930.rs @@ -8,12 +8,11 @@ use nom::{ use serde::{Deserialize, Serialize}; use crate::{ - models::{ + devices::a3930_features, models::{ A3909ButtonModel, AgeRange, Battery, ButtonModel, CustomHearID, DualBattery, EQConfiguration, Gender, HearID, SideTone, SoundMode, SoundcoreFeatureFlags, StereoEQConfiguration, TwsStatus, - }, - parsers::u8_parser, + }, parsers::u8_parser }; use crate::parsers::{ @@ -40,23 +39,11 @@ pub struct A3930StateResponse { pub hear_id_eq_index: Option<(u8, u8)>, // TODO: Parse this correctly } -const A3930_FEATURE_FLAGS: BitFlags = make_bitflags!(SoundcoreFeatureFlags::{ - // TODO: Check if these are correct - SOUND_MODE - | ANC_MODE - | TRANS_MODE - | CUSTOM_ANC - | CUSTOM_BUTTONS - | EQ - | STEREO_EQ - | DRC - | HEARID -}); impl From for DeviceStateResponse { fn from(value: A3930StateResponse) -> Self { DeviceStateResponse { - feature_flags: A3930_FEATURE_FLAGS, + feature_set: a3930_features(), battery: Battery::Dual(value.battery), sound_mode: value.sound_mode, host_device: Some(value.host_device), diff --git a/soundcore-lib/src/packets/response/state/a3951.rs b/soundcore-lib/src/packets/response/state/a3951.rs index 64fafdf..42338e3 100644 --- a/soundcore-lib/src/packets/response/state/a3951.rs +++ b/soundcore-lib/src/packets/response/state/a3951.rs @@ -8,12 +8,11 @@ use nom::{ use serde::{Deserialize, Serialize}; use crate::{ - models::{ + devices::a3951_features, models::{ A3909ButtonModel, AgeRange, Battery, ButtonModel, CustomHearID, DualBattery, EQConfiguration, Gender, HearID, SideTone, SoundMode, SoundcoreFeatureFlags, StereoEQConfiguration, TouchTone, TwsStatus, WearDetection, - }, - parsers::u8_parser, + }, parsers::u8_parser }; use crate::parsers::{ @@ -43,24 +42,12 @@ pub struct A3951StateResponse { pub new_battery: Option<(u8, u8)>, } -const A3951_FEATURE_FLAGS: BitFlags = make_bitflags!(SoundcoreFeatureFlags::{ - SOUND_MODE - | ANC_MODE - | TRANS_MODE - | CUSTOM_ANC - | CUSTOM_BUTTONS - | WEAR_DETECTION - | EQ - | STEREO_EQ - | DRC - | HEARID - | TOUCH_TONE -}); + impl From for DeviceStateResponse { fn from(value: A3951StateResponse) -> Self { DeviceStateResponse { - feature_flags: A3951_FEATURE_FLAGS, + feature_set: a3951_features(), battery: Battery::Dual(value.battery), sound_mode: value.sound_mode, host_device: Some(value.host_device), @@ -164,45 +151,4 @@ mod a3951_state { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 1, 99, 1, 84, 1, 102, 1, 84, 0, 1, 0, 0, 0, 1, 1, 6, 0, 1, 0, 0, 0, 0, 242, ]; - - #[test] - fn should_parse_state_same_as_old() { - let (remaining, state) = - parse_a3951_state_response::>(&RESP_BYTES).unwrap(); - let old_state = crate::devices::A3951::device_status(&ORIG_RESP_BYTES); - - assert!(remaining.is_empty()); - - assert_eq!(state.data.host_device, old_state.host_device); - assert_eq!(state.data.tws_status.0, old_state.tws_status); - assert_eq!( - state.data.battery.left.charging, - old_state.battery_charging.left - ); - assert_eq!(state.data.battery.left.level, old_state.battery_level.left); - assert_eq!( - state.data.battery.right.charging, - old_state.battery_charging.right - ); - assert_eq!( - state.data.battery.right.level, - old_state.battery_level.right - ); - assert_eq!( - state.data.sound_mode.anc_mode.as_u8(), - old_state.anc_status.anc_option - ); - assert_eq!( - state.data.sound_mode.custom_anc.as_u8(), - old_state.anc_status.anc_custom - ); - assert_eq!( - state.data.sound_mode.anc_mode.as_u8(), - old_state.anc_status.anc_option - ); - assert_eq!( - state.data.sound_mode.trans_mode.as_u8(), - old_state.anc_status.transparency_option - ); - } } diff --git a/soundcore-lib/src/parsers/sound_mode.rs b/soundcore-lib/src/parsers/sound_mode.rs index 8ca688b..48679aa 100644 --- a/soundcore-lib/src/parsers/sound_mode.rs +++ b/soundcore-lib/src/parsers/sound_mode.rs @@ -59,7 +59,7 @@ pub fn parse_adaptive_sound_mode_customizable_trans<'a, E: ParseError<'a>>( parse_custom_trans, )), |(current_mode, custom_anc, trans_mode, anc_mode, _e, custom_trans)| { - let custom_anc_value = CustomANCValue::from_u8((custom_anc << 4) as u8); + let custom_anc_value = CustomANCValue::from_u8(custom_anc << 4); let _unk = custom_anc & 0x0F; SoundMode { current: current_mode,