react-native-nitro-compass 1.0.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -3
- package/android/src/main/java/com/margelo/nitro/nitrocompass/HybridNitroCompass.kt +103 -11
- package/ios/HybridNitroCompass.swift +13 -3
- package/lib/commonjs/hook.js +4 -0
- package/lib/commonjs/hook.js.map +1 -1
- package/lib/module/hook.js +4 -0
- package/lib/module/hook.js.map +1 -1
- package/lib/typescript/src/hook.d.ts +10 -0
- package/lib/typescript/src/hook.d.ts.map +1 -1
- package/lib/typescript/src/specs/NitroCompass.nitro.d.ts +16 -0
- package/lib/typescript/src/specs/NitroCompass.nitro.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHybridNitroCompassSpec.cpp +4 -0
- package/nitrogen/generated/android/c++/JHybridNitroCompassSpec.hpp +1 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/HybridNitroCompassSpec.kt +4 -0
- package/nitrogen/generated/ios/c++/HybridNitroCompassSpecSwift.hpp +6 -0
- package/nitrogen/generated/ios/swift/HybridNitroCompassSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridNitroCompassSpec_cxx.swift +11 -0
- package/nitrogen/generated/shared/c++/HybridNitroCompassSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridNitroCompassSpec.hpp +1 -0
- package/package.json +1 -1
- package/src/hook.ts +15 -0
- package/src/specs/NitroCompass.nitro.ts +17 -0
package/README.md
CHANGED
|
@@ -52,6 +52,7 @@ NitroCompass.isStarted(): boolean
|
|
|
52
52
|
NitroCompass.hasCompass(): boolean
|
|
53
53
|
|
|
54
54
|
NitroCompass.setFilter(degrees: number): void
|
|
55
|
+
NitroCompass.setSmoothing(alpha: number): void
|
|
55
56
|
NitroCompass.getCurrentHeading(): CompassSample | undefined
|
|
56
57
|
NitroCompass.getDiagnostics(): SensorDiagnostics | undefined
|
|
57
58
|
NitroCompass.setDeclination(degrees: number): void
|
|
@@ -70,6 +71,7 @@ interface SensorDiagnostics { sensor: SensorKind }
|
|
|
70
71
|
```
|
|
71
72
|
|
|
72
73
|
- `filterDegrees` — minimum change between successive samples before the next one is delivered. Pass `0` for "every event"; typical UI values are `1`–`3`. Use `setFilter()` to change live without tearing down the subscription.
|
|
74
|
+
- `setSmoothing(alpha)` — low-pass smoothing factor (EMA α) applied to heading samples on Android. Range `(0, 1]`, default `0.2` (~100 ms time constant at 50 Hz). `1.0` disables smoothing; smaller values smooth more (kills jitter, adds a touch of latency). **No-op on iOS** — `CLLocationManager` filters internally with Apple's own algorithm, so layering an EMA on top would only add latency. See [Smoothing](#smoothing) below.
|
|
73
75
|
- `start()` is idempotent in the destructive sense — calling it while already started silently replaces the previous subscription with the new callback. `stop()` is idempotent and safe from inside the `onHeading` callback.
|
|
74
76
|
- `getDiagnostics()` reports which sensor would produce headings on this device — useful for explaining quality differences (e.g. a phone falling back to `geomagneticRotationVector` will be more susceptible to magnetic interference than one using `rotationVector`). Safe to call before `start()`.
|
|
75
77
|
- `accuracy` is a numeric uncertainty (degrees). On iOS it comes from `CLHeading.headingAccuracy` directly. On Android it comes from `event.values[4]` of the rotation-vector sensor; if the sensor stack does not publish that (rare), the module falls back to a coarse degree estimate from `SensorManager.SENSOR_STATUS_*` (`HIGH→5°`, `MEDIUM→15°`, `LOW→30°`).
|
|
@@ -77,7 +79,12 @@ interface SensorDiagnostics { sensor: SensorKind }
|
|
|
77
79
|
|
|
78
80
|
### Calibration
|
|
79
81
|
|
|
80
|
-
`setOnCalibrationNeeded(cb)` registers a callback fired whenever the calibration bucket transitions.
|
|
82
|
+
`setOnCalibrationNeeded(cb)` registers a callback fired whenever the calibration bucket transitions. Each platform's bucket is derived from its **native** accuracy semantics, since the underlying values are not directly comparable:
|
|
83
|
+
|
|
84
|
+
- **iOS** uses `CLHeading.headingAccuracy` (degrees). Apple is conservative — even well-calibrated iPhones typically report `10–15°` and rarely below `5°` (per [Apple staff on the developer forums](https://developer.apple.com/forums/thread/79687)). Buckets: `<20°` → `'high'`, `<35°` → `'medium'`, `<55°` → `'low'`, otherwise `'unreliable'`. The system's "wave the device in a figure-8" prompt is suppressed and reported to your callback as `'unreliable'` — show your own UI when you receive that bucket.
|
|
85
|
+
- **Android** uses `SensorManager.SENSOR_STATUS_*` from `onAccuracyChanged` directly (`HIGH` / `MEDIUM` / `LOW` / `UNRELIABLE`); when `event.values[4]` of the rotation vector publishes a per-sample degree estimate, that's used with thresholds `<5°` / `<15°` / `<30°`. **When magnetic interference is currently detected, the surfaced bucket is downgraded by one notch** (`HIGH→MEDIUM`, `MEDIUM→LOW`, `LOW→UNRELIABLE`) — Android's gyro+accel sensor fusion can keep the OS rotation-vector bucket high even while the magnetometer is being skewed, and surfacing `quality='high'` alongside `interfering=true` is contradictory UX.
|
|
86
|
+
|
|
87
|
+
Both platforms can plausibly emit `'high'` on a clean device — the threshold split just reflects each OS's reporting style.
|
|
81
88
|
|
|
82
89
|
```ts
|
|
83
90
|
NitroCompass.setOnCalibrationNeeded((q) => {
|
|
@@ -87,7 +94,9 @@ NitroCompass.setOnCalibrationNeeded((q) => {
|
|
|
87
94
|
|
|
88
95
|
### Magnetic interference
|
|
89
96
|
|
|
90
|
-
`setOnInterferenceDetected(cb)` fires `true` when the raw magnetic field magnitude leaves the normal Earth band (~20–70 µT) and `false` when it returns. Typical sources are laptops, monitors, car engines, and large steel structures — these can skew heading by tens of degrees
|
|
97
|
+
`setOnInterferenceDetected(cb)` fires `true` when the raw magnetic field magnitude leaves the normal Earth band (~20–70 µT) and `false` when it returns. Typical sources are laptops, monitors, car engines, and large steel structures — these can skew heading by tens of degrees.
|
|
98
|
+
|
|
99
|
+
Interference is surfaced two ways: (1) directly via this callback, and (2) on Android, the calibration bucket emitted by `setOnCalibrationNeeded` is downgraded by one notch while interference is detected (see the Calibration section above). On iOS, only the direct callback fires — `CLLocationManager`'s own accuracy reporting already responds to magnetometer disturbance, so a separate downgrade would double-count.
|
|
91
100
|
|
|
92
101
|
```ts
|
|
93
102
|
NitroCompass.setOnInterferenceDetected((interfering) => {
|
|
@@ -116,6 +125,20 @@ NitroCompass.setDeclination(declination)
|
|
|
116
125
|
|
|
117
126
|
Pass `0` to revert to magnetic. Declination survives `stop()`/`start()` cycles.
|
|
118
127
|
|
|
128
|
+
### Smoothing
|
|
129
|
+
|
|
130
|
+
Android's raw `TYPE_ROTATION_VECTOR` output jitters by `±1–3°` even at rest, which produces a visibly twitchy compass dial. iOS's `CLLocationManager` filters internally; Android does not. The library applies a circular EMA low-pass filter on `(sin θ, cos θ)` (handles `359°→0°` wraparound cleanly) before delivering samples, with `α = 0.2` by default — the same value used in [phishman3579/android-compass](https://github.com/phishman3579/android-compass/blob/master/src/com/jwetherell/compass/common/LowPassFilter.java) and within the range used by [Trail Sense](https://github.com/kylecorry31/Trail-Sense)'s production compass code.
|
|
131
|
+
|
|
132
|
+
Tune live:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
NitroCompass.setSmoothing(0.2) // default — kills jitter, ~100 ms latency
|
|
136
|
+
NitroCompass.setSmoothing(0.4) // snappier, more visible jitter
|
|
137
|
+
NitroCompass.setSmoothing(1.0) // disabled — every sample passes through
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`setSmoothing` is a no-op on iOS — Apple's stack already filters heading internally, so layering an EMA on top would only add latency without removing noise.
|
|
141
|
+
|
|
119
142
|
### Background pause
|
|
120
143
|
|
|
121
144
|
By default the underlying sensor / location-manager subscription is silently paused while the app is backgrounded and resumed when it returns to the foreground; the JS callback and any declination set via `setDeclination` are preserved across the pause. To opt out (e.g. for a fitness tracker that needs heading while screen-off):
|
|
@@ -134,6 +157,7 @@ import { useCompass } from 'react-native-nitro-compass'
|
|
|
134
157
|
function CompassView() {
|
|
135
158
|
const { reading, quality, interfering, hasCompass } = useCompass({
|
|
136
159
|
filterDegrees: 1,
|
|
160
|
+
smoothingAlpha: 0.2,
|
|
137
161
|
declination: 0,
|
|
138
162
|
pauseOnBackground: true,
|
|
139
163
|
enabled: true,
|
|
@@ -161,11 +185,12 @@ function useCompass(options?: UseCompassOptions): UseCompassResult
|
|
|
161
185
|
| Option | Type | Default | Description |
|
|
162
186
|
| --- | --- | --- | --- |
|
|
163
187
|
| `filterDegrees` | `number` | `1` | Minimum change between successive samples in degrees. Pass `0` for "every event". Updated live via `NitroCompass.setFilter()` whenever the prop changes. |
|
|
188
|
+
| `smoothingAlpha` | `number` | `0.2` | Low-pass smoothing factor (EMA α) on Android. `1.0` disables smoothing; smaller values smooth more. No-op on iOS. See [Smoothing](#smoothing). |
|
|
164
189
|
| `declination` | `number` | `0` | Magnetic-to-true offset in signed degrees. Pull from a model like [`geomagnetism`](https://github.com/kahirokunn/geomagnetism) keyed on the user's lat/lon. When non-zero, every emitted sample is true-north. |
|
|
165
190
|
| `pauseOnBackground` | `boolean` | `true` | Pause the underlying sensor / location-manager subscription while the app is backgrounded and resume on foreground. |
|
|
166
191
|
| `enabled` | `boolean` | `true` | Toggle the heading subscription without unmounting. When `false`, `reading` stops updating but calibration and interference observation continue (so you can still show warnings). |
|
|
167
192
|
|
|
168
|
-
`filterDegrees`, `declination`, and `pauseOnBackground` map to global state on `NitroCompass` — if multiple hooks set them, last-write-wins.
|
|
193
|
+
`filterDegrees`, `smoothingAlpha`, `declination`, and `pauseOnBackground` map to global state on `NitroCompass` — if multiple hooks set them, last-write-wins.
|
|
169
194
|
|
|
170
195
|
#### Result
|
|
171
196
|
|
|
@@ -56,6 +56,16 @@ class HybridNitroCompass : HybridNitroCompassSpec() {
|
|
|
56
56
|
// structural steel routinely push readings well above 100 µT.
|
|
57
57
|
private const val EARTH_FIELD_MIN_UT = 20.0
|
|
58
58
|
private const val EARTH_FIELD_MAX_UT = 70.0
|
|
59
|
+
|
|
60
|
+
// Default low-pass smoothing for the rotation-vector output. iOS's
|
|
61
|
+
// CLLocationManager already filters heading internally; the raw
|
|
62
|
+
// Android rotation vector does not, so the dial visibly jitters
|
|
63
|
+
// by 1–3° at rest. Smoothing (sin θ, cos θ) instead of θ avoids
|
|
64
|
+
// 359°→0° wraparound artifacts. α=0.2 gives a ~5-sample time
|
|
65
|
+
// constant — at SENSOR_DELAY_GAME (~20ms) that's ~100ms of lag,
|
|
66
|
+
// imperceptible compared to the noise it removes. Tunable live
|
|
67
|
+
// via setSmoothing().
|
|
68
|
+
private const val DEFAULT_SMOOTHING_ALPHA = 0.2
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
@Volatile private var filterDeg: Double = 1.0
|
|
@@ -70,6 +80,17 @@ class HybridNitroCompass : HybridNitroCompassSpec() {
|
|
|
70
80
|
@Volatile private var lastEventNs: Long = 0L
|
|
71
81
|
@Volatile private var lastInterference: Boolean? = null
|
|
72
82
|
@Volatile private var currentActivityRef: WeakReference<Activity>? = null
|
|
83
|
+
@Volatile private var smoothedSin: Double = Double.NaN
|
|
84
|
+
@Volatile private var smoothedCos: Double = Double.NaN
|
|
85
|
+
@Volatile private var smoothingAlpha: Double = DEFAULT_SMOOTHING_ALPHA
|
|
86
|
+
// Tracks whether the rotation-vector sensor publishes a per-sample
|
|
87
|
+
// accuracy in `event.values[4]`. Many devices don't, in which case the
|
|
88
|
+
// synthetic degree floor derived from SENSOR_STATUS_* is the only
|
|
89
|
+
// accuracy signal available — and we have to keep it updated.
|
|
90
|
+
@Volatile private var hasPerSampleAccuracy: Boolean = false
|
|
91
|
+
// Last raw quality from the OS, before the interference downgrade is
|
|
92
|
+
// applied. Used to re-derive `lastQuality` when interference toggles.
|
|
93
|
+
@Volatile private var lastRawQuality: AccuracyQuality? = null
|
|
73
94
|
|
|
74
95
|
private val rotationMatrix = FloatArray(16)
|
|
75
96
|
private val remappedMatrix = FloatArray(16)
|
|
@@ -131,6 +152,8 @@ class HybridNitroCompass : HybridNitroCompassSpec() {
|
|
|
131
152
|
lastSample = null
|
|
132
153
|
lastQuality = null
|
|
133
154
|
|
|
155
|
+
hasPerSampleAccuracy = false
|
|
156
|
+
lastRawQuality = null
|
|
134
157
|
registerLifecycleCallbacks()
|
|
135
158
|
subscribeLocked()
|
|
136
159
|
}
|
|
@@ -155,6 +178,10 @@ class HybridNitroCompass : HybridNitroCompassSpec() {
|
|
|
155
178
|
filterDeg = degrees.coerceAtLeast(0.0)
|
|
156
179
|
}
|
|
157
180
|
|
|
181
|
+
override fun setSmoothing(alpha: Double) {
|
|
182
|
+
smoothingAlpha = alpha.coerceIn(0.0, 1.0)
|
|
183
|
+
}
|
|
184
|
+
|
|
158
185
|
override fun getDiagnostics(): SensorDiagnostics? {
|
|
159
186
|
val sm = NitroModules.applicationContext?.getSystemService(Context.SENSOR_SERVICE) as? SensorManager
|
|
160
187
|
?: return null
|
|
@@ -254,6 +281,8 @@ class HybridNitroCompass : HybridNitroCompassSpec() {
|
|
|
254
281
|
activeListener = listener
|
|
255
282
|
isSubscribed = true
|
|
256
283
|
|
|
284
|
+
smoothedSin = Double.NaN
|
|
285
|
+
smoothedCos = Double.NaN
|
|
257
286
|
lastEventNs = 0L
|
|
258
287
|
watchdogHandler.removeCallbacks(watchdogRunnable)
|
|
259
288
|
watchdogHandler.postDelayed(watchdogRunnable, WATCHDOG_PERIOD_MS)
|
|
@@ -380,9 +409,11 @@ class HybridNitroCompass : HybridNitroCompassSpec() {
|
|
|
380
409
|
|
|
381
410
|
var heading = Math.toDegrees(orientation[0].toDouble())
|
|
382
411
|
if (heading < 0.0) heading += 360.0
|
|
412
|
+
heading = smoothHeading(heading)
|
|
383
413
|
|
|
384
414
|
if (event.values.size > 4 && event.values[4] >= 0f) {
|
|
385
415
|
val acc = Math.toDegrees(event.values[4].toDouble())
|
|
416
|
+
hasPerSampleAccuracy = true
|
|
386
417
|
lastAccuracyDeg = acc
|
|
387
418
|
fireCalibration(qualityFor(acc))
|
|
388
419
|
}
|
|
@@ -409,6 +440,14 @@ class HybridNitroCompass : HybridNitroCompassSpec() {
|
|
|
409
440
|
if (lastInterference == isInterference) return
|
|
410
441
|
lastInterference = isInterference
|
|
411
442
|
interferenceCb?.invoke(isInterference)
|
|
443
|
+
// External interference makes the heading less trustworthy even when
|
|
444
|
+
// the OS rotation-vector accuracy hasn't downgraded (gyro+accel can
|
|
445
|
+
// keep its bucket high while the magnetometer is being skewed). Pump
|
|
446
|
+
// the last raw quality back through fireCalibration so the
|
|
447
|
+
// interference-aware downgrade is applied, and refresh the synthetic
|
|
448
|
+
// degree value to match.
|
|
449
|
+
lastRawQuality?.let { fireCalibration(it) }
|
|
450
|
+
if (!hasPerSampleAccuracy) refreshSyntheticAccuracy()
|
|
412
451
|
}
|
|
413
452
|
|
|
414
453
|
private fun handleAccuracyChanged(accuracy: Int) {
|
|
@@ -418,15 +457,34 @@ class HybridNitroCompass : HybridNitroCompassSpec() {
|
|
|
418
457
|
SensorManager.SENSOR_STATUS_ACCURACY_LOW -> AccuracyQuality.LOW
|
|
419
458
|
else -> AccuracyQuality.UNRELIABLE
|
|
420
459
|
}
|
|
421
|
-
if (lastAccuracyDeg < 0.0) {
|
|
422
|
-
lastAccuracyDeg = when (quality) {
|
|
423
|
-
AccuracyQuality.HIGH -> 5.0
|
|
424
|
-
AccuracyQuality.MEDIUM -> 15.0
|
|
425
|
-
AccuracyQuality.LOW -> 30.0
|
|
426
|
-
AccuracyQuality.UNRELIABLE -> -1.0
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
460
|
fireCalibration(quality)
|
|
461
|
+
if (!hasPerSampleAccuracy) refreshSyntheticAccuracy()
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
private fun smoothHeading(degrees: Double): Double {
|
|
465
|
+
val rad = Math.toRadians(degrees)
|
|
466
|
+
val s = Math.sin(rad)
|
|
467
|
+
val c = Math.cos(rad)
|
|
468
|
+
val ss = smoothedSin
|
|
469
|
+
val cs = smoothedCos
|
|
470
|
+
if (ss.isNaN() || cs.isNaN()) {
|
|
471
|
+
smoothedSin = s
|
|
472
|
+
smoothedCos = c
|
|
473
|
+
return degrees
|
|
474
|
+
}
|
|
475
|
+
val a = smoothingAlpha
|
|
476
|
+
if (a >= 1.0) {
|
|
477
|
+
smoothedSin = s
|
|
478
|
+
smoothedCos = c
|
|
479
|
+
return degrees
|
|
480
|
+
}
|
|
481
|
+
val newSin = a * s + (1.0 - a) * ss
|
|
482
|
+
val newCos = a * c + (1.0 - a) * cs
|
|
483
|
+
smoothedSin = newSin
|
|
484
|
+
smoothedCos = newCos
|
|
485
|
+
var deg = Math.toDegrees(Math.atan2(newSin, newCos))
|
|
486
|
+
if (deg < 0.0) deg += 360.0
|
|
487
|
+
return deg
|
|
430
488
|
}
|
|
431
489
|
|
|
432
490
|
private fun qualityFor(accuracyDeg: Double): AccuracyQuality {
|
|
@@ -439,10 +497,44 @@ class HybridNitroCompass : HybridNitroCompassSpec() {
|
|
|
439
497
|
}
|
|
440
498
|
}
|
|
441
499
|
|
|
500
|
+
private fun degreesFor(quality: AccuracyQuality): Double = when (quality) {
|
|
501
|
+
AccuracyQuality.HIGH -> 5.0
|
|
502
|
+
AccuracyQuality.MEDIUM -> 15.0
|
|
503
|
+
AccuracyQuality.LOW -> 30.0
|
|
504
|
+
AccuracyQuality.UNRELIABLE -> -1.0
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Magnetic interference is a separate signal from rotation-vector
|
|
508
|
+
// accuracy on Android — gyro+accel can keep the OS bucket "HIGH" even
|
|
509
|
+
// while the magnetometer is being skewed by a laptop / car / steel.
|
|
510
|
+
// Reporting `quality=high` while `interfering=true` is contradictory
|
|
511
|
+
// UX, so we downgrade the surfaced bucket by one notch when
|
|
512
|
+
// interference is currently detected.
|
|
513
|
+
private fun effectiveQuality(raw: AccuracyQuality): AccuracyQuality {
|
|
514
|
+
if (lastInterference != true) return raw
|
|
515
|
+
return when (raw) {
|
|
516
|
+
AccuracyQuality.HIGH -> AccuracyQuality.MEDIUM
|
|
517
|
+
AccuracyQuality.MEDIUM -> AccuracyQuality.LOW
|
|
518
|
+
AccuracyQuality.LOW -> AccuracyQuality.UNRELIABLE
|
|
519
|
+
AccuracyQuality.UNRELIABLE -> AccuracyQuality.UNRELIABLE
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// For devices that don't publish a per-sample accuracy in
|
|
524
|
+
// event.values[4], the only accuracy signal is the OS bucket. Map the
|
|
525
|
+
// current effective bucket back to a representative degree value so
|
|
526
|
+
// CompassSample.accuracy reflects interference too.
|
|
527
|
+
private fun refreshSyntheticAccuracy() {
|
|
528
|
+
val raw = lastRawQuality ?: return
|
|
529
|
+
lastAccuracyDeg = degreesFor(effectiveQuality(raw))
|
|
530
|
+
}
|
|
531
|
+
|
|
442
532
|
private fun fireCalibration(quality: AccuracyQuality) {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
533
|
+
lastRawQuality = quality
|
|
534
|
+
val effective = effectiveQuality(quality)
|
|
535
|
+
if (effective == lastQuality) return
|
|
536
|
+
lastQuality = effective
|
|
537
|
+
calibrationCb?.invoke(effective)
|
|
446
538
|
}
|
|
447
539
|
|
|
448
540
|
private fun currentSurfaceRotation(): Int {
|
|
@@ -183,6 +183,11 @@ class HybridNitroCompass: HybridNitroCompassSpec {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
// CLLocationManager filters heading internally with Apple's own
|
|
187
|
+
// algorithm; layering an EMA on top would only add latency. Kept as a
|
|
188
|
+
// no-op so cross-platform JS callers can call it unconditionally.
|
|
189
|
+
func setSmoothing(alpha: Double) throws {}
|
|
190
|
+
|
|
186
191
|
func getDiagnostics() throws -> SensorDiagnostics? {
|
|
187
192
|
guard CLLocationManager.headingAvailable() else { return nil }
|
|
188
193
|
return SensorDiagnostics(sensor: .corelocation)
|
|
@@ -363,14 +368,19 @@ class HybridNitroCompass: HybridNitroCompassSpec {
|
|
|
363
368
|
let sample = CompassSample(heading: heading, accuracy: accuracy)
|
|
364
369
|
lastSample = sample
|
|
365
370
|
|
|
371
|
+
// CLLocationManager.headingAccuracy is conservative — even a
|
|
372
|
+
// well-calibrated compass usually reports 5–15°, and values under
|
|
373
|
+
// 5° are basically never produced. Bucket against the realistic
|
|
374
|
+
// distribution so `.high` is reachable, mirroring how Android maps
|
|
375
|
+
// SENSOR_STATUS_ACCURACY_HIGH.
|
|
366
376
|
let quality: AccuracyQuality
|
|
367
377
|
if accuracy < 0 {
|
|
368
378
|
quality = .unreliable
|
|
369
|
-
} else if accuracy <
|
|
379
|
+
} else if accuracy < 20 {
|
|
370
380
|
quality = .high
|
|
371
|
-
} else if accuracy <
|
|
381
|
+
} else if accuracy < 35 {
|
|
372
382
|
quality = .medium
|
|
373
|
-
} else if accuracy <
|
|
383
|
+
} else if accuracy < 55 {
|
|
374
384
|
quality = .low
|
|
375
385
|
} else {
|
|
376
386
|
quality = .unreliable
|
package/lib/commonjs/hook.js
CHANGED
|
@@ -17,6 +17,7 @@ var _multiplex = require("./multiplex");
|
|
|
17
17
|
function useCompass(options = {}) {
|
|
18
18
|
const {
|
|
19
19
|
filterDegrees = 1,
|
|
20
|
+
smoothingAlpha = 0.2,
|
|
20
21
|
declination = 0,
|
|
21
22
|
pauseOnBackground = true,
|
|
22
23
|
enabled = true
|
|
@@ -35,6 +36,9 @@ function useCompass(options = {}) {
|
|
|
35
36
|
(0, _react.useEffect)(() => {
|
|
36
37
|
_native.NitroCompass.setFilter(filterDegrees);
|
|
37
38
|
}, [filterDegrees]);
|
|
39
|
+
(0, _react.useEffect)(() => {
|
|
40
|
+
_native.NitroCompass.setSmoothing(smoothingAlpha);
|
|
41
|
+
}, [smoothingAlpha]);
|
|
38
42
|
(0, _react.useEffect)(() => {
|
|
39
43
|
_native.NitroCompass.setDeclination(declination);
|
|
40
44
|
}, [declination]);
|
package/lib/commonjs/hook.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","require","_native","_multiplex","useCompass","options","filterDegrees","declination","pauseOnBackground","enabled","reading","setReading","useState","quality","setQuality","interfering","setInterfering","hasCompass","NitroCompass","diagnostics","getDiagnostics","filterRef","useRef","current","useEffect","setFilter","setDeclination","setPauseOnBackground","addCalibrationListener","addInterferenceListener","off","addHeadingListener"],"sourceRoot":"../../src","sources":["hook.ts"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAMA,IAAAC,OAAA,GAAAD,OAAA;AACA,IAAAE,UAAA,GAAAF,OAAA;
|
|
1
|
+
{"version":3,"names":["_react","require","_native","_multiplex","useCompass","options","filterDegrees","smoothingAlpha","declination","pauseOnBackground","enabled","reading","setReading","useState","quality","setQuality","interfering","setInterfering","hasCompass","NitroCompass","diagnostics","getDiagnostics","filterRef","useRef","current","useEffect","setFilter","setSmoothing","setDeclination","setPauseOnBackground","addCalibrationListener","addInterferenceListener","off","addHeadingListener"],"sourceRoot":"../../src","sources":["hook.ts"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAMA,IAAAC,OAAA,GAAAD,OAAA;AACA,IAAAE,UAAA,GAAAF,OAAA;AAyDA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASG,UAAUA,CACxBC,OAA0B,GAAG,CAAC,CAAC,EACb;EAClB,MAAM;IACJC,aAAa,GAAG,CAAC;IACjBC,cAAc,GAAG,GAAG;IACpBC,WAAW,GAAG,CAAC;IACfC,iBAAiB,GAAG,IAAI;IACxBC,OAAO,GAAG;EACZ,CAAC,GAAGL,OAAO;EAEX,MAAM,CAACM,OAAO,EAAEC,UAAU,CAAC,GAAG,IAAAC,eAAQ,EAAuB,IAAI,CAAC;EAClE,MAAM,CAACC,OAAO,EAAEC,UAAU,CAAC,GAAG,IAAAF,eAAQ,EAAyB,IAAI,CAAC;EACpE,MAAM,CAACG,WAAW,EAAEC,cAAc,CAAC,GAAG,IAAAJ,eAAQ,EAAC,KAAK,CAAC;EAErD,MAAM,CAACK,UAAU,CAAC,GAAG,IAAAL,eAAQ,EAAC,MAAMM,oBAAY,CAACD,UAAU,CAAC,CAAC,CAAC;EAC9D,MAAM,CAACE,WAAW,CAAC,GAAG,IAAAP,eAAQ,EAAC,MAAMM,oBAAY,CAACE,cAAc,CAAC,CAAC,CAAC;;EAEnE;EACA;EACA;EACA,MAAMC,SAAS,GAAG,IAAAC,aAAM,EAACjB,aAAa,CAAC;EACvCgB,SAAS,CAACE,OAAO,GAAGlB,aAAa;EAEjC,IAAAmB,gBAAS,EAAC,MAAM;IACdN,oBAAY,CAACO,SAAS,CAACpB,aAAa,CAAC;EACvC,CAAC,EAAE,CAACA,aAAa,CAAC,CAAC;EAEnB,IAAAmB,gBAAS,EAAC,MAAM;IACdN,oBAAY,CAACQ,YAAY,CAACpB,cAAc,CAAC;EAC3C,CAAC,EAAE,CAACA,cAAc,CAAC,CAAC;EAEpB,IAAAkB,gBAAS,EAAC,MAAM;IACdN,oBAAY,CAACS,cAAc,CAACpB,WAAW,CAAC;EAC1C,CAAC,EAAE,CAACA,WAAW,CAAC,CAAC;EAEjB,IAAAiB,gBAAS,EAAC,MAAM;IACdN,oBAAY,CAACU,oBAAoB,CAACpB,iBAAiB,CAAC;EACtD,CAAC,EAAE,CAACA,iBAAiB,CAAC,CAAC;EAEvB,IAAAgB,gBAAS,EAAC,MAAM;IACd,IAAI,CAACP,UAAU,EAAE;IACjB,OAAO,IAAAY,iCAAsB,EAACf,UAAU,CAAC;EAC3C,CAAC,EAAE,CAACG,UAAU,CAAC,CAAC;EAEhB,IAAAO,gBAAS,EAAC,MAAM;IACd,IAAI,CAACP,UAAU,EAAE;IACjB,OAAO,IAAAa,kCAAuB,EAACd,cAAc,CAAC;EAChD,CAAC,EAAE,CAACC,UAAU,CAAC,CAAC;EAEhB,IAAAO,gBAAS,EAAC,MAAM;IACd,IAAI,CAACP,UAAU,IAAI,CAACR,OAAO,EAAE;IAC7B,MAAMsB,GAAG,GAAG,IAAAC,6BAAkB,EAACrB,UAAU,CAAC;IAC1C;IACA;IACAO,oBAAY,CAACO,SAAS,CAACJ,SAAS,CAACE,OAAO,CAAC;IACzC,OAAOQ,GAAG;IACV;EACF,CAAC,EAAE,CAACd,UAAU,EAAER,OAAO,CAAC,CAAC;EAEzB,OAAO;IAAEC,OAAO;IAAEG,OAAO;IAAEE,WAAW;IAAEE,UAAU;IAAEE;EAAY,CAAC;AACnE","ignoreList":[]}
|
package/lib/module/hook.js
CHANGED
|
@@ -13,6 +13,7 @@ import { addCalibrationListener, addHeadingListener, addInterferenceListener } f
|
|
|
13
13
|
export function useCompass(options = {}) {
|
|
14
14
|
const {
|
|
15
15
|
filterDegrees = 1,
|
|
16
|
+
smoothingAlpha = 0.2,
|
|
16
17
|
declination = 0,
|
|
17
18
|
pauseOnBackground = true,
|
|
18
19
|
enabled = true
|
|
@@ -31,6 +32,9 @@ export function useCompass(options = {}) {
|
|
|
31
32
|
useEffect(() => {
|
|
32
33
|
NitroCompass.setFilter(filterDegrees);
|
|
33
34
|
}, [filterDegrees]);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
NitroCompass.setSmoothing(smoothingAlpha);
|
|
37
|
+
}, [smoothingAlpha]);
|
|
34
38
|
useEffect(() => {
|
|
35
39
|
NitroCompass.setDeclination(declination);
|
|
36
40
|
}, [declination]);
|
package/lib/module/hook.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useEffect","useRef","useState","NitroCompass","addCalibrationListener","addHeadingListener","addInterferenceListener","useCompass","options","filterDegrees","declination","pauseOnBackground","enabled","reading","setReading","quality","setQuality","interfering","setInterfering","hasCompass","diagnostics","getDiagnostics","filterRef","current","setFilter","setDeclination","setPauseOnBackground","off"],"sourceRoot":"../../src","sources":["hook.ts"],"mappings":";;AAAA,SAASA,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,OAAO;AAMnD,SAASC,YAAY,QAAQ,UAAU;AACvC,SACEC,sBAAsB,EACtBC,kBAAkB,EAClBC,uBAAuB,QAClB,aAAa;
|
|
1
|
+
{"version":3,"names":["useEffect","useRef","useState","NitroCompass","addCalibrationListener","addHeadingListener","addInterferenceListener","useCompass","options","filterDegrees","smoothingAlpha","declination","pauseOnBackground","enabled","reading","setReading","quality","setQuality","interfering","setInterfering","hasCompass","diagnostics","getDiagnostics","filterRef","current","setFilter","setSmoothing","setDeclination","setPauseOnBackground","off"],"sourceRoot":"../../src","sources":["hook.ts"],"mappings":";;AAAA,SAASA,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,OAAO;AAMnD,SAASC,YAAY,QAAQ,UAAU;AACvC,SACEC,sBAAsB,EACtBC,kBAAkB,EAClBC,uBAAuB,QAClB,aAAa;AAqDpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CACxBC,OAA0B,GAAG,CAAC,CAAC,EACb;EAClB,MAAM;IACJC,aAAa,GAAG,CAAC;IACjBC,cAAc,GAAG,GAAG;IACpBC,WAAW,GAAG,CAAC;IACfC,iBAAiB,GAAG,IAAI;IACxBC,OAAO,GAAG;EACZ,CAAC,GAAGL,OAAO;EAEX,MAAM,CAACM,OAAO,EAAEC,UAAU,CAAC,GAAGb,QAAQ,CAAuB,IAAI,CAAC;EAClE,MAAM,CAACc,OAAO,EAAEC,UAAU,CAAC,GAAGf,QAAQ,CAAyB,IAAI,CAAC;EACpE,MAAM,CAACgB,WAAW,EAAEC,cAAc,CAAC,GAAGjB,QAAQ,CAAC,KAAK,CAAC;EAErD,MAAM,CAACkB,UAAU,CAAC,GAAGlB,QAAQ,CAAC,MAAMC,YAAY,CAACiB,UAAU,CAAC,CAAC,CAAC;EAC9D,MAAM,CAACC,WAAW,CAAC,GAAGnB,QAAQ,CAAC,MAAMC,YAAY,CAACmB,cAAc,CAAC,CAAC,CAAC;;EAEnE;EACA;EACA;EACA,MAAMC,SAAS,GAAGtB,MAAM,CAACQ,aAAa,CAAC;EACvCc,SAAS,CAACC,OAAO,GAAGf,aAAa;EAEjCT,SAAS,CAAC,MAAM;IACdG,YAAY,CAACsB,SAAS,CAAChB,aAAa,CAAC;EACvC,CAAC,EAAE,CAACA,aAAa,CAAC,CAAC;EAEnBT,SAAS,CAAC,MAAM;IACdG,YAAY,CAACuB,YAAY,CAAChB,cAAc,CAAC;EAC3C,CAAC,EAAE,CAACA,cAAc,CAAC,CAAC;EAEpBV,SAAS,CAAC,MAAM;IACdG,YAAY,CAACwB,cAAc,CAAChB,WAAW,CAAC;EAC1C,CAAC,EAAE,CAACA,WAAW,CAAC,CAAC;EAEjBX,SAAS,CAAC,MAAM;IACdG,YAAY,CAACyB,oBAAoB,CAAChB,iBAAiB,CAAC;EACtD,CAAC,EAAE,CAACA,iBAAiB,CAAC,CAAC;EAEvBZ,SAAS,CAAC,MAAM;IACd,IAAI,CAACoB,UAAU,EAAE;IACjB,OAAOhB,sBAAsB,CAACa,UAAU,CAAC;EAC3C,CAAC,EAAE,CAACG,UAAU,CAAC,CAAC;EAEhBpB,SAAS,CAAC,MAAM;IACd,IAAI,CAACoB,UAAU,EAAE;IACjB,OAAOd,uBAAuB,CAACa,cAAc,CAAC;EAChD,CAAC,EAAE,CAACC,UAAU,CAAC,CAAC;EAEhBpB,SAAS,CAAC,MAAM;IACd,IAAI,CAACoB,UAAU,IAAI,CAACP,OAAO,EAAE;IAC7B,MAAMgB,GAAG,GAAGxB,kBAAkB,CAACU,UAAU,CAAC;IAC1C;IACA;IACAZ,YAAY,CAACsB,SAAS,CAACF,SAAS,CAACC,OAAO,CAAC;IACzC,OAAOK,GAAG;IACV;EACF,CAAC,EAAE,CAACT,UAAU,EAAEP,OAAO,CAAC,CAAC;EAEzB,OAAO;IAAEC,OAAO;IAAEE,OAAO;IAAEE,WAAW;IAAEE,UAAU;IAAEC;EAAY,CAAC;AACnE","ignoreList":[]}
|
|
@@ -7,6 +7,16 @@ export interface UseCompassOptions {
|
|
|
7
7
|
* the library — last-write-wins.
|
|
8
8
|
*/
|
|
9
9
|
filterDegrees?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Low-pass smoothing factor (EMA α) applied to heading samples.
|
|
12
|
+
* Range `(0, 1]`. Default `0.2` ≈ 100ms time constant at typical
|
|
13
|
+
* Android sample rates. `1.0` disables smoothing. Smaller values
|
|
14
|
+
* smooth more (kills jitter, adds a touch of latency).
|
|
15
|
+
*
|
|
16
|
+
* No-op on iOS — CLLocationManager filters internally.
|
|
17
|
+
* Shared global state — last-write-wins.
|
|
18
|
+
*/
|
|
19
|
+
smoothingAlpha?: number;
|
|
10
20
|
/**
|
|
11
21
|
* Magnetic-to-true offset in signed degrees. Default `0` (magnetic).
|
|
12
22
|
* Pull from a model like `geomagnetism` keyed on the user's lat/lon.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../../../src/hook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,iBAAiB,EAClB,MAAM,4BAA4B,CAAA;AAQnC,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,OAAO,EAAE,aAAa,GAAG,IAAI,CAAA;IAC7B,oEAAoE;IACpE,OAAO,EAAE,eAAe,GAAG,IAAI,CAAA;IAC/B,oEAAoE;IACpE,WAAW,EAAE,OAAO,CAAA;IACpB,yDAAyD;IACzD,UAAU,EAAE,OAAO,CAAA;IACnB,sDAAsD;IACtD,WAAW,EAAE,iBAAiB,GAAG,SAAS,CAAA;CAC3C;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,
|
|
1
|
+
{"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../../../src/hook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,iBAAiB,EAClB,MAAM,4BAA4B,CAAA;AAQnC,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,OAAO,EAAE,aAAa,GAAG,IAAI,CAAA;IAC7B,oEAAoE;IACpE,OAAO,EAAE,eAAe,GAAG,IAAI,CAAA;IAC/B,oEAAoE;IACpE,WAAW,EAAE,OAAO,CAAA;IACpB,yDAAyD;IACzD,UAAU,EAAE,OAAO,CAAA;IACnB,sDAAsD;IACtD,WAAW,EAAE,iBAAiB,GAAG,SAAS,CAAA;CAC3C;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CA2DlB"}
|
|
@@ -95,6 +95,22 @@ export interface NitroCompass extends HybridObject<{
|
|
|
95
95
|
* effect until `start()` is called.
|
|
96
96
|
*/
|
|
97
97
|
setFilter(degrees: number): void;
|
|
98
|
+
/**
|
|
99
|
+
* Set the low-pass smoothing factor (EMA α) applied to heading samples
|
|
100
|
+
* before delivery. Range `(0, 1]`. Default `0.2` ≈ 100ms time constant
|
|
101
|
+
* at Android's typical 50 Hz sample rate.
|
|
102
|
+
*
|
|
103
|
+
* - `1.0` disables smoothing (every sample passes through unfiltered).
|
|
104
|
+
* - Smaller values smooth more — eliminates rotation-vector jitter at
|
|
105
|
+
* the cost of a small amount of latency.
|
|
106
|
+
*
|
|
107
|
+
* Implemented as a circular EMA on `(sin θ, cos θ)` so the 359°→0°
|
|
108
|
+
* wraparound doesn't bias the average. Survives `start`/`stop`.
|
|
109
|
+
*
|
|
110
|
+
* **No-op on iOS.** `CLLocationManager` filters heading internally with
|
|
111
|
+
* Apple's own algorithm; layering EMA on top would only add latency.
|
|
112
|
+
*/
|
|
113
|
+
setSmoothing(alpha: number): void;
|
|
98
114
|
/**
|
|
99
115
|
* Describe which underlying sensor / framework would produce headings on
|
|
100
116
|
* this device. Returns `undefined` if the device has no compass hardware
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NitroCompass.nitro.d.ts","sourceRoot":"","sources":["../../../../src/specs/NitroCompass.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAE9D;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,YAAY,CAAA;AAEtE;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,UAAU,GAClB,gBAAgB,GAChB,2BAA2B,GAC3B,cAAc,CAAA;AAElB,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,UAAU,CAAA;CACnB;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAA;AAE/D;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,YAAa,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;IACrF;;;;;;;;;;OAUG;IACH,KAAK,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI,CAAA;IAE9E,8GAA8G;IAC9G,IAAI,IAAI,IAAI,CAAA;IAEZ,qEAAqE;IACrE,SAAS,IAAI,OAAO,CAAA;IAEpB;;;;OAIG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAEhC;;;;OAIG;IACH,cAAc,IAAI,iBAAiB,GAAG,SAAS,CAAA;IAE/C;;;;OAIG;IACH,UAAU,IAAI,OAAO,CAAA;IAErB;;;;OAIG;IACH,iBAAiB,IAAI,aAAa,GAAG,SAAS,CAAA;IAE9C;;;;;OAKG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAErC;;;;OAIG;IACH,sBAAsB,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI,CAAA;IAE1E;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,yBAAyB,CAAC,QAAQ,EAAE,CAAC,oBAAoB,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CAAA;IAElF;;;;;;;OAOG;IACH,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IAE5C;;;;OAIG;IACH,mBAAmB,IAAI,gBAAgB,CAAA;IAEvC;;;;;;;OAOG;IACH,iBAAiB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAA;CAC/C"}
|
|
1
|
+
{"version":3,"file":"NitroCompass.nitro.d.ts","sourceRoot":"","sources":["../../../../src/specs/NitroCompass.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAE9D;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,YAAY,CAAA;AAEtE;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,UAAU,GAClB,gBAAgB,GAChB,2BAA2B,GAC3B,cAAc,CAAA;AAElB,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,UAAU,CAAA;CACnB;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAA;AAE/D;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,YAAa,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;IACrF;;;;;;;;;;OAUG;IACH,KAAK,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI,CAAA;IAE9E,8GAA8G;IAC9G,IAAI,IAAI,IAAI,CAAA;IAEZ,qEAAqE;IACrE,SAAS,IAAI,OAAO,CAAA;IAEpB;;;;OAIG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAEhC;;;;;;;;;;;;;;OAcG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAEjC;;;;OAIG;IACH,cAAc,IAAI,iBAAiB,GAAG,SAAS,CAAA;IAE/C;;;;OAIG;IACH,UAAU,IAAI,OAAO,CAAA;IAErB;;;;OAIG;IACH,iBAAiB,IAAI,aAAa,GAAG,SAAS,CAAA;IAE9C;;;;;OAKG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAErC;;;;OAIG;IACH,sBAAsB,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI,CAAA;IAE1E;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,yBAAyB,CAAC,QAAQ,EAAE,CAAC,oBAAoB,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CAAA;IAElF;;;;;;;OAOG;IACH,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IAE5C;;;;OAIG;IACH,mBAAmB,IAAI,gBAAgB,CAAA;IAEvC;;;;;;;OAOG;IACH,iBAAiB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAA;CAC/C"}
|
|
@@ -87,6 +87,10 @@ namespace margelo::nitro::nitrocompass {
|
|
|
87
87
|
static const auto method = _javaPart->javaClassStatic()->getMethod<void(double /* degrees */)>("setFilter");
|
|
88
88
|
method(_javaPart, degrees);
|
|
89
89
|
}
|
|
90
|
+
void JHybridNitroCompassSpec::setSmoothing(double alpha) {
|
|
91
|
+
static const auto method = _javaPart->javaClassStatic()->getMethod<void(double /* alpha */)>("setSmoothing");
|
|
92
|
+
method(_javaPart, alpha);
|
|
93
|
+
}
|
|
90
94
|
std::optional<SensorDiagnostics> JHybridNitroCompassSpec::getDiagnostics() {
|
|
91
95
|
static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JSensorDiagnostics>()>("getDiagnostics");
|
|
92
96
|
auto __result = method(_javaPart);
|
|
@@ -58,6 +58,7 @@ namespace margelo::nitro::nitrocompass {
|
|
|
58
58
|
void stop() override;
|
|
59
59
|
bool isStarted() override;
|
|
60
60
|
void setFilter(double degrees) override;
|
|
61
|
+
void setSmoothing(double alpha) override;
|
|
61
62
|
std::optional<SensorDiagnostics> getDiagnostics() override;
|
|
62
63
|
bool hasCompass() override;
|
|
63
64
|
std::optional<CompassSample> getCurrentHeading() override;
|
package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/HybridNitroCompassSpec.kt
CHANGED
|
@@ -50,6 +50,10 @@ abstract class HybridNitroCompassSpec: HybridObject() {
|
|
|
50
50
|
@Keep
|
|
51
51
|
abstract fun setFilter(degrees: Double): Unit
|
|
52
52
|
|
|
53
|
+
@DoNotStrip
|
|
54
|
+
@Keep
|
|
55
|
+
abstract fun setSmoothing(alpha: Double): Unit
|
|
56
|
+
|
|
53
57
|
@DoNotStrip
|
|
54
58
|
@Keep
|
|
55
59
|
abstract fun getDiagnostics(): SensorDiagnostics?
|
|
@@ -108,6 +108,12 @@ namespace margelo::nitro::nitrocompass {
|
|
|
108
108
|
std::rethrow_exception(__result.error());
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
+
inline void setSmoothing(double alpha) override {
|
|
112
|
+
auto __result = _swiftPart.setSmoothing(std::forward<decltype(alpha)>(alpha));
|
|
113
|
+
if (__result.hasError()) [[unlikely]] {
|
|
114
|
+
std::rethrow_exception(__result.error());
|
|
115
|
+
}
|
|
116
|
+
}
|
|
111
117
|
inline std::optional<SensorDiagnostics> getDiagnostics() override {
|
|
112
118
|
auto __result = _swiftPart.getDiagnostics();
|
|
113
119
|
if (__result.hasError()) [[unlikely]] {
|
|
@@ -17,6 +17,7 @@ public protocol HybridNitroCompassSpec_protocol: HybridObject {
|
|
|
17
17
|
func stop() throws -> Void
|
|
18
18
|
func isStarted() throws -> Bool
|
|
19
19
|
func setFilter(degrees: Double) throws -> Void
|
|
20
|
+
func setSmoothing(alpha: Double) throws -> Void
|
|
20
21
|
func getDiagnostics() throws -> SensorDiagnostics?
|
|
21
22
|
func hasCompass() throws -> Bool
|
|
22
23
|
func getCurrentHeading() throws -> CompassSample?
|
|
@@ -174,6 +174,17 @@ open class HybridNitroCompassSpec_cxx {
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
@inline(__always)
|
|
178
|
+
public final func setSmoothing(alpha: Double) -> bridge.Result_void_ {
|
|
179
|
+
do {
|
|
180
|
+
try self.__implementation.setSmoothing(alpha: alpha)
|
|
181
|
+
return bridge.create_Result_void_()
|
|
182
|
+
} catch (let __error) {
|
|
183
|
+
let __exceptionPtr = __error.toCpp()
|
|
184
|
+
return bridge.create_Result_void_(__exceptionPtr)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
177
188
|
@inline(__always)
|
|
178
189
|
public final func getDiagnostics() -> bridge.Result_std__optional_SensorDiagnostics__ {
|
|
179
190
|
do {
|
|
@@ -18,6 +18,7 @@ namespace margelo::nitro::nitrocompass {
|
|
|
18
18
|
prototype.registerHybridMethod("stop", &HybridNitroCompassSpec::stop);
|
|
19
19
|
prototype.registerHybridMethod("isStarted", &HybridNitroCompassSpec::isStarted);
|
|
20
20
|
prototype.registerHybridMethod("setFilter", &HybridNitroCompassSpec::setFilter);
|
|
21
|
+
prototype.registerHybridMethod("setSmoothing", &HybridNitroCompassSpec::setSmoothing);
|
|
21
22
|
prototype.registerHybridMethod("getDiagnostics", &HybridNitroCompassSpec::getDiagnostics);
|
|
22
23
|
prototype.registerHybridMethod("hasCompass", &HybridNitroCompassSpec::hasCompass);
|
|
23
24
|
prototype.registerHybridMethod("getCurrentHeading", &HybridNitroCompassSpec::getCurrentHeading);
|
|
@@ -65,6 +65,7 @@ namespace margelo::nitro::nitrocompass {
|
|
|
65
65
|
virtual void stop() = 0;
|
|
66
66
|
virtual bool isStarted() = 0;
|
|
67
67
|
virtual void setFilter(double degrees) = 0;
|
|
68
|
+
virtual void setSmoothing(double alpha) = 0;
|
|
68
69
|
virtual std::optional<SensorDiagnostics> getDiagnostics() = 0;
|
|
69
70
|
virtual bool hasCompass() = 0;
|
|
70
71
|
virtual std::optional<CompassSample> getCurrentHeading() = 0;
|
package/package.json
CHANGED
package/src/hook.ts
CHANGED
|
@@ -19,6 +19,16 @@ export interface UseCompassOptions {
|
|
|
19
19
|
* the library — last-write-wins.
|
|
20
20
|
*/
|
|
21
21
|
filterDegrees?: number
|
|
22
|
+
/**
|
|
23
|
+
* Low-pass smoothing factor (EMA α) applied to heading samples.
|
|
24
|
+
* Range `(0, 1]`. Default `0.2` ≈ 100ms time constant at typical
|
|
25
|
+
* Android sample rates. `1.0` disables smoothing. Smaller values
|
|
26
|
+
* smooth more (kills jitter, adds a touch of latency).
|
|
27
|
+
*
|
|
28
|
+
* No-op on iOS — CLLocationManager filters internally.
|
|
29
|
+
* Shared global state — last-write-wins.
|
|
30
|
+
*/
|
|
31
|
+
smoothingAlpha?: number
|
|
22
32
|
/**
|
|
23
33
|
* Magnetic-to-true offset in signed degrees. Default `0` (magnetic).
|
|
24
34
|
* Pull from a model like `geomagnetism` keyed on the user's lat/lon.
|
|
@@ -64,6 +74,7 @@ export function useCompass(
|
|
|
64
74
|
): UseCompassResult {
|
|
65
75
|
const {
|
|
66
76
|
filterDegrees = 1,
|
|
77
|
+
smoothingAlpha = 0.2,
|
|
67
78
|
declination = 0,
|
|
68
79
|
pauseOnBackground = true,
|
|
69
80
|
enabled = true,
|
|
@@ -86,6 +97,10 @@ export function useCompass(
|
|
|
86
97
|
NitroCompass.setFilter(filterDegrees)
|
|
87
98
|
}, [filterDegrees])
|
|
88
99
|
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
NitroCompass.setSmoothing(smoothingAlpha)
|
|
102
|
+
}, [smoothingAlpha])
|
|
103
|
+
|
|
89
104
|
useEffect(() => {
|
|
90
105
|
NitroCompass.setDeclination(declination)
|
|
91
106
|
}, [declination])
|
|
@@ -105,6 +105,23 @@ export interface NitroCompass extends HybridObject<{ ios: 'swift'; android: 'kot
|
|
|
105
105
|
*/
|
|
106
106
|
setFilter(degrees: number): void
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Set the low-pass smoothing factor (EMA α) applied to heading samples
|
|
110
|
+
* before delivery. Range `(0, 1]`. Default `0.2` ≈ 100ms time constant
|
|
111
|
+
* at Android's typical 50 Hz sample rate.
|
|
112
|
+
*
|
|
113
|
+
* - `1.0` disables smoothing (every sample passes through unfiltered).
|
|
114
|
+
* - Smaller values smooth more — eliminates rotation-vector jitter at
|
|
115
|
+
* the cost of a small amount of latency.
|
|
116
|
+
*
|
|
117
|
+
* Implemented as a circular EMA on `(sin θ, cos θ)` so the 359°→0°
|
|
118
|
+
* wraparound doesn't bias the average. Survives `start`/`stop`.
|
|
119
|
+
*
|
|
120
|
+
* **No-op on iOS.** `CLLocationManager` filters heading internally with
|
|
121
|
+
* Apple's own algorithm; layering EMA on top would only add latency.
|
|
122
|
+
*/
|
|
123
|
+
setSmoothing(alpha: number): void
|
|
124
|
+
|
|
108
125
|
/**
|
|
109
126
|
* Describe which underlying sensor / framework would produce headings on
|
|
110
127
|
* this device. Returns `undefined` if the device has no compass hardware
|