/*
* Copyright (C) 2016 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 <stdlib.h>
#include <string.h>
#include <timer.h>
#include <heap.h>
#include <plat/rtc.h>
#include <plat/syscfg.h>
#include <hostIntf.h>
#include <nanohubPacket.h>
#include <floatRt.h>
#include <seos.h>
#include <nanohub_math.h>
#include <algos/fusion.h>
#include <sensors.h>
#include <variant/sensType.h>
#include <limits.h>
#include <slab.h>
#define ORIENTATION_APP_VERSION 1
#define MAX_NUM_COMMS_EVENT_SAMPLES 15 // at most 15 samples can fit in one comms_event
#define NUM_COMMS_EVENTS_IN_FIFO 2 // This controls how often the hub needs to wake up
// in batching
// needs to be greater than max raw sensor rate ratio
#define FIFO_DEPTH (NUM_COMMS_EVENTS_IN_FIFO * MAX_NUM_COMMS_EVENT_SAMPLES)
/*
* FIFO_MARGIN: max raw sensor rate ratio is 8:1.
* If 2 batchs of high rate data comes before 1 low rate data, there can be at max 15 samples left
* in the FIFO
*/
#define FIFO_MARGIN 15
#define MAX_NUM_SAMPLES (FIFO_MARGIN + FIFO_DEPTH) // actual input sample fifo depth
#define EVT_SENSOR_ACC_DATA_RDY sensorGetMyEventType(SENS_TYPE_ACCEL)
#define EVT_SENSOR_GYR_DATA_RDY sensorGetMyEventType(SENS_TYPE_GYRO)
#define EVT_SENSOR_MAG_DATA_RDY sensorGetMyEventType(SENS_TYPE_MAG)
#define EVT_SENSOR_MAG_BIAS sensorGetMyEventType(SENS_TYPE_MAG_BIAS)
#define kGravityEarth 9.80665f
#define kRad2deg (180.0f / M_PI)
#define MIN_GYRO_RATE_HZ SENSOR_HZ(100.0f)
#define MAX_MAG_RATE_HZ SENSOR_HZ(50.0f)
enum
{
FUSION_FLAG_ENABLED = 0x01,
FUSION_FLAG_INITIALIZED = 0x08,
FUSION_FLAG_GAME_ENABLED = 0x10,
FUSION_FLAG_GAME_INITIALIZED = 0x20
};
enum RawSensorType
{
ACC,
GYR,
MAG,
NUM_OF_RAW_SENSOR
};
enum FusionSensorType
{
ORIENT,
GRAVITY,
GEOMAG,
LINEAR,
GAME,
ROTAT,
NUM_OF_FUSION_SENSOR
};
struct FusionSensorSample {
uint64_t time;
float x, y, z;
};
struct FusionSensor {
uint32_t handle;
struct TripleAxisDataEvent *ev;
uint64_t prev_time;
uint64_t latency;
uint32_t rate;
bool active;
bool use_gyro_data;
bool use_mag_data;
uint8_t idx;
};
struct FusionTask {
uint32_t tid;
uint32_t accelHandle;
uint32_t gyroHandle;
uint32_t magHandle;
struct Fusion fusion;
struct Fusion game;
struct FusionSensor sensors[NUM_OF_FUSION_SENSOR];
struct FusionSensorSample samples[NUM_OF_RAW_SENSOR][MAX_NUM_SAMPLES];
size_t sample_indices[NUM_OF_RAW_SENSOR];
size_t sample_counts[NUM_OF_RAW_SENSOR];
uint32_t counters[NUM_OF_RAW_SENSOR];
uint64_t ResamplePeriodNs[NUM_OF_RAW_SENSOR];
uint64_t last_time[NUM_OF_RAW_SENSOR];
struct TripleAxisDataPoint last_sample[NUM_OF_RAW_SENSOR];
uint32_t flags;
uint32_t raw_sensor_rate[NUM_OF_RAW_SENSOR];
uint64_t raw_sensor_latency;
uint8_t accel_client_cnt;
uint8_t gyro_client_cnt;
uint8_t mag_client_cnt;
};
static uint32_t FusionRates[] = {
SENSOR_HZ(12.5f),
SENSOR_HZ(25.0f),
SENSOR_HZ(50.0f),
SENSOR_HZ(100.0f),
SENSOR_HZ(200.0f),
0,
};
//should match "supported rates in length" and be the timer length for that rate in nanosecs
static const uint64_t rateTimerVals[] = {
1000000000ULL / 12.5f,
1000000000ULL / 25,
1000000000ULL / 50,
1000000000ULL / 100,
1000000000ULL / 200,
};
static struct FusionTask mTask;
#define DEC_INFO_RATE(name, rates, type, axis, inter, samples) \
.sensorName = name, \
.supportedRates = rates, \
.sensorType = type, \
.numAxis = axis, \
.interrupt = inter, \
.minSamples = samples
static const struct SensorInfo mSi[NUM_OF_FUSION_SENSOR] =
{
{ DEC_INFO_RATE("Orientation", FusionRates, SENS_TYPE_ORIENTATION, NUM_AXIS_THREE,
NANOHUB_INT_NONWAKEUP, 20) },
{ DEC_INFO_RATE("Gravity", FusionRates, SENS_TYPE_GRAVITY, NUM_AXIS_THREE,
NANOHUB_INT_NONWAKEUP, 20) },
{ DEC_INFO_RATE("Geomagnetic Rotation Vector", FusionRates, SENS_TYPE_GEO_MAG_ROT_VEC,
NUM_AXIS_THREE, NANOHUB_INT_NONWAKEUP, 20) },
{ DEC_INFO_RATE("Linear Acceleration", FusionRates, SENS_TYPE_LINEAR_ACCEL, NUM_AXIS_THREE,
NANOHUB_INT_NONWAKEUP, 20) },
{ DEC_INFO_RATE("Game Rotation Vector", FusionRates, SENS_TYPE_GAME_ROT_VECTOR, NUM_AXIS_THREE,
NANOHUB_INT_NONWAKEUP, 300) },
{ DEC_INFO_RATE("Rotation Vector", FusionRates, SENS_TYPE_ROTATION_VECTOR, NUM_AXIS_THREE,
NANOHUB_INT_NONWAKEUP, 20) },
};
static struct SlabAllocator *mDataSlab;
static void dataEvtFree(void *ptr)
{
slabAllocatorFree(mDataSlab, ptr);
}
static void fillSamples(struct TripleAxisDataEvent *ev, enum RawSensorType index)
{
bool bad_timestamp;
size_t i, w, n, num_samples;
struct TripleAxisDataPoint *curr_sample, *next_sample;
uint32_t counter;
uint64_t ResamplePeriodNs, curr_time, next_time;
uint64_t sample_spacing_ns;
float weight_next;
if (index == GYR && mTask.gyro_client_cnt == 0) {
return;
}
if (index == MAG && mTask.mag_client_cnt == 0) {
return;
}
n = mTask.sample_counts[index];
i = mTask.sample_indices[index];
counter = mTask.counters[index];
ResamplePeriodNs = mTask.ResamplePeriodNs[index];
w = (mTask.sample_indices[index] + n) % MAX_NUM_SAMPLES;
// check if this sensor was used before
if (mTask.last_time[index] == ULONG_LONG_MAX) {
curr_sample = ev->samples;
next_sample = curr_sample + 1;
num_samples = ev->samples[0].firstSample.numSamples;
curr_time = ev->referenceTime;
} else {
curr_sample = &mTask.last_sample[index];
next_sample = ev->samples;
num_samples = ev->samples[0].firstSample.numSamples + 1;
curr_time = mTask.last_time[index];
}
while (num_samples > 1) {
if (next_sample == ev->samples)
next_time = ev->referenceTime;
else
next_time = curr_time + next_sample->deltaTime;
// error handling for non-chronological accel timestamps
sample_spacing_ns = (next_time > curr_time) ? (next_time - curr_time) : 0;
// This can happen during sensor config changes
bad_timestamp = (sample_spacing_ns > 10 * ResamplePeriodNs);
// Check to see if we need to move the interpolation window or
// interpolate
if ((counter >= sample_spacing_ns) || bad_timestamp) {
num_samples--;
counter -= (bad_timestamp ? counter : sample_spacing_ns);
curr_sample = next_sample;
next_sample++;
curr_time = next_time;
} else {
weight_next = (float)counter / floatFromUint64(sample_spacing_ns);
mTask.samples[index][w].x = curr_sample->x + weight_next *
(next_sample->x - curr_sample->x);
mTask.samples[index][w].y = curr_sample->y + weight_next *
(next_sample->y - curr_sample->y);
mTask.samples[index][w].z = curr_sample->z + weight_next *
(next_sample->z - curr_sample->z);
mTask.samples[index][w].time = curr_time + counter;
// Move the read index when buffer is full
if (++n > MAX_NUM_SAMPLES) {
n = MAX_NUM_SAMPLES;
if (++i == MAX_NUM_SAMPLES) {
i = 0;
}
}
// Reset the write index
if (++w == MAX_NUM_SAMPLES) {
w = 0;
}
// Move to the next resample
counter += ResamplePeriodNs;
}
}
mTask.sample_counts[index] = n;
mTask.sample_indices[index] = i;
mTask.counters[index] = counter;
mTask.last_sample[index] = *curr_sample;
mTask.last_time[index] = curr_time;
}
static bool allocateDataEvt(struct FusionSensor *mSensor, uint64_t time)
{
mSensor->ev = slabAllocatorAlloc(mDataSlab);
if (mSensor->ev == NULL) {
// slab allocation failed, need to stop draining raw samples for now.
osLog(LOG_INFO, "ORIENTATION: slabAllocatorAlloc() Failed\n");
return false;
}
// delta time for the first sample is sample count
memset(&mSensor->ev->samples[0].firstSample, 0x00, sizeof(struct SensorFirstSample));
mSensor->ev->referenceTime = time;
mSensor->prev_time = time;
return true;
}
// returns false if addSample() fails
static bool addSample(struct FusionSensor *mSensor, uint64_t time, float x, float y, float z)
{
struct TripleAxisDataPoint *sample;
// Bypass processing this accel sample.
// This is needed after recovering from a slab shortage.
if (mSensor->prev_time == time) {
osLog(LOG_INFO, "Accel sample has been processed by fusion sensor %d\n",
mSensor->idx);
return true;
}
if (mSensor->ev == NULL) {
if (!allocateDataEvt(mSensor, time))
return false;
}
if (mSensor->ev->samples[0].firstSample.numSamples >= MAX_NUM_COMMS_EVENT_SAMPLES) {
osLog(LOG_ERROR, "ORIENTATION: BAD_INDEX\n");
return false;
}
sample = &mSensor->ev->samples[mSensor->ev->samples[0].firstSample.numSamples++];
if (mSensor->ev->samples[0].firstSample.numSamples > 1) {
sample->deltaTime = time > mSensor->prev_time ? (time - mSensor->prev_time) : 0;
mSensor->prev_time = time;
}
sample->x = x;
sample->y = y;
sample->z = z;
if (mSensor->ev->samples[0].firstSample.numSamples == MAX_NUM_COMMS_EVENT_SAMPLES) {
osEnqueueEvtOrFree(
EVENT_TYPE_BIT_DISCARDABLE | sensorGetMyEventType(mSi[mSensor->idx].sensorType),
mSensor->ev, dataEvtFree);
mSensor->ev = NULL;
}
return true;
}
// returns false if addSample fails for any fusion sensor
// (most likely due to slab allocation failure)
static bool updateOutput(ssize_t last_accel_sample_index, uint64_t last_sensor_time)
{
struct Vec4 attitude;
struct Vec3 g, a;
struct Mat33 R; // direction-cosine/rotation matrix, inertial -> device
bool rInited; // indicates if matrix R has been initialzed. for avoiding repeated computation
bool ret = true;
if (fusionHasEstimate(&mTask.game)) {
rInited = false;
if (mTask.sensors[GAME].active) {
fusionGetAttitude(&mTask.game, &attitude);
if (!addSample(&mTask.sensors[GAME],
last_sensor_time,
attitude.x,
attitude.y,
attitude.z)) {
ret = false;
}
}
if (mTask.sensors[GRAVITY].active) {
fusionGetRotationMatrix(&mTask.game, &R);
rInited = true;
initVec3(&g, R.elem[0][2], R.elem[1][2], R.elem[2][2]);
vec3ScalarMul(&g, kGravityEarth);
if (!addSample(&mTask.sensors[GRAVITY],
last_sensor_time,
g.x,
g.y,
g.z)) {
ret = false;
}
}
if (last_accel_sample_index >= 0
&& mTask.sensors[LINEAR].active) {
if (!rInited) {
fusionGetRotationMatrix(&mTask.game, &R);
}
initVec3(&g, R.elem[0][2], R.elem[1][2], R.elem[2][2]);
vec3ScalarMul(&g, kGravityEarth);
initVec3(&a,
mTask.samples[0][last_accel_sample_index].x,
mTask.samples[0][last_accel_sample_index].y,
mTask.samples[0][last_accel_sample_index].z);
if (!addSample(&mTask.sensors[LINEAR],
mTask.samples[0][last_accel_sample_index].time,
a.x - g.x,
a.y - g.y,
a.z - g.z)) {
ret = false;
}
}
}
if (fusionHasEstimate(&mTask.fusion)) {
fusionGetAttitude(&mTask.fusion, &attitude);
if (mTask.sensors[ORIENT].active) {
fusionGetRotationMatrix(&mTask.fusion, &R);
// x, y, z = yaw, pitch, roll
float x = atan2f(-R.elem[0][1], R.elem[0][0]) * kRad2deg;
float y = atan2f(-R.elem[1][2], R.elem[2][2]) * kRad2deg;
float z = asinf(R.elem[0][2]) * kRad2deg;
if (x < 0.0f) {
x += 360.0f;
}
if (!addSample(&mTask.sensors[ORIENT],
last_sensor_time,
x,
y,
z)) {
ret = false;
}
}
if (mTask.sensors[GEOMAG].active) {
if (!addSample(&mTask.sensors[GEOMAG],
last_sensor_time,
attitude.x,
attitude.y,
attitude.z)) {
ret = false;
}
}
if (mTask.sensors[ROTAT].active) {
if (!addSample(&mTask.sensors[ROTAT],
last_sensor_time,
attitude.x,
attitude.y,
attitude.z)) {
ret = false;
}
}
}
return ret;
}
static void drainSamples()
{
struct Vec3 a, w, m;
uint64_t a_time, g_time, m_time;
size_t i = mTask.sample_indices[ACC];
size_t j = 0;
size_t k = 0;
size_t which;
float dT;
bool success = true;
if (mTask.gyro_client_cnt > 0)
j = mTask.sample_indices[GYR];
if (mTask.mag_client_cnt > 0)
k = mTask.sample_indices[MAG];
// Keep draining raw samples and producing fusion samples only if
// 1) all raw sensors needed are present (to compare timestamp) and
// 2) updateOutput() succeeded (no slab shortage)
// Otherwise, wait till next raw sample event.
while (mTask.sample_counts[ACC] > 0
&& (!(mTask.gyro_client_cnt > 0) || mTask.sample_counts[GYR] > 0)
&& (!(mTask.mag_client_cnt > 0) || mTask.sample_counts[MAG] > 0)
&& success) {
a_time = mTask.samples[ACC][i].time;
g_time = mTask.gyro_client_cnt > 0 ? mTask.samples[GYR][j].time
: ULONG_LONG_MAX;
m_time = mTask.mag_client_cnt > 0 ? mTask.samples[MAG][k].time
: ULONG_LONG_MAX;
// priority with same timestamp: gyro > acc > mag
if (g_time <= a_time && g_time <= m_time) {
which = GYR;
} else if (a_time <= m_time) {
which = ACC;
} else {
which = MAG;
}
dT = floatFromUint64(mTask.ResamplePeriodNs[which]) * 1e-9f;
switch (which) {
case ACC:
initVec3(&a, mTask.samples[ACC][i].x, mTask.samples[ACC][i].y, mTask.samples[ACC][i].z);
if (mTask.flags & FUSION_FLAG_ENABLED)
fusionHandleAcc(&mTask.fusion, &a, dT);
if (mTask.flags & FUSION_FLAG_GAME_ENABLED)
fusionHandleAcc(&mTask.game, &a, dT);
success = updateOutput(i, mTask.samples[ACC][i].time);
// Do not remove the accel sample until all active fusion sesnsors
// successfully updated the output.
// Fusion sensors that have processed this accel sample will bypass
// it in addSample().
if (success) {
--mTask.sample_counts[ACC];
if (++i == MAX_NUM_SAMPLES) {
i = 0;
}
}
break;
case GYR:
initVec3(&w, mTask.samples[GYR][j].x, mTask.samples[GYR][j].y, mTask.samples[GYR][j].z);
if (mTask.flags & FUSION_FLAG_ENABLED)
fusionHandleGyro(&mTask.fusion, &w, dT);
if (mTask.flags & FUSION_FLAG_GAME_ENABLED)
fusionHandleGyro(&mTask.game, &w, dT);
--mTask.sample_counts[GYR];
if (++j == MAX_NUM_SAMPLES)
j = 0;
break;
case MAG:
initVec3(&m, mTask.samples[MAG][k].x, mTask.samples[MAG][k].y, mTask.samples[MAG][k].z);
fusionHandleMag(&mTask.fusion, &m, dT);
--mTask.sample_counts[MAG];
if (++k == MAX_NUM_SAMPLES)
k = 0;
break;
}
}
mTask.sample_indices[ACC] = i;
if (mTask.gyro_client_cnt > 0)
mTask.sample_indices[GYR] = j;
if (mTask.mag_client_cnt > 0)
mTask.sample_indices[MAG] = k;
for (i = ORIENT; i < NUM_OF_FUSION_SENSOR; i++) {
if (mTask.sensors[i].ev != NULL) {
osEnqueueEvtOrFree(EVENT_TYPE_BIT_DISCARDABLE | sensorGetMyEventType(mSi[i].sensorType),
mTask.sensors[i].ev, dataEvtFree);
mTask.sensors[i].ev = NULL;
}
}
}
static void configureFusion()
{
if (mTask.sensors[ORIENT].active
|| mTask.sensors[ROTAT].active
|| mTask.sensors[GEOMAG].active) {
mTask.flags |= FUSION_FLAG_ENABLED;
initFusion(&mTask.fusion,
(mTask.mag_client_cnt > 0 ? FUSION_USE_MAG : 0) |
(mTask.gyro_client_cnt > 0 ? FUSION_USE_GYRO : 0) |
((mTask.flags & FUSION_FLAG_INITIALIZED) ? 0 : FUSION_REINITIALIZE));
mTask.flags |= FUSION_FLAG_INITIALIZED;
} else {
mTask.flags &= ~FUSION_FLAG_ENABLED;
mTask.flags &= ~FUSION_FLAG_INITIALIZED;
}
}
static void configureGame()
{
if (mTask.sensors[GAME].active || mTask.sensors[GRAVITY].active ||
mTask.sensors[LINEAR].active) {
mTask.flags |= FUSION_FLAG_GAME_ENABLED;
initFusion(&mTask.game, FUSION_USE_GYRO |
((mTask.flags & FUSION_FLAG_INITIALIZED) ? 0 : FUSION_REINITIALIZE));
mTask.flags |= FUSION_FLAG_GAME_INITIALIZED;
} else {
mTask.flags &= ~FUSION_FLAG_GAME_ENABLED;
mTask.flags &= ~FUSION_FLAG_GAME_INITIALIZED;
}
}
static void fusionSetRateAcc(void)
{
int i;
if (mTask.accelHandle == 0) {
mTask.sample_counts[ACC] = 0;
mTask.sample_indices[ACC] = 0;
mTask.counters[ACC] = 0;
mTask.last_time[ACC] = ULONG_LONG_MAX;
for (i = 0; sensorFind(SENS_TYPE_ACCEL, i, &mTask.accelHandle) != NULL; i++) {
if (sensorRequest(mTask.tid, mTask.accelHandle, mTask.raw_sensor_rate[ACC],
mTask.raw_sensor_latency)) {
osEventSubscribe(mTask.tid, EVT_SENSOR_ACC_DATA_RDY);
break;
}
}
} else {
sensorRequestRateChange(mTask.tid, mTask.accelHandle, mTask.raw_sensor_rate[ACC],
mTask.raw_sensor_latency);
}
}
static void fusionSetRateGyr(void)
{
int i;
if (mTask.gyroHandle == 0) {
mTask.sample_counts[GYR] = 0;
mTask.sample_indices[GYR] = 0;
mTask.counters[GYR] = 0;
mTask.last_time[GYR] = ULONG_LONG_MAX;
for (i = 0; sensorFind(SENS_TYPE_GYRO, i, &mTask.gyroHandle) != NULL; i++) {
if (sensorRequest(mTask.tid, mTask.gyroHandle, mTask.raw_sensor_rate[GYR],
mTask.raw_sensor_latency)) {
osEventSubscribe(mTask.tid, EVT_SENSOR_GYR_DATA_RDY);
break;
}
}
} else {
sensorRequestRateChange(mTask.tid, mTask.gyroHandle, mTask.raw_sensor_rate[GYR],
mTask.raw_sensor_latency);
}
}
static void fusionSetRateMag(void)
{
int i;
if (mTask.magHandle == 0) {
mTask.sample_counts[MAG] = 0;
mTask.sample_indices[MAG] = 0;
mTask.counters[MAG] = 0;
mTask.last_time[MAG] = ULONG_LONG_MAX;
for (i = 0; sensorFind(SENS_TYPE_MAG, i, &mTask.magHandle) != NULL; i++) {
if (sensorRequest(mTask.tid, mTask.magHandle, mTask.raw_sensor_rate[MAG],
mTask.raw_sensor_latency)) {
osEventSubscribe(mTask.tid, EVT_SENSOR_MAG_DATA_RDY);
osEventSubscribe(mTask.tid, EVT_SENSOR_MAG_BIAS);
break;
}
}
} else {
sensorRequestRateChange(mTask.tid, mTask.magHandle, mTask.raw_sensor_rate[MAG],
mTask.raw_sensor_latency);
}
}
static bool fusionSetRate(uint32_t rate, uint64_t latency, void *cookie)
{
struct FusionSensor *mSensor = &mTask.sensors[(int)cookie];
int i;
uint32_t max_rate = 0;
uint32_t gyr_rate, mag_rate;
uint64_t min_resample_period = ULONG_LONG_MAX;
mSensor->rate = rate;
mSensor->latency = latency;
for (i = ORIENT; i < NUM_OF_FUSION_SENSOR; i++) {
if (mTask.sensors[i].active) {
max_rate = max_rate > mTask.sensors[i].rate ? max_rate : mTask.sensors[i].rate;
}
}
if (mTask.accel_client_cnt > 0) {
mTask.raw_sensor_rate[ACC] = max_rate;
mTask.ResamplePeriodNs[ACC] = sensorTimerLookupCommon(FusionRates, rateTimerVals, max_rate);
min_resample_period = mTask.ResamplePeriodNs[ACC] < min_resample_period ?
mTask.ResamplePeriodNs[ACC] : min_resample_period;
}
if (mTask.gyro_client_cnt > 0) {
gyr_rate = max_rate > MIN_GYRO_RATE_HZ ? max_rate : MIN_GYRO_RATE_HZ;
mTask.raw_sensor_rate[GYR] = gyr_rate;
mTask.ResamplePeriodNs[GYR] = sensorTimerLookupCommon(FusionRates, rateTimerVals, gyr_rate);
min_resample_period = mTask.ResamplePeriodNs[GYR] < min_resample_period ?
mTask.ResamplePeriodNs[GYR] : min_resample_period;
}
if (mTask.mag_client_cnt > 0) {
mag_rate = max_rate < MAX_MAG_RATE_HZ ? max_rate : MAX_MAG_RATE_HZ;
mTask.raw_sensor_rate[MAG] = mag_rate;
mTask.ResamplePeriodNs[MAG] = sensorTimerLookupCommon(FusionRates, rateTimerVals, mag_rate);
min_resample_period = mTask.ResamplePeriodNs[MAG] < min_resample_period ?
mTask.ResamplePeriodNs[MAG] : min_resample_period;
}
// This guarantees that local raw sensor FIFOs won't overflow.
mTask.raw_sensor_latency = min_resample_period * (FIFO_DEPTH - 1);
for (i = ORIENT; i < NUM_OF_FUSION_SENSOR; i++) {
if (mTask.sensors[i].active) {
mTask.raw_sensor_latency = mTask.sensors[i].latency < mTask.raw_sensor_latency ?
mTask.sensors[i].latency : mTask.raw_sensor_latency;
}
}
if (mTask.accel_client_cnt > 0)
fusionSetRateAcc();
if (mTask.gyro_client_cnt > 0)
fusionSetRateGyr();
if (mTask.mag_client_cnt > 0)
fusionSetRateMag();
if (mSensor->rate > 0)
sensorSignalInternalEvt(mSensor->handle, SENSOR_INTERNAL_EVT_RATE_CHG, rate, latency);
return true;
}
static bool fusionPower(bool on, void *cookie)
{
struct FusionSensor *mSensor = &mTask.sensors[(int)cookie];
int idx;
mSensor->active = on;
if (on == false) {
mTask.accel_client_cnt--;
if (mSensor->use_gyro_data)
mTask.gyro_client_cnt--;
if (mSensor->use_mag_data)
mTask.mag_client_cnt--;
// if client_cnt == 0 and Handle == 0, nothing need to be done.
// if client_cnt > 0 and Handle == 0, something else is turning it on, all will be done.
if (mTask.accel_client_cnt == 0 && mTask.accelHandle != 0) {
sensorRelease(mTask.tid, mTask.accelHandle);
mTask.accelHandle = 0;
osEventUnsubscribe(mTask.tid, EVT_SENSOR_ACC_DATA_RDY);
}
if (mTask.gyro_client_cnt == 0 && mTask.gyroHandle != 0) {
sensorRelease(mTask.tid, mTask.gyroHandle);
mTask.gyroHandle = 0;
osEventUnsubscribe(mTask.tid, EVT_SENSOR_GYR_DATA_RDY);
}
if (mTask.mag_client_cnt == 0 && mTask.magHandle != 0) {
sensorRelease(mTask.tid, mTask.magHandle);
mTask.magHandle = 0;
osEventUnsubscribe(mTask.tid, EVT_SENSOR_MAG_DATA_RDY);
}
idx = mSensor->idx;
(void) fusionSetRate(0, ULONG_LONG_MAX, (void *)idx);
} else {
mTask.accel_client_cnt++;
if (mSensor->use_gyro_data)
mTask.gyro_client_cnt++;
if (mSensor->use_mag_data)
mTask.mag_client_cnt++;
}
configureFusion();
configureGame();
sensorSignalInternalEvt(mSensor->handle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, on, 0);
return true;
}
static bool fusionFirmwareUpload(void *cookie)
{
struct FusionSensor *mSensor = &mTask.sensors[(int)cookie];
sensorSignalInternalEvt(mSensor->handle, SENSOR_INTERNAL_EVT_FW_STATE_CHG, 1, 0);
return true;
}
static bool fusionFlush(void *cookie)
{
struct FusionSensor *mSensor = &mTask.sensors[(int)cookie];
uint32_t evtType = sensorGetMyEventType(mSi[mSensor->idx].sensorType);
osEnqueueEvt(evtType, SENSOR_DATA_EVENT_FLUSH, NULL);
return true;
}
static void fusionHandleEvent(uint32_t evtType, const void* evtData)
{
struct TripleAxisDataEvent *ev;
int i;
if (evtData == SENSOR_DATA_EVENT_FLUSH)
return;
switch (evtType) {
case EVT_APP_START:
// check for gyro and mag
osEventUnsubscribe(mTask.tid, EVT_APP_START);
if (!sensorFind(SENS_TYPE_GYRO, 0, &mTask.gyroHandle)) {
for (i = ORIENT; i < NUM_OF_FUSION_SENSOR; i++)
mTask.sensors[i].use_gyro_data = false;
}
mTask.gyroHandle = 0;
if (!sensorFind(SENS_TYPE_MAG, 0, &mTask.magHandle)) {
for (i = ORIENT; i < NUM_OF_FUSION_SENSOR; i++)
mTask.sensors[i].use_mag_data = false;
}
mTask.magHandle = 0;
break;
case EVT_SENSOR_ACC_DATA_RDY:
ev = (struct TripleAxisDataEvent *)evtData;
fillSamples(ev, ACC);
drainSamples();
break;
case EVT_SENSOR_GYR_DATA_RDY:
ev = (struct TripleAxisDataEvent *)evtData;
fillSamples(ev, GYR);
drainSamples();
break;
case EVT_SENSOR_MAG_BIAS:
ev = (struct TripleAxisDataEvent *)evtData;
if (ev->samples[0].firstSample.biasPresent && mTask.flags & FUSION_FLAG_ENABLED) {
//it is a user initiated mag cal event
fusionSetMagTrust(&mTask.fusion, MANUAL_MAG_CAL);
}
break;
case EVT_SENSOR_MAG_DATA_RDY:
ev = (struct TripleAxisDataEvent *)evtData;
fillSamples(ev, MAG);
drainSamples();
break;
}
}
static const struct SensorOps mSops =
{
.sensorPower = fusionPower,
.sensorFirmwareUpload = fusionFirmwareUpload,
.sensorSetRate = fusionSetRate,
.sensorFlush = fusionFlush,
};
static bool fusionStart(uint32_t tid)
{
size_t i, slabSize;
mTask.tid = tid;
mTask.flags = 0;
for (i = 0; i < NUM_OF_RAW_SENSOR; i++) {
mTask.sample_counts[i] = 0;
mTask.sample_indices[i] = 0;
}
for (i = ORIENT; i < NUM_OF_FUSION_SENSOR; i++) {
mTask.sensors[i].handle = sensorRegister(&mSi[i], &mSops, (void *)i, true);
mTask.sensors[i].idx = i;
mTask.sensors[i].use_gyro_data = true;
mTask.sensors[i].use_mag_data = true;
}
mTask.sensors[GEOMAG].use_gyro_data = false;
mTask.sensors[GAME].use_mag_data = false;
mTask.sensors[GRAVITY].use_mag_data = false;
mTask.sensors[LINEAR].use_mag_data = false;
mTask.accel_client_cnt = 0;
mTask.gyro_client_cnt = 0;
mTask.mag_client_cnt = 0;
slabSize = sizeof(struct TripleAxisDataEvent)
+ MAX_NUM_COMMS_EVENT_SAMPLES * sizeof(struct TripleAxisDataPoint);
// worst case 6 output sensors * (N + 1) comms_events
mDataSlab = slabAllocatorNew(slabSize, 4, 6 * (NUM_COMMS_EVENTS_IN_FIFO + 1));
if (!mDataSlab) {
osLog(LOG_ERROR, "ORIENTATION: slabAllocatorNew() FAILED\n");
return false;
}
osEventSubscribe(mTask.tid, EVT_APP_START);
return true;
}
static void fusionEnd()
{
mTask.flags &= ~FUSION_FLAG_INITIALIZED;
mTask.flags &= ~FUSION_FLAG_GAME_INITIALIZED;
slabAllocatorDestroy(mDataSlab);
}
INTERNAL_APP_INIT(
APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 4),
ORIENTATION_APP_VERSION,
fusionStart,
fusionEnd,
fusionHandleEvent);