Skip to content

Commit

Permalink
fix(lib): a3933 extra two bands quirk
Browse files Browse the repository at this point in the history
There are some differences, so the A3945 code should not be reused.

The hear id extra two bands need to be fixed still, but it doesn't
matter much since none of the clients make use of this lib feature.
  • Loading branch information
Oppzippy committed Jan 3, 2024
1 parent 11f4807 commit 67102c5
Show file tree
Hide file tree
Showing 2 changed files with 265 additions and 32 deletions.
226 changes: 221 additions & 5 deletions lib/src/devices/a3933/device_profile.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
use std::sync::Arc;
use std::{
collections::HashMap,
sync::{
atomic::{self, AtomicI32},
Arc,
},
};

use nom::error::VerboseError;

use crate::{
device_profiles::{
DeviceProfile, NoiseCancelingModeType, SoundModeProfile, TransparencyModeType,
},
devices::a3945::device_profile::A3945Dispatcher,
devices::standard::{
packets::outbound::{OutboundPacket, OutboundPacketBytes, SetEqualizerPacket},
state::DeviceState,
structures::{EqualizerConfiguration, STATE_UPDATE},
},
soundcore_device::device::{
device_command_dispatcher::DeviceCommandDispatcher,
packet_handlers::state_update::state_update_handler, soundcore_command::CommandResponse,
},
};

use super::packets::inbound::take_a3933_state_update_packet;

pub const A3933_DEVICE_PROFILE: DeviceProfile = DeviceProfile {
sound_mode: Some(SoundModeProfile {
noise_canceling_mode_type: NoiseCancelingModeType::Basic,
transparency_mode_type: TransparencyModeType::Custom,
}),
has_hear_id: false,
has_hear_id: true,
num_equalizer_channels: 2,
num_equalizer_bands: 8,
has_dynamic_range_compression: true,
Expand All @@ -22,6 +40,204 @@ pub const A3933_DEVICE_PROFILE: DeviceProfile = DeviceProfile {
has_touch_tone: false,
has_auto_power_off: false,
has_ambient_sound_mode_cycle: true,
// The A3933 has the same quirks as the A3945
custom_dispatchers: Some(|| Arc::new(A3945Dispatcher::default())),
custom_dispatchers: Some(|| Arc::new(A3933Dispatcher::default())),
};

#[derive(Debug, Default)]
pub struct A3933Dispatcher {
// The official app only displays 8 bands, so I have no idea what bands 9 and 10 do. We'll just keep track
// of their initial value and resend that.
band_9_and_10_left_and_right: Arc<AtomicI32>,
}

impl DeviceCommandDispatcher for A3933Dispatcher {
fn packet_handlers(
&self,
) -> HashMap<[u8; 7], Box<dyn Fn(&[u8], DeviceState) -> DeviceState + Send + Sync>> {
let band_9_and_10 = self.band_9_and_10_left_and_right.to_owned();
let mut handlers: HashMap<
[u8; 7],
Box<dyn Fn(&[u8], DeviceState) -> DeviceState + Send + Sync>,
> = HashMap::new();

handlers.insert(
STATE_UPDATE,
Box::new(move |packet_bytes, state| {
let result = take_a3933_state_update_packet::<VerboseError<_>>(packet_bytes);
let packet = match result {
Ok((_, packet)) => packet,
Err(err) => {
tracing::error!("failed to parse packet: {err:?}");
return state;
}
};
let squashed_bytes = i32::from_ne_bytes([
packet.left_band_9_and_10[0],
packet.left_band_9_and_10[1],
packet.right_band_9_and_10[0],
packet.right_band_9_and_10[1],
]);
band_9_and_10.store(squashed_bytes, atomic::Ordering::Relaxed);

// We only needed to capture information. The actual state transformation is passed on to the default handler..
state_update_handler(packet_bytes, state)
}),
);

handlers
}

fn set_equalizer_configuration(
&self,
state: DeviceState,
equalizer_configuration: EqualizerConfiguration,
) -> crate::Result<CommandResponse> {
let left_channel = &equalizer_configuration;
let right_channel = &equalizer_configuration;
let band_9_and_10 = self
.band_9_and_10_left_and_right
.load(atomic::Ordering::Relaxed)
.to_ne_bytes();

let packet_bytes = CustomSetEqualizerPacket {
left_channel,
right_channel,
left_band_9_and_10: [band_9_and_10[0], band_9_and_10[1]],
right_band_9_and_10: [band_9_and_10[2], band_9_and_10[3]],
}
.bytes();

Ok(CommandResponse {
packets: vec![packet_bytes],
new_state: DeviceState {
equalizer_configuration,
..state
},
})
}
}

struct CustomSetEqualizerPacket<'a> {
pub left_channel: &'a EqualizerConfiguration,
pub right_channel: &'a EqualizerConfiguration,
pub left_band_9_and_10: [u8; 2],
pub right_band_9_and_10: [u8; 2],
}

