В прошлой статье (http://www.oszone.net/24591/) мы обсудили текущий технологический ландшафт в сфере межмашинных (machine-to-machine, M2M) вычислений. Такие вычисления относятся к технологиям, которые соединяют устройства между собой (обычно для промышленных систем контроля и управления) в виде датчиков или контрольно-измерительных приборов. Распространение недорогих и простых в программировании крошечных компьютеров привело к расширению этой концепции в то, что теперь называют Интернетом вещей (Internet-of-Things, IoT), сделав возможными сценарии, где даже обыкновенную бытовую технику можно контролировать или использовать в качестве источников информации для генерации событий. Благодаря этому нетрудно посылать оповещения, когда нужно пополнить холодильник, автоматически закрыть оконные жалюзи на ночь или отрегулировать температуру в соответствии с привычками конкретной семьи.
Мы также рассмотрели пример использования Microsoft Azure Service Bus для соединения устройств как альтернативу применению VPN, когда нужно решать проблемы адресации, защиты и производительности, связанные с развертыванием большого количества датчиков или контрольно-измерительных приборов (КИП). Это становится все важнее, учитывая, что, согласно новейшему отчету «BI Intelligence» от Business Insider, к 2018 году в мире будет насчитываться более девяти миллиардов соединений, прямо относящихся к IoT (read.bi/18L5cg8).
Используя выделенную очередь Service Bus или Topic для какого-либо устройства, можно обеспечить элегантный способ, позволяющий добиться отказоустойчивости в условиях непостоянства подключений для приложений IoT. В данной статье мы подробно рассмотрим практическую реализацию на основе Microsoft Azure, которая иллюстрирует эти концепции, и разработаем проект Service Bus с очередями устройств, применяя рабочую роль для прослушивания этих очередей в Cloud Services, и запрограммируем устройство Arduino, которое будет выполнять команды, удаленно посылаемые мобильными клиентами, как показано на рис. 1.
Увеличить
Рис. 1. Архитектура IoT с применением Microsoft Azure Service Bus
Mobile and Desktop Devices | Мобильные и настольные устройства |
REST Interface/SDKs | REST-интерфейс/SDK |
Microsoft Azure Service Bus | Microsoft Azure Service Bus |
Microsoft Azure Cloud Service | Облачный сервис Microsoft Azure |
TCP Connection | TCP-соединение |
Arduino | Arduino |
Если посмотреть на эту схему, то становится очевидным, что компонент Microsoft Azure Service Bus является центральной частью проекта, обеспечивая аутентификацию, распространение сообщений и масштабируемость для поддержки множества устройств, которые будут отправлять данные или принимать удаленные команды. Service Bus доступна во всех информационных центрах Microsoft, которые предлагают сервисы Microsoft Azure, и поддерживается инфраструктурой хранилищ с высокой степенью избыточности. Кроме того, как и остальные компоненты Microsoft Azure, она предоставляет открытый и простой в понимании REST-интерфейс наряду с несколькими SDK (Microsoft .NET Framework, Java, PHP, Ruby и др.), опирающимися на этот интерфейс.
В предлагаемой нами архитектуре устройства «общаются» с .NET-приложением, выполняемым в Microsoft Azure Cloud Services, который действует как шлюз к Service Bus. Это упрощает процесс взаимодействия приложения с назначенной ему очередью. Такой подход обеспечивает полную поддержку любого из четырех шаблонов коммуникации IoT, описанных в нашей предыдущей статье: Telemetry, Inquiry, Command и Notification. Здесь мы реализуем сценарий, в котором мобильное устройство посылает команду другому устройству, чтобы выполнить некое действие; в данном случае включить или выключить LED-индикатор. Одно из преимуществ этого решения в том, что, если устройство временно отсоединено от сети, оно сможет получить команды, как только соединение с Интернетом будет возобновлено. Кроме того, в сообщении можно указать срок его действия, чтобы избежать выполнения задачи в неподходящий момент, или запланировать отправку сообщений в определенное время в будущем.
Для этого примера мы используем хорошо известное и подробно документированное устройство Arduino, о котором мы говорили в прошлой статье. Для подтверждения концепции в части мобильного клиента мы создадим приложение Windows Phone.
Вот наш простой сценарий.
- При запуске устройство Arduino посылает идентификационный сигнал шлюзовому приложению, выполняемому в Microsoft Azure Cloud Services. Шлюз создает очередь Service Bus для этого устройства, если таковой еще нет, и устанавливает TCP-соединение, готовое к отправке команд.
- Приложение Windows Phone посылает команду в очередь Microsoft Azure Service Bus, назначенную устройству.
- Сообщение остается в очереди, пока шлюзовое приложение не извлечет его и не отправит команду устройству Arduino по установленному TCP-соединению.
- Устройство Arduino включает или выключает LED-индикатор в зависимости от команды.
Рассмотрим этапы, которые делают возможными перечисленные выше операции.
Этап 1: создание пространства имен Microsoft Azure Service Bus Используя свои удостоверения Microsoft Azure (вы можете запросить пробную учетную запись по ссылке bit.ly/1atsgSa), войдите на веб-портал и выберите раздел SERVICE BUS (рис. 2). Выберите вариант CREATE и введите имя для вашего пространства имен. Затем щелкните CONNECTION INFORMATION и скопируйте текст в поле Connection String, которое потребуется вам позже.
Рис. 2. Создание пространства имен Microsoft Azure Service Bus
Этап 2: создание шлюзового приложения и его развертывание в Microsoft Azure Cloud Services Код для шлюзового приложения, которое извлекает сообщения из очереди Service Bus и ретранслирует команды устройству Arduino, включен в пакет исходного кода к этой статье (доступен по ссылке msdn.microsoft.com/magazine/msdnmag0314). Он основан на разработках Клеменса Вастерса (Clemens Vasters), который любезно поделился ими для этой статьи. Его исходный проект можно найти по ссылке bit.ly/L0uK0v.
Прежде чем углубиться в этот код, убедитесь, что у вас установлена Visual Studio 2013 наряду с версией 2.2 пакета Microsoft Azure SDK for .NET (bit.ly/JYXx5n). Решение включает три проекта:
- ArduinoListener — содержит основной код WorkerRole;
- ConsoleListener — консольная версия ArduinoListener для локального тестирования;
- MSDNArduinoListener — проект развертывания ArduinoListener в Microsoft Azure.
Если вы изучите файлы ServiceConfiguration.cscfg (для облачного и локального развертывания) в проекте MSDNArduinoListener, то заметите параметр, в котором хранится строка подключения для Service Bus. Замените его значение тем, что вы получили на этапе 1. Остальное уже сконфигурировано, чтобы решение могло работать, в том числе определен порт 10100 для приема соединений от устройств. Затем откройте файл WorkerRole.cs в проекте ArduinoListener, где находится основной код.
Проанализируйте четыре основных раздела.
Сначала создается TcpListener, и соединения от устройств начинают приниматься:
var deviceServer = new TcpListener(deviceEP);
deviceServer.Start(10);
try
{
do
{
TcpClient connection =
await deviceServer.AcceptTcpClientAsync();
if (connection != null)
{ ...
Как только соединение с устройством установлено, определяется NetworkStream и переводится в режим прослушивания. Переменная readBuffer будет содержать значение идентификатора, передаваемое каждым устройством Arduino:
NetworkStream deviceConnectionStream = connection.GetStream();
var readBuffer = new byte[64];
if (await deviceConnectionStream.ReadAsync(readBuffer, 0, 4) == 4)
{
int deviceId =
IPAddress.NetworkToHostOrder(BitConverter.ToInt32(readBuffer, 0));
...
Затем создается очередь на основе значения deviceId (в случае, если ее нет) и определяется объект — приемник сообщений (рис. 3). Далее приемник очереди устройства (device queue receiver) настраивается на асинхронный режим для извлечения сообщений (команд) из очереди. В этой очереди будут храниться команды, отправленные мобильными устройствами, такими как Windows Phone.
Рис. 3. Создание очереди
var namespaceManager =
NamespaceManager.CreateFromConnectionString(
RoleEnvironment.GetConfigurationSettingValue("serviceBusConnectionString"));
if (!namespaceManager.QueueExists(string.Format("dev{0:X8}", deviceId)))
{
namespaceManager.CreateQueue(string.Format("dev{0:X8}", deviceId));
}
var deviceQueueReceiver = messagingFactory.CreateMessageReceiver(
string.Format("dev{0:X8}", deviceId), ReceiveMode.PeekLock);
do
{
BrokeredMessage message = null;
message = await deviceQueueReceiver.ReceiveAsync();
...
Когда в очередь поступает сообщение, его содержимое анализируется и, если оно совпадает с командой «ON» или «OFF», в поток соединения, установленного с устройством, записывается соответствующая информация (рис. 4).
Рис. 4. Запись в поток соединения
if (message != null)
{
Stream stream = message.GetBody<Stream>();
StreamReader reader = new StreamReader(stream);
string command = reader.ReadToEnd();
if (command != null)
{
switch (command.ToUpperInvariant())
{
case "ON":
await deviceConnectionStream.WriteAsync(OnFrame, 0,
OnFrame.Length);
await message.CompleteAsync();
break;
case "OFF":
await deviceConnectionStream.WriteAsync(OffFrame, 0,
OffFrame.Length);
await message.CompleteAsync();
break;
}
}
}
Заметьте, что сообщение не удаляется из очереди (message.CompleteAsync), пока операция записи в поток соединения с устройством не будет успешной. Кроме того, чтобы поддерживать соединение в активном состоянии, ожидается, что устройство будет периодически посылать команду ping для проверки связи (ping heartbeat). В этом прототипе мы не ожидаем подтверждения от устройства при получении сообщения. Однако в производственной системе это было бы обязательным условием для соответствия шаблону Command.
Этап 3: развертывание проекта ArduinoListener в Microsoft Azure Cloud Services Развернуть ArduinoListener в Microsoft Azure крайне просто. В Visual Studio 2013 щелкните правой кнопкой мыши проект MSDNArduinoListener и выберите Publish. Конкретные инструкции по работе с Publish Microsoft Azure Application Wizard вы найдете по ссылке bit.ly/1iP9g2p. По окончании работы мастера вы получите облачный сервис, расположенный в xyz.cloudapp.net. Запишите это имя, так как оно понадобится, когда вы будете создавать клиент Arduino.
Этап 4: программирование устройства Arduino на взаимодействие со шлюзом (слушателем) Устройства Arduino предлагают богатый интерфейс для выполнения сетевых операций, используя простой объект веб-клиента. В нашем прототипе мы решили использовать модель Arduino Uno R3 (bit.ly/18ZlcM8) наряду с соответствующей ей защитой Ethernet (bit.ly/1do6eRD). Для установки, взаимодействия и программирования устройств Arduino в Windows следуйте руководству по ссылке bit.ly/1dNBi9R. В итоге вы получите простую в применении IDE (называемую приложением Arduino), где можно будет писать программы (называемые скетчами) на JavaScript, как показано на рис. 5.
Рис. 5. Приложение Arduino
На рис. 6 приведен скетч для взаимодействия с Arduino Listener, созданным на этапе 3 и теперь развернутым в Microsoft Azure.
Рис. 6. Код для устройства Arduino
#include <SPI.h>#include <Ethernet.h>#include <StopWatch.h>
// Введите MAC-адрес и IP-адрес своего контроллера ниже.
// IP-адрес будет зависеть от вашей локальной сети,
// и он не обязателен, если включен DHCP.
byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0xBC, 0xAE };
static const byte deviceId[] = { 0x00, 0x00, 0x00, 0x01 };
static const uint8_t ACK = 0x01;
static const int LED_PIN = 8;
int connected = 0;
EthernetClient client;
StopWatch stopWatch;
long pingInterval = 200000;
void setup() { Serial.begin(9600);
Serial.println("Initialized");
Ethernet.begin(mac);
pinMode(LED_PIN, OUTPUT);}
void turnLedOn(){ digitalWrite(LED_PIN, HIGH);}
void turnLedOff(){ digitalWrite(LED_PIN, LOW);}
void loop() {
if ( connected == 0) {
Serial.println("Trying to connect");
char* host = "xyz.cloudapp.net";
client.setTimeout(10000);
connected = client.connect(host, 10100);
if (connected) {
Serial.println(
"Connected to port, writing deviceId and waiting for commands...");
client.write(deviceId, sizeof(deviceId));
stopWatch.start();
}
else
{
Serial.println("Connection unsuccessful");
client.stop();
stopWatch.reset();
}
}
if (connected == 1)
{
if (stopWatch.elapsed() > pingInterval)
{
Serial.println("Pinging Server to keep connection alive...");
client.write(deviceId, sizeof(deviceId));
stopWatch.reset();
stopWatch.start();
}
byte buf[16];
int readResult = client.read(buf, 1);
if (readResult == 0)
{
Serial.println("Can't find listener, disconnecting...");
connected = 0;
stopWatch.reset();
}
else if (readResult == 1)
{
Serial.println("Data acquired, processing...");
switch ( buf[0] )
{
case 1:
Serial.println("Command to turn led on received...");
turnLedOn();
break;
case 2:
Serial.println("Command to turn led off received...");
turnLedOff();
break;
}
stopWatch.reset();
stopWatch.start();
}
}
}
Скетчи для Arduino имеют два основных раздела: setup и loop. Инструкции в разделе setup выполняются только раз; именно здесь происходит инициализация переменных и установление соединений. В нашем примере определяются Ethernet-клиент и связанные значения, устанавливается последовательное соединение (для целей отладки), а затем разъем (pin), к которому подключен LED-индикатор, инициализируется как порт вывода.
Код в разделе loop выполняется постоянно и включает два основных блока на основе состояния TCP-соединения между устройством Arduino и слушателем, работающим в Microsoft Azure Cloud Services: connected (подключено) или disconnected (отключено). Когда соединение устанавливается впервые, запускается объект stopwatch, который начинает отслеживать время этого соединения. Кроме того, слушателю посылается идентификатор устройства, который используется в качестве имени очереди, где будут храниться сообщения и команды.
Блок кода, обрабатывающий поведение Arduino после установления соединения, отслеживает время, прошедшее с момента создания соединения, и посылает слушателю команду ping каждые 200 000 мс, чтобы поддерживать соединение в активном состоянии в отсутствие принимаемых команд. Этот код также пытается считывать данные от слушателя, помещая их в массив buf, если таковые данные поступили. Если обнаружено значение 1, LED-индикатор включается, а если значение равно 2, LED-индикатор выключается. Объект stopWatch сбрасывается после каждой команды.
Как только скетч загружается в устройство, этот код выполняется контроллером Arduino в бесконечном цикле, пытаясь подключиться к облачному сервису. После подключения он пересылает идентификатор устройства, чтобы облачный сервис знал, с каким устройством он взаимодействует. Затем код начинает считывать входные данные от облачного сервиса, сообщающие устройству включить или выключить LED-индикатор (в данном случае он подключен к цифровому порту 8 на устройстве).
Этап 5: создание клиента Windows Phone для отправки сообщений в очередь устройства Взаимодействие с устройством сводится к простейшей отправке сообщений в очередь устройства. Как упоминалось в начале статьи, Microsoft Azure Service Bus предоставляет REST-интерфейс, который позволяет взаимодействовать с ним из нескольких языков программирования. Поскольку официального SDK для разработчиков под Windows Phone нет, мы воспользовались одним из примеров из сообщества Windows Phone, который показывает, как выполнять аутентификацию и взаимодействовать с Service Bus с помощью HTTP-запросов и объекта WebClient. Соответствующий код тоже включен в пакет исходного кода для этой статьи, а проект Visual Studio 2013 называется MSDNArduinoClient. Основной экран клиента, с которого вы посылаете команды на устройство Arduino, показан на рис. 7.
Рис. 7. Интерфейс клиента Windows Phone
Создание подобных клиентов для других мобильных устройств (в том числе под управлением iOS и Android) не составит особого труда, поскольку большинство из них предоставляет библиотеки для генерации REST-команд, используя клиенты HTTP-запросов. Более того, можно напрямую взаимодействовать с Microsoft Azure Service Bus, применяя традиционные языки вроде Java, PHP или Ruby, которые упрощают этот процесс. Эти SDK публикуются по лицензии открытого исходного кода, и вы найдете их по ссылке github.com/WindowsAzure.
Заключение
Создание архитектуры Интернета вещей с применением Microsoft Azure Service Bus для управления соединениями с устройствами и сервисами обеспечивает простой способ защиты, масштабирования и адресации к индивидуальным клиентам без издержек VPN-решений, а также позволяет эффективно обрабатывать ситуации, где постоянно связи нет. Очереди действуют как выделенные почтовые ящики, через которые устройства и сервисы обмениваются сообщениями, поддерживая различные сценарии и шаблоны использования, распространенные в полевых условиях. Microsoft Azure предоставляет надежную, географически распределенную и отказоустойчивую инфраструктуру для развертывания необходимых сервисов с большим количеством подключенных между собой датчиков и контрольно-измерительных приборов — этот тренд будет лишь расти в предстоящие годы.