energykit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

EnergyKit

EnergyKit

Provide grid electricity forecasts to help users choose when to use electricity. EnergyKit identifies times when there is relatively cleaner or less expensive electricity on the grid, enabling apps to shift or reduce load accordingly. Targets Swift 6.2 / iOS 26+.
提供电网电力预测,帮助用户选择用电时间。 EnergyKit可识别电网供电相对更清洁或更便宜的时段,让应用能够相应地转移或减少负载。适配Swift 6.2 / iOS 26+。

Contents

目录

Setup

安装配置

Entitlement

权限配置

EnergyKit requires the
com.apple.developer.energykit
entitlement. Add it to your app's entitlements file.
EnergyKit需要
com.apple.developer.energykit
权限,请将其添加到应用的权限配置文件中。

Import

导入

swift
import EnergyKit
Platform availability: iOS 26+, iPadOS 26+.
swift
import EnergyKit
支持平台: iOS 26+, iPadOS 26+.

Core Concepts

核心概念

EnergyKit provides two main capabilities:
  1. Electricity Guidance -- time-weighted forecasts telling apps when electricity is cleaner or cheaper, so devices can shift or reduce consumption
  2. Load Events -- telemetry from devices (EV chargers, HVAC) submitted back to the system to track how well the app follows guidance
EnergyKit提供两大核心能力:
  1. 用电指引——带时间权重的预测数据,告知应用何时电力更清洁或更便宜,以便设备转移或降低功耗
  2. 负载事件——来自设备(EV充电桩、HVAC)的遥测数据回传给系统,用于追踪应用遵循指引的效果

Key Types

核心类型

TypeRole
ElectricityGuidance
Forecast data with weighted time intervals
ElectricityGuidance.Service
Interface for obtaining guidance data
ElectricityGuidance.Query
Query specifying shift or reduce action
ElectricityGuidance.Value
A time interval with a rating (0.0-1.0)
EnergyVenue
A physical location (home) registered for energy management
ElectricVehicleLoadEvent
Load event for EV charger telemetry
ElectricHVACLoadEvent
Load event for HVAC system telemetry
ElectricityInsightService
Service for querying energy/runtime insights
ElectricityInsightRecord
Historical energy data broken down by cleanliness/tariff
ElectricityInsightQuery
Query for historical insight data
类型作用
ElectricityGuidance
带加权时间间隔的预测数据
ElectricityGuidance.Service
获取指引数据的接口
ElectricityGuidance.Query
指定转移或降低负载操作的查询对象
ElectricityGuidance.Value
带评分(0.0-1.0)的时间间隔对象
EnergyVenue
注册用于能源管理的物理地点(家庭)
ElectricVehicleLoadEvent
对应EV充电桩遥测的负载事件
ElectricHVACLoadEvent
对应HVAC系统遥测的负载事件
ElectricityInsightService
查询能源/运行时长洞察的服务
ElectricityInsightRecord
按清洁度/电价拆分的历史能源数据
ElectricityInsightQuery
查询历史洞察数据的查询对象

Suggested Actions

建议操作

ActionUse Case
.shift
Devices that can move consumption to a different time (EV charging)
.reduce
Devices that can lower consumption without stopping (HVAC setback)
操作使用场景
.shift
可将用电转移到其他时段的设备(如EV充电)
.reduce
可在不停止运行的前提下降低功耗的设备(如HVAC调温)

Querying Electricity Guidance

查询用电指引

Use
ElectricityGuidance.Service
to get a forecast stream for a venue.
swift
import EnergyKit

func observeGuidance(venueID: UUID) async throws {
    let query = ElectricityGuidance.Query(suggestedAction: .shift)
    let service = ElectricityGuidance.sharedService  // Verify access pattern against Xcode 26 SDK

    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)")

        // Check if rate plan information is available
        if guidance.options.contains(.guidanceIncorporatesRatePlan) {
            print("Rate plan data incorporated")
        }
        if guidance.options.contains(.locationHasRatePlan) {
            print("Location has a rate plan")
        }

        processGuidanceValues(guidance.values)
    }
}
使用
ElectricityGuidance.Service
获取某个场所的预测流。
swift
import EnergyKit

func observeGuidance(venueID: UUID) async throws {
    let query = ElectricityGuidance.Query(suggestedAction: .shift)
    let service = ElectricityGuidance.sharedService  // 对照Xcode 26 SDK验证访问模式

    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)
    }
}

Working with Guidance Values

处理指引数值

