capacitor-motioncal 0.0.3 → 0.0.5
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 +316 -70
- package/android/CMakeLists.txt +29 -0
- package/android/build.gradle +4 -4
- package/android/src/main/java/com/denizak/motioncalibration/{MotionCalibrationPlugin.java → MotionCalibration.java} +16 -45
- package/android/src/main/jni/jni_bridge.c +197 -0
- package/common/imuread.h +1 -15
- package/common/rawdata.c +8 -11
- package/common/serialdata.c +0 -59
- package/dist/docs.json +7 -68
- package/dist/esm/definitions.d.ts +11 -29
- package/dist/esm/web.d.ts +2 -13
- package/dist/esm/web.js +1 -10
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +1 -10
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +1 -10
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/MotionCalibrationPlugin/MotionCalibrationPlugin.swift +7 -43
- package/package.json +3 -2
- package/android/src/main/jniLibs/arm64-v8a/libmotioncalibration.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libmotioncalibration.so +0 -0
- package/android/src/main/jniLibs/x86/libmotioncalibration.so +0 -0
- package/android/src/main/jniLibs/x86_64/libmotioncalibration.so +0 -0
- package/common/motioncalibration.c +0 -7
- package/common/motioncalibration.h +0 -12
package/README.md
CHANGED
|
@@ -1,126 +1,372 @@
|
|
|
1
1
|
# Capacitor MotionCal Plugin
|
|
2
2
|
|
|
3
|
-
A Capacitor plugin for
|
|
3
|
+
A Capacitor plugin for real-time magnetometer calibration using the Freescale/NXP MotionCal algorithm (ported from [PaulStoffregen/MotionCal](https://github.com/PaulStoffregen/MotionCal)). The plugin accepts raw IMU sensor readings and computes hard-iron offset, soft-iron matrix, and geomagnetic field magnitude — the three values needed to correct a magnetometer heading.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Ionic app (sensor events)
|
|
9
|
+
│
|
|
10
|
+
▼ rawData({ data: [9 int16 counts] }) — every sensor sample
|
|
11
|
+
MotionCal plugin ──► C calibration engine
|
|
12
|
+
│
|
|
13
|
+
▼ isSendCalAvailable() — poll until quality is good
|
|
14
|
+
│
|
|
15
|
+
▼ sendCalibration() — finalise
|
|
16
|
+
│
|
|
17
|
+
▼ getHardIronOffset() / getSoftIronMatrix() — apply to compass readings
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The calibration engine accumulates magnetic field samples in a sphere-fitting buffer. Once the sphere is well-covered (quality metrics below threshold), `isSendCalAvailable()` returns `1` and you can call `sendCalibration()` to lock in the result.
|
|
21
|
+
|
|
22
|
+
---
|
|
4
23
|
|
|
5
24
|
## Installation
|
|
6
25
|
|
|
7
|
-
|
|
26
|
+
### From npm (after publish)
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install capacitor-motioncal
|
|
30
|
+
npx cap sync
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### From GitHub
|
|
8
34
|
|
|
9
35
|
```bash
|
|
10
36
|
npm install github:denizak/motioncal-capacitor
|
|
11
37
|
npx cap sync
|
|
12
38
|
```
|
|
13
39
|
|
|
14
|
-
|
|
40
|
+
### Local path install (for testing before publish — see [Testing](#testing-locally-before-publishing))
|
|
15
41
|
|
|
16
42
|
```bash
|
|
17
|
-
|
|
18
|
-
npm install ./capacitor-motioncal
|
|
43
|
+
npm install ../path/to/Capacitor-MotionCal
|
|
19
44
|
npx cap sync
|
|
20
45
|
```
|
|
21
46
|
|
|
22
|
-
|
|
47
|
+
---
|
|
23
48
|
|
|
24
|
-
|
|
49
|
+
## Ionic / Angular Integration
|
|
25
50
|
|
|
26
|
-
|
|
51
|
+
### 1. Install and sync
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install capacitor-motioncal
|
|
55
|
+
npx cap sync
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 2. Create a calibration service
|
|
59
|
+
|
|
60
|
+
Create `src/app/services/motioncal.service.ts`:
|
|
27
61
|
|
|
28
62
|
```typescript
|
|
63
|
+
import { Injectable } from '@angular/core';
|
|
29
64
|
import { MotionCalibration } from 'capacitor-motioncal';
|
|
30
65
|
|
|
31
|
-
|
|
66
|
+
export interface CalibrationResult {
|
|
67
|
+
hardIronOffset: number[]; // [x, y, z] in µT
|
|
68
|
+
softIronMatrix: number[][]; // 3×3 correction matrix
|
|
69
|
+
fieldMagnitude: number; // geomagnetic field strength in µT
|
|
70
|
+
drawPoints: number[][]; // collected sphere points for visualisation
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Injectable({ providedIn: 'root' })
|
|
74
|
+
export class MotionCalService {
|
|
75
|
+
|
|
76
|
+
/** Feed one IMU sample into the calibration engine.
|
|
77
|
+
*
|
|
78
|
+
* All values must be converted to int16 raw counts using:
|
|
79
|
+
* Accelerometer: multiply g × 8192 (G_PER_COUNT = 1/8192)
|
|
80
|
+
* Gyroscope: multiply deg/s × 16 (DEG_PER_SEC_PER_COUNT = 1/16)
|
|
81
|
+
* Magnetometer: multiply µT × 10 (UT_PER_COUNT = 0.1)
|
|
82
|
+
*/
|
|
83
|
+
async feedSample(
|
|
84
|
+
ax: number, ay: number, az: number, // accelerometer in g
|
|
85
|
+
gx: number, gy: number, gz: number, // gyroscope in deg/s
|
|
86
|
+
mx: number, my: number, mz: number, // magnetometer in µT
|
|
87
|
+
): Promise<void> {
|
|
88
|
+
await MotionCalibration.rawData({
|
|
89
|
+
data: [
|
|
90
|
+
Math.round(ax * 8192), Math.round(ay * 8192), Math.round(az * 8192),
|
|
91
|
+
Math.round(gx * 16), Math.round(gy * 16), Math.round(gz * 16),
|
|
92
|
+
Math.round(mx * 10), Math.round(my * 10), Math.round(mz * 10),
|
|
93
|
+
],
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Returns true when the algorithm has enough data for a good calibration. */
|
|
98
|
+
async isReady(): Promise<boolean> {
|
|
99
|
+
const { available } = await MotionCalibration.isSendCalAvailable();
|
|
100
|
+
return available === 1;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Returns quality error values — lower is better.
|
|
104
|
+
* isSendCalAvailable() already gates on these, but you can surface them in UI. */
|
|
105
|
+
async getQuality() {
|
|
106
|
+
const [gap, variance, wobble, fit] = await Promise.all([
|
|
107
|
+
MotionCalibration.getQualitySurfaceGapError(),
|
|
108
|
+
MotionCalibration.getQualityMagnitudeVarianceError(),
|
|
109
|
+
MotionCalibration.getQualityWobbleError(),
|
|
110
|
+
MotionCalibration.getQualitySphericalFitError(),
|
|
111
|
+
]);
|
|
112
|
+
return {
|
|
113
|
+
surfaceGap: gap.error,
|
|
114
|
+
magnitudeVariance: variance.error,
|
|
115
|
+
wobble: wobble.error,
|
|
116
|
+
sphericalFit: fit.error,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Finalise calibration and return all results. Call only after isReady() === true. */
|
|
121
|
+
async finalise(): Promise<CalibrationResult> {
|
|
122
|
+
await MotionCalibration.sendCalibration();
|
|
123
|
+
|
|
124
|
+
const [offset, matrix, magnitude, points] = await Promise.all([
|
|
125
|
+
MotionCalibration.getHardIronOffset(),
|
|
126
|
+
MotionCalibration.getSoftIronMatrix(),
|
|
127
|
+
MotionCalibration.getGeomagneticFieldMagnitude(),
|
|
128
|
+
MotionCalibration.getDrawPoints(),
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
hardIronOffset: offset.offset,
|
|
133
|
+
softIronMatrix: matrix.matrix,
|
|
134
|
+
fieldMagnitude: magnitude.magnitude,
|
|
135
|
+
drawPoints: points.points,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Reset the engine to start a new calibration session. */
|
|
140
|
+
async reset(): Promise<void> {
|
|
141
|
+
await MotionCalibration.resetRawData();
|
|
142
|
+
await MotionCalibration.clearDrawPoints();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
32
145
|
```
|
|
33
146
|
|
|
34
|
-
###
|
|
147
|
+
### 3. Use the service in a component
|
|
35
148
|
|
|
36
|
-
|
|
149
|
+
```typescript
|
|
150
|
+
import { Component, OnDestroy } from '@angular/core';
|
|
151
|
+
import { MotionCalService, CalibrationResult } from '../services/motioncal.service';
|
|
152
|
+
|
|
153
|
+
// Use @capacitor/motion or the Web DeviceMotion API to obtain sensor events.
|
|
154
|
+
// The plugin also integrates with @awesome-cordova-plugins/device-motion if needed.
|
|
155
|
+
|
|
156
|
+
@Component({
|
|
157
|
+
selector: 'app-calibration',
|
|
158
|
+
template: `
|
|
159
|
+
<ion-content>
|
|
160
|
+
<ion-button (click)="start()" [disabled]="running">Start Calibration</ion-button>
|
|
161
|
+
<ion-button (click)="stop()" [disabled]="!running">Stop</ion-button>
|
|
162
|
+
|
|
163
|
+
<div *ngIf="running">
|
|
164
|
+
<p>Quality — gap: {{ quality?.surfaceGap | number:'1.3-3' }}
|
|
165
|
+
wobble: {{ quality?.wobble | number:'1.3-3' }}</p>
|
|
166
|
+
<p *ngIf="ready">✓ Ready — tap Stop to finalise</p>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<div *ngIf="result">
|
|
170
|
+
<h3>Calibration Complete</h3>
|
|
171
|
+
<p>Hard iron offset: {{ result.hardIronOffset | json }}</p>
|
|
172
|
+
<p>Field magnitude: {{ result.fieldMagnitude | number:'1.2-2' }} µT</p>
|
|
173
|
+
</div>
|
|
174
|
+
</ion-content>
|
|
175
|
+
`,
|
|
176
|
+
})
|
|
177
|
+
export class CalibrationPage implements OnDestroy {
|
|
178
|
+
running = false;
|
|
179
|
+
ready = false;
|
|
180
|
+
quality: Awaited<ReturnType<MotionCalService['getQuality']>> | null = null;
|
|
181
|
+
result: CalibrationResult | null = null;
|
|
182
|
+
|
|
183
|
+
private intervalId: ReturnType<typeof setInterval> | null = null;
|
|
184
|
+
|
|
185
|
+
constructor(private cal: MotionCalService) {}
|
|
186
|
+
|
|
187
|
+
async start() {
|
|
188
|
+
await this.cal.reset();
|
|
189
|
+
this.running = true;
|
|
190
|
+
this.result = null;
|
|
191
|
+
|
|
192
|
+
// Subscribe to your sensor source here.
|
|
193
|
+
// Example using window.addEventListener for DeviceMotion + DeviceOrientation:
|
|
194
|
+
window.addEventListener('devicemotion', this.onMotion);
|
|
195
|
+
window.addEventListener('deviceorientation', this.onOrientation);
|
|
196
|
+
|
|
197
|
+
// Poll quality every 500 ms for UI feedback.
|
|
198
|
+
this.intervalId = setInterval(async () => {
|
|
199
|
+
this.quality = await this.cal.getQuality();
|
|
200
|
+
this.ready = await this.cal.isReady();
|
|
201
|
+
}, 500);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async stop() {
|
|
205
|
+
this.running = false;
|
|
206
|
+
window.removeEventListener('devicemotion', this.onMotion);
|
|
207
|
+
window.removeEventListener('deviceorientation', this.onOrientation);
|
|
208
|
+
if (this.intervalId) clearInterval(this.intervalId);
|
|
209
|
+
|
|
210
|
+
if (this.ready) {
|
|
211
|
+
this.result = await this.cal.finalise();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Replace with actual sensor values from your sensor plugin/service.
|
|
216
|
+
private onMotion = async (e: DeviceMotionEvent) => {
|
|
217
|
+
const a = e.accelerationIncludingGravity;
|
|
218
|
+
const r = e.rotationRate;
|
|
219
|
+
if (!a || !r) return;
|
|
220
|
+
// Magnetometer must come from a separate sensor source (e.g. @capacitor/motion
|
|
221
|
+
// does not expose magnetometer — use a dedicated plugin or native bridge).
|
|
222
|
+
// Here mx/my/mz are placeholders; replace with real values.
|
|
223
|
+
await this.cal.feedSample(
|
|
224
|
+
a.x ?? 0, a.y ?? 0, a.z ?? 0,
|
|
225
|
+
r.alpha ?? 0, r.beta ?? 0, r.gamma ?? 0,
|
|
226
|
+
0, 0, 0, // ← replace with real magnetometer µT values
|
|
227
|
+
);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
private onOrientation = (_e: DeviceOrientationEvent) => { /* optional */ };
|
|
231
|
+
|
|
232
|
+
ngOnDestroy() { this.stop(); }
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### 4. Apply calibration to compass readings
|
|
37
237
|
|
|
38
238
|
```typescript
|
|
39
|
-
|
|
40
|
-
|
|
239
|
+
// After calibration is done, correct raw magnetometer readings before computing heading:
|
|
240
|
+
function applyCalibration(
|
|
241
|
+
rawMag: [number, number, number],
|
|
242
|
+
hardIron: number[],
|
|
243
|
+
softIron: number[][],
|
|
244
|
+
): [number, number, number] {
|
|
245
|
+
// Step 1: subtract hard-iron offset
|
|
246
|
+
const hx = rawMag[0] - hardIron[0];
|
|
247
|
+
const hy = rawMag[1] - hardIron[1];
|
|
248
|
+
const hz = rawMag[2] - hardIron[2];
|
|
249
|
+
|
|
250
|
+
// Step 2: multiply by soft-iron matrix
|
|
251
|
+
return [
|
|
252
|
+
softIron[0][0]*hx + softIron[0][1]*hy + softIron[0][2]*hz,
|
|
253
|
+
softIron[1][0]*hx + softIron[1][1]*hy + softIron[1][2]*hz,
|
|
254
|
+
softIron[2][0]*hx + softIron[2][1]*hy + softIron[2][2]*hz,
|
|
255
|
+
];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function headingDegrees(mx: number, my: number): number {
|
|
259
|
+
return (Math.atan2(my, mx) * 180 / Math.PI + 360) % 360;
|
|
260
|
+
}
|
|
41
261
|
```
|
|
42
262
|
|
|
43
|
-
|
|
263
|
+
---
|
|
44
264
|
|
|
45
|
-
|
|
265
|
+
## Testing Locally Before Publishing
|
|
46
266
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
267
|
+
### Step 1 — Build the plugin
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
cd /path/to/Capacitor-MotionCal
|
|
271
|
+
npm run build
|
|
50
272
|
```
|
|
51
273
|
|
|
52
|
-
|
|
274
|
+
Verify `dist/` was generated with no TypeScript errors.
|
|
53
275
|
|
|
54
|
-
|
|
276
|
+
### Step 2 — Install into the Ionic app via local path
|
|
55
277
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
278
|
+
In your Ionic app directory:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
npm install /path/to/Capacitor-MotionCal
|
|
282
|
+
npx cap sync
|
|
59
283
|
```
|
|
60
284
|
|
|
61
|
-
|
|
285
|
+
This installs the exact local build. You can iterate on the plugin and re-run `npm run build` + `npx cap sync` to pick up changes without publishing.
|
|
62
286
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
287
|
+
### Step 3 — Verify the iOS build compiles
|
|
288
|
+
|
|
289
|
+
Back in the plugin directory:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
npm run verify:ios
|
|
68
293
|
```
|
|
69
294
|
|
|
70
|
-
|
|
295
|
+
This runs `pod install` + `xcodebuild` for the iOS target. Fix any Swift/C compile errors before proceeding.
|
|
71
296
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
297
|
+
### Step 4 — Verify the Android build compiles
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
npm run verify:android
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Runs `./gradlew clean build test`. Requires the Android SDK in your `$PATH` / `$ANDROID_HOME`.
|
|
304
|
+
|
|
305
|
+
### Step 5 — Smoke test on a real device
|
|
76
306
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
307
|
+
1. Open the Ionic app in Xcode (`npx cap open ios`) or Android Studio (`npx cap open android`).
|
|
308
|
+
2. Run on a physical device (simulators have no magnetometer).
|
|
309
|
+
3. With the app open, wave/rotate the device in a figure-8 pattern to ensure `rawData` calls are being accepted and `isSendCalAvailable` eventually returns `1`.
|
|
310
|
+
4. Call `sendCalibration()` and log the results — `hardIronOffset` should be non-zero and `fieldMagnitude` should be in the range 20–80 µT (typical for Earth's field).
|
|
80
311
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
312
|
+
### Step 6 — Pack and inspect what will be published
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
npm pack --dry-run
|
|
84
316
|
```
|
|
85
317
|
|
|
86
|
-
|
|
318
|
+
This lists all files that will be included in the npm package. Verify:
|
|
319
|
+
- `dist/` is present
|
|
320
|
+
- `android/src/main/` is present
|
|
321
|
+
- `ios/Sources/` is present
|
|
322
|
+
- `common/` (C source files) is present
|
|
323
|
+
- No test files, node_modules, or local config bleed in
|
|
87
324
|
|
|
88
|
-
|
|
89
|
-
// Get points for 3D visualization
|
|
90
|
-
const points = await MotionCalibration.getDrawPoints();
|
|
91
|
-
console.log('Draw points:', points.points);
|
|
325
|
+
To create the actual tarball for manual inspection:
|
|
92
326
|
|
|
93
|
-
|
|
94
|
-
|
|
327
|
+
```bash
|
|
328
|
+
npm pack
|
|
329
|
+
# creates capacitor-motioncal-x.x.x.tgz
|
|
330
|
+
tar -tzf capacitor-motioncal-*.tgz | sort
|
|
95
331
|
```
|
|
96
332
|
|
|
97
|
-
###
|
|
333
|
+
### Step 7 — Publish
|
|
98
334
|
|
|
99
|
-
```
|
|
100
|
-
|
|
335
|
+
```bash
|
|
336
|
+
npm login # one-time
|
|
337
|
+
npm publish
|
|
101
338
|
```
|
|
102
339
|
|
|
340
|
+
For a scoped package use `npm publish --access public`.
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
103
344
|
## Full API Reference
|
|
104
345
|
|
|
105
|
-
| Method | Parameters | Returns |
|
|
106
|
-
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `getDrawPoints` |
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
346
|
+
| Method | Parameters | Returns | Notes |
|
|
347
|
+
|--------|------------|---------|-------|
|
|
348
|
+
| `rawData` | `{ data: number[] }` | `Promise<void>` | 9 int16 counts; call every sensor sample |
|
|
349
|
+
| `isSendCalAvailable` | — | `Promise<{ available: number }>` | `1` = ready to finalise |
|
|
350
|
+
| `sendCalibration` | — | `Promise<{ result: number }>` | Finalises calibration; call after `isSendCalAvailable` |
|
|
351
|
+
| `getHardIronOffset` | — | `Promise<{ offset: number[] }>` | [x, y, z] bias in µT |
|
|
352
|
+
| `getSoftIronMatrix` | — | `Promise<{ matrix: number[][] }>` | 3×3 scaling/rotation matrix |
|
|
353
|
+
| `getGeomagneticFieldMagnitude` | — | `Promise<{ magnitude: number }>` | Earth field strength in µT |
|
|
354
|
+
| `getCalibrationData` | — | `Promise<{ data: string }>` | Raw 68-byte calibration packet as base64 |
|
|
355
|
+
| `getQualitySurfaceGapError` | — | `Promise<{ error: number }>` | Lower is better |
|
|
356
|
+
| `getQualityMagnitudeVarianceError` | — | `Promise<{ error: number }>` | Lower is better |
|
|
357
|
+
| `getQualityWobbleError` | — | `Promise<{ error: number }>` | Lower is better |
|
|
358
|
+
| `getQualitySphericalFitError` | — | `Promise<{ error: number }>` | Lower is better |
|
|
359
|
+
| `displayCallback` | — | `Promise<void>` | Triggers internal visualisation update |
|
|
360
|
+
| `getDrawPoints` | — | `Promise<{ points: number[][] }>` | Sphere surface points for 3D display |
|
|
361
|
+
| `clearDrawPoints` | — | `Promise<void>` | Clears the visualisation buffer |
|
|
362
|
+
| `resetRawData` | — | `Promise<void>` | Resets entire calibration state |
|
|
363
|
+
|
|
364
|
+
### int16 conversion factors
|
|
365
|
+
|
|
366
|
+
| Sensor | Unit | Multiply by | Constant |
|
|
367
|
+
|--------|------|-------------|----------|
|
|
368
|
+
| Accelerometer | g | 8192 | `G_PER_COUNT = 1/8192` |
|
|
369
|
+
| Gyroscope | deg/s | 16 | `DEG_PER_SEC_PER_COUNT = 1/16` |
|
|
370
|
+
| Magnetometer | µT | 10 | `UT_PER_COUNT = 0.1` |
|
|
125
371
|
|
|
126
372
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.22.1)
|
|
2
|
+
project("motioncalibration" C)
|
|
3
|
+
|
|
4
|
+
# Suppress warnings from upstream C code (Freescale/NXP algorithm, not authored here).
|
|
5
|
+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -w")
|
|
6
|
+
|
|
7
|
+
add_library(
|
|
8
|
+
motioncalibration
|
|
9
|
+
SHARED
|
|
10
|
+
|
|
11
|
+
# JNI bridge (Java ↔ C)
|
|
12
|
+
src/main/jni/jni_bridge.c
|
|
13
|
+
|
|
14
|
+
# Algorithm sources (shared with iOS via common/)
|
|
15
|
+
../common/rawdata.c
|
|
16
|
+
../common/serialdata.c
|
|
17
|
+
../common/visualize.c
|
|
18
|
+
../common/magcal.c
|
|
19
|
+
../common/mahony.c
|
|
20
|
+
../common/matrix.c
|
|
21
|
+
../common/quality.c
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Make imuread.h and other common/ headers visible to all C files.
|
|
25
|
+
target_include_directories(motioncalibration PRIVATE ../common)
|
|
26
|
+
|
|
27
|
+
# Android system libraries
|
|
28
|
+
find_library(log-lib log)
|
|
29
|
+
target_link_libraries(motioncalibration ${log-lib} m)
|
package/android/build.gradle
CHANGED
|
@@ -44,10 +44,10 @@ android {
|
|
|
44
44
|
sourceCompatibility JavaVersion.VERSION_17
|
|
45
45
|
targetCompatibility JavaVersion.VERSION_17
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
|
|
48
|
+
externalNativeBuild {
|
|
49
|
+
cmake {
|
|
50
|
+
path "CMakeLists.txt"
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -13,7 +13,7 @@ import org.json.JSONArray;
|
|
|
13
13
|
import org.json.JSONException;
|
|
14
14
|
|
|
15
15
|
@CapacitorPlugin(name = "MotionCalibration")
|
|
16
|
-
public class
|
|
16
|
+
public class MotionCalibration extends Plugin {
|
|
17
17
|
|
|
18
18
|
// Load the native library
|
|
19
19
|
static {
|
|
@@ -21,11 +21,8 @@ public class MotionCalibrationPlugin extends Plugin {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
// Native method declarations
|
|
24
|
-
private native void updateBValueNative(float bValue);
|
|
25
|
-
private native float getBValueNative();
|
|
26
24
|
private native short isSendCalAvailableNative();
|
|
27
|
-
private native
|
|
28
|
-
private native void setResultFilenameNative(String filename);
|
|
25
|
+
private native void rawDataNative(short[] data);
|
|
29
26
|
private native int sendCalibrationNative();
|
|
30
27
|
private native float getQualitySurfaceGapErrorNative();
|
|
31
28
|
private native float getQualityMagnitudeVarianceErrorNative();
|
|
@@ -40,25 +37,6 @@ public class MotionCalibrationPlugin extends Plugin {
|
|
|
40
37
|
private native float getGeomagneticFieldMagnitudeNative();
|
|
41
38
|
private native void clearDrawPointsNative();
|
|
42
39
|
|
|
43
|
-
@PluginMethod
|
|
44
|
-
public void updateBValue(PluginCall call) {
|
|
45
|
-
Float value = call.getFloat("value");
|
|
46
|
-
if (value == null) {
|
|
47
|
-
call.reject("Value is required");
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
updateBValueNative(value);
|
|
51
|
-
call.resolve();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
@PluginMethod
|
|
55
|
-
public void getBValue(PluginCall call) {
|
|
56
|
-
float result = getBValueNative();
|
|
57
|
-
JSObject ret = new JSObject();
|
|
58
|
-
ret.put("value", result);
|
|
59
|
-
call.resolve(ret);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
40
|
@PluginMethod
|
|
63
41
|
public void isSendCalAvailable(PluginCall call) {
|
|
64
42
|
short isAvailable = isSendCalAvailableNative();
|
|
@@ -68,29 +46,22 @@ public class MotionCalibrationPlugin extends Plugin {
|
|
|
68
46
|
}
|
|
69
47
|
|
|
70
48
|
@PluginMethod
|
|
71
|
-
public void
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
call.reject("
|
|
49
|
+
public void rawData(PluginCall call) {
|
|
50
|
+
JSArray dataArray = call.getArray("data");
|
|
51
|
+
if (dataArray == null || dataArray.length() != 9) {
|
|
52
|
+
call.reject("Expected array of 9 numbers");
|
|
75
53
|
return;
|
|
76
54
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
String filename = call.getString("filename");
|
|
87
|
-
if (filename == null) {
|
|
88
|
-
call.reject("Filename is required");
|
|
89
|
-
return;
|
|
55
|
+
try {
|
|
56
|
+
short[] data = new short[9];
|
|
57
|
+
for (int i = 0; i < 9; i++) {
|
|
58
|
+
data[i] = (short) dataArray.getInt(i);
|
|
59
|
+
}
|
|
60
|
+
rawDataNative(data);
|
|
61
|
+
call.resolve();
|
|
62
|
+
} catch (Exception e) {
|
|
63
|
+
call.reject("Failed to process raw data: " + e.getMessage());
|
|
90
64
|
}
|
|
91
|
-
String fullPath = getContext().getFilesDir().getAbsolutePath() + "/" + filename;
|
|
92
|
-
setResultFilenameNative(fullPath);
|
|
93
|
-
call.resolve();
|
|
94
65
|
}
|
|
95
66
|
|
|
96
67
|
@PluginMethod
|
|
@@ -245,4 +216,4 @@ public class MotionCalibrationPlugin extends Plugin {
|
|
|
245
216
|
clearDrawPointsNative();
|
|
246
217
|
call.resolve();
|
|
247
218
|
}
|
|
248
|
-
}
|
|
219
|
+
}
|