Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/sensors_plus.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,19 @@ jobs:
- name: "Run Integration Test"
run: ./.github/workflows/scripts/integration-test.sh ios sensors_plus_example

- name: "iOS Native Unit Tests"
working-directory: packages/sensors_plus/sensors_plus/example
run: |
# Reset the temporary Flutter test entrypoint before building the XCTest host.
flutter build ios --simulator --debug --config-only --target lib/main.dart
xcodebuild test -quiet \
-workspace ios/Runner.xcworkspace \
-scheme Runner \
-destination 'platform=iOS Simulator,name=iPhone 17' \
-only-testing:RunnerTests \
-parallel-testing-enabled NO \
CODE_SIGNING_ALLOWED=NO

web_example_build:
runs-on: ubuntu-latest
timeout-minutes: 30
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,54 @@
import Flutter
import UIKit
import XCTest

@testable import sensors_plus

class RunnerTests: XCTestCase {

func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
private let unixTime: TimeInterval = 1_700_000_000
private let accuracyMicroseconds: Double = 1

func testConvertsUptimeToUnixMicroseconds() {
let actual = sensorTimestampToUnixMicroseconds(
1000, currentUptime: 1000, currentUnixTime: unixTime)

XCTAssertEqual(actual, unixTime * 1_000_000, accuracy: accuracyMicroseconds)
}

func testRefreshesConversionAfterSimulatedSleep() {
let before = sensorTimestampToUnixMicroseconds(
1000, currentUptime: 1000, currentUnixTime: unixTime)
// Two minutes pass in wall time while uptime is paused. A new sample is
// then measured ten uptime seconds later in the same app process.
let after = sensorTimestampToUnixMicroseconds(
1010, currentUptime: 1010, currentUnixTime: unixTime + 130)

XCTAssertEqual(after - before, 130_000_000, accuracy: accuracyMicroseconds)
}

func testPreservesMeasurementTimeForDelayedDelivery() {
let actual = sensorTimestampToUnixMicroseconds(
999.75, currentUptime: 1000, currentUnixTime: unixTime)

XCTAssertEqual(actual, (unixTime - 0.25) * 1_000_000, accuracy: accuracyMicroseconds)
}

func testPreservesSampleSpacingDespiteDifferentDeliveryDelays() {
let first = sensorTimestampToUnixMicroseconds(
1000, currentUptime: 1001, currentUnixTime: unixTime + 1)
let second = sensorTimestampToUnixMicroseconds(
1000.02, currentUptime: 1004, currentUnixTime: unixTime + 4)

XCTAssertEqual(second - first, 20_000, accuracy: accuracyMicroseconds)
}

func testUsesCurrentWallTimeAfterClockAdjustment() {
let before = sensorTimestampToUnixMicroseconds(
1000, currentUptime: 1000, currentUnixTime: unixTime)
// The user sets the wall clock back one minute; the sensor clock does not reset.
let after = sensorTimestampToUnixMicroseconds(
1000, currentUptime: 1000, currentUnixTime: unixTime - 60)

XCTAssertEqual(after - before, -60_000_000, accuracy: accuracyMicroseconds)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ public protocol MotionStreamHandler: FlutterStreamHandler {
var samplingPeriod: Int { get set }
}

let timestampMicroAtBoot = (Date().timeIntervalSince1970 - ProcessInfo.processInfo.systemUptime) * 1000000

func _initMotionManager() {
if (_motionManager == nil) {
_motionManager = CMMotionManager()
Expand All @@ -39,8 +37,8 @@ func sendFlutter(x: Float64, y: Float64, z: Float64, timestamp: TimeInterval, si
}
// Even after [detachFromEngineForRegistrar] some events may still be received
// and fired until fully detached.
let timestampSince1970Micro = sensorTimestampToUnixMicroseconds(timestamp)
DispatchQueue.main.async {
let timestampSince1970Micro = timestampMicroAtBoot + (timestamp * 1000000)
let triplet = [x, y, z, timestampSince1970Micro]
triplet.withUnsafeBufferPointer { buffer in
sink(FlutterStandardTypedData.init(float64: Data(buffer: buffer)))
Expand Down Expand Up @@ -273,8 +271,8 @@ class FPPBarometerStreamHandlerPlus: NSObject, MotionStreamHandler {
return
}
let pressure = data!.pressure.doubleValue * 10.0 // kPa to hPa (hectopascals)
let timestampSince1970Micro = sensorTimestampToUnixMicroseconds(data!.timestamp)
DispatchQueue.main.async {
let timestampSince1970Micro = timestampMicroAtBoot + (data!.timestamp * 1000000)
let pressureArray: [Double] = [pressure, timestampSince1970Micro]
pressureArray.withUnsafeBufferPointer { buffer in
sink(FlutterStandardTypedData.init(float64: Data(buffer: buffer)))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2026 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import Foundation

func sensorTimestampToUnixMicroseconds(
_ sensorTimestamp: TimeInterval,
currentUptime: TimeInterval = ProcessInfo.processInfo.systemUptime,
currentUnixTime: TimeInterval = Date().timeIntervalSince1970
) -> Double {
// Uptime excludes sleep, so its offset from wall time must not be cached
// across callbacks. Preserve the measurement's age instead of timestamping
// it at delivery time.
let sampleAge = currentUptime - sensorTimestamp
return (currentUnixTime - sampleAge) * 1_000_000
}
Loading