Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: A3027 Implementation #10

Merged
merged 25 commits into from
Feb 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
08615cc
Test A3027 parsing
gmallios Jan 20, 2023
bf20be6
chore: Add push gh workflow
gmallios Jan 20, 2023
aa10f76
try to use DeviceStatus for level/charging
gmallios Jan 20, 2023
fe87929
choire: Update gh workflow
gmallios Jan 20, 2023
10e898c
chore: fix workflow
gmallios Jan 20, 2023
15fe3cf
chore: don't prevent exit on other platforms except macOS
gmallios Jan 20, 2023
0b8fa12
chore: Add A3027 frontend type
gmallios Jan 20, 2023
4ac6e82
feat(A3027): Introduce A3027
gmallios Jan 20, 2023
2f5f810
chore: enable devtools
gmallios Jan 20, 2023
216023e
refactor: prepare for A3027 support
gmallios Jan 20, 2023
769f9b4
ci: update push.yml
gmallios Jan 20, 2023
bac1b55
ci: enable cache on failure
gmallios Jan 20, 2023
991bdcc
ci: Change Cargo caching layer
gmallios Jan 20, 2023
1ae0204
ci: update push.yml
gmallios Jan 20, 2023
b58fbbe
fix: disable A3027 response verification
gmallios Jan 20, 2023
ed60ecd
ci: use npm instead of yarn
gmallios Jan 20, 2023
388518c
ci: update push.yml
gmallios Jan 20, 2023
a71b0ca
fix: Disable DRC for A3027
gmallios Jan 20, 2023
8b52793
feat(A3027): Handle A3027 in OverviewCard
gmallios Jan 20, 2023
5011f40
ci: Update Push Workflow [skip ci]
gmallios Jan 20, 2023
460924e
fix: Switch EQ command to Non-DRC one
gmallios Jan 21, 2023
e43881d
fix: Add new EQ command for A3027
gmallios Jan 21, 2023
2ea038b
fix: Update set_eq implementation for A3027
gmallios Jan 22, 2023
a33c919
docs: Add some EQ Indexes
gmallios Feb 13, 2023
c915a26
docs: Bump patch version
gmallios Feb 13, 2023
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
59 changes: 59 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: 'Push - Build'

on: push

jobs:
build-tauri:
strategy:
fail-fast: false
matrix:
platform: [windows-latest]

runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3

- name: ⏬ Install Rust stable
uses: dtolnay/rust-toolchain@stable

- name: ⚡ Set up cargo cache
uses: actions/cache@v3
continue-on-error: false
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
src-tauri/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo

- name: 🔄 Sync node version
uses: actions/setup-node@v3
with:
node-version: 'lts/*'
cache: 'npm' # Set this to npm, yarn or pnpm.


- name: ⏬ Install Ubuntu dependencies
if: matrix.platform == 'ubuntu-20.04'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf

- name: 📥 Install Node Dependencies
run: npm install && npm run build

- name: 🔨 Build
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: ⏫ Upload artifact
uses: actions/upload-artifact@v3
with:
name: |
SoundcoreManager-${{ matrix.platform }}
path: |
src-tauri/target/${{ matrix.target }}/release/*.exe
231 changes: 231 additions & 0 deletions soundcore-lib/src/devices/A3027Device.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
use async_trait::async_trait;
use bluetooth_lib::{platform::RFCOMM, BluetoothAdrr, RFCOMMClient};
use log::debug;
use tokio::time::sleep;

use crate::{
base::{SoundcoreANC, SoundcoreDevice, SoundcoreEQ, SoundcoreHearID, SoundcoreLDAC},
error::SoundcoreError,
statics::*,
types::{
ANCProfile, BatteryCharging, BatteryLevel, DeviceInfo, DeviceStatus, EQWave, EQWaveInt, ResponseDecoder
},
utils::{build_command_array_with_options_toggle_enabled, i8_to_u8vec, verify_resp, Clamp},
};
use std::time::Duration;

static SLEEP_DURATION: Duration = std::time::Duration::from_millis(30);


pub struct A3027 {
btaddr: Option<BluetoothAdrr>,
rfcomm: Option<RFCOMM>,
}

impl Default for A3027 {
fn default() -> Self {
Self {
btaddr: None,
rfcomm: None,
}
}
}

#[async_trait]
impl SoundcoreDevice for A3027 {
async fn init(&self, btaddr: BluetoothAdrr) -> Result<Box<dyn SoundcoreDevice>, SoundcoreError> {
let mut rfcomm = RFCOMM::new().await?;
rfcomm
.connect_uuid(btaddr.clone(), A3951_RFCOMM_UUID)
.await?;
Ok(Box::new(A3027 { btaddr: Some(btaddr), rfcomm: Some(rfcomm) }))
}

async fn close(&self) -> Result<(), SoundcoreError> {
match &self.rfcomm {
Some(rfcomm) => {
rfcomm.close().await;
Ok(())
}
None => Err(SoundcoreError::NotConnected),
}
}

async fn send(&self, data: &[u8]) -> Result<(), SoundcoreError> {
match &self.rfcomm {
Some(rfcomm) => {
rfcomm.send(data).await?;
Ok(())
}
None => Err(SoundcoreError::NotConnected),
}
}
async fn recv(&self, size: usize) -> Result<Vec<u8>, SoundcoreError> {
match &self.rfcomm {
Some(rfcomm) => Ok(rfcomm.recv(size).await?),
None => Err(SoundcoreError::NotConnected),
}
}

async fn build_and_send_cmd(
&self,
cmd: [i8; 7],
data: Option<&[u8]>,
) -> Result<(), SoundcoreError> {
let to_send = build_command_array_with_options_toggle_enabled(&i8_to_u8vec(&cmd), data);
let _ = &self.send(&to_send).await?;
sleep(SLEEP_DURATION).await;
Ok(())
}

async fn get_status(&self) -> Result<DeviceStatus, SoundcoreError> {
self.build_and_send_cmd(A3951_CMD_DEVICE_STATUS, None).await?;
let resp = self.recv(97).await?;
// if !verify_resp(&resp) {
// return Err(SoundcoreError::ResponseChecksumError);
// }
Ok(Self::decode(&resp)?)
}

async fn get_info(&self) -> Result<DeviceInfo, SoundcoreError> {
self.build_and_send_cmd(A3951_CMD_DEVICE_INFO, None).await?;
let resp = self.recv(36).await?;
// if !verify_resp(&resp) {
// return Err(SoundcoreError::ResponseChecksumError);
// }
Ok(Self::decode(&resp)?)
}
async fn get_battery_level(&self) -> Result<BatteryLevel, SoundcoreError> {
Ok(self.get_status().await?.battery_level)
}

async fn get_battery_charging(&self) -> Result<BatteryCharging, SoundcoreError> {
Ok(self.get_status().await?.battery_charging)
}
}

#[async_trait]
impl SoundcoreANC for A3027 {
async fn set_anc(&self, profile: ANCProfile) -> Result<(), crate::error::SoundcoreError> {
self.build_and_send_cmd(A3951_CMD_DEVICE_SETANC, Some(&profile.to_bytes()))
.await?;
let _resp = self.recv(10).await?; /* No response validation - Need more info */
Ok(())
}

