whatcable-macos-usb-inspector

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

WhatCable macOS USB-C Inspector

WhatCable macOS USB-C 检测工具

Skill by ara.so — Daily 2026 Skills collection.
WhatCable is a macOS 14+ menu bar app (Swift/SwiftUI) that reads IOKit services to surface what each USB-C cable plugged into your Mac can actually do — speed, power rating, e-marker data, PDO profiles, and connected device identity — in plain English.
ara.so开发的技能应用 — 属于Daily 2026 Skills系列。
WhatCable是一款适用于macOS 14+的菜单栏应用(基于Swift/SwiftUI开发),它通过读取IOKit服务,以通俗易懂的方式展示连接到Mac的每根USB-C线缆的实际能力——包括传输速度、功率额定值、电子标记数据、PDO配置文件以及连接设备的身份信息。

Project Structure

项目结构

Sources/WhatCable/
├── WhatCableApp.swift          # App entry point, menu bar setup
├── ContentView.swift           # Main popover UI
├── PortSummary.swift           # Plain-English logic per port
├── PDVDO.swift                 # PD VDO bit-twiddling / spec decoding
├── IOKitReader.swift           # IOKit service queries
└── ...
scripts/
└── build-app.sh                # Universal binary + notarisation
Sources/WhatCable/
├── WhatCableApp.swift          # 应用入口,菜单栏设置
├── ContentView.swift           # 主弹出窗口UI
├── PortSummary.swift           # 单端口的通俗化逻辑处理
├── PDVDO.swift                 # PD VDO位操作/规范解码
├── IOKitReader.swift           # IOKit服务查询
└── ...
scripts/
└── build-app.sh                # 通用二进制包构建 + 公证

Install / Build

安装/构建

bash
undefined
bash
undefined

Run locally (development)

本地运行(开发环境)

swift run WhatCable
swift run WhatCable

Build distributable universal app (arm64 + x86_64)

构建可分发的通用应用(arm64 + x86_64)

./scripts/build-app.sh
./scripts/build-app.sh

→ dist/WhatCable.app

→ dist/WhatCable.app

→ dist/WhatCable.zip

→ dist/WhatCable.zip


Requires Swift 5.9 / Xcode 15+, macOS 14 (Sonoma) or later.

需要Swift 5.9 / Xcode 15+,以及macOS 14(Sonoma)或更高版本。

Signed + Notarised Build

签名+公证构建

bash
cp .env.example .env
bash
cp .env.example .env

Edit .env:

编辑.env:

DEVELOPER_ID="Developer ID Application: Your Name (TEAMID)"

DEVELOPER_ID="Developer ID Application: Your Name (TEAMID)"

NOTARY_PROFILE="WhatCable-notary"

NOTARY_PROFILE="WhatCable-notary"

Store notarytool credentials once

一次性存储notarytool凭证

xcrun notarytool store-credentials "WhatCable-notary"
--apple-id "$APPLE_ID"
--team-id "$TEAM_ID"
--password "$APP_SPECIFIC_PASSWORD"
./scripts/build-app.sh
undefined
xcrun notarytool store-credentials "WhatCable-notary"
--apple-id "$APPLE_ID"
--team-id "$TEAM_ID"
--password "$APP_SPECIFIC_PASSWORD"
./scripts/build-app.sh
undefined

Key Concepts

核心概念

IOKit Service Families

IOKit服务家族

