/* * 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 #include #include #include #include #include #include #include #include #include #include #include #define TILT_APP_VERSION 1 #define EVT_SENSOR_ANY_MOTION sensorGetMyEventType(SENS_TYPE_ANY_MOTION) #define EVT_SENSOR_NO_MOTION sensorGetMyEventType(SENS_TYPE_NO_MOTION) #define EVT_SENSOR_ACCEL sensorGetMyEventType(SENS_TYPE_ACCEL) #define ACCEL_MIN_RATE SENSOR_HZ(50) #define ACCEL_MAX_LATENCY 250000000ull // 250 ms #define BATCH_TIME 2000000000ull // 2.0 seconds #define ANGLE_THRESH (0.819 * 9.81 * 9.81) // ~cos(35) * (1G in m/s^2)^2 struct TiltAlgoState { uint64_t this_batch_init_ts; uint32_t this_batch_num_samples; float this_batch_sample_sum[3]; float this_batch_g[3]; float last_ref_g_vector[3]; bool last_ref_g_vector_valid; bool anamoly_this_batch; bool tilt_detected; }; static struct TiltDetectionTask { struct TiltAlgoState algoState; uint32_t taskId; uint32_t handle; uint32_t anyMotionHandle; uint32_t noMotionHandle; uint32_t accelHandle; enum { STATE_DISABLED, STATE_AWAITING_ANY_MOTION, STATE_AWAITING_TILT, } taskState; } mTask; // ***************************************************************************** static void algoInit() { // nothing here } static bool algoUpdate(struct TripleAxisDataEvent *ev) { float dotProduct = 0.0f; uint64_t dt; bool latch_g_vector = false; bool tilt_detected = false; struct TiltAlgoState *state = &mTask.algoState; uint64_t sample_ts = ev->referenceTime; uint32_t numSamples = ev->samples[0].firstSample.numSamples; uint32_t i; struct TripleAxisDataPoint *sample; float invN; for (i = 0; i < numSamples; i++) { sample = &ev->samples[i]; if (i > 0) sample_ts += sample->deltaTime; if (state->this_batch_init_ts == 0) { state->this_batch_init_ts = sample_ts; } state->this_batch_sample_sum[0] += sample->x; state->this_batch_sample_sum[1] += sample->y; state->this_batch_sample_sum[2] += sample->z; state->this_batch_num_samples++; dt = (sample_ts - state->this_batch_init_ts); if (dt > BATCH_TIME) { invN = 1.0f / state->this_batch_num_samples; state->this_batch_g[0] = state->this_batch_sample_sum[0] * invN; state->this_batch_g[1] = state->this_batch_sample_sum[1] * invN; state->this_batch_g[2] = state->this_batch_sample_sum[2] * invN; if (state->last_ref_g_vector_valid) { dotProduct = state->this_batch_g[0] * state->last_ref_g_vector[0] + state->this_batch_g[1] * state->last_ref_g_vector[1] + state->this_batch_g[2] * state->last_ref_g_vector[2]; if (dotProduct < ANGLE_THRESH) { tilt_detected = true; latch_g_vector = true; } } else { // reference g vector not valid, first time computing latch_g_vector = true; state->last_ref_g_vector_valid = true; } // latch the first batch or when dotProduct < ANGLE_THRESH if (latch_g_vector) { state->last_ref_g_vector[0] = state->this_batch_g[0]; state->last_ref_g_vector[1] = state->this_batch_g[1]; state->last_ref_g_vector[2] = state->this_batch_g[2]; } // Seed the next batch state->this_batch_init_ts = 0; state->this_batch_num_samples = 0; state->this_batch_sample_sum[0] = 0; state->this_batch_sample_sum[1] = 0; state->this_batch_sample_sum[2] = 0; } } return tilt_detected; } static void configAnyMotion(bool on) { if (on) { sensorRequest(mTask.taskId, mTask.anyMotionHandle, SENSOR_RATE_ONCHANGE, 0); osEventSubscribe(mTask.taskId, EVT_SENSOR_ANY_MOTION); } else { sensorRelease(mTask.taskId, mTask.anyMotionHandle); osEventUnsubscribe(mTask.taskId, EVT_SENSOR_ANY_MOTION); } } static void configNoMotion(bool on) { if (on) { sensorRequest(mTask.taskId, mTask.noMotionHandle, SENSOR_RATE_ONCHANGE, 0); osEventSubscribe(mTask.taskId, EVT_SENSOR_NO_MOTION); } else { sensorRelease(mTask.taskId, mTask.noMotionHandle); osEventUnsubscribe(mTask.taskId, EVT_SENSOR_NO_MOTION); } } static void configAccel(bool on) { if (on) { sensorRequest(mTask.taskId, mTask.accelHandle, ACCEL_MIN_RATE, ACCEL_MAX_LATENCY); osEventSubscribe(mTask.taskId, EVT_SENSOR_ACCEL); } else { sensorRelease(mTask.taskId, mTask.accelHandle); osEventUnsubscribe(mTask.taskId, EVT_SENSOR_ACCEL); } } // ***************************************************************************** static const struct SensorInfo mSi = { .sensorName = "Tilt Detection", .sensorType = SENS_TYPE_TILT, .numAxis = NUM_AXIS_EMBEDDED, .interrupt = NANOHUB_INT_WAKEUP, .minSamples = 20 }; static bool tiltDetectionPower(bool on, void *cookie) { if (on) { configAnyMotion(true); mTask.taskState = STATE_AWAITING_ANY_MOTION; } else { configAnyMotion(false); configNoMotion(false); configAccel(false); mTask.taskState = STATE_DISABLED; } sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, on, 0); return true; } static bool tiltDetectionSetRate(uint32_t rate, uint64_t latency, void *cookie) { sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_RATE_CHG, rate, latency); return true; } static bool tiltDetectionFirmwareUpload(void *cookie) { sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_FW_STATE_CHG, 1, 0); return true; } static bool tiltDetectionFlush(void *cookie) { return osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_TILT), SENSOR_DATA_EVENT_FLUSH, NULL); } static void tiltDetectionHandleEvent(uint32_t evtType, const void* evtData) { if (evtData == SENSOR_DATA_EVENT_FLUSH) return; switch (evtType) { case EVT_APP_START: osEventUnsubscribe(mTask.taskId, EVT_APP_START); sensorFind(SENS_TYPE_ANY_MOTION, 0, &mTask.anyMotionHandle); sensorFind(SENS_TYPE_NO_MOTION, 0, &mTask.noMotionHandle); sensorFind(SENS_TYPE_ACCEL, 0, &mTask.accelHandle); break; case EVT_SENSOR_ANY_MOTION: if (mTask.taskState == STATE_AWAITING_ANY_MOTION) { configAnyMotion(false); configNoMotion(true); configAccel(true); mTask.taskState = STATE_AWAITING_TILT; } break; case EVT_SENSOR_NO_MOTION: if (mTask.taskState == STATE_AWAITING_TILT) { configNoMotion(false); configAccel(false); configAnyMotion(true); mTask.taskState = STATE_AWAITING_ANY_MOTION; } break; case EVT_SENSOR_ACCEL: if (mTask.taskState == STATE_AWAITING_TILT) { if (algoUpdate((struct TripleAxisDataEvent *)evtData)) { union EmbeddedDataPoint sample; sample.idata = 1; osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_TILT), sample.vptr, NULL); } } break; } } static const struct SensorOps mSops = { .sensorPower = tiltDetectionPower, .sensorFirmwareUpload = tiltDetectionFirmwareUpload, .sensorSetRate = tiltDetectionSetRate, .sensorFlush = tiltDetectionFlush, }; static bool tiltDetectionStart(uint32_t taskId) { mTask.taskId = taskId; mTask.handle = sensorRegister(&mSi, &mSops, NULL, true); algoInit(); osEventSubscribe(taskId, EVT_APP_START); return true; } static void tiltDetectionEnd() { } INTERNAL_APP_INIT( APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 8), TILT_APP_VERSION, tiltDetectionStart, tiltDetectionEnd, tiltDetectionHandleEvent);