В прошлой статье (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 предоставляет надежную, географически распределенную и отказоустойчивую инфраструктуру для развертывания необходимых сервисов с большим количеством подключенных между собой датчиков и контрольно-измерительных приборов — этот тренд будет лишь расти в предстоящие годы.