Подключение к датчикам в Android

Наблюдаем за окружающей средой дистанционно

В этой статье мы научимся взаимодействовать через Android с различными датчиками в целях мониторинга окружающей среды.

Фрэнк Эйблсон, проектировщик ПО, Независимый разработчик

Когда Фрэнк Эйблсон (Frank Ableson) закончил карьеру баскетболиста в команде своего колледжа, не заключив многолетнего контракта с «Лос-Анджедес Лейкерс», он занялся разработкой компьютерных программ. Он любит решать сложные задачи, особенно из области связи и интерфейсов с аппаратурой. Свободное время Фрэнк проводит со своей женой Никки и детьми. С ним можно связаться по адресу: frank@cfgsolutions.com.



26.02.2010

Введение

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

Какое приложение лучше построить, чтобы задействовать аппаратные возможности устройства на платформе Android? Подойдет любое, в котором требуются электронные глаза и уши. На ум приходит электронная няня, система безопасности или даже сейсмограф. Хотя с метафизической точки зрения одновременно присутствовать в двух местах невозможно, Android может предложить некоторые практические способы преодоления этого ограничения. В рамках этой статьи под Android-устройством понимается не просто "мобильный телефон", а установленное в определенном месте устройство с беспроводным соединением с сетью, таким как EDGE или Wi-Fi. Загрузите исходный код примера приложения для этой статьи.


Сенсорные возможности Android

Один из приятных аспектов работы с платформой Android заключается в возможности получить доступ к некоторым полезным компонентам самого устройства. До сих пор разработчиков мобильных устройств разочаровывала невозможность доступа к их внутреннему оборудованию. Хотя между вами и металлом все же остается прослойка Java-среды Android, команда разработчиков Android вывела многие возможности аппаратуры на поверхность. А так как это платформа с открытым исходным кодом, можно засучить рукава и написать собственный код для решения своих задач.

Загрузите SDK Android, если вы этого еще не сделали. Рекомендуем также изучить содержимое пакета android.hardware и следить за примерами этой статьи. Пакет android.media package содержит классы, которые предоставляют разработчикам новые полезные функции.

Ниже описаны некоторые аппаратно-ориентированные функции, содержащиеся в SDK Android.

Таблица 1. Аппаратно-ориентированные функции SDK Android
ФункцияОписание
android.hardware.CameraКласс, позволяющий приложениям взаимодействовать с видеокамерой в целях фотосъемки, записи изображений с экрана предварительного просмотра или для изменения параметров настройки.
android.hardware.SensorManagerКласс, обеспечивающий доступ к внутренним датчикам платформы Android. Не каждое устройство на платформе Android поддерживает все датчики из SensorManager, однако интересно обдумать такие возможности. (Краткое описание имеющихся датчиков приведено ниже.)
android.hardware.SensorListenerИнтерфейс реализован с помощью класса, который используется для ввода значений датчиков по мере их изменения в режиме реального времени. Приложение реализует этот интерфейс для мониторинга одного или нескольких имеющихся аппаратных датчиков. Например, код из этой статьи содержит класс, который использует этот интерфейс для контроля ориентации устройства и показаний встроенного акселерометра.
android.media.MediaRecorderКласс, используемый для записи медиафрагментов, который можно применять для записи звуков в определенном месте (например, в детской комнате). Можно также анализировать аудиофрагменты для контроля доступа в помещение и в целях безопасности. Например, можно открывать дверь собственным голосом в обычное время своего прихода, вместо того, чтобы обращаться к консьержу за ключом.
android.FaceDetectorКласс, который позволяет распознавать лицо человека по хранящейся в памяти фотографии. Ничто не удостоверяет личность лучше, чем лицо. Если использовать его для блокировки устройства, вам больше не придется запоминать пароли – достаточно биометрических возможностей мобильного телефона.
android.os.*Пакет, содержащий несколько полезных классов для взаимодействия с операционной средой, включая управление питанием, поиск файлов, обработчик и классы для обмена сообщениями. Как и многие другие портативные устройства, телефоны на базе Android могут потреблять достаточно много электроэнергии. Обеспечение "бодрствования" устройства в нужный момент, чтобы проконтролировать нужное событие, - важный аспект проектирования, заслуживающий особого внимания.
java.util.Date
java.util.Timer
java.util.TimerTask
При измерении событий реального мира часто имеют значение дата и время. Например, класс java.util.Date позволяет получить метку времени, когда происходит какое-либо событие или возникает определенное состояние. java.util.Timer и java.util.TimerTask можно использовать соответственно для выполнения периодических действий по расписанию или разового действия в определенный момент времени.