impl<'a> OutboundPacket for CustomSetEqualizerPacket<'a> {
fn command(&self) -> [u8; 7] {
SetEqualizerPacket::COMMAND
}

fn body(&self) -> Vec<u8> {
self.left_channel
.profile_id()
.to_le_bytes()
.into_iter()
.chain(self.left_channel.volume_adjustments().bytes())
.chain(self.left_band_9_and_10)
.chain(self.right_channel.volume_adjustments().bytes())
.chain(self.right_band_9_and_10)
.collect::<Vec<_>>()
}
}

#[cfg(test)]
mod tests {
use nom::error::VerboseError;

use crate::devices::{
a3933::{
device_profile::{CustomSetEqualizerPacket, A3933_DEVICE_PROFILE},
packets::inbound::take_a3933_state_update_packet,
},
standard::{
packets::{
inbound::{state_update_packet::StateUpdatePacket, take_inbound_packet_body},
outbound::{OutboundPacket, OutboundPacketBytes},
},
state::DeviceState,
structures::{EqualizerConfiguration, PresetEqualizerProfile, STATE_UPDATE},
},
};

struct A3933TestStateUpdatePacket {
body: Vec<u8>,
}
impl OutboundPacket for A3933TestStateUpdatePacket {
fn command(&self) -> [u8; 7] {
STATE_UPDATE
}

fn body(&self) -> Vec<u8> {
self.body.to_owned()
}
}

#[test]
fn it_remembers_eq_band_9_and_10_values() {
let data = A3933TestStateUpdatePacket {
body: vec![
0x00, // host device
0x00, // tws status
0x00, 0x00, 0x00, 0x00, // dual battery
b'0', b'0', b'.', b'0', b'0', // left firmware version
b'0', b'0', b'.', b'0', b'0', // right firmware version
b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0',
b'0', b'0', // serial number
0x00, 0x00, // eq profile id
120, 120, 120, 120, 120, 120, 120, 120, 121, 122, // left eq
120, 120, 120, 120, 120, 120, 120, 120, 123, 124, // right eq
0x00, // age range
0x01, // hear id enabled
120, 120, 120, 120, 120, 120, 120, 120, 125, 126, // left hear id
120, 120, 120, 120, 120, 120, 120, 120, 127, 0, // right hear id
0x00, 0x00, 0x00, 0x00, // hear id time
0x00, // hear id type
120, 120, 120, 120, 120, 120, 120, 120, 1, 2, // left hear id custom
120, 120, 120, 120, 120, 120, 120, 120, 3, 4, // right hear id custom
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, // custom button model
0x07, // ambient sound mode cycle
0x00, // ambient sound mode
0x00, // noise canceling mode
0x00, // transparency mode
0x00, // custom noise canceling
0x00, // touch tone
0x00, // wear detection
0x00, // gaming mode
0x00, // case battery
0x00, // ?
0x00, // device color
0x00, // wind noise detection
],
}
.bytes();

let body = take_inbound_packet_body(&data).unwrap().1;
let state_update = take_a3933_state_update_packet::<VerboseError<_>>(body)
.unwrap()
.1;
let state: DeviceState = StateUpdatePacket::from(state_update).into();
let dispatchers = A3933_DEVICE_PROFILE.custom_dispatchers.unwrap()();
let state = (&dispatchers.packet_handlers()[&STATE_UPDATE])(&body, state);

let equalizer_configuration =
EqualizerConfiguration::new_from_preset_profile(PresetEqualizerProfile::TrebleReducer);
let command_response = dispatchers
.set_equalizer_configuration(state, equalizer_configuration.to_owned())
.unwrap();

assert_eq!(1, command_response.packets.len());
assert_eq!(
&CustomSetEqualizerPacket {
left_channel: &equalizer_configuration,
right_channel: &equalizer_configuration,
left_band_9_and_10: [121, 122],
right_band_9_and_10: [123, 124],
}
.bytes(),
command_response.packets.first().unwrap(),
);
}
}
71 changes: 44 additions & 27 deletions lib/src/devices/a3933/packets/inbound/state_update_packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ use crate::devices::{
take_ambient_sound_mode_cycle, take_battery_level, take_bool,
take_custom_button_model, take_custom_hear_id_without_music_type,
take_dual_battery, take_equalizer_configuration, take_firmware_version,
take_serial_number, take_sound_modes, ParseResult,
take_serial_number, take_sound_modes, take_volume_adjustments, ParseResult,
},
},
structures::{
AmbientSoundModeCycle, BatteryLevel, CustomButtonModel, CustomHearId, DualBattery,
EqualizerConfiguration, FirmwareVersion, SerialNumber, SoundModes,
EqualizerConfiguration, FirmwareVersion, HearId, SerialNumber, SoundModes,
},
},
};
Expand All @@ -29,36 +29,39 @@ use crate::devices::{
// Despite EQ being 10 bands, only the first 8 seem to be used?
#[derive(Debug, Clone, PartialEq)]
pub struct A3933StateUpdatePacket {
host_device: u8,
tws_status: bool,
battery: DualBattery,
left_firmware: FirmwareVersion,
right_firmware: FirmwareVersion,
serial_number: SerialNumber,
equalizer_configuration: EqualizerConfiguration, // 10 bands mono
age_range: u8,
hear_id: CustomHearId, // 10 bands
custom_button_model: CustomButtonModel,
ambient_sound_mode_cycle: AmbientSoundModeCycle,
sound_modes: SoundModes,
touch_tone_switch: bool,
wear_detection_switch: bool,
game_mode_switch: bool,
charging_case_battery_level: BatteryLevel,
device_color: u8,
wind_noise_detection: bool,
pub host_device: u8,
pub tws_status: bool,
pub battery: DualBattery,
pub left_firmware: FirmwareVersion,
pub right_firmware: FirmwareVersion,
pub serial_number: SerialNumber,
pub left_equalizer_configuration: EqualizerConfiguration,
pub left_band_9_and_10: [u8; 2],
pub right_equalizer_configuration: EqualizerConfiguration,
pub right_band_9_and_10: [u8; 2],
pub age_range: u8,
pub hear_id: CustomHearId, // 10 bands
pub custom_button_model: CustomButtonModel,
pub ambient_sound_mode_cycle: AmbientSoundModeCycle,
pub sound_modes: SoundModes,
pub touch_tone_switch: bool,
pub wear_detection_switch: bool,
pub game_mode_switch: bool,
pub charging_case_battery_level: BatteryLevel,
pub device_color: u8,
pub wind_noise_detection: bool,
}

impl From<A3933StateUpdatePacket> for StateUpdatePacket {
fn from(packet: A3933StateUpdatePacket) -> Self {
Self {
device_profile: A3933_DEVICE_PROFILE,
battery: packet.battery.into(),
equalizer_configuration: packet.equalizer_configuration,
equalizer_configuration: packet.left_equalizer_configuration,
sound_modes: Some(packet.sound_modes),
age_range: None,
gender: None,
hear_id: None,
hear_id: Some(HearId::Custom(packet.hear_id)),
custom_button_model: Some(packet.custom_button_model),
firmware_version: Some(packet.left_firmware.min(packet.right_firmware)),
serial_number: Some(packet.serial_number),
Expand All @@ -80,8 +83,10 @@ pub fn take_a3933_state_update_packet<'a, E: ParseError<&'a [u8]> + ContextError
take_firmware_version,
take_firmware_version,
take_serial_number,
take_equalizer_configuration(10),
take(10usize), // ??, maybe stereo eq?
take_equalizer_configuration(8),
take(2usize),
take_volume_adjustments(8),
take(2usize),
le_u8,
take_custom_hear_id_without_music_type(10),
take_custom_button_model,
Expand All @@ -96,8 +101,10 @@ pub fn take_a3933_state_update_packet<'a, E: ParseError<&'a [u8]> + ContextError
left_firmware,
right_firmware,
serial_number,
equalizer_configuration, // 10 bands mono
_,
left_equalizer_configuration,
left_band_9_and_10,
right_volume_adjustments,
right_band_9_and_10,
age_range,
hear_id,
custom_button_model,
Expand All @@ -112,7 +119,17 @@ pub fn take_a3933_state_update_packet<'a, E: ParseError<&'a [u8]> + ContextError
left_firmware,
right_firmware,
serial_number,
equalizer_configuration,
right_equalizer_configuration: if left_equalizer_configuration
.preset_profile()
.is_some()
{
left_equalizer_configuration.to_owned()
} else {
EqualizerConfiguration::new_custom_profile(right_volume_adjustments)
},
right_band_9_and_10: right_band_9_and_10.try_into().unwrap(),
left_equalizer_configuration,
left_band_9_and_10: left_band_9_and_10.try_into().unwrap(),
age_range,
hear_id,
custom_button_model,
Expand Down

0 comments on commit 67102c5

Please sign in to comment.