使用 EnergyKit 查询电网电力预测并提交负载事件,帮助用户优化家庭用电。适用于构建智能家居应用、电动汽车充电器控制、暖通空调调度或能源管理仪表板,引导用户在电网更清洁或更便宜的时间段用电。
EnergyKit
利用电网清洁度和成本指导来转移或减少受控设备的负载。
对于受控设备洞察,请及时提交设备的真实负载事件。
Beta 敏感。 核心 EnergyKit 在 iOS/iPadOS 26 中提供。iOS/iPadOS 27 中的
ElectricalLoadDevice和面向家庭的 LoadEvents 体验为 beta 版本;在依赖这些 API 之前,
请重新检查当前的 Apple 文档。
目录
设置
授权和版本区分
| 运行时 | 负载事件设备 API | 功能 |
|---|---|---|
| iOS/iPadOS 26.x | deviceID: 兼容性初始化器 |
EnergyKit |
| iOS/iPadOS 27+ beta | 使用 device: 初始化器的 ElectricalLoadDevice |
EnergyKit;添加 EnergyKit LoadEvents 以集成家庭应用 |
所有 EnergyKit 使用都需要 com.apple.developer.energykit;在应用目标上启用 EnergyKit 功能。在 iOS/iPadOS 27+ 上,仅当应用需要在家庭应用中显示设备名称、能源上下文、活动日志、历史图表或趋势通知时,才添加 EnergyKit LoadEvents 功能(com.apple.developer.energykit.loadevents-experience)。该家庭体验需要同时具备这两个功能。缺少权限可能会显示为 EnergyKitError.permissionDenied。
导入
import EnergyKit
平台可用性: 核心 EnergyKit API 适用于 iOS/iPadOS 26.0+。某些洞察细分 API(包括电网清洁度类别)适用于 26.1+,需要可用性保护。Apple 目前仅记录美国本土的电力指导;请处理 EnergyKitError.unsupportedRegion。
核心概念
EnergyKit 提供两个主要功能:
- 电力指导 —— 时间加权预测,告知应用何时电力更清洁,以及当费率数据可用时,何时更便宜
- 负载事件 —— 来自受控设备(电动汽车充电器、暖通空调)的遥测数据,由请求指导的同一设备/应用提交,以便 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.guidanceToken)")
print("时间间隔: \(guidance.interval)")
print("场所: \(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 的设备/应用必须提交相应的负载事件,并使用 EnergyKit 返回的指导令牌。不要自行创建令牌。
电动汽车充电器负载事件
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 功能时,才承诺家庭应用中的设备名称、能源上下文、活动日志、图表和趋势通知。
电力洞察
使用 ElectricityInsightService 查询设备的历史能源和运行时数据。空的 ElectricityInsightQuery.Options 选项集仅返回总计;不会填充清洁度或费率细分。仅当 UI 需要这些细分时才请求 .cleanliness 和/或 .tariff。不要用 MetricKit 应用功耗指标替代 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 目前仅记录美国本土的指导;处理不支持的区域和不可用的场所/指导状态。 |
| 伪造或丢弃指导令牌 | 在请求设备上持久保存真实令牌,并将其与负载事件一起提交。 |
| 发送孤立或延迟的负载样本 | 保持 .begin → .active → .end 顺序,及时提交,并保留事件直到提交成功。 |
使用 deviceID: 作为当前默认值 |
使用 iOS/iPadOS 27+ 的 ElectricalLoadDevice 和 device:;仅在 26.x 运行时分支中保留 deviceID:。 |
| 使用硬编码的场所 ID | 使用 EnergyVenue.venues() 发现场所并选择目标场所。 |
审查清单
- [ ] 基础 EnergyKit 功能已存在;iOS/iPadOS 27+ 家庭集成也具备 EnergyKit LoadEvents
- [ ] 已处理区域、权限、场所发现、不可用指导和服务错误
- [ ] 真实指导令牌保留在请求设备/应用及其提交的负载事件中
- [ ] iOS/iPadOS 27+ 使用
ElectricalLoadDevice/device:;deviceID:仅限于 26.x 兼容性 - [ ]
.begin → .active → .end事件遵循设备节奏,及时提交,在失败时保留,并使用有限退避重试速率限制 - [ ] 评分/操作解释正确;洞察选项、可用性、粒度和最小范围与 UI 匹配
- [ ] 未用 MetricKit 遥测替代 EnergyKit 负载事件或洞察
参考
- 应用架构、EV/HVAC 会话节奏、仪表板展示、洞察、错误和场所发现的扩展工作流:
references/energykit-patterns.md - EnergyKit 框架
- ElectricityGuidance
- ElectricityGuidance.Service
- ElectricityGuidance.Query
- ElectricityGuidance.Value
- EnergyVenue
- ElectricalLoadDevice
- ElectricVehicleLoadEvent
- ElectricHVACLoadEvent
- ElectricityInsightService
- ElectricityInsightRecord
- ElectricityInsightQuery
- EnergyKitError
- EnergyKit 授权
- EnergyKit LoadEvents 授权
- 为电动汽车提供充电历史
- 优化家庭用电