Android.hardware.SensorManager содержит несколько констант, которые характеризуют различные аспекты системы датчиков Android, в том числе:

Тип датчика
Ориентация, акселерометр, свет, магнитное поле, близость, температура и т.д.
Частота измерений
Максимальная, для игр, обычная, для пользовательского интерфейса. Когда приложение запрашивает конкретное значение частоты отсчетов, с точки зрения сенсорной подсистемы это лишь рекомендация. Никакой гарантии, что измерения будут производиться с указанной частотой, нет.
Точность
Высокая, низкая, средняя, ненадежные данные.

Центром сенсорных приложений служит интерфейс SensorListener. Он включает в себя два необходимых метода:

  • Метод onSensorChanged(int sensor,float values[]) вызывается всякий раз, когда изменяется значение датчика. Этот метод вызывается только для датчиков, контролируемых данным приложением (подробнее об этом ниже). В число аргументов метода входит целое, которое указывает, что значение датчика изменилось, и массив значений с плавающей запятой, отражающих собственно значение датчика. Некоторые датчики выдают только одно значение данных, тогда как другие предоставляют три значения с плавающей запятой. Датчики ориентации и акселерометр дают по три значения данных каждый.
  • Метод onAccuracyChanged(int sensor,int accuracy) вызывается при изменении точности показаний датчика. Аргументами служат два целых числа: одно указывает датчик, а другое соответствует новому значению точности этого датчика.

Для взаимодействия с датчиком приложение должно зарегистрироваться на прием действий, связанных с одним или несколькими датчиками. Регистрация осуществляется с помощью метода registerListener класса SensorManager. Пример кода для этой статьи демонстрирует, как приложение регистрируется и отменяет регистрацию с помощью SensorListener.

Помните, что не каждое устройство Android поддерживает тот или иной датчик, указанный в SDK. Если на конкретном устройстве тот или иной датчик отсутствует, ваше приложение должно обрабатывать эту ситуацию аккуратно.


Пример работы с датчиком

Этот пример приложения просто следит за изменениями значений датчиков ориентации и акселерометра (исходный код приведен в разделе Загрузки). При изменении значений датчиков они отображаются на экране в виджете TextView. Рисунок 1 демонстрирует приложение в действии.

Рисунок 1. Мониторинг ускорения и ориентации
Мониторинг ускорения и ориентации

Приложение создано в среде Eclipse с плагином Android Developer Tools. (Более подробную информацию о разработке Android-приложений с помощью Eclipse см. в разделе Ресурсы.) В листинге 1 приведен код этого приложения.

