Files
iOS/Sources/Shared/API/WatchHelpers.swift
Bruno Pantaleão Gonçalves 9c9e544e62 Add Apple Watch mTLS compatibility and settings screen (#4753)
<!-- Thank you for submitting a Pull Request and helping to improve Home
Assistant. Please complete the following sections to help the processing
and review of your changes. Please do not delete anything from this
template. -->

## Summary
<!-- Provide a brief summary of the changes you have made and most
importantly what they aim to achieve -->
This PR bumps HAKit to 0.4.15 which allows mTLS on Apple Watch.
It also includes connectivity changes + a settings screen to visualize
connectivity information and define where to execute scripts and scenes
from.

## Screenshots
<!-- If this is a user-facing change not in the frontend, please include
screenshots in light and dark mode. -->
<img width="742" height="864" alt="CleanShot 2026-06-16 at 16 39 30@2x"
src="https://github.com/user-attachments/assets/3b9ec512-76f4-4a2f-a677-710c7f37bef4"
/>


## Link to pull request in Documentation repository
<!-- Pull requests that add, change or remove functionality must have a
corresponding pull request in the Companion App Documentation repository
(https://github.com/home-assistant/companion.home-assistant). Please add
the number of this pull request after the "#" -->
Documentation: home-assistant/companion.home-assistant#

## Any other notes
<!-- If there is any other information of note, like if this Pull
Request is part of a bigger change, please include it here. -->

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-16 17:57:46 +02:00

112 lines
4.0 KiB
Swift

import Communicator
import Foundation
import ObjectMapper
import PromiseKit
import RealmSwift
#if os(watchOS)
import ClockKit
import WatchKit
#endif
public enum WatchContext: String, CaseIterable {
case servers
case complications
case ssid = "SSID"
case activeFamilies
case watchModel
case watchVersion
case watchBattery
case watchBatteryState
}
public extension HomeAssistantAPI {
// Be mindful of 262.1kb maximum size for context - https://stackoverflow.com/a/35076706/486182
private static var watchContext: Content {
var content: Content = Communicator.shared.mostRecentlyReceievedContext.content
#if os(iOS)
// Servers are delivered on demand via the `serversConfigSync` interactive message (see
// WatchCommunicatorService), mirroring how the watch configuration is fetched not here.
content[WatchContext.complications.rawValue] = Array(Current.realm().objects(WatchComplication.self)).toJSON()
#if targetEnvironment(simulator)
content[WatchContext.ssid.rawValue] = "SimulatorWiFi"
#else
content[WatchContext.ssid.rawValue] = Current.connectivity.currentWiFiSSID()
#endif
#elseif os(watchOS)
let activeFamilies: [String]? = CLKComplicationServer.sharedInstance().activeComplications?.compactMap {
ComplicationGroupMember(family: $0.family).rawValue
}
content[WatchContext.activeFamilies.rawValue] = activeFamilies
content[WatchContext.watchModel.rawValue] = Current.device.systemModel()
content[WatchContext.watchVersion.rawValue] = Current.device.systemVersion()
let currentWatchInterfaceDevice = WKInterfaceDevice.current()
currentWatchInterfaceDevice.isBatteryMonitoringEnabled = true
content[WatchContext.watchBattery.rawValue] = currentWatchInterfaceDevice.batteryLevel
content[WatchContext.watchBatteryState.rawValue] = currentWatchInterfaceDevice.batteryState.rawValue
#endif
return content
}
static func SyncWatchContext() -> NSError? {
#if os(iOS)
guard case .paired(.installed) = Communicator.shared.currentWatchState else {
Current.Log.warning("Tried to sync HAAPI config to watch but watch not paired or app not installed")
return nil
}
#endif
let context = Context(content: HomeAssistantAPI.watchContext)
do {
try Communicator.shared.sync(context)
Current.Log.info("updated context")
} catch let error as NSError {
Current.Log.error("Updating the context failed: \(error)")
return error
}
return nil
}
func updateComplications(passively: Bool) -> Promise<Void> {
#if os(iOS)
guard case .paired = Communicator.shared.currentWatchState else {
Current.Log.verbose("skipping complication updates; no paired watch")
return .value(())
}
#endif
let complications = Set(
Current.realm().objects(WatchComplication.self)
.filter("serverIdentifier = %@", server.identifier.rawValue)
)
guard let request = WebhookResponseUpdateComplications.request(for: complications) else {
Current.Log.verbose("no complications need templates rendered")
#if os(iOS)
// in case the user deleted the last complication, sync that fact up to the watch
_ = HomeAssistantAPI.SyncWatchContext()
#else
// in case the user updated just the complication's metadata, force a refresh
WebhookResponseUpdateComplications.updateComplications()
#endif
return .value(())
}
if passively {
return Current.webhooks.sendPassive(identifier: .updateComplications, server: server, request: request)
} else {
return Current.webhooks.send(identifier: .updateComplications, server: server, request: request)
}
}
}