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
17package com.googlecode.android_scripting.facade;
18
19import android.content.Context;
20import android.hardware.Sensor;
21import android.hardware.SensorEvent;
22import android.hardware.SensorEventListener;
23import android.hardware.SensorManager;
24import android.os.Bundle;
25
26import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
27import com.googlecode.android_scripting.rpc.Rpc;
28import com.googlecode.android_scripting.rpc.RpcDefault;
29import com.googlecode.android_scripting.rpc.RpcDeprecated;
30import com.googlecode.android_scripting.rpc.RpcParameter;
31import com.googlecode.android_scripting.rpc.RpcStartEvent;
32import com.googlecode.android_scripting.rpc.RpcStopEvent;
33
34import java.util.Arrays;
35import 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 */
93public 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
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")
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")
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.")
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")
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.")
221  public Integer sensorsGetAccuracy() {
222    return mAccuracy;
223  }
224
225  @Rpc(description = "Returns the most recently received light value.")
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].")
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].")
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].")
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")
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
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
277    public SensorValuesCollector() {
278      mmAzimuth = new RollingAverage();
279      mmPitch = new RollingAverage();
280      mmRoll = new RollingAverage();
281    }
282
283    private void postEvent() {
284      mSensorReadings.putDouble("time", System.currentTimeMillis() / 1000.0);
285      mEventFacade.postEvent("sensors", mSensorReadings.clone());
286    }
287
288    @Override
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
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
470    public RollingAverage() {
471      mmSampleSize = 5;
472      mmData = new double[mmSampleSize];
473    }
474
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
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