Листинг 1. IBMEyes.java
package com.msi.ibm.eyes;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.hardware.SensorManager;
import android.hardware.SensorListener;
public class IBMEyes extends Activity implements SensorListener {
    final String tag = "IBMEyes";
    SensorManager sm = null;
    TextView xViewA = null;
    TextView yViewA = null;
    TextView zViewA = null;
    TextView xViewO = null;
    TextView yViewO = null;
    TextView zViewO = null;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       // get reference to SensorManager
        sm = (SensorManager) getSystemService(SENSOR_SERVICE);
        setContentView(R.layout.main);
        xViewA = (TextView) findViewById(R.id.xbox);
        yViewA = (TextView) findViewById(R.id.ybox);
        zViewA = (TextView) findViewById(R.id.zbox);
        xViewO = (TextView) findViewById(R.id.xboxo);
        yViewO = (TextView) findViewById(R.id.yboxo);
        zViewO = (TextView) findViewById(R.id.zboxo);
    }
    public void onSensorChanged(int sensor, float[] values) {
        synchronized (this) {
            Log.d(tag, "onSensorChanged: " + sensor + ", x: " + 
values[0] + ", y: " + values[1] + ", z: " + values[2]);
            if (sensor == SensorManager.SENSOR_ORIENTATION) {
                xViewO.setText("Orientation X: " + values[0]);
                yViewO.setText("Orientation Y: " + values[1]);
                zViewO.setText("Orientation Z: " + values[2]);
            }
            if (sensor == SensorManager.SENSOR_ACCELEROMETER) {
                xViewA.setText("Accel X: " + values[0]);
                yViewA.setText("Accel Y: " + values[1]);
                zViewA.setText("Accel Z: " + values[2]);
            }            
        }
    }

    public void onAccuracyChanged(int sensor, int accuracy) {
    	Log.d(tag,"onAccuracyChanged: " + sensor + ", accuracy: " + accuracy);
    }
    @Override
    protected void onResume() {
        super.onResume();
      // register this class as a listener for the orientation and accelerometer sensors
        sm.registerListener(this, 
                SensorManager.SENSOR_ORIENTATION |SensorManager.SENSOR_ACCELEROMETER,
                SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    protected void onStop() {
        // unregister listener
        sm.unregisterListener(this);
        super.onStop();
    }    
}

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

Метод действия onCreate получает ссылку на SensorManager, где расположены все связанные с датчиками функции. Кроме того, метод onCreate создает ссылки на шесть виджетов TextView, в которые будут выводиться результаты измерений.

Метод onResume(), используя ссылку на SensorManager, регистрируется на прием обновлений данных датчика посредством метода registerListener:

  • Первый параметр представляет собой экземпляр класса, реализующий интерфейс SensorListener.
  • Второй параметр - это битовая маска желаемых датчиков. В данном случае приложение запрашивает данные из SENSOR_ORIENTATION и SENSOR_ACCELEROMETER.
  • Третий параметр указывает системе, как быстро должны обновляться значения датчиков для данного приложения.

Когда работа приложения приостанавливается, нужно отменить регистрацию приемника, чтобы больше не получать обновления значений датчика. Это делается с помощью метода unregisterListener класса SensorManager. Единственным параметром является экземпляр SensorListener.

При вызове обоих методов registerListener и unregisterListener приложение использует ключевое слово this. Обратите внимание на ключевое слово implements в определении класса, которое декларирует, что этот класс реализует интерфейс SensorListener. Вот почему в registerListener и unregisterListener передается this.

SensorListener должен реализовать два метода: onSensorChange и onAccuracyChanged. Наш пример приложения не заботится о точности датчиков, а лишь вводит текущие значения X, Y и Z этих датчиков. Метод onAccuracyChanged, по существу, ничего не делает; он просто добавляет запись в журнал при каждом вызове.

Кажется, что метод onSensorChanged вызывается постоянно, так как акселерометр и датчик ориентации передают данные быстро. Чтобы определить, какой датчик передает данные, мы смотрим на первый параметр. Когда передающий датчик выявлен, соответствующие элементы пользовательского интерфейса обновляются данными, содержащимися в массиве значений с плавающей запятой, принятом в качестве второго аргумента метода. Хотя пример просто отображает эти значения, в более сложных приложениях они могут анализироваться, сравниваться с предыдущими значениями или использоваться в некоем алгоритме распознавания образов для определения того, что делает пользователь (или что происходит во внешней среде).

Теперь, когда мы рассмотрели подсистему датчиков, в следующем разделе приведем пример кода, который записывает звук на телефон Android. Этот пример работает на устройстве для разработчиков Dev1.


Применение MediaRecorder

Пакет Android.media содержит классы для взаимодействия с мультимедийной подсистемой. Класс android.media.MediaRecorder используется для записи медиафрагментов, включая аудио и видео. MediaRecorder действует как конечный автомат. Вы задаете различные параметры, такие как устройство-источник и формат. После установки запись может выполняться как угодно долго, пока не будет остановлена.

В листинге 2 приведен код для записи звука на устройство Android. Этот код не включает элементы пользовательского интерфейса приложения (полный исходный код см. в разделе загрузок).

Листинг 2. Запись аудиофрагмента
MediaRecorder mrec ;
File audiofile = null;
private static final String TAG="SoundRecordingDemo";
protected void startRecording() throws IOException 
{
   mrec.setAudioSource(MediaRecorder.AudioSource.MIC);
   mrec.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
   mrec.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
   if (mSampleFile == null) 
   {
       File sampleDir = Environment.getExternalStorageDirectory();
       try 
       { 
          audiofile = File.createTempFile("ibm", ".3gp", sampleDir);
       }
       catch (IOException e) 
       {
           Log.e(TAG,"sdcard access error");
           return;
       }
   }
   mrec.setOutputFile(audiofile.getAbsolutePath());
   mrec.prepare();
   mrec.start();
}
protected void stopRecording() 
{
   mrec.stop();
   mrec.release();
   processaudiofile(audiofile.getAbsolutePath());
}
protected void processaudiofile() 
{
   ContentValues values = new ContentValues(3);
   long current = System.currentTimeMillis();
   values.put(MediaStore.Audio.Media.TITLE, "audio" + audiofile.getName());
   values.put(MediaStore.Audio.Media.DATE_ADDED, (int) (current / 1000));
   values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/3gpp");
   values.put(MediaStore.Audio.Media.DATA, audiofile.getAbsolutePath());
   ContentResolver contentResolver = getContentResolver();

   Uri base = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
   Uri newUri = contentResolver.insert(base, values);

   sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, newUri));
}

