mirror of
https://github.com/home-assistant/iOS.git
synced 2026-06-25 07:32:12 -05:00
138 lines
4.7 KiB
Swift
138 lines
4.7 KiB
Swift
import CoreMotion
|
|
import Foundation
|
|
import PromiseKit
|
|
|
|
final class BarometerSensorUpdateSignaler: BaseSensorUpdateSignaler, SensorProviderUpdateSignaler {
|
|
private let signal: () -> Void
|
|
private var lastSignaledPressureKpa: Double?
|
|
private var observationQueue: OperationQueue?
|
|
|
|
/// The most recent pressure in kilopascals from CMAltimeter, used by BarometerSensor
|
|
/// to avoid starting a separate one-shot read that would conflict with the signaler's stream.
|
|
private(set) var latestPressureKpa: Double?
|
|
|
|
required init(signal: @escaping () -> Void) {
|
|
self.signal = signal
|
|
super.init(relatedSensorsIds: [.pressure])
|
|
}
|
|
|
|
override func observe() {
|
|
super.observe()
|
|
guard !isObserving else { return }
|
|
guard Current.barometer.isAvailable(), Current.barometer.isAuthorized() else { return }
|
|
|
|
let queue = OperationQueue()
|
|
queue.name = "barometer-signaler"
|
|
queue.maxConcurrentOperationCount = 1
|
|
observationQueue = queue
|
|
|
|
Current.barometer.startUpdatesOnQueueHandler(queue) { [weak self] data, _ in
|
|
guard let self, let data else { return }
|
|
let newPressure = data.pressure.doubleValue
|
|
latestPressureKpa = newPressure
|
|
if let last = lastSignaledPressureKpa, abs(newPressure - last) < 0.01 {
|
|
// Less than 0.1 hPa change, skip update
|
|
return
|
|
}
|
|
lastSignaledPressureKpa = newPressure
|
|
signal()
|
|
}
|
|
isObserving = true
|
|
|
|
#if DEBUG
|
|
notifyObservation?()
|
|
#endif
|
|
}
|
|
|
|
override func stopObserving() {
|
|
super.stopObserving()
|
|
guard isObserving else { return }
|
|
Current.barometer.stopUpdates()
|
|
observationQueue = nil
|
|
lastSignaledPressureKpa = nil
|
|
latestPressureKpa = nil
|
|
isObserving = false
|
|
}
|
|
}
|
|
|
|
public class BarometerSensor: SensorProvider {
|
|
public enum BarometerError: Error, Equatable {
|
|
case unauthorized
|
|
case unavailable
|
|
case noData
|
|
}
|
|
|
|
public let request: SensorProviderRequest
|
|
public required init(request: SensorProviderRequest) {
|
|
self.request = request
|
|
}
|
|
|
|
public func sensors() -> Promise<[WebhookSensor]> {
|
|
let signaler: BarometerSensorUpdateSignaler = request.dependencies.updateSignaler(for: self)
|
|
|
|
// If the signaler is actively observing, use its cached pressure to avoid
|
|
// starting a separate one-shot read that would stop the signaler's stream.
|
|
if let cachedKpa = signaler.latestPressureKpa {
|
|
return .value([Self.pressureSensor(fromKpa: cachedKpa)])
|
|
} else if signaler.isObserving {
|
|
// Signaler started but no data yet — skip rather than racing with a one-shot
|
|
return .init(error: BarometerError.noData)
|
|
}
|
|
|
|
return firstly {
|
|
latestBarometerData()
|
|
}.map { data in
|
|
[Self.pressureSensor(fromKpa: data.pressure.doubleValue)]
|
|
}
|
|
}
|
|
|
|
static func pressureSensor(fromKpa kpa: Double) -> WebhookSensor {
|
|
// CMAltitudeData.pressure is in kilopascals; HA pressure device class expects hPa (= mbar)
|
|
let pressureHpa = kpa * 10.0
|
|
return WebhookSensor(
|
|
name: "Pressure",
|
|
uniqueID: WebhookSensorId.pressure.rawValue,
|
|
icon: "mdi:gauge",
|
|
deviceClass: .pressure,
|
|
state: round(pressureHpa * 100) / 100,
|
|
unit: "hPa"
|
|
)
|
|
}
|
|
|
|
private func latestBarometerData() -> Promise<CMAltitudeData> {
|
|
guard Current.barometer.isAuthorized() else {
|
|
return .init(error: BarometerError.unauthorized)
|
|
}
|
|
|
|
guard Current.barometer.isAvailable() else {
|
|
Current.Log.warning("Barometer is not available")
|
|
return .init(error: BarometerError.unavailable)
|
|
}
|
|
|
|
let (promise, seal) = Promise<CMAltitudeData>.pending()
|
|
let queue = OperationQueue()
|
|
queue.name = "barometer-sensor"
|
|
queue.maxConcurrentOperationCount = 1
|
|
|
|
// startRelativeAltitudeUpdates is a streaming API, so an in-flight callback
|
|
// could arrive after stopUpdates(). Guard against double-resolving the promise,
|
|
// and ensure late callbacks become no-ops before stopping updates.
|
|
var resolved = false
|
|
Current.barometer.startUpdatesOnQueueHandler(queue) { data, error in
|
|
guard !resolved else { return }
|
|
resolved = true
|
|
Current.barometer.stopUpdates()
|
|
|
|
if let data {
|
|
seal.fulfill(data)
|
|
} else if let error {
|
|
seal.reject(error)
|
|
} else {
|
|
seal.reject(BarometerError.noData)
|
|
}
|
|
}
|
|
|
|
return promise
|
|
}
|
|
}
|