Pull to refresh

Windows 7 Sensor and Location platform

Reading time10 min
Views4.2K
Одна из составных частей Windows 7 — Sensor and Location platform. Sensor and Location — это часть Windows 7, которая позволяет организовать работу с различными датчиками и дополнительными устройствами для измерения чего-либо.

Зачем это нужно? Датчики нужны для того, чтобы упросить некоторые тривиальные действия и избавить нас от лишних забот в работе. Особенно актуально это для владельцев ноутбуков, чья жизнь является очень динамичной. Представьте себе, что в компьютер встроен датчик освещения, который доступен для всех приложений и дает возможность этим приложениям корректировать свою картинку в зависимости от освещения. Другим примером может являться датчик GPS-координат. В этом случае приложения могут подстраиваться под ту местность, где вы в данный момент находитесь. Например, приложения могут отображать информацию о погоде именно для того города, где вы находитесь. На самом деле примеров можно привести большое количество, все зависит от фантазии и конкретных случаев. Приложения, которые изменяют свое поведение в зависимости от внешних условий выделяются в отдельный класс приложений и называются контекстно-зависимыми приложениями.



Может возникнуть вопрос — «а что, собственно, изменилось?», «почему этого нельзя было сделать раньше?». Ответ прост — ранее эти сценарии также можно было реализовать. Однако, сделать это было не так просто. Фактически, работа с внешними датчиками сводилась к обмену информацией через COM-порт и каждый датчик имел свой специфический API. По этой причине было очень тяжело организовать какой-то универсальный программный интерфейс, с которым можно было работать одновременно из нескольких приложений и этот процесс был бы прозрачен.

Именно эту проблему решает библиотека Sensor and Location. С ее помощью можно обращаться к различными датчикам и получать от них информацию в едином для всех стилей. Важно, что эта проблема решается на уровне операционной системы. Такой шаг может дать новый толчок для развития контекстно-зависимых приложений. Далее приведена диаграмма, отображающая структуру объектов для работы с датчиками. Далее мы рассмотрим это подробнее.



Для подключения датчика к Sensor and Location platform в Windows 7 необходимо реализовать для него драйвер и несложные классы-обертки на .NET для работы с этим датчиком.

Безусловно, в ближайшее время конечные пользователи вряд ли смогут в полной мере ощутить мощь всей этой платформы. Для этого потребуется некоторое время, чтобы разработчики аппаратного обеспечения могли разработать и встроить свои датчики в аппаратные платформы. Однако, нам, разработчикам, можно начинать готовиться к этому уже сегодня. Поэтому далее я планирую рассказать о том, каким образом работать с Sensor and Location platform в контексте наших бизнес-приложений.

Для того, чтобы проводить эксперименты не с виртуальными датчиками, а с чем-то более-менее приближенным к реальности, мы будем использовать устройство от Freescale semiconductor, построенное на базе микроконтроллера JMBADGE2008-B. Это устройство представляет собой небольшую плату, на которой также есть несколько датчиков — акселерометр, датчик света и кнопки.



Это устройство разработано специально для демонстрации возможностей Sensor and Location platform в Windows 7. Фактически, каждый может купить его. Таким образом, это устройство хорошо подходит для того, чтобы продемонстировать эту возможность Windows 7.

Прежде чем рассматривать конкретные приложения, давайте посмотрим на то, как устроена платформа Sensor and Location. До появления Windows 7 и Sensor&Location platform подключение различных датчиков сводилось к реализации драйвера и программного обеспечения к нему.



При такой организации задачи взаимодействия с внешними датчиками можно, но тяжело. Для этого каждое приложение должно взаимодействовать с тем API, которое предложит разработчик датчика и программного обеспечения, которое обслуживает этот датчик. Проблема встает особенно остро в случае, если приложение должно использовать множество однотипных датчиков от разных производителей. Как эту проблему предлагает решить Sensor&Location platform?

