• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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