ServicePurpose
AppleHPMInterfaceType10/11/12
(M3+)
Per-port state, transports, plug orientation, e-marker presence
AppleTCControllerType10
(M1/M2)
Same, older HPM interface
IOPortFeaturePowerSource
Full PDO list + live negotiated PDO
IOPortTransportComponentCCUSBPDSOP
PD Discover Identity VDOs (SOP = partner, SOP' = cable e-marker)
服务用途
AppleHPMInterfaceType10/11/12
(M3+)
单端口状态、传输类型、插头方向、电子标记存在情况
AppleTCControllerType10
(M1/M2)
功能同上,旧版HPM接口
IOPortFeaturePowerSource
完整PDO列表 + 当前协商的PDO
IOPortTransportComponentCCUSBPDSOP
PD身份识别VDO(SOP = 对接设备,SOP' = 线缆电子标记)

USB PD VDO Decoding (PDVDO.swift)

USB PD VDO解码(PDVDO.swift)

The core bit-twiddling follows USB Power Delivery 3.x spec. Cable e-marker VDOs encode speed and current in specific bit fields.
swift
// Example: decode cable speed from Passive Cable VDO
struct PassiveCableVDO {
    let raw: UInt32

    // Bits [5:3] — USB SuperSpeed signalling support
    var usbSSSignalling: UInt8 {
        UInt8((raw >> 3) & 0b111)
    }

    var dataRate: String {
        switch usbSSSignalling {
        case 0b000: return "USB 2.0 only"
        case 0b001: return "USB 3.2 Gen 1 (5 Gbps)"
        case 0b010: return "USB 3.2 Gen 2 (10 Gbps)"
        case 0b011: return "USB 3.2 Gen 2x2 / USB4 Gen 2 (20 Gbps)"
        case 0b100: return "USB4 Gen 3 (40 Gbps)"
        default:    return "Unknown"
        }
    }

    // Bits [6:5] — VBUS current handling
    var currentCapability: String {
        switch (raw >> 5) & 0b11 {
        case 0b00: return "USB Type-C Default (≤3A)"
        case 0b01: return "3A"
        case 0b10: return "5A"
        default:   return "Reserved"
        }
    }
}
核心的位操作遵循USB Power Delivery 3.x规范。线缆电子标记VDO在特定位字段中编码了速度和电流信息。
swift
// 示例:从无源线缆VDO中解码线缆速度
struct PassiveCableVDO {
    let raw: UInt32

    // 位[5:3] — USB SuperSpeed信号支持
    var usbSSSignalling: UInt8 {
        UInt8((raw >> 3) & 0b111)
    }

    var dataRate: String {
        switch usbSSSignalling {
        case 0b000: return "USB 2.0 only"
        case 0b001: return "USB 3.2 Gen 1 (5 Gbps)"
        case 0b010: return "USB 3.2 Gen 2 (10 Gbps)"
        case 0b011: return "USB 3.2 Gen 2x2 / USB4 Gen 2 (20 Gbps)"
        case 0b100: return "USB4 Gen 3 (40 Gbps)"
        default:    return "Unknown"
        }
    }

    // 位[6:5] — VBUS电流承载能力
    var currentCapability: String {
        switch (raw >> 5) & 0b11 {
        case 0b00: return "USB Type-C Default (≤3A)"
        case 0b01: return "3A"
        case 0b10: return "5A"
        default:   return "Reserved"
        }
    }
}

Reading IOKit Properties

读取IOKit属性

swift
import IOKit

func readPortProperties(serviceName: String) -> [String: Any]? {
    var iterator: io_iterator_t = 0
    let matchDict = IOServiceMatching(serviceName)
    guard IOServiceGetMatchingServices(kIOMainPortDefault,
                                       matchDict, &iterator) == KERN_SUCCESS else {
        return nil
    }
    defer { IOObjectRelease(iterator) }

    var service = IOIteratorNext(iterator)
    var results: [String: Any] = [:]
    while service != 0 {
        defer {
            IOObjectRelease(service)
            service = IOIteratorNext(iterator)
        }
        if let props = copyProperties(service) {
            results.merge(props) { _, new in new }
        }
    }
    return results.isEmpty ? nil : results
}

private func copyProperties(_ service: io_service_t) -> [String: Any]? {
    var propsRef: Unmanaged<CFMutableDictionary>?
    guard IORegistryEntryCreateCFProperties(service, &propsRef,
                                             kCFAllocatorDefault, 0) == KERN_SUCCESS,
          let props = propsRef?.takeRetainedValue() as? [String: Any] else {
        return nil
    }
    return props
}
swift
import IOKit

func readPortProperties(serviceName: String) -> [String: Any]? {
    var iterator: io_iterator_t = 0
    let matchDict = IOServiceMatching(serviceName)
    guard IOServiceGetMatchingServices(kIOMainPortDefault,
                                       matchDict, &iterator) == KERN_SUCCESS else {
        return nil
    }
    defer { IOObjectRelease(iterator) }

    var service = IOIteratorNext(iterator)
    var results: [String: Any] = [:]
    while service != 0 {
        defer {
            IOObjectRelease(service)
            service = IOIteratorNext(iterator)
        }
        if let props = copyProperties(service) {
            results.merge(props) { _, new in new }
        }
    }
    return results.isEmpty ? nil : results
}

private func copyProperties(_ service: io_service_t) -> [String: Any]? {
    var propsRef: Unmanaged<CFMutableDictionary>?
    guard IORegistryEntryCreateCFProperties(service, &propsRef,
                                             kCFAllocatorDefault, 0) == KERN_SUCCESS,
          let props = propsRef?.takeRetainedValue() as? [String: Any] else {
        return nil
    }
    return props
}

Port Summary Plain-English Logic (PortSummary.swift)

端口信息通俗化逻辑(PortSummary.swift)

swift
enum PortHeadline: String {
    case thunderbolt    = "Thunderbolt / USB4"
    case usbDevice      = "USB device connected"
    case chargingOnly   = "Charging only"
    case slowCable      = "Slow USB / charge-only cable"
    case nothing        = "Nothing connected"
}

struct PortSummary {
    let headline: PortHeadline
    let chargingDiagnostic: String?
    let dataRate: String?
    let currentRating: String?
    let negotiatedPDO: PDO?
    let allPDOs: [PDO]

    static func from(ioKitProps: [String: Any]) -> PortSummary {
        let hasThunderbolt = ioKitProps["Thunderbolt"] as? Bool ?? false
        let hasUSB3       = ioKitProps["USB3"] as? Bool ?? false
        let isConnected   = ioKitProps["Connected"] as? Bool ?? false
        let emarkerPresent = ioKitProps["CableEMarker"] as? Bool ?? false

        let headline: PortHeadline
        if !isConnected {
            headline = .nothing
        } else if hasThunderbolt {
            headline = .thunderbolt
        } else if hasUSB3 {
            headline = .usbDevice
        } else if emarkerPresent {
            headline = .chargingOnly
        } else {
            headline = .slowCable
        }

        // Parse PDOs for charging diagnostic
        let pdos = parsePDOs(from: ioKitProps)
        let negotiated = pdos.first(where: { $0.isActive })
        let diagnostic = buildChargingDiagnostic(pdos: pdos, negotiated: negotiated)

        return PortSummary(
            headline: headline,
            chargingDiagnostic: diagnostic,
            dataRate: emarkerPresent ? decodeDataRate(ioKitProps) : nil,
            currentRating: emarkerPresent ? decodeCurrentRating(ioKitProps) : nil,
            negotiatedPDO: negotiated,
            allPDOs: pdos
        )
    }
}
swift
enum PortHeadline: String {
    case thunderbolt    = "Thunderbolt / USB4"
    case usbDevice      = "USB device connected"
    case chargingOnly   = "Charging only"
    case slowCable      = "Slow USB / charge-only cable"
    case nothing        = "Nothing connected"
}

struct PortSummary {
    let headline: PortHeadline
    let chargingDiagnostic: String?
    let dataRate: String?
    let currentRating: String?
    let negotiatedPDO: PDO?
    let allPDOs: [PDO]

    static func from(ioKitProps: [String: Any]) -> PortSummary {
        let hasThunderbolt = ioKitProps["Thunderbolt"] as? Bool ?? false
        let hasUSB3       = ioKitProps["USB3"] as? Bool ?? false
        let isConnected   = ioKitProps["Connected"] as? Bool ?? false
        let emarkerPresent = ioKitProps["CableEMarker"] as? Bool ?? false

        let headline: PortHeadline
        if !isConnected {
            headline = .nothing
        } else if hasThunderbolt {
            headline = .thunderbolt
        } else if hasUSB3 {
            headline = .usbDevice
        } else if emarkerPresent {
            headline = .chargingOnly
        } else {
            headline = .slowCable
        }

        // 解析PDO以生成充电诊断信息
        let pdos = parsePDOs(from: ioKitProps)
        let negotiated = pdos.first(where: { $0.isActive })
        let diagnostic = buildChargingDiagnostic(pdos: pdos, negotiated: negotiated)

        return PortSummary(
            headline: headline,
            chargingDiagnostic: diagnostic,
            dataRate: emarkerPresent ? decodeDataRate(ioKitProps) : nil,
            currentRating: emarkerPresent ? decodeCurrentRating(ioKitProps) : nil,
            negotiatedPDO: negotiated,
            allPDOs: pdos
        )
    }
}

PDO Parsing (Power Data Objects)

PDO解析(电源数据对象)

swift
struct PDO {
    let voltage: Double    // Volts
    let maxCurrent: Double // Amps
    let maxWatts: Double   // voltage * maxCurrent
    let isActive: Bool

    static func parse(raw: UInt32, isActive: Bool) -> PDO? {
        // Fixed supply PDO: bits [19:10] = max current (10mA units),
        //                   bits [29:20] = voltage (50mV units)
        let currentRaw = (raw >> 10) & 0x3FF
        let voltageRaw = (raw >> 20) & 0x3FF
        guard voltageRaw > 0 else { return nil }

        let voltage = Double(voltageRaw) * 0.05
        let current = Double(currentRaw) * 0.01
        return PDO(
            voltage: voltage,
            maxCurrent: current,
            maxWatts: voltage * current,
            isActive: isActive
        )
    }
}

func parsePDOs(from props: [String: Any]) -> [PDO] {
    guard let pdoArray = props["PDOs"] as? [UInt32],
          let activePDOIndex = props["ActivePDOIndex"] as? Int else {
        return []
    }
    return pdoArray.enumerated().compactMap { idx, raw in
        PDO.parse(raw: raw, isActive: idx == activePDOIndex)
    }
}
swift
struct PDO {
    let voltage: Double    // 伏特
    let maxCurrent: Double // 安培
    let maxWatts: Double   // voltage * maxCurrent
    let isActive: Bool

    static func parse(raw: UInt32, isActive: Bool) -> PDO? {
        // 固定供电PDO:位[19:10] = 最大电流(单位10mA),
        //                   位[29:20] = 电压(单位50mV)
        let currentRaw = (raw >> 10) & 0x3FF
        let voltageRaw = (raw >> 20) & 0x3FF
        guard voltageRaw > 0 else { return nil }

        let voltage = Double(voltageRaw) * 0.05
        let current = Double(currentRaw) * 0.01
        return PDO(
            voltage: voltage,
            maxCurrent: current,
            maxWatts: voltage * current,
            isActive: isActive
        )
    }
}

func parsePDOs(from props: [String: Any]) -> [PDO] {
    guard let pdoArray = props["PDOs"] as? [UInt32],
          let activePDOIndex = props["ActivePDOIndex"] as? Int else {
        return []
    }
    return pdoArray.enumerated().compactMap { idx, raw in
        PDO.parse(raw: raw, isActive: idx == activePDOIndex)
    }
}

SwiftUI Popover Pattern (ContentView.swift)

SwiftUI弹出窗口模式(ContentView.swift)

swift
import SwiftUI

struct ContentView: View {
    @StateObject private var model = CableModel()

    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            HeaderView()
            Divider()
            ScrollView {
                VStack(alignment: .leading, spacing: 12) {
                    ForEach(model.ports) { port in
                        PortRowView(port: port)
                    }
                }
                .padding()
            }
        }
        .frame(width: 360)
        .onAppear { model.refresh() }
    }
}

struct PortRowView: View {
    let port: PortSummary

    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Label(port.headline.rawValue, systemImage: iconName(for: port.headline))
                .font(.headline)

            if let diagnostic = port.chargingDiagnostic {
                Text(diagnostic)
                    .font(.subheadline)
                    .foregroundStyle(.secondary)
            }

            if let rate = port.dataRate {
                Text("Speed: \(rate)")
                    .font(.caption)
            }
        }
        .padding(.vertical, 6)
    }

    func iconName(for headline: PortHeadline) -> String {
        switch headline {
        case .thunderbolt:  return "bolt.fill"
        case .usbDevice:    return "cable.connector"
        case .chargingOnly: return "battery.100.bolt"
        case .slowCable:    return "exclamationmark.triangle"
        case .nothing:      return "circle.dashed"
        }
    }
}
swift
import SwiftUI

struct ContentView: View {
    @StateObject private var model = CableModel()

    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            HeaderView()
            Divider()
            ScrollView {
                VStack(alignment: .leading, spacing: 12) {
                    ForEach(model.ports) { port in
                        PortRowView(port: port)
                    }
                }
                .padding()
            }
        }
        .frame(width: 360)
        .onAppear { model.refresh() }
    }
}

struct PortRowView: View {
    let port: PortSummary

    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Label(port.headline.rawValue, systemImage: iconName(for: port.headline))
                .font(.headline)

            if let diagnostic = port.chargingDiagnostic {
                Text(diagnostic)
                    .font(.subheadline)
                    .foregroundStyle(.secondary)
            }

            if let rate = port.dataRate {
                Text("Speed: \(rate)")
                    .font(.caption)
            }
        }
        .padding(.vertical, 6)
    }

    func iconName(for headline: PortHeadline) -> String {
        switch headline {
        case .thunderbolt:  return "bolt.fill"
        case .usbDevice:    return "cable.connector"
        case .chargingOnly: return "battery.100.bolt"
        case .slowCable:    return "exclamationmark.triangle"
        case .nothing:      return "circle.dashed"
        }
    }
}

Menu Bar App Setup (WhatCableApp.swift)

菜单栏应用设置(WhatCableApp.swift)

swift
import SwiftUI

@main
struct WhatCableApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        // No WindowGroup — pure menu bar app
        Settings { SettingsView() }
    }
}

class AppDelegate: NSObject, NSApplicationDelegate {
    var statusItem: NSStatusItem?
    var popover = NSPopover()

    func applicationDidFinishLaunching(_ notification: Notification) {
        NSApp.setActivationPolicy(.accessory) // hide from Dock by default

        statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
        if let button = statusItem?.button {
            button.image = NSImage(systemSymbolName: "cable.connector",
                                   accessibilityDescription: "WhatCable")
            button.action = #selector(togglePopover)
            button.sendAction(on: [.leftMouseUp, .rightMouseUp])
        }

        popover.contentViewController = NSHostingController(rootView: ContentView())
        popover.behavior = .transient
    }

    @objc func togglePopover(_ sender: NSStatusBarButton) {
        if popover.isShown {
            popover.performClose(sender)
        } else {
            if let button = statusItem?.button {
                popover.show(relativeTo: button.bounds, of: button,
                             preferredEdge: .minY)
            }
        }
    }
}
swift
import SwiftUI

@main
struct WhatCableApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        // 无需WindowGroup — 纯菜单栏应用
        Settings { SettingsView() }
    }
}

class AppDelegate: NSObject, NSApplicationDelegate {
    var statusItem: NSStatusItem?
    var popover = NSPopover()

    func applicationDidFinishLaunching(_ notification: Notification) {
        NSApp.setActivationPolicy(.accessory) // 默认隐藏Dock图标

        statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
        if let button = statusItem?.button {
            button.image = NSImage(systemSymbolName: "cable.connector",
                                   accessibilityDescription: "WhatCable")
            button.action = #selector(togglePopover)
            button.sendAction(on: [.leftMouseUp, .rightMouseUp])
        }

        popover.contentViewController = NSHostingController(rootView: ContentView())
        popover.behavior = .transient
    }

    @objc func togglePopover(_ sender: NSStatusBarButton) {
        if popover.isShown {
            popover.performClose(sender)
        } else {
            if let button = statusItem?.button {
                popover.show(relativeTo: button.bounds, of: button,
                             preferredEdge: .minY)
            }
        }
    }
}

Charging Diagnostic Helper

充电诊断辅助函数

swift
func buildChargingDiagnostic(pdos: [PDO], negotiated: PDO?) -> String? {
    guard let active = negotiated else { return nil }
    let maxAvailable = pdos.map(\.maxWatts).max() ?? 0

    let activeW = active.maxWatts
    let ratio = maxAvailable > 0 ? activeW / maxAvailable : 1.0

    if ratio < 0.5 {
        return String(format: "Charging at %.0fW (charger can do up to %.0fW)", activeW, maxAvailable)
    } else if ratio < 0.9 {
        return String(format: "Cable is limiting charging speed (%.0fW of %.0fW available)", activeW, maxAvailable)
    } else {
        return String(format: "Charging well at %.0fW", activeW)
    }
}
swift
func buildChargingDiagnostic(pdos: [PDO], negotiated: PDO?) -> String? {
    guard let active = negotiated else { return nil }
    let maxAvailable = pdos.map(\.maxWatts).max() ?? 0

    let activeW = active.maxWatts
    let ratio = maxAvailable > 0 ? activeW / maxAvailable : 1.0

    if ratio < 0.5 {
        return String(format: "Charging at %.0fW (charger can do up to %.0fW)", activeW, maxAvailable)
    } else if ratio < 0.9 {
        return String(format: "Cable is limiting charging speed (%.0fW of %.0fW available)", activeW, maxAvailable)
    } else {
        return String(format: "Charging well at %.0fW", activeW)
    }
}

Common Patterns

通用开发模式

Adding a New Port Property

添加新的端口属性

  1. Read the raw value from IOKit props in
    IOKitReader.swift
  2. Decode it (bit-fields) in
    PDVDO.swift
    or a dedicated decoder
  3. Expose it on
    PortSummary
    struct
  4. Display it in
    PortRowView
    or a detail expansion in
    ContentView.swift
  1. IOKitReader.swift
    中从IOKit属性读取原始值
  2. PDVDO.swift
    或专用解码器中解码(位字段处理)
  3. PortSummary
    结构体中暴露该属性
  4. PortRowView
    ContentView.swift
    的详情扩展中展示

Handling Different Mac Generations

适配不同Mac世代

swift
let hpmServiceNames = [
    "AppleHPMInterfaceType12",  // M3-era
    "AppleHPMInterfaceType11",
    "AppleHPMInterfaceType10",
    "AppleTCControllerType10",  // M1 / M2
]

func findHPMService() -> io_service_t {
    for name in hpmServiceNames {
        let service = IOServiceGetMatchingService(
            kIOMainPortDefault,
            IOServiceMatching(name)
        )
        if service != IO_OBJECT_NULL { return service }
    }
    return IO_OBJECT_NULL
}
swift
let hpmServiceNames = [
    "AppleHPMInterfaceType12",  // M3系列
    "AppleHPMInterfaceType11",
    "AppleHPMInterfaceType10",
    "AppleTCControllerType10",  // M1 / M2
]

func findHPMService() -> io_service_t {
    for name in hpmServiceNames {
        let service = IOServiceGetMatchingService(
            kIOMainPortDefault,
            IOServiceMatching(name)
        )
        if service != IO_OBJECT_NULL { return service }
    }
    return IO_OBJECT_NULL
}

Debug / Engineer Mode (⌥-click)

调试/工程师模式(⌥-点击)

swift
@State private var showRawProps = false

// In button handler:
if NSEvent.modifierFlags.contains(.option) {
    showRawProps.toggle()
}

// In view:
if showRawProps, let props = port.rawIOKitProperties {
    RawPropertiesView(props: props)
}
swift
@State private var showRawProps = false

// 在按钮处理函数中:
if NSEvent.modifierFlags.contains(.option) {
    showRawProps.toggle()
}

// 在视图中:
if showRawProps, let props = port.rawIOKitProperties {
    RawPropertiesView(props: props)
}

Troubleshooting

故障排除

SymptomLikely CauseFix
No ports shownIOKit service name mismatchTry all
hpmServiceNames
variants; check
ioreg -l
output
E-marker data missingCable has no e-marker chipExpected for cables < 60W; only marked cables have VDOs
PDO list emptyDevice not PD-capable or port is source-onlyCheck
IOPortFeaturePowerSource
presence in
ioreg
Build fails on IntelArchitecture flag missingUse
./scripts/build-app.sh
for universal build
Gatekeeper warningAd-hoc signature onlySet
DEVELOPER_ID
in
.env
and re-run build script
Wrong wattage shownPD 3.2 EPR AVS PDO formatEPR PDOs use different bit layout; check PD 3.2 spec §6.4.1
症状可能原因解决方法
无端口显示IOKit服务名称不匹配尝试所有
hpmServiceNames
变体;查看
ioreg -l
输出
电子标记数据缺失线缆无电子标记芯片功率<60W的线缆通常无此芯片;仅带标记的线缆有VDO
PDO列表为空设备不支持PD或端口仅为供电端
ioreg
中检查
IOPortFeaturePowerSource
是否存在
Intel设备上构建失败缺少架构标识使用
./scripts/build-app.sh
构建通用包
Gatekeeper警告仅为临时签名
.env
中设置
DEVELOPER_ID
并重新运行构建脚本
显示功率错误PD 3.2 EPR AVS PDO格式EPR PDO使用不同的位布局;查看PD 3.2规范§6.4.1

Inspect IOKit Live

实时检查IOKit

bash
undefined
bash
undefined

Dump all HPM interface properties

导出所有HPM接口属性

ioreg -l -n AppleHPMInterfaceType10 | less
ioreg -l -n AppleHPMInterfaceType10 | less

Watch for USB-C connect/disconnect events

监听USB-C连接/断开事件

ioreg -w 0 -l -c IOUSBDevice | grep -E "Product|Vendor|Speed"
ioreg -w 0 -l -c IOUSBDevice | grep -E "Product|Vendor|Speed"

Check power delivery objects

检查电源传输对象

ioreg -l | grep -A 20 IOPortFeaturePowerSource
undefined
ioreg -l | grep -A 20 IOPortFeaturePowerSource
undefined

Key IOKit Property Names to Watch

需要关注的关键IOKit属性名称

swift
// Connection state
"Connected"              // Bool
"PlugOrientation"        // Int (0 = unflipped, 1 = flipped)

// Transports
"USB2"                   // Bool
"USB3"                   // Bool  
"Thunderbolt"            // Bool
"DisplayPort"            // Bool

// E-marker
"CableEMarker"           // Bool
"CableVDO"               // UInt32 — raw passive cable VDO
"ActiveCableVDO1"        // UInt32 — active cable VDO

// Power
"PDOs"                   // [UInt32]
"ActivePDOIndex"         // Int
"NegotiatedWatts"        // Double
swift
// 连接状态
"Connected"              // Bool
"PlugOrientation"        // Int (0 = 未翻转, 1 = 已翻转)

// 传输类型
"USB2"                   // Bool
"USB3"                   // Bool  
"Thunderbolt"            // Bool
"DisplayPort"            // Bool

// 电子标记
"CableEMarker"           // Bool
"CableVDO"               // UInt32 — 原始无源线缆VDO
"ActiveCableVDO1"        // UInt32 — 有源线缆VDO

// 电源
"PDOs"                   // [UInt32]
"ActivePDOIndex"         // Int
"NegotiatedWatts"        // Double