1 /* 2 * Copyright (C) 2016 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.googlecode.android_scripting.facade; 18 19 import android.content.Context; 20 import android.hardware.Sensor; 21 import android.hardware.SensorEvent; 22 import android.hardware.SensorEventListener; 23 import android.hardware.SensorManager; 24 import android.os.Bundle; 25 26 import com.googlecode.android_scripting.jsonrpc.RpcReceiver; 27 import com.googlecode.android_scripting.rpc.Rpc; 28 import com.googlecode.android_scripting.rpc.RpcDefault; 29 import com.googlecode.android_scripting.rpc.RpcDeprecated; 30 import com.googlecode.android_scripting.rpc.RpcParameter; 31 import com.googlecode.android_scripting.rpc.RpcStartEvent; 32 import com.googlecode.android_scripting.rpc.RpcStopEvent; 33 34 import java.util.Arrays; 35 import java.util.List; 36 37 /** 38 * Exposes the SensorManager related functionality. <br> 39 * <br> 40 * <b>Guidance notes</b> <br> 41 * For reasons of economy the sensors on smart phones are usually low cost and, therefore, low 42 * accuracy (usually represented by 10 bit data). The floating point data values obtained from 43 * sensor readings have up to 16 decimal places, the majority of which are noise. On many phones the 44 * accelerometer is limited (by the phone manufacturer) to a maximum reading of 2g. The magnetometer 45 * (which also provides orientation readings) is strongly affected by the presence of ferrous metals 46 * and can give large errors in vehicles, on board ship etc. 47 * 48 * Following a startSensingTimed(A,B) api call sensor events are entered into the Event Queue (see 49 * EventFacade). For the A parameter: 1 = All Sensors, 2 = Accelerometer, 3 = Magnetometer and 4 = 50 * Light. The B parameter is the minimum delay between recordings in milliseconds. To avoid 51 * duplicate readings the minimum delay should be 20 milliseconds. The light sensor will probably be 52 * much slower (taking about 1 second to register a change in light level). Note that if the light 53 * level is constant no sensor events will be registered by the light sensor. 54 * 55 * Following a startSensingThreshold(A,B,C) api call sensor events greater than a given threshold 56 * are entered into the Event Queue. For the A parameter: 1 = Orientation, 2 = Accelerometer, 3 = 57 * Magnetometer and 4 = Light. The B parameter is the integer value of the required threshold level. 58 * For orientation sensing the integer threshold value is in milliradians. Since orientation events 59 * can exceed the threshold value for long periods only crossing and return events are recorded. The 60 * C parameter is the required axis (XYZ) of the sensor: 0 = No axis, 1 = X, 2 = Y, 3 = X+Y, 4 = Z, 61 * 5= X+Z, 6 = Y+Z, 7 = X+Y+Z. For orientation X = azimuth, Y = pitch and Z = roll. <br> 62 * 63 * <br> 64 * <b>Example (python)</b> 65 * 66 * <pre> 67 * import android, time 68 * droid = android.Android() 69 * droid.startSensingTimed(1, 250) 70 * time.sleep(1) 71 * s1 = droid.readSensors().result 72 * s2 = droid.sensorsGetAccuracy().result 73 * s3 = droid.sensorsGetLight().result 74 * s4 = droid.sensorsReadAccelerometer().result 75 * s5 = droid.sensorsReadMagnetometer().result 76 * s6 = droid.sensorsReadOrientation().result 77 * droid.stopSensing() 78 * </pre> 79 * 80 * Returns:<br> 81 * s1 = {u'accuracy': 3, u'pitch': -0.47323511242866517, u'xmag': 1.75, u'azimuth': 82 * -0.26701245009899138, u'zforce': 8.4718560000000007, u'yforce': 4.2495484000000001, u'time': 83 * 1297160391.2820001, u'ymag': -8.9375, u'zmag': -41.0625, u'roll': -0.031366908922791481, 84 * u'xforce': 0.23154590999999999}<br> 85 * s2 = 3 (Highest accuracy)<br> 86 * s3 = None ---(not available on many phones)<br> 87 * s4 = [0.23154590999999999, 4.2495484000000001, 8.4718560000000007] ----(x, y, z accelerations)<br> 88 * s5 = [1.75, -8.9375, -41.0625] -----(x, y, z magnetic readings)<br> 89 * s6 = [-0.26701245009899138, -0.47323511242866517, -0.031366908922791481] ---(azimuth, pitch, roll 90 * in radians)<br> 91 * 92 * @author Damon Kohler (damonkohler@gmail.com) 93 * @author Felix Arends (felix.arends@gmail.com) 94 * @author Alexey Reznichenko (alexey.reznichenko@gmail.com) 95 * @author Robbie Mathews (rjmatthews62@gmail.com) 96 * @author John Karwatzki (jokar49@gmail.com) 97 */ 98 public class SensorManagerFacade extends RpcReceiver { 99 private final EventFacade mEventFacade; 100 private final SensorManager mSensorManager; 101 102 private volatile Bundle mSensorReadings; 103 104 private volatile Integer mAccuracy; 105 private volatile Integer mSensorNumber; 106 private volatile Integer mXAxis = 0; 107 private volatile Integer mYAxis = 0; 108 private volatile Integer mZAxis = 0; 109 private volatile Integer mThreshing = 0; 110 private volatile Integer mThreshOrientation = 0; 111 private volatile Integer mXCrossed = 0; 112 private volatile Integer mYCrossed = 0; 113 private volatile Integer mZCrossed = 0; 114 115 private volatile Float mThreshold; 116 private volatile Float mXForce; 117 private volatile Float mYForce; 118 private volatile Float mZForce; 119 120 private volatile Float mXMag; 121 private volatile Float mYMag; 122 private volatile Float mZMag; 123 124 private volatile Float mLight; 125 126 private volatile Double mAzimuth; 127 private volatile Double mPitch; 128 private volatile Double mRoll; 129 130 private volatile Long mLastTime; 131 private volatile Long mDelayTime; 132 133 private SensorEventListener mSensorListener; 134 SensorManagerFacade(FacadeManager manager)135 public SensorManagerFacade(FacadeManager manager) { 136 super(manager); 137 mEventFacade = manager.getReceiver(EventFacade.class); 138 mSensorManager = (SensorManager) manager.getService().getSystemService(Context.SENSOR_SERVICE); 139 } 140 141 @Rpc(description = "Starts recording sensor data to be available for polling.") 142 @RpcStartEvent("sensors") startSensingTimed( @pcParametername = "sensorNumber", description = "1 = All, 2 = Accelerometer, 3 = Magnetometer and 4 = Light") Integer sensorNumber, @RpcParameter(name = "delayTime", description = "Minimum time between readings in milliseconds") Integer delayTime)143 public void startSensingTimed( 144 @RpcParameter(name = "sensorNumber", description = "1 = All, 2 = Accelerometer, 3 = Magnetometer and 4 = Light") Integer sensorNumber, 145 @RpcParameter(name = "delayTime", description = "Minimum time between readings in milliseconds") Integer delayTime) { 146 mSensorNumber = sensorNumber; 147 if (delayTime < 20) { 148 delayTime = 20; 149 } 150 mDelayTime = (long) (delayTime); 151 mLastTime = System.currentTimeMillis(); 152 if (mSensorListener == null) { 153 mSensorListener = new SensorValuesCollector(); 154 mSensorReadings = new Bundle(); 155 switch (mSensorNumber) { 156 case 1: 157 for (Sensor sensor : mSensorManager.getSensorList(Sensor.TYPE_ALL)) { 158 mSensorManager.registerListener(mSensorListener, sensor, 159 SensorManager.SENSOR_DELAY_FASTEST); 160 } 161 break; 162 case 2: 163 for (Sensor sensor : mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER)) { 164 mSensorManager.registerListener(mSensorListener, sensor, 165 SensorManager.SENSOR_DELAY_FASTEST); 166 } 167 break; 168 case 3: 169 for (Sensor sensor : mSensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD)) { 170 mSensorManager.registerListener(mSensorListener, sensor, 171 SensorManager.SENSOR_DELAY_FASTEST); 172 } 173 break; 174 case 4: 175 for (Sensor sensor : mSensorManager.getSensorList(Sensor.TYPE_LIGHT)) { 176 mSensorManager.registerListener(mSensorListener, sensor, 177 SensorManager.SENSOR_DELAY_FASTEST); 178 } 179 } 180 } 181 } 182 183 @Rpc(description = "Records to the Event Queue sensor data exceeding a chosen threshold.") 184 @RpcStartEvent("threshold") startSensingThreshold( @pcParametername = "sensorNumber", description = "1 = Orientation, 2 = Accelerometer, 3 = Magnetometer and 4 = Light") Integer sensorNumber, @RpcParameter(name = "threshold", description = "Threshold level for chosen sensor (integer)") Integer threshold, @RpcParameter(name = "axis", description = "0 = No axis, 1 = X, 2 = Y, 3 = X+Y, 4 = Z, 5= X+Z, 6 = Y+Z, 7 = X+Y+Z") Integer axis)185 public void startSensingThreshold( 186 187 @RpcParameter(name = "sensorNumber", description = "1 = Orientation, 2 = Accelerometer, 3 = Magnetometer and 4 = Light") Integer sensorNumber, 188 @RpcParameter(name = "threshold", description = "Threshold level for chosen sensor (integer)") Integer threshold, 189 @RpcParameter(name = "axis", description = "0 = No axis, 1 = X, 2 = Y, 3 = X+Y, 4 = Z, 5= X+Z, 6 = Y+Z, 7 = X+Y+Z") Integer axis) { 190 mSensorNumber = sensorNumber; 191 mXAxis = axis & 1; 192 mYAxis = axis & 2; 193 mZAxis = axis & 4; 194 if (mSensorNumber == 1) { 195 mThreshing = 0; 196 mThreshOrientation = 1; 197 mThreshold = ((float) threshold) / ((float) 1000); 198 } else { 199 mThreshing = 1; 200 mThreshold = (float) threshold; 201 } 202 startSensingTimed(mSensorNumber, 20); 203 } 204 205 @Rpc(description = "Returns the most recently recorded sensor data.") readSensors()206 public Bundle readSensors() { 207 if (mSensorReadings == null) { 208 return null; 209 } 210 synchronized (mSensorReadings) { 211 return new Bundle(mSensorReadings); 212 } 213 } 214 215 @Rpc(description = "Stops collecting sensor data.") 216 @RpcStopEvent("sensors") stopSensing()217 public void stopSensing() { 218 mSensorManager.unregisterListener(mSensorListener); 219 mSensorListener = null; 220 mSensorReadings = null; 221 mThreshing = 0; 222 mThreshOrientation = 0; 223 } 224 225 @Rpc(description = "Returns the most recently received accuracy value.") sensorsGetAccuracy()226 public Integer sensorsGetAccuracy() { 227 return mAccuracy; 228 } 229 230 @Rpc(description = "Returns the most recently received light value.") sensorsGetLight()231 public Float sensorsGetLight() { 232 return mLight; 233 } 234 235 @Rpc(description = "Returns the most recently received accelerometer values.", returns = "a List of Floats [(acceleration on the) X axis, Y axis, Z axis].") sensorsReadAccelerometer()236 public List<Float> sensorsReadAccelerometer() { 237 synchronized (mSensorReadings) { 238 return Arrays.asList(mXForce, mYForce, mZForce); 239 } 240 } 241 242 @Rpc(description = "Returns the most recently received magnetic field values.", returns = "a List of Floats [(magnetic field value for) X axis, Y axis, Z axis].") sensorsReadMagnetometer()243 public List<Float> sensorsReadMagnetometer() { 244 synchronized (mSensorReadings) { 245 return Arrays.asList(mXMag, mYMag, mZMag); 246 } 247 } 248 249 @Rpc(description = "Returns the most recently received orientation values.", returns = "a List of Doubles [azimuth, pitch, roll].") sensorsReadOrientation()250 public List<Double> sensorsReadOrientation() { 251 synchronized (mSensorReadings) { 252 return Arrays.asList(mAzimuth, mPitch, mRoll); 253 } 254 } 255 256 @Rpc(description = "Starts recording sensor data to be available for polling.") 257 @RpcDeprecated(value = "startSensingTimed or startSensingThreshhold", release = "4") startSensing( @pcParametername = "sampleSize", description = "number of samples for calculating average readings") @pcDefault"5") Integer sampleSize)258 public void startSensing( 259 @RpcParameter(name = "sampleSize", description = "number of samples for calculating average readings") @RpcDefault("5") Integer sampleSize) { 260 if (mSensorListener == null) { 261 startSensingTimed(1, 220); 262 } 263 } 264 265 @Override shutdown()266 public void shutdown() { 267 stopSensing(); 268 } 269 270 private class SensorValuesCollector implements SensorEventListener { 271 private final static int MATRIX_SIZE = 9; 272 273 private final RollingAverage mmAzimuth; 274 private final RollingAverage mmPitch; 275 private final RollingAverage mmRoll; 276 277 private float[] mmGeomagneticValues; 278 private float[] mmGravityValues; 279 private float[] mmR; 280 private float[] mmOrientation; 281 SensorValuesCollector()282 public SensorValuesCollector() { 283 mmAzimuth = new RollingAverage(); 284 mmPitch = new RollingAverage(); 285 mmRoll = new RollingAverage(); 286 } 287 postEvent()288 private void postEvent() { 289 mSensorReadings.putDouble("time", System.currentTimeMillis() / 1000.0); 290 mEventFacade.postEvent("sensors", mSensorReadings.clone()); 291 } 292 293 @Override onAccuracyChanged(Sensor sensor, int accuracy)294 public void onAccuracyChanged(Sensor sensor, int accuracy) { 295 if (mSensorReadings == null) { 296 return; 297 } 298 synchronized (mSensorReadings) { 299 mSensorReadings.putInt("accuracy", accuracy); 300 mAccuracy = accuracy; 301 302 } 303 } 304 305 @Override onSensorChanged(SensorEvent event)306 public void onSensorChanged(SensorEvent event) { 307 if (mSensorReadings == null) { 308 return; 309 } 310 synchronized (mSensorReadings) { 311 switch (event.sensor.getType()) { 312 case Sensor.TYPE_ACCELEROMETER: 313 mXForce = event.values[0]; 314 mYForce = event.values[1]; 315 mZForce = event.values[2]; 316 if (mThreshing == 0) { 317 mSensorReadings.putFloat("xforce", mXForce); 318 mSensorReadings.putFloat("yforce", mYForce); 319 mSensorReadings.putFloat("zforce", mZForce); 320 if ((mSensorNumber == 2) && (System.currentTimeMillis() > (mDelayTime + mLastTime))) { 321 mLastTime = System.currentTimeMillis(); 322 postEvent(); 323 } 324 } 325 if ((mThreshing == 1) && (mSensorNumber == 2)) { 326 if ((Math.abs(mXForce) > mThreshold) && (mXAxis == 1)) { 327 mSensorReadings.putFloat("xforce", mXForce); 328 postEvent(); 329 } 330 331 if ((Math.abs(mYForce) > mThreshold) && (mYAxis == 2)) { 332 mSensorReadings.putFloat("yforce", mYForce); 333 postEvent(); 334 } 335 336 if ((Math.abs(mZForce) > mThreshold) && (mZAxis == 4)) { 337 mSensorReadings.putFloat("zforce", mZForce); 338 postEvent(); 339 } 340 } 341 342 mmGravityValues = event.values.clone(); 343 break; 344 case Sensor.TYPE_MAGNETIC_FIELD: 345 mXMag = event.values[0]; 346 mYMag = event.values[1]; 347 mZMag = event.values[2]; 348 if (mThreshing == 0) { 349 mSensorReadings.putFloat("xMag", mXMag); 350 mSensorReadings.putFloat("yMag", mYMag); 351 mSensorReadings.putFloat("zMag", mZMag); 352 if ((mSensorNumber == 3) && (System.currentTimeMillis() > (mDelayTime + mLastTime))) { 353 mLastTime = System.currentTimeMillis(); 354 postEvent(); 355 } 356 } 357 if ((mThreshing == 1) && (mSensorNumber == 3)) { 358 if ((Math.abs(mXMag) > mThreshold) && (mXAxis == 1)) { 359 mSensorReadings.putFloat("xforce", mXMag); 360 postEvent(); 361 } 362 if ((Math.abs(mYMag) > mThreshold) && (mYAxis == 2)) { 363 mSensorReadings.putFloat("yforce", mYMag); 364 postEvent(); 365 } 366 if ((Math.abs(mZMag) > mThreshold) && (mZAxis == 4)) { 367 mSensorReadings.putFloat("zforce", mZMag); 368 postEvent(); 369 } 370 } 371 mmGeomagneticValues = event.values.clone(); 372 break; 373 case Sensor.TYPE_LIGHT: 374 mLight = event.values[0]; 375 if (mThreshing == 0) { 376 mSensorReadings.putFloat("light", mLight); 377 if ((mSensorNumber == 4) && (System.currentTimeMillis() > (mDelayTime + mLastTime))) { 378 mLastTime = System.currentTimeMillis(); 379 postEvent(); 380 } 381 } 382 if ((mThreshing == 1) && (mSensorNumber == 4)) { 383 if (mLight > mThreshold) { 384 mSensorReadings.putFloat("light", mLight); 385 postEvent(); 386 } 387 } 388 break; 389 390 } 391 if (mSensorNumber == 1) { 392 if (mmGeomagneticValues != null && mmGravityValues != null) { 393 if (mmR == null) { 394 mmR = new float[MATRIX_SIZE]; 395 } 396 if (SensorManager.getRotationMatrix(mmR, null, mmGravityValues, mmGeomagneticValues)) { 397 if (mmOrientation == null) { 398 mmOrientation = new float[3]; 399 } 400 SensorManager.getOrientation(mmR, mmOrientation); 401 mmAzimuth.add(mmOrientation[0]); 402 mmPitch.add(mmOrientation[1]); 403 mmRoll.add(mmOrientation[2]); 404 405 mAzimuth = mmAzimuth.get(); 406 mPitch = mmPitch.get(); 407 mRoll = mmRoll.get(); 408 if (mThreshOrientation == 0) { 409 mSensorReadings.putDouble("azimuth", mAzimuth); 410 mSensorReadings.putDouble("pitch", mPitch); 411 mSensorReadings.putDouble("roll", mRoll); 412 if ((mSensorNumber == 1) && (System.currentTimeMillis() > (mDelayTime + mLastTime))) { 413 mLastTime = System.currentTimeMillis(); 414 postEvent(); 415 } 416 } 417 if ((mThreshOrientation == 1) && (mSensorNumber == 1)) { 418 if ((mXAxis == 1) && (mXCrossed == 0)) { 419 if (Math.abs(mAzimuth) > ((double) mThreshold)) { 420 mSensorReadings.putDouble("azimuth", mAzimuth); 421 postEvent(); 422 mXCrossed = 1; 423 } 424 } 425 if ((mXAxis == 1) && (mXCrossed == 1)) { 426 if (Math.abs(mAzimuth) < ((double) mThreshold)) { 427 mSensorReadings.putDouble("azimuth", mAzimuth); 428 postEvent(); 429 mXCrossed = 0; 430 } 431 } 432 if ((mYAxis == 2) && (mYCrossed == 0)) { 433 if (Math.abs(mPitch) > ((double) mThreshold)) { 434 mSensorReadings.putDouble("pitch", mPitch); 435 postEvent(); 436 mYCrossed = 1; 437 } 438 } 439 if ((mYAxis == 2) && (mYCrossed == 1)) { 440 if (Math.abs(mPitch) < ((double) mThreshold)) { 441 mSensorReadings.putDouble("pitch", mPitch); 442 postEvent(); 443 mYCrossed = 0; 444 } 445 } 446 if ((mZAxis == 4) && (mZCrossed == 0)) { 447 if (Math.abs(mRoll) > ((double) mThreshold)) { 448 mSensorReadings.putDouble("roll", mRoll); 449 postEvent(); 450 mZCrossed = 1; 451 } 452 } 453 if ((mZAxis == 4) && (mZCrossed == 1)) { 454 if (Math.abs(mRoll) < ((double) mThreshold)) { 455 mSensorReadings.putDouble("roll", mRoll); 456 postEvent(); 457 mZCrossed = 0; 458 } 459 } 460 } 461 } 462 } 463 } 464 } 465 } 466 } 467 468 static class RollingAverage { 469 private final int mmSampleSize; 470 private final double mmData[]; 471 private int mmIndex = 0; 472 private boolean mmFilled = false; 473 private double mmSum = 0.0; 474 RollingAverage()475 public RollingAverage() { 476 mmSampleSize = 5; 477 mmData = new double[mmSampleSize]; 478 } 479 add(double value)480 public void add(double value) { 481 mmSum -= mmData[mmIndex]; 482 mmData[mmIndex] = value; 483 mmSum += mmData[mmIndex]; 484 ++mmIndex; 485 mmIndex %= mmSampleSize; 486 mmFilled = (!mmFilled) ? mmIndex == 0 : mmFilled; 487 } 488 get()489 public double get() throws IllegalStateException { 490 if (!mmFilled && mmIndex == 0) { 491 throw new IllegalStateException("No values to average."); 492 } 493 return (mmFilled) ? (mmSum / mmSampleSize) : (mmSum / mmIndex); 494 } 495 } 496 } 497