use core::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[non_exhaustive]
pub enum DeviceModel {
NanoS,
NanoSPlus,
NanoX,
Stax,
Flex,
NanoGen5,
}
impl DeviceModel {
pub const ALL: [DeviceModel; 6] = [
DeviceModel::NanoS,
DeviceModel::NanoSPlus,
DeviceModel::NanoX,
DeviceModel::Stax,
DeviceModel::Flex,
DeviceModel::NanoGen5,
];
pub fn name(self) -> &'static str {
match self {
DeviceModel::NanoS => "Nano S",
DeviceModel::NanoSPlus => "Nano S Plus",
DeviceModel::NanoX => "Nano X",
DeviceModel::Stax => "Stax",
DeviceModel::Flex => "Flex",
DeviceModel::NanoGen5 => "Nano Gen5",
}
}
pub fn id(self) -> &'static str {
match self {
DeviceModel::NanoS => "nanoS",
DeviceModel::NanoSPlus => "nanoSP",
DeviceModel::NanoX => "nanoX",
DeviceModel::Stax => "stax",
DeviceModel::Flex => "flex",
DeviceModel::NanoGen5 => "apexp",
}
}
pub fn usb_product_id(self) -> u8 {
match self {
DeviceModel::NanoS => 0x10,
DeviceModel::NanoX => 0x40,
DeviceModel::NanoSPlus => 0x50,
DeviceModel::Stax => 0x60,
DeviceModel::Flex => 0x70,
DeviceModel::NanoGen5 => 0x80,
}
}
pub fn bootloader_product_id(self) -> u16 {
match self {
DeviceModel::NanoS => 0x0001,
DeviceModel::NanoX => 0x0004,
DeviceModel::NanoSPlus => 0x0005,
DeviceModel::Stax => 0x0006,
DeviceModel::Flex => 0x0007,
DeviceModel::NanoGen5 => 0x0008,
}
}
pub fn target_id_mask(self) -> u32 {
match self {
DeviceModel::NanoS => 0x3110_0000,
DeviceModel::NanoX => 0x3300_0000,
DeviceModel::NanoSPlus => 0x3310_0000,
DeviceModel::Stax => 0x3320_0000,
DeviceModel::Flex => 0x3330_0000,
DeviceModel::NanoGen5 => 0x3340_0000,
}
}
pub fn memory_size(self) -> u32 {
match self {
DeviceModel::NanoS => 320 * 1024,
DeviceModel::NanoX => 2 * 1024 * 1024,
_ => 1533 * 1024,
}
}
pub fn block_size(self, firmware_version: &str) -> u32 {
match self {
DeviceModel::NanoS => {
if version_lt(firmware_version, (2, 0, 0)) {
4096
} else {
2048
}
}
DeviceModel::NanoX => 4096,
_ => 512,
}
}
pub fn has_touch_screen(self) -> bool {
matches!(
self,
DeviceModel::Stax | DeviceModel::Flex | DeviceModel::NanoGen5
)
}
pub fn has_battery(self) -> bool {
!matches!(self, DeviceModel::NanoS | DeviceModel::NanoSPlus)
}
pub fn from_product_id(pid: u16) -> Option<Self> {
let hi = (pid >> 8) as u8;
Self::ALL
.into_iter()
.find(|m| m.usb_product_id() == hi || (hi == 0 && m.bootloader_product_id() == pid))
}
pub fn from_target_id(target_id: u32) -> Option<Self> {
Self::ALL
.into_iter()
.find(|m| target_id & 0xffff_0000 == m.target_id_mask())
}
pub fn is_bootloader_product_id(pid: u16) -> bool {
pid >> 8 == 0 && Self::ALL.iter().any(|m| m.bootloader_product_id() == pid)
}
}
impl fmt::Display for DeviceModel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
pub fn parse_version(v: &str) -> (u32, u32, u32) {
let mut it = v
.split(|c: char| !c.is_ascii_digit())
.filter(|s| !s.is_empty())
.map(|s| s.parse::<u32>().unwrap_or(0));
(
it.next().unwrap_or(0),
it.next().unwrap_or(0),
it.next().unwrap_or(0),
)
}
pub fn version_lt(v: &str, target: (u32, u32, u32)) -> bool {
parse_version(v) < target
}
pub fn version_gte(v: &str, target: (u32, u32, u32)) -> bool {
!version_lt(v, target)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detect() {
assert_eq!(
DeviceModel::from_product_id(0x5011),
Some(DeviceModel::NanoSPlus)
);
assert_eq!(
DeviceModel::from_product_id(0x4015),
Some(DeviceModel::NanoX)
);
assert_eq!(
DeviceModel::from_product_id(0x0005),
Some(DeviceModel::NanoSPlus)
);
assert_eq!(
DeviceModel::from_target_id(0x33100004),
Some(DeviceModel::NanoSPlus)
);
assert_eq!(
DeviceModel::from_target_id(0x31100004),
Some(DeviceModel::NanoS)
);
assert!(version_lt("1.6.1", (2, 0, 0)));
assert!(version_gte("2.2.3-rc1", (2, 2, 3)));
}
}