На уровне операционной системы присутствуют механизмы работы с датчиками. Существует стандартный унифицированный программный интерфейс для работы с датчиками — Sensor API. При этом все взаимодействия с датчиком происходят именно через Sensor API. Важно, что взаимодействия со всеми датчиками происходит в едином стиле. Теперь вам не нужно интегрироваться с native API через p/invoke.



Для того, чтобы работать с Sensor and Location API необходимо загрузить соответствующую библиотеку .NET Interop Sample Library. В ней существуют .NET-обертки для работы с Sensor API. В нем существует несколько классов, при помощи которых можно работать с датчиками.

Класс SensorManager является точкой входа. Через него можно получить информацию о сенсорах, а также поработать с ними. Например, при помощи метода GetSensorBySensorId<> можно получить доступ к интересующему нас датчику. Каждый датчик должен иметь класс-обертку, который наследуется от базового класса Sensor. В .NET Interop Sample Library уже существуют три таких реализации — AmbientLightSensor, Accelerometer3D, UnknownSensor.



Основная идея при работе с датчиками заключается в следующем. При изменении состояния датчика (подключен/отключен/активен/итд) генерируется событие StateChanged. Это событие необходимо для начала или прекращения работы с датчиками. После того, как связь с датчиком налажена, при получении новых данных генерируется событие DataReportChanged. Насколько часто будет генерироваться это событие зависит от реализации датчика и драйвера к нему. При обработке этого события можно считать состояние датчиков и каким-то образом изменить работу приложения. Для этих целей используется метод GetProperty. В параметрах этого метода передается идентификатор свойства, которое нужно считать от датчика. Как правило, детали вызовов этого метода скрываются в классах, которые реализуются для специфичного датчика.

Кроме того, каждый датчик имеет собственный идентификатор (GUID), по которому можно идентифицировать устройство. При реализации класса-обертки для датчика этот ID указывается при помощи атрибута. Таким образом, доступ к датчику можно получить как явно указав идентификатор этого датчика, так и сославшись на этот класс-обертку.

