Содержание


Интернет вещей в вашем доме - подключите к дому свою машину

Используйте платформу Интернета вещей IBM Watson IoT Platform, миникомпьютер Raspberry Pi, Bluetooth-устройство и программируемый разветвитель электропитания (сетевой фильтр) для включения освещения и домашней техники, когда ваш автомобиль приближается к дому

Comments

«Подключенные» (connected) автомобили и «умные» (smart) дома – самые популярные сегодня темы в области Интернета вещей (IoT). Однако если присмотреться внимательнее, вы обнаружите, что подключенные к сети автомобили и умные дома, похоже, существуют как бы изолированно друг от друга. Да, жильцы умного дома могут включать освещение и бытовые электроприборы с помощью своего мобильного телефона. Но почему это не может делать автомобиль, когда подъезжает к умному дому?

Из этой статьи вы узнаете, как с помощью облачной PaaS-платформы IBM IBM Cloud и инструмента Node-RED связать автомобиль с умным домом и реализовать автоматическую активацию автомобилем некоторых бытовых домашних функций, таких, в частности, как включение освещения и регулировка кондиционера.

Мы будем использовать одноплатный миникомпьютер Raspberry Pi для приема и определения уровня (интенсивности) сигнала Bluetooth-устройства в умном доме и передачи этой информации на Watson IoT Platform. Затем, основываясь на уровне сигнала, мы будем включать электроприбор с помощью этого Bluetooth-устройства. Мы также будем использовать бортовую систему автоматического слежения GPS в автомобиле для создания геозоны (виртуальной области на географической карте) вокруг умного дома, чтобы управлять подключением автомобиля к дому.

