1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under 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 */ 93 public class SensorManagerFacade extends RpcReceiver { 94 private final EventFacade mEventFacade; 95 private final SensorManager mSensorManager; 96 97 private volatile Bundle mSensorReadings; 98 99 private volatile Integer mAccuracy; 100 private volatile Integer mSensorNumber; 101 private volatile Integer mXAxis = 0; 102 private volatile Integer mYAxis = 0; 103 private volatile Integer mZAxis = 0; 104 private volatile Integer mThreshing = 0; 105 private volatile Integer mThreshOrientation = 0; 106 private volatile Integer mXCrossed = 0; 107 private volatile Integer mYCrossed = 0; 108 private volatile Integer mZCrossed = 0; 109 110 private volatile Float mThreshold; 111 private volatile Float mXForce; 112 private volatile Float mYForce; 113 private volatile Float mZForce; 114 115 private volatile Float mXMag; 116 private volatile Float mYMag; 117 private volatile Float mZMag; 118 119 private volatile Float mLight; 120 121 private volatile Double mAzimuth; 122 private volatile Double mPitch; 123 private volatile Double mRoll; 124 125 private volatile Long mLastTime; 126 private volatile Long mDelayTime; 127 128 private SensorEventListener mSensorListener; 129 SensorManagerFacade(FacadeManager manager)130 public SensorManagerFacade(FacadeManager manager) { 131 super(manager); 132 mEventFacade = manager.getReceiver(EventFacade.class); 133 mSensorManager = (SensorManager) manager.getService().getSystemService(Context.SENSOR_SERVICE); 134 } 135 136 @Rpc(description = "Starts recording sensor data to be available for polling.") 137 @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)138 public void startSensingTimed( 139 @RpcParameter(name = "sensorNumber", description = "1 = All, 2 = Accelerometer, 3 = Magnetometer and 4 = Light") Integer sensorNumber, 140 @RpcParameter(name = "delayTime", description = "Minimum time between readings in milliseconds") Integer delayTime) { 141 mSensorNumber = sensorNumber; 142 if (delayTime < 20) { 143 delayTime = 20; 144 } 145 mDelayTime = (long) (delayTime); 146 mLastTime = System.currentTimeMillis(); 147 if (mSensorListener == null) { 148 mSensorListener = new SensorValuesCollector(); 149 mSensorReadings = new Bundle(); 150 switch (mSensorNumber) { 151 case 1: 152 for (Sensor sensor : mSensorManager.getSensorList(Sensor.TYPE_ALL)) { 153 mSensorManager.registerListener(mSensorListener, sensor, 154 SensorManager.SENSOR_DELAY_FASTEST); 155 } 156 break; 157 case 2: 158 for (Sensor sensor : mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER)) { 159 mSensorManager.registerListener(mSensorListener, sensor, 160 SensorManager.SENSOR_DELAY_FASTEST); 161 } 162 break; 163 case 3: 164 for (Sensor sensor : mSensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD)) { 165 mSensorManager.registerListener(mSensorListener, sensor, 166 SensorManager.SENSOR_DELAY_FASTEST); 167 } 168 break; 169 case 4: 170 for (Sensor sensor : mSensorManager.getSensorList(Sensor.TYPE_LIGHT)) { 171 mSensorManager.registerListener(mSensorListener, sensor, 172 SensorManager.SENSOR_DELAY_FASTEST); 173 } 174 } 175 } 176 } 177 178 @Rpc(description = "Records to the Event Queue sensor data exceeding a chosen threshold.") 179 @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)180 public void startSensingThreshold( 181 182 @RpcParameter(name = "sensorNumber", description = "1 = Orientation, 2 = Accelerometer, 3 = Magnetometer and 4 = Light") Integer sensorNumber, 183 @RpcParameter(name = "threshold", description = "Threshold level for chosen sensor (integer)") Integer threshold, 184 @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 mSensorNumber = sensorNumber; 186 mXAxis = axis & 1; 187 mYAxis = axis & 2; 188 mZAxis = axis & 4; 189 if (mSensorNumber == 1) { 190 mThreshing = 0; 191 mThreshOrientation = 1; 192 mThreshold = ((float) threshold) / ((float) 1000); 193 } else { 194 mThreshing = 1; 195 mThreshold = (float) threshold; 196 } 197 startSensingTimed(mSensorNumber, 20); 198 } 199 200 @Rpc(description = "Returns the most recently recorded sensor data.") readSensors()201 public Bundle readSensors() { 202 if (mSensorReadings == null) { 203 return null; 204 } 205 synchronized (mSensorReadings) { 206 return new Bundle(mSensorReadings); 207 } 208 } 209 210 @Rpc(description = "Stops collecting sensor data.") 211 @RpcStopEvent("sensors") stopSensing()212 public void stopSensing() { 213 mSensorManager.unregisterListener(mSensorListener); 214 mSensorListener = null; 215 mSensorReadings = null; 216 mThreshing = 0; 217 mThreshOrientation = 0; 218 } 219 220 @Rpc(description = "Returns the most recently received accuracy value.") sensorsGetAccuracy()221 public Integer sensorsGetAccuracy() { 222 return mAccuracy; 223 } 224 225 @Rpc(description = "Returns the most recently received light value.") sensorsGetLight()226 public Float sensorsGetLight() { 227 return mLight; 228 } 229 230 @Rpc(description = "Returns the most recently received accelerometer values.", returns = "a List of Floats [(acceleration on the) X axis, Y axis, Z axis].") sensorsReadAccelerometer()231 public List<Float> sensorsReadAccelerometer() { 232 synchronized (mSensorReadings) { 233 return Arrays.asList(mXForce, mYForce, mZForce); 234 } 235 } 236 237 @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()238 public List<Float> sensorsReadMagnetometer() { 239 synchronized (mSensorReadings) { 240 return Arrays.asList(mXMag, mYMag, mZMag); 241 } 242 } 243 244 @Rpc(description = "Returns the most recently received orientation values.", returns = "a List of Doubles [azimuth, pitch, roll].") sensorsReadOrientation()245 public List<Double> sensorsReadOrientation() { 246 synchronized (mSensorReadings) { 247 return Arrays.asList(mAzimuth, mPitch, mRoll); 248 } 249 } 250 251 @Rpc(description = "Starts recording sensor data to be available for polling.") 252 @RpcDeprecated(value = "startSensingTimed or startSensingThreshhold", release = "4") startSensing( @pcParametername = "sampleSize", description = "number of samples for calculating average readings") @pcDefault"5") Integer sampleSize)253 public void startSensing( 254 @RpcParameter(name = "sampleSize", description = "number of samples for calculating average readings") @RpcDefault("5") Integer sampleSize) { 255 if (mSensorListener == null) { 256 startSensingTimed(1, 220); 257 } 258 } 259 260 @Override shutdown()261 public void shutdown() { 262 stopSensing(); 263 } 264 265 private class SensorValuesCollector implements SensorEventListener { 266 private final static int MATRIX_SIZE = 9; 267 268 private final RollingAverage mmAzimuth; 269 private final RollingAverage mmPitch; 270 private final RollingAverage mmRoll; 271 272 private float[] mmGeomagneticValues; 273 private float[] mmGravityValues; 274 private float[] mmR; 275 private float[] mmOrientation; 276 SensorValuesCollector()277 public SensorValuesCollector() { 278 mmAzimuth = new RollingAverage(); 279 mmPitch = new RollingAverage(); 280 mmRoll = new RollingAverage(); 281 } 282 postEvent()283 private void postEvent() { 284 mSensorReadings.putDouble("time", System.currentTimeMillis() / 1000.0); 285 mEventFacade.postEvent("sensors", mSensorReadings.clone()); 286 } 287 288 @Override onAccuracyChanged(Sensor sensor, int accuracy)289 public void onAccuracyChanged(Sensor sensor, int accuracy) { 290 if (mSensorReadings == null) { 291 return; 292 } 293 synchronized (mSensorReadings) { 294 mSensorReadings.putInt("accuracy", accuracy); 295 mAccuracy = accuracy; 296 297 } 298 } 299 300 @Override onSensorChanged(SensorEvent event)301 public void onSensorChanged(SensorEvent event) { 302 if (mSensorReadings == null) { 303 return; 304 } 305 synchronized (mSensorReadings) { 306 switch (event.sensor.getType()) { 307 case Sensor.TYPE_ACCELEROMETER: 308 mXForce = event.values[0]; 309 mYForce = event.values[1]; 310 mZForce = event.values[2]; 311 if (mThreshing == 0) { 312 mSensorReadings.putFloat("xforce", mXForce); 313 mSensorReadings.putFloat("yforce", mYForce); 314 mSensorReadings.putFloat("zforce", mZForce); 315 if ((mSensorNumber == 2) && (System.currentTimeMillis() > (mDelayTime + mLastTime))) { 316 mLastTime = System.currentTimeMillis(); 317 postEvent(); 318 } 319 } 320 if ((mThreshing == 1) && (mSensorNumber == 2)) { 321 if ((Math.abs(mXForce) > mThreshold) && (mXAxis == 1)) { 322 mSensorReadings.putFloat("xforce", mXForce); 323 postEvent(); 324 } 325 326 if ((Math.abs(mYForce) > mThreshold) && (mYAxis == 2)) { 327 mSensorReadings.putFloat("yforce", mYForce); 328 postEvent(); 329 } 330 331 if ((Math.abs(mZForce) > mThreshold) && (mZAxis == 4)) { 332 mSensorReadings.putFloat("zforce", mZForce); 333 postEvent(); 334 } 335 } 336 337 mmGravityValues = event.values.clone(); 338 break; 339 case Sensor.TYPE_MAGNETIC_FIELD: 340 mXMag = event.values[0]; 341 mYMag = event.values[1]; 342 mZMag = event.values[2]; 343 if (mThreshing == 0) { 344 mSensorReadings.putFloat("xMag", mXMag); 345 mSensorReadings.putFloat("yMag", mYMag); 346 mSensorReadings.putFloat("zMag", mZMag); 347 if ((mSensorNumber == 3) && (System.currentTimeMillis() > (mDelayTime + mLastTime))) { 348 mLastTime = System.currentTimeMillis(); 349 postEvent(); 350 } 351 } 352 if ((mThreshing == 1) && (mSensorNumber == 3)) { 353 if ((Math.abs(mXMag) > mThreshold) && (mXAxis == 1)) { 354 mSensorReadings.putFloat("xforce", mXMag); 355 postEvent(); 356 } 357 if ((Math.abs(mYMag) > mThreshold) && (mYAxis == 2)) { 358 mSensorReadings.putFloat("yforce", mYMag); 359 postEvent(); 360 } 361 if ((Math.abs(mZMag) > mThreshold) && (mZAxis == 4)) { 362 mSensorReadings.putFloat("zforce", mZMag); 363 postEvent(); 364 } 365 } 366 mmGeomagneticValues = event.values.clone(); 367 break; 368 case Sensor.TYPE_LIGHT: 369 mLight = event.values[0]; 370 if (mThreshing == 0) { 371 mSensorReadings.putFloat("light", mLight); 372 if ((mSensorNumber == 4) && (System.currentTimeMillis() > (mDelayTime + mLastTime))) { 373 mLastTime = System.currentTimeMillis(); 374 postEvent(); 375 } 376 } 377 if ((mThreshing == 1) && (mSensorNumber == 4)) { 378 if (mLight > mThreshold) { 379 mSensorReadings.putFloat("light", mLight); 380 postEvent(); 381 } 382 } 383 break; 384 385 } 386 if (mSensorNumber == 1) { 387 if (mmGeomagneticValues != null && mmGravityValues != null) { 388 if (mmR == null) { 389 mmR = new float[MATRIX_SIZE]; 390 } 391 if (SensorManager.getRotationMatrix(mmR, null, mmGravityValues, mmGeomagneticValues)) { 392 if (mmOrientation == null) { 393 mmOrientation = new float[3]; 394 } 395 SensorManager.getOrientation(mmR, mmOrientation); 396 mmAzimuth.add(mmOrientation[0]); 397 mmPitch.add(mmOrientation[1]); 398 mmRoll.add(mmOrientation[2]); 399 400 mAzimuth = mmAzimuth.get(); 401 mPitch = mmPitch.get(); 402 mRoll = mmRoll.get(); 403 if (mThreshOrientation == 0) { 404 mSensorReadings.putDouble("azimuth", mAzimuth); 405 mSensorReadings.putDouble("pitch", mPitch); 406 mSensorReadings.putDouble("roll", mRoll); 407 if ((mSensorNumber == 1) && (System.currentTimeMillis() > (mDelayTime + mLastTime))) { 408 mLastTime = System.currentTimeMillis(); 409 postEvent(); 410 } 411 } 412 if ((mThreshOrientation == 1) && (mSensorNumber == 1)) { 413 if ((mXAxis == 1) && (mXCrossed == 0)) { 414 if (Math.abs(mAzimuth) > ((double) mThreshold)) { 415 mSensorReadings.putDouble("azimuth", mAzimuth); 416 postEvent(); 417 mXCrossed = 1; 418 } 419 } 420 if ((mXAxis == 1) && (mXCrossed == 1)) { 421 if (Math.abs(mAzimuth) < ((double) mThreshold)) { 422 mSensorReadings.putDouble("azimuth", mAzimuth); 423 postEvent(); 424 mXCrossed = 0; 425 } 426 } 427 if ((mYAxis == 2) && (mYCrossed == 0)) { 428 if (Math.abs(mPitch) > ((double) mThreshold)) { 429 mSensorReadings.putDouble("pitch", mPitch); 430 postEvent(); 431 mYCrossed = 1; 432 } 433 } 434 if ((mYAxis == 2) && (mYCrossed == 1)) { 435 if (Math.abs(mPitch) < ((double) mThreshold)) { 436 mSensorReadings.putDouble("pitch", mPitch); 437 postEvent(); 438 mYCrossed = 0; 439 } 440 } 441 if ((mZAxis == 4) && (mZCrossed == 0)) { 442 if (Math.abs(mRoll) > ((double) mThreshold)) { 443 mSensorReadings.putDouble("roll", mRoll); 444 postEvent(); 445 mZCrossed = 1; 446 } 447 } 448 if ((mZAxis == 4) && (mZCrossed == 1)) { 449 if (Math.abs(mRoll) < ((double) mThreshold)) { 450 mSensorReadings.putDouble("roll", mRoll); 451 postEvent(); 452 mZCrossed = 0; 453 } 454 } 455 } 456 } 457 } 458 } 459 } 460 } 461 } 462 463 static class RollingAverage { 464 private final int mmSampleSize; 465 private final double mmData[]; 466 private int mmIndex = 0; 467 private boolean mmFilled = false; 468 private double mmSum = 0.0; 469 RollingAverage()470 public RollingAverage() { 471 mmSampleSize = 5; 472 mmData = new double[mmSampleSize]; 473 } 474 add(double value)475 public void add(double value) { 476 mmSum -= mmData[mmIndex]; 477 mmData[mmIndex] = value; 478 mmSum += mmData[mmIndex]; 479 ++mmIndex; 480 mmIndex %= mmSampleSize; 481 mmFilled = (!mmFilled) ? mmIndex == 0 : mmFilled; 482 } 483 get()484 public double get() throws IllegalStateException { 485 if (!mmFilled && mmIndex == 0) { 486 throw new IllegalStateException("No values to average."); 487 } 488 return (mmFilled) ? (mmSum / mmSampleSize) : (mmSum / mmIndex); 489 } 490 } 491 } 492