Each
ElectricityGuidance.Value
contains a time interval and a rating from 0.0 to 1.0. Lower ratings indicate better times to use electricity.
swift
func processGuidanceValues(_ values: [ElectricityGuidance.Value]) {
    for value in values {
        let interval = value.interval
        let rating = value.rating  // 0.0 (best) to 1.0 (worst)

        print("From \(interval.start) to \(interval.end): rating \(rating)")
    }
}

// Find the best time to charge
func bestChargingWindow(
    in values: [ElectricityGuidance.Value]
) -> ElectricityGuidance.Value? {
    values.min(by: { $0.rating < $1.rating })
}

// Find all "good" windows below a threshold
func goodWindows(
    in values: [ElectricityGuidance.Value],
    threshold: Double = 0.3
) -> [ElectricityGuidance.Value] {
    values.filter { $0.rating <= threshold }
}
每个
ElectricityGuidance.Value
包含一个时间间隔和0.0到1.0的评分,评分越低代表用电时机越好。
swift
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 }
}

Displaying Guidance in SwiftUI

在SwiftUI中展示指引

swift
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 "Good" }
        if rating <= 0.6 { return "Fair" }
        return "Avoid"
    }

    var body: some View {
        Text(label)
            .padding(.horizontal, 8)
            .padding(.vertical, 4)
            .background(color.opacity(0.2))
            .foregroundStyle(color)
            .clipShape(Capsule())
    }
}
swift
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, 8)
            .padding(.vertical, 4)
            .background(color.opacity(0.2))
            .foregroundStyle(color)
            .clipShape(Capsule())
    }
}

Energy Venues

能源场所

An
EnergyVenue
represents a physical location registered for energy management.
swift
// List all venues
func listVenues() async throws -> [EnergyVenue] {
    try await EnergyVenue.venues()
}

// Get a specific venue by ID
func getVenue(id: UUID) async throws -> EnergyVenue {
    try await EnergyVenue.venue(for: id)
}

// Get a venue matching a HomeKit home
func getVenueForHome(homeID: UUID) async throws -> EnergyVenue {
    try await EnergyVenue.venue(matchingHomeUniqueIdentifier: homeID)
}
EnergyVenue
代表注册用于能源管理的物理位置。
swift
// 列出所有场所
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)
}

Venue Properties

场所属性

swift
let venue = try await EnergyVenue.venue(for: venueID)
print("Venue ID: \(venue.id)")
print("Venue name: \(venue.name)")
swift
let venue = try await EnergyVenue.venue(for: venueID)
print("场所ID: \(venue.id)")
print("场所名称: \(venue.name)")

Submitting Load Events

提交负载事件

Report device consumption data back to the system. This helps the system improve future guidance accuracy.
向系统上报设备用电数据,可帮助系统提升未来指引的准确性。

EV Charger Load Events

EV充电桩负载事件

swift
func submitEVChargingEvent(
    at venue: EnergyVenue,
    guidanceToken: UUID,
    deviceID: 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: 7.2, unit: .kilowatts),
        energy: Measurement(value: 0, unit: .kilowattHours)
    )

    let event = ElectricVehicleLoadEvent(
        timestamp: Date(),
        measurement: measurement,
        session: session,
        deviceID: deviceID
    )

    try await venue.submitEvents([event])
}
swift
func submitEVChargingEvent(
    at venue: EnergyVenue,
    guidanceToken: UUID,
    deviceID: 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: 7.2, unit: .kilowatts),
        energy: Measurement(value: 0, unit: .kilowattHours)
    )

    let event = ElectricVehicleLoadEvent(
        timestamp: Date(),
        measurement: measurement,
        session: session,
        deviceID: deviceID
    )

    try await venue.submitEvents([event])
}

HVAC Load Events

HVAC负载事件

swift
func submitHVACEvent(
    at venue: EnergyVenue,
    guidanceToken: UUID,
    stage: Int,
    deviceID: 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(
        timestamp: Date(),
        measurement: measurement,
        session: session,
        deviceID: deviceID
    )

    try await venue.submitEvents([event])
}
swift
func submitHVACEvent(
    at venue: EnergyVenue,
    guidanceToken: UUID,
    stage: Int,
    deviceID: 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(
        timestamp: Date(),
        measurement: measurement,
        session: session,
        deviceID: deviceID
    )

    try await venue.submitEvents([event])
}

Session States

会话状态

