From e9ee629705b14dcd1571a0e90ce680c8c3b43155 Mon Sep 17 00:00:00 2001 From: Grigorios Mallios Date: Sun, 15 Oct 2023 23:52:53 +0300 Subject: [PATCH] wip: introduce compat module and use new parser --- soundcore-lib/src/compat.rs | 3 + soundcore-lib/src/compat/state.rs | 103 ++++++++++++++++ soundcore-lib/src/devices/A3951Device.rs | 15 +-- soundcore-lib/src/error.rs | 18 ++- soundcore-lib/src/lib.rs | 4 +- soundcore-lib/src/main.rs | 111 ------------------ soundcore-lib/src/models/custom_anc.rs | 2 +- soundcore-lib/src/packets/response.rs | 3 +- soundcore-lib/src/packets/response/state.rs | 8 +- .../src/packets/response/state/a3951.rs | 2 + soundcore-lib/src/parsers.rs | 3 + 11 files changed, 145 insertions(+), 127 deletions(-) create mode 100644 soundcore-lib/src/compat.rs create mode 100644 soundcore-lib/src/compat/state.rs delete mode 100644 soundcore-lib/src/main.rs diff --git a/soundcore-lib/src/compat.rs b/soundcore-lib/src/compat.rs new file mode 100644 index 0000000..e271814 --- /dev/null +++ b/soundcore-lib/src/compat.rs @@ -0,0 +1,3 @@ +pub use state::*; + +mod state; diff --git a/soundcore-lib/src/compat/state.rs b/soundcore-lib/src/compat/state.rs new file mode 100644 index 0000000..d10914b --- /dev/null +++ b/soundcore-lib/src/compat/state.rs @@ -0,0 +1,103 @@ +use crate::error::SoundcoreError; +use crate::models::{ + Battery, DualBattery, EQConfiguration, MonoEQ, SideTone, SoundMode, StereoEQConfiguration, + TouchTone, TwsStatus, WearDetection, +}; +use crate::packets::ResponsePacket; +use crate::types::{ANCProfile, BatteryCharging, BatteryLevel, DeviceStatus, EQWave}; + +impl TryInto for ResponsePacket { + type Error = SoundcoreError; + fn try_into(self) -> Result { + match self { + ResponsePacket::DeviceState(state) => { + let (battery_level, battery_charging) = state.battery.into(); + let (left_eq, right_eq) = state.eq.into(); + Ok(DeviceStatus { + host_device: state.host_device.unwrap_or(0), + tws_status: state.tws_status.unwrap_or(TwsStatus(false)).0, + battery_level, + battery_charging, + anc_status: ANCProfile::from(state.sound_mode), + side_tone_enabled: state.side_tone.unwrap_or(SideTone(false)).0, + wear_detection_enabled: state.wear_detection.unwrap_or(WearDetection(false)).0, + touch_tone_enabled: state.touch_tone.unwrap_or(TouchTone(false)).0, + left_eq, + right_eq, + // TODO: Use actual HearID data + hearid_enabled: false, + left_hearid: EQWave::default(), + right_hearid: EQWave::default(), + left_hearid_customdata: EQWave::default(), + right_hearid_customdata: EQWave::default(), + }) + } + _ => Err(SoundcoreError::IncompatibleResponse), + } + } +} + +impl From for ANCProfile { + fn from(mode: SoundMode) -> Self { + let bytes = mode.to_bytes(); + + ANCProfile { + option: bytes[0], + anc_option: bytes[1], + transparency_option: bytes[2], + anc_custom: bytes[3], + } + } +} + +impl Into<(BatteryLevel, BatteryCharging)> for Battery { + fn into(self) -> (BatteryLevel, BatteryCharging) { + match self { + Battery::Single(batt) => { + let left = BatteryLevel { + left: batt.level, + right: 0, + }; + let right = BatteryCharging { + left: batt.charging, + right: false, + }; + (left, right) + } + Battery::Dual(batt) => { + let left = BatteryLevel { + left: batt.left.level, + right: batt.right.level, + }; + let right = BatteryCharging { + left: batt.left.charging, + right: batt.right.charging, + }; + (left, right) + } + } + } +} +// Maybe figure out how to do this better and test it +impl Into for MonoEQ { + fn into(self) -> EQWave { + EQWave::decode(&self.to_bytes()).unwrap() + } +} + +impl Into<(EQWave, EQWave)> for EQConfiguration { + fn into(self) -> (EQWave, EQWave) { + match self { + EQConfiguration::Stereo(stereo) => { + let left = stereo.eq.left.into(); + let right = stereo.eq.right.into(); + (left, right) + } + EQConfiguration::Mono(mono) => { + let left = mono.eq.into(); + let right = EQWave::default(); + (left, right) + } + } + } +} diff --git a/soundcore-lib/src/devices/A3951Device.rs b/soundcore-lib/src/devices/A3951Device.rs index dce3146..bd3c5f4 100644 --- a/soundcore-lib/src/devices/A3951Device.rs +++ b/soundcore-lib/src/devices/A3951Device.rs @@ -1,8 +1,4 @@ -use async_trait::async_trait; -use bluetooth_lib::{platform::RFCOMM, BluetoothAdrr, RFCOMMClient}; -use log::debug; -use tokio::time::sleep; - +use crate::packets::ResponsePacket; use crate::{ base::{SoundcoreANC, SoundcoreDevice, SoundcoreEQ, SoundcoreHearID, SoundcoreLDAC}, error::SoundcoreError, @@ -16,7 +12,11 @@ use crate::{ 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); @@ -87,10 +87,7 @@ impl SoundcoreDevice for A3951 { self.build_and_send_cmd(A3951_CMD_DEVICE_STATUS, None) .await?; let resp = self.recv().await?; - if A3951_RESPONSE_VERIFICATION { - verify_resp(&resp)?; - } - Ok(Self::decode(self, &resp)?) + Ok(ResponsePacket::from_bytes(&resp)?.try_into()?) } async fn get_info(&self) -> Result { diff --git a/soundcore-lib/src/error.rs b/soundcore-lib/src/error.rs index 62f4a9c..5eb5f12 100644 --- a/soundcore-lib/src/error.rs +++ b/soundcore-lib/src/error.rs @@ -45,7 +45,23 @@ pub enum SoundcoreError { #[error("Invalid response")] InvalidResponse, #[error("Invalid response length (expected {expected}, got {got}, data: {data:?})")] - InvalidResponseLength { expected: usize, got: usize, data: Vec }, + InvalidResponseLength { + expected: usize, + got: usize, + data: Vec, + }, + #[error("Nom Parsing error")] + NomParseError { error: String }, + #[error("Incompatible response")] + IncompatibleResponse, +} + +impl From>> for SoundcoreError { + fn from<'a>(error: nom::Err>) -> Self { + SoundcoreError::NomParseError { + error: format!("{:?}", error), + } + } } // impl std::fmt::Display for SoundcoreError { diff --git a/soundcore-lib/src/lib.rs b/soundcore-lib/src/lib.rs index ccbc7ce..5b38245 100644 --- a/soundcore-lib/src/lib.rs +++ b/soundcore-lib/src/lib.rs @@ -1,5 +1,8 @@ +pub use bluetooth_lib::BluetoothAdrr; + pub mod api; pub mod base; +pub mod compat; pub mod devices; pub mod error; mod models; @@ -9,4 +12,3 @@ pub mod statics; pub mod types; #[allow(non_snake_case)] mod utils; -pub use bluetooth_lib::BluetoothAdrr; diff --git a/soundcore-lib/src/main.rs b/soundcore-lib/src/main.rs deleted file mode 100644 index 91fc1f4..0000000 --- a/soundcore-lib/src/main.rs +++ /dev/null @@ -1,111 +0,0 @@ -#![allow(warnings, unused)] -use core::time; -use std::{fmt::Write, num::ParseIntError, ops::Add, thread, time::Duration}; - -use crate::devices::A3951; - -use crate::types::{EQWave, RecvFnType, SendFnType}; - -mod base; -mod cli; -mod devices; -mod error; -mod statics; -mod types; -mod utils; - -// Modes -// const TRANSPORT_MODE: &[u8; 14] = b"\x08\xee\x00\x00\x00\x06\x81\x0e\x00\x01\x01\x01\x00\x8e"; -// const NORMAL_MODE: &[u8; 14] = b"\x08\xee\x00\x00\x00\x06\x81\x0e\x00\x02\x01\x01\x00\x8f"; -// const ANC_INDOOR: &[u8; 14] = b"\x08\xee\x00\x00\x00\x06\x81\x0e\x00\x00\x02\x01\x00\x8e"; -// const ANC_OUTDOOR: &[u8; 14] = b"\x08\xee\x00\x00\x00\x06\x81\x0e\x00\x00\x01\x01\x00\x8d"; -// const ANC_TRANSPORT: &[u8; 14] = b"\x08\xee\x00\x00\x00\x06\x81\x0e\x00\x00\x00\x01\x00\x8c"; -const BYTE_OFF: i32 = -1; - -const OPCODE_BAT: [u8; 7] = [0x08, 0xEE, 0x00, 0x00, 0x00, 0x01, 0x05]; - -fn main() { - // let mut device = A3951Device::new().unwrap(); - // device - // .connect_uuid("AC:12:2F:6A:D2:07", known_uuids[0]) - // .unwrap(); - // let info = device.get_info().unwrap(); - // println!( - // "SN: {}, Right FW: {}, Left FW: {}", - // info.sn, info.right_fw, info.left_fw - // ); - // let status = device.get_status().unwrap(); - // println!( - // "Left Battery: {:X?}, LeftCharging: {:X?}, Right Battery: {:X?}, RightCharging: {:X?},", - // status.battery_level.left, status.battery_charging.left, status.battery_level.right, status.battery_charging.right, - // ); - - // println!("Left EQ: {:X?}", status.left_eq); - // println!("Right EQ: {:X?}", status.right_eq); - // println!("ANC Status: {:?}", status.anc_status); - // println!("ANC Status from get_anc: {:?}", device.get_anc().unwrap()); - // println!("LDAC Status: {}", device.get_ldac_status().unwrap()); - - // //device.set_anc(A3951DeviceANC::ANC_INDOOR_MODE).unwrap(); - - // let wave = EQWave { - // pos0: 14.5, - // pos1: 13.0, - // pos2: 12.0, - // pos3: 12.0, - // pos4: 12.0, - // pos5: 12.0, - // pos6: 12.0, - // pos7: 12.0, - // pos8: 12.0, - // pos9: 12.0, - // }; - - // device.set_eq(wave); - // let status1 = device.get_status().unwrap(); - // println!("Left EQ: {:X?}", status1.left_eq); - // println!("Right EQ: {:X?}", status1.right_eq); -} - -// // https://stackoverflow.com/questions/52987181/how-can-i-convert-a-hex-string-to-a-u8-slice -// pub fn decode_hex(s: &str) -> Result, std::num::ParseIntError> { -// (0..s.len()) -// .step_by(2) -// .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) -// .collect() -// } - -// pub fn encode_hex(bytes: &[u8]) -> String { -// let mut s = String::with_capacity(bytes.len() * 2); -// for &b in bytes { -// write!(&mut s, "{:02x}", b).unwrap(); -// } -// s -// } - -// pub fn encode_hex_to_cmd_str(bytes: &[u8]) -> String { -// let mut s = String::with_capacity(bytes.len() * 2); -// for &b in bytes { -// let hexString = format!("{:X}", b); -// if (hexString.len() < 2) { -// write!(&mut s, "0").unwrap(); -// } -// write!(&mut s, "{}", hexString).unwrap(); -// } -// s -// } - -// // TODO: Remove unwraps and return Result -// pub fn build_command_array_with_options(bArr: &[u8], bArr2: Option<&[u8]>) -> Vec { -// if (bArr2.is_none()) { -// return Vec::from(bArr); -// } -// let mut result = Vec::new(); -// let length = bArr.len() + 2; -// let length2 = bArr2.unwrap().len() + length; -// result.copy_from_slice(&bArr); -// result[bArr.len()] = (length2 % 256) as u8; -// result[bArr.len() + 1] = (length2 % 256) as u8; -// result[..length].copy_from_slice(&bArr2.unwrap()); -// return result; -// } diff --git a/soundcore-lib/src/models/custom_anc.rs b/soundcore-lib/src/models/custom_anc.rs index 0b380ce..d6fd6d2 100644 --- a/soundcore-lib/src/models/custom_anc.rs +++ b/soundcore-lib/src/models/custom_anc.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; #[derive( Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Default, Hash, )] -pub struct CustomANC(u8); +pub struct CustomANC(pub u8); impl CustomANC { pub fn from_u8(value: u8) -> Self { diff --git a/soundcore-lib/src/packets/response.rs b/soundcore-lib/src/packets/response.rs index 5883efd..24e47ca 100644 --- a/soundcore-lib/src/packets/response.rs +++ b/soundcore-lib/src/packets/response.rs @@ -3,8 +3,9 @@ use crate::{ parsers::{parse_and_check_checksum, parse_packet_header}, }; -use nom::error::{VerboseError}; +use nom::error::VerboseError; +#[derive(Debug)] pub enum ResponsePacket { DeviceState(DeviceStateResponse), DeviceInfo, // TODO diff --git a/soundcore-lib/src/packets/response/state.rs b/soundcore-lib/src/packets/response/state.rs index 2ce8095..ddd8ac2 100644 --- a/soundcore-lib/src/packets/response/state.rs +++ b/soundcore-lib/src/packets/response/state.rs @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize}; use crate::{ models::{ - AgeRange, Battery, ButtonModel, CustomHearID, SideTone, - SoundMode, SoundcoreFeatureFlags, TwsStatus, WearDetection, + AgeRange, Battery, ButtonModel, CustomHearID, EQConfiguration, SideTone, SoundMode, + SoundcoreFeatureFlags, TouchTone, TwsStatus, WearDetection, }, parsers::{SoundcoreParseError, SoundcoreParseResult}, }; @@ -13,11 +13,12 @@ use crate::{ /// This is a generalized version of the state responses for all devices /// All device-specific state responses should be able to be converted to this type /// Also, this must be impl Into -#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Hash, Default)] +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Hash)] pub struct DeviceStateResponse { pub feature_flags: BitFlags, pub battery: Battery, pub sound_mode: SoundMode, + pub eq: EQConfiguration, pub host_device: Option, pub tws_status: Option, pub button_model: Option, @@ -26,6 +27,7 @@ pub struct DeviceStateResponse { pub wear_detection: Option, pub hear_id: Option, pub age_range: Option, + pub touch_tone: Option, } // TODO diff --git a/soundcore-lib/src/packets/response/state/a3951.rs b/soundcore-lib/src/packets/response/state/a3951.rs index 79f6c96..36e19bc 100644 --- a/soundcore-lib/src/packets/response/state/a3951.rs +++ b/soundcore-lib/src/packets/response/state/a3951.rs @@ -66,6 +66,8 @@ impl From for DeviceStateResponse { wear_detection: Some(value.wear_detection), hear_id: Some(value.hear_id), age_range: Some(value.age_range), + touch_tone: Some(value.touch_tone), + eq: crate::models::EQConfiguration::Stereo(value.eq), } } } diff --git a/soundcore-lib/src/parsers.rs b/soundcore-lib/src/parsers.rs index c5c35fb..1b2359d 100644 --- a/soundcore-lib/src/parsers.rs +++ b/soundcore-lib/src/parsers.rs @@ -43,3 +43,6 @@ pub type SoundcoreParseResult<'a, T, E> = IResult<&'a [u8], T, E>; pub trait SoundcoreParseError<'a>: ParseError<&'a [u8]> + ContextError<&'a [u8]> {} impl<'a> SoundcoreParseError<'a> for nom::error::VerboseError<&'a [u8]> {} + +#[cfg(test)] +pub type TestParserError<'a> = nom::error::VerboseError<&'a [u8]>;