Делаем управление «Умным домом» через интернет за пару минут

в 2:04, , рубрики: Без рубрики

Доброго дня. У многих из нас, в том числе и у меня, давно возникла идея создания своего «Умного дома». Но она откладывалась в виду большой сложности реализации как с аппаратной стороны так и со стороны программного обеспечения, что требовало от её создателя быть «гуру» в многих областях.
image
В этой статье я расскажу вам об очень простом и вполне функциональном способе управления системой умного дома через интернет. На её создание при наличии необходимых компонентов уйдет всего несколько минут.

Заинтригованы? Прошу под кат =)

Однажды рыская по интернету в поисках информации про «Умные дома» наткнулся на интересное видео, где мужчина в белом халате показывал как управлять ARDUINO через интренет. Позже оказалось, что этот мужчина один из разработчиков проекта на kickstarter.com под названием Ninja Blocks.

Вот, то самое видео.

Ninja Blocks — очень интересный и довольно успешный проект, который предлагает управление и контроль большой периферией домашних устройств с помощью своего модуля ( который они предлагают купить за 199 «вражеских» единиц). Модуль взаимодействует с облаком, через которое и происходит управление устройствами. Так же существуют приложения для iOS и Android с помощью которых также возможно управление.
Разработчики Ninja Blocks не поленились и написали свою библиотеку для ARDUINO за что им большое СПАСИБО!

Облако не совсем простое, оно не только посылает и принимает данные, то также поддается программированию со стороны пользователя, который создает так называемые ПРАВИЛА. Таким образом облако становиться онлайн «мозгом» «Умного дома».
НАПРИМЕР: при нажатии на кнопку, облако ждет 30 секунд, а потом влючает какое-либо реле, ждет еще 60 секунд и выключает его.
Правила легко создаются из панели управления.

Для повторения его примера нужен был Ethetnet Shield. На тот момент в моем распоряжении быт модуль на базе enc28j60. Но как я ни пытался повторить пример с использованием разных библиотек, так ничего и не получалось. Необходим был модуль совместимый с родной Ethernet библиотекой для ARDUINO т.е. на базе чипа w5100. Шилд был заказан из поднебесной и всё было отложено на долгий месяц.

Если вы уже посмотрели видео выше, то поняли что нам потребуется:

— совместимая плата ARDUINO
— thernet shield на базе w5100
— аккаунт на сайте a.ninja.is
— разная мелочевка в виде резисторов, кнопок и светодиодов

И так. Плата пришла. Все собрал, подключил, загрузил.
Это было удивительно, но все заработало )) Но всегда бывает «НО». Буквально через пару минут заметил, что светодиод перестал реагировать на управление через сайт. Перезагрузка контроллера помогла, но только на те же пару минут. При этом кнопка всегда работала испаравно. Да это была серьезная проблема с которой «Умный дом» не построить.

После изучения данного примера возникло несколько вопросов:
1. Как управлять несколькими устройствами принимающих данные (светодиодами)?
2. Как добавить несколько устройств, отсылающих данные( кнопка, датчик температуры)?
3. Как починить управление для светодиодов через панель, которое «отваливается» через пару минут?

Поиск начался с не очень популярного раздела на форуме с названием ARDUINO. Тут было найдено решение для исправление проблемы с управлением. Одни из участников форума доработал библиотеку проекта. Самое удивительное, так это то что за пол года разработчики так и не обновили библиотеку на github. Видно опасаются конкуренции для себя со стороны проектов на базе Arduino )).

Ниже вы сможете скачать уже исправленную библиотеку.

Несколько часов опытов и изучения смазанных объяснений разработчиков на форуме и библиотеки дали свои плоды. Все вопросы были успешно решены… =)

И так теперь по делу.
Библиотека для Arduino занимается, только приемом и отправкой данных.
Основным параметром в данных является ID устройства к которому обращается сервер или от которого принимает информацию. Посмотреть полный список можно здесь. ninjablocks.com/pages/device-ids Каждому ID соответствует свой виджет в панели управления.