async fn get_anc(&self) -> Result<ANCProfile, crate::error::SoundcoreError> {
self.build_and_send_cmd(A3951_CMD_DEVICE_GETANC, None)
.await?;
let resp = self.recv(14).await?;
if !verify_resp(&resp) {
return Err(SoundcoreError::ResponseChecksumError);
}
Ok(ANCProfile::decode(&resp[9..13])?)
}
}

#[async_trait]
impl SoundcoreEQ for A3027 {
async fn set_eq(&self, wave: EQWave) -> Result<(), SoundcoreError> {
/* Original Java method name: SendEQ_NoDrc_Not_A3951_A3930 */
let mut wave_out = vec![0; 10];
let eq_index: i32 = 65278; /* Custom EQ Index */
let eq_wave = EQWaveInt::from_eq_wave(wave).to_8bytes();
wave_out[0] = eq_index as u8;
wave_out[1] = (eq_index >> 8) as u8;
wave_out[2..10].copy_from_slice(&eq_wave);

/* A3027 Doesn't appear to be using DRC */
self.build_and_send_cmd(A3027_CMD_DEVICE_SETEQ, Some(&wave_out))
.await?;
let _resp = self.recv(100).await?;
Ok(())
}

async fn get_eq(&self) -> Result<EQWave, SoundcoreError> {
Ok(self.get_status().await?.left_eq) /* Return both left and right? */
}

}

#[async_trait]
impl SoundcoreLDAC for A3027 {
async fn get_ldac(&self) -> Result<bool, SoundcoreError> {
self.build_and_send_cmd(A3951_CMD_DEVICE_GETLDAC, None).await?;
let resp = self.recv(11).await?;
if !verify_resp(&resp) {
return Err(SoundcoreError::ResponseChecksumError);
}
Ok(resp[9] == 1)
}

async fn set_ldac(&self, enabled: bool) -> Result<(), SoundcoreError> {
unimplemented!()
}
}
impl SoundcoreHearID for A3027 {}

impl ResponseDecoder<DeviceInfo> for A3027 {
fn decode(arr: &[u8]) -> Result<DeviceInfo, SoundcoreError> {
Ok(DeviceInfo {
left_fw: String::from_utf8(arr[9..14].to_vec())?,
right_fw: String::from_utf8(arr[14..19].to_vec())?,
sn: String::from_utf8(arr[19..35].to_vec())?,
})
}
}

impl ResponseDecoder<DeviceStatus> for A3027 {
fn decode(arr: &[u8]) -> Result<DeviceStatus, SoundcoreError> {
if arr.len() < 93 {
return Err(SoundcoreError::Unknown);
}
let chargeArr = vec![arr[10], arr[10]];
let levelArr = vec![arr[9], arr[9]];

Ok(DeviceStatus {
host_device: arr[9],
tws_status: arr[10] == 1,
battery_level: Self::decode(&*levelArr)?,
battery_charging: Self::decode(&*chargeArr)?,
left_eq: EQWave::decode(&arr[13..21])?,
right_eq: EQWave::decode(&arr[13..21])?,
hearid_enabled: arr[23] == 1,
left_hearid: EQWave::decode(&arr[24..32])?,
right_hearid: EQWave::decode(&arr[32..40])?,
left_hearid_customdata: EQWave::default(),
right_hearid_customdata: EQWave::default(),
anc_status: ANCProfile::decode(&arr[44..48])?,
side_tone_enabled: false,
wear_detection_enabled: arr[69] == 1,
touch_tone_enabled: false,
})
}
}


impl ResponseDecoder<BatteryLevel> for A3027 {
fn decode(arr: &[u8]) -> Result<BatteryLevel, SoundcoreError> {
if arr.len() < 2 {
return Err(SoundcoreError::Unknown);
}

Ok(BatteryLevel {
left: Clamp::clamp(arr[0], 0, 5),
right: Clamp::clamp(arr[1], 0, 5),
})
}
}

impl ResponseDecoder<BatteryCharging> for A3027 {
fn decode(arr: &[u8]) -> Result<BatteryCharging, SoundcoreError> {
if arr.len() < 2 {
return Err(SoundcoreError::Unknown);
}

Ok(BatteryCharging {
left: arr[0] == 1,
right: arr[1] == 1,
})
}
}
Loading