energykit

energykit

熱門

使用 EnergyKit 查詢電網電力預測並提交負載事件,幫助使用者最佳化家庭用電。適用於建置智慧家庭 App、電動車充電控制、空調排程或能源管理儀表板,引導使用者在電網更乾淨或電價更低的時段用電。

940星標
47分支
更新於 2026/7/15
SKILL.md
唯讀
名稱
energykit
描述

使用 EnergyKit 查詢電網電力預測並提交負載事件,幫助使用者最佳化家庭用電。適用於建置智慧家庭 App、電動車充電控制、空調排程或能源管理儀表板,引導使用者在電網更乾淨或電價更低的時段用電。

EnergyKit

利用電網清潔度與成本指引,來轉移或減少受管理裝置的負載。
若要取得受管理裝置的洞察,請即時提交該裝置的真實負載事件。

Beta 敏感注意。 核心 EnergyKit 於 iOS/iPadOS 26 推出。iOS/iPadOS 27 的
ElectricalLoadDevice 與 Home 端 LoadEvents 體驗仍為 Beta;在依賴這些 API 前,
請重新確認最新的 Apple 文件。

目錄

設定

授權與版本差異

執行環境 負載事件裝置 API 功能權限
iOS/iPadOS 26.x deviceID: 相容性初始化器 EnergyKit
iOS/iPadOS 27+ Beta ElectricalLoadDevice 搭配 device: 初始化器 EnergyKit;若要整合 Home App 需額外加入 EnergyKit LoadEvents

所有 EnergyKit 使用都需要 com.apple.developer.energykit;請在 App Target 中啟用 EnergyKit 功能。
在 iOS/iPadOS 27+ 上,僅當 App 需要在 Home App 中顯示裝置名稱、能源情境、活動記錄、歷史圖表或趨勢通知時,
才需加入 EnergyKit LoadEvents 功能(com.apple.developer.energykit.loadevents-experience)。
該 Home 體驗需要同時具備兩項授權。缺少權限可能導致 EnergyKitError.permissionDenied

匯入

import EnergyKit

平台可用性: 核心 EnergyKit API 適用於 iOS/iPadOS 26.0 以上。部分洞察細分 API(包括電網清潔度分類)
為 26.1 以上,需要加上可用性檢查。Apple 目前僅針對美國本土提供電力指引文件;請處理 EnergyKitError.unsupportedRegion

核心概念

EnergyKit 提供兩項主要功能:

  1. 電力指引 — 時間加權的預測,告訴 App 何時用電較乾淨,以及(當費率資料可用時)何時較便宜
  2. 負載事件 — 來自受管理裝置(電動車充電器、空調)的遙測資料,由同一個請求指引的裝置/App 提交,以便 EnergyKit 產生洞察

主要型別

型別 角色
ElectricityGuidance 包含加權時間區間的預測資料
ElectricityGuidance.Service 取得指引資料的介面
ElectricityGuidance.Query 指定轉移或減少動作的查詢
ElectricityGuidance.Value 包含評分(0.0-1.0)的時間區間
EnergyVenue 已註冊能源管理的實際位置(家庭)
ElectricVehicleLoadEvent 電動車充電器遙測的負載事件
ElectricHVACLoadEvent 空調系統遙測的負載事件
ElectricalLoadDevice iOS/iPadOS 27+ Beta 中負載事件的裝置識別
ElectricityInsightService 查詢能源/執行時間洞察的服務
ElectricityInsightRecord 歷史能源或執行時間資料,可依費率或 26.1+ 電網清潔度細分
ElectricityInsightQuery 查詢歷史洞察資料的查詢

建議動作

動作 使用情境
.shift 可將用電移至不同時間的裝置(電動車充電)
.reduce 可降低用電量而不停止的裝置(空調調節)

查詢電力指引

使用 ElectricityGuidance.Service 取得某個場域的預測串流。

import EnergyKit

func observeGuidance(venueID: UUID) async throws {
    let query = ElectricityGuidance.Query(suggestedAction: .shift)
    let service = ElectricityGuidance.sharedService

    let guidanceStream = service.guidance(using: query, at: venueID)

    for try await guidance in guidanceStream {
        print("Guidance token: \(guidance.guidanceToken)")
        print("Interval: \(guidance.interval)")
        print("Venue: \(guidance.energyVenueID)")

        // 檢查是否有費率方案資訊
        if guidance.options.contains(.guidanceIncorporatesRatePlan) {
            print("已納入費率方案資料")
        }
        if guidance.options.contains(.locationHasRatePlan) {
            print("該地點有費率方案")
        }

        processGuidanceValues(guidance.values)
    }
}

處理指引數值

每個 ElectricityGuidance.Value 包含一個時間區間與一個 0.0 到 1.0 的評分。
評分越低表示用電時機越好。

func processGuidanceValues(_ values: [ElectricityGuidance.Value]) {
    for value in values {
        let interval = value.interval
        let rating = value.rating  // 0.0(最佳)到 1.0(最差)

        print("從 \(interval.start) 到 \(interval.end):評分 \(rating)")
    }
}

// 找出最佳充電時段
func bestChargingWindow(
    in values: [ElectricityGuidance.Value]
) -> ElectricityGuidance.Value? {
    values.min(by: { $0.rating < $1.rating })
}

// 找出低於門檻的所有「良好」時段
func goodWindows(
    in values: [ElectricityGuidance.Value],
    threshold: Double = 0.3
) -> [ElectricityGuidance.Value] {
    values.filter { $0.rating <= threshold }
}

在 SwiftUI 中顯示指引

import SwiftUI
import EnergyKit

struct GuidanceTimelineView: View {
    let values: [ElectricityGuidance.Value]

    var body: some View {
        List(values, id: \.interval.start) { value in
            HStack {
                VStack(alignment: .leading) {
                    Text(value.interval.start, style: .time)
                    Text(value.interval.end, style: .time)
                        .foregroundStyle(.secondary)
                }
                Spacer()
                RatingIndicator(rating: value.rating)
            }
        }
    }
}

struct RatingIndicator: View {
    let rating: Double

    var color: Color {
        if rating <= 0.3 { return .green }
        if rating <= 0.6 { return .yellow }
        return .red
    }

    var label: String {
        if rating <= 0.3 { return "良好" }
        if rating <= 0.6 { return "普通" }
        return "避免"
    }

    var body: some View {
        Text(label)
            .padding(.horizontal)
            .padding(.vertical)
            .background(color.opacity(0.2))
            .foregroundStyle(color)
            .clipShape(Capsule())
    }
}

能源場域

EnergyVenue 代表已註冊能源管理的實際位置。

// 列出所有場域
func listVenues() async throws -> [EnergyVenue] {
    try await EnergyVenue.venues()
}

// 根據 ID 取得特定場域
func getVenue(id: UUID) async throws -> EnergyVenue {
    try await EnergyVenue.venue(for: id)
}

// 取得與 HomeKit 家庭相符的場域
func getVenueForHome(homeID: UUID) async throws -> EnergyVenue {
    try await EnergyVenue.venue(matchingHomeUniqueIdentifier: homeID)
}

場域屬性

let venue = try await EnergyVenue.venue(for: venueID)
print("場域 ID:\(venue.id)")
print("場域名稱:\(venue.name)")

提交負載事件

將裝置的用電資料回報給系統。這有助於系統產生電力洞察。
必須由同一個具備 EnergyKit 能力的裝置/App(即請求電力指引的那個)提交對應的負載事件,
並使用 EnergyKit 回傳的指引 token。請勿自行偽造 token。

電動車充電器負載事件

func submitEVBeginEvent(
    at venue: EnergyVenue,
    guidanceToken: UUID,
    deviceID: String,
    deviceName: String
) async throws {
    let session = ElectricVehicleLoadEvent.Session(
        id: UUID(),
        state: .begin,
        guidanceState: ElectricVehicleLoadEvent.Session.GuidanceState(
            wasFollowingGuidance: true,
            guidanceToken: guidanceToken
        )
    )

    let measurement = ElectricVehicleLoadEvent.ElectricalMeasurement(
        stateOfCharge: 45,
        direction: .imported,
        power: Measurement(value: 0, unit: .kilowatts),
        energy: Measurement(value: 0, unit: .kilowattHours)
    )

    let event: ElectricVehicleLoadEvent
    if #available(iOS 27.0, iPadOS 27.0, *) {
        let device = ElectricalLoadDevice(
            id: deviceID,
            name: deviceName,
            type: .electricVehicle
        )
        event = ElectricVehicleLoadEvent(
            timestamp: Date(), measurement: measurement,
            session: session, device: device
        )
    } else {
        // iOS/iPadOS 26 相容性;在 iOS 27 SDK 中已棄用。
        event = ElectricVehicleLoadEvent(
            timestamp: Date(), measurement: measurement,
            session: session, deviceID: deviceID
        )
    }

    try await venue.submitEvents([event])
}

空調負載事件

func submitHVACEvent(
    at venue: EnergyVenue,
    guidanceToken: UUID,
    stage: Int,
    deviceID: String,
    deviceName: String
) async throws {
    let session = ElectricHVACLoadEvent.Session(
        id: UUID(),
        state: .active,
        guidanceState: ElectricHVACLoadEvent.Session.GuidanceState(
            wasFollowingGuidance: true,
            guidanceToken: guidanceToken
        )
    )

    let measurement = ElectricHVACLoadEvent.ElectricalMeasurement(stage: stage)

    let event: ElectricHVACLoadEvent
    if #available(iOS 27.0, iPadOS 27.0, *) {
        let device = ElectricalLoadDevice(
            id: deviceID,
            name: deviceName,
            type: .hvac
        )
        event = ElectricHVACLoadEvent(
            timestamp: Date(), measurement: measurement,
            session: session, device: device
        )
    } else {
        // iOS/iPadOS 26 相容性;在 iOS 27 SDK 中已棄用。
        event = ElectricHVACLoadEvent(
            timestamp: Date(), measurement: measurement,
            session: session, deviceID: deviceID
        )
    }

    try await venue.submitEvents([event])
}

工作階段狀態

狀態 使用時機
.begin 裝置開始用電
.active 裝置正在用電(定期更新)
.end 裝置停止用電

請保持 .begin → .active → .end 的順序,並即時提交事件,避免長時間累積批次。
對於電動車充電,請在開始時提交 .begin(功率與能量為零),大約每 15 分鐘或發生顯著變化時提交 .active
結束時提交 .end(功率為零,能量為累計值)。保留未確認的事件,並在遇到 EnergyKitError.rateLimitExceeded
以有限退避方式重試。請參考
EV 工作階段管理員
HVAC 控制管理員 以取得裝置特定的生命週期處理。

僅在 iOS/iPadOS 27+ 且同時具備基礎 EnergyKit 與 EnergyKit LoadEvents 功能時,
才承諾 Home App 中的裝置名稱、能源情境、活動記錄、圖表與趨勢通知。

電力洞察

使用 ElectricityInsightService 查詢裝置的歷史能源與執行時間資料。
空的 ElectricityInsightQuery.Options 選項集只會回傳總計;不會填入清潔度或費率細分。
僅在 UI 需要這些細分時才請求 .cleanliness 及/或 .tariff
請勿以 MetricKit 的 App 電源指標取代 EnergyKit 洞察;EnergyKit 洞察依賴於為受管理裝置提交的 EnergyKit 負載事件。

根據請求的範圍選擇洞察粒度。對於七天的檢視,請查詢 .hourly;僅當查詢涵蓋至少一個日曆月時才使用 .daily

func queryEnergyInsights(deviceID: String, venueID: UUID) async throws {
    let sevenDaysAgo = Calendar.current.date(
        byAdding: .day,
        value: -7,
        to: Date()
    )!

    let query = ElectricityInsightQuery(
        options: [.cleanliness, .tariff],
        range: DateInterval(
            start: sevenDaysAgo,
            end: Date()
        ),
        granularity: .hourly,
        flowDirection: .imported
    )

    let service = ElectricityInsightService.shared
    let stream = try await service.energyInsights(
        forDeviceID: deviceID, using: query, atVenue: venueID
    )

    for await record in stream {
        if let total = record.totalEnergy { print("總計:\(total)") }

        if #available(iOS 26.1, iPadOS 26.1, *),
           let cleaner = record.dataByGridCleanliness?.cleaner {
            print("較乾淨:\(cleaner)")
        }
    }
}

對於執行時間資料,請使用 runtimeInsights(forDeviceID:using:atVenue:) 而非能源資料。
粒度選項:.hourly.daily.weekly.monthly.yearly
選擇符合 Apple 最小彙總視窗的範圍:至少一個日曆週使用每小時、至少一個日曆月使用每日、
至少六個月使用每週、至少一個日曆年使用每月或每年。
完整的洞察範例請參閱 references/energykit-patterns.md

常見錯誤

錯誤 修正
在設定功能權限前進行查詢 確認 EnergyKit 授權,並處理 .permissionDenied
假設每個區域都有指引 Apple 目前僅在美國本土提供指引文件;請處理不支援區域及場域/指引不可用的狀態。
偽造或丟棄指引 token 在請求裝置上保留真實 token,並在提交負載事件時一併提交。
發送孤立或延遲的負載樣本 保持 .begin → .active → .end 順序,即時提交,並在提交成功前保留事件。
使用 deviceID: 作為目前預設 使用 iOS/iPadOS 27+ 的 ElectricalLoadDevicedevice:;僅在 26.x 執行環境分支保留 deviceID:
使用寫死的場域 ID 使用 EnergyVenue.venues() 探索場域,並選擇目標場域。

審查清單

  • [ ] 已加入基礎 EnergyKit 功能;iOS/iPadOS 27+ 的 Home 整合也加入了 EnergyKit LoadEvents
  • [ ] 已處理區域、權限、場域探索、指引不可用及服務錯誤
  • [ ] 真實的指引 token 保留在請求裝置/App 中,並與其提交的負載事件一同送出
  • [ ] iOS/iPadOS 27+ 使用 ElectricalLoadDevice/device:deviceID: 僅限於 26.x 相容性
  • [ ] .begin → .active → .end 事件遵循裝置節奏,即時提交,在失敗時保留,並以有限退避重試速率限制
  • [ ] 評分/動作的解釋正確;洞察選項、可用性、粒度與最小範圍符合 UI 需求
  • [ ] 未以 MetricKit 遙測取代 EnergyKit 負載事件或洞察

參考資料