Принципы разработки ПО для iPhone с использованием акселерометра

среда, 2 марта 2011, Александр Краковецкий

Так как ранее созданный мной iphoner.org.ua приказал долго жить (закончился срок действия домена, а продлевать не вижу смысла, так как разорваться на несколько проектов все равно не получается). Поэтому по возможности буду переносить все материалы сюда (несмотря на то, что iphoner был посвящен разработке для iPhone, публикую я его в блог о разработке для мобильных платформ в целом). Первой статьей будет "Принципы разработки ПО для iPhone с использованием акселерометра".

В статье рассмотрено принципы работы iPhone акселерометра, показаны примеры приложений, использующие акселерометр в качестве главного компонента, наведены рекомендации по использованию акселерометра. Также показано, как использовать акселерометр в веб-приложениях. Часть материалов была взята из статьи «Скроллинг при помощи акселерометра».

Что такое акселерометр?

Обратимся к Википедии:

Акселерометр (от лат. accelero — ускоряю и μετρέω — измеряю) — прибор, измеряющий проекцию кажущегося ускорения. Кажущееся ускорение есть равнодействующая сил не гравитационной природы, действующая на массу и отнесённая к величине этой массы. Акселерометр может применяться как для измерения проекций абсолютного линейного ускорения, так и для косвенных измерений проекции гравитационного ускорения. Последнее свойство используется для создания инклинометров. Акселерометры входят в состав инерциальных навигационных систем, где полученные с их помощью измерения интегрируют, получая инерциальную скорость и координаты носителя. Электронные акселерометры часто встраиваются в мобильные устройства (в частности, в телефоны) и применяются в качестве шагомеров, датчиков для определения положения в пространстве, автоматического поворота дисплея и других целей. В игровых приставках акселерометры используются для управления без использования кнопок — путем поворотов в пространстве, встряхиваний и т. д.

Apple — не первая компания, которая внедрила акселерометр в мобильный телефон, но первая, у которой это получилось хорошо.

Как можно использовать акселерометр?

Перед тем, как перейти к технической части, посмотрим на готовые приложения, которые используют акселерометр.

Несколько базовых примеров:

C помощью iBeer можно попить виртуальное пиво:

iBoobs — программа, которая не прошла отдел цензуры в Apple.

Обзор других программ можно посмотреть по ссылкам внизу.

Немного теории

Датчик ускорения внутри iPhone использует три элемента: кремниевое тело, набор кремниевых пружин и электрический ток. Кремниевые пружины определяют положение кремниевого тела с помощью электрического тока. При повороте iPhone возникает колебание электрического тока, проходящего по кремниевым пружинам. Датчик ускорения фиксирует эти колебания и сообщает iPhone о необходимом изменении картинки на дисплее.

iPhone использует MEMS motion sensor 3-axis — ± 2g/± 8g smart digital output piccolo accelerometer, спецификацию по которому можно загрузить отсюда.

В состоянии покоя, когда устройство не двигается, величина замеряемого ускорения равна силе тяжести и принята за единицу. Соотношения величин проекций этой силы на оси координат дают нам углы поворота устройства в пространстве. Если iPhone находится в движении, то величину ускорения, с которым разгоняется аппарат, можно посредством дополнительных преобразований вычислить на основе значений проекций.

Заметьте, это именно ускорение, а не скорость движения устройства. То есть, если ваш iPhone начнет падать на землю, то проекции величины ускорения примут значение 0 по всем осям — ваш iPhone будет в невесомости :) А если вы поднимаетесь с должным ускорением на лифте вверх, то значение силы тяжести на время ускорения увеличится.

Интерфейс акселерометра

Несколько фактов:

  • является частью UIKit фреймворка
  • предоставляет информацию по трем осям
  • можно настроить частоту обновления данных (приблизительно 10-100 Гц)

Классы:

  • UIAccelerometer
  • UIAcceleration

Протокол:

  • UIAccelerometerDelegate

Координатные оси

Сразу хочу заметить, что координаты X, Y, Z не показывают координаты (положение) устройства в пространстве, как это можно было бы предположить. На самом деле они означают следующее:

  • координата X (Roll) показывает информацию о повороте устройства влево или вправо;
  • координата Y (Pitch) дает нам следующую информацию: iPhone находится в вертикальном положении (-1), лежит в горизонтальной плоскости (0) или находится в вертикальном положении, только вверх ногами (+1);
  • координата Z (Face up/face down) показывает, в каком положении находится устройство: лицом вверх (-1, при нулевых значениях по другим осям), в вертикальном положении (0) или лицом вниз (+1).

