I am wokring on an iOS app that triggers the internal vibration motor of the iPhone and while the phone vibrates, it collects accelerometer and gyroscope readings. I've visualized the data after collection and I could see the vibrations clearly on the gyroscope, but I cannot see anything on the accelerometer. I was wondering if someone here has done something like this before on iPhones and was able to see the vibrations on the accelerometer.
I will share the code that I used for both accelerometer and gyroscope data collection below.
Firstly, this class is responsible for the collection process:
class SensorCollector: ObservableObject {
private let motionManager = CMMotionManager()
private let sensorQueue = OperationQueue()
var accBuffer: [VibrateView.SensorReading] = []
var gyrBuffer: [VibrateView.SensorReading] = []
init() {
sensorQueue.name = "SensorCollectionQueue"
sensorQueue.qualityOfService = .userInitiated
}
func startSensors(startTime: TimeInterval) {
// 1. Clear buffers
accBuffer.removeAll(keepingCapacity: true)
gyrBuffer.removeAll(keepingCapacity: true)
// 2. Set Interval (sampling rate)
let interval = 0.0025
motionManager.accelerometerUpdateInterval = interval
motionManager.gyroUpdateInterval = interval
// 3. Start Updates on BACKGROUND QUEUE
if motionManager.isAccelerometerAvailable {
motionManager.startAccelerometerUpdates(to: sensorQueue) { [weak self] data, error in
guard let self = self, let data = data else { return }
let reading = VibrateView.SensorReading(
x: data.acceleration.x,
y: data.acceleration.y,
z: data.acceleration.z,
timestamp: data.timestamp - startTime
)
self.accBuffer.append(reading)
}
}
if motionManager.isGyroAvailable {
motionManager.startGyroUpdates(to: sensorQueue) { [weak self] data, error in
guard let self = self, let data = data else { return }
let reading = VibrateView.SensorReading(
x: data.rotationRate.x,
y: data.rotationRate.y,
z: data.rotationRate.z,
timestamp: data.timestamp - startTime
)
self.gyrBuffer.append(reading)
}
}
}
func stopSensors() {
motionManager.stopAccelerometerUpdates()
motionManager.stopGyroUpdates()
}
// Check availability
var isAccelAvailable: Bool { motionManager.isAccelerometerAvailable }
var isGyroAvailable: Bool { motionManager.isGyroAvailable }
}
Then, I am using this function to trigger the iPhone's internal vibration motor. User selects a test from the UI:
struct VibrationEvent {
let time: TimeInterval
let duration: TimeInterval
let intensity: Float
}
func vibrateDevice(forTest test: String) async throws {
var events = [CHHapticEvent]()
let traceDuration: TimeInterval = 2.0
let currentTime: TimeInterval = 0
var someEvents: [VibrationEvent] = []
let test1: [VibrationEvent] = [
VibrationEvent(time: currentTime, duration: 2.0, intensity: 1.0)
]
let test2: [VibrationEvent] = [
VibrationEvent(time: currentTime, duration: 0.5, intensity: 0.8),
VibrationEvent(time: currentTime + 0.5, duration: 0.5, intensity: 0.9),
VibrationEvent(time: currentTime + 1.0, duration: 0.5, intensity: 1.0),
VibrationEvent(time: currentTime + 1.5, duration: 0.5, intensity: 1.0)
]
let test3: [VibrationEvent] = [
VibrationEvent(time: currentTime, duration: 0.4, intensity: 1.0),
VibrationEvent(time: currentTime + 0.8, duration: 0.4, intensity: 1.0),
VibrationEvent(time: currentTime + 1.6, duration: 0.4, intensity: 1.0)
]
let test4: [VibrationEvent] = [
VibrationEvent(time: currentTime, duration: 0.4, intensity: 0.8),
VibrationEvent(time: currentTime + 0.8, duration: 0.4, intensity: 0.9),
VibrationEvent(time: currentTime + 1.6, duration: 0.4, intensity: 1.0)
]
let test5: [VibrationEvent] = [
VibrationEvent(time: currentTime, duration: 0.25, intensity: 1.0),
VibrationEvent(time: currentTime + 0.25, duration: 0.25, intensity: 0.2),
VibrationEvent(time: currentTime + 0.50, duration: 0.25, intensity: 1.0),
VibrationEvent(time: currentTime + 0.75, duration: 0.25, intensity: 0.2),
VibrationEvent(time: currentTime + 1.00, duration: 0.25, intensity: 1.0),
VibrationEvent(time: currentTime + 1.25, duration: 0.25, intensity: 0.2),
VibrationEvent(time: currentTime + 1.50, duration: 0.25, intensity: 1.0),
VibrationEvent(time: currentTime + 1.75, duration: 0.25, intensity: 0.2)
]
let test6: [VibrationEvent] = [
VibrationEvent(time: currentTime, duration: 0.4, intensity: 0.6),
VibrationEvent(time: currentTime + 0.4, duration: 0.4, intensity: 0.7),
VibrationEvent(time: currentTime + 0.8, duration: 0.4, intensity: 0.8),
VibrationEvent(time: currentTime + 1.2, duration: 0.4, intensity: 0.9),
VibrationEvent(time: currentTime + 1.6, duration: 0.4, intensity: 1.0)
]
if selectedTest.contains("Test 1"){
someEvents = test1
} else if selectedTest.contains("Test 2") {
someEvents = test2
} else if selectedTest.contains("Test 3") {
someEvents = test3
} else if selectedTest.contains("Test 4") {
someEvents = test4
} else if selectedTest.contains("Test 5") {
someEvents = test5
} else if selectedTest.contains("Test 6"){
someEvents = test6
}
for event in someEvents {
events.append(addVibration(at: event.time, duration: event.duration, intensity: event.intensity))
}
do {
let pattern = try CHHapticPattern(events: events, parameters: [])
let player = try engine?.makePlayer(with: pattern)
try player?.start(atTime: 0)
} catch {
print("Failed to play pattern: \(error.localizedDescription)")
}
let totalDuration = (someEvents.map { $0.time + $0.duration }.max() ?? 0)
try await Task.sleep(nanoseconds: UInt64(totalDuration * 1_000_000_000))
}
Finally, I use this function to piece it all together:
func startVibrationTest() async {
// 1. UI Setup
testRunning = true
canClearResults = false
tracesData = []
testStartTime = currentUptimeSeconds()
do {
try await prepareHaptics()
// MARK: - SCENARIO A: Test 0 (Rest Only)
if selectedTest.contains("Test 0") {
let restDuration: TimeInterval = 10.0
let restStart = currentUptimeSeconds()
// A. Start Collecting
collector.startSensors(startTime: currentUptimeSeconds())
// B. Wait
try await Task.sleep(nanoseconds: UInt64(restDuration * 1_000_000_000))
// C. Stop Collecting
collector.stopSensors()
// D. Save Data
let restTrace = TraceReadings(
id: 1,
accelerometer: collector.accBuffer,
gyroscope: collector.gyrBuffer,
startTime: restStart,
endTime: currentUptimeSeconds(),
traceType: .rest
)
// Append to array (UI Update on Main Actor)
await MainActor.run {
tracesData.append(restTrace)
}
sendDataToServer()
}
// MARK: - SCENARIO B: Vibration Tests (1-6)
else {
let numberOfTraces = 10
let breakDuration: TimeInterval = 10.0
for traceIndex in 0..
I want to also clarify that the vibration test is composed of 10 vibrations (10 motor-ON cases) and between each vibration and the next one, there is a 10 second motor-OFF period.
To reiterate, the gyroscope captures the vibration and we can see a clear difference between motor-ON and motor-OFF traces. On the other hand, the acclereometer shows almost no difference between the 2 cases.
Below you can see a visualization of the Gyroscope data:
[Gyroscope data VIB vs REST visuals][1]
You can see the Accelerometer data here:
[Accelerometer data VIB vs REST visuals][2]
Your insight would be highly appreciated. I am not sure if this is a coding error in the Swift app, if the internal vibrations are just not enough to trigger the iPhone's accelerometer, or if there is some sort of limitation from Apple that denies us from seeing vibrations in the accelerometer. What do you think?
Thank you all!