В Serial Monitor передача или прием одной команды или данных выглядит таким образом:

  {"GUID": "ETHERSHIELDBLOCK_0_0_1","G": "0","V": 0,"D": 1,"DA": 27}

Теперь рассмотрим что там внутри.
«G»: «0» — параметр GUID указывает порядковый номер в рамках группы устройств с одинаковым ID. Например установлено 3 датчика температуры с одинfковым ID = 1. Тогда для первого датчика GUID будет равен 0, для второго — 1 и 2 для третьего.
«V»: 0 — параметр VID является идентификатором для устройств как и ID.
«D»: 1 — параметр DID ( Device ID) указывает тип устройтсва. Напримет датчик температуры имеет ID=1 или 31, кнопка — 5.
«GUID»: «ETHERSHIELDBLOCK_0_0_1» — трудно сказать для чего это надо, но в общем он содержит в себе название блока ARDUINO (котрое можно изменить в начале примера из библиотеки) и всех выше пересичленные параметры.
«DA»: 27 — ну и сами данные, которые мы передаем. Для кнопки или реле это 0 или 1, для RGB светодиода это код цвета например FFFFFF.

Условно все датчики и устройства можно разделить на два типа. Первый тип только передают информацию ( кнопка, датчик температуры и влажности). И второй тип только принимают её. Тут кроется один нюанс. Панель управления изначально не знает что у нас подключено к контроллеру. И если первый тип сами сообщат о себе, когда будут передавать данные и панель сразу создаст для них виждет согласно их ID. То для второго типа необходимо произвести создание виджетов в панели управления путем отправки данных.

Как отправлять данные с нескольких устройств с одинаковым ID.
Все очень просто перед каждой отправкой данных мы должны указать порядковый номер устройства (GUID)

    NinjaBlock.guid = "1";            // указываем порядковый номер датчика  0 для 1-го, 1 для 2-го и т.д.
    NinjaBlock.deviceID=(1);      // указывает ID  в данном случае термометр
    NinjaBlock.send("24");            // отправляем данные

При приеме данных для нескольких устройств c одинаковым ID нам необходимо проверять чему равен GUID

    if (strcmp(NinjaBlock.strGUID,"1") == 0)  // проверяем равен ли GUID  1 для второго устройства

Вернемся к устройствам второго типа. Для создания виджета в панели управления достаточно всего лишь раз отправить данные от его имени. Создадим виджет с переключателем ON/OFF для каждого из двух светодиодов написав в цикле void setup следующее:

    NinjaBlock.guid = "0";
    NinjaBlock.deviceID=(1002);
    NinjaBlock.send("0");
    
    NinjaBlock.guid = "1";
    NinjaBlock.deviceID=(1002);
    NinjaBlock.send("0");

Получив эти данные панель создаст для них виджет.

Теперь предлагаю вашему вниманию простую программу для работы с Ninja Blocks.
— Управляет 4-я светодиодами
— Отсылает показания 2-х датчиков температуры ds18b20 c интервалом в 60 секунд
— Каждые 20 секунд синхронизирует положение переключателей ON/OFF с состоянием светодиодов. Так как панель управления никак не следит дошли ли данные до контроллера и поэтому иногда бывает что на панели положение ON а на самом деле выход выключен. Данный способ позволяет каждые 20 секунд устанавливать переключатели в панели в правильно положение если до этого они стояли не верно.
— Подключена кнопка, при нажатии на которую выполняется запрограммированный сценарий.

Код программы


#include <SPI.h>                
#include <Ethernet.h>
#include <NinjaBlockEthernet.h>  
//#include <MemoryFree.h>

