energykit by dpearson2699/swift-ios-skills
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill energykit提供电网电力预测,帮助用户选择用电时机。EnergyKit 识别电网中电力相对更清洁或更便宜的时段,使应用能够相应地转移或减少负载。目标平台:Swift 6.2 / iOS 26+。
EnergyKit 需要 com.apple.developer.energykit 权限。请将其添加到您应用的权限文件中。
import EnergyKit
平台可用性: iOS 26+, iPadOS 26+。
EnergyKit 提供两大核心功能:
| 类型 | 角色 |
|---|
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
ElectricityGuidance | 包含加权时间间隔的预测数据 |
ElectricityGuidance.Service | 用于获取指导数据的接口 |
ElectricityGuidance.Query | 指定转移或减少操作的查询 |
ElectricityGuidance.Value | 包含评分(0.0-1.0)的时间间隔 |
EnergyVenue | 为能源管理注册的物理位置(家庭) |
ElectricVehicleLoadEvent | 用于电动汽车充电桩遥测的负载事件 |
ElectricHVACLoadEvent | 用于暖通空调系统遥测的负载事件 |
ElectricityInsightService | 用于查询能源/运行时洞察的服务 |
ElectricityInsightRecord | 按清洁度/电价细分的历史能源数据 |
ElectricityInsightQuery | 用于历史洞察数据的查询 |
| 操作 | 使用场景 |
|---|---|
.shift | 可以将用电转移到不同时间的设备(电动汽车充电) |
.reduce | 可以降低用电量而无需完全停止的设备(暖通空调温度设定回调) |
使用 ElectricityGuidance.Service 获取某个场所的预测数据流。
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("Rate plan data incorporated")
}
if guidance.options.contains(.locationHasRatePlan) {
print("Location has a rate plan")
}
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("From \(interval.start) to \(interval.end): rating \(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 }
}
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())
}
}
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("Venue ID: \(venue.id)")
print("Venue name: \(venue.name)")
将设备用电数据报告给系统。这有助于系统提高未来指导的准确性。
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])
}
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])
}
| 状态 | 何时使用 |
|---|---|
.begin | 设备开始用电 |
.active | 设备正在积极用电(定期更新) |
.end | 设备停止用电 |
使用 ElectricityInsightService 查询设备的历史能源和运行时数据。
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)")
}
}
}
使用 runtimeInsights(forDeviceID:using:atVenue:) 来获取运行时数据而非能源数据。粒度选项:.hourly、.daily、.weekly、.monthly、.yearly。完整洞察示例请参阅 references/energykit-patterns.md。
没有该权限,所有 EnergyKit 调用都会静默失败或抛出错误。
// 错误:未配置权限
let service = ElectricityGuidance.sharedService // 将会失败
// 正确:将 com.apple.developer.energykit 添加到权限文件
// 然后使用服务
let service = ElectricityGuidance.sharedService
EnergyKit 并非在所有地区都可用。请处理 .unsupportedRegion 和 .guidanceUnavailable 错误。
// 错误:假设指导始终可用
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
}
}
guidanceToken 将负载事件与影响它们的指导关联起来。请始终存储并将其传递到负载事件提交中。
// 错误:忽略指导令牌
for try await guidance in guidanceStream {
startCharging()
}
// 正确:为负载事件存储令牌
for try await guidance in guidanceStream {
let token = guidance.guidanceToken
startCharging(followingGuidanceToken: token)
}
请始终先提交 .begin,然后是 .active 更新,最后是 .end 事件。
// 错误:只提交一个事件
let event = ElectricVehicleLoadEvent(/* state: .active */)
try await venue.submitEvents([event])
// 正确:完整的会话生命周期
try await venue.submitEvents([beginEvent])
// ... 定期活跃事件 ...
try await venue.submitEvents([activeEvent])
// ... 完成后 ...
try await venue.submitEvents([endEvent])
EnergyKit 需要一个场所 ID。请先列出场所并选择合适的一个。
// 错误:使用硬编码的 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)
com.apple.developer.energykit 权限添加到项目EnergyKitError.unsupportedRegion 并显示面向用户的消息EnergyKitError.permissionDeniedEnergyVenue.venues() 发现场所.begin -> .active -> .end 生命周期ElectricityGuidance.Value.rating 解释正确(越低越好)SuggestedAction 与设备类型匹配(电动汽车用 .shift,暖通空调用 .reduce)EnergyKitError.rateLimitExceeded 处理速率限制references/energykit-patterns.md每周安装量
322
代码仓库
GitHub 星标
269
首次出现
2026年3月8日
安全审计
已安装于
codex319
opencode316
github-copilot316
amp316
cline316
kimi-cli316
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 requires the com.apple.developer.energykit entitlement. Add it to your app's entitlements file.
import EnergyKit
Platform availability: iOS 26+, iPadOS 26+.
EnergyKit provides two main capabilities:
| Type | Role |
|---|---|
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 |
| Action | Use Case |
|---|---|
.shift | Devices that can move consumption to a different time (EV charging) |
.reduce | Devices that can lower consumption without stopping (HVAC setback) |
Use ElectricityGuidance.Service to get a forecast stream for a venue.
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)
}
}
Each ElectricityGuidance.Value contains a time interval and a rating from 0.0 to 1.0. Lower ratings indicate better times to use electricity.
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 }
}
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())
}
}
An EnergyVenue represents a physical location registered for energy management.
// 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)
}
let venue = try await EnergyVenue.venue(for: venueID)
print("Venue ID: \(venue.id)")
print("Venue name: \(venue.name)")
Report device consumption data back to the system. This helps the system improve future guidance accuracy.
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])
}
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])
}
| State | When to Use |
|---|---|
.begin | Device starts consuming electricity |
.active | Device is actively consuming (periodic updates) |
.end | Device stops consuming electricity |
Query historical energy and runtime data for devices using ElectricityInsightService.
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.
Without the entitlement, all EnergyKit calls fail silently or throw errors.
// 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 is not available in all regions. Handle the .unsupportedRegion and .guidanceUnavailable errors.
// 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
}
}
The guidanceToken links load events to the guidance that influenced them. Always store and pass it through to load event submissions.
// 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)
}
Always submit .begin, then .active updates, then .end events.
// 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])
EnergyKit requires a venue ID. List venues first and select the appropriate one.
// 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)
com.apple.developer.energykit entitlement added to the projectEnergyKitError.unsupportedRegion handled with user-facing messageEnergyKitError.permissionDenied handled gracefullyEnergyVenue.venues() before querying guidance.begin -> .active -> .end lifecycleElectricityGuidance.Value.rating interpreted correctly (lower is better)SuggestedAction matches the device type ( for EV, for HVAC)references/energykit-patterns.mdWeekly Installs
322
Repository
GitHub Stars
269
First Seen
Mar 8, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex319
opencode316
github-copilot316
amp316
cline316
kimi-cli316
| 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 |
.shift.reduceEnergyKitError.rateLimitExceeded