Home | History | Annotate | Download | only in facade
      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 (at) gmail.com)
     93  * @author Felix Arends (felix.arends (at) gmail.com)
     94  * @author Alexey Reznichenko (alexey.reznichenko (at) gmail.com)
     95  * @author Robbie Mathews (rjmatthews62 (at) gmail.com)
     96  * @author John Karwatzki (jokar49 (at) 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 
    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")
    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")
    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.")
    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")
    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.")
    226   public Integer sensorsGetAccuracy() {
    227     return mAccuracy;
    228   }
    229 
    230   @Rpc(description = "Returns the most recently received light value.")
    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].")
    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].")
    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].")
    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")
    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
    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 
    282     public SensorValuesCollector() {
    283       mmAzimuth = new RollingAverage();
    284       mmPitch = new RollingAverage();
    285       mmRoll = new RollingAverage();
    286     }
    287 
    288     private void postEvent() {
    289       mSensorReadings.putDouble("time", System.currentTimeMillis() / 1000.0);
    290       mEventFacade.postEvent("sensors", mSensorReadings.clone());
    291     }
    292 
    293     @Override
    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
    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 
    475     public RollingAverage() {
    476       mmSampleSize = 5;
    477       mmData = new double[mmSampleSize];
    478     }
    479 
    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 
    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