2025 System Services
WWDC25 · 15 min · System Services
Optimize home electricity usage with EnergyKit
Learn how to support EnergyKit in your app so people can optimize electricity usage at home. This can help people run appliances or charge EVs during times when electricity is cleaner and cheaper. Get details about onboarding, generating a charging schedule, and providing energy usage insights back to people through electricity usage feedback.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 11 snippets
Retrive an EnergyVenue
// Retrieve an EnergyVenue
import EnergyKit
import Foundation
final class EnergyVenueManager {
let venue: EnergyVenue
init?(venueID: UUID) async {
guard let energyVenue = await EnergyVenue.venue(for: venueID) else {
return nil
}
venue = energyVenue
}
} Fetch Electricity Guidance at a selected EnergyVenue
// Fetch ElectricityGuidance
import EnergyKit
import Foundation
final class EnergyVenueManager {
// The current active guidance.
var guidance: ElectricityGuidance?
fileprivate func streamGuidance(
venueID: UUID,
update: (_ guidance: ElectricityGuidance) -> Void
) async throws {
let query = ElectricityGuidance.Query(suggestedAction: .shift)
for try await currentGuidance in ElectricityGuidance.sharedService.guidance(
using: query,
at: venueID
) {
update(currentGuidance)
break
}
}
} Start monitoring Electricity Guidance
// Fetch ElectricityGuidance
import EnergyKit
import Foundation
final class EnergyVenueManager {
// The task used to stream guidance.
private var streamGuidanceTask: Task<(), Error>?
///Start streaming guidance and store the value in the observed property 'guidance'.
func startGuidanceMonitoring() {
streamGuidanceTask?.cancel()
streamGuidanceTask = Task.detached { [weak self] in
if let venueID = self?.venue.id {
try? await self?.streamGuidance(venueID: venueID) { guidance in
self?.guidance = guidance
if Task.isCancelled {
return
}
}
}
}
}
} Update charging measurements
// Update charging measurements
import EnergyKit
// A controller that handles an electric vehicle
class ElectricVehicleController {
fileprivate func chargingMeasurement() -> ElectricVehicleLoadEvent.ElectricalMeasurement {
let stateOfCharge = Int(configuration.state.stateOfCharge.rounded(.down))
let power = Measurement<UnitPower>(
value: configuration.properties.chargingPower * 1000000,
unit: .milliwatts
)
let energy = Measurement<UnitEnergy>(
value: configuration.state.cummulativeEnergy * 1000000,
unit: .EnergyKit.milliwattHours
)
return ElectricVehicleLoadEvent.ElectricalMeasurement(
stateOfCharge: stateOfCharge,
direction: .imported,
power: power,
energy: energy
)
}
} Start a session
// Start a session
import EnergyKit
// A controller that handles an electric vehicle
class ElectricVehicleController {
// The session
var session: ElectricVehicleLoadEvent.Session?
// The current guidance stored at the EV
var currentGuidance: ElectricityGuidance
// Whether the EV is following guidance
var isFollowingGuidance: Bool = true
fileprivate func beginSession() {
session = ElectricVehicleLoadEvent.Session(
id: UUID(),
state: .begin,
guidanceState: .init(
wasFollowingGuidance: isFollowingGuidance,
guidanceToken: currentGuidance.guidanceToken
)
)
}
} Update a session
// Update a session
import EnergyKit
// A controller that handles an electric vehicle
class ElectricVehicleController {
fileprivate func updateSession() {
if let session {
self.session = ElectricVehicleLoadEvent.Session(
id: session.id,
state: .active,
guidanceState: .init(
wasFollowingGuidance:
isFollowingGuidance,
guidanceToken:
currentGuidance.guidanceToken
)
)
}
}
} End a session
// End a session
import EnergyKit
// A controller that handles an electric vehicle.
class ElectricVehicleController {
fileprivate func endSession() {
if let session {
self.session = ElectricVehicleLoadEvent.Session(
id: session.id,
state: .end,
guidanceState: .init(
wasFollowingGuidance:
isFollowingGuidance,
guidanceToken:
currentGuidance.guidanceToken
)
)
}
}
} Create a load event
// Create a ElectricVehicleLoadEvent
class ElectricVehicleController {
fileprivate func createLoadEvent(
sessionState: ElectricVehicleLoadEvent.Session.State
) {
switch sessionState {
case .begin:
beginSession()
case .active:
updateSession()
case .end:
endSession()
@unknown default:
fatalError()
}
if let session {
let event = ElectricVehicleLoadEvent(
timestamp: configuration.state.timestamp,
measurement: chargingMeasurement(),
session: session,
deviceID: configuration.properties.vehicleID
)
events.append(event)
}
}
} Submit events
// Submit events
import EnergyKit
// A controller that handles an electric vehicle
class ElectricVehicleController {
// EnergyVenue
// The venue at which the EV uses energy
var currentVenue: EnergyVenue
// Electric EV Events
// The list of generated EV load events
var events = [ElectricVehicleLoadEvent]()
func submitEvents() async throws {
try await currentVenue.submitEvents(events)
}
} Create an insight query
// Create an insight query
import EnergyKit
final class EnergyVenueManager {
func createInsightsQuery(on date: Date) -> ElectricityInsightQuery {
return ElectricityInsightQuery(
options: .cleanliness.union(.tariff),
range: self.dayInterval(date: date),
granularity: .daily,
flowDirection: .imported
)
}
} Request insights
// Request an insights
import EnergyKit
final class EnergyVenueManager {
func generateInsights(for vehicleIdentifier: String, on date: Date) async throws ->
ElectricityInsightRecord<Measurement<UnitEnergy>>? {
let query = createInsightsQuery(on: date)
return try await ElectricityInsightService.shared.energyInsights(
forDeviceID: vehicleIdentifier,
using: query,
atVenue: self.venue.id
).first { record in
return record.range.start == query.range.start
}
}
} Resources
Related sessions
-
25 min -
19 min