Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/i18n/en/openscq30-lib.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ soundcore-a3939 = Soundcore Life P3
soundcore-a3935 = Soundcore Life A2 NC
soundcore-a3959 = Soundcore P30i / Soundcore R50i NC
soundcore-a3955 = Soundcore P40i
soundcore-a3957 = Soundcore Liberty 5
soundcore-development = Soundcore Development Information

general = General
Expand Down Expand Up @@ -193,3 +194,8 @@ percent = { $percent }%

immersive-experience = Immersive Experience
movie-mode = Movie Mode
enabled = Enabled
pressure-sensitivity = Pressure Sensitivity
softest = Softest
medium = Medium
firmest = Firmest
3 changes: 3 additions & 0 deletions lib/src/devices/device_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub enum DeviceModel {
SoundcoreA3939,
SoundcoreA3935,
SoundcoreA3955,
SoundcoreA3957,
SoundcoreA3959,
SoundcoreA3947,
SoundcoreA3948,
Expand Down Expand Up @@ -100,6 +101,7 @@ impl DeviceModel {
Self::SoundcoreA3949 => new_soundcore_device!(soundcore::a3949),
Self::SoundcoreA3951 => new_soundcore_device!(soundcore::a3951),
Self::SoundcoreA3955 => new_soundcore_device!(soundcore::a3955),
Self::SoundcoreA3957 => new_soundcore_device!(soundcore::a3957),
Self::SoundcoreA3959 => new_soundcore_device!(soundcore::a3959),
Self::SoundcoreDevelopment => new_soundcore_device!(soundcore::development),
}
Expand Down Expand Up @@ -143,6 +145,7 @@ impl DeviceModel {
Self::SoundcoreA3949 => new_soundcore_device!(soundcore::a3949),
Self::SoundcoreA3951 => new_soundcore_device!(soundcore::a3951),
Self::SoundcoreA3955 => new_soundcore_device!(soundcore::a3955),
Self::SoundcoreA3957 => new_soundcore_device!(soundcore::a3957),
Self::SoundcoreA3959 => new_soundcore_device!(soundcore::a3959),
Self::SoundcoreDevelopment => new_soundcore_device!(soundcore::development),
}
Expand Down
1 change: 1 addition & 0 deletions lib/src/devices/soundcore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod a3948;
pub mod a3949;
pub mod a3951;
pub mod a3955;
pub mod a3957;
pub mod a3959;
pub mod common;
pub mod development;
Expand Down
238 changes: 238 additions & 0 deletions lib/src/devices/soundcore/a3957.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
use std::collections::HashMap;

use openscq30_i18n::Translate;
use strum::{IntoStaticStr, VariantArray};

use crate::{
devices::soundcore::common::{
device::fetch_state_from_state_update_packet,
macros::soundcore_device,
modules::{
button_configuration::{
ButtonConfigurationSettings, ButtonDisableMode, ButtonSettings, COMMON_ACTIONS,
},
equalizer,
},
packet::outbound::{RequestState, ToPacket},
structures::button_configuration::{
ActionKind, Button, ButtonParseSettings, ButtonPressKind, EnabledFlagKind,
},
},
i18n::fl,
};

mod modules;
mod packets;
mod state;
pub mod structures;

soundcore_device!(
state::A3957State,
async |packet_io| {
fetch_state_from_state_update_packet::<
_,
state::A3957State,
packets::inbound::A3957StateUpdatePacket,
>(packet_io)
.await
},
async |builder| {
builder.module_collection().add_state_update();
builder.a3957_sound_modes();
builder
.equalizer_with_custom_hear_id_tws(equalizer::common_settings())
.await;
builder.button_configuration(&BUTTON_CONFIGURATION_SETTINGS);
builder.ambient_sound_mode_cycle();
builder.reset_button_configuration::<packets::inbound::A3957StateUpdatePacket>(
RequestState::default().to_packet(),
);

builder.limit_high_volume();

builder.auto_power_off(AutoPowerOffDuration::VARIANTS);
builder.touch_tone();
builder.low_battery_prompt();
builder.wearing_tone();
builder.wearing_detection();
builder.sound_leak_compensation();
builder.gaming_mode();

builder.tws_status();
builder.a3957_dual_battery(10);
builder.a3957_case_battery_level(10);
builder.serial_number_and_dual_firmware_version();
},
{
HashMap::from([(
RequestState::COMMAND,
packets::inbound::A3957StateUpdatePacket::default().to_packet(),
)])
},
);

pub const BUTTON_CONFIGURATION_SETTINGS: ButtonConfigurationSettings<8, 4> =
ButtonConfigurationSettings {
supports_set_all_packet: false,
ignore_enabled_flag: false,
order: [
Button::LeftSinglePress,
Button::RightSinglePress,
Button::LeftDoublePress,
Button::RightDoublePress,
Button::LeftTriplePress,
Button::RightTriplePress,
Button::LeftLongPress,
Button::RightLongPress,
],
settings: [
ButtonSettings {
parse_settings: ButtonParseSettings {
enabled_flag_kind: EnabledFlagKind::None,
action_kind: ActionKind::TwsLowBits,
},
button_id: 2,
press_kind: ButtonPressKind::Single,
available_actions: COMMON_ACTIONS,
disable_mode: ButtonDisableMode::IndividualDisable,
},
ButtonSettings {
parse_settings: ButtonParseSettings {
enabled_flag_kind: EnabledFlagKind::None,
action_kind: ActionKind::TwsLowBits,
},
button_id: 0,
press_kind: ButtonPressKind::Double,
available_actions: COMMON_ACTIONS,
disable_mode: ButtonDisableMode::IndividualDisable,
},
ButtonSettings {
parse_settings: ButtonParseSettings {
enabled_flag_kind: EnabledFlagKind::None,
action_kind: ActionKind::TwsLowBits,
},
button_id: 5,
press_kind: ButtonPressKind::Triple,
available_actions: COMMON_ACTIONS,
disable_mode: ButtonDisableMode::IndividualDisable,
},
ButtonSettings {
parse_settings: ButtonParseSettings {
enabled_flag_kind: EnabledFlagKind::None,
action_kind: ActionKind::TwsLowBits,
},
button_id: 1,
press_kind: ButtonPressKind::Long,
available_actions: COMMON_ACTIONS,
disable_mode: ButtonDisableMode::IndividualDisable,
},
],
};

#[derive(IntoStaticStr, VariantArray)]
#[allow(clippy::enum_variant_names)]
enum AutoPowerOffDuration {
#[strum(serialize = "10m")]
TenMinutes,
#[strum(serialize = "20m")]
TwentyMinutes,
#[strum(serialize = "30m")]
ThirtyMinutes,
#[strum(serialize = "60m")]
SixtyMinutes,
}

impl Translate for AutoPowerOffDuration {
fn translate(&self) -> String {
match self {
Self::TenMinutes => fl!("x-minutes", minutes = 10),
Self::TwentyMinutes => fl!("x-minutes", minutes = 20),
Self::ThirtyMinutes => fl!("x-minutes", minutes = 30),
Self::SixtyMinutes => fl!("x-minutes", minutes = 60),
}
}
}

#[cfg(test)]
mod tests {
use std::collections::HashMap;

use crate::{
DeviceModel,
devices::soundcore::common::{
device::{SoundcoreDeviceConfig, test_utils::TestSoundcoreDevice},
packet,
},
settings::{SettingId, Value},
};

#[tokio::test(start_paused = true)]
async fn test_with_liberty5_packet_from_issue_226() {
let device = TestSoundcoreDevice::new(
super::device_registry,
DeviceModel::SoundcoreA3957,
HashMap::from([(
packet::Command([1, 1]),
packet::Inbound::new(
packet::Command([1, 1]),
vec![
0x00, 0x01, 0x08, 0x08, 0x00, 0x00, 0x30, 0x33, 0x2e, 0x39, 0x30, 0x30,
0x33, 0x2e, 0x39, 0x30, 0x33, 0x39, 0x35, 0x37, 0x46, 0x34, 0x39, 0x44,
0x38, 0x41, 0x43, 0x32, 0x30, 0x33, 0x33, 0x34, 0x30, 0x30, 0x2e, 0x30,
0x30, 0x02, 0x00, 0x00, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x78, 0x78, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x01, 0x91, 0x82, 0x73, 0x77, 0x8b, 0x93, 0x8f, 0x96, 0x00, 0x00,
0x91, 0x82, 0x73, 0x77, 0x8b, 0x93, 0x8f, 0x96, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x91, 0x82, 0x73, 0x77, 0x8b, 0x93, 0x8f, 0x96, 0x00,
0x00, 0x91, 0x82, 0x73, 0x77, 0x8b, 0x93, 0x8f, 0x96, 0x00, 0x00, 0x00,
0x00, 0x0a, 0x66, 0x66, 0x32, 0x33, 0xff, 0xff, 0x44, 0x44, 0x33, 0x00,
0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x01, 0x01, 0x00, 0x01, 0x01,
0x02, 0x00, 0x5a, 0x00, 0x00, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00,
0x01, 0x01, 0x00, 0x01, 0xff, 0xff, 0xff, 0x03, 0xff, 0xff,
],
),
)]),
SoundcoreDeviceConfig::default(),
)
.await;

device.assert_setting_values([
(SettingId::FirmwareVersionLeft, "03.90".into()),
(SettingId::FirmwareVersionRight, "03.90".into()),
(SettingId::SerialNumber, "3957F49D8AC20334".into()),
(
SettingId::PresetEqualizerProfile,
Some("SoundcoreSignature").into(),
),
(SettingId::AncPersonalizedToEarCanal, true.into()),
(SettingId::BatteryLevelLeft, "9/10".into()),
(SettingId::BatteryLevelRight, "9/10".into()),
(SettingId::CaseBatteryLevel, "3/10".into()),
(SettingId::AmbientSoundMode, "NoiseCanceling".into()),
(SettingId::NoiseCancelingMode, "Manual".into()),
(SettingId::ManualNoiseCanceling, 5.into()),
(SettingId::TransportationMode, "Plane".into()),
(SettingId::WindNoiseSuppression, false.into()),
(SettingId::LeftSinglePress, Some("PlayPause").into()),
(SettingId::LeftDoublePress, Some("PreviousSong").into()),
(SettingId::LeftTriplePress, Value::OptionalString(None)),
(SettingId::LeftLongPress, Some("AmbientSoundMode").into()),
(SettingId::RightSinglePress, Some("PlayPause").into()),
(SettingId::RightDoublePress, Some("NextSong").into()),
(SettingId::RightTriplePress, Value::OptionalString(None)),
(SettingId::RightLongPress, Some("AmbientSoundMode").into()),
(SettingId::GamingMode, false.into()),
(SettingId::WearingDetection, true.into()),
(SettingId::SoundLeakCompensation, false.into()),
(SettingId::TouchTone, true.into()),
(SettingId::WearingTone, true.into()),
(SettingId::LowBatteryPrompt, true.into()),
(SettingId::LimitHighVolume, false.into()),
(SettingId::LimitHighVolumeRefreshRate, "RealTime".into()),
(SettingId::LimitHighVolumeDbLimit, 90.into()),
// (SettingId::DolbyAudio, Value::OptionalString(None)),
// (SettingId::InCallSoundAwareness, true.into()),
// (SettingId::EarbudPressureSensitivity, "Medium".into()),
]);
}
}
48 changes: 48 additions & 0 deletions lib/src/devices/soundcore/a3957/modules.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use openscq30_lib_has::Has;

use crate::{
api::connection::RfcommConnection,
devices::soundcore::common::{
device::SoundcoreDeviceBuilder,
structures::{CaseBatteryLevel, DualBattery},
},
};

use super::structures::{AncPersonalizedToEarCanal, SoundModes};

mod case_battery_level;
mod dual_battery;
mod sound_modes;

impl<ConnectionType, StateType> SoundcoreDeviceBuilder<ConnectionType, StateType>
where
ConnectionType: RfcommConnection + Send + Sync + 'static,
StateType: Has<SoundModes> + Has<AncPersonalizedToEarCanal> + Send + Sync + Clone + 'static,
{
pub fn a3957_sound_modes(&mut self) {
let packet_io_controller = self.packet_io_controller().clone();
self.module_collection()
.add_a3957_sound_modes(packet_io_controller);
}
}

impl<ConnectionType, StateType> SoundcoreDeviceBuilder<ConnectionType, StateType>
where
ConnectionType: RfcommConnection + Send + Sync + 'static,
StateType: Has<DualBattery> + Send + Sync + Clone + 'static,
{
pub fn a3957_dual_battery(&mut self, max_level: u8) {
self.module_collection().add_a3957_dual_battery(max_level);
}
}

impl<ConnectionType, StateType> SoundcoreDeviceBuilder<ConnectionType, StateType>
where
ConnectionType: RfcommConnection + Send + Sync + 'static,
StateType: Has<CaseBatteryLevel> + Send + Sync + Clone + 'static,
{
pub fn a3957_case_battery_level(&mut self, max_level: u8) {
self.module_collection()
.add_a3957_case_battery_level(max_level);
}
}
30 changes: 30 additions & 0 deletions lib/src/devices/soundcore/a3957/modules/case_battery_level.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
mod setting_handler;

use openscq30_lib_has::Has;
use strum::{EnumIter, EnumString, IntoStaticStr};

use crate::{
api::settings::{CategoryId, SettingId},
devices::soundcore::common::{modules::ModuleCollection, structures::CaseBatteryLevel},
macros::enum_subset,
};

enum_subset!(
SettingId,
#[derive(EnumString, EnumIter, IntoStaticStr)]
enum CaseBatteryLevelSetting {
CaseBatteryLevel,
}
);

impl<T> ModuleCollection<T>
where
T: Has<CaseBatteryLevel> + Clone + Send + Sync,
{
pub fn add_a3957_case_battery_level(&mut self, max_level: u8) {
self.setting_manager.add_handler(
CategoryId::DeviceInformation,
setting_handler::CaseBatteryLevelSettingHandler::new(max_level),
);
}
}
Loading