Skip to content

Commit

Permalink
feat(lib): Add firmware version packet
Browse files Browse the repository at this point in the history
Some devices don't provide the firmware version in their state update
packet. For those, we need to request it separately. This can be done
immediately after fetching the initial state.
  • Loading branch information
Oppzippy committed Aug 10, 2023
1 parent fc7655a commit 019bc43
Show file tree
Hide file tree
Showing 15 changed files with 192 additions and 27 deletions.
5 changes: 3 additions & 2 deletions lib/src/demo/device/demo_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ impl DemoDevice {
}
.into(),
),
firmware_version: Some(FirmwareVersion::new(01, 00)),
left_firmware_version: Some(FirmwareVersion::new(2, 0)),
right_firmware_version: Some(FirmwareVersion::new(2, 0)),
serial_number: Some(SerialNumber("0123456789ABCDEF".into())),
dynamic_range_compression_min_firmware_version: None,
dynamic_range_compression_min_firmware_version: Some(FirmwareVersion::new(2, 0)),
}),
}
}
Expand Down
2 changes: 2 additions & 0 deletions lib/src/packets/inbound.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
mod firmware_version_update_packet;
mod inbound_packet;
mod set_equalizer_ok_packet;
mod set_sound_mode_ok_packet;
mod sound_mode_update_packet;
pub mod state_update_packet;

pub use firmware_version_update_packet::*;
pub use inbound_packet::*;
pub use set_equalizer_ok_packet::*;
pub use set_sound_mode_ok_packet::*;
Expand Down
77 changes: 77 additions & 0 deletions lib/src/packets/inbound/firmware_version_update_packet.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use nom::{
combinator::{all_consuming, map},
error::{context, ContextError, ParseError},
sequence::tuple,
};

use crate::packets::{
parsing::{take_firmware_version, take_serial_number, ParseResult},
structures::{FirmwareVersion, SerialNumber},
};

// TODO think of a better name. this could be misleading since this does not update the firmware on the device,
// it simply updates our state with the version number of the firmware running on the device.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct FirmwareVersionUpdatePacket {
pub left_firmware_version: FirmwareVersion,
pub right_firmware_version: FirmwareVersion,
pub serial_number: SerialNumber,
}

pub fn take_firmware_version_update_packet<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
input: &'a [u8],
) -> ParseResult<FirmwareVersionUpdatePacket, E> {
context(
"SoundModeUpdatePacket",
all_consuming(map(
tuple((
take_firmware_version,
take_firmware_version,
take_serial_number,
)),
|(left_firmware_version, right_firmware_version, serial_number)| {
FirmwareVersionUpdatePacket {
left_firmware_version,
right_firmware_version,
serial_number,
}
},
)),
)(input)
}

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

use crate::packets::{
inbound::take_firmware_version_update_packet,
parsing::{take_checksum, take_packet_header},
structures::{FirmwareVersion, SerialNumber},
};

fn strip(input: &[u8]) -> &[u8] {
let input = take_checksum::<VerboseError<&[u8]>>(input).unwrap().0;
let input = take_packet_header::<VerboseError<&[u8]>>(input).unwrap().0;
input
}