alt text

Координатные оси iPhone акселерометра

Для случая произвольной ориентации iPhone данные о величине ускорения распределяются по осям согласно проекции вектора ускорения.

alt text

Расчет вектора при произвольном положении iPhone

Подключаем акселерометр в проект

Всю необходимую информацию аккумулирует объект класса UIAcceleration, возвращающий данные по всем осям, а также временной маркер, позволяющий определить относительное время замера указанных величин. Напрямую подступиться к данным этого класса нельзя, эту информацию можно получить только через делегат UIAccelerometerDelegate, предоставляющего для реализации один единственный метод accelerometer:didAccelerate:, в который возвращается объект класса UIAcceleration. Назначение делегата и инициализация вызовов метода accelerometer:didAccelerate: происходит при помощи класса UIAccelerometer.

Для того, чтобы подключить акселерометр, необходимо в методе applicationDidFinishLaunching написать следующий код:

[[UIAccelerometer sharedAccelerometer] setUpdateInterval: 1.0 / kUpdateFrequency];
[[UIAccelerometer sharedAccelerometer] setDelegate:self];

Метод setUpdateInterval устанавливать частоту обновления данных, kUpdateFrequency — коэффициент, который показывает, как часто нужно получать данные.

Например, в случае #define kUpdateFrequency 60.0 получим 60 «опросов» в секунду.

Кроме того, в заголовочном файле класса вашего делегата нужно указать протокол UIAccelerometerDelegate:

@interface AppDelegate : NSObject<UIApplicationDelegate>
@interface accelerometerAppDelegate : NSObject <UIApplicationDelegate, UIAccelerometerDelegate>

Логику обработки данных акселерометра нужно добавить в метод didAccelerate в классе делегата:

- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
    // Get the event data
    UIAccelerometValue x, y, z; 

    x = acceleration.x;
    y = acceleration.y;
    z = acceleration.z;
}

Замечания:

  • можно объявлять лишь один делегат для приложения
  • данные приходят асинхронно к основному потоку

Настройка акселерометра:

  • Диапазон частоты — 10 -100 Гц.
  • Рекомендуемая частота для игр: 30-60 Гц, для определения ориентации — 10-30 Гц.

Для остановки получения значений необходимо вызвать следующий код:

- (void) disableAccelerometerEvents
{
UIAccelerometer * acc = [UIAccelerometer sharedAccelerometer];
acc.delegate = nil;
}

Угол наклона

Геометрически можно показать работу акселерометра следующим образом:

alt text

Из рисунка видно, что угол может быть рассчитан с помощью функции арктангенса, т.е.:

float angle = atan2(y, -x);

С помощью информации об угле поворота можно менять ориентацию экрана:

- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
    // Get the current device angle
    float xx = -[acceleration x];
    float yy = [acceleration y];
    float angle = atan2(yy, xx); 

    // Add 1.5 to the angle to keep the label constantly horizontal to the viewer.
    [interfaceOrientationLabel setTransform:CGAffineTransformMakeRotation(angle+1.5)]; 

    // Read my blog for more details on the angles. It should be obvious that you
    // could fire a custom shouldAutorotateToInterfaceOrientation-event here.
    if(angle >= -2.25 && angle <= -0.75)
    {
        if(deviceOrientation != UIInterfaceOrientationPortrait)
        {
            deviceOrientation = UIInterfaceOrientationPortrait;
            [interfaceOrientationLabel setText:@"UIInterfaceOrientationPortrait"];
        }
    }
    else if(angle >= -0.75 && angle <= 0.75)
    {
        if(deviceOrientation != UIInterfaceOrientationLandscapeRight)
        {
            deviceOrientation = UIInterfaceOrientationLandscapeRight;
            [interfaceOrientationLabel setText:@"UIInterfaceOrientationLandscapeRight"];
        }
    }
    else if(angle >= 0.75 && angle <= 2.25)
    {
        if(deviceOrientation != UIInterfaceOrientationPortraitUpsideDown)
        {
            deviceOrientation = UIInterfaceOrientationPortraitUpsideDown;
            [interfaceOrientationLabel setText:@"UIInterfaceOrientationPortraitUpsideDown"];
        }
    }
    else if(angle <= -2.25 || angle >= 2.25)
    {
        if(deviceOrientation != UIInterfaceOrientationLandscapeLeft)
        {
            deviceOrientation = UIInterfaceOrientationLandscapeLeft;
            [interfaceOrientationLabel setText:@"UIInterfaceOrientationLandscapeLeft"];
        }
    }
}