/// /// Represents a generic ambient light sensor
///
[SensorDescription(«97F115C8-599A-4153-8894-D2D12899918A»)]
public class AmbientLightSensor: Sensor
{
//…
//…
//…
var sensors = SensorManager.GetSensorsByTypeId<AmbientLightSensor>();


Давайте попробуем реализовать несколько примеров работы с датчиками, которые доступны в устройстве от Freescale. Мы будем работать с двуми типами датчиков — акселерометром (позволяет измерять угол наклона устройства) и датчиком света (измеряет уровень освещенности в помещении).

Первое приложение, которое мы реализуем будет отображать уровень освещенности в виде горящей лампочки на форме. Для начала подпишемся на событие изменения состояния в Sensor API. Это необходимо для того, чтобы приложение начало работать, если датчик подключен на ходу. В обработчике этого события мы получим список всех датчиков нужного типа и подпишемся у них на событие DataReportChanged. В обработчике этого события мы будем считывать значение с датчика освещенности и записывать его в TextBox на форме. Т.к. событие генерируется в дополнительном потоке, также потребуется сделать вызов метода Dispatcher.Invoke, чтобы обработка шла в основном потоке и мы могли взаимодействовать с элементами на форме. Таким образом получим следующий код.

private void Window_Loaded(object sender, RoutedEventArgs e)
{
SensorManager.SensorsChanged += SensorManagerSensorsChanged;
}
void SensorManagerSensorsChanged(SensorsChangedEventArgs change)
{
Dispatcher.Invoke((System.Threading.ThreadStart)(UpdateSensorsList));
}
private void UpdateSensorsList()
{
var sensors = SensorManager.GetSensorsByTypeId<AmbientLightSensor>();
foreach (var sensor in sensors)
sensor.DataReportChanged += delegate(Sensor sender, EventArgs e)
{
Dispatcher.Invoke((System.Threading.ThreadStart)(delegate
{
if (ActiveSensorsListBox.SelectedItem == sender)
{
CurrentValue.Text =
((AmbientLightSensor)sender).CurrentLuminousIntensity.Intensity.ToString();
}
}));
};
}


Теперь в TextBox на форме выводится текущее значение освещенности. Теперь нетрудно реализовать какую-то визуализацию для этого. При помощи привязок в WPF будем отображать степень освещенности в виде лампочек. В итоге получим следующее приложение.



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

Демонстрация >>

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

private void UpdateSensorsList()
{
foreach (var sensor in SensorManager.GetSensorsByTypeId<Accelerometer3D>())
{
sensor.DataReportChanged += delegate(Sensor sender, EventArgs e)
{
Dispatcher.Invoke((System.Threading.ThreadStart)(delegate
{
if (UseXCoordinate.IsChecked == true)
CurrentXValue.Text = ((Accelerometer3D)sender).CurrentAcceleration[Accelerometer3D.AccelerationAxis.X].ToString();
if (UseYCoordinate.IsChecked == true) CurrentYValue.Text = ((Accelerometer3D)sender).CurrentAcceleration[Accelerometer3D.AccelerationAxis.Y].ToString();
if (UseZCoordinate.IsChecked == true) CurrentZValue.Text = ((Accelerometer3D)sender).CurrentAcceleration[Accelerometer3D.AccelerationAxis.Z].ToString();
}));
};
}
}


Как видно из этого примера, код для работы с датчиками изменился не очень сильно. Фактически, изменился только код получения данных от датчиков, а остальное осталось неизменным.





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

Демонстрация >>

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





Также можно посмотреть это приложение в динамике.

Демонстрация >>

На этих примерах хорошо видно, что работа с датчиками в Windows 7 является очень простой. Однако, для этого необходимо иметь драйвер для Windows 7 и класс-обертку для Sensor&Location platform. Как правило, драйвера поставляются самим производителем аппаратной платформы, а вот класс-обертку можно реализовать самостоятельно.

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



Каждый сенсор имеет два основных типа идентификаторов — SensorId и TypeId. TypeId идентифицирует отдельный класс устройств. Например, по нему можно получить все датчики света в системе, или какие-то другие типы устройств. SensorId присваивается уникально каждому устройству. Например, если в системе три однотипных датчика движений, то каждый будет иметь уникальный идентификатор. Есть еще также CategoryId, который объединяет сенсоры в категории.

Каждый идентификатор представляет собой GUID. Они задаются производителями при разработке устройства и драйверов. Таким образом, можно получить конкретный сенсор только зная его ID. Каждый сенсор представлен классом Sensor. Он имеет общую информацию о сенсоре и методы, которые позволяют получить данные из обобщенных коллекций в нетипизированном виде. Понятно, что такое представление данных не очень удобно для наших приложений. Поэтому для каждого сенсора принято реализовывать класс-обертку в рамках Sensor API. Реализуется она путем наследования от общего класса Sensor. В демонстрационных примерах уже есть две такие реализации — для акселерометра и для датчика света. Однако, в устройстве, которое мы рассматривали ранее присутствуют также сенсорные кнопки, которые также можно использовать. Поэтому давайте реализуем такой класс для этого датчика.

Мы определим новый класс, который будет наследником класса Sensor. Для того, чтобы он распознавался в Sensor API его нужно пометить атрибутом SensorDescription, в котором указать TypeId для этого типа сенсоров. В базовом классе Sensor существуют две важные вещи для нас — свойство DataReport и событие DataReportChanged. Это свойство содержит данные от сенсора, а событие срабатывает при их изменении. Задача нашего класса — воспользоваться этими данными и доставить их пользователю нашего класса в удобном виде. Для этого создадим еще один небольшой класс, который будет заниматься разбором информации из DataReport.

Экспериментальным путем мы выясним, что при нажатии кнопки 1 генерируется код 1, при нажатии 2 — генерируется код 2, при нажатии 3 — генерируется код 4, а при нажатии 4 — генерируется код 8. Видно, что здесь используются просто двоичные разряды. Также генерируется код 0 в случае отпускания всех кнопок. Таким образом, мы можем написать следующий код.

[SensorDescription(«545C8BA5-B143-4545-868F-CA7FD986B4F6»)]
public class SwitchArraySensor: Sensor
{
public class SwitchArraySensorData
{
private static Guid KeyStateProperyId = new Guid(@«38564a7c-f2f2-49bb-9b2b-ba60f66a58df»);

public SwitchArraySensorData(SensorReport report)
{
uint state = (uint) report.Values[KeyStateProperyId][0];
Button1Pressed = (state & 0x01) != 0;
Button2Pressed = (state & 0x02) != 0;
Button3Pressed = (state & 0x04) != 0;
Button4Pressed = (state & 0x08) != 0;
}
public bool Button1Pressed { get; private set; }
public bool Button2Pressed { get; private set; }
public bool Button3Pressed { get; private set; }
public bool Button4Pressed { get; private set; }
}
public SwitchArraySensorData Current
{
get { return new SwitchArraySensorData(DataReport); }
}

public event EventHandler StateChanged;

public SwitchArraySensor()
{
DataReportChanged += SwitchArraySensor_DataReportChanged;
}
void SwitchArraySensor_DataReportChanged(Sensor sender, EventArgs e)
{
if (StateChanged != null)
{
StateChanged.Invoke(sender, e);
}
}
}


Фактически, этот класс является оберткой в Sensor API для нужного нам сенсора. Для его использования я должен подписаться на событие StateChanged и получать информацию через свойство Current.

Для получения списка доступных сенсоров заданного типа можно использовать метод GetSensorsByTypeId класса SensorManager. При этом TypeId этих сенсоров будет определяться исходя из заданного атрибута SensorDescription. Теперь, используя эти сенсоры мы можем подписаться на нужные событие и получать данные в удобном для приложения виде. Например, можем отображать на форме состояние нажатия кнопок.

private void Window_Loaded(object sender, RoutedEventArgs e)
{
var sensors = SensorManager.GetSensorsByTypeId<SwitchArraySensor>();
foreach (SwitchArraySensor sensor in sensors)
{
switch (sensor.FriendlyName)
{
case «Left Switch Array Sensor»: sensor.StateChanged += delegate(object leftSensor, EventArgs arg)
{
var buttons = ((SwitchArraySensor)leftSensor).Current;
SwitchState(LeftButton1, buttons.Button1Pressed);
SwitchState(LeftButton2, buttons.Button2Pressed);
SwitchState(LeftButton3, buttons.Button3Pressed);
SwitchState(LeftButton4, buttons.Button4Pressed);
};
break;
case «Right Switch Array Sensor»:
sensor.StateChanged += delegate(object rightSensor, EventArgs arg)
{
var buttons = ((SwitchArraySensor)rightSensor).Current;
SwitchState(RightButton1, buttons.Button1Pressed);
SwitchState(RightButton2, buttons.Button2Pressed);
SwitchState(RightButton3, buttons.Button3Pressed);
SwitchState(RightButton4, buttons.Button4Pressed);
};
break;
}
}
}


В итоге получим приложение, которое выглядит следующим образом.



Конечно, пример с реализацией подобного сенсора достаточно синтетический. Однако, он явно демонстрирует процесс подключения датчика к Sensor API.

Также, если вам необходимо реализовать собственный драйвер для устройства с целью подключения к Windows 7 Sensor and Location platform, рекомендую вам обратиться на официальный ресурс.

Успехов вам в создании ваших контекстно-зависимых приложений!

Демонстрационные приложения:
Ambient.zip
Accelerometer3D.zip
Combined.zip
ButtonSensor.zip
Tags:
Hubs:
+22
Comments14

Articles