Skip to content

Commit

Permalink
feat: a3040 sound mode change
Browse files Browse the repository at this point in the history
  • Loading branch information
gmallios committed Apr 7, 2024
1 parent 10179db commit d66c9cc
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 46 deletions.
62 changes: 34 additions & 28 deletions manager-ui/src/components/SoundModeCard/soundModeCard.tsx
Original file line number Diff line number Diff line change
@@ -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 }) => {
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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
}))
})
}
/>
)}
Expand All @@ -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
}))
})
}
/>
)}
Expand All @@ -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
}))
})
}
/>
)}
</SliderSelectorWrapper>
</Grid>

{modeButtons && modeButtons.length > 0 && currentSoundModeKey && (
<Collapse in={modeButtons && modeButtons.length > 0} timeout="auto">
<ModeGroupButtons
buttons={modeButtons}
onClick={(value) =>
useUpdateDeviceSoundMode(deviceAddr, {
...selectedSoundMode,
[currentSoundModeKey]: { type: currentSoundModeType, value }
})
}
onClick={(value) => {
if (currentSoundModeKey) {
useUpdateDeviceSoundMode(deviceAddr, {
...selectedSoundMode,
[currentSoundModeKey]: { type: currentSoundModeType, value }
});
}
}}
selectedValue={currentSubValue}
/>
)}
</Collapse>
{hasCustomValueSlider && <h1> Custom</h1>}
{/* <SliderSubButtons layout={layout} position={position} /> */}
</Grid>
</Paper>
Expand All @@ -195,16 +200,17 @@ const ModeGroupButtons: React.FC<{
container
direction="row"
spacing={1}
sx={{ display: 'flex', justifyContent: 'space-evenly', pt: 2 }}></Grid>
sx={{ display: 'flex', justifyContent: 'space-evenly', pt: 2 }}>
{buttons.map((button) => (
<ModeGroupButton
key={button.value}
active={selectedValue === button.value}
onClick={() => onClick(button.value)}>
{button.title}
</ModeGroupButton>
))}
</Grid>
</Stack>
{buttons.map((button) => (
<ModeGroupButton
key={button.value}
active={selectedValue === button.value}
onClick={() => onClick(button.value)}>
{button.title}
</ModeGroupButton>
))}
</Grid>
);
};
Expand Down
1 change: 1 addition & 0 deletions soundcore-lib/src/ble/btleplug/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
29 changes: 26 additions & 3 deletions soundcore-lib/src/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<()> {
Expand Down
4 changes: 3 additions & 1 deletion soundcore-lib/src/devices/a3040.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod features;
mod sound_mode_update_command;

pub use features::*;
pub use features::*;
pub use sound_mode_update_command::*;
53 changes: 53 additions & 0 deletions soundcore-lib/src/devices/a3040/sound_mode_update_command.rs
Original file line number Diff line number Diff line change
@@ -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<u8> {
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,
]
);
}
}
2 changes: 1 addition & 1 deletion soundcore-lib/src/models/curr_sound_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ FromRepr,
Display,
Hash,
)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all = "lowercase")]
#[typeshare]
pub enum CurrentSoundMode {
ANC = 0,
Expand Down
3 changes: 2 additions & 1 deletion soundcore-lib/src/models/custom_anc_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
}
}

Expand Down
9 changes: 5 additions & 4 deletions soundcore-lib/src/models/sound_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -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(),
]
}
Expand Down
12 changes: 6 additions & 6 deletions soundcore-lib/src/packets.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions soundcore-lib/src/packets/command.rs
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
mod sound_mode;

pub use sound_mode::*;
23 changes: 23 additions & 0 deletions soundcore-lib/src/packets/command/sound_mode.rs
Original file line number Diff line number Diff line change
@@ -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<u8> {
match self.model {
SupportedModels::A3040 => A3040SoundModeUpdateCommand::new(self.sound_mode).bytes(),
// TODO: use a default comamnd A3951?
_ => panic!("Unsupported model"),
}
}
}
12 changes: 10 additions & 2 deletions soundcore-lib/src/packets/response.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use log::error;
use log::{error, warn};
use nom::error::VerboseError;

pub use info::*;
Expand All @@ -17,6 +17,7 @@ pub enum ResponsePacket {
DeviceState(TaggedData<DeviceStateResponse>),
SoundModeUpdate(SoundModeUpdateResponse),
DeviceInfo(DeviceInfoResponse),
Unknown
}

pub trait StateTransformationPacket {
Expand All @@ -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
},
})
}

Expand Down

0 comments on commit d66c9cc

Please sign in to comment.