#define DEFAULT_VENDOR_ID 0  // ID  по умолчанию
#define LED_DEVICE_ID 1002   // ID для светодиодов определили их как выключатель с 2-я положениями вкл и выкл
#define BUTTON_DEVICE_ID 5   // ID для кнопки
#define TEMP_ID 1            // ID для датчиков температуры


#include <OneWire.h>
#include <DallasTemperature.h>  // библиотека для тачика температуры ds18b20
#define ONE_WIRE_BUS 2          // подключаем дачик к 2 цифровому выводу 

#define ENABLE_SERIAL false    // отключаем serial monitor

byte button = 5;  
byte led_1 = 7;
byte led_2 = 6;
byte led_3 = 4;
byte led_4 = 3;




boolean isButtonDown = false;
int time =500;                // переменная для отсчета времени для датчиков температуры
int timeup=500;               // переменная для синхронизации кнопок

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

void setup(){
  
   sensors.begin(); // инициализируем датчики температуры

   pinMode(button, INPUT);  // конфигурируем входа и выхода контроллера
   pinMode(led_1, OUTPUT); 
   pinMode(led_2, OUTPUT);
   pinMode(led_3, OUTPUT);
   pinMode(led_4, OUTPUT);
  
  
   
  #if ENABLE_SERIAL
    Serial.begin(9600);
    Serial.println("Starting..");
  #endif
    delay(1000);   // This delay is to wait for the Ethernet Controller to get ready

    NinjaBlock.host = "api.ninja.is";
    NinjaBlock.port = 80;
    NinjaBlock.nodeID = "ETHERSHIELDBLOCK"; // имя для вашего блока
    NinjaBlock.token = "ВАШНОМЕРс САЙТА"; // Get yours from https://a.ninja.is/hacking вписывем ваш код 
    NinjaBlock.guid = "0";
    NinjaBlock.vendorID=DEFAULT_VENDOR_ID;
    NinjaBlock.deviceID=LED_DEVICE_ID;
    NinjaBlock.deviceID=TEMP_ID;

    if (NinjaBlock.begin()==0) // подключаемся к облаку
    
    NinjaBlock.guid = "0";       // отсылаем данные от имени 2-х позиционного выключателя и создаем  виджет в панели для 1-го светодиода
    NinjaBlock.deviceID=(1002);
    NinjaBlock.send("0");
    
    NinjaBlock.guid = "1";       // для 2-го
    NinjaBlock.deviceID=(1002);
    NinjaBlock.send("0");
    
    NinjaBlock.guid = "2";       // для 3-го
    NinjaBlock.deviceID=(1002);
    NinjaBlock.send("0");
    
    NinjaBlock.guid = "3";      // для 4-го
    NinjaBlock.deviceID=(1002);
    NinjaBlock.send("0");

}

