use std::collections::BTreeMap;
use std::path::PathBuf;
use serde::Serialize;
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct TypeCIdentity {
pub vendor_id: u16,
pub product_id: u16,
pub vdos: Vec<u32>,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct TypeCPartner {
pub r#type: String,
pub identity: Option<TypeCIdentity>,
pub raw_attributes: BTreeMap<String, String>,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct TypeCCable {
pub r#type: String,
pub plug_type: String,
pub identity: Option<TypeCIdentity>,
pub raw_attributes: BTreeMap<String, String>,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct TypeCPowerSupply {
pub sysfs_path: PathBuf,
pub name: String,
pub online: bool,
pub voltage_now_uv: Option<i64>,
pub current_now_ua: Option<i64>,
pub current_max_ua: Option<i64>,
pub voltage_min_uv: Option<i64>,
pub voltage_max_uv: Option<i64>,
pub charge_type: String,
pub usb_type: String,
pub raw_attributes: BTreeMap<String, String>,
}
impl TypeCPowerSupply {
pub fn negotiated_power_mw(&self) -> Option<i64> {
let v = self.voltage_now_uv?;
let i = self.current_now_ua?;
if v <= 0 || i <= 0 {
return None;
}
Some(((v as i128 * i as i128) / 1_000_000_000) as i64)
}
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct TypeCPort {
pub sysfs_path: PathBuf,
pub port_name: String,
pub port_number: i32,
pub data_role: String,
pub power_role: String,
pub port_type: String,
pub power_op_mode: String,
pub orientation: String,
pub pd_revision: String,
pub usb_typec_rev: String,
pub power_supply: Option<TypeCPowerSupply>,
pub partner: Option<TypeCPartner>,
pub cable: Option<TypeCCable>,
pub raw_attributes: BTreeMap<String, String>,
}
impl TypeCPort {
pub fn is_connected(&self) -> bool {
self.partner.is_some() || self.cable.is_some()
}
pub fn current_data_role(&self) -> String {
bracketed(&self.data_role)
}
pub fn current_power_role(&self) -> String {
bracketed(&self.power_role)
}
}
pub fn bracketed(s: &str) -> String {
if let Some(start) = s.find('[') {
if let Some(end) = s[start + 1..].find(']') {
return s[start + 1..start + 1 + end].to_string();
}
}
s.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bracketed_extracts_active_role() {
assert_eq!(bracketed("host [device]"), "device");
assert_eq!(bracketed("[source] sink"), "source");
assert_eq!(bracketed("plain"), "plain");
assert_eq!(bracketed(""), "");
}
#[test]
fn is_connected_logic() {
let mut p = TypeCPort::default();
assert!(!p.is_connected());
p.cable = Some(TypeCCable::default());
assert!(p.is_connected());
p.cable = None;
p.partner = Some(TypeCPartner::default());
assert!(p.is_connected());
}
#[test]
fn negotiated_power_mw_basic() {
let psy = TypeCPowerSupply {
voltage_now_uv: Some(5_000_000),
current_now_ua: Some(3_000_000),
..Default::default()
};
assert_eq!(psy.negotiated_power_mw(), Some(15_000));
}
#[test]
fn negotiated_power_mw_epr_no_overflow() {
let psy = TypeCPowerSupply {
voltage_now_uv: Some(48_000_000),
current_now_ua: Some(5_000_000),
..Default::default()
};
assert_eq!(psy.negotiated_power_mw(), Some(240_000));
}
#[test]
fn negotiated_power_mw_returns_none_on_missing() {
let psy = TypeCPowerSupply::default();
assert!(psy.negotiated_power_mw().is_none());
let only_voltage = TypeCPowerSupply {
voltage_now_uv: Some(5_000_000),
..Default::default()
};
assert!(only_voltage.negotiated_power_mw().is_none());
}
#[test]
fn negotiated_power_mw_rejects_zero_or_negative() {
let zero = TypeCPowerSupply {
voltage_now_uv: Some(0),
current_now_ua: Some(3_000_000),
..Default::default()
};
assert!(zero.negotiated_power_mw().is_none());
let neg = TypeCPowerSupply {
voltage_now_uv: Some(5_000_000),
current_now_ua: Some(-1),
..Default::default()
};
assert!(neg.negotiated_power_mw().is_none());
}
#[test]
fn current_roles_handle_brackets() {
let p = TypeCPort {
data_role: "host [device]".into(),
power_role: "[source] sink".into(),
..Default::default()
};
assert_eq!(p.current_data_role(), "device");
assert_eq!(p.current_power_role(), "source");
}
}