Что вам понадобится для создания такого IoT-приложения

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

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

  • Linux-система с Bluetooth-устройством, такая, например, как миникомпьютер Raspberry Pi с USB–адаптером Bluetooth. На самом деле, хотя подойдет любое Linux-устройство с настроенным компилятором С, вряд ли кто-либо захочет использовать компьютер в режиме 24х7 в качестве шлюза устройства.
  • Точка подсоединения к сети электропитания (розетка питания), которая может включаться с компьютера. Я использовал Gembird Silver Shield (программируемую розетку-разветвитель электропитания, обычно именуемую «сетевой фильтр»), но существуют и другие бренды, которые вам подойдут.
  • Знание языка программирования C и навыки разработки на С в среде ОС Linux (такой, например, как Fedora – свободно распространяемая операционная система с открытым исходным кодом на основе Linux).
  • Учетная записьIBM Cloud®. (Здесь вы можете запросить бесплатную пробную версию.
  • Знание платформы IBM Cloud. Вы должны уметь просматривать каталог и создавать экземпляр сервиса IBM Cloud.
  • Потребуется знание сетевого протокола MQTT (Message Queue Telemetry Transport), особенно библиотеки Eclipse Mosquitto – брокера сообщений с открытым исходным кодом, который реализует протокол MQTT (можно найти во всех основных дистрибутивах Linux). Используйте инструмент управления пакетами вашей системы Linux для поиска Mosquitto и установки пакета на ваше устройство.
  • Знание инструмента Node-RED. Вы должны уметь использовать узлы в Node-RED и обрабатывать сообщение в Node-RED. Инструмент Node-RED – это внешний интерфейс к JavaScript, и поэтому вы также должны иметь опыт работы с JavaScript. Для получения необходимых знаний вы можете воспользоваться справочной документацией на веб-сайте Node-RED.

Получить кодЗапустить приложение

 

Архитектура нашего IoT-приложения

На Рисунок 1 изображена первая фаза архитектуры IoT-приложения, на которой осуществляется получение данных об уровне сигнала Bluetooth.

Рисунок 1. Архитектурная диаграмма примера IoT-приложения и устройств

На Рисунок 1 цифры в кружках соответствуют этапам (шагам) создания IoT-приложения:

  1. В IBM Cloud вы создаете приложение с использованием Internet of Things Platform Starter. Приложение будет считывать данные датчика, полученные от устройства, и передавать данные в устройство. Для защиты приложения от случайных устройств, передающих данные, каждое устройство должно аутентифицировать себя с помощью ключа (токена) API. Также приложение должно аутентифицировать себя с помощью ключа API, чтобы иметь возможность считывать и обрабатывать данные устройства, которые оно получает.
  2. В устройстве Raspberry Pi вы создаете клиент MQTT для считывания данных об уровне сигнала Bluetooth и непрерывной отправки соответствующих значений на IBM Watson IoT Platform.
  3. Вы создаете приложение Node-RED для обработки данных и генерирования команд для передачи на программируемый разветвитель электропитания (сетевой фильтр), чтобы включать освещение и кондиционер в доме.

После того как вы успешно реализуете эту первоначальную версию IoT-приложения, вы узнаете, как расширить это приложение для учета GPS-координат автомобиля. Кроме того, вы разработаете инструментальную панель для задания GPS-геозоны и настройки чувствительности Bluetooth-датчика приближения.

Итак, начнем!

1

Создаем IoT-приложение и подключаем миникомпьютер Raspberry Pi к Watson IoT Platform

Сначала давайте подключим наш Raspberry Pi к Watson IoT Platform.

1a

Создаем IoT-приложение в Bluemix

  1. Войдите в свою учетную запись IBM Cloud.
  2. Для создания приложения используйте шаблон Internet of Things Platform Starter. В этой статье в качестве имени приложения я использовал dw-example-iot. Вы можете выбрать для своего приложения другое имя. Приложение будет запущено; это может занять некоторое время.
  3. Щелкните по ссылке обзора в навигационной панели слева и дождитесь развертывания сервиса.
  4. Щелкните ADD A SERVICE OR API (ДОБАВИТЬ СЕРВИС ИЛИ API).
  5. Найдите сервис Internet of Things Platform и щелкните CREATE (СОЗДАТЬ).
1b

Зарегистрируйте ваше устройство Raspberry Pi с помощью сервиса Internet of Things Platform и задайте ключи аутентификации и авторизации

Чтобы отправлять данные на Watson IoT Platform, вам нужно зарегистрировать свои устройства в сервисе Internet of Things Platform. В методике, описываемой в этой статье, я использую Raspberry Pi в качестве шлюза. Ключ аутентификации, который генерируется при регистрации вашего устройства, позволяет устройству передавать данные на Watson IoT Platform.

Для однозначной идентификации устройства ему нужен идентификатор (ID) клиента. Идентификатор клиента создается в следующем заранее определенном формате, где typeid - строка, определяющая назначение устройства, а device-id - уникальный идентификатор вашего устройства:
d:<organization>:<typeid>:<device-id>

Выполните следующую команду на вашем устройстве Raspberry Pi для определения уникального MAC-адреса устройства, который можно использовать в качестве уникального идентификатора device-id:
ip add show|grep -A1 '^[0-9]:'

В выходных данных команды обратите внимание на интерфейсы под названием "eth0", "em1" или что-то вроде "enp1s0". Названия могут различаться в зависимости от дистрибутива Linux, но строка после интерфейсов отображает ваш MAC-адрес в виде "link/ether":

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
--
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 12597 qdisc pfifo_fast state UP….
link/ether b8:72:be:f2:7a:a8 brd ff:ff:ff:ff:ff:ff

Для регистрации устройства выполните следующие действия:

  1. В инструментальной панели вашего IBM Cloud-приложения выберите сервис Internet of Things Platform.
  2. В в нижней части левой колонки щелкните по ссылке Launch dashboard (Запустить инструментальную панель). В браузере откроется новое окно или вкладка. В верхней части инструментальной панели отображается идентификатор вашей организации.
  3. В разделе Devices (Устройства), ниже Organization ID, щелкните Add Device (Добавить устройство).
  4. Укажите proximity в качестве типа устройства (typeid)и введите MAC-адрес вашего Ethernet-устройства без двоеточий в качестве идентификатора устройства (device-id).
  5. Щелкните Continue (Продолжить).
  6. Внимательно просмотрите следующую страницу и запишите для себя параметры доступа устройства из раздела Credentials, в особенности Authentication Token (Ключ аутенфикации).
1c

Проверяем подключение

Теперь, когда ваше устройство зарегистрировано в сервисе Internet of Things Platform, вы можете использовать ключ аутентификации для отправки тестового сообщения из Raspberry Pi. Воспользуемся библиотекой Mosquitto MQTT. Например, войдите в свой Raspberry Pi и выполните следующую команду Mosquitto, подставляя собственные значения для идентификаторов (ID) организации и устройства:

mosquitto_pub -h e62whi.messaging.internetofthings.ibmcloud.com \
-i d:e62whi:proximity:b872bef27aa8 \
-u use-token-auth -P '<your device credentials here>' \
-t /iot/x -m '{"d":"hello iot"}'

В инструментальной панели для Watson IoT Platform вы увидите сообщение "hello iot" в информации датчика вашего устройства:

Пока мы находимся в инструментальной панели Watson IoT Platform, давайте позаботимся о том, чтобы наше приложение Node-RED было авторизовано использовать данные устройства, передаваемые в наше IoT-приложение. Вместо ключа аутентификации нам нужен ключ авторизации API. Для авторизации приложения, чтобы оно могло использовать данные из этого приложения, выполните следующие действия:

  1. В инструментальной панели Watson IoT Platform щелкните Access (Доступ) и, затем, API Keys (Ключи API).
  2. Щелкните Generate API Key (Генерировать ключ API).
  3. Внимательно просмотрите следующую страницу и запишите для себя параметры доступа API (API credentials), в особенности Authentication Token (Ключ аутенфикации).
2

Создаем на Raspberry Pi клиент MQTT для считывания данных об уровне сигнала Bluetooth

Клиент MQTT – это программа, написанная на C, которая считывает данные об уровне сигнала Bluetooth и посылает соответствующие значения в IoT-приложение на платформе IBM Cloud. Кроме того, клиент получает команды из IoT-приложения и вызывает скрипты для выполнения команд.

2a

Определяем MAC-адрес Bluetooth-устройства

Клиент устройства связывается с устройством Bluetooth, используя MAC-адрес устройства. Вы можете выяснить MAC-адрес Bluetooth-устройства, выполнив следующие команды на вашем устройстве (Raspberry Pi в моем случае), и получить на выходе что-то подобное:

# bluetoothctl
[NEW] Controller 00:15:83:07:CC:97 traumcloud.enet [default]

[Bluetooth]# power on
Changing power on succeeded
[CHG] Controller 00:15:83:07:CC:97 Powered: yes

[Bluetooth]# scan on
Discovery started
[CHG] Controller 00:15:83:07:CC:97 Discovering: yes
[NEW] Device 3C:77:E6:EF:57:F5 3C-77-E6-EF-57-F5
[NEW] Device 00:0A:3A:2B:C6:ED MSI FT200
[CHG] Device 00:0A:3A:2B:C6:ED RSSI: -79
[CHG] Device 00:0A:3A:2B:C6:ED RSSI: -88

[Bluetooth]# agent on
Agent registered

[Bluetooth]# pair 00:0A:3A:2B:C6:ED
Attempting to pair with 00:0A:3A:2B:C6:ED
Request PIN code
[agent] Enter PIN code: 1234
[DEL] Device 3C:77:E6:EF:57:F5 3C-77-E6-EF-57-F5
[CHG] Device 00:0A:3A:2B:C6:ED Connected: yes
[CHG] Device 00:0A:3A:2B:C6:ED UUIDs:
        00001108-0000-1000-8000-00805f9b34fb
        0000111e-0000-1000-8000-00805f9b34fb
[CHG] Device 00:0A:3A:2B:C6:ED Paired: yes
Pairing successful

[Bluetooth]#exit

В моем примере я знаю, что ищу беспроводную гарнитуру MSI FT200 headset, и вижу, что ее MAC-адрес 00:0A:3A:2B:C6:ED. Запишите для себя MAC-адрес вашего устройства.

2b

Пишем основной код MQTT

После того как библиотека Mosquitto инициализирована, программа устанавливает обратные вызовы для событий, в которых она заинтересована, и подключается к сервису Интернета вещей на IBM Cloud. Код инициализации в основном соответствует примеру кода с веб-сайта Mosquitto:

        mqtt = mosquitto_new(cfg.deviceid, 1, NULL); 
        if(!mqtt){
                tprintf("Cannot Initialize MQTT library\n");
                mosquitto_lib_cleanup();
                return 1;
        }
        mosquitto_connect_callback_set(mqtt, bt_connect_callback);
        mosquitto_disconnect_callback_set(mqtt, bt_disconnect_callback);

После этого добавьте обратные вызовы Mosquitto к событиям MQTT:

        mosquitto_log_callback_set(mqtt, bt_log_callback);
        mosquitto_message_callback_set(mqtt, bt_message_callback);
        mosquitto_subscribe_callback_set(mqtt, bt_subscribe_callback);
         dprintf("now setting username and password to %s/*************\n", cfg.username);
        sts=mosquitto_username_pw_set(mqtt, cfg.username, cfg.password);
        if(sts) {
                tprintf("Error setting username/password\n");
                return sts;
        }
        dprintf("now connection using host=%s port=%d\n", cfg.host, cfg.port);
        sts = mosquitto_connect_bind(mqtt, cfg.host, cfg.port, 60, NULL);
        if(sts) {
                tprintf("Error connecting to %s:%d as %s\n", HOST, PORT, IDFMT);
                return sts;

Затем программа устанавливает подписку, когда обратный вызов соединения указывает на корректное подключение:

void bt_connect_callback(struct mosquitto *mqtt, void *obj, int result) {
         char buf[1024];
        dprintf("conn cb %d '%s'\n", result, mosquitto_connack_string(result));
        is_connected=(result==0?CONN_OK:CONN_ERR);
        if (is_connected==CONN_OK) {
                mosquitto_subscribe(mqtt, NULL, cfg.subscription, 0);
        }
        sprintf(buf, PAYLOAD, -100, -100); // имитируйте «очень далекий» сигнал
        mosquitto_publish(mqtt, NULL, TOPIC, strlen(buf), buf, 0, false);
}

Основная программа выполняется в цикле, который периодически посылает данные об уровне сигнала Bluetooth.

while (is_connected != CONN_ERR) {
                if (is_connected == CONN_OK) publish_bt(mqtt);
                else dprintf("waiting for connection to come up\n", "");
                sleep(cfg.sleep);
        }
2c

Вызываем внешний скрипт

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

void bt_message_callback(struct mosquitto *mqtt, void *udata, const struct mosquitto_message *message) {
        dprintf("msg cb topic='%s' payload='%s'\n", message->topic, message->payload);
/*
 * A execute script btclient.d/command.sh
 */
         int pid,sts;
        pid = fork();
        switch (pid) {
        case 0:         // дочерний (порожденный), выполнить скрипт оболочки
                setenv("TOPIC", message->topic, 1);
                setenv("PAYLOAD", message->payload, 1);
                sts=execl("btclient.d/mqtt-command.sh", "mqtt-command", (const char*)NULL);
                tprintf("something went wrong with execl(), error='%s'\n", strerror(errno));
         break;
        case -1:        // ошибка
                tprintf("something went wrong with fork(), error='%s'\n", strerror(errno));
        break;
        default:        // родительский
                wait(&sts);
        break;
        }
}

Применяя скрипт, клиент MQTT можно с легкостью адаптировать к аппаратным средствам или «умным» устройствам, которые вы используете. Я использовал программируемую колодку розеток (разветвитель электропитания) Gembird Silver Shield, которая позволяет мне раздельно управлять переключением четырех силовых розеток по интерфейсу USB с помощью утилиты Linux под названием sispmctl (имеется в дистрибутиве Fedora Linux).

#!/bin/bash
if [ "$TOPIC" == "iot-2/cmd/switch/fmt/text" ];then
        [ "$PAYLOAD" == "on" ] && arg="-o4"
        [ "$PAYLOAD" == "off" ] && arg="-f4"
        [ "$arg" != "" ] && sispmctl $arg
fi

В качестве альтернативы я использовал приемопередатчик на 433 МГц для включения беспроводных розеток с помощью Open source-решения Pilight - инфраструктуры для домашней автоматизации на базе Raspberry Pi. Выясните, какой тип силовой розетки и какой программный интерфейс вам подходят.

2d

Настраиваем конфигурационный файл клиента MQTT

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

Конфигурационный файл задает следующие параметры:

  • btmac: используйте MAC-адрес Bluetooth-устройства, уровень сигнала которого вы измеряете (а не встроенного устройства вашей Linux-системы).
  • subscription: укажите топик, на который клиент должен подписаться. В моем примере это iot-2/cmd/switch/fmt/+.
  • host: имя хоста вашего экземпляра сервиса Internet of Things Platform. Укажите имя, которое вы применяли при отправке сообщения "hello iot".
  • password: пароль – это device authentication token (ключ аутентификации устройства). Используйте пароль, который вы ввели при регистрации устройства.
  • username: имя пользователя – это литеральная строка use-token-auth
  • typeid: это device type (тип устройства), который вы использовали при регистрации устройства. В моем примере я использовал proximity.
  • organization: укажите идентификатор (ID) вашей организации.
3

Создаем приложение Node-RED для распознавания уровня сигнала Bluetooth и включения электроприборов

Клиент MQTT через регулярные промежутки времени посылает данные об уровне Bluetooth-сигнала сервису Internet of Things Platform на Bluemix. Теперь вам нужно создать приложение на Bluemix для обработки этих значений. В качестве среды для создания этого приложения мы используем Node-RED.

3a

Создаем приложение Node-RED и подключаем устройство

Сначала мы подключим наше устройство и убедимся, что оно получает сообщения.

  1. В вашем IoT-приложении Bluemix щелкните по ссылке start coding (начать кодирование) слева в навигационной панели.
  2. В верхней части экрана найдите URL-адрес для вашего запущенного приложения. Щелкните по этому URL-адресу.
  3. Щелкните по большой красной кнопке, чтобы открыть редактор потока Node-RED.
  4. В редакторе потока отображается пример приложения. Выберите все элементы в потоке и удалите их.
  5. Перетащите входной узел ibmiot в рабочую область и настройте его с помощью двойного щелчка. Используйте API Authentication Token (Ключ аутентификации API), который вы создали ранее, чтобы получить доступ к данным устройства, как показано на рисунке ниже.
  6. Выберите All (Все) для device type (тип устройства), device ID (идентификатор устройства), event (событие) и format (формат).
  7. Для тестирования устройства запустите клиент устройства на вашей Linux-системе. Убедитесь в том, что сообщения отправляются на Watson IoT Platform.
    [root@traumcloud btclient]# ./dwclient
    [2015-08-18 17:16:39] trying to use ./dwclient.cfg for configuration
    [2015-08-18 17:16:39] got option 'btmac' with arg 00:0A:3A:2B:C6:ED
    [2015-08-18 17:16:39] got option 'subscription' with arg iot-2/cmd/switch/fmt/+
    [2015-08-18 17:16:39] got option 'host' with arg e62whi.messaging.internetofthings.ibmcloud.com
    [2015-08-18 17:16:39] got option 'password' with arg R3!1sFY(846HRCC0P6
    [2015-08-18 17:16:39] got option 'username' with arg use-token-auth
    [2015-08-18 17:16:39] got option 'typeid' with arg proximity
    [2015-08-18 17:16:39] got option 'organisation' with arg e62whi
    [2015-08-18 17:16:39] got MAC address of this machine as 'b827eb2fa78a'@  eth0
    [2015-08-18 17:16:39] now setting username and password to use-token-auth/*************
    [2015-08-18 17:16:39] now connection using host=e62whi.messaging.internetofthings.ibmcloud.com port=1883
    [2015-08-18 17:16:39] log cb: 'Client d:e62whi:proximity:b827eb2fa78a sending CONNECT'
    [2015-08-18 17:16:40] log cb: 'Client d:e62whi:proximity:b827eb2fa78a received CONNACK'
    [2015-08-18 17:16:40] conn cb 0 'Connection Accepted.'
    [2015-08-18 17:16:40] log cb: 'Client d:e62whi:proximity:b827eb2fa78a sending SUBSCRIBE (Mid: 1, Topic: iot-2/cmd/switch/fmt/+, QoS: 0)'
    [2015-08-18 17:16:40] log cb: 'Client d:e62whi:proximity:b827eb2fa78a sending PUBLISH (d0, q0, r0, m2, 'iot-2/evt/status/fmt/json', ... (31 bytes))'
    [2015-08-18 17:16:40] log cb: 'Client d:e62whi:proximity:b827eb2fa78a received SUBACK'
    [2015-08-18 17:16:40] sub cb 1 subscriptions
    [2015-08-18 17:16:41] BT MQTT msg '{ "d": {"lq":235,"rssi":-13}}'
    [2015-08-18 17:16:41] log cb: 'Client d:e62whi:proximity:b827eb2fa78a sending PUBLISH (d0, q0, r0, m3, 'iot-2/evt/status/fmt/json', ... (29 bytes))'
    [2015-08-18 17:16:46] BT MQTT msg '{ "d": {"lq":250,"rssi":-13}}'
    [2015-08-18 17:16:46] log cb: 'Client d:e62whi:proximity:b827eb2fa78a sending PUBLISH (d0, q0, r0, m4, 'iot-2/evt/status/fmt/json', ... (29 bytes))'
  8. Перетащите выходной узел debug в рабочую область и соедините его с узлом ibmiot.
  9. Щелкните Deploy (Развернуть).
  10. Щелкните по вкладке Debug (Отладка) справа, чтобы увидеть поступление отладочных сообщений.
  11. Перемещайте ваше устройство Bluetooth, чтобы увидеть изменения уровня принимаемого сигнала с минимального до максимального значения.
3b

Создаем приложение Node-RED для обработки данных Bluetooth-устройства

После того как вы проверили подключение вашего устройства, вы можете создать приложение Node-RED.

  1. Начнем с функции инициализации. Перетащите узел Inject, узел function и выходной узел debug в вашу рабочую область и соедините их в этой последовательности.
  2. Настройте узел Inject с пустым полем payload, отметьте опцию inject once at start и дайте узлу содержательное имя, например, AppSetup ).
  3. Настройте узел function с использованием следующего кода и дайте ему содержательное имя. Значение rssimin должно находиться в диапазоне между зафиксированными минимальным и максимальным значениями, которые вы отметили ранее для вашего Bluetooth-устройства.
    	// связанный bluetooth-сигнал
    context.global.bttime=0;
    context.global.rssimin=-32;
    context.global.rssi=0;
    context.global.inBeacon=false;
    	
    // связанный таймер
    context.global.lastCmd="";
    context.global.nextSwitch=0;
    context.global.delay=30*1000; // 30 секунд 
    
    	// будут использоваться позже с GPS
    	context.global.gpstime=0;
    	context.global.gps={lat: 0.0, lng:0.0};
    	context.global.inFence=true;
    	msg.payload="App initialized";
    	return msg;
  4. Настройте узел Debug для вывода msg.payload и дайте ему содержательное имя.
  5. Разверните узел. Вы должны увидеть сообщение "App initialized" («Приложение инициализировано») во вкладке Debug.
  6. Перетащите в рабочую область узел inject, входной узел ibmiot, два узла function, выходной узел ibmiot и узел debug.
  7. Настройте первый узел function с помощью следующего кода:
     	context.global.rssi=msg.payload.d.rssi
    	context.global.inBeacon=context.global.rssi>context.global.rssimin;
    	return msg;
  8. Настройте второй узел function с помощью следующего кода:
    	//
    // Команда посылается, когда истек период отсрочки
    // со времени последнего переключения (для защиты
    // электроприбора), и если состояние прибора
    // должно быть изменено,
    now = Date.now();
    dbg={ payload:""};
    var cmd=(context.global.inBeacon &&
     context.globalinFence)?"on":"off";
    if (now > context.global.nextSwitch && 
     context.global.lastCmd != cmd) {
     // мы должны переключить прямо сейчас
     context.global.nextSwitch+=now+context.global.delay;
     context.global.lastCmd=cmd;
     msg.payload=cmd;
     dbg.payload="switch appliance '"+cmd+"'";
    } else {
     msg=null
     dbg.payload="not switching appliance";
    }
    return [ msg, dbg ];
  9. Настройте узел ibmiot, как показано на рисунке ниже.
  10. И, наконец, соедините узлы между собой:
3c

Тестируем приложение Node-RED

  1. Разверните ваше приложение. Вы должны увидеть сообщение "App initialized" («Приложение инициализировано»).
  2. Для проверки потока щелкните узел timestamp для ввода сообщения, если ваше устройство еще не подключено. Если устройство подключено, вы должны увидеть периодические сообщения "not switching appliance" («нет переключения электроприбора»).
  3. 3. Медленно перемещайте Bluetooth-устройство то ближе, то дальше от вашей системы (миникомпьютера) Linux, и вы увидите, что отладочные сообщения пытаются включать и выключать электроприбор. В то же время, вы должны увидеть команды на включение или выключение, поступающие в клиентскую программу, которая запущена на вашем Raspberry Pi, и вызов скрипта, который сконфигурирован для включения вентилятора, освещения или кондиционера. Помните, что установлена 30-секундная задержка на каждое переключение прибора; для тестирования вы можете уменьшить значение параметра context.global.delay.
4

Добавляем поток в приложение Node-RED для приема и обработки данных GPS

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

Хотя современные GPS-устройства используют для подключения и взаимодействия различные коммуникационные протоколы, я предполагаю, что GPS посылает HTTP-запросы с парами координат географической широты и долготы в качестве полезной нагрузки сообщения.

4a

Создайте конечную точку HTTP для GPS-устройства

В редакторе потоков Node-RED перетащите в вашу рабочую область узел HTTP-inputДважды щелкните по нему и настройте его, указав следующие значения параметров:

  • Method:POST
  • URL:/gps
  • Name:GPS Input [POST] /gps

Этот узел HTTP принимает запросы POST по URL-адресу http://dwexample-iot.mybluemix.net/gps. Данные POST доступны в качестве объекта сообщения в потоке Node-RED. Значения координат долготы и широты, получаемые от GPS-устройства, дополнительно обрабатываются с учетом заданной геозоны.

Управление электроприборами с помощью геолокации не привязано к одному конкретному местоположению. Как правило, управление осуществляется при входе в заданную геозону или при выходе из нее. Зона в нашем приложении определена в виде массива пар координат (широты и долготы) в формате JSON – например, так:

[
    {"lat":43.7074340290373,"lng":7.28219747543335},
	…
	…	
    {"lat":43.705196002070636,"lng":7.284321784973145},
    {"lat":43.706995349373884,"lng":7.284793853759766}
]
4b

Получение параметров геозоны из базы данных Cloudant

Массив пар координат широты и долготы хранится в базе данных Cloudant, которая является частью пакета Internet of Things Platform Starter, использованного нами для создания нашего приложения. Если служба базы данных Cloudant отсутствует в вашем приложении, перейдите в каталог Bluemix, найдите сервис Cloudant NoSQL DB и создайте эту службу.

Получив сообщение с координатами GPS, наше приложение должно извлечь параметры зоны из базы данных и затем определить, находятся ли полученные координаты внутри или вне зоны. Узел базы данных переопределяет элемент полезной нагрузки сообщения, поэтому нам нужно временно сохранить тело запроса HTTP. В качестве временного хранилища для запроса http мы используем глобальные атрибуты Node-RED.

В редакторе потока Node-RED добавьте узел function, а также включите в узел следующий код JavaScript:

context.global.gps=msg.payload;
context.global.gpstime=Date.now();
msg.save=msg.payload;
msg.payload="fence";
return msg;

Фактический запрос к базе данных осуществляется с помощью узла Cloudant из раздела хранения в панели. Перетащите узел с маленьким изображением облака слева (см. ниже), который является узлом query в Cloudant.

Настройте узел, указав следующие значения:

  • Service (сервис): <your-app-name>
  • Database (база данных): tsldemo
  • Search by (поиск по): _id
  • Name (имя): Retrieve Fence
4c

Определяем, попадают ли GPS-координаты в геозону

Выход узла Cloudant передается в другой узел function, где фрагмент кода JavaScript определяет, находятся ли GPS-координаты внутри или вне зоны. Алгоритм взят из репозитария кода point-in-polygon из подстека на GitHub. Узел function записывает состояние интерфейса в элемент глобальной структуры.

Выход узла function передается в следующие узлы:

  • Узел HTTP response, который возвращает надлежащий статус для GPS-устройства
  • Узел, который объединяет измерение уровня сигнала Bluetooth с отслеживанием GPS

Этот узел реализует функцию включения кондиционера, если либо координаты GPS находятся внутри зоны, либо автомобиль находится достаточно близко к датчику Bluetooth, но не в случае, если последнее переключение имело место меньше, чем <n> минут назад. Последнее условие позволяет избежать включения или выключения электропитания через короткие промежутки времени, когда автомобиль находится на краю зоны. Код, который реализует функцию, приблизительно аналогичен следующему коду:

//
// определить, когда посылать команду на переключение
// Команда посылается, когда истек период отсрочки
// со времени последнего переключения (для защиты
// электроприбора), и если состояние прибора
// должно быть изменено
now = Date.now();
var cmd=(context.global.inBeacon ||
 context.global.inFence)?"on":"off";
if (now > context.global.nextSwitch && 
 context.global.lastCmd != cmd) {
 // мы должны переключить прямо сейчас
 context.global.nextSwitch=now+context.global.delay;
 context.global.lastCmd=cmd;
 msg.payload=cmd;
 msg.format="text";
} else {
 msg=null
}
return msg;

Итоговая версия приложения Node-RED выглядит как поток на рисунке 2. Светло-серые и темно-серые узлы – это узлы, созданные на этапах (шагах) 1—4.

Рисунок 2. Итоговая версия приложения Node-RED
4d

Проверяем функционирование геозоны

Выполним простое тестирование новых функций геозоны с условными GPS-координатами, которые мы посылаем с помощью команды curl.

  1. В инструментальной панели Bluemix дважды щелкните по службе Cloudant DB.
  2. Щелкните Launch (Запуск), чтобы открыть инструментальную панель для вашей базы данных Cloudant:
  3. В инструментальной панели вашей базы данных добавьте новый документ:
    	"_id": "fence",
      "fence": "[{\"lat\":50.945,\"lng\":6.955},{\"lat\":50.946,\"lng\":6.957},{\"lat\":50.943,\"lng\":6.962},{\"lat\":50.940,\"lng\":6.962}]"
  4. Щелкните Save (Сохранить) для сохранения документа.
  5. Протестируйте поток Node-RED с помощью двух условных GPS-координат (пара координат широта/долгота внутри зоны и пара координат широта/долгота заведомо за пределами зоны), выполнив следующую curl-команду (и указав имя приложения в вашем примере):
    	curl -XPOST -d 'lat=1&lng=26' dwexample-iot.mybluemix.net/gps
    	{
    	  "bttime": 0,
    	  "rssimin": -32,
    	  "rssi": 0,
    	  "inBeacon": false,
    	  "lastCmd": "on",
    	  "nextSwitch": 1447771815039,
    	  "delay": 5000,
    	  "gpstime": 1447771815841,
    	  "gps": {
    	    "lat": "1",
    	    "lng": "2"
    	  },
    	  "inFence": false,
    	}

    Просмотрите сообщения во вкладке Debug (Отладка) в редакторе потока Node-RED.

    [root@traumcloud btclient]# ./dwclient
    trying to use ./dwclient.cfg for configuration
    got option 'btmac' with arg 00:0A:3A:2B:C6:ED
    got option 'subscription' with arg iot-2/cmd/switch/fmt/+
    [2015-11-17 18:42:25] got option 'host' with arg e62whi.messaging.internetofthings.ibmcloud.com
    got option 'password' with arg R3!1sFY(846HRCC0P6
    got option 'username' with arg use-token-auth
    got option 'typeid' with arg proximity
    got option 'organisation' with arg e62whi
    got MAC address of this machine as 'b827eb2fa78a'@  eth0
    now setting username and password
    log cb:received PUBLISH (d0, q0, r0, m0, '...'
    msg cb topic='iot-2/cmd/switch/fmt/text' payload='on'
    set state of switch2 to on
    log cb: 'Client received PUBLISH (d0, q0, r0, m0, '...'))'
    msg cb topic='iot-2/cmd/switch/fmt/text' payload='off'
    set state of switch2 to off
5

Добавляем инструментальную панель для редактирования зоны GPS и установления пороговых значений для уровня сигнала Bluetooth

До сих пор мы управляли кондиционером, используя один из двух способов получения данных о местонахождении автомобиля:

  • либо отслеживая сигнал Bluetooth на стоянке или в гараже,
  • либо отслеживая автомобиль по координатам GPS.

Теперь для нашего примера приложения нам нужно управлять этими двумя механизмами контроля приближения одновременно. Нам нужно установить пороговые значения для интенсивности Bluetooth-сигнала, а также более реалистично определить геозону. Для управления этими параметрами мы создадим веб-страницу, которая будет служить интерфейсом администрирования. Кроме того, мы добавим два новых потока для нашего backend-приложения, которые возвращают текущие рабочие значения параметров или устанавливают новые рабочие значения. См. Рисунок 3.

Рисунок 3. Инструментальная панель для отображения уровня сигнала Bluetooth и визуализации данных GPS

На диаграмме последовательности действий, представленной на рисунке 4, изображены компоненты и их взаимодействия, которые необходимы для создания инструментальной панели. Инструментальная панель поддерживает следующие действия:

  • Непрерывный мониторинг GPS-координат и интенсивности сигнала Bluetooth
  • Редактирование и изменение интенсивности сигнала Bluetooth
  • Редактирование и изменение геозоны

Инструментальная панель реализована в виде HTML-страницы и использует Ajax-запрос для получения данных от backend-приложения. Ключевые линии слева на рисунке 4 реализованы в виде потоков в Node-RED.

Рисунок 4. Инструментальная панель для отображения уровня сигнала Bluetooth и визуализации данных GPS
5a

Создаем веб-страницу инструментальной панели

Вы можете скачать файлы, необходимые для создания инструментальной панели, из моего репозитария кода car-meets-home в GitHub и импортировать их в приложение Bluemix IoT.

  1. В вашей инструментальной панели IBM Cloud выберите ваше IoT-приложение и щелкните Edit Code (Редактировать код).

    Откроются сервисы Bluemix DevOps. В первый раз это может занять некоторое время, поскольку для вас создается новый проект.

  2. В навигационной панели слева найдите общую (public) папку. Щелкните по общей папке правой кнопкой мыши и в появившемся контекстном меню выберите пункт Import (Импорт).
  3. В открывшемся диалоговом окне перейдите к файлу dash.html и щелкните Open (Открыть). Файл dash.html появляется в общей папке.
  4. Щелкните правой кнопкой мыши по общей папке и выберите New > Folder. Назовите новую папку js.
  5. Щелкните правой кнопкой мыши по папке js и импортируйте файл dash.js.
  6. Щелкните правой кнопкой мыши по папке css и импортируйте файл dash.css.
  7. Щелкните правой кнопкой мыши по папке images и импортируйте файлы .png.

    Эти файлы составляют приложение инструментальной панели. Вы можете открыть вашу инструментальную панель, добавив /dash.html в путь доступа к вашему приложению (например, <ваше_имя>.mybluemix.net/dash.html).

5b

Добавляем API-интерфейсы в поток для обслуживания Ajax-запросов

Для начала работы с инструментальной панелью нам нужно добавить ряд API-интерфейсов в наш поток Node-RED. Инструментальная панель получает данные о состоянии backend-приложения с помощью запроса "HTTP-GET"–запроса /status. Аналогичным образом, новое значение rssimin (интенсивность) передается по запросу "HTTP-POST"–запросу /status. Возврат параметров геозоны осуществляется с помощью GET to /fence, а сохраняются параметры зоны с помощью запроса POST в /fence.

Создайте потоки и добавьте код в функции:

  1. Добавьте потоки для возврата данных о состоянии и обновите, как показано на рисунке ниже:
  2. Добавьте следующий код в узел Add global as payload:
    msg.payload=context.global;
    msg.payload.jt=Date.now();
    return msg;
  3. 3. Следующий код JavaScript в веб-странице отображает значения интенсивности сигнала Bluetooth в текстовом виде и графически в виде показаний измерительного прибора уровня сигнала Bluetooth, а также визуализирует координаты GPS в виде позиций на карте. Ниже приводятся основные строки кода для отображения данных:
    	    //
    	    // методы получения данных о состоянии и отображения
    	    //
    	    this.getData=function () {
    		 	$.ajax({url: "/status",
    		 	 	success: thiz.getDataOK,
    		 	 	error: thiz.getDataErr,
    		 	 	dataType: "json"
    		 	});
    		 	window.setTimeout(thiz.getData, 5000);
    	    };
    	    this.getDataOK=function (data) {
    		 	//console.dir(data);
    		 	if (olddata == null) olddata=data;
    	
    		 	car.setLatLng(L.latLng(data.gps.lat, data.gps.lng));
    		 	map.panTo(L.latLng(data.gps.lat, data.gps.lng));
    	
    		 	$('#bt-rssi').text(data.rssi+"/"+data.lq);
    		 	$('#bt-rssimin').text(data.rssimin);
    		 
    		 	$('#time').text(new Date().toLocaleString());
    		 	thiz.drawRssiMeter(data);
    		 	olddata=data;
    	
    	    };
    	    this.drawRssiMeter=function (data) {
    		 	var rssi=Math.floor(data.rssi||-50);
    		 	var rssimin=Math.floor(data.rssimin||-50);
    		 	var min = Math.min(rssimin, rssi);
    		 	var max = Math.max(rssimin, rssi);
    	
    		 	var rssiMeterData = new google.visualization.DataTable();
    	
    		 	rssiMeterData.addColumn('number', 'Rssi');
    		 	rssiMeterData.addColumn('number', 'lq');
    		 	rssiMeterData.addRows(2);
    		 	rssiMeterData.setCell(0, 0, rssi);
    	
    		 	rssiMeterOptions.min=min-15;
    	 	 	rssiMeterOptions.max=max+5;
    		 	 	 	 
    		 	rssiMeterOptions.greenFrom=rssimin;
    		 	rssiMeterOptions.yellowTo=rssimin;
    	  	 	// создать области от минимума до желтого, наполовину желтого, наполовину красного
    		 	var step=Math.floor(Math.abs(rssimin-rssiMeterOptions.min)/2);
    		 	rssiMeterOptions.yellowFrom=rssimin-step;
    		 	rssiMeterOptions.redTo=rssimin-step;
    	
    		 	rssiMeterOptions.redFrom=Math.min(min,rssimin)-2*step;
    		 	 
    		 	rssiMeter.draw(rssiMeterData, rssiMeterOptions);
    	    };
  4. Код инструментальной панели включает несколько строк кода, которые отображают красным цветом значения, изменившиеся между опросами данных.

    При нажатии кнопки "Enter new value for RSSI Min" (Введите новые значения для RSSI Min) введенные значения уровня принимаемого сигнала (RSSI) передаются в Node-RED API.

        this.getDataErr=function (xhr, sts, txt) {
    	    thiz.error("Ajax Error: sts='"+sts+"', txt='"+txt+"'");
        };
        //
        // обновить уровень сигнала Bluetooth
        //
        this.setRssiVal=function() {
    	 	var url="/status";
    	 	$.ajax({url: url,
    	 	 	success: thiz.getDataOK,
    	 	 	error: thiz.getDataErr,
    	 	 	method: "POST",
    	 	 	data: {rssimin: $('#rssid-val-text').val().trim()},
    	 	 	dataType: "json"
    	 	 	});
        };
  5. Node-RED API устанавливает значение RSSI (уровень сигнала Bluetooth) в структуре глобальной переменной.
    if (msg.payload.rssimin) context.global.rssimin=msg.payload.rssimin;
    msg.payload=context.global;
    return msg;
  6. Аналогичным образом вы можете добавить элементы пользовательского интерфейса для временной задержки между операциями переключения электроприбора и установить их в потоках, подобных rssimin.

    Добавьте потоки, которые возвращают данные геозоны, как показано на рисунке ниже:

        var storeFence=function() {
        	var ll=fence.getLatLngs();
    	 	var data={ fence: JSON.stringify(ll) };
    	 	if (fencers._id !== undefined) data._id=fencers._id;
     	 	if (fencers._rev !== undefined) data._rev=fencers._rev;
    	 	// выгрузить в Bluemix
    	 	$.ajax({
    	 	 	url: "/fence2",
    	 	 	data: data,
      	 	 	contenttype: "application/json",
    	 	 	method: "POST",
    	 	 	/**
    	 	 	 * @callback
    	 	 	 */
    	 	 	success: function( data ) {
    	 	 	 	$('#upload').show();
    	 	 	 	window.setTimeout(function() {
    	 	 	 	 	$('#upload').fadeOut(2000);
    	 	 	 	}, 2000);
    	 	 	 	loadFence(); // to update _rev
    	 	 	}
    	 	});
    	};
  7. Загрузка данных геозоны реализована двумя методами: первый метод вызывает Node-RED API в асинхронном режиме, а второй приводит в действие функцию обратного вызова. Геозона загружается при запуске приложения или когда пользователь нажимает кнопку Load (Загрузить).
    var loadFence=function() {
    	// загрузить определение зоны из Bluemix
    	//
    	$('#download').show();
    	$.ajax({
    	 	url: "/fence",
    	 	contenttype: "application/json",
    	 	method: "GET",
    	 	success: function( data ) {
    	 	 	$('#download').fadeOut(1000);
    	 	 	$('#received').fadeIn(1000);
    	 	 	window.setTimeout(function() {
    	 	 	 	$('#received').fadeOut(1000);
    	 	 	}, 2000);
    	 	 	loadFenceOK(data);
    	 	},
    	 	error: function(a, b) {
    	 	 	thiz.error("Cannot load fence data, reason given: '"+a+"':'"+b+"'");
    	 	}
    	});
    };
    var loadFenceOK=function(data) {
        var ll=[];
        var i;
        fencers=data;
        if (data.fence) {
            ll=JSON.parse(data.fence);
    	    for (i=0; i<ll.length; i++) {
    	 	L.marker([ll[i].lat, ll[i].lng], {icon: reddot}).addTo(map).on('click', removeDot);
    	    }
    	}
    	fence=L.polygon(ll, {color: 'red'}).addTo(map);
    	//
    	// разместить карту таким образом, чтобы автомобиль и зона отображались в окне браузера
    	//
    	ll=fence.getLatLngs();
    	var b=L.latLngBounds(ll);
    	b.extend(car.getLatLng());
    	map.fitBounds(b, {animate: true, duration: 2.0});
    	$('#save').prop( "disabled", ll.length <= 2 );
    	$('#load').prop( "disabled", false);
    };
  8. 8. Кроме того, инструментальная панель содержит простой редактор геозоны. Когда вы щелкаете по точке на карте, точка добавляется в многоугольник, определяющий область геозоны. Если вы щелкаете по одной из красных точек, точка удаляется из многоугольника геозоны.
    1. Для удаления точек из многоугольника цикл сравнивает координаты точек многоугольника с координатами пиктограммы, созданной с помощью JavaScript-библиотеки Leaflet. Если они совпадают, то точка удаляется из многоугольника.
          //
            // простой редактор зоны
          //
          var removeDot=function(me) {
      	 	var ll=fence.getLatLngs();
      	 	var i, l=ll.length;
      	 	for (i=0; i<l; i++) {
      	 	 	var p=ll[i];
      	 	 	var dx=p.lat-me.latlng.lat; // расстояние от точки зоны до места щелчка на карте
      	 	 	var dy=p.lng-me.latlng.lng;
      	 	 	if (dy === 0 || dx === 0) {
      	    		fence.spliceLatLngs(i, 1);
      	    		l--;
      	    		break;
      	 	 	}
      	 	}
      	 	 
      	 	map.removeLayer(me.target);	// удалить выбранную точку;
      	 	$('#save').prop( "disabled", l <= 2 );
      
          };
    2. Добавление точки в многоугольник требует несколько больших усилий, поскольку программа должна найти две точки многоугольника, между которыми лучше всего вставить новую точку. Приложение инструментальной панели выполняет следующие действия:
      • Находит точку, ближайшую к координатам места щелчка на карте.
      • Выбирает из двух соседних с ней точек ближайшую к точке на карте.
      • Добавляет новую точку между двумя найденными точками.

      Код JavaScript, который следит за (почти) пустыми многоугольниками и положением кнопки сохранения, выглядит примерно так:

      var addDot=function(me) {
          	// посмотреть, какая существующая точка находится близко к событию мыши (me) в качестве p1
          	// найти смежные узлы p1, ближайшие к me
          	// вставить me между ними
          	var ll=fence.getLatLngs();
          	var i, l=ll.length;
          	var p0=me.latlng, p1=null, p2=null;
          	if (l < 2) {
          	 	// нет точек в зоне или есть только одна точка в зоне - просто добавить одну новую
          	 	fence.addLatLng([ok, good, me.latlng.lng]);
          	} else {
          	 	var dist=0x07ffffff;	// диапазон макс. 32 бита со знаком
      	    	var p, dlat, dlng;
      	    	var d=Math.sqrt(dlat*dlat+dlng*dlng);
      	    	for (i=0; i<l; i++) {
      	    		p=ll[i];
      	    		dlat=p0.lat-p.lat;
      	    		dlng=p0.lng-p.lng;
      	    		d=Math.sqrt(dlat*dlat+dlng*dlng);
      	    		if (d < dist) {
      	    			p1=i;
      	    			dist=d;
      	    		}
      	    	}
      	    	// найти ближайшую смежную точку
          	 	p2=(p1+l-1)%l;
          	 	p=ll[p2];
          	 	dlat=p0.lat-p.lat;
          	 	dlng=p0.lng-p.lng;
      	    	dist=Math.sqrt(dlat*dlat+dlng*dlng);
      	    	
          	 	p=ll[(p1+l+1)%l];
          	 	dlat=p0.lat-p.lat;
           	 	dlng=p0.lng-p.lng;
      	    	d=Math.sqrt(dlat*dlat+dlng*dlng);
      	    	if (d < dist) p2=(p1+l+1)%l;
      	    	console.log("add dot @"+p1+"/"+p2);
      	    	if (p1 > p2) { var t=p1; p1=p2; p2=t;}
      	    	// особый случай: p1 и p2 – начало и конец массива
        	    	if (p1 === 0 && p2 === l-1) {
      	    		fence.addLatLng(p0);
      	    	} else {
      	    		fence.spliceLatLngs(p2, 0, p0);
      	    	}
          	}
      	L.marker(me.latlng, {icon: reddot}).addTo(map).on('click', removeDot);
      	$('#save').prop( "disabled", l+1 <= 2 );
          };

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