void loop() {
  
  
  // СИНХРОНИЗАЦИЯ ПОЛОЖЕНИЯ КНОПОК//
  
    if ( timeup == 1)                    // проверяем пришло ли время для проверки ( каждые 20 секунд)
    {  
      if( digitalRead(led_1)== HIGH)    // если светодиод 1 включен
        {
          NinjaBlock.guid = "0";        // указываем порядковый номер светодиода
          NinjaBlock.deviceID=(1002);   // его ID
          NinjaBlock.send("1");         // отсылаем 1 , что обозначает включен
        }  
      if(digitalRead(led_1)== LOW)      // если светодиод 1 выключен  повторяем что и выше, только отсылаем 0
        {
          NinjaBlock.guid = "0";
          NinjaBlock.deviceID=(1002);
          NinjaBlock.send("0");
           Serial.println( " Led-1 OFF UPdate");
        }  
        
        if(digitalRead(led_2)== HIGH)
        {
          NinjaBlock.guid = "1";
          NinjaBlock.deviceID=(1002);
          NinjaBlock.send("1");
          Serial.println( " Led-2 ON UPdate");
        }  
      if(digitalRead(led_2)== LOW)
        {
          NinjaBlock.guid = "1";
          NinjaBlock.deviceID=(1002);
          NinjaBlock.send("0");
          Serial.println( " Led-2 OFF UPdate");
        }  
        
        if(digitalRead(led_3)== HIGH)
        {
          NinjaBlock.guid = "2";
          NinjaBlock.deviceID=(1002);
          NinjaBlock.send("1");
          Serial.println( " Led-3 ON UPdate");
        }  
      if(digitalRead(led_3)== LOW)
        {
          NinjaBlock.guid = "2";
          NinjaBlock.deviceID=(1002);
          NinjaBlock.send("0");
          Serial.println( " Led-3 OFF UPdate");
        }  
        
        if(digitalRead(led_4)== HIGH)
        {
          NinjaBlock.guid = "3";
          NinjaBlock.deviceID=(1002);
          NinjaBlock.send("1");
          Serial.println( " Led-4 ON UPdate");
        }  
      if(digitalRead(led_4)== LOW)
        {
          NinjaBlock.guid = "3";
          NinjaBlock.deviceID=(1002);
          NinjaBlock.send("0");
          Serial.println( " Led-4 OFF UPdate");
        }  
        
     timeup=2000;   
    }  
    
   //   END - СИНХРОНИЗАЦИЯ  ПОЛОЖКЕНИЯ КНОПОК//
        
        timeup= timeup-1;  // уменьшаем переменную для проверки кнопок
        delay(1);
        
        
    // ПРИНИМАЕМ ДАННЫЕ ДЛЯ СВЕТОДИОДОВ// 
    if(NinjaBlock.receive()) {            // если нам прислали данные
        // структура данных
        // Return values are:
        // NinjaBlock.strGUID
        // NinjaBlock.intVID
        // NinjaBlock.intDID
        // NinjaBlock.intDATA - if data is integer
        // NinjaBlock.strDATA - if data is string (note char[64])
        
             if (NinjaBlock.IsDATAString) {
              
             //Serial.print("strDATA=");
             //Serial.println(NinjaBlock.strDATA);
            

            if (NinjaBlock.intDID == 1002)   // Проверяем нашим ли светодиодам адресованы данные т.е. совпадает ли с нашим ID
            {
             
              if (strcmp(NinjaBlock.strGUID,"0") == 0) // Проверяем какому именно светодиоду из 4-х пприслали данные т.е.  сравниваем GUID
              {
              
                if (strcmp(NinjaBlock.strDATA,"1") == 0) // если GUID совпал , то смотрим что нам примлали и действуем согласно данным
                {                                        // нам прислали 1 значит будем включать светодиод
                    digitalWrite(led_1, HIGH); 
                }
                else if (strcmp(NinjaBlock.strDATA,"0") == 0) // прислали 0 - выключаем
                {
                    digitalWrite(led_1, LOW); 
                }
            }
            if (strcmp(NinjaBlock.strGUID,"1") == 0)  // то же самое только для 2-го светодиода
            {
              if (strcmp(NinjaBlock.strDATA,"1") == 0)
                {           
                    digitalWrite(led_2, HIGH); 
                }
                else if (strcmp(NinjaBlock.strDATA,"0") == 0)
                {
                    digitalWrite(led_2, LOW); 
                }
              
            }
            
            if (strcmp(NinjaBlock.strGUID,"2") == 0)    // то же самое только для 3-го светодиода
            {
              if (strcmp(NinjaBlock.strDATA,"1") == 0)
                {           
                    digitalWrite(led_3, HIGH); 
                }
                else if (strcmp(NinjaBlock.strDATA,"0") == 0)
                {
                    digitalWrite(led_3, LOW); 
                }
              
            }
            
            if (strcmp(NinjaBlock.strGUID,"3") == 0)    // то же самое только для 4-го светодиода
            {
              if (strcmp(NinjaBlock.strDATA,"1") == 0)
                {           
                    digitalWrite(led_4, HIGH); 
                }
                else if (strcmp(NinjaBlock.strDATA,"0") == 0)
                {
                    digitalWrite(led_4, LOW); 
                }
              
            }
            
            }     
        }
        
    }
    
    // ОТПРАВЛЯЕМ ДАННЫЕ С КНОПКИ//

    if (digitalRead(button) == HIGH) { // если кнопка нажата
        if (!isButtonDown) {           // и до этого была отжата
            #if ENABLE_SERIAL
              Serial.println("Button Down");
            #endif
            NinjaBlock.guid = "0";    // отправляем порядковый новер
            NinjaBlock.deviceID=5;    // отправляем ID кнопки 5
            NinjaBlock.send(1);       // Отправляем данные 1 - нажата
            isButtonDown = true;      // записываем последнее значение нажата 
        }
    } else {
        if (isButtonDown) {            // если кнока была раньше нажата
            #if ENABLE_SERIAL           
              Serial.println("Button Up");
            #endif
            NinjaBlock.guid = "0";   // отправляем порядковый новер
            NinjaBlock.deviceID=5;   // отправляем ID кнопки 5
            NinjaBlock.send(0);       // Отправляем данные 0 - отжата
            isButtonDown = false;     // записываем последнее значение - отжата
        }
    }
    
    
    if (time==1)                      // проверяем пришло ли время отправлять данные термодатчиков в обоако ( раз в 60 сек)
    {
  sensors.requestTemperatures();     // запрашиваем показания датчиков
  
  Serial.println(sensors.getTempCByIndex(0));
  NinjaBlock.guid = "0";                        // отправляем порядковый номер 
  NinjaBlock.deviceID=(1);                      // отправляем ID 
  NinjaBlock.send(sensors.getTempCByIndex(0));  // отправляем показания первого датчика
  
  
  Serial.println(sensors.getTempCByIndex(1));   // здесь то же самое
  NinjaBlock.guid = "1";
  NinjaBlock.deviceID=(1);
  NinjaBlock.send(sensors.getTempCByIndex(1));
  
  time=6000;                                   // обновляем переменную для датчиков
    }
    time=time-1;                               // уменшаем переменную с каждым циклом
    delay (9);
}

