Home | History | Annotate | Download | only in window_orientation
      1 /*
      2  * Copyright (C) 2016 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 #include <stdlib.h>
     18 #include <string.h>
     19 #include <timer.h>
     20 #include <heap.h>
     21 #include <plat/inc/rtc.h>
     22 #include <plat/inc/syscfg.h>
     23 #include <hostIntf.h>
     24 #include <nanohubPacket.h>
     25 #include <floatRt.h>
     26 
     27 #include <seos.h>
     28 
     29 #include <nanohub_math.h>
     30 #include <sensors.h>
     31 #include <limits.h>
     32 
     33 #define ACCEL_MIN_RATE_HZ                  SENSOR_HZ(15) // 15 HZ
     34 #define ACCEL_MAX_LATENCY_NS               40000000ull   // 40 ms in nsec
     35 
     36 // all time units in usec, angles in degrees
     37 #define RADIANS_TO_DEGREES                              (180.0f / M_PI)
     38 
     39 #define NS2US(x) (x >> 10)   // convert nsec to approx usec
     40 
     41 #define PROPOSAL_SETTLE_TIME                            NS2US(40000000ull)       // 40 ms
     42 #define PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED              NS2US(500000000ull)      // 500 ms
     43 #define PROPOSAL_MIN_TIME_SINCE_SWING_ENDED             NS2US(300000000ull)      // 300 ms
     44 #define PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED      NS2US(500000000ull)      // 500 ms
     45 
     46 #define FLAT_ANGLE                      80
     47 #define FLAT_TIME                       NS2US(1000000000ull)     // 1 sec
     48 
     49 #define SWING_AWAY_ANGLE_DELTA          20
     50 #define SWING_TIME                      NS2US(300000000ull)      // 300 ms
     51 
     52 #define MAX_FILTER_DELTA_TIME           NS2US(1000000000ull)     // 1 sec
     53 #define FILTER_TIME_CONSTANT            NS2US(200000000ull)      // 200 ms
     54 
     55 #define NEAR_ZERO_MAGNITUDE             1.0f        // m/s^2
     56 #define ACCELERATION_TOLERANCE          4.0f
     57 #define STANDARD_GRAVITY                9.8f
     58 #define MIN_ACCELERATION_MAGNITUDE  (STANDARD_GRAVITY - ACCELERATION_TOLERANCE)
     59 #define MAX_ACCELERATION_MAGNITUDE  (STANDARD_GRAVITY + ACCELERATION_TOLERANCE)
     60 
     61 #define MAX_TILT                        80
     62 #define TILT_OVERHEAD_ENTER             -40
     63 #define TILT_OVERHEAD_EXIT              -15
     64 
     65 #define ADJACENT_ORIENTATION_ANGLE_GAP  45
     66 
     67 #define TILT_HISTORY_SIZE               200
     68 #define TILT_REFERENCE_PERIOD           NS2US(1800000000000ull)  // 30 min
     69 #define TILT_REFERENCE_BACKOFF          NS2US(300000000000ull)   // 5 min
     70 
     71 #define MIN_ACCEL_INTERVAL              NS2US(33333333ull)       // 33.3 ms for 30 Hz
     72 
     73 #define EVT_SENSOR_ACC_DATA_RDY sensorGetMyEventType(SENS_TYPE_ACCEL)
     74 #define EVT_SENSOR_WIN_ORIENTATION_DATA_RDY sensorGetMyEventType(SENS_TYPE_WIN_ORIENTATION)
     75 
     76 static int8_t Tilt_Tolerance[4][2] = {
     77     /* ROTATION_0   */ { -25, 70 },
     78     /* ROTATION_90  */ { -25, 65 },
     79     /* ROTATION_180 */ { -25, 60 },
     80     /* ROTATION_270 */ { -25, 65 }
     81 };
     82 
     83 struct WindowOrientationTask {
     84     uint32_t tid;
     85     uint32_t handle;
     86     uint32_t accelHandle;
     87 
     88     uint64_t last_filtered_time;
     89     struct TripleAxisDataPoint last_filtered_sample;
     90 
     91     uint64_t tilt_reference_time;
     92     uint64_t accelerating_time;
     93     uint64_t predicted_rotation_time;
     94     uint64_t flat_time;
     95     uint64_t swinging_time;
     96 
     97     uint32_t tilt_history_time[TILT_HISTORY_SIZE];
     98     int tilt_history_index;
     99     int8_t tilt_history[TILT_HISTORY_SIZE];
    100 
    101     int8_t current_rotation;
    102     int8_t prev_valid_rotation;
    103     int8_t proposed_rotation;
    104     int8_t predicted_rotation;
    105 
    106     bool flat;
    107     bool swinging;
    108     bool accelerating;
    109     bool overhead;
    110 };
    111 
    112 static struct WindowOrientationTask mTask;
    113 
    114 static const struct SensorInfo mSi =
    115 {
    116     .sensorName = "Window Orientation",
    117     .sensorType = SENS_TYPE_WIN_ORIENTATION,
    118     .numAxis = NUM_AXIS_EMBEDDED,
    119     .interrupt = NANOHUB_INT_NONWAKEUP,
    120     .minSamples = 20
    121 };
    122 
    123 static bool isTiltAngleAcceptable(int rotation, int8_t tilt_angle)
    124 {
    125     return ((tilt_angle >= Tilt_Tolerance[rotation][0])
    126                 && (tilt_angle <= Tilt_Tolerance[rotation][1]));
    127 }
    128 
    129 static bool isOrientationAngleAcceptable(int current_rotation, int rotation,
    130                                             int orientation_angle)
    131 {
    132     // If there is no current rotation, then there is no gap.
    133     // The gap is used only to introduce hysteresis among advertised orientation
    134     // changes to avoid flapping.
    135     int lower_bound, upper_bound;
    136 
    137     if (current_rotation >= 0) {
    138         // If the specified rotation is the same or is counter-clockwise
    139         // adjacent to the current rotation, then we set a lower bound on the
    140         // orientation angle.
    141         // For example, if currentRotation is ROTATION_0 and proposed is
    142         // ROTATION_90, then we want to check orientationAngle > 45 + GAP / 2.
    143         if ((rotation == current_rotation)
    144                 || (rotation == (current_rotation + 1) % 4)) {
    145             lower_bound = rotation * 90 - 45
    146                     + ADJACENT_ORIENTATION_ANGLE_GAP / 2;
    147             if (rotation == 0) {
    148                 if ((orientation_angle >= 315)
    149                         && (orientation_angle < lower_bound + 360)) {
    150                     return false;
    151                 }
    152             } else {
    153                 if (orientation_angle < lower_bound) {
    154                     return false;
    155                 }
    156             }
    157         }
    158 
    159         // If the specified rotation is the same or is clockwise adjacent,
    160         // then we set an upper bound on the orientation angle.
    161         // For example, if currentRotation is ROTATION_0 and rotation is
    162         // ROTATION_270, then we want to check orientationAngle < 315 - GAP / 2.
    163         if ((rotation == current_rotation)
    164                 || (rotation == (current_rotation + 3) % 4)) {
    165             upper_bound = rotation * 90 + 45
    166                     - ADJACENT_ORIENTATION_ANGLE_GAP / 2;
    167             if (rotation == 0) {
    168                 if ((orientation_angle <= 45)
    169                         && (orientation_angle > upper_bound)) {
    170                     return false;
    171                 }
    172             } else {
    173                 if (orientation_angle > upper_bound) {
    174                     return false;
    175                 }
    176             }
    177         }
    178     }
    179     return true;
    180 }
    181 
    182 static bool isPredictedRotationAcceptable(uint64_t now)
    183 {
    184     // The predicted rotation must have settled long enough.
    185     if (now < mTask.predicted_rotation_time + PROPOSAL_SETTLE_TIME) {
    186         return false;
    187     }
    188 
    189     // The last flat state (time since picked up) must have been sufficiently
    190     // long ago.
    191     if (now < mTask.flat_time + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED) {
    192         return false;
    193     }
    194 
    195     // The last swing state (time since last movement to put down) must have
    196     // been sufficiently long ago.
    197     if (now < mTask.swinging_time + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED) {
    198         return false;
    199     }
    200 
    201     // The last acceleration state must have been sufficiently long ago.
    202     if (now < mTask.accelerating_time
    203             + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED) {
    204         return false;
    205     }
    206 
    207     // Looks good!
    208     return true;
    209 }
    210 
    211 static void clearPredictedRotation()
    212 {
    213     mTask.predicted_rotation = -1;
    214     mTask.predicted_rotation_time = 0;
    215 }
    216 
    217 static void clearTiltHistory()
    218 {
    219     mTask.tilt_history_time[0] = 0;
    220     mTask.tilt_history_index = 1;
    221     mTask.tilt_reference_time = 0;
    222 }
    223 
    224 static void reset()
    225 {
    226     mTask.last_filtered_time = 0;
    227     mTask.proposed_rotation = -1;
    228 
    229     mTask.flat_time = 0;
    230     mTask.flat = false;
    231 
    232     mTask.swinging_time = 0;
    233     mTask.swinging = false;
    234 
    235     mTask.accelerating_time = 0;
    236     mTask.accelerating = false;
    237 
    238     mTask.overhead = false;
    239 
    240     clearPredictedRotation();
    241     clearTiltHistory();
    242 }
    243 
    244 static void updatePredictedRotation(uint64_t now, int rotation)
    245 {
    246     if (mTask.predicted_rotation != rotation) {
    247         mTask.predicted_rotation = rotation;
    248         mTask.predicted_rotation_time = now;
    249     }
    250 }
    251 
    252 static bool isAccelerating(float magnitude)
    253 {
    254     return ((magnitude < MIN_ACCELERATION_MAGNITUDE)
    255                 || (magnitude > MAX_ACCELERATION_MAGNITUDE));
    256 }
    257 
    258 static void addTiltHistoryEntry(uint64_t now, int8_t tilt)
    259 {
    260     uint64_t old_reference_time, delta;
    261     size_t i;
    262     int index;
    263 
    264     if (mTask.tilt_reference_time == 0) {
    265         // set reference_time after reset()
    266 
    267         mTask.tilt_reference_time = now - 1;
    268     } else if (mTask.tilt_reference_time + TILT_REFERENCE_PERIOD < now) {
    269         // uint32_t tilt_history_time[] is good up to 71 min (2^32 * 1e-6 sec).
    270         // proactively shift reference_time every 30 min,
    271         // all history entries are within 5 min interval (15Hz x 200 samples)
    272 
    273         old_reference_time = mTask.tilt_reference_time;
    274         mTask.tilt_reference_time = now - TILT_REFERENCE_BACKOFF;
    275 
    276         delta = mTask.tilt_reference_time - old_reference_time;
    277         for (i = 0; i < TILT_HISTORY_SIZE; ++i) {
    278             mTask.tilt_history_time[i] = (mTask.tilt_history_time[i] > delta)
    279                 ? (mTask.tilt_history_time[i] - delta) : 0;
    280         }
    281     }
    282 
    283     index = mTask.tilt_history_index;
    284     mTask.tilt_history[index] = tilt;
    285     mTask.tilt_history_time[index] = now - mTask.tilt_reference_time;
    286 
    287     index = ((index + 1) == TILT_HISTORY_SIZE) ? 0 : (index + 1);
    288     mTask.tilt_history_index = index;
    289     mTask.tilt_history_time[index] = 0;
    290 }
    291 
    292 static int nextTiltHistoryIndex(int index)
    293 {
    294     int next = (index == 0) ? (TILT_HISTORY_SIZE - 1): (index - 1);
    295     return ((mTask.tilt_history_time[next] != 0) ? next : -1);
    296 }
    297 
    298 static bool isFlat(uint64_t now)
    299 {
    300     int i = mTask.tilt_history_index;
    301     for (; (i = nextTiltHistoryIndex(i)) >= 0;) {
    302         if (mTask.tilt_history[i] < FLAT_ANGLE) {
    303             break;
    304         }
    305         if (mTask.tilt_reference_time + mTask.tilt_history_time[i] + FLAT_TIME <= now) {
    306             // Tilt has remained greater than FLAT_ANGLE for FLAT_TIME.
    307             return true;
    308         }
    309     }
    310     return false;
    311 }
    312 
    313 static bool isSwinging(uint64_t now, int8_t tilt)
    314 {
    315     int i = mTask.tilt_history_index;
    316     for (; (i = nextTiltHistoryIndex(i)) >= 0;) {
    317         if (mTask.tilt_reference_time + mTask.tilt_history_time[i] + SWING_TIME
    318                 < now) {
    319             break;
    320         }
    321         if (mTask.tilt_history[i] + SWING_AWAY_ANGLE_DELTA <= tilt) {
    322             // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME.
    323             return true;
    324         }
    325     }
    326     return false;
    327 }
    328 
    329 static bool add_samples(struct TripleAxisDataEvent *ev)
    330 {
    331     int i, tilt_tmp;
    332     int orientation_angle, nearest_rotation;
    333     float x, y, z, alpha, magnitude;
    334     uint64_t now_nsec = ev->referenceTime, now;
    335     uint64_t then, time_delta;
    336     struct TripleAxisDataPoint *last_sample;
    337     size_t sampleCnt = ev->samples[0].firstSample.numSamples;
    338     bool skip_sample;
    339     bool accelerating, flat, swinging;
    340     bool change_detected;
    341     int8_t old_proposed_rotation, proposed_rotation;
    342     int8_t tilt_angle;
    343 
    344     for (i = 0; i < sampleCnt; i++) {
    345 
    346         x = ev->samples[i].x;
    347         y = ev->samples[i].y;
    348         z = ev->samples[i].z;
    349 
    350         // Apply a low-pass filter to the acceleration up vector in cartesian space.
    351         // Reset the orientation listener state if the samples are too far apart in time.
    352 
    353         now_nsec += i > 0 ? ev->samples[i].deltaTime : 0;
    354         now = NS2US(now_nsec); // convert to ~usec
    355 
    356         last_sample = &mTask.last_filtered_sample;
    357         then = mTask.last_filtered_time;
    358         time_delta = now - then;
    359 
    360         if ((now < then) || (now > then + MAX_FILTER_DELTA_TIME)) {
    361             reset();
    362             skip_sample = true;
    363         } else {
    364             // alpha is the weight on the new sample
    365             alpha = floatFromUint64(time_delta) / floatFromUint64(FILTER_TIME_CONSTANT + time_delta);
    366             x = alpha * (x - last_sample->x) + last_sample->x;
    367             y = alpha * (y - last_sample->y) + last_sample->y;
    368             z = alpha * (z - last_sample->z) + last_sample->z;
    369 
    370             skip_sample = false;
    371         }
    372 
    373         // drop samples when input sampling rate is 2x higher than requested
    374         if (!skip_sample && (time_delta < MIN_ACCEL_INTERVAL)) {
    375             skip_sample = true;
    376         } else {
    377             mTask.last_filtered_time = now;
    378             mTask.last_filtered_sample.x = x;
    379             mTask.last_filtered_sample.y = y;
    380             mTask.last_filtered_sample.z = z;
    381         }
    382 
    383         accelerating = false;
    384         flat = false;
    385         swinging = false;
    386 
    387         if (!skip_sample) {
    388             // Calculate the magnitude of the acceleration vector.
    389             magnitude = sqrtf(x * x + y * y + z * z);
    390 
    391             if (magnitude < NEAR_ZERO_MAGNITUDE) {
    392                 clearPredictedRotation();
    393             } else {
    394                 // Determine whether the device appears to be undergoing
    395                 // external acceleration.
    396                 if (isAccelerating(magnitude)) {
    397                     accelerating = true;
    398                     mTask.accelerating_time = now;
    399                 }
    400 
    401                 // Calculate the tilt angle.
    402                 // This is the angle between the up vector and the x-y plane
    403                 // (the plane of the screen) in a range of [-90, 90] degrees.
    404                 //  -90 degrees: screen horizontal and facing the ground (overhead)
    405                 //    0 degrees: screen vertical
    406                 //   90 degrees: screen horizontal and facing the sky (on table)
    407                 tilt_tmp = (int)(asinf(z / magnitude) * RADIANS_TO_DEGREES);
    408                 tilt_tmp = (tilt_tmp > 127) ? 127 : tilt_tmp;
    409                 tilt_tmp = (tilt_tmp < -128) ? -128 : tilt_tmp;
    410                 tilt_angle = tilt_tmp;
    411                 addTiltHistoryEntry(now, tilt_angle);
    412 
    413                 // Determine whether the device appears to be flat or swinging.
    414                 if (isFlat(now)) {
    415                     flat = true;
    416                     mTask.flat_time = now;
    417                 }
    418                 if (isSwinging(now, tilt_angle)) {
    419                     swinging = true;
    420                     mTask.swinging_time = now;
    421                 }
    422 
    423                 // If the tilt angle is too close to horizontal then we cannot
    424                 // determine the orientation angle of the screen.
    425                 if (tilt_angle <= TILT_OVERHEAD_ENTER) {
    426                     mTask.overhead = true;
    427                 } else if (tilt_angle >= TILT_OVERHEAD_EXIT) {
    428                     mTask.overhead = false;
    429                 }
    430 
    431                 if (mTask.overhead) {
    432                     clearPredictedRotation();
    433                 } else if (fabsf(tilt_angle) > MAX_TILT) {
    434                     clearPredictedRotation();
    435                 } else {
    436                     // Calculate the orientation angle.
    437                     // This is the angle between the x-y projection of the up
    438                     // vector onto the +y-axis, increasing clockwise in a range
    439                     // of [0, 360] degrees.
    440                     orientation_angle = (int)(-atan2f(-x, y) * RADIANS_TO_DEGREES);
    441                     if (orientation_angle < 0) {
    442                         // atan2 returns [-180, 180]; normalize to [0, 360]
    443                         orientation_angle += 360;
    444                     }
    445 
    446                     // Find the nearest rotation.
    447                     nearest_rotation = (orientation_angle + 45) / 90;
    448                     if (nearest_rotation == 4) {
    449                         nearest_rotation = 0;
    450                     }
    451                     // Determine the predicted orientation.
    452                     if (isTiltAngleAcceptable(nearest_rotation, tilt_angle)
    453                         && isOrientationAngleAcceptable(mTask.current_rotation,
    454                                                            nearest_rotation,
    455                                                            orientation_angle)) {
    456                         updatePredictedRotation(now, nearest_rotation);
    457                     } else {
    458                         clearPredictedRotation();
    459                     }
    460                 }
    461             }
    462         }
    463 
    464         mTask.flat = flat;
    465         mTask.swinging = swinging;
    466         mTask.accelerating = accelerating;
    467 
    468         // Determine new proposed rotation.
    469         old_proposed_rotation = mTask.proposed_rotation;
    470         if ((mTask.predicted_rotation < 0)
    471                 || isPredictedRotationAcceptable(now)) {
    472 
    473             mTask.proposed_rotation = mTask.predicted_rotation;
    474         }
    475         proposed_rotation = mTask.proposed_rotation;
    476 
    477         if ((proposed_rotation != old_proposed_rotation)
    478                 && (proposed_rotation >= 0)) {
    479             mTask.current_rotation = proposed_rotation;
    480 
    481             change_detected = (proposed_rotation != mTask.prev_valid_rotation);
    482             mTask.prev_valid_rotation = proposed_rotation;
    483 
    484             if (change_detected) {
    485                 return true;
    486             }
    487         }
    488     }
    489 
    490     return false;
    491 }
    492 
    493 
    494 static bool windowOrientationPower(bool on, void *cookie)
    495 {
    496     if (on == false && mTask.accelHandle != 0) {
    497         sensorRelease(mTask.tid, mTask.accelHandle);
    498         mTask.accelHandle = 0;
    499         osEventUnsubscribe(mTask.tid, EVT_SENSOR_ACC_DATA_RDY);
    500     }
    501 
    502     sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, on, 0);
    503 
    504     return true;
    505 }
    506 
    507 static bool windowOrientationSetRate(uint32_t rate, uint64_t latency, void *cookie)
    508 {
    509     int i;
    510 
    511     if (mTask.accelHandle == 0) {
    512         for (i = 0; sensorFind(SENS_TYPE_ACCEL, i, &mTask.accelHandle) != NULL; i++) {
    513             if (sensorRequest(mTask.tid, mTask.accelHandle, ACCEL_MIN_RATE_HZ, ACCEL_MAX_LATENCY_NS)) {
    514                 // clear hysteresis
    515                 mTask.current_rotation = -1;
    516                 mTask.prev_valid_rotation = -1;
    517                 reset();
    518                 osEventSubscribe(mTask.tid, EVT_SENSOR_ACC_DATA_RDY);
    519                 break;
    520             }
    521         }
    522     }
    523 
    524     if (mTask.accelHandle != 0)
    525         sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_RATE_CHG, rate, latency);
    526 
    527     return true;
    528 }
    529 
    530 static bool windowOrientationFirmwareUpload(void *cookie)
    531 {
    532     sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_FW_STATE_CHG,
    533             1, 0);
    534     return true;
    535 }
    536 
    537 static bool windowOrientationFlush(void *cookie)
    538 {
    539     return osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_WIN_ORIENTATION), SENSOR_DATA_EVENT_FLUSH, NULL);
    540 }
    541 
    542 static void windowOrientationHandleEvent(uint32_t evtType, const void* evtData)
    543 {
    544     struct TripleAxisDataEvent *ev;
    545     union EmbeddedDataPoint sample;
    546     bool rotation_changed;
    547 
    548     if (evtData == SENSOR_DATA_EVENT_FLUSH)
    549         return;
    550 
    551     switch (evtType) {
    552     case EVT_SENSOR_ACC_DATA_RDY:
    553         ev = (struct TripleAxisDataEvent *)evtData;
    554         rotation_changed = add_samples(ev);
    555 
    556         if (rotation_changed) {
    557             //osLog(LOG_INFO, "WO:     ********** rotation changed to ********: %d\n", (int)mTask.proposed_rotation);
    558 
    559             // send a single int32 here so no memory alloc/free needed.
    560             sample.idata = mTask.proposed_rotation;
    561             osEnqueueEvt(EVT_SENSOR_WIN_ORIENTATION_DATA_RDY, sample.vptr, NULL);
    562         }
    563         break;
    564     }
    565 }
    566 
    567 static const struct SensorOps mSops =
    568 {
    569     .sensorPower = windowOrientationPower,
    570     .sensorFirmwareUpload = windowOrientationFirmwareUpload,
    571     .sensorSetRate = windowOrientationSetRate,
    572     .sensorFlush = windowOrientationFlush,
    573 };
    574 
    575 static bool window_orientation_start(uint32_t tid)
    576 {
    577     osLog(LOG_INFO, "        WINDOW ORIENTATION:  %ld\n", tid);
    578 
    579     mTask.tid = tid;
    580 
    581     mTask.current_rotation = -1;
    582     mTask.prev_valid_rotation = -1;
    583     reset();
    584 
    585     mTask.handle = sensorRegister(&mSi, &mSops, NULL, true);
    586 
    587     return true;
    588 }
    589 
    590 static void windowOrientationEnd()
    591 {
    592 }
    593 
    594 INTERNAL_APP_INIT(
    595         APP_ID_MAKE(APP_ID_VENDOR_GOOGLE, 3),
    596         0,
    597         window_orientation_start,
    598         windowOrientationEnd,
    599         windowOrientationHandleEvent);
    600 
    601