Посмотреть демо-ролик инструментальной панели, работающей на платформе Bluemix, можно по ссылке
http://car-meets-home.mybluemix.net/.

Заключение

Из этой статьи вы узнали, как создать приложение Bluemix, зарегистрировать Raspberry Pi в Watson IoT Platform, получить доступ к данным устройства и создать приложение Node-RED для обработки данных, передаваемых с использованием протокола MQTT.

В Raspberry Pi вы создали клиент MQTT, который может сообщать об уровне сигнала Bluetooth и получать команды от вашего приложения Bluemix.

Мы быстро и легко расширили базовое приложение, добавив новые функции, такие как получение GPS-координат от внешнего устройства и определение момента включения электроприборов на основании интенсивности сигнала Bluetooth и геолокации (местонахождения внутри или за пределами геозоны). И, наконец, мы создали инструментальную панель, позволяющую определять геозону для GPS-контроля и настраивать чувствительность датчика Bluetooth. Эти возможности добавлены в качестве API-интерфейсов с помощью всего лишь нескольких дополнительных узлов в приложении Node-RED.

Теперь вы можете похвастаться своими умными устройствами и подключить свою машину к своему умному дому.


Ресурсы для скачивания


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Облачные вычисления, Internet of Things
ArticleID=1038403
ArticleTitle=Интернет вещей в вашем доме - подключите к дому свою машину
publish-date=10122016