В методе startRecording создается и инициализируется экземпляр MediaRecorder.

  • В качестве источника данных выбирается микрофон (MIC).
  • Выходной формат устанавливается в 3GPP (файлы *.3gp) - медиаформат, ориентированный на мобильные устройства.
  • Кодер настроен на AMR_NB - аудиоформат с частотой дискретизации 8 кГц. NB означает узкую полосу частот. Различные форматы данных и имеющиеся кодеры рассматриваются в документации SDK.

Аудиофайл хранится не во внутренней памяти, а на отдельной карте. External.getExternalStorageDirectory() возвращает имена карты памяти и временного файла, созданного в этом каталоге. Затем этот файл связывается с экземпляром MediaRecorder, обращаясь к методу setOutputFile. Аудиоданные будут храниться в этом файле.

Вызов метода prepare завершает инициализацию MediaRecorder. Когда нужно начать процесс записи, вызывается метод start. Запись в файл на карте памяти ведется до тех пор, пока не будет вызван метод stop. Этот метод освобождает ресурсы, выделенные экземпляру MediaRecorder.

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

  • Добавить аудиозапись в медиатеку на устройстве.
  • Выполнить некоторые шаги по распознаванию звука:
    • Не плач ли это ребенка?
    • Это голос хозяина, и нужно разблокировать телефон?
    • Или это фраза "Сезам, откройся", которая отпирает потайную дверь?
  • Автоматически загрузить звуковой файл в сетевую папку для обработки.

В примере кода метод processaudiofile добавляет аудиозапись в медиатеку. Для уведомления встроенного приложения о том, что доступна новая информация, используется Intent.

И последнее замечание об этом фрагменте кода: сразу после создания он не будет записывать аудио. Вы увидите созданный файл, но не услышите звука. Нужно добавить разрешение в файл AndroidManifest.xml:

<uses-permission android:name="android.permission.RECORD_AUDIO"></uses-permission>

Теперь вы кое-что знаете о взаимодействии с датчиками Android и записи аудио. В следующем разделе приводится более широкий обзор архитектуры приложений, связанных со сбором данных и системами передачи сообщений.


Android как измерительная платформа

Платформа Android располагает широкими возможностями по измерению параметров окружающей среды. При таком многообразии входных параметров и датчиков в сочетании с мощными вычислительными и сетевыми функциями Android становится привлекательной платформой для создания систем взаимодействия с реальным миром. На рисунке 2 упрощенно показано соотношение между входами, логикой приложений и методами уведомления или выходами.

Рисунок 2. Блок-схема Android-ориентированной системы датчиков
Блок-схема построенной на Android системы датчиков

Это очень гибкая архитектура; логику приложения можно распределять между локальным Android-устройством и серверными ресурсами, которые могут подключаться к более крупным базам данных и вычислительным мощностям. Например, аудиотрек, записанный на локальном Android-устройстве, можно отправить методом POST на Web-сервер, где данные сопоставляются с базой данных образцов голоса. Ясно, что это только самое поверхностное знакомство с возможностями платформы. Надеюсь, что вы заинтересовались и начнете копать глубже, чтобы использовать платформу Android не только для целей мобильной телефонии.


Заключение

Из этой статьи вы получили первое представление о работе с датчиками Android. Приведенные примеры приложений определяют ориентацию и ускорение, а также взаимодействуют со средствами звукозаписи с использованием класса MediaRecorder. Android - гибкая, удобная платформа для создания систем взаимодействия с реальным миром. Сфера влияния Android очень быстро расширяется, и эта платформа осваивает все новые области применения. Не упускайте ее из вида.


Загрузка

ОписаниеИмяРазмер
Исходный код примера Eyes os-android-sensorEyes.zip28 КБ
Исходный код примера IBMAudioos-android-sensorIBMAudio.zip33 КБ

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


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

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Мобильные приложения
ArticleID=469994
ArticleTitle=Подключение к датчикам в Android
publish-date=02262010