OSDN Git Service

libsensors: Integrate the quaternion sensor and use for compass
authorAndy Ross <andrew.j.ross@intel.com>
Thu, 17 Oct 2013 18:38:21 +0000 (11:38 -0700)
committerMattias Pettersson <mattias.pettersson@intel.com>
Wed, 4 Dec 2013 14:13:12 +0000 (15:13 +0100)
The sensor hub has a built-in integrated sensor integration scheme
which emits a unified quaternion orientation.  Use this in lieu of
Google's SensorFusion software implementation.

Also: the sensor hub magnetometer emits uncalibrated data because of
requirements on other platforms, but Android demands calibrated
numbers.  Instead of doing magnetometer calibration in the HAL, use
the orientation output to derive a faked "magenetometer" which always
points to magnetic north along the ground plane.  Call this a "leveled
compass".  This behavior is apparently kosher: future (JB 4.3+) API
versions separate the existing "calibrated" compass output from a new
"raw" one which matches the sensor hub behavior.

Finally, the leveled compass implementation requires a little rework
to the SensorIIODev class to allow for reference counting of the
stream (so that it can be active when the sensor itself is disabled).

Issue: AXIA-3283
Change-Id: I16e90fa1119f908e979ca1fa010b2dea7f136deb
Depends-Change-Id: Id4feb36f32d0033f744752e238717bcd163aebb5
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
Reviewed-on: https://otc-android.intel.com/gerrit/22344
Tested-by: jenkins autobuilder
Reviewed-by: Brian E Woodruff <brian.e.woodruff@intel.com>
Works-for-me: jenkins autobuilder
Reviewed-by: Daniel Leung <daniel.leung@intel.com>
Reviewed-by: Robert Chiras <robert.chiras@intel.com>
bigcore/libsensors/Android.mk
bigcore/libsensors/BoardConfig.cpp
bigcore/libsensors/SensorConfig.h
common/libsensors/RotVecSensor.cpp [new file with mode: 0644]
common/libsensors/RotVecSensor.h [new file with mode: 0644]
common/libsensors/SensorIIODev.cpp
common/libsensors/SensorIIODev.h
common/libsensors/SynthCompassSensor.cpp [new file with mode: 0644]
common/libsensors/SynthCompassSensor.h [new file with mode: 0644]
common/libsensors/common.h
common/libsensors/sensors.cpp

index 177f72d..f1b8979 100644 (file)
@@ -36,6 +36,8 @@ sensor_src_files := $(common_src_path)/HidSensor_Accel3D.cpp \
                    $(common_src_path)/HidSensor_Gyro3D.cpp \
                    $(common_src_path)/HidSensor_Compass3D.cpp \
                    $(common_src_path)/HidSensor_ALS.cpp \
+                   $(common_src_path)/RotVecSensor.cpp \
+                   $(common_src_path)/SynthCompassSensor.cpp \
 
 include external/stlport/libstlport.mk
 LOCAL_C_INCLUDES += $(LOCAL_PATH) device/intel/common/libsensors
index 5e92105..31db853 100644 (file)
 #include "HidSensor_Gyro3D.h"
 #include "HidSensor_Compass3D.h"
 #include "HidSensor_ALS.h"
+#include "RotVecSensor.h"
+#include "SynthCompassSensor.h"
 
 static const struct sensor_t sSensorList[] = {
     AccelSensor::sSensorInfo_accel3D,
     GyroSensor::sSensorInfo_gyro3D,
-    CompassSensor::sSensorInfo_compass3D,
+    SynthCompassSensor::sSensorInfo_compass,
     ALSSensor::sSensorInfo_als,
+    RotVecSensor::sSensorInfo_rotvec,
 };
 
 const struct sensor_t* BoardConfig::sensorList()
@@ -94,6 +97,8 @@ void BoardConfig::initSensors(SensorBase* sensors[])
     sensors[gyro] = new GyroSensor();
     sensors[compass] = new CompassSensor();
     sensors[light] = new ALSSensor();
+    sensors[rotvec] = new RotVecSensor();
+    sensors[syncompass] = new SynthCompassSensor();
 }
 
 int BoardConfig::handleToDriver(int handle)
@@ -110,6 +115,10 @@ int BoardConfig::handleToDriver(int handle)
         return gyro;
     case ID_L:
         return light;
+    case ID_R:
+        return rotvec;
+    case ID_SC:
+        return syncompass;
   default:
         return -EINVAL;
     }
index bd18eb9..6ff565e 100644 (file)
@@ -27,6 +27,8 @@ enum {
     gyro,
     compass,
     light,
+    rotvec,
+    syncompass,
     numSensorDrivers,
     numFds,
 };
