Программирование для Windows Phone для начинающих

Датчики и службы

Показывать лекцию целиком

Датчики и сервисы

Цель лекции: научиться работать с координатами, акселерометром, картами и другими, службами и датчиками.

Устройство Windows Phone 7 должно иметь ряд аппаратных возможностей – иногда называемых датчиками – и предоставлять некоторые программные сервисы, возможно, с аппаратной поддержкой. Рассмотрим те из них, которые наиболее интересны разработчикам:

  • Wi-Fi - для доступа к Интернету телефон снабжен Wi-Fi в дополнение к доступу к данным по технологиям 3G (3G data access), предоставляемому поставщиком мобильной связи. В программное обеспечение, установленное на телефоне, включена версия Internet Explorer.
  • Камера – телефон снабжен камерой с разрешением не менее 5 мегапикселей и вспышкой. Программы могут вызывать ПО камеры для осуществления ввода с нее или регистрироваться как приложение расширений для обработки фотографий. В этом случае они будут отображаться в меню для получения доступа к сфотографированным изображениям, например, для обработки этих изображений определенным образом.
  • Акселерометр- измеряет ускорение, что является физической величиной, обозначающей изменение скорости. Если камера неподвижна, акселерометр реагирует на изменение гравитации. Программы могут получать трехмерный вектор, определяющий положение камеры относительно земли. Акселерометр также может выявлять резкие перемещения телефона.
  • Местоположение - по желанию пользователя телефон может применять множество стратегий определения своего географического местоположения. Телефон передает в аппаратное устройство GPS данные из Интернета или вышек сотовой связи. При его перемещении также могут предоставляться данные о направлении и скорости.
  • Вибрация - программное управление вибрацией телефона.
  • FM-радио - программный доступ к FM-радио.
  • Принудительные уведомления - для обновления данных, предоставляемых некоторыми Веб-сервисами, телефону пришлось бы регулярно опрашивать эти сервисы. Это могло бы приводить к разрядке батареи и сокращению времени автономной работы.
  • Акселерометр

    Устройства Windows Phone имеют акселерометр – небольшое аппаратное устройство, по сути, измеряющее силу, которая, согласно элементарной физике, пропорциональна ускорению. Когда телефон неподвижен, акселерометр регистрирует силу притяжения, т.е. позволяет определить положение телефона относительно земли.

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

    Акселерометр также реагирует на неожиданные перемещения, такие как встряхивание или толчки, что позволяет моделировать игру в кости или некоторые другие виды действий с элементом случайности. Изобретение различных применений акселерометра является одной из множества задач разработки приложений для телефонов.

    Выходные данные акселерометра удобно представить в виде трехмерного вектора. Обычно векторы записываются полужирным шрифтом, поэтому вектор ускорения может быть обозначен так: $$(x, y, z)$$. В XNA есть тип трехмерный вектор, в Silverlight нет.

    Тогда как три координаты $$(x, y, z)$$ точно определяют точку в пространстве, вектор $$(x, y, z)$$ обозначает направление и величину. Очевидно, что точка и вектор взаимосвязаны. Направление вектора $$(x, y, z)$$ – это луч из точки $$(0, 0, 0)$$ в точку $$(x, y, z)$$. Но вектор $$(x, y, z)$$ это совсем не отрезок, соединяющий $$(0, 0, 0)$$ и $$(x, y, z)$$. Это направление этого отрезка.

    Для вычисления модуля вектора $$(x, y, z)$$ используется трехмерная форма теоремы Пифагора:

    Модуль = $$\sqrt{x^2+y^2+z^2}$$

    Для работы с акселерометром ассоциируем с телефоном трехмерную систему координат. Не важно, как ориентирован телефон, но положительное направление оси $$Y$$ будет проходить вдоль максимального размера телефона снизу (где располагаются кнопки) вверх, и положительное направление оси $$X$$ – слева направо. Ось $$Z$$ направлена перпендикулярно телефону, прямо на пользователя.

    (рис 8.1) Трехмерная система координат

    Это традиционная трехмерная система координат, такая же используется при создании 3D-графики на XNA. Ее называют правой системой координат. Расположите указательный палец правой руки вдоль положительного направления $$Х$$, средний палец – вдоль положительного направления $$Y$$, и большой палец будет указывать на положительное направление $$Z$$.

    Эта координатная система фиксирована относительно телефона независимо от его положения в пространстве и независимо от ориентации изображения выполняемых приложений. Кстати, как можно догадаться, акселерометр является основным компонентом, обеспечивающим возможность изменения ориентации изображения приложений Windows Phone 7.

    Когда телефон неподвижен, вектор акселерометра указывает на землю. Модуль равен 1, т.е. $$1g$$ (постоянная – ускорение свободного падения), что соответствует силе притяжения на поверхности земли. Когда телефон расположен вертикально, вектор ускорения – $$(0, –1, 0)$$, т.е. направлен прямо вниз.

    Поверните телефон на $$90°$$ против часовой стрелки (так называемое левостороннее альбомное расположение), и вектор ускорения станет равным $$(–1, 0, 0)$$; переверните телефон "вверх ногами" – вектор равен $$(0, 1, 0)$$; еще один поворот на $$90°$$ против часовой стрелки обеспечивает правостороннее альбомное расположение и вектор ускорения равный $$(1, 0, 0)$$. Положите телефон на стол экраном вверх, и вектор ускорения будет $$(0, 0, –1)$$. (Эмулятор Windows Phone 7 всегда возвращает это значение.)

    Конечно, вектор ускорения редко будет принимать такие точные значения, даже его модуль не будет точно равен 1. Для неподвижного телефона значения модуля могут колебаться в пределах нескольких процентов для разных ориентаций. На Луне величина вектора ускорения Windows Phone 7 будет в районе 0,17, но мобильная связь неустойчивая.

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

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

    При свободном падении модуль вектора ускорения, теоретически, должен стремиться к нулю.

    Для работы с акселерометром используется библиотека Microsoft.Devices.Sensors и пространство имен Microsoft.Devices.Sensors. В файле WMAppManifest.xml необходимо указать:

    <Capability Name="ID_CAP_SENSORS"/>

    Это задается по умолчанию.

    В приложении создается экземпляр класса Accelerometer (Акселерометр), задается обработчик события ReadingChanging (Изменение показаний прибора) и вызывается метод Start.

    Рассмотрим проект SilverlightAccelerometer.project, который просто обеспечивает выведение на экран текущих показаний. Центрированный TextBlock описывается в файле XAML:

    Проект Silverlight: SilverlightAccelerometer Файл: MainPage.xaml (фрагмент)

    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <TextBlock Name="txtblk" HorizontalAlignment="Center" VerticalAlignment="Center" />
     </Grid>

    Чтобы добавить библиотеку Microsoft.Devices.Sensors в проект нужно:

  • нажать правой кнопкой на узел SilverlightAccelerometer / References в окне Solution Explorer и выберите Add Reference. На вкладке .NET появившегося окна для выбора библиотек выберите пункт Microsoft.Devices.Sensors и нажать OK; (рис 8.2) Добавление библиотеки Microsoft.Devices.Sensors
  • добавить строчку using Microsoft.Devices.Sensors; в MainPage.xaml.cs
  • Это приложение будет обеспечивать вывод на экран вектора акселерометра на протяжении всего времени его выполнения, поэтому создание класса Accelerometer и вызов метода Start происходит в конструкторе:

    Проект Silverlight: SilverlightAccelerometer Файл: MainPage.xaml.cs (фрагмент)

    public MainPage()
            { 
                InitializeComponent();
                Accelerometer acc = new Accelerometer();
                acc.ReadingChanged += OnAccelerometerReadingChanged;
                try 
                { 
                    acc.Start(); 
                } 
                catch (Exception exc) 
                { 
                    txtblk.Text = exc.Message; 
                } 
    
            } 
    

    Документация предупреждает, что вызов Start может привести к формированию исключения, поэтому приложение защищает себя от этого. Класс Accelerometer также поддерживает методы Stop (Остановить) и Dispose (Удалить), но в данной программе они не используются. Для обеспечения сведения о доступности акселерометра и его состоянии предоставляется свойство State.

    Событие ReadingChanged обеспечивается классом аргументов события AccelerometerReadingEventArgs. Этот объект имеет свойства $$X$$, $$Y$$ и $$Z$$ типа double и свойство TimeStamp типа DateTimeOffset (Смещение даты-времени). В приложении SilverlightAccelerometer задача обработчика события – форматировать эти данные в строку и задать ее как значение свойства Text объекта TextBlock.

    Загвоздка здесь в том, что обработчик события (в данном случае, OnAccelerometerReadingChanged (При изменении показаний акселерометра)) вызывается в другом потоке выполнения. Это означает, что он должен обрабатываться особым образом.

    Немного теории. Создание и доступ ко всем элементам и объектам пользовательского интерфейса в приложении на Silverlight выполняется в основном потоке выполнения, который часто называют потоком пользовательского интерфейса или UI-потоком. Эти объекты пользовательского интерфейса не являются потокобезопасными; для них не предусмотрен одновременный доступ из множества потоков. Поэтому Silverlight не допустит доступа к объекту пользовательского интерфейса из потока, не являющегося UI-потоком.

    Это означает, что метод OnAccelerometerReadingChanged не может напрямую обратиться к элементу TextBlock, чтобы задать новое значение его свойству Text.

    К счастью в пространстве имен System.Windows.Threading описан класс Dispatcher (Диспетчер), который обеспечивает решение этой проблемы. Через класс Dispatcher можно направлять задания из потока, не являющегося UI-потоком, в очередь, которая позднее будет обработана UI-потоком. Все это звучит довольно запутанно, но с точки зрения разработчика все довольно просто, потому что эти задания принимают форму простых вызовов методов.

    Экземпляр Dispatcher уже доступен. Класс DependencyObject (Зависимость) описывает свойство Dispatcher типа Dispatcher, и от DependencyObject наследуется множество классов Silverlight. С экземплярами всех этих классов можно работать из потоков, не являющихся UI-потоками, потому что все они имеют свойства Dispatcher. Из любого производного от DependencyObject класса, созданного в UI-потоке, может использоваться любой объект Dispatcher. Они все одинаковые.

    Класс Dispatcher определяет метод CheckAccess (Проверить возможность доступа), который возвращает true, если текущий поток имеет доступ к конкретному объекту пользовательского интерфейса. (Метод CheckAccess также дублируется самим DependencyObject.) Для случаев, когда доступ к объекту из текущего потока невозможен, Dispatcher предоставляет две версии метода Invoke (Вызвать), с помощью которых выполняется отправка заданий для UI-потока.

    В проекте SilverlightAccelerometer реализована версия кода, доскональная с точки зрения синтаксиса.

    В развернутой версии требуется делегат и метод, соответствующий этому делегату. Делегат (и метод) не должен иметь возвращаемого значения, но у него должно быть такое количество аргументов, которого будет достаточно для выполнения задания. В данном случае это задание состоит в присвоении строки в качестве значения свойства Text объекта TextBlock:

    Проект: SilverlightAccelerometer Файл: MainPage.xaml.cs (фрагмент)

    delegate void SetTextBlockTextDelegate(TextBlock txtblk, string text);
            void SetTextBlockText(TextBlock txtblk, string text) 
            { 
                txtblk.Text = text; 
            }

    OnAccelerometerReadingChanged отвечает за вызов SetTextBlockText (Задать текст блока текста). Он впервые использует CheckAccess, чтобы определить возможность вызова метода SetTextBlockText напрямую. Если это невозможно, обработчик вызывает метод BeginInvoke (Начать вызов). Его первым аргументом является экземпляр делегата с методом SetTextBlockText. За ним следуют все необходимые SetTextBlockText аргументы:

    Проект: SilverlightAccelerometer Файл: MainPage.xaml.cs (фрагмент)

    void OnAccelerometerReadingChanged(object sender, AccelerometerReadingEventArgs args) 
            { 
                string str = String.Format("X = {0:F2}\n" + "Y = {1:F2}\n" + "
    Z = {2:F2}\n\n" + "Magnitude = {3:F2}\n\n" + "{4}", 
    args.X, args.Y, args.Z, Math.Sqrt(args.X * args.X + args.Y * args.Y + args.Z * args.Z), args.Timestamp); 
                if (txtblk.CheckAccess()) { SetTextBlockText(txtblk, str); 
                } 
                else 
                { 
                    txtblk.Dispatcher.BeginInvoke(new SetTextBlockTextDelegate(SetTextBlockText), txtblk, str); 
                } 
            } 

    Это не самый плохой вариант, но необходимость перепрыгивать из потока в поток повлекла за собой применение дополнительного метода и делегата.

    В эмуляторе Windows Phone 7 нет акселерометра, поэтому он всегда возвращает значение $$(0, 0, –1)$$, свидетельствующее о том, что телефон лежит на плоской поверхности. Выполнять это приложение имеет смысл только на реальном телефоне:

    (рис 8.3) Результат программы

    Географические координаты

    С согласия пользователя приложение для Windows Phone 7 может принимать географические координаты телефона по методу Assisted-GPS или A-GPS.

    Наиболее точный метод определения местоположения – по сигналам спутников Глобальной системы позиционирования (Global Positioning System, GPS). Но GPS может быть медленной. Эта система плохо работает в больших городах и в помещениях, ее применение энергоневыгодно, потому что приводит к большому расходу заряда батареи. Для энергосбережения и увеличения скорости, система A-GPS может определять местоположение по вышкам сотовой связи или сети. Эти методы намного более производительны и надежны, но менее точные.

    Основным классом для определения местоположения является GeoCoordinateWatcher (Система отслеживания географических координат). Нам понадобится ссылка на сборку System.Device и директива using для пространства имен System.Device.Location. В файле WMAppManifest.xml должен присутствовать такой тег:

    <Capability Name="ID_CAP_LOCATION"/>
    

    Конструктор GeoCoordinateWatcher принимает один из элементов перечисления GeoPositionAccuracy (Точность географических координат):

  • Default (По умолчанию)
  • High (Высокая)
  • После создания объекта GeoCoordinateWatcher необходимо задать обработчик события PositionChanged (Местоположение изменилось) и вызвать метод Start. Событие PositionChanged возвращает объект GeoCoordinate (Географические координаты), имеющий восемь свойств:

  • Latitude (Широта), значение типа double в диапазоне от –90 до 90 градусов.
  • Longitude (Долгота), значение типа double в диапазоне от –180 до 180 градусов.
  • Altitude (Высота) типа double.
  • HorizontalAccuracy (Точность в горизонтальном направлении) и VerticalAccuracy (Точность в вертикальном направлении) типа double.
  • Course (Курс), значение типа double в диапазоне от 0 до 360 градусов.
  • Speed (Скорость) типа double.
  • IsUnknown (Неизвестно), булево значение, которое равно true, если Latitude или Longitude не является числом.
  • Если приложение не имеет разрешения на определение местоположения, свойства Latitude и Longitude будут иметь значение Double.NaN, и IsUnknown будет true.

    Кроме того, в GeoCoordinate есть метод GetDistanceTo (Определить расстояние до), вычисляющий расстояние между двумя объектами GeoCoordinate.

    Мы рассмотрим первые два свойства. Их называют географическими координатами. Они определяют точку на поверхности Земли. Широта – это угловое расстояние от экватора. Как правило, широта обозначается как угол от 0 до 90 градусов с указанием С или Ю (сервер или юг). Например, широта Нью-Йорка примерно 40°С. В объекте GeoCoordinate широты к северу от экватора соответствуют положительным значениям, и широты к югу от экватора – отрицательным. Северный Полюс соответствует 90°, и Южный Полюс – –90°.

    Географические местоположения с одинаковой широтой образуют линию широты или параллель. Для заданной широты долгота – это угловое расстояние от нулевого меридиана, который проходит через Гринвичскую астрономическую обсерваторию, Англия. Долготу определяют как западную или восточную. Нью-Йорк располагается на 74° западной долготы, потому что он находится на запад от нулевого меридиана. В объекте GeoCoordinate положительные значения долготы соответствуют восточному полушарию, и отрицательные значения – западному. Значения 180 и –180 встречаются на линии перемены дат.

    Пространство имен System.Device.Location включает классы, которые используют географические координаты для определения адресов (улиц и городов), но эта возможность не реализована в первой выпущенной версии Windows Phone 7.

    Проект XnaLocation просто обеспечивает вывод на экран числовых значений.

    Не забудьте добавить соответствующий шрифт.

    Добавьте библиотеку System.Device тем же способом, как мы добавляли Microsoft.Devices.Sensors в прошлой программе.

    Проект XNA: XnaLocation Файл: Game1.cs (фрагмент, демонстрирующий поля)

    …
    using System.Device.Location;
    …
      public class Game1 : Microsoft.Xna.Framework.Game 
        { GraphicsDeviceManager graphics;
            SpriteBatch spriteBatch;
            SpriteFont segoe14;
            string text = "Obtaining location...";
            Viewport viewport;
            Vector2 textPosition; 
      …

    Как и в случае с акселерометром, GeoCoordinateWatcher создается и инициализируется в перегруженном Initialize. Обработчик события вызывается в том же потоке, поэтому для форматирования результатов в строку не надо делать ничего особенного:

    Проект XNA: XnaLocation Файл: Game1.cs (фрагмент)

      protected override void Initialize()
            {
                GeoCoordinateWatcher geoWatcher = new GeoCoordinateWatcher();
                geoWatcher.PositionChanged += OnGeoWatcherPositionChanged; 
                geoWatcher.Start();
                base.Initialize(); 
            }
            void OnGeoWatcherPositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> args) 
            { 
                text = String.Format("Latitude: {0:F3}\r\n" + "Longitude: {1:F3}\r\n" + 
    "Altitude: {2}\r\n\r\n" + "{3}", args.Position.Location.Latitude, 
    args.Position.Location.Longitude, args.Position.Location.Altitude, args.Position.Timestamp);
            } 

    Метод LoadContent просто получает шрифт и сохраняет Viewport для позиционирования текста впоследствии:

    Проект XNA: XnaLocation Файл: Game1.cs (фрагмент)

    protected override void LoadContent() 
            { 
                spriteBatch = new SpriteBatch(GraphicsDevice); 
                segoe14 = this.Content.Load<SpriteFont>("Segoe14");
                viewport = this.GraphicsDevice.Viewport;
            } 

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

    Проект XNA: XnaLocation Файл: Game1.cs (фрагмент)

    protected override void Update(GameTime gameTime) 
           {
               // Обеспечиваем возможность выхода из игры 
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit();
               Vector2 textSize = segoe14.MeasureString(text);
     textPosition = new Vector2((viewport.Width - textSize.X) / 2,(viewport.Height - textSize.Y) / 2);
               base.Update(gameTime);
           } 

    Метод Draw обычный:

    Проект XNA: XnaLocation Файл: Game1.cs (фрагмент)

    protected override void Draw(GameTime gameTime) 
           {
               GraphicsDevice.Clear(Color.Navy); 
               spriteBatch.Begin(); 
               spriteBatch.DrawString(segoe14, text, textPosition, Color.White); 
               spriteBatch.End(); base.Draw(gameTime); 
           } 

    GeoCoordinateWatcher выполняется в течение всего выполнения приложения, поэтому он должен обновлять данные о местоположении в случае перемещения телефона.

    Ключевые термины

    Microsoft.Devices.Sensors – библиотека, которая используется для работы с акселерометром в Windows Phone 7.

    Акселерометр– небольшое аппаратное устройство, по сути, измеряющее силу, пропорциональную ускорению.

    Глобальная система позиционирования (Global Positioning System, GPS) – система, использующая для определения местоположения, сигнал спутников.

    Краткие итоги

    В данной лекции мы:

  • разобрали все датчики и сервисы, используемые в устройствах на Windows Phone 7;
  • научились использовать акселерометр;
  • работали с географическими координатами, написали программу, реагирующую на изменение координат и выводящую их на экран.
  • Набор для практики

    Упражнения

    Если вы пользовались эмулятором, постарайтесь найти мобильное устройство на базе WP7 и протестировать на нём написанные программы. Это позволит более наглядно продемонстрировать работу акселерометра и изменение географических координат.

    Вернуться к учебному плану