Add barometer as an entity that iPhone/some iPads expose#4491
Add barometer as an entity that iPhone/some iPads expose#4491bgoncal merged 12 commits intohome-assistant:mainfrom
Conversation
Uses CMAltimeter to read barometric pressure from the iPhone's built-in barometer chip and exposes it as a Home Assistant pressure sensor entity. The sensor converts from CMAltimeter's kilopascals to hPa for HA's pressure device class. Reuses the existing Motion & Fitness permission. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests cover authorization, availability, and error handling paths, verifies kPa-to-hPa conversion and rounding, and confirms that CMAltimeter updates are stopped after a single reading. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds support for barometric pressure sensing to the Home Assistant iOS app by introducing a new BarometerSensor that leverages the device's built-in barometer via CoreMotion's CMAltimeter API. The sensor converts pressure readings from kilopascals to hectopascals (hPa) and reports them through the existing webhook sensor infrastructure.
Changes:
- Introduces a new
BarometerSensorclass that provides pressure readings from the device's barometer - Adds a
Barometerenvironment wrapper in AppEnvironment that abstracts the CoreMotion CMAltimeter API - Extends
WebhookSensorIdenum with a newpressurecase - Includes comprehensive test coverage for authorization, availability, data validation, and pressure conversion logic
- Updates Xcode project configuration to include the new sensor files in appropriate build phases
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| Sources/Shared/API/Webhook/Sensors/BarometerSensor.swift | New sensor implementation that retrieves pressure data and converts it from kPa to hPa with 2 decimal place rounding |
| Sources/Shared/Environment/Environment.swift | Added Barometer struct wrapping CMAltimeter and registered BarometerSensor in the sensor container |
| Sources/Shared/API/Webhook/WebhookSensorId.swift | Added new pressure case to the sensor ID enum |
| Tests/Shared/Sensors/BarometerSensor.test.swift | Comprehensive test suite covering error conditions, data conversion, and proper cleanup |
| Tests/App/Webhook/WebhookSensorIdTests.swift | Updated test case count to 22 and added pressure sensor ID verification |
| HomeAssistant.xcodeproj/project.pbxproj | Added file references and build phase entries for both implementation and test files |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4491 +/- ##
=======================================
Coverage ? 43.46%
=======================================
Files ? 275
Lines ? 17882
Branches ? 0
=======================================
Hits ? 7772
Misses ? 10110
Partials ? 0 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
How's the permission request flow? I see it has "unauthorized" as one of the possible state |
I took the same approach as PedometerSensor and ActivitySensor, which are also bundled into the Motion & Fitness permission but otherwise are silently skipped in the list of available sensors. I figured this was enough of a niche thing that it shouldn't be something that pops up and requests the user grant permission how we do for Bluetooth, Notifications, or Camera (etc). |
Instead of only reading pressure during periodic sensor update cycles, keep CMAltimeter running and signal for updates when pressure changes by >= 0.1 hPa. Uses BaseSensorUpdateSignaler to automatically stop observation when the sensor is disabled. The signaler caches the latest CMAltitudeData so that sensors() uses the cached reading when the signaler is active, avoiding a one-shot read that would conflict with the signaler's continuous stream. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
startRelativeAltitudeUpdates is a streaming API, so an in-flight callback could arrive after stopUpdates(). Add a resolved flag to ensure only the first callback resolves the promise. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move resolved guard before stopUpdates() so late in-flight callbacks become no-ops and cannot stop a subsequently started signaler stream - Add NSLock to protect latestData against data races between the altimeter callback queue and the thread reading from sensors() - Make both OperationQueues serial to eliminate concurrent callback concerns - Replace try? hang() with XCTAssertNoThrow in test to surface unexpected failures Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BaseSensorUpdateSignaler.init registers as a SensorContainer observer, which can trigger observe() before the test's explicit call. This caused the handler capture variable to be overwritten non-deterministically. Fix: collect all registered handlers in an array and use the most recent one, making the test resilient to observer-driven re-registration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BaseSensorUpdateSignaler.init registers as a SensorContainer observer. If a previous test left lastUpdate populated on Current.sensors, the observer fires immediately during init, calling observe() and racing with the test's explicit signaler.observe() call. Fix: reset Current.sensors to a fresh SensorContainer in setUp() to ensure clean test isolation. Also collect registered handlers in an array to handle potential re-registration from the observer path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
I did what I should have done before and replicated the CI/CD environment locally to see what was really going on vs. guessing. Tracked it down, this should now be good to go (at least, the tests should pass cleanly). |
|
@teancom can you check the tests? |
The setUp was assigning a fresh empty SensorContainer to Current.sensors to avoid cross-test lastUpdate interference, but never put the original back. That leaked out to sibling test classes: by the time ConnectivitySensorTests/DeviceSensorTests/FocusSensorTests.testSignaler ran, Current.sensors had no providers registered, so Current.sensors.sensors(reason: .registration, ...) produced an empty sensor list, the signalers' didUpdate found no related sensors, observe() was never called, and their "Observation needs to start" expectations timed out. Save the original container in setUp and restore it in tearDown so the reset is scoped to this class only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Man, it was surprisingly difficult to figure out why everything passed locally and failed in CI. I'm hoping this is the final-for-real-fix-v1-(final) commit. 😅 |
Summary
I was reading this article https://www.howtogeek.com/your-phone-has-a-powerful-weather-instrument-hidden-inside/ - and it seemed neat! And adding support was actually pretty simple. Mostly just the one file plus tests and a little scattering of changes to the Xcode project file.
Screenshots
Link to pull request in Documentation repository
Documentation: home-assistant/companion.home-assistant#1309
Any other notes
Nothing bigger, just thought it would be neat.