diff --git a/common/libsensors/RotVecSensor.cpp b/common/libsensors/RotVecSensor.cpp
new file mode 100644 (file)
index 0000000..6c19e33
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <cutils/log.h>
+#include "common.h"
+#include "SensorConfig.h"
+#include "SynthCompassSensor.h"
+#include "RotVecSensor.h"
+
+const struct sensor_t RotVecSensor::sSensorInfo_rotvec = {
+    .name       = "HID_SENSOR Rotation Vector",
+    .vendor     = "Intel",
+    .version    = 1,
+    .handle     = SENSORS_ROT_VEC_HANDLE,
+    .type       = SENSOR_TYPE_ROTATION_VECTOR,
+    .maxRange   = 1.0,
+    .resolution = 1./512, // Advertise as 9 bits, no idea about reality
+    .power      = 0.1f,
+    .minDelay   = 0,
+    .reserved   = {},
+};
+
+RotVecSensor::RotVecSensor()
+    : SensorIIODev("dev_rotation",  // name
+                   "in_rot_scale",  // units sysfs node
+                   "in_rot_offset", // exponent sysfs node
+                   "in_rot_",       // channel_prefix
+                   10)              // retry count
+{
+    mPendingEvent.version = sizeof(sensors_event_t);
+    mPendingEvent.sensor = ID_R;
+    mPendingEvent.type = SENSOR_TYPE_ROTATION_VECTOR;
+    memset(mPendingEvent.data, 0, sizeof(mPendingEvent.data));
+
+    sample_delay_min_ms = 50; // 20Hz default
+
+    mSynthCompass = NULL;
+}
+
+int RotVecSensor::processEvent(unsigned char *data, size_t len)
+{
+    if (IsDeviceInitialized() == false) {
+        ALOGE("Device was not initialized \n");
+        return -1;
+    }
+
+    if (len < 4*sizeof(unsigned int)) {
+        ALOGE("Insufficient length \n");
+        return -1;
+    }
+
+    // The Intel Sensor Hub emits a normalized x/y/z/w quaternion
+    // which acts to rotate points in the device coordinate system
+    // (left/up/out) to the world coordinate system (north/east/down).
+    // This is pleasingly identical to the Android convention, so just
+    // copy out the raw data.
+
+    unsigned int *sample = (unsigned int*)data;
+    long ex = GetExponentValue();
+    for (int i=0; i<4; i++) {
+        int sz = GetChannelBytesUsedSize(i);
+        mPendingEvent.data[i] = convert_from_vtf_format(sz, ex, sample[i]);
+    }
+
+    if (mSynthCompass)
+        mSynthCompass->setQuaternion(mPendingEvent.data);
+
+    return 0;
+}
diff --git a/common/libsensors/RotVecSensor.h b/common/libsensors/RotVecSensor.h
new file mode 100644 (file)
index 0000000..443fea4
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef HIDSENSOR_ROTVEC_H
+#define HIDSENSOR_ROTVEC_H
+
+#include "SensorIIODev.h"
+
+class SynthCompassSensor;
+
+class RotVecSensor : public SensorIIODev {
+
+public:
+    RotVecSensor();
+    virtual int processEvent(unsigned char *data, size_t len);
+    void setSynthCompass(SynthCompassSensor *c) { mSynthCompass = c; }
+
+    static const struct sensor_t sSensorInfo_rotvec;
+
+ private:
+    SynthCompassSensor *mSynthCompass;
+
+    friend class SynthCompassSensor;
+};
+#endif
index dfc2638..2e0c3b5 100644 (file)
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <algorithm>
 #include <dirent.h>
 #include <fcntl.h>
 #include <cutils/log.h>
@@ -20,6 +21,8 @@
 #include "SensorIIODev.h"
 #include "Helpers.h"
 
+using namespace std;
+
 static const std::string IIO_DIR = "/sys/bus/iio/devices";
 static const int DEF_BUFFER_LEN = 2;
 static const int DEF_HYST_VALUE = 0;
