Принципы разработки ПО для 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).



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

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

    Проекция вектора силы тяжести на оси координат в произвольном положении iPhone
    Расчет вектора при произвольном положении 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;
    }


    Угол наклона


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

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

    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 (ссылка на appstore) — это развлекательная программа, которая реагирует на дейсвия пользователя и проигрывает звуки колокольчиков.

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


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

    - (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.

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






    P. S. Автор статьи Александр Краковецкий (sashaeve), пожалуйста дайте ему инвайт спасибо Speakus за инвайт.
    Поделиться публикацией

    Похожие публикации

    Комментарии 26
      +4
      Мощно.
        +1
        знай наших ;)
        +1
        Очень интересная статья, впечатляет, спасибо. Сразу видно что писал профессионал в своем деле, надо сделать достаточно обьемное исследование для такой статьи.
          +2
          Только бы код оформить, а то воспринимается тяжело.
            +1
            Подскажи чем его отформатировать можно?
              +2
              Хм, это же Object-C?
              Можно попробовать:
              source.virtser.net/
              dumpz.org
              s-c.me

              который из них подойдет для хабра — не помню.
            +2
            Годная статья, но есть вопросы.

            >>При повороте iPhone возникает колебание электрического тока, проходящего по кремниевым пружинам
            Это вы загнули или нафантазировали :) Хотя чувствительный элемент и основан на механическом эффекте, этот акселлерометр емкостного типа. Если почитать хотя бы те datashit, которые вы привели, то на странице 16 можно найти описание метода — при приложении к акселлерометру ускорения, емкостной мост разбалансируется; этот разбаланс можно замерить и перевести в единицы ускорения.
              +1
              Автор утверждает что это Apple скорее загнул, эта фраза с офсайта и подкрепляет это ссылкой www.apple.com/ru/iphone/features/accelerometer.html
                +3
                Ну в Висте как бы тоже в свойствах экрана можно настроить (цитирую) «мерцание». Это, конечно, не кремниевые пружины, но тоже весело :)

                Ну к автору никаких претензий — раз Apple сказал «есть пружины», значит точно есть :)
                +1
                s/datashit/datasheet/ ;) там все не настолько плохо
                  +1
                  Ну как вам сказать. Механические датчики ускорения с рабочим телом таких размеров, да еще и с внешним токовым драйвером — это уже прошлый век, к сожалению :/
                +4
                Ух-ты сколько всего оказывается происходит по ту сторону болтающихся титек!
                  +1
                  «координата Y (Face up/face down) „
                  а по-моему Z
                    +1
                    Отлично, спасибо
                    +3
                    Понравилось. Выслал инвайт.
                      +1
                      Спасибо! ++
                        +6
                        Спасибо большое! Я уже с вами!
                        0
                        Интересная статья, спасибо автору! Не совсем понятен только остался момент касаемо того, что если телефон начнет падать, то он окажется в невесомости. А как же ускорение свободного падения? Насколько я знаю, Эппл использует акселерометр в макбуках как раз для того, чтобы экстренно парковать головки жесткого диска в случае падения.
                          +1
                          Вы немного путаете.

                          Акселерометр возвращает измерение ускорения, но находится в системе (в физическом смысле) самого телефона.

                          Если взять за систему отсчета сам телефон — тогда он в невесомости (во время свободного падения).

                          А диски в макбуках паркуются именно тогда, когда вектор с приходящий с акселерометра по длинне близок к нулю.
                          0
                          Отличная компиляция из разных статей. Еще бы стилистику подправить:"… использует три элемента: кремниевое тело, набор кремниевых пружин и электрический ток"
                            0
                            Акселерометр и симулятор

                            www.iphonearch.com/topic/6/iphone-accelerometer-simulator/

                            по этой ссылке статься в которой описано как можно использовать акселерометр и симулятор без девайса.
                              0
                              в 3.0 добавили в API определение именно тряски. Подробностей, к сожалению, не знаю.
                              0
                              Можно тестировать приложение, использующее акселерометр, и в симуляторе: для этого используют акселерометр самого macbook-а. Вот статья о том, как это сделать.
                                0
                                класс, но из комментов подчеркнул больше полезного :)

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

                                Самое читаемое