#[test]
fn it_parses_a_manually_crafted_packet() {
let input: &[u8] = &[
0x09, 0xff, 0x00, 0x00, 0x01, 0x01, 0x05, 0x25, 0x00, 0x31, 0x32, 0x2e, 0x33, 0x34,
0x32, 0x33, 0x2e, 0x34, 0x35, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0xca,
];
let input = strip(input);
let packet = take_firmware_version_update_packet::<VerboseError<&[u8]>>(input)
.unwrap()
.1;
assert_eq!(FirmwareVersion::new(12, 34), packet.left_firmware_version);
assert_eq!(FirmwareVersion::new(23, 45), packet.right_firmware_version);
assert_eq!(
SerialNumber("0123456789ABCDEF".into()),
packet.serial_number
);
}
}
10 changes: 7 additions & 3 deletions lib/src/packets/inbound/inbound_packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use crate::packets::{

use super::{
state_update_packet::{take_state_update_packet, StateUpdatePacket},
take_ambient_sound_mode_update_packet, take_set_ambient_sound_mode_ok_packet,
take_set_equalizer_ok_packet, SetEqualizerOkPacket, SetSoundModeOkPacket,
SoundModeUpdatePacket,
take_ambient_sound_mode_update_packet, take_firmware_version_update_packet,
take_set_ambient_sound_mode_ok_packet, take_set_equalizer_ok_packet,
FirmwareVersionUpdatePacket, SetEqualizerOkPacket, SetSoundModeOkPacket, SoundModeUpdatePacket,
};

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
Expand All @@ -18,6 +18,7 @@ pub enum InboundPacket {
SoundModeUpdate(SoundModeUpdatePacket),
SetSoundModeOk(SetSoundModeOkPacket),
SetEqualizerOk(SetEqualizerOkPacket),
FirmwareVersionUpdate(FirmwareVersionUpdatePacket),
}

impl InboundPacket {
Expand All @@ -35,6 +36,9 @@ impl InboundPacket {
Self::SetEqualizerOk(take_set_equalizer_ok_packet(input)?.1)
}
PacketType::StateUpdate => Self::StateUpdate(take_state_update_packet(input)?.1),
PacketType::FirmwareVersionUpdate => {
Self::FirmwareVersionUpdate(take_firmware_version_update_packet(input)?.1)
}
})
}
}
Expand Down
2 changes: 2 additions & 0 deletions lib/src/packets/outbound.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
mod outbound_packet;
mod request_firmware_version_packet;
mod request_state_packet;
mod set_equalizer;
mod set_equalizer_with_drc;
mod set_sound_mode;

pub use outbound_packet::*;
pub use request_firmware_version_packet::*;
pub use request_state_packet::*;
pub use set_equalizer::*;
pub use set_equalizer_with_drc::*;
Expand Down
31 changes: 31 additions & 0 deletions lib/src/packets/outbound/request_firmware_version_packet.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use super::OutboundPacket;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct RequestFirmwareVersionPacket {}

impl RequestFirmwareVersionPacket {
pub fn new() -> Self {
Self {}
}
}

impl OutboundPacket for RequestFirmwareVersionPacket {
fn command(&self) -> [u8; 7] {
[0x08, 0xee, 0x00, 0x00, 0x00, 0x01, 0x05]
}

fn body(&self) -> Vec<u8> {
Vec::new()
}
}

#[cfg(test)]
mod tests {
use crate::packets::outbound::{OutboundPacketBytes, RequestFirmwareVersionPacket};

#[test]
fn it_matches_a_manually_crafted_packet() {
let expected: &[u8] = &[0x08, 0xee, 0x00, 0x00, 0x00, 0x01, 0x05, 0x0a, 0x00, 0x06];
assert_eq!(expected, RequestFirmwareVersionPacket::new().bytes())
}
}
16 changes: 6 additions & 10 deletions lib/src/packets/parsing/packet_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,17 @@ use crate::packets::structures::PacketType;

use super::ParseResult;

const SOUND_MODE_UPDATE_PREFIX: &[u8] = &[0x09, 0xff, 0x00, 0x00, 0x01, 0x06, 0x01];
const SET_SOUND_MODE_OK_PREFIX: &[u8] = &[0x09, 0xff, 0x00, 0x00, 0x01, 0x06, 0x81];
const SET_EQUALIZER_OK_PREFIX: &[u8] = &[0x09, 0xff, 0x00, 0x00, 0x01, 0x02, 0x81];
const STATE_UPDATE_PREFIX: &[u8] = &[0x09, 0xff, 0x00, 0x00, 0x01, 0x01, 0x01];

pub fn take_packet_type<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
input: &'a [u8],
) -> ParseResult<PacketType, E> {
context(
"packet type 7 byte prefix",
map_opt(take(7usize), |prefix| match prefix {
SOUND_MODE_UPDATE_PREFIX => Some(PacketType::SoundModeUpdate),
SET_SOUND_MODE_OK_PREFIX => Some(PacketType::SetSoundModeOk),
SET_EQUALIZER_OK_PREFIX => Some(PacketType::SetEqualizerOk),
STATE_UPDATE_PREFIX => Some(PacketType::StateUpdate),
map_opt(take(7usize), |prefix: &[u8]| match prefix {
&[0x09, 0xff, 0x00, 0x00, 0x01, 0x06, 0x01] => Some(PacketType::SoundModeUpdate),
&[0x09, 0xff, 0x00, 0x00, 0x01, 0x06, 0x81] => Some(PacketType::SetSoundModeOk),
&[0x09, 0xff, 0x00, 0x00, 0x01, 0x02, 0x81] => Some(PacketType::SetEqualizerOk),
&[0x09, 0xff, 0x00, 0x00, 0x01, 0x01, 0x01] => Some(PacketType::StateUpdate),
&[0x09, 0xff, 0x00, 0x00, 0x01, 0x01, 0x05] => Some(PacketType::FirmwareVersionUpdate),
_ => None,
}),
)(input)
Expand Down
1 change: 1 addition & 0 deletions lib/src/packets/structures/packet_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ pub enum PacketType {
SetSoundModeOk,
SetEqualizerOk,
StateUpdate,
FirmwareVersionUpdate,
}
11 changes: 10 additions & 1 deletion lib/src/q30/device/q30_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use crate::{
futures::{sleep, spawn, JoinHandle},
packets::{
outbound::{
OutboundPacketBytes, SetEqualizerPacket, SetEqualizerWithDrcPacket, SetSoundModePacket,
OutboundPacketBytes, RequestFirmwareVersionPacket, SetEqualizerPacket,
SetEqualizerWithDrcPacket, SetSoundModePacket,
},
structures::{AmbientSoundMode, DeviceFeatureFlags, EqualizerConfiguration, SoundModes},
},
Expand Down Expand Up @@ -40,6 +41,14 @@ where
let mut inbound_receiver = connection.inbound_packets_channel().await?;
let initial_state = Self::fetch_initial_state(&connection, &mut inbound_receiver).await?;

// TODO consider making this a part of fetch_initial_state
// For devices that don't include the firmware version in their state update packet, we need to request it
if initial_state.left_firmware_version.is_none() {
connection
.write_with_response(&RequestFirmwareVersionPacket::new().bytes())
.await?;
}

let current_state_lock = Arc::new(RwLock::new(initial_state));
let current_state_lock_async = current_state_lock.to_owned();

Expand Down
33 changes: 26 additions & 7 deletions lib/src/state/device_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ pub struct DeviceState {
pub age_range: Option<AgeRange>,
pub custom_hear_id: Option<HearId>,
pub custom_button_model: Option<CustomButtonModel>,
pub firmware_version: Option<FirmwareVersion>,
pub left_firmware_version: Option<FirmwareVersion>,
pub right_firmware_version: Option<FirmwareVersion>,
pub serial_number: Option<SerialNumber>,
pub dynamic_range_compression_min_firmware_version: Option<FirmwareVersion>,
}
Expand All @@ -33,7 +34,8 @@ impl From<StateUpdatePacket> for DeviceState {
age_range: packet.age_range,
custom_hear_id: packet.custom_hear_id,
custom_button_model: packet.custom_button_model,
firmware_version: packet.firmware_version,
left_firmware_version: packet.firmware_version,
right_firmware_version: None,
serial_number: packet.serial_number.clone(),
dynamic_range_compression_min_firmware_version: packet
.dynamic_range_compression_min_firmware_version,
Expand All @@ -45,11 +47,28 @@ impl DeviceState {
// need drc: A3951, A3930, A3931, A3931XR, A3935, A3935W,
// separate left/right firmware: A3951, A3930, A3931, A3931XR, A3935, A3935W,
pub fn supports_dynamic_range_compression(&self) -> bool {
self.feature_flags
if self
.feature_flags
.contains(DeviceFeatureFlags::DYNAMIC_RANGE_COMPRESSION)
&& self.firmware_version.unwrap_or_default()
>= self
.dynamic_range_compression_min_firmware_version
.unwrap_or_default()
{
match (self.left_firmware_version, self.right_firmware_version) {
(Some(left), Some(right)) => {
self.does_firmware_version_support_drc(left)
&& self.does_firmware_version_support_drc(right)
}
(Some(left), None) => self.does_firmware_version_support_drc(left),
(None, Some(_)) => unreachable!("right firmware version is set but not left"),
(None, None) => false,
}
} else {
false
}
}

fn does_firmware_version_support_drc(&self, firmware_version: FirmwareVersion) -> bool {
firmware_version
>= self
.dynamic_range_compression_min_firmware_version
.unwrap_or_default()
}
}
1 change: 1 addition & 0 deletions lib/src/state/device_state_transformer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub fn inbound_packet_to_state_transformer(
match value {
InboundPacket::StateUpdate(packet) => Some(Box::new(packet)),
InboundPacket::SoundModeUpdate(packet) => Some(Box::new(packet)),
InboundPacket::FirmwareVersionUpdate(packet) => Some(Box::new(packet)),
InboundPacket::SetSoundModeOk(_packet) => None,
InboundPacket::SetEqualizerOk(_packet) => None,
}
Expand Down
2 changes: 2 additions & 0 deletions lib/src/state/transformers.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod ambient_sound_mode_update;
mod firmware_version_update;
mod state_update;

pub use ambient_sound_mode_update::*;
pub use firmware_version_update::*;
pub use state_update::*;
14 changes: 14 additions & 0 deletions lib/src/state/transformers/firmware_version_update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use crate::{
packets::inbound::FirmwareVersionUpdatePacket,
state::{DeviceState, DeviceStateTransformer},
};

impl DeviceStateTransformer for FirmwareVersionUpdatePacket {
fn transform(&self, state: &DeviceState) -> DeviceState {
DeviceState {
left_firmware_version: Some(self.left_firmware_version),
right_firmware_version: Some(self.right_firmware_version),
..state.clone()
}
}
}
6 changes: 3 additions & 3 deletions lib/src/state/transformers/state_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ impl DeviceStateTransformer for StateUpdatePacket {
age_range: self.age_range.or(state.age_range),
custom_button_model: self.custom_button_model.or(state.custom_button_model),
custom_hear_id: self.custom_hear_id.or(state.custom_hear_id),
firmware_version: self
left_firmware_version: self
.firmware_version
.as_ref()
.or(state.firmware_version.as_ref())
.or(state.left_firmware_version.as_ref())
.cloned(),
right_firmware_version: state.right_firmware_version,
serial_number: self
.serial_number
.as_ref()
Expand All @@ -25,7 +26,6 @@ impl DeviceStateTransformer for StateUpdatePacket {
sound_modes: self.sound_modes.or(state.sound_modes),
dynamic_range_compression_min_firmware_version: state
.dynamic_range_compression_min_firmware_version,
// ..state.clone()
}
}
}
8 changes: 7 additions & 1 deletion web/src/libTypes/DeviceState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,13 @@ const deviceStateSchema = Type.Object({
}),
]),
),
firmwareVersion: Type.Optional(
leftFirmwareVersion: Type.Optional(
Type.Object({
major: Type.Number(),
minor: Type.Number(),
}),
),
rightFirmwareVersion: Type.Optional(
Type.Object({
major: Type.Number(),
minor: Type.Number(),
Expand Down

0 comments on commit 019bc43

Please sign in to comment.