Скачать пример приложения можно здесь.

Пример 1. Прокрутка с помощью акселерометра

Возьмем за точку отсчета положение нашего устройства в пространстве, при котором угол между задней стенкой и землей составляет 45 градусов. В этом случае проекции силы тяжести на ось Y будет составлять -0.7. Если мы отклоняем аппарат чуть ближе к вертикальному положению, то примем, что при достижении угла в 30 градусов от вертикали мы должны перелистнуть список к концу. И наоборот, при достижении угла в 30 и менее градусов от горизонтального положения, мы должны перелистнуть список к началу.

В первом случае абсолютная величина проекции силы тяжести на ось Y, направленная вдоль аппарата, станет равна 0.86. Те, кто не понял откуда взялось это значение, вспоминаем геометрию и вычисление проекции на ось координат вектора единичной длины. Во втором случае это же значение равно 0.5. Для реализации прокрутки мы воспользуемся методом scrollToRowAtIndexPath:atScrollPosition:animated: класса UITableView.

- (void)accelerometer:(UIAccelerometer *)accelerometer 
                      didAccelerate:(UIAcceleration *)acceleration 
{
  double absY = fabs(acceleration.y);

  if (absY <= 0.5) {
    // Прокрутка к началу списка
    [viewController.tableView 
         scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
         atScrollPosition:UITableViewScrollPositionTop
         animated:YES];
    } 
    else if (absY >= 0.86) 
    {
      // Прокрутка к концу списка
      [viewController.tableView 
         scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:([list count] — 1) inSection:0]
         atScrollPosition:UITableViewScrollPositionBottom
         animated:YES];
    }
}

Фильтры

В основном, используются два фильтра — высокочастотный (high-pass) и низкочастотный (low-pass). Эти фильтры можно использовать для отсеивания эффектов «дрожания», медленных поворотов и т.д.

Низкочастотный фильтр используется для нахождения ориентации устройства, высокочастотный — для определения тряски.

Самый простой низкочастотный фильтр реализует следующий код:

#define FILTERFACTOR 0.1
value = (newAcceleration * FILTERFACTOR) + (previousValue * (1.0 - FILTERFACTOR));
previousValue = value;

Самый простой высокочастотный фильтр реализует следующий код:

#define FILTERFACTOR 0.1
value = newAcceleration - (newAcceleration * FILTERFACTOR) + (previousValue * (1.0 -  FILTERFACTOR));
previousValue = value;

Пример 2. AccelerometrGraph

Отличная программа для исследования работы акселерометра — показывает изменение значений по трем координатам. Можно скачать с официального сайта Apple.

В обычном режиме отображает именно ту информацию, которую получает. Также можно использовать фильтры, которые будут отсекать обыкновенные повороты, а реагировать лишь на тряску (что, в основном, и бывает нужно в реальных приложениях).

Пример 3. iBells

iBells — это развлекательная программа, которая реагирует на дейсвия пользователя и проигрывает звуки колокольчиков.

Являясь разработчиков этой программы, нам нужно было решить такие задачи:

  • корректное реагирование на действия пользователей, т.е. именно в тот момент когда пользователь трясет устройства;
  • реализация минимальной задержки, в течении которой музыкальный файл не должен проигрываться (это нужно для того, чтобы исключить очень частые срабатывания).

Корректное реагирование на тряску можно добиться используя высокочастотный фильтр:

- (void)accelerateWithX:(float)x Y:(float)y Z:(float)z
{
    // Use double filtering for passed coords
    acceleration[0] = x * kFilteringFactor + acceleration[0] * (1.0 - kFilteringFactor);
    x = x - acceleration[0];

    acceleration[1] = y * kFilteringFactor + acceleration[1] * (1.0 - kFilteringFactor);
    y = y - acceleration[1];

    acceleration[2] = z * kFilteringFactor + acceleration[2] * (1.0 - kFilteringFactor);
    z = z - acceleration[2];
}

kFilteringFactor — коэффициент «заглушения» реакции на случайные колебания. Этот параметр нужно подбирать индивидуально в зависимости от требований.

Интервал измеряется просто:

NSDate *now = [NSDate date];
NSTimeInterval interval = [now timeIntervalSinceDate:self.lastPlayedTime];

// Check playback time condition
if (interval > minTimeDelta)
{
    [self play];
}

