Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
90
90%
Does it follow best practices?
Impact
—
Average score across 248 eval scenarios
Advisory
Suggest reviewing before use
Provide grid electricity forecasts to help users choose when to use electricity. EnergyKit identifies times when grid electricity is relatively cleaner and, when cost information is available, less expensive. Apps use that guidance to shift or reduce managed device load. Targets Swift 6.3 / iOS 26+.
Beta-sensitive. EnergyKit is new in iOS 26 and may change before GM. Re-check current Apple documentation before relying on specific API details.
EnergyKit requires the com.apple.developer.energykit entitlement. Enable the
EnergyKit capability in Xcode so the entitlement is added to the app target.
Treat this as a top-level setup prerequisite before writing guidance queries,
venue lookup, load-event submission, or insight code. Missing permission can
surface as EnergyKitError.permissionDenied.
import EnergyKitPlatform availability: Core EnergyKit APIs are iOS 26.0+ and iPadOS 26.0+. Some insight breakdown APIs, including grid cleanliness categories, are iOS 26.1+ / iPadOS 26.1+ and need availability guards.
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 | Load event for EV charger telemetry |
ElectricHVACLoadEvent | Load event for HVAC system telemetry |
ElectricityInsightService | Service for querying energy/runtime insights |
ElectricityInsightRecord | Historical energy or runtime data, optionally broken down by tariff or 26.1+ grid cleanliness |
ElectricityInsightQuery | Query for historical insight data |
| 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
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)
.padding(.vertical)
.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 generate electricity insights. The same EnergyKit-capable device/app that requested electricity guidance must submit the corresponding load events, using the guidance token returned by EnergyKit. Do not invent a token.
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 |
For EV charging, record begin/end, one steady sample about every 15 minutes,
and extra samples for user actions, pauses, new guidance, or rapid power
changes. For HVAC, submit separate events when equipment starts, when heating
or cooling stage changes (heat stage 1 -> 2, heat -> cool, cool -> idle), and
when equipment stops. Batch events when practical for performance. Insights
are only available for submitted events, and load events for an EnergyVenue
are visible to people who share the associated Home in the Home app.
Query historical energy and runtime data for devices using
ElectricityInsightService. An empty ElectricityInsightQuery.Options option
set returns totals only; it does not populate cleanliness or tariff breakdowns.
Request .cleanliness and/or .tariff only when the UI needs those breakdowns.
Do not substitute MetricKit app power metrics for EnergyKit insights; EnergyKit
insights depend on EnergyKit load events submitted for the managed device.
Choose insight granularity from the requested range. For a seven-day view,
query .hourly; use .daily only when the query covers at least a calendar
month.
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: \(total)") }
if #available(iOS 26.1, iPadOS 26.1, *),
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. Choose a range that matches Apple's minimum aggregation windows:
hourly for at least a calendar week, daily for at least a calendar month,
weekly for at least six months, and monthly or yearly for at least a calendar
year. See references/energykit-patterns.md for full insight examples.
Without the entitlement, EnergyKit APIs can fail with permission errors such as
EnergyKitError.permissionDenied. Treat the EnergyKit capability as setup, not
as an implementation detail to discover after writing queries.
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 was in effect. Store
the token returned from EnergyKit on the device that fetched it and pass that
real token to load event submissions.
// WRONG: Ignore the guidance token
for try await guidance in guidanceStream {
startCharging(followingGuidanceToken: UUID()) // fabricated token
}
// 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 (.shift for EV, .reduce for HVAC)EnergyKitError.rateLimitExceeded.tessl-plugin
skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
references
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references