Постарался обеспечить код подробными комментариями.

Подготовил небольшое демонстрационное видео

Подведем небольшой итог.

Достоинства:
— Управление и котроль из любой точки мира ( при наличии интренета)
— Собирается и настраивается за считанные минуты.
— Очень простой и недорогой
— Наличие специальных сценариев
— Широкий перечень виджетов в панели управления.
— Если добавить роутер TP-Link TL-MR3020 (20$) и 3G модем, то можно использовать на даче и других местах без интернета.
— Если нет желания тянуть сетевой кабель, то достаточно купить за 15$ TP-Link TL-WR702N, который будет работать в качестве wi-fi шилда
— Хорошая стабильность в работе. За 2 дня тестирования не потерялась ни одна команда все работает четко.

Недостатки:
— Самый главный минус этой системы это невозможность работы без интренета.
— Имеется задержка при включении колеблется от долей секунды до нескольких
— Библиотека кушает много места — целых 16 кБ, если добавить библиотеку для nrf24l01 то ничего почти не остается. Придется переходить на Mega скорее всего.
— Приложение для смартфонов не позволяет просматривать показание датчиков. Управление устройствами сделано не очень удобно, не видно текущего состояния on/off.

Планы на будущее:
— Дописать в коде проверку соединения и в случае его отсутствия Arduino будет действовать самостоятельно и будет перезагружать TP-Link TL-MR3020 и 3G модем если работает с ними в связке.
— Добавить nrf24l01 для управления и контроля других датчиков.

Очень интересно услышать ваше мнение и конструктивную критику. Оригинальные вопросы приветствуются! Фух…

ФАЙЛЫ:
Исправленная библиотека.

Автор: ivizil

Источник

Поделиться

* - обязательные к заполнению поля