Переменная lastPlayedTime фиксирует время последнего проигрывания, minTimeDelta — минимальный промежуток, по истечению которого можно проигрывать музыкальный файл.

Недавно я нашел еще один способ определить тряску:

// Ensures the shake is strong enough on at least two axes before declaring it a shake.
// "Strong enough" means "greater than a client-supplied threshold" in G's.
static BOOL L0AccelerationIsShaking(UIAcceleration* last, UIAcceleration* current, double threshold) {
        double
                deltaX = fabs(last.x - current.x),
                deltaY = fabs(last.y - current.y),
                deltaZ = fabs(last.z - current.z);

        return
                (deltaX > threshold && deltaY > threshold) ||
                (deltaX > threshold && deltaZ > threshold) ||
                (deltaY > threshold && deltaZ > threshold);
}

@interface L0AppDelegate : NSObject  {
        BOOL histeresisExcited;
        UIAcceleration* lastAcceleration;
}

@property(retain) UIAcceleration* lastAcceleration;

@end

@implementation L0AppDelegate

- (void)applicationDidFinishLaunching:(UIApplication *)application {
        [UIAccelerometer sharedAccelerometer].delegate = self;
}

- (void) accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {

        if (self.lastAcceleration) {
                if (!histeresisExcited && L0AccelerationIsShaking(self.lastAcceleration, acceleration, 0.7)) {
                        histeresisExcited = YES;

                        /* SHAKE DETECTED. DO HERE WHAT YOU WANT. */

                } else if (histeresisExcited && !L0AccelerationIsShaking(self.lastAcceleration, acceleration, 0.2)) {
                        histeresisExcited = NO;
                }
        }

        self.lastAcceleration = acceleration;
}

// and proper @synthesize and -dealloc boilerplate code

@end

Использование акселерометра в веб-приложениях

В Safari был добавлен новый метод onorientationchange, который срабатывает при изменении положения на 90%. Ниже приведен javascript код, с помощью которого можно менять положение (orientation) веб-страницы.

function updateOrientation()    {
    /*
        window.orientation returns a value that indicates whether iPhone is in portrait mode, 
        landscape mode with the screen turned to the left, or landscape mode 
        with the screen turned to the right. 
    */
    var orientation = window.orientation;

    switch(orientation) 
    {
        case 0:
            /*
                If in portrait mode, sets the body's class attribute to portrait. 
                Consequently, all style definitions matching the body[class="portrait"] 
                declaration in the iPhoneOrientation.css file will be selected and used 
                to style "Handling iPhone or iPod touch Orientation Events".
            */
            document.body.setAttribute("class","portrait");

            /*
                Add a descriptive message on "Handling iPhone or iPod touch Orientation Events" 
            */
            document.getElementById("currentOrientation").innerHTML 
                = "Now in portrait orientation (Home button on the bottom).";
            break; 

        case 90:
            /*
                If in landscape mode with the screen turned to the left, 
                sets the body's class attribute to landscapeLeft. 
                In this case, all style definitions matching the body[class="landscapeLeft"] 
                declaration in the iPhoneOrientation.css file will be 
                selected and used to style "Handling iPhone or iPod touch Orientation Events".
            */
            document.body.setAttribute("class","landscapeLeft");
            document.getElementById("currentOrientation").innerHTML 
                = "Now in landscape orientation and turned to the left (Home button to the right).";
            break;

        case -90: 
            /* 
                If in landscape mode with the screen turned to the right, 
                sets the body's class attribute to landscapeRight. 
                Here, all style definitions matching the body[class="landscapeRight"] 
                declaration in the iPhoneOrientation.css file will be selected and used 
                to style "Handling iPhone or iPod touch Orientation Events".
            */
            document.body.setAttribute("class","landscapeRight");
            break;
    }

window.onorientationchange = updateOrientation;

<div id="currentOrientation" style="font-size: 40px;">
          Now in portrait orientation (Home button on the bottom).</div>

Примеры веб-страницы можно посмотреть здесь и здесь.

Акселерометр и симулятор

По понятным причинам тестировать приложения в симуляторе не представляется возможным. Для этого нужно использовать реальное устройство.

По ссылке можно посмотреть (а здесь скачать), как все таки можно передавать данные в симулятор. Но, так как там все равно используется реальное устройство, практической пользы в этом нет. Just for fun.

Ссылки и дополнительные материалы


Ищите нас в интернетах!

Комментарии

Свежие вакансии