energykit

energykit

热门

使用 EnergyKit 查询电网电力预测并提交负载事件,帮助用户优化家庭用电。适用于构建智能家居应用、电动汽车充电器控制、暖通空调调度或能源管理仪表板,引导用户在电网更清洁或更便宜的时间段用电。

940Star
47Fork
更新于 2026/7/15
SKILL.md
只读
名称
energykit
描述

使用 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 提供两个主要功能:

  1. 电力指导 —— 时间加权预测,告知应用何时电力更清洁,以及当费率数据可用时,何时更便宜
  2. 负载事件 —— 来自受控设备(电动汽车充电器、暖通空调)的遥测数据,由请求指导的同一设备/应用提交,以便 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+ 的 ElectricalLoadDevicedevice:;仅在 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 负载事件或洞察

参考