openseries-gg 1.0.0

Control supported SteelSeries headsets and mice from the command line or Rust
Documentation
use super::*;

pub(crate) static DEFINITION: DeviceDefinition = DeviceDefinition {
    slug: "arctis-nova-7p",
    product_ids: &[0x220a, 0x22a7],
    rule: MatchRule::VendorControl,
    connect: |identity, transport| {
        Device::Headset(Headset::new(Box::new(Nova7P::new(identity, transport))))
    },
};

pub(crate) struct Nova7P {
    base: DeviceContext,
}
impl Nova7P {
    pub(crate) fn new(identity: Identity, transport: HidTransport) -> Self {
        Self {
            base: DeviceContext::new(identity, transport),
        }
    }
}
impl DeviceProtocol for Nova7P {
    fn id(&self) -> &str {
        &self.base.identity.id
    }
    fn name(&self) -> &str {
        "SteelSeries Arctis Nova 7P"
    }
    fn product_id(&self) -> u16 {
        self.base.identity.product_id
    }
    fn supported_features(&self) -> Capabilities {
        Capabilities::BATTERY_STATUS
            | Capabilities::INACTIVE_TIME
            | Capabilities::EQUALIZER
            | Capabilities::EQUALIZER_PRESET
    }
}
impl HeadsetProtocol for Nova7P {
    fn equalizer_info(&self) -> Result<EqualizerInfo> {
        Ok(EqualizerInfo {
            band_count: 10,
            minimum: -10.0,
            maximum: 10.0,
            step: 0.5,
        })
    }
    fn equalizer_presets(&self) -> Result<&'static [EqualizerPreset]> {
        Ok(&PRESETS_NOVA)
    }
    fn get_battery(&mut self) -> Result<BatteryInfo> {
        let data = status(&mut self.base.transport, 6)?;
        if data[3] == 0 {
            return Ok(BatteryInfo {
                level_percentage: 0,
                status: BatteryStatus::Disconnected,
            });
        }
        let level = if self.base.identity.product_id == 0x220a {
            map(i32::from(data[2]), 0, 4, 0, 100)
        } else {
            i32::from(data[2])
        }
        .clamp(0, 100) as u16;
        let state = if matches!(data[3], 1 | 2) {
            BatteryStatus::Charging
        } else if level == 100 {
            BatteryStatus::Charged
        } else {
            BatteryStatus::Discharging
        };
        Ok(BatteryInfo {
            level_percentage: level,
            status: state,
        })
    }
    fn set_inactive_time(&mut self, minutes: u16) -> Result<()> {
        if minutes > 90 {
            return Err(invalid("Inactive time must be between 0 and 90 minutes."));
        }
        self.base
            .transport
            .write_output(&[0, 0xa3, minutes as u8], 0, 64)
    }
    fn set_equalizer_preset(&mut self, preset: usize) -> Result<()> {
        let bands = PRESETS_NOVA
            .get(preset)
            .ok_or_else(|| invalid("Preset index must be between 0 and 3."))?
            .bands;
        self.set_equalizer(bands)
    }
    fn set_equalizer(&mut self, bands: &[f32]) -> Result<()> {
        validate_eq(bands, -10.0, 10.0)?;
        let mut command = [0_u8; 64];
        command[1] = 0x33;
        for (index, value) in bands.iter().enumerate() {
            command[index + 2] = (20.0 + value) as u8;
        }
        self.base.transport.write_output(&command, 0, 64)
    }
}