diff --git a/manager-ui/src/components/SoundModeCard/soundModeCard.tsx b/manager-ui/src/components/SoundModeCard/soundModeCard.tsx
index 16a604c..2e5eebc 100644
--- a/manager-ui/src/components/SoundModeCard/soundModeCard.tsx
+++ b/manager-ui/src/components/SoundModeCard/soundModeCard.tsx
@@ -1,10 +1,10 @@
-import { Button, Grid, Icon, Paper, Stack, styled } from '@mui/material';
+import { Button, Collapse, Grid, Icon, Paper, Stack, styled } from '@mui/material';
import { useSoundcoreStore } from '@stores/useSoundcoreStore';
import ANCIcon from '../../assets/ambient_icon_anc.png';
import NormalIcon from '../../assets/ambient_icon_off.png';
import TransIcon from '../../assets/ambient_icon_trans.png';
import React, { useCallback, useEffect, useState } from 'react';
-import { CurrentSoundMode, DeviceFeatureSet, SoundMode } from '@generated-types/soundcore-lib.d.ts';
+import { CurrentSoundMode, DeviceFeatureSet, SoundMode } from '@generated-types/soundcore-lib';
import { useUpdateDeviceSoundMode } from '@hooks/useDeviceCommand';
export const SoundModeCard: React.FC<{ features: DeviceFeatureSet }> = ({ features }) => {
@@ -98,10 +98,12 @@ export const SoundModeCard: React.FC<{ features: DeviceFeatureSet }> = ({ featur
const currentSoundModeKey = mapModeToCurrentSoundModeKey(selectedSoundMode.current);
let currentSubValue;
let currentSoundModeType: string;
+ let hasCustomValueSlider: boolean = false;
if (currentSoundModeKey) {
currentSubValue = selectedSoundMode[currentSoundModeKey].value;
currentSoundModeType = selectedSoundMode[currentSoundModeKey].type;
+ hasCustomValueSlider = currentSoundModeType.toLowerCase() === 'custom';
}
return (
@@ -129,10 +131,10 @@ export const SoundModeCard: React.FC<{ features: DeviceFeatureSet }> = ({ featur
icon={ANCIcon}
setSliderIcon={setIcon}
setSliderPosition={() =>
- setSelectedSoundMode((prev) => ({
- ...prev,
+ useUpdateDeviceSoundMode(deviceAddr, {
+ ...selectedSoundMode,
current: CurrentSoundMode.ANC
- }))
+ })
}
/>
)}
@@ -142,10 +144,10 @@ export const SoundModeCard: React.FC<{ features: DeviceFeatureSet }> = ({ featur
icon={NormalIcon}
setSliderIcon={setIcon}
setSliderPosition={() =>
- setSelectedSoundMode((prev) => ({
- ...prev,
+ useUpdateDeviceSoundMode(deviceAddr, {
+ ...selectedSoundMode,
current: CurrentSoundMode.Normal
- }))
+ })
}
/>
)}
@@ -155,28 +157,31 @@ export const SoundModeCard: React.FC<{ features: DeviceFeatureSet }> = ({ featur
icon={TransIcon}
setSliderIcon={setIcon}
setSliderPosition={() =>
- setSelectedSoundMode((prev) => ({
- ...prev,
+ useUpdateDeviceSoundMode(deviceAddr, {
+ ...selectedSoundMode,
current: CurrentSoundMode.Transparency
- }))
+ })
}
/>
)}
- {modeButtons && modeButtons.length > 0 && currentSoundModeKey && (
+ 0} timeout="auto">
- useUpdateDeviceSoundMode(deviceAddr, {
- ...selectedSoundMode,
- [currentSoundModeKey]: { type: currentSoundModeType, value }
- })
- }
+ onClick={(value) => {
+ if (currentSoundModeKey) {
+ useUpdateDeviceSoundMode(deviceAddr, {
+ ...selectedSoundMode,
+ [currentSoundModeKey]: { type: currentSoundModeType, value }
+ });
+ }
+ }}
selectedValue={currentSubValue}
/>
- )}
+
+ {hasCustomValueSlider &&
Custom
}
{/* */}
@@ -195,16 +200,17 @@ const ModeGroupButtons: React.FC<{
container
direction="row"
spacing={1}
- sx={{ display: 'flex', justifyContent: 'space-evenly', pt: 2 }}>
+ sx={{ display: 'flex', justifyContent: 'space-evenly', pt: 2 }}>
+ {buttons.map((button) => (
+ onClick(button.value)}>
+ {button.title}
+
+ ))}
+
- {buttons.map((button) => (
- onClick(button.value)}>
- {button.title}
-
- ))}
);
};
diff --git a/soundcore-lib/src/ble/btleplug/connection.rs b/soundcore-lib/src/ble/btleplug/connection.rs
index ef90f91..1191bf3 100644
--- a/soundcore-lib/src/ble/btleplug/connection.rs
+++ b/soundcore-lib/src/ble/btleplug/connection.rs
@@ -173,6 +173,7 @@ impl BLEConnection for BtlePlugConnection {
self.write_characteristic.clone(),
bytes.to_owned(),
);
+ trace!("Writing bytes: {:#X?} to characteristic: {:?}", bytes, self.write_characteristic);
tokio::spawn(async move {
peripheral
.write(&writer_characteristic, &bytes, write_type.into())
diff --git a/soundcore-lib/src/device.rs b/soundcore-lib/src/device.rs
index e4117a8..41fa4b5 100644
--- a/soundcore-lib/src/device.rs
+++ b/soundcore-lib/src/device.rs
@@ -12,7 +12,7 @@ use crate::error::{SoundcoreLibError, SoundcoreLibResult};
use crate::models::{EQConfiguration, SoundMode};
use crate::packets::{
DeviceStateResponse, RequestPacketBuilder, RequestPacketKind, ResponsePacket,
- StateTransformationPacket,
+ SoundModeCommandBuilder, StateTransformationPacket,
};
use crate::parsers::TaggedData;
use crate::types::SupportedModels;
@@ -41,7 +41,7 @@ where
);
let state_sender = Arc::new(Mutex::new(watch::channel(initial_state.data.clone()).0));
let packet_handler = Self::spawn_packet_handler(state_sender.to_owned(), byte_channel);
-
+
let model = if let Some(sn) = initial_state.data.serial {
sn.to_model().unwrap_or(initial_state.tag)
} else {
@@ -134,6 +134,10 @@ where
tokio::task::spawn(async move {
while let Some(bytes) = byte_channel.recv().await {
trace!("Received bytes: {:?}", bytes);
+ if bytes.is_empty() {
+ continue;
+ }
+
match ResponsePacket::from_bytes(&bytes) {
Ok(packet) => {
let state_sender = state_sender.lock().await;
@@ -165,7 +169,26 @@ where
}
pub async fn set_sound_mode(&self, sound_mode: SoundMode) -> SoundcoreLibResult<()> {
- todo!()
+ // TODO: perform some validation on the sound mode/features
+ // TODO: Check if https://github.com/Oppzippy/OpenSCQ30/blob/dec0ad3f2659205ff6efdb8d12ec333ba9f3a0b4/lib/src/soundcore_device/device/device_command_dispatcher.rs#L28
+ // is valid for all models or device-specific
+ let command = SoundModeCommandBuilder::new(sound_mode, self.model).build();
+ let latest_state = self.latest_state().await;
+
+ if latest_state.sound_mode == sound_mode {
+ return Ok(());
+ }
+
+ self.connection
+ .write(&command, WriteType::WithoutResponse)
+ .await?;
+
+ let state_sender = self.state_channel.lock().await;
+ let mut new_state = state_sender.borrow().clone();
+ new_state.sound_mode = sound_mode;
+ state_sender.send_replace(new_state);
+
+ Ok(())
}
pub async fn set_eq(&self, eq: EQConfiguration) -> SoundcoreLibResult<()> {
diff --git a/soundcore-lib/src/devices/a3040.rs b/soundcore-lib/src/devices/a3040.rs
index 02c6244..aba382c 100644
--- a/soundcore-lib/src/devices/a3040.rs
+++ b/soundcore-lib/src/devices/a3040.rs
@@ -1,3 +1,5 @@
mod features;
+mod sound_mode_update_command;
-pub use features::*;
\ No newline at end of file
+pub use features::*;
+pub use sound_mode_update_command::*;
\ No newline at end of file
diff --git a/soundcore-lib/src/devices/a3040/sound_mode_update_command.rs b/soundcore-lib/src/devices/a3040/sound_mode_update_command.rs
new file mode 100644
index 0000000..7ec3dcd
--- /dev/null
+++ b/soundcore-lib/src/devices/a3040/sound_mode_update_command.rs
@@ -0,0 +1,53 @@
+use crate::{models::SoundMode, packets::Packet};
+
+pub struct A3040SoundModeUpdateCommand {
+ sound_mode: SoundMode,
+}
+
+impl A3040SoundModeUpdateCommand {
+ pub fn new(sound_mode: SoundMode) -> Self {
+ Self { sound_mode }
+ }
+}
+
+impl Packet for A3040SoundModeUpdateCommand {
+ fn command(&self) -> [u8; 7] {
+ [0x08, 0xEE, 0x00, 0x00, 0x00, 0x06, 0x81]
+ }
+
+ fn payload(&self) -> Vec {
+ self.sound_mode.to_bytes_with_custom_transparency().to_vec()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::vec;
+
+ use super::*;
+ use crate::models::{
+ ANCMode, AdaptiveANCMode, CurrentSoundMode, CustomANCValue, CustomTransparencyValue,
+ CustomizableTransparencyMode, TransparencyMode,
+ };
+
+ #[test]
+ fn test_sound_mode_update_command() {
+ let command = A3040SoundModeUpdateCommand {
+ sound_mode: SoundMode {
+ current: CurrentSoundMode::ANC,
+ anc_mode: ANCMode::Adaptive(AdaptiveANCMode::Adaptive),
+ custom_anc: CustomANCValue::from_u8(5),
+ trans_mode: TransparencyMode::Customizable(CustomizableTransparencyMode::Custom),
+ custom_trans: Some(CustomTransparencyValue::from_u8(3)),
+ },
+ };
+
+ assert_eq!(
+ command.bytes(),
+ vec![
+ 0x08, 0xee, 0x00, 0x00, 0x00, 0x06, 0x81, 0x10, 0x00, 0x00, 0x51, 0x01, 0x01, 0x00,
+ 0x03, 0xe3,
+ ]
+ );
+ }
+}
diff --git a/soundcore-lib/src/models/curr_sound_mode.rs b/soundcore-lib/src/models/curr_sound_mode.rs
index 3a85bf7..c59a8b0 100644
--- a/soundcore-lib/src/models/curr_sound_mode.rs
+++ b/soundcore-lib/src/models/curr_sound_mode.rs
@@ -18,7 +18,7 @@ FromRepr,
Display,
Hash,
)]
-#[serde(rename_all = "camelCase")]
+#[serde(rename_all = "lowercase")]
#[typeshare]
pub enum CurrentSoundMode {
ANC = 0,
diff --git a/soundcore-lib/src/models/custom_anc_value.rs b/soundcore-lib/src/models/custom_anc_value.rs
index 8ba29cb..1fd117b 100644
--- a/soundcore-lib/src/models/custom_anc_value.rs
+++ b/soundcore-lib/src/models/custom_anc_value.rs
@@ -11,7 +11,8 @@ impl CustomANCValue {
trace!("CustomANC::from_u8({})", value);
match value {
255 => CustomANCValue(255),
- _ => CustomANCValue(value.clamp(0, 10)),
+ // TODO: Check if any other device has a different range and implement it
+ _ => CustomANCValue(value.clamp(0, 5)),
}
}
diff --git a/soundcore-lib/src/models/sound_mode.rs b/soundcore-lib/src/models/sound_mode.rs
index f641b91..35851ca 100644
--- a/soundcore-lib/src/models/sound_mode.rs
+++ b/soundcore-lib/src/models/sound_mode.rs
@@ -6,7 +6,7 @@ use crate::models::custom_trans_value::CustomTransparencyValue;
use super::{ANCMode, CurrentSoundMode, CustomANCValue, TransparencyMode};
#[derive(
-Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Default, Hash,
+ Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Default, Hash,
)]
#[typeshare]
#[serde(rename_all = "camelCase", tag = "type")]
@@ -28,12 +28,13 @@ impl SoundMode {
]
}
- pub fn to_bytes_with_custom_transparency(&self) -> [u8; 5] {
+ pub fn to_bytes_with_custom_transparency(&self) -> [u8; 6] {
[
self.current.as_u8(),
- self.anc_mode.as_u8(),
+ (self.custom_anc.as_u8() << 4) | 0x01, // TODO: 0x01 mask is unknown if it is constant
self.trans_mode.as_u8(),
- self.custom_anc.as_u8(),
+ self.anc_mode.as_u8(),
+ 0x00,
self.custom_trans.unwrap_or_default().as_u8(),
]
}
diff --git a/soundcore-lib/src/packets.rs b/soundcore-lib/src/packets.rs
index 510d638..bca613d 100644
--- a/soundcore-lib/src/packets.rs
+++ b/soundcore-lib/src/packets.rs
@@ -1,12 +1,12 @@
-pub use request::*;
-pub use response::*;
-
-use crate::parsers::generate_checksum;
-
mod command;
mod request;
mod response;
+use crate::parsers::generate_checksum;
+pub use command::*;
+pub use request::*;
+pub use response::*;
+
const PACKET_SIZE_LENGTH: usize = 2;
const CHECKSUM_BIT_LENGTH: usize = 1;
@@ -18,7 +18,7 @@ pub trait Packet {
let length_bytes: [u8; PACKET_SIZE_LENGTH] =
((command.len() + PACKET_SIZE_LENGTH + payload.len() + CHECKSUM_BIT_LENGTH) as u16)
.to_le_bytes();
- let mut bytes = vec![command.to_vec(), length_bytes.to_vec(), payload].concat();
+ let mut bytes = [command.to_vec(), length_bytes.to_vec(), payload].concat();
bytes.push(generate_checksum(&bytes));
bytes
diff --git a/soundcore-lib/src/packets/command.rs b/soundcore-lib/src/packets/command.rs
index 8b13789..117872c 100644
--- a/soundcore-lib/src/packets/command.rs
+++ b/soundcore-lib/src/packets/command.rs
@@ -1 +1,3 @@
+mod sound_mode;
+pub use sound_mode::*;
\ No newline at end of file
diff --git a/soundcore-lib/src/packets/command/sound_mode.rs b/soundcore-lib/src/packets/command/sound_mode.rs
new file mode 100644
index 0000000..5033135
--- /dev/null
+++ b/soundcore-lib/src/packets/command/sound_mode.rs
@@ -0,0 +1,23 @@
+use crate::{
+ devices::A3040SoundModeUpdateCommand, models::SoundMode, packets::Packet,
+ types::SupportedModels,
+};
+
+pub struct SoundModeCommandBuilder {
+ sound_mode: SoundMode,
+ model: SupportedModels,
+}
+
+impl SoundModeCommandBuilder {
+ pub fn new(sound_mode: SoundMode, model: SupportedModels) -> Self {
+ Self { sound_mode, model }
+ }
+
+ pub fn build(self) -> Vec {
+ match self.model {
+ SupportedModels::A3040 => A3040SoundModeUpdateCommand::new(self.sound_mode).bytes(),
+ // TODO: use a default comamnd A3951?
+ _ => panic!("Unsupported model"),
+ }
+ }
+}
diff --git a/soundcore-lib/src/packets/response.rs b/soundcore-lib/src/packets/response.rs
index 6279a62..329ea29 100644
--- a/soundcore-lib/src/packets/response.rs
+++ b/soundcore-lib/src/packets/response.rs
@@ -1,4 +1,4 @@
-use log::error;
+use log::{error, warn};
use nom::error::VerboseError;
pub use info::*;
@@ -17,6 +17,7 @@ pub enum ResponsePacket {
DeviceState(TaggedData),
SoundModeUpdate(SoundModeUpdateResponse),
DeviceInfo(DeviceInfoResponse),
+ Unknown
}
pub trait StateTransformationPacket {
@@ -36,7 +37,14 @@ impl ResponsePacket {
Self::SoundModeUpdate(parse_sound_mode_update_packet(bytes)?.1)
}
ResponsePacketKind::InfoUpdate => Self::DeviceInfo(parse_device_info_packet(bytes)?.1),
- _ => unimplemented!(),
+ _ => {
+ // TODO: Have an array of Acks and handle those properly
+ error!(
+ "Unexpected or unhandled packet kind {:?} and bytes {:?}",
+ packet_header.kind, bytes
+ );
+ ResponsePacket::Unknown
+ },
})
}