This sensor module collects motion data from AirPods Pro and AirPods Max via CMHeadphoneMotionManager (Core Motion). It captures attitude (Euler angles, quaternion, rotation matrix), rotation rate, gravity, user acceleration, and sensor location (left/right earbud).
The update rate is controlled by the AirPods firmware and cannot be configured programmatically. Data is delivered at the hardware's native rate (approximately 100 Hz).
Apple | CMHeadphoneMotionManager
Apple | CMDeviceMotion
- iOS 14 or later
- AirPods Pro or AirPods Max
NSMotionUsageDescriptionkey in Info.plist
-
Open Package Manager Windows
- Open
Xcode→ SelectMenu Bar→File→Add Package Dependencies...
- Open
-
Find the package using the manager
- Select
Search Package URLand typehttps://github.com/awareframework/com.awareframework.ios.sensor.airpods.git
- Select
-
Import the package into your target.
init(config: AirPodsMotionSensor.Config): Initializes the sensor with the given configuration.start(): Starts motion data collection.stop(): Stops motion data collection.sync(force: Bool): Syncs stored data to the configured host.set(label: String): Updates the data label at runtime.
Class to hold the configuration of the sensor.
sensorObserver: AirPodsMotionObserver?: Callback for live data updates. (default =nil)period: Double: Interval in minutes at which buffered data is saved to the database. (default =1)debug: Bool: Enable/disable logging to the Xcode console. (default =false)label: String: Customizable label attached to every data record. (default ="")deviceId: String: Device UUID associated with the data. (default = system UUID)dbType: Engine: Database engine to use. (default =Engine.DatabaseType.NONE)dbPath: String: Path of the database. (default ="aware_airpods")dbHost: String?: Host URL for database sync. (default =nil)
public protocol AirPodsMotionObserver {
func onDataChanged(data: AirPodsMotionData)
func onConnected()
func onDisconnected()
}AirPodsMotionSensor.ACTION_AWARE_AIRPODS_MOTION: Fired when motion data is saved to the database after the period ends.AirPodsMotionSensor.ACTION_AWARE_AIRPODS_MOTION_START: Fired when the sensor starts.AirPodsMotionSensor.ACTION_AWARE_AIRPODS_MOTION_STOP: Fired when the sensor stops.AirPodsMotionSensor.ACTION_AWARE_AIRPODS_MOTION_CONNECTED: Fired when AirPods connect.AirPodsMotionSensor.ACTION_AWARE_AIRPODS_MOTION_DISCONNECTED: Fired when AirPods disconnect.
AirPodsMotionSensor.ACTION_AWARE_AIRPODS_MOTION_START: Start the sensor.AirPodsMotionSensor.ACTION_AWARE_AIRPODS_MOTION_STOP: Stop the sensor.AirPodsMotionSensor.ACTION_AWARE_AIRPODS_MOTION_SYNC: Trigger a database sync.AirPodsMotionSensor.ACTION_AWARE_AIRPODS_MOTION_SET_LABEL: Set the data label. Provide the value inAirPodsMotionSensor.EXTRA_LABEL.
Contains one sample of AirPods motion data. Table name: airpods_motion.
| Field | Type | Description |
|---|---|---|
| roll | Double | Roll angle in radians |
| pitch | Double | Pitch angle in radians |
| yaw | Double | Yaw angle in radians |
| Field | Type | Description |
|---|---|---|
| quaternionX | Double | Quaternion x component |
| quaternionY | Double | Quaternion y component |
| quaternionZ | Double | Quaternion z component |
| quaternionW | Double | Quaternion w component |
| Field | Type | Description |
|---|---|---|
| rotationMatrixM11 | Double | Matrix element (1,1) |
| rotationMatrixM12 | Double | Matrix element (1,2) |
| rotationMatrixM13 | Double | Matrix element (1,3) |
| rotationMatrixM21 | Double | Matrix element (2,1) |
| rotationMatrixM22 | Double | Matrix element (2,2) |
| rotationMatrixM23 | Double | Matrix element (2,3) |
| rotationMatrixM31 | Double | Matrix element (3,1) |
| rotationMatrixM32 | Double | Matrix element (3,2) |
| rotationMatrixM33 | Double | Matrix element (3,3) |
| Field | Type | Description |
|---|---|---|
| rotationX | Double | Rotation rate around x-axis (rad/s) |
| rotationY | Double | Rotation rate around y-axis (rad/s) |
| rotationZ | Double | Rotation rate around z-axis (rad/s) |
| Field | Type | Description |
|---|---|---|
| gravityX | Double | Gravity vector x component (G) |
| gravityY | Double | Gravity vector y component (G) |
| gravityZ | Double | Gravity vector z component (G) |
| Field | Type | Description |
|---|---|---|
| userAccX | Double | User acceleration x component, gravity removed (G) |
| userAccY | Double | User acceleration y component, gravity removed (G) |
| userAccZ | Double | User acceleration z component, gravity removed (G) |
| Field | Type | Description |
|---|---|---|
| headphonePlacement | Int | Source earbud: 0=default, 1=left, 2=right (CMSensorLocation.rawValue) |
| label | String | Customizable label. Useful for data calibration or traceability |
| deviceId | String | AWARE device UUID |
| timestamp | Int64 | Unix time in milliseconds since 1970 |
| timezone | Int | Raw timezone offset of the device |
| os | String | Operating system of the device ("iOS") |
| jsonVersion | Int | JSON schema version |
headphonePlacementis derived fromCMDeviceMotion.sensorLocation.rawValue(iOS 14+):
.default→0,.headphoneLeft→1,.headphoneRight→2.
import com_awareframework_ios_sensor_airpods
let sensor = AirPodsMotionSensor(AirPodsMotionSensor.Config().apply { config in
config.debug = true
config.period = 1.0
config.sensorObserver = Observer()
})
sensor.start()class Observer: AirPodsMotionObserver {
func onDataChanged(data: AirPodsMotionData) {
print("placement=\(data.headphonePlacement) roll=\(data.roll) pitch=\(data.pitch) yaw=\(data.yaw)")
}
func onConnected() {
print("AirPods connected")
}
func onDisconnected() {
print("AirPods disconnected")
}
}Yuuki Nishiyama (The University of Tokyo), nishiyama@csis.u-tokyo.ac.jp
Copyright (c) 2026 AWARE Mobile Context Instrumentation Middleware/Framework (http://www.awareframework.com)
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.