community-cordova-plugin-magnetometer 1.0.1

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.
@@ -0,0 +1,524 @@
1
+ package com.community.cordova.magnetometer;
2
+
3
+ import android.content.Context;
4
+ import android.hardware.Sensor;
5
+ import android.hardware.SensorEvent;
6
+ import android.hardware.SensorEventListener;
7
+ import android.hardware.SensorManager;
8
+ import android.os.Handler;
9
+ import android.os.Looper;
10
+ import android.util.Log;
11
+
12
+ import org.apache.cordova.CallbackContext;
13
+ import org.apache.cordova.CordovaPlugin;
14
+ import org.apache.cordova.PluginResult;
15
+ import org.json.JSONArray;
16
+ import org.json.JSONException;
17
+ import org.json.JSONObject;
18
+
19
+ public class Magnetometer extends CordovaPlugin implements SensorEventListener {
20
+
21
+ private static final String LOG_TAG = "Magnetometer";
22
+
23
+ private SensorManager sensorManager;
24
+ private Sensor magnetometer;
25
+ private Sensor rotationVector;
26
+
27
+ private CallbackContext watchCallbackContext;
28
+ private CallbackContext watchHeadingCallbackContext;
29
+
30
+ private float[] magnetometerValues = new float[3];
31
+ private float[] rotationMatrix = new float[9];
32
+ private float[] orientationValues = new float[3];
33
+
34
+ private int currentAccuracy = SensorManager.SENSOR_STATUS_ACCURACY_HIGH;
35
+ private boolean calibrationNeeded = false;
36
+
37
+ private Handler handler;
38
+ private Runnable watchRunnable;
39
+ private Runnable watchHeadingRunnable;
40
+
41
+ @Override
42
+ protected void pluginInitialize() {
43
+ sensorManager = (SensorManager) cordova.getActivity().getSystemService(Context.SENSOR_SERVICE);
44
+ magnetometer = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
45
+ rotationVector = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
46
+ handler = new Handler(Looper.getMainLooper());
47
+ }
48
+
49
+ @Override
50
+ public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
51
+ switch (action) {
52
+ case "isAvailable":
53
+ isAvailable(callbackContext);
54
+ return true;
55
+ case "getReading":
56
+ getReading(callbackContext);
57
+ return true;
58
+ case "getHeading":
59
+ getHeading(callbackContext);
60
+ return true;
61
+ case "watchReadings":
62
+ int frequency = args.optInt(0, 100);
63
+ watchReadings(callbackContext, frequency);
64
+ return true;
65
+ case "stopWatch":
66
+ stopWatch(callbackContext);
67
+ return true;
68
+ case "watchHeading":
69
+ int headingFrequency = args.optInt(0, 100);
70
+ watchHeading(callbackContext, headingFrequency);
71
+ return true;
72
+ case "stopWatchHeading":
73
+ stopWatchHeading(callbackContext);
74
+ return true;
75
+ case "getMagnetometerInfo":
76
+ getMagnetometerInfo(callbackContext);
77
+ return true;
78
+ case "getAccuracy":
79
+ getAccuracy(callbackContext);
80
+ return true;
81
+ case "isCalibrationNeeded":
82
+ isCalibrationNeeded(callbackContext);
83
+ return true;
84
+ case "getFieldStrength":
85
+ getFieldStrength(callbackContext);
86
+ return true;
87
+ default:
88
+ return false;
89
+ }
90
+ }
91
+
92
+ private void isAvailable(CallbackContext callbackContext) {
93
+ boolean available = magnetometer != null;
94
+ callbackContext.success(available ? 1 : 0);
95
+ }
96
+
97
+ private void getReading(final CallbackContext callbackContext) {
98
+ if (magnetometer == null) {
99
+ callbackContext.error("Magnetometer not available");
100
+ return;
101
+ }
102
+
103
+ cordova.getThreadPool().execute(new Runnable() {
104
+ @Override
105
+ public void run() {
106
+ final boolean[] dataReceived = {false};
107
+
108
+ SensorEventListener listener = new SensorEventListener() {
109
+ @Override
110
+ public void onSensorChanged(SensorEvent event) {
111
+ if (!dataReceived[0] && event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
112
+ dataReceived[0] = true;
113
+ sensorManager.unregisterListener(this);
114
+
115
+ try {
116
+ JSONObject reading = createReadingObject(event.values);
117
+ callbackContext.success(reading);
118
+ } catch (JSONException e) {
119
+ callbackContext.error("Failed to create reading: " + e.getMessage());
120
+ }
121
+ }
122
+ }
123
+
124
+ @Override
125
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
126
+ currentAccuracy = accuracy;
127
+ calibrationNeeded = accuracy < SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM;
128
+ }
129
+ };
130
+
131
+ sensorManager.registerListener(listener, magnetometer, SensorManager.SENSOR_DELAY_UI);
132
+
133
+ // Timeout after 1 second
134
+ handler.postDelayed(new Runnable() {
135
+ @Override
136
+ public void run() {
137
+ if (!dataReceived[0]) {
138
+ dataReceived[0] = true;
139
+ callbackContext.error("Timeout waiting for magnetometer reading");
140
+ }
141
+ }
142
+ }, 1000);
143
+ }
144
+ });
145
+ }
146
+
147
+ private void getHeading(final CallbackContext callbackContext) {
148
+ if (magnetometer == null) {
149
+ callbackContext.error("Magnetometer not available");
150
+ return;
151
+ }
152
+
153
+ cordova.getThreadPool().execute(new Runnable() {
154
+ @Override
155
+ public void run() {
156
+ final boolean[] dataReceived = {false};
157
+ final float[] magValues = new float[3];
158
+ final float[] accelValues = new float[3];
159
+ final boolean[] hasMag = {false};
160
+ final boolean[] hasAccel = {false};
161
+
162
+ Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
163
+
164
+ SensorEventListener listener = new SensorEventListener() {
165
+ @Override
166
+ public void onSensorChanged(SensorEvent event) {
167
+ if (dataReceived[0]) return;
168
+
169
+ if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
170
+ System.arraycopy(event.values, 0, magValues, 0, 3);
171
+ hasMag[0] = true;
172
+ } else if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
173
+ System.arraycopy(event.values, 0, accelValues, 0, 3);
174
+ hasAccel[0] = true;
175
+ }
176
+
177
+ if (hasMag[0] && hasAccel[0]) {
178
+ dataReceived[0] = true;
179
+ sensorManager.unregisterListener(this);
180
+
181
+ try {
182
+ JSONObject heading = calculateHeading(magValues, accelValues);
183
+ callbackContext.success(heading);
184
+ } catch (JSONException e) {
185
+ callbackContext.error("Failed to calculate heading: " + e.getMessage());
186
+ }
187
+ }
188
+ }
189
+
190
+ @Override
191
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
192
+ if (sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
193
+ currentAccuracy = accuracy;
194
+ calibrationNeeded = accuracy < SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM;
195
+ }
196
+ }
197
+ };
198
+
199
+ sensorManager.registerListener(listener, magnetometer, SensorManager.SENSOR_DELAY_UI);
200
+ if (accelerometer != null) {
201
+ sensorManager.registerListener(listener, accelerometer, SensorManager.SENSOR_DELAY_UI);
202
+ }
203
+
204
+ // Timeout after 1 second
205
+ handler.postDelayed(new Runnable() {
206
+ @Override
207
+ public void run() {
208
+ if (!dataReceived[0]) {
209
+ dataReceived[0] = true;
210
+ callbackContext.error("Timeout waiting for heading");
211
+ }
212
+ }
213
+ }, 1000);
214
+ }
215
+ });
216
+ }
217
+
218
+ private void watchReadings(CallbackContext callbackContext, final int frequency) {
219
+ if (magnetometer == null) {
220
+ callbackContext.error("Magnetometer not available");
221
+ return;
222
+ }
223
+
224
+ // Stop existing watch
225
+ if (watchCallbackContext != null) {
226
+ sensorManager.unregisterListener(this, magnetometer);
227
+ }
228
+
229
+ watchCallbackContext = callbackContext;
230
+
231
+ int sensorDelay = getSensorDelay(frequency);
232
+ sensorManager.registerListener(this, magnetometer, sensorDelay);
233
+
234
+ PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT);
235
+ result.setKeepCallback(true);
236
+ callbackContext.sendPluginResult(result);
237
+ }
238
+
239
+ private void stopWatch(CallbackContext callbackContext) {
240
+ if (watchCallbackContext != null) {
241
+ sensorManager.unregisterListener(this, magnetometer);
242
+ watchCallbackContext = null;
243
+ }
244
+ callbackContext.success();
245
+ }
246
+
247
+ private void watchHeading(CallbackContext callbackContext, final int frequency) {
248
+ if (magnetometer == null) {
249
+ callbackContext.error("Magnetometer not available");
250
+ return;
251
+ }
252
+
253
+ // Stop existing watch
254
+ if (watchHeadingCallbackContext != null) {
255
+ stopHeadingSensors();
256
+ }
257
+
258
+ watchHeadingCallbackContext = callbackContext;
259
+
260
+ int sensorDelay = getSensorDelay(frequency);
261
+ Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
262
+
263
+ sensorManager.registerListener(headingListener, magnetometer, sensorDelay);
264
+ if (accelerometer != null) {
265
+ sensorManager.registerListener(headingListener, accelerometer, sensorDelay);
266
+ }
267
+
268
+ PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT);
269
+ result.setKeepCallback(true);
270
+ callbackContext.sendPluginResult(result);
271
+ }
272
+
273
+ private void stopWatchHeading(CallbackContext callbackContext) {
274
+ stopHeadingSensors();
275
+ watchHeadingCallbackContext = null;
276
+ callbackContext.success();
277
+ }
278
+
279
+ private void stopHeadingSensors() {
280
+ sensorManager.unregisterListener(headingListener);
281
+ }
282
+
283
+ private final float[] headingMagValues = new float[3];
284
+ private final float[] headingAccelValues = new float[3];
285
+ private boolean headingHasMag = false;
286
+ private boolean headingHasAccel = false;
287
+
288
+ private SensorEventListener headingListener = new SensorEventListener() {
289
+ @Override
290
+ public void onSensorChanged(SensorEvent event) {
291
+ if (watchHeadingCallbackContext == null) return;
292
+
293
+ if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
294
+ System.arraycopy(event.values, 0, headingMagValues, 0, 3);
295
+ headingHasMag = true;
296
+ } else if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
297
+ System.arraycopy(event.values, 0, headingAccelValues, 0, 3);
298
+ headingHasAccel = true;
299
+ }
300
+
301
+ if (headingHasMag && headingHasAccel) {
302
+ try {
303
+ JSONObject heading = calculateHeading(headingMagValues, headingAccelValues);
304
+ PluginResult result = new PluginResult(PluginResult.Status.OK, heading);
305
+ result.setKeepCallback(true);
306
+ watchHeadingCallbackContext.sendPluginResult(result);
307
+ } catch (JSONException e) {
308
+ Log.e(LOG_TAG, "Error calculating heading: " + e.getMessage());
309
+ }
310
+ }
311
+ }
312
+
313
+ @Override
314
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
315
+ if (sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
316
+ currentAccuracy = accuracy;
317
+ calibrationNeeded = accuracy < SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM;
318
+ }
319
+ }
320
+ };
321
+
322
+ private void getMagnetometerInfo(final CallbackContext callbackContext) {
323
+ cordova.getThreadPool().execute(new Runnable() {
324
+ @Override
325
+ public void run() {
326
+ try {
327
+ JSONObject info = new JSONObject();
328
+ info.put("isAvailable", magnetometer != null);
329
+ info.put("accuracy", currentAccuracy);
330
+ info.put("calibrationNeeded", calibrationNeeded);
331
+ info.put("platform", "android");
332
+
333
+ if (magnetometer != null) {
334
+ final boolean[] dataReceived = {false};
335
+ final JSONObject[] readingObj = {null};
336
+
337
+ SensorEventListener listener = new SensorEventListener() {
338
+ @Override
339
+ public void onSensorChanged(SensorEvent event) {
340
+ if (!dataReceived[0] && event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
341
+ dataReceived[0] = true;
342
+ sensorManager.unregisterListener(this);
343
+ try {
344
+ readingObj[0] = createReadingObject(event.values);
345
+ } catch (JSONException e) {
346
+ Log.e(LOG_TAG, "Error creating reading: " + e.getMessage());
347
+ }
348
+ synchronized (readingObj) {
349
+ readingObj.notify();
350
+ }
351
+ }
352
+ }
353
+
354
+ @Override
355
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {}
356
+ };
357
+
358
+ sensorManager.registerListener(listener, magnetometer, SensorManager.SENSOR_DELAY_UI);
359
+
360
+ synchronized (readingObj) {
361
+ try {
362
+ readingObj.wait(500);
363
+ } catch (InterruptedException e) {
364
+ // Ignored
365
+ }
366
+ }
367
+
368
+ if (readingObj[0] != null) {
369
+ info.put("reading", readingObj[0]);
370
+ }
371
+ }
372
+
373
+ callbackContext.success(info);
374
+ } catch (JSONException e) {
375
+ callbackContext.error("Failed to get magnetometer info: " + e.getMessage());
376
+ }
377
+ }
378
+ });
379
+ }
380
+
381
+ private void getAccuracy(CallbackContext callbackContext) {
382
+ callbackContext.success(currentAccuracy);
383
+ }
384
+
385
+ private void isCalibrationNeeded(CallbackContext callbackContext) {
386
+ callbackContext.success(calibrationNeeded ? 1 : 0);
387
+ }
388
+
389
+ private void getFieldStrength(final CallbackContext callbackContext) {
390
+ if (magnetometer == null) {
391
+ callbackContext.error("Magnetometer not available");
392
+ return;
393
+ }
394
+
395
+ cordova.getThreadPool().execute(new Runnable() {
396
+ @Override
397
+ public void run() {
398
+ final boolean[] dataReceived = {false};
399
+
400
+ SensorEventListener listener = new SensorEventListener() {
401
+ @Override
402
+ public void onSensorChanged(SensorEvent event) {
403
+ if (!dataReceived[0] && event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
404
+ dataReceived[0] = true;
405
+ sensorManager.unregisterListener(this);
406
+
407
+ float x = event.values[0];
408
+ float y = event.values[1];
409
+ float z = event.values[2];
410
+ double magnitude = Math.sqrt(x * x + y * y + z * z);
411
+
412
+ callbackContext.success((int) Math.round(magnitude));
413
+ }
414
+ }
415
+
416
+ @Override
417
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {}
418
+ };
419
+
420
+ sensorManager.registerListener(listener, magnetometer, SensorManager.SENSOR_DELAY_UI);
421
+
422
+ handler.postDelayed(new Runnable() {
423
+ @Override
424
+ public void run() {
425
+ if (!dataReceived[0]) {
426
+ dataReceived[0] = true;
427
+ callbackContext.error("Timeout waiting for field strength");
428
+ }
429
+ }
430
+ }, 1000);
431
+ }
432
+ });
433
+ }
434
+
435
+ @Override
436
+ public void onSensorChanged(SensorEvent event) {
437
+ if (watchCallbackContext != null && event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
438
+ try {
439
+ JSONObject reading = createReadingObject(event.values);
440
+ PluginResult result = new PluginResult(PluginResult.Status.OK, reading);
441
+ result.setKeepCallback(true);
442
+ watchCallbackContext.sendPluginResult(result);
443
+ } catch (JSONException e) {
444
+ Log.e(LOG_TAG, "Error sending reading: " + e.getMessage());
445
+ }
446
+ }
447
+ }
448
+
449
+ @Override
450
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
451
+ if (sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
452
+ currentAccuracy = accuracy;
453
+ calibrationNeeded = accuracy < SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM;
454
+ }
455
+ }
456
+
457
+ private JSONObject createReadingObject(float[] values) throws JSONException {
458
+ float x = values[0];
459
+ float y = values[1];
460
+ float z = values[2];
461
+ double magnitude = Math.sqrt(x * x + y * y + z * z);
462
+
463
+ JSONObject reading = new JSONObject();
464
+ reading.put("x", x);
465
+ reading.put("y", y);
466
+ reading.put("z", z);
467
+ reading.put("magnitude", magnitude);
468
+ reading.put("timestamp", System.currentTimeMillis());
469
+
470
+ return reading;
471
+ }
472
+
473
+ private JSONObject calculateHeading(float[] magValues, float[] accelValues) throws JSONException {
474
+ float[] R = new float[9];
475
+ float[] I = new float[9];
476
+
477
+ boolean success = SensorManager.getRotationMatrix(R, I, accelValues, magValues);
478
+
479
+ float azimuth = 0;
480
+ if (success) {
481
+ float[] orientation = new float[3];
482
+ SensorManager.getOrientation(R, orientation);
483
+ azimuth = (float) Math.toDegrees(orientation[0]);
484
+ if (azimuth < 0) {
485
+ azimuth += 360;
486
+ }
487
+ }
488
+
489
+ JSONObject heading = new JSONObject();
490
+ heading.put("magneticHeading", azimuth);
491
+ heading.put("trueHeading", azimuth); // True heading requires GPS, using magnetic as fallback
492
+ heading.put("headingAccuracy", -1); // Not available on Android
493
+ heading.put("timestamp", System.currentTimeMillis());
494
+
495
+ return heading;
496
+ }
497
+
498
+ private int getSensorDelay(int frequencyMs) {
499
+ if (frequencyMs <= 20) {
500
+ return SensorManager.SENSOR_DELAY_FASTEST;
501
+ } else if (frequencyMs <= 60) {
502
+ return SensorManager.SENSOR_DELAY_GAME;
503
+ } else if (frequencyMs <= 200) {
504
+ return SensorManager.SENSOR_DELAY_UI;
505
+ } else {
506
+ return SensorManager.SENSOR_DELAY_NORMAL;
507
+ }
508
+ }
509
+
510
+ @Override
511
+ public void onReset() {
512
+ if (watchCallbackContext != null) {
513
+ sensorManager.unregisterListener(this, magnetometer);
514
+ watchCallbackContext = null;
515
+ }
516
+ stopHeadingSensors();
517
+ watchHeadingCallbackContext = null;
518
+ }
519
+
520
+ @Override
521
+ public void onDestroy() {
522
+ onReset();
523
+ }
524
+ }