@@ -37,6 +40,7 @@ SensorIIODev::SensorIIODev(const std::string& dev_name, const std::string& units
                units_value(0),
                retry_count(retry_cnt),
                raw_buffer(NULL),
+               mRefCount(0),
                sample_delay_min_ms(0)
 {
     ALOGV("%s", __func__);
@@ -54,7 +58,8 @@ SensorIIODev::SensorIIODev(const std::string& dev_name, const std::string& units
                unit_expo_value(0),
                units_value(0),
                retry_count(1),
-               raw_buffer(NULL)
+               raw_buffer(NULL),
+               mRefCount(0)
 {
 
     ALOGV("%s", __func__);
@@ -119,14 +124,38 @@ int SensorIIODev::FreeRxBuffer()
 
 int SensorIIODev::enable(int enabled)
 {
+    // startStop() handles the internal sensor stream, which may be
+    // used by slaves.  The mEnabled flag is what gates the output of
+    // this particular sensor.
+    int ret = startStop(enabled);
+    if (ret)
+        return ret;
+    mEnabled = enabled;
+    return 0;
+}
+
+int SensorIIODev::startStop(int enabled)
+{
+    int alive = mRefCount > 0;
+    mRefCount = max(0, enabled ? mRefCount+1 : mRefCount-1);
+    int alivenew = mRefCount > 0;
+    if (alive == alivenew)
+        return 0;
+
     int ret =0;
 
     ALOGD(">>%s enabled:%d", __func__, enabled);
 
-    if (mEnabled == enabled) {
-        return 0;
-    }
     if (enabled){
+        if ((ret = discover()) < 0) {
+            ALOGE("discover failed: %s\n", device_name.c_str());
+            return ret;
+        }
+        if ((ret = open()) < 0) {
+            ALOGE("open failed: %s\n", device_name.c_str());
+            return ret;
+        }
+
         // QUIRK: some sensor hubs need to be turned on and off and on
         // before sending any information. So we turn it on and off first
         // before enabling again later in this function.
@@ -147,10 +176,8 @@ int SensorIIODev::enable(int enabled)
             goto err_ret;
         if (AllocateRxBuffer() < 0)
             goto err_ret;
-        mEnabled = enabled;
     }
     else{
-        mEnabled = enabled;
         if (SetDataReadyTrigger(GetDeviceNumber(), false) < 0)
             goto err_ret;
         if (EnableBuffer(0) < 0)
@@ -162,10 +189,12 @@ int SensorIIODev::enable(int enabled)
         if (FreeRxBuffer() < 0)
             goto err_ret;
         mDevPath = "";
+        close();
     }
     return 0;
 
 err_ret:
+    close();
     ALOGE("SesnorIIO: Enable failed\n");
     return -1;
 }
@@ -638,10 +667,15 @@ int SensorIIODev::readEvents(sensors_event_t *data, int count){
     if(read_size != datum_size) {
         ALOGE("read() error (or short count) from IIO device: %d\n", errno);
     } else {
+        // Always call processEvent(), but only emit output if
+        // enabled.  We may have slave devices (e.g. we could be a
+        // RotVec serving a SynthCompass without clients of our own).
         if (processEvent(raw_buffer, datum_size) >= 0) {
-            mPendingEvent.timestamp = getTimestamp();
-            *data = mPendingEvent;
-            numEventReceived++;
+            if (mEnabled) {
+                mPendingEvent.timestamp = getTimestamp();
+                *data = mPendingEvent;
+                numEventReceived++;
+            }
         }
     }
     return numEventReceived;
index e01521a..2372ad0 100644 (file)
@@ -76,6 +76,7 @@ private:
     long units_value;
     int retry_count;
     unsigned char *raw_buffer;
+    int mRefCount;
 
     int discover();
     int EnableIIODevice();
@@ -128,5 +129,7 @@ public:
     SensorIIODev(const std::string& dev_name, const std::string& units, const std::string& exponent, const std::string& channel_prefix);
     SensorIIODev(const std::string& dev_name, const std::string& units, const std::string& exponent, const std::string& channel_prefix, int retry_cnt);
 
+    // start/stop stream without changing "enabled" status. For slaves.
+    int startStop(int enabled);
 };
 #endif
diff --git a/common/libsensors/SynthCompassSensor.cpp b/common/libsensors/SynthCompassSensor.cpp
new file mode 100644 (file)
index 0000000..214dc91
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <cutils/log.h>
+#include "common.h"
+#include "SensorConfig.h"
+#include "SynthCompassSensor.h"
+
+// This is a "2D" faked compass.  The Intel sensor hub reports
+// uncalibrated/raw magnetometer data because of Microsoft
+// requirements for Windows sensor data.  Android has no calibration
+// built into the frameworks and relies on the HAL to do it.  Which is
+// sad, because that calibration is already being done internal to the
+// sensor hub for use by the integrated orientation/quaternion
+// sensors.
+//
+// Instead of doing the calibration again, we're just using that
+// output quaternion by returning a faked "magnetometer" reading
+// pointing directly at magnetic north.  This isn't quite correct, as
+// real magnetic fields are 3D vectors with (typically) non-zero
+// elevations.  The output from this sensor is always constrained to
+// lie along the ground plane.
+
+#define MAG_FIELD_UT 45 // Microtessla "field strength" reported
+
+const struct sensor_t SynthCompassSensor::sSensorInfo_compass = {
+    .name       = "HID_SENSOR Leveled Compass",
+    .vendor     = "Intel",
+    .version    = 1,
+    .handle     = SENSORS_SYNCOMPASS_HANDLE,
+    .type       = SENSOR_TYPE_MAGNETIC_FIELD,
+    .maxRange   = MAG_FIELD_UT * 2,
+    .resolution = MAG_FIELD_UT/256.0, // IIO reports 8 bit samples
+    .power      = 0.1f,
+    .minDelay   = 0,
+    .reserved   = {},
+};
+
+SynthCompassSensor::SynthCompassSensor()
+{
+    mPendingEvent.version = sizeof(sensors_event_t);
+    mPendingEvent.sensor = ID_SC;
+    mPendingEvent.type = SENSOR_TYPE_MAGNETIC_FIELD;
+    memset(mPendingEvent.data, 0, sizeof(mPendingEvent.data));
+}
+
+// Converts a quaternion (need not be normalized) into a 3x3 rotation
+// matrix.  Form and convetions are basically verbatim from Ken
+// Shoemake's quatut.pdf.
+static void quat2mat(const float q[4], float m[9])
+{
+    float x=q[0], y=q[1], z=q[2], w=q[3];
+    float norm = 1/sqrt(x*x+y*y+z*z+w*w);
+    x *= norm; y *= norm; z *= norm; w *= norm;
+    float result[] = { 1-2*(y*y+z*z),   2*(x*y-w*z),   2*(x*z+w*y),
+                         2*(x*y+w*z), 1-2*(x*x+z*z),   2*(y*z-w*x),
+                         2*(x*z-w*y),   2*(y*z+w*x), 1-2*(x*x+y*y) };
+    memcpy(m, result, 9*sizeof(float));
+}
+
+// Multiplies a 3-vector by a 3x3 matrix in row-major order
+static void matmul(const float m[9], const float v[3], float out[3])
+{
+    float x=v[0], y=v[1], z=v[2];
+    out[0] = x*m[0] + y*m[1] + z*m[2];
+    out[1] = x*m[3] + y*m[4] + z*m[5];
+    out[2] = x*m[6] + y*m[7] + z*m[8];
+}
+
+int SynthCompassSensor::enable(int en)
+{
+    if (!mRotVec)
+        return -1;
+    mRotVec->startStop(en);
+    mEnabled = en;
+    return 0;
+}
+
+int SynthCompassSensor::readEvents(sensors_event_t *data, int count)
+{
+    if (!mHasPendingEvent)
+        return 0;
+
+    mHasPendingEvent = false;
+    mPendingEvent.timestamp = getTimestamp();
+    *data = mPendingEvent;
+    return 1;
+}
+
+void SynthCompassSensor::setQuaternion(float qin[4])
+{
+    if (!mEnabled)
+        return;
+
+    // The quat defines a rotation from device coordinates to world
+    // coordinates, so invert it and generate a matrix that takes
+    // world coords to device space.
+    float q[4];
+    memcpy(q, qin, sizeof(float)*4);
+    q[0] *= -1;  q[1] *= -1; q[2] *= -1;
+
+    float mat[9];
+    quat2mat(q, mat);
+
+    // "North" is alone the Y axis in world coordiantes, convert to
+    // device space
+    static const float v[] = { 0, MAG_FIELD_UT, 0 };
+    matmul(mat, v, mPendingEvent.data);
+    mPendingEvent.timestamp = getTimestamp();
+
+    mHasPendingEvent = true;
+}
diff --git a/common/libsensors/SynthCompassSensor.h b/common/libsensors/SynthCompassSensor.h
new file mode 100644 (file)
index 0000000..be230f6
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef HIDSENSOR_SYNTHCOMPASS_H
+#define HIDSENSOR_SYNTHCOMPASS_H
+
+#include "SensorIIODev.h"
+#include "RotVecSensor.h"
+
+class RotVecSensor;
+
+class SynthCompassSensor : public SensorBase {
+
+public:
+    SynthCompassSensor();
+
+    void setRotVecSensor(RotVecSensor *s) { mRotVec = s; }
+
+    // From the RotationVector sensor
+    void setQuaternion(float q[4]);
+
+    // From SensorBase:
+    virtual int open() { return mRotVec != NULL; }
+    virtual void close() {}
+    virtual int enable(int en);
+    virtual int readEvents(sensors_event_t *data, int count);
+    virtual int setDelay(int64_t ns) { return mRotVec ? mRotVec->setDelay(ns) : -1; }
+
+    static const struct sensor_t sSensorInfo_compass;
+
+ private:
+    RotVecSensor *mRotVec;
+};
+#endif
index d901d5c..5b10429 100644 (file)
@@ -29,7 +29,9 @@
 #define ID_GY (5)
 #define ID_PR (6)
 #define ID_T  (7)
-#define MAX_SENSOR_CNT (ID_T+1)
+#define ID_R  (8) // rotation vector
+#define ID_SC (9) // "faked" level compass
+#define MAX_SENSOR_CNT (ID_SC+1)
 
 #define SENSORS_ACCELERATION     (1<<ID_A)
 #define SENSORS_MAGNETIC_FIELD   (1<<ID_M)
@@ -39,6 +41,8 @@
 #define SENSORS_GYROSCOPE        (1<<ID_GY)
 #define SENSORS_PRESSURE         (1<<ID_PR)
 #define SENSORS_TEMPERATURE      (1<<ID_T)
+#define SENSORS_ROT_VEC          (1<<ID_R)
+#define SENSORS_SYNCOMPASS       (1<<ID_SC)
 
 #define SENSORS_ACCELERATION_HANDLE     0
 #define SENSORS_MAGNETIC_FIELD_HANDLE   1
@@ -48,6 +52,8 @@
 #define SENSORS_GYROSCOPE_HANDLE        5
 #define SENSORS_PRESSURE_HANDLE         6
 #define SENSORS_TEMPERATURE_HANDLE      7
+#define SENSORS_ROT_VEC_HANDLE          8
+#define SENSORS_SYNCOMPASS_HANDLE       9
 
 /* The next one is used to set the timeout used for polling the file
  * descriptors.
index 93f9742..ace9019 100644 (file)
@@ -38,6 +38,9 @@
 #include "SensorConfig.h"
 #include "BoardConfig.h"
 
+#include "RotVecSensor.h"
+#include "SynthCompassSensor.h"
+
 /*****************************************************************************/
 
 /* The SENSORS Module */
@@ -102,6 +105,15 @@ sensors_poll_context_t::sensors_poll_context_t()
         }
     }
 
+    // Mild hack: the synthetic compass is implemented as a slave of
+    // the rotation vector, so we have to tell them about each other
+    // explicitly in lieu of an architecture that lets them probe.
+    SensorBase *rv = mSensors[rotvec], *sc = mSensors[syncompass];
+    if (rv && sc) {
+        ((RotVecSensor*)rv)->setSynthCompass((SynthCompassSensor*)sc);
+        ((SynthCompassSensor*)sc)->setRotVecSensor((RotVecSensor*)rv);
+    }
+
     int wakeFds[2];
     int result = pipe(wakeFds);
     ALOGE_IF(result<0, "error creating wake pipe (%s)", strerror(errno));
@@ -131,14 +143,8 @@ int sensors_poll_context_t::activate(int handle, int enabled) {
     int err = 0;
 
     if (enabled) {
-        err = s->discover();
-        if (err < 0) {
-            return err;
-        }
-        if (s->open() > 0) {
-            if ((err = s->enable(1)) < 0)
-                s->close();
-        }
+        if ((err = s->enable(1)) < 0)
+            s->close();
     } else {
         s->enable(0);
         s->close();
@@ -172,6 +178,7 @@ int sensors_poll_context_t::pollEvents(sensors_event_t* data, int count)
         // see if we have some leftover from the last poll()
         for (int i=0 ; count && i<numSensorDrivers ; i++) {
             SensorBase* const sensor(mSensors[i]);
+            mPollFds[i].fd = sensor->fd();
             if (sensor && ((mPollFds[i].revents & POLLIN) || (sensor->hasPendingEvents()))) {
                 int nb = sensor->readEvents(data, count);
                 if (nb < count) {
@@ -189,6 +196,20 @@ int sensors_poll_context_t::pollEvents(sensors_event_t* data, int count)
             }
         }
 
+        // Some sensors ("slaves") may have generated events based on
+        // the input to the sensors above.  So check again.
+        for (int i=0 ; count && i<numSensorDrivers ; i++) {
+            SensorBase *sensor = mSensors[i];
+            if (sensor->hasPendingEvents()) {
+                int nb = sensor->readEvents(data, count);
+                if (nb > 0) {
+                    count -= nb;
+                    nbEvents += nb;
+                    data += nb;
+                }
+            }
+        }
+
         if (count) {
             // we still have some room, so try to see if we can get
             // some events immediately or just wait if we don't have