StateWhen to Use
.begin
Device starts consuming electricity
.active
Device is actively consuming (periodic updates)
.end
Device stops consuming electricity
状态使用时机
.begin
设备开始用电时
.active
设备正在用电时(周期性上报)
.end
设备停止用电时

Electricity Insights

用电洞察

Query historical energy and runtime data for devices using
ElectricityInsightService
.
swift
func queryEnergyInsights(deviceID: String, venueID: UUID) async throws {
    let query = ElectricityInsightQuery(
        options: [.cleanliness, .tariff],
        range: DateInterval(
            start: Calendar.current.date(byAdding: .day, value: -7, to: Date())!,
            end: Date()
        ),
        granularity: .daily,
        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: \(total)") }
        if let cleaner = record.dataByGridCleanliness?.cleaner {
            print("Cleaner: \(cleaner)")
        }
    }
}
Use
runtimeInsights(forDeviceID:using:atVenue:)
for runtime data instead of energy. Granularity options:
.hourly
,
.daily
,
.weekly
,
.monthly
,
.yearly
. See
references/energykit-patterns.md
for full insight examples.
使用
ElectricityInsightService
查询设备的历史能源和运行时长数据。
swift
func queryEnergyInsights(deviceID: String, venueID: UUID) async throws {
    let query = ElectricityInsightQuery(
        options: [.cleanliness, .tariff],
        range: DateInterval(
            start: Calendar.current.date(byAdding: .day, value: -7, to: Date())!,
            end: Date()
        ),
        granularity: .daily,
        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 let cleaner = record.dataByGridCleanliness?.cleaner {
            print("更清洁时段: \(cleaner)")
        }
    }
}
如需查询运行时长数据而非能源数据,请使用
runtimeInsights(forDeviceID:using:atVenue:)
。时间粒度选项:
.hourly
(每小时)、
.daily
(每日)、
.weekly
(每周)、
.monthly
(每月)、
.yearly
(每年)。完整的洞察示例请查看
references/energykit-patterns.md

Common Mistakes

常见错误

DON'T: Forget the EnergyKit entitlement

禁止:忘记配置EnergyKit权限

Without the entitlement, all EnergyKit calls fail silently or throw errors.
swift
// WRONG: No entitlement configured
let service = ElectricityGuidance.sharedService  // Will fail

// CORRECT: Add com.apple.developer.energykit to entitlements
// Then use the service
let service = ElectricityGuidance.sharedService
没有配置权限的话,所有EnergyKit调用会静默失败或抛出错误。
swift
// 错误:未配置权限
let service = ElectricityGuidance.sharedService  // 会失败

// 正确:将com.apple.developer.energykit添加到权限配置
// 再使用服务
let service = ElectricityGuidance.sharedService

DON'T: Ignore unsupported regions

禁止:忽略不支持的地区

EnergyKit is not available in all regions. Handle the
.unsupportedRegion
and
.guidanceUnavailable
errors.
swift
// WRONG: Assume guidance is always available
for try await guidance in service.guidance(using: query, at: venueID) {
    updateUI(guidance)
}

// CORRECT: Handle region-specific errors
do {
    for try await guidance in service.guidance(using: query, at: venueID) {
        updateUI(guidance)
    }
} catch let error as EnergyKitError {
    switch error {
    case .unsupportedRegion:
        showUnsupportedRegionMessage()
    case .guidanceUnavailable:
        showGuidanceUnavailableMessage()
    case .venueUnavailable:
        showNoVenueMessage()
    case .permissionDenied:
        showPermissionDeniedMessage()
    case .serviceUnavailable:
        retryLater()
    case .rateLimitExceeded:
        backOff()
    default:
        break
    }
}
EnergyKit并非在所有地区都可用,请处理
.unsupportedRegion
.guidanceUnavailable
错误。
swift
// 错误:默认指引始终可用
for try await guidance in service.guidance(using: query, at: venueID) {
    updateUI(guidance)
}

// 正确:处理地区相关错误
do {
    for try await guidance in service.guidance(using: query, at: venueID) {
        updateUI(guidance)
    }
} catch let error as EnergyKitError {
    switch error {
    case .unsupportedRegion:
        showUnsupportedRegionMessage()
    case .guidanceUnavailable:
        showGuidanceUnavailableMessage()
    case .venueUnavailable:
        showNoVenueMessage()
    case .permissionDenied:
        showPermissionDeniedMessage()
    case .serviceUnavailable:
        retryLater()
    case .rateLimitExceeded:
        backOff()
    default:
        break
    }
}

DON'T: Discard the guidance token

禁止:丢弃guidance token

The
guidanceToken
links load events to the guidance that influenced them. Always store and pass it through to load event submissions.
swift
// WRONG: Ignore the guidance token
for try await guidance in guidanceStream {
    startCharging()
}

// CORRECT: Store the token for load events
for try await guidance in guidanceStream {
    let token = guidance.guidanceToken
    startCharging(followingGuidanceToken: token)
}
guidanceToken
用于将负载事件和影响用电决策的指引关联起来,请始终存储该token并在提交负载事件时传入。
swift
// 错误:忽略guidance token
for try await guidance in guidanceStream {
    startCharging()
}

// 正确:存储token用于后续负载事件提交
for try await guidance in guidanceStream {
    let token = guidance.guidanceToken
    startCharging(followingGuidanceToken: token)
}

DON'T: Submit load events without a session lifecycle

禁止:提交负载事件时不遵循会话生命周期

Always submit
.begin
, then
.active
updates, then
.end
events.
swift
// WRONG: Only submit one event
let event = ElectricVehicleLoadEvent(/* state: .active */)
try await venue.submitEvents([event])

// CORRECT: Full session lifecycle
try await venue.submitEvents([beginEvent])
// ... periodic active events ...
try await venue.submitEvents([activeEvent])
// ... when done ...
try await venue.submitEvents([endEvent])
请始终按顺序提交
.begin
.active
更新、
.end
事件。
swift
// 错误:仅提交一个事件
let event = ElectricVehicleLoadEvent(/* state: .active */)
try await venue.submitEvents([event])

// 正确:完整的会话生命周期
try await venue.submitEvents([beginEvent])
// ... 周期性提交active事件 ...
try await venue.submitEvents([activeEvent])
// ... 用电结束时 ...
try await venue.submitEvents([endEvent])

DON'T: Query guidance without a venue

禁止:没有场所ID就查询指引

EnergyKit requires a venue ID. List venues first and select the appropriate one.
swift
// WRONG: Use a hardcoded UUID
let fakeID = UUID()
service.guidance(using: query, at: fakeID)  // Will fail

// CORRECT: Discover venues first
let venues = try await EnergyVenue.venues()
guard let venue = venues.first else {
    showNoVenueSetup()
    return
}
let guidanceStream = service.guidance(using: query, at: venue.id)
EnergyKit需要场所ID,请先列出所有场所并选择合适的ID。
swift
// 错误:使用硬编码的UUID
let fakeID = UUID()
service.guidance(using: query, at: fakeID)  // 会失败

// 正确:先发现场所
let venues = try await EnergyVenue.venues()
guard let venue = venues.first else {
    showNoVenueSetup()
    return
}
let guidanceStream = service.guidance(using: query, at: venue.id)

Review Checklist

审核检查清单

  • com.apple.developer.energykit
    entitlement added to the project
  • EnergyKitError.unsupportedRegion
    handled with user-facing message
  • EnergyKitError.permissionDenied
    handled gracefully
  • Guidance token stored and passed to load event submissions
  • Venues discovered via
    EnergyVenue.venues()
    before querying guidance
  • Load event sessions follow
    .begin
    ->
    .active
    ->
    .end
    lifecycle
  • ElectricityGuidance.Value.rating
    interpreted correctly (lower is better)
  • SuggestedAction
    matches the device type (
    .shift
    for EV,
    .reduce
    for HVAC)
  • Insight queries use appropriate granularity for the time range
  • Rate limiting handled via
    EnergyKitError.rateLimitExceeded
  • Service unavailability handled with retry logic
  • 已将
    com.apple.developer.energykit
    权限添加到项目
  • 已为
    EnergyKitError.unsupportedRegion
    配置用户提示
  • 已优雅处理
    EnergyKitError.permissionDenied
    错误
  • 已存储guidance token并在提交负载事件时传入
  • 查询指引前已通过
    EnergyVenue.venues()
    发现可用场所
  • 负载事件会话遵循
    .begin
    ->
    .active
    ->
    .end
    生命周期
  • 正确解读
    ElectricityGuidance.Value.rating
    (数值越低越好)
  • SuggestedAction
    与设备类型匹配(EV用
    .shift
    ,HVAC用
    .reduce
  • 洞察查询使用了与时间范围匹配的粒度
  • 已通过
    EnergyKitError.rateLimitExceeded
    处理限流逻辑
  • 已通过重试逻辑处理服务不可用的情况

References

参考文档