diff --git a/README.md b/README.md index b470b6b..bc4d759 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,15 @@ ID can be used with `navigator.compass.clearWatch` to stop watching the navigato var watchID = navigator.compass.watchHeading(onSuccess, onError, options); +### Update 29/08/2022 + +On Android, the plugin used a `Sensor.TYPE_ORIENTATION` which is [deprecated](https://developer.android.com/guide/topics/sensors/sensors_position). +This sensor was more and more [omitted](https://github.com/apache/cordova-plugin-device-orientation/issues/64) in new devices. +So now the plugin uses `Sensor.TYPE_ACCELEROMETER` and `Sensor.TYPE_MAGNETIC_FIELD` which are available everywhere. +They are fused together as instructed [here](https://developer.android.com/guide/topics/sensors/sensors_position#sensors-pos-orient), +except that the `SensorManager.getOrientation()` has a [bug that will not be fixed](https://issuetracker.google.com/issues/37127944). +[Stochastically](https://stackoverflow.com/users/2110762/stochastically) found a [solution](https://stackoverflow.com/questions/15537125/inconsistent-orientation-sensor-values-on-android-for-azimuth-yaw-and-roll/16418016#16418016) that works like a charm and is used here. + ### Browser Quirks diff --git a/package-lock.json b/package-lock.json index ab24c1f..7e07988 100644 --- a/package-lock.json +++ b/package-lock.json @@ -334,9 +334,9 @@ } }, "node_modules/cross-spawn/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -2000,9 +2000,9 @@ "dev": true }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -2634,9 +2634,9 @@ }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true } } @@ -3861,9 +3861,9 @@ "dev": true }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true }, "shebang-command": { diff --git a/src/android/CompassListener.java b/src/android/CompassListener.java index 194db0d..0553578 100755 --- a/src/android/CompassListener.java +++ b/src/android/CompassListener.java @@ -18,6 +18,8 @@ Licensed to the Apache Software Foundation (ASF) under one */ package org.apache.cordova.deviceorientation; +import static java.lang.Float.isNaN; + import java.util.List; import org.apache.cordova.CordovaWebView; @@ -38,259 +40,308 @@ Licensed to the Apache Software Foundation (ASF) under one import android.os.Handler; import android.os.Looper; +import org.apache.cordova.LOG; + /** * This class listens to the compass sensor and stores the latest heading value. */ public class CompassListener extends CordovaPlugin implements SensorEventListener { - public static int STOPPED = 0; - public static int STARTING = 1; - public static int RUNNING = 2; - public static int ERROR_FAILED_TO_START = 3; - - public long TIMEOUT = 30000; // Timeout in msec to shut off listener - - int status; // status of listener - float heading; // most recent heading value - long timeStamp; // time of most recent value - long lastAccessTime; // time the value was last retrieved - int accuracy; // accuracy of the sensor - - private SensorManager sensorManager;// Sensor manager - Sensor mSensor; // Compass sensor returned by sensor manager - - private CallbackContext callbackContext; - - /** - * Constructor. - */ - public CompassListener() { - this.heading = 0; - this.timeStamp = 0; - this.setStatus(CompassListener.STOPPED); - } - - /** - * Sets the context of the Command. This can then be used to do things like - * get file paths associated with the Activity. - * - * @param cordova The context of the main Activity. - * @param webView The CordovaWebView Cordova is running in. - */ - public void initialize(CordovaInterface cordova, CordovaWebView webView) { - super.initialize(cordova, webView); - this.sensorManager = (SensorManager) cordova.getActivity().getSystemService(Context.SENSOR_SERVICE); - } - - /** - * Executes the request and returns PluginResult. - * - * @param action The action to execute. - * @param args JSONArry of arguments for the plugin. - * @param callbackS=Context The callback id used when calling back into JavaScript. - * @return True if the action was valid. - * @throws JSONException - */ - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - if (action.equals("start")) { - this.start(); - } - else if (action.equals("stop")) { - this.stop(); + public static int STOPPED = 0; + public static int STARTING = 1; + public static int RUNNING = 2; + public static int ERROR_FAILED_TO_START = 3; + + public long TIMEOUT = 30000; // Timeout in msec to shut off listener + + int status; // status of listener + // float heading; // most recent heading value + // Replaced by sin & cos + float sinHeading; // sin of most recent heading value + float cosHeading; // cos of most recent heading value + long timeStamp; // time of most recent value + long lastAccessTime; // time the value was last retrieved + int rotationAccuracy = 2; // SENSOR_STATUS_ACCURACY_MEDIUM accuracy of the sensor + + private SensorManager sensorManager;// Sensor manager + + Sensor rvSensor; // Compass sensor returned by sensor manager + Sensor magSensor; // Magnetic field sensor used for accuracy + + private CallbackContext callbackContext; + + /** + * Constructor. + */ + public CompassListener() { + this.sinHeading = 0; + this.cosHeading = 0; + this.timeStamp = 0; + this.setStatus(CompassListener.STOPPED); + } + + /** + * Sets the context of the Command. This can then be used to do things like + * get file paths associated with the Activity. + * + * @param cordova The context of the main Activity. + * @param webView The CordovaWebView Cordova is running in. + */ + public void initialize(CordovaInterface cordova, CordovaWebView webView) { + super.initialize(cordova, webView); + this.sensorManager = (SensorManager) cordova.getActivity().getSystemService(Context.SENSOR_SERVICE); + } + + /** + * Executes the request and returns PluginResult. + * + * @param action The action to execute. + * @param args JSONArry of arguments for the plugin. + * @param callbackS=Context The callback id used when calling back into + * JavaScript. + * @return True if the action was valid. + * @throws JSONException + */ + public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { + if (action.equals("start")) { + this.start(); + } else if (action.equals("stop")) { + this.stop(); + } else if (action.equals("getStatus")) { + int i = this.getStatus(); + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, i)); + } else if (action.equals("getHeading")) { + // If not running, then this is an async call, so don't worry about waiting + if (this.status != CompassListener.RUNNING) { + int r = this.start(); + if (r == CompassListener.ERROR_FAILED_TO_START) { + callbackContext.sendPluginResult( + new PluginResult(PluginResult.Status.IO_EXCEPTION, CompassListener.ERROR_FAILED_TO_START)); + return true; } - else if (action.equals("getStatus")) { - int i = this.getStatus(); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, i)); - } - else if (action.equals("getHeading")) { - // If not running, then this is an async call, so don't worry about waiting - if (this.status != CompassListener.RUNNING) { - int r = this.start(); - if (r == CompassListener.ERROR_FAILED_TO_START) { - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, CompassListener.ERROR_FAILED_TO_START)); - return true; - } - // Set a timeout callback on the main thread. - Handler handler = new Handler(Looper.getMainLooper()); - handler.postDelayed(new Runnable() { - public void run() { - CompassListener.this.timeout(); - } - }, 2000); - } - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, getCompassHeading())); - } - else if (action.equals("setTimeout")) { - this.setTimeout(args.getLong(0)); - } - else if (action.equals("getTimeout")) { - long l = this.getTimeout(); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, l)); - } else { - // Unsupported action - return false; - } - return true; - } - - /** - * Called when listener is to be shut down and object is being destroyed. - */ - public void onDestroy() { - this.stop(); + // Set a timeout callback on the main thread. + Handler handler = new Handler(Looper.getMainLooper()); + handler.postDelayed(new Runnable() { + public void run() { + CompassListener.this.timeout(); + } + }, 2000); + } + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, getCompassHeading())); + } else if (action.equals("setTimeout")) { + this.setTimeout(args.getLong(0)); + } else if (action.equals("getTimeout")) { + long l = this.getTimeout(); + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, l)); + } else { + // Unsupported action + return false; } - - /** - * Called when app has navigated and JS listeners have been destroyed. - */ - public void onReset() { - this.stop(); - } - - //-------------------------------------------------------------------------- - // LOCAL METHODS - //-------------------------------------------------------------------------- - - /** - * Start listening for compass sensor. - * - * @return status of listener - */ - public int start() { - - // If already starting or running, then just return - if ((this.status == CompassListener.RUNNING) || (this.status == CompassListener.STARTING)) { - return this.status; - } - - // Get compass sensor from sensor manager - @SuppressWarnings("deprecation") - List list = this.sensorManager.getSensorList(Sensor.TYPE_ORIENTATION); - - // If found, then register as listener - if (list != null && list.size() > 0) { - this.mSensor = list.get(0); - this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_NORMAL); - this.lastAccessTime = System.currentTimeMillis(); - this.setStatus(CompassListener.STARTING); - } - - // If error, then set status to error - else { - this.setStatus(CompassListener.ERROR_FAILED_TO_START); - } - - return this.status; + return true; + } + + /** + * Called when listener is to be shut down and object is being destroyed. + */ + public void onDestroy() { + this.stop(); + } + + /** + * Called when app has navigated and JS listeners have been destroyed. + */ + public void onReset() { + this.stop(); + } + + // -------------------------------------------------------------------------- + // LOCAL METHODS + // -------------------------------------------------------------------------- + + /** + * Start listening for compass sensor. + * + * @return status of listener + */ + public int start() { + // final int SENSOR_DELAY = sensorManager.SENSOR_DELAY_NORMAL; + final int SENSOR_DELAY = sensorManager.SENSOR_DELAY_UI; + // final int SENSOR_DELAY = sensorManager.SENSOR_DELAY_GAME; + + // If already starting or running, then just return + if ((this.status == CompassListener.RUNNING) || (this.status == CompassListener.STARTING)) { + return this.status; } - /** - * Stop listening to compass sensor. - */ - public void stop() { - if (this.status != CompassListener.STOPPED) { - this.sensorManager.unregisterListener(this); - } - this.setStatus(CompassListener.STOPPED); + // use accelerometer & magnetometer + // http://web.archive.org/web/20151205103652/http://www.codingforandroid.com/2011/01/using-orientation-sensors-simple.html + // https://android-developers.googleblog.com/2010/09/one-screen-turn-deserves-another.html + // https://stackoverflow.com/questions/15537125/inconsistent-orientation-sensor-values-on-android-for-azimuth-yaw-and-roll/16418016#16418016 + rvSensor = this.sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); + // Next one only used for getting accuracy + magSensor = this.sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); + + if (rvSensor != null && magSensor != null) { + this.sensorManager.registerListener(this, this.rvSensor, SENSOR_DELAY); + this.sensorManager.registerListener(this, this.magSensor, sensorManager.SENSOR_DELAY_NORMAL); + this.lastAccessTime = System.currentTimeMillis(); + this.setStatus(CompassListener.STARTING); + + } else { + // If error, then set status to error + this.setStatus(CompassListener.ERROR_FAILED_TO_START); } - - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // TODO Auto-generated method stub + return this.status; + } + + /** + * Stop listening to compass sensor. + */ + public void stop() { + if (this.status != CompassListener.STOPPED) { + this.sensorManager.unregisterListener(this); } - - /** - * Called after a delay to time out if the listener has not attached fast enough. - */ - private void timeout() { - if (this.status == CompassListener.STARTING) { - this.setStatus(CompassListener.ERROR_FAILED_TO_START); - if (this.callbackContext != null) { - this.callbackContext.error("Compass listener failed to start."); - } - } + this.setStatus(CompassListener.STOPPED); + } + + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // TODO Auto-generated method stub + } + + /** + * Called after a delay to time out if the listener has not attached fast + * enough. + */ + private void timeout() { + if (this.status == CompassListener.STARTING) { + this.setStatus(CompassListener.ERROR_FAILED_TO_START); + if (this.callbackContext != null) { + this.callbackContext.error("Compass listener failed to start."); + } } - - /** - * Sensor listener event. - * - * @param SensorEvent event - */ - public void onSensorChanged(SensorEvent event) { - - // We only care about the orientation as far as it refers to Magnetic North - float heading = event.values[0]; - - // Save heading - this.timeStamp = System.currentTimeMillis(); - this.heading = heading; - this.setStatus(CompassListener.RUNNING); - - // If heading hasn't been read for TIMEOUT time, then turn off compass sensor to save power - if ((this.timeStamp - this.lastAccessTime) > this.TIMEOUT) { - this.stop(); + } + + private float rotationVector[] = null; + + private float toDeg(float x) { + return x * 180 / (float) Math.PI; // Convert to degrees + } + + /** + * Sensor listener event. + * + * @param SensorEvent event + */ + @SuppressWarnings("deprecation") + public void onSensorChanged(SensorEvent event) { + float sinHeading = (0f / 0f); // NaN + float cosHeading = (0f / 0f); // NaN + + long myNow = System.currentTimeMillis(); + switch (event.sensor.getType()) { + case Sensor.TYPE_ROTATION_VECTOR: + if (rotationVector == null) { + rotationVector = new float[3]; } - } - - /** - * Get status of compass sensor. - * - * @return status - */ - public int getStatus() { - return this.status; - } + rotationVector = event.values; + break; - /** - * Get the most recent compass heading. - * - * @return heading - */ - public float getHeading() { - this.lastAccessTime = System.currentTimeMillis(); - return this.heading; + case Sensor.TYPE_MAGNETIC_FIELD: + this.rotationAccuracy = event.accuracy; + return; } - /** - * Set the timeout to turn off compass sensor if getHeading() hasn't been called. - * - * @param timeout Timeout in msec. - */ - public void setTimeout(long timeout) { - this.TIMEOUT = timeout; + if (rotationVector != null) { + float R[] = new float[9]; + SensorManager.getRotationMatrixFromVector(R, event.values); + // Android recommends using SensorManager.getOrientation() but it has a wontFix + // bug: + // https://stackoverflow.com/questions/67824884/pitch-returned-by-getorientation-function-is-wrong + // So we use Stochastically's method: + // https://stackoverflow.com/questions/15537125/inconsistent-orientation-sensor-values-on-android-for-azimuth-yaw-and-roll/16418016#16418016 + // which works like a charm. + // Beware also of screen orientation: + // https://android-developers.googleblog.com/2010/09/one-screen-turn-deserves-another.html + + // heading = (float) Math.atan2((double) (R[1] - R[3]), (double) (R[0] + R[4])); + // heading = toDeg(heading); + // Replaced by sin & cos + this.sinHeading = R[1] - R[3]; + this.cosHeading = R[0] + R[4]; } + this.timeStamp = System.currentTimeMillis(); + this.setStatus(CompassListener.RUNNING); - /** - * Get the timeout to turn off compass sensor if getHeading() hasn't been called. - * - * @return timeout in msec - */ - public long getTimeout() { - return this.TIMEOUT; + // If heading hasn't been read for TIMEOUT time, then turn off compass sensor to + // save power + if ((this.timeStamp - this.lastAccessTime) > this.TIMEOUT) { + this.stop(); } - - /** - * Set the status and send it to JavaScript. - * @param status - */ - private void setStatus(int status) { - this.status = status; - } - - /** - * Create the CompassHeading JSON object to be returned to JavaScript - * - * @return a compass heading - */ - private JSONObject getCompassHeading() throws JSONException { - JSONObject obj = new JSONObject(); - - obj.put("magneticHeading", this.getHeading()); - obj.put("trueHeading", this.getHeading()); - // Since the magnetic and true heading are always the same our and accuracy - // is defined as the difference between true and magnetic always return zero - obj.put("headingAccuracy", 0); - obj.put("timestamp", this.timeStamp); - - return obj; - } - + } + + /** + * Get status of compass sensor. + * + * @return status + */ + public int getStatus() { + return this.status; + } + + /** + * Set the timeout to turn off compass sensor if getHeading() hasn't been + * called. + * + * @param timeout Timeout in msec. + */ + public void setTimeout(long timeout) { + this.TIMEOUT = timeout; + } + + /** + * Get the timeout to turn off compass sensor if getHeading() hasn't been + * called. + * + * @return timeout in msec + */ + public long getTimeout() { + return this.TIMEOUT; + } + + /** + * Set the status and send it to JavaScript. + * + * @param status + */ + private void setStatus(int status) { + this.status = status; + } + + private boolean isNan(float val) { + return val != val; + } + + /** + * Create the CompassHeading JSON object to be returned to JavaScript + * + * @return a compass heading + */ + private JSONObject getCompassHeading() throws JSONException { + JSONObject obj = new JSONObject(); + long myNow = System.currentTimeMillis(); + + // obj.put("magneticHeading", this.heading); + // obj.put("trueHeading", this.heading); + if (!isNan(this.sinHeading)) + obj.put("sinHeading", this.sinHeading); + if (!isNan(this.cosHeading)) + obj.put("cosHeading", this.cosHeading); + // Since the magnetic and true heading are always the same our and accuracy + // is defined as the difference between true and magnetic always return zero + if (!isNan(this.rotationAccuracy)) + obj.put("headingAccuracy", this.rotationAccuracy); + obj.put("timeStamp", myNow); + this.lastAccessTime = myNow; + return obj; + } } diff --git a/www/CompassHeading.js b/www/CompassHeading.js index 2753f43..9a94745 100644 --- a/www/CompassHeading.js +++ b/www/CompassHeading.js @@ -19,11 +19,14 @@ * */ -var CompassHeading = function (magneticHeading, trueHeading, headingAccuracy, timestamp) { - this.magneticHeading = magneticHeading; - this.trueHeading = trueHeading; - this.headingAccuracy = headingAccuracy; - this.timestamp = timestamp || new Date().getTime(); +var CompassHeading = function (obj) { + if (obj.sinHeading != undefined) this.sinHeading = obj.sinHeading; + if (obj.cosHeading != undefined) this.cosHeading = obj.cosHeading; + this.headingAccuracy = obj.headingAccuracy; + if (obj.magneticHeading != undefined) this.magneticHeading = obj.magneticHeading; + if (obj.trueHeading != undefined) this.trueHeading = obj.trueHeading; + if (obj.timeStamp != undefined) this.timeStamp = obj.timeStamp; + else this.timeStamp = Date.now(); }; module.exports = CompassHeading; diff --git a/www/compass.js b/www/compass.js index ba3df60..fdd24fc 100644 --- a/www/compass.js +++ b/www/compass.js @@ -21,11 +21,11 @@ /* global cordova */ -var argscheck = require('cordova/argscheck'); -var exec = require('cordova/exec'); -var utils = require('cordova/utils'); -var CompassHeading = require('./CompassHeading'); -var CompassError = require('./CompassError'); +var argscheck = require("cordova/argscheck"); +var exec = require("cordova/exec"); +var utils = require("cordova/utils"); +var CompassHeading = require("./CompassHeading"); +var CompassError = require("./CompassError"); var timers = {}; var eventTimerId = null; @@ -39,10 +39,11 @@ var compass = { * @param {CompassOptions} options The options for getting the heading data (not used). */ getCurrentHeading: function (successCallback, errorCallback, options) { - argscheck.checkArgs('fFO', 'compass.getCurrentHeading', arguments); + argscheck.checkArgs("fFO", "compass.getCurrentHeading", arguments); var win = function (result) { - var ch = new CompassHeading(result.magneticHeading, result.trueHeading, result.headingAccuracy, result.timestamp); + // var ch = new CompassHeading(result.magneticHeading, result.trueHeading, result.headingAccuracy, result.timestamp); + var ch = new CompassHeading(result); successCallback(ch); }; var fail = @@ -53,7 +54,7 @@ var compass = { }; // Get heading - exec(win, fail, 'Compass', 'getHeading', [options]); + exec(win, fail, "Compass", "getHeading", [options]); }, /** @@ -67,15 +68,16 @@ var compass = { * specifies to watch via a distance filter rather than time. */ watchHeading: function (successCallback, errorCallback, options) { - argscheck.checkArgs('fFO', 'compass.watchHeading', arguments); + argscheck.checkArgs("fFO", "compass.watchHeading", arguments); // Default interval (100 msec) - var frequency = options !== undefined && options.frequency !== undefined ? options.frequency : 100; + var frequency = + options !== undefined && options.frequency !== undefined ? options.frequency : 100; var filter = options !== undefined && options.filter !== undefined ? options.filter : 0; var id = utils.createUUID(); if (filter > 0) { // is an iOS request for watch by filter, no timer needed - timers[id] = 'iOS'; + timers[id] = "iOS"; compass.getCurrentHeading(successCallback, errorCallback, options); } else { // Start watch timer to get headings @@ -84,9 +86,9 @@ var compass = { }, frequency); } - if (cordova.platformId === 'browser' && !eventTimerId) { + if (cordova.platformId === "browser" && !eventTimerId) { // Start firing deviceorientation events if haven't already - var deviceorientationEvent = new Event('deviceorientation'); + var deviceorientationEvent = new Event("deviceorientation"); eventTimerId = window.setInterval(function () { window.dispatchEvent(deviceorientationEvent); }, 200); @@ -102,11 +104,11 @@ var compass = { clearWatch: function (id) { // Stop javascript timer & remove from timer list if (id && timers[id]) { - if (timers[id] !== 'iOS') { + if (timers[id] !== "iOS") { clearInterval(timers[id]); } else { // is iOS watch by filter so call into device to stop - exec(null, null, 'Compass', 'stopHeading', []); + exec(null, null, "Compass", "stopHeading", []); } delete timers[id]; @@ -116,7 +118,7 @@ var compass = { eventTimerId = null; } } - } + }, }; module.exports = compass;