Шаблон интерфейса для «умного дома» на Ардуино

от автора

Здравствуйте. Целью написания данной статьи является желание поделиться с общественностью накопленной информацией и, конечно же, узнать что-то новое. Я опишу, как подключить ардуину к роутеру и как сделать шаблон веб-интерфейса для управления ардуиной, а точнее, для дискретного «дёрганья ножками».

image

Как это выглядит «вживую», можно посмотреть здесь.

Повествование будет вестись на основе популярного нынче роутера TL-MR3020 и Arduino Nano.

image

Подключаться ардуина будет к USB, вариант с UARTом хоть и проще, но всё же трудней. Такая вот суперпозиция )

Я предполагаю что у заинтересованных лиц уже есть роутер прошитый OpenWrt и установлен сервер (Lighttpd), поддерживающий PHP. Ну, или просто компьютер image

Если то — истина, тогда follow my way image

Подключение ардуино

Прошиваем пробный скетч

int led = 13;  byte descript[5];  void setup()   {      Serial.begin(57600);      pinMode(led, OUTPUT);        }    void loop()   {    if (Serial.available()>4) // ждём дескриптор и нужный символ  {   if (Serial.read()=='Y') // проверяем первый символ, если это 'Y', то продолжаем принимать, если нет, то выходим из цикла чтения     {     for (byte i=0; i < 5; i++)       {          descript[i] = Serial.read();  // добавляем символы в массив          }            if((descript[0] =='+') && (descript[1] =='=') && (descript[2] =='Z'))  // проверяем дескриптор   {    switch (descript[3])     {       case 'A':       digitalWrite(led, HIGH);       Serial.println("OK vkl"); // ответ       break;              case 'a':       digitalWrite(led, LOW);       Serial.println("OK otkl"); // ответ       break;      }    }       else // если символ был не 'Y', то очищаем буфер     {       for(byte i=0; i < 255; i++)        {          Serial.read();            }      }     } // конец if (Serial.read()=='Y')   } // конец чтение порта  } 

Будем отправлять в ардуину запрос состоящий из дескриптора (Y+=Z) и управляющего символа (например, ‘А‘- вкл d13). Дескриптор позволит отфильтровать возможный мусор и исключит случайные срабатывания.

Ардуина обрабатывает управляющий символ внутри функции switch (например, включает светодиод) и рапортует о выполнении.

Подключаем ардуину к роутерувключаем в розеткуустанавливаем необходимые пакеты.

Поскольку придётся использовать хаб, то необходимо помнить, что некоторые из них работают некорректно.

Драйвера для всех существующих ардуин, утилиту для настройки порта stty и ser2net (о нём ниже):
Тот, кто разбирается, сам выберет необходимый драйвер.

opkg update

opkg install kmod-usb-serial-ftdi kmod-usb-acm kmod-usb-serial-pl2303 kmod-usb-serial-cp210x kmod-usb-serial-ch341 libftdi coreutils-stty ser2net 

Если действия производятся на «большом компьютере», то достаточно:

apt-get install ser2net

Проверим, как подцепилось устройство: Может быть, придётся перегрузить.

ls /dev/tty*

Как-то так…

image

Настроим порт утилитой stty:

stty -F /dev/ttyUSB0 cs8 57600 ignbrk -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke noflsh -ixon -crtscts -hupcl 

Открываем вторую консоль и пишем в неё:

cat /dev/ttyUSB0 

Тут будет ответ.

В первой консоли испытываем:

echo 'Y+=ZA' > /dev/ttyUSB0

D13 загорелась

echo 'Y+=Za' > /dev/ttyUSB0

D13 погасла.

Если при посылке пакета ардуина перегружается (диоды моргают, но D13 не горит), тогда нужно поставить электролитический конденсатор 5-10мкФ между Reset и GND.

Не забудьте отключать его, когда заливаете скетч.

Существуют трудности у ардуин с мостом ch341

image
Если всё работает, то идём дальше.

Редактируем файл конфигурации ser2net:

nano /etc/ser2net.conf

Закомментируйте всё строчки в конце и сохраните.

Добавьте stty и ser2net в автозагрузку:

nano /etc/rc.local

Вот так нужно сделать:

stty -F /dev/ttyUSB0 cs8 57600 ignbrk -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke noflsh -ixon -crtscts -hupcl  ser2net -C "3002:raw:0:/dev/ttyUSB0:57600 NONE 1STOPBIT 8DATABITS -XONXOFF -LOCAL -RTSCTS"  exit 0 

Обратите внимание. Строки инициализации должны быть записаны одной строкой (без переноса).

Перегрузите и проверьте.

echo 'Y+=ZA' > /dev/ttyUSB0 echo 'Y+=Za' > /dev/ttyUSB0 

Теперь залейте в ардуину новый скетч:

byte d2 = 0; // флаги byte d3 = 0; byte d4 = 0; byte d5 = 0; byte d6 = 0; byte d7 = 0; byte d8 = 0; byte d9 = 0; byte d10 = 0; byte d11 = 0; byte d12 = 0; byte d13 = 0;  byte descript[5]; // массив  void setup()  {   Serial.begin(57600);   pinMode(2, OUTPUT);    pinMode(3, OUTPUT);   pinMode(4, OUTPUT);   pinMode(5, OUTPUT);   pinMode(6, OUTPUT);   pinMode(7, OUTPUT);   pinMode(8, OUTPUT);   pinMode(9, OUTPUT);   pinMode(10, OUTPUT);   pinMode(11, OUTPUT);   pinMode(12, OUTPUT);   pinMode(13, OUTPUT); }    void loop()  {     if (Serial.available()>4) // ждём дескриптор и нужный символ    {     if (Serial.read()=='Y') // проверяем первый символ, если это 'Y', то продолжаем принимать, если нет, то выходим из цикла чтения      {       for (byte i=0; i < 5; i++)         {            descript[i] = Serial.read(); // добавляем символы в массив            }               if((descript[0] =='+') && (descript[1] =='=') && (descript[2] =='Z')) // проверяем дескриптор      {       switch (descript[3])        {          case 'o': // обновление          glavnaia(); // отправка ответа          break;                    case 'A': // d2 вкл          digitalWrite(2, HIGH); // вкл d2          d2 = 1; // ставим флаг в единицу (вкл)          glavnaia(); // отправка ответа          break;                    case 'a': // d2 откл          digitalWrite(2, LOW); // откл d2          d2 = 0; // ставим флаг в ноль (откл)          glavnaia(); // отправка ответа          break;             case 'B': // d3          digitalWrite(3, HIGH);          d3 = 1;          glavnaia();          break;                    case 'b': // d3          digitalWrite(3, LOW);          d3 = 0;          glavnaia();          break;                       case 'C': // d4          digitalWrite(4, HIGH);          d4 = 1;          glavnaia();          break;                    case 'c': // d4          digitalWrite(4, LOW);          d4 = 0;          glavnaia();          break;                case 'D': // d5          digitalWrite(5, HIGH);          d5 = 1;          glavnaia();          break;                    case 'd': // d5          digitalWrite(5, LOW);          d5 = 0;          glavnaia();          break;               case 'E': // d6          digitalWrite(6, HIGH);          d6 = 1;          glavnaia();          break;                    case 'e': // d6          digitalWrite(6, LOW);          d6 = 0;          glavnaia();          break;                case 'F': // d7          digitalWrite(7, HIGH);          d7 = 1;          glavnaia();          break;                    case 'f': // d7          digitalWrite(7, LOW);          d7 = 0;          glavnaia();          break;              case 'G': // d8          digitalWrite(8, HIGH);          d8 = 1;          glavnaia();          break;                    case 'g': // d8          digitalWrite(8, LOW);          d8 = 0;          glavnaia();          break;              case 'H': // d9          digitalWrite(9, HIGH);          d9 = 1;          glavnaia();          break;                    case 'h': // d9          digitalWrite(9, LOW);          d9 = 0;          glavnaia();          break;                     case 'I': // d10          digitalWrite(10, HIGH);          d10 = 1;          glavnaia();          break;                    case 'i': // d10          digitalWrite(10, LOW);          d10 = 0;          glavnaia();          break;                     case 'J': // d11          digitalWrite(11, HIGH);          d11 = 1;          glavnaia();          break;                    case 'j': // d11          digitalWrite(11, LOW);          d11 = 0;          glavnaia();          break;                     case 'K': // d12          digitalWrite(12, HIGH);          d12 = 1;          glavnaia();          break;                    case 'k': // d12          digitalWrite(12, LOW);          d12 = 0;          glavnaia();          break;                          case 'M': // d13          digitalWrite(13, HIGH);          d13 = 1;          glavnaia();          break;                    case 'm': // d13          digitalWrite(13, LOW);          d13 = 0;          glavnaia();          break;            default:          glavnaia();        }      }         else // если символ был не 'Y', то всё, что есть в буфере, делим на ноль )       {         for(byte i=0; i < 255; i++)           {            Serial.read();              }        }       }    // конец if (Serial.read()=='Y')    }    // конец чтение порта   } // конец loop   void glavnaia() // отправка данных  {       Serial.print(d2);//0       Serial.print(","); // по запятой будем парсить        Serial.print(d3);//1       Serial.print(",");       Serial.print(d4);//2       Serial.print(",");       Serial.print(d5);//3       Serial.print(",");       Serial.print(d6);//4       Serial.print(",");       Serial.print(d7);//5       Serial.print(",");       Serial.print(d8);//6       Serial.print(",");       Serial.print(d9);//7        Serial.print(",");       Serial.print(d10);//8       Serial.print(",");       Serial.print(d11);//9       Serial.print(",");       Serial.print(d12);//10       Serial.print(",");       Serial.println(d13);//11 // отсылается 12 значений разделённых запятой  } 

Роутер отправляет в ардуину запрос от клиента состоящий из дескриптора (Y+=Z) и управляющего символа (например ‘А‘- вкл d2).Ардуина обрабатывает управляющий символ и отправляет ответ роутеру, который в свою очередь отдаст его клиенту. Работа с клиентом описана ниже.

Скачайте архив и распакуйте его в рабочую папку сервера (по умолчанию это /var/www), вот так – /var/www/knopki (у вас может быть своя папка).

В браузере зайдите по адресу ваш_роутер/knopki/. Если связь установлена, то вы увидите это:

image

Нажмите на D13 — загорится светодиод на ардуине и кнопка подсветится.

Суть работы

Обновление страницы:

index.html раз в три секунды (интервал можно изменить) запрашивает данные у ардуины (отправляя ей символ о) с помощью функции ajax (ajax позволяет не перегружать страницу).

/*обновление*/ show(); setInterval(show,3000);  /* частота обновления в миллисекундах */  function show(){  /* функция обновления */               $.ajax({                  type: "GET",                 url: "box2.php?df=o", /* отправка символа о */                 timeout:300,                            cache: false,                        success: function(data){  ... 

Запрос передаётся php-файлу (box2.php) находящемуся на сервере, который в свою очередь обращается к ардуине через сокет ser2net.

ser2net

ser2net — это крохотный прокси-серверок создающий соединение между сокетом и устройством (/dev/tty*).

Как показала практика, через сокет php-файл работает лучше, нежели обращаясь к устройству напрямую.

То есть, если пхп-файл открывает устройство вот так ( fopen(‘/dev/tty*’, …) ), то через некоторое время (несколько часов) всё зависает, а если так ( fsockopen(«localhost», 3002, $errno, $errstr, 1); ), то никаких проблем.

Мне кажется, что это как-то связано с переполнением буфера. И исходя из вышесказанного, хотелось бы услышать на этот счёт мнение Умных людей.

<?php if($fp = fsockopen("localhost", 3002, $errno, $errstr, 1)) // открываем порт, в качестве посредника между роутером и ардуиной выступает ser2net    {        fwrite($fp, 'Y+=Z'); // отправляем в порт дескриптор Y+=Z       fwrite($fp, $_GET['df']); // отправляем в порт символ, полученный от html-странички       $bufft = fgets($fp); // получаем ответ от ардуины       fclose($fp); // закрываем порт       echo $bufft; // отправляем ответ клиенту     }   ?>  

Ардуина получает команду, обрабатывает её и отправляет ответ, который по той же цепочке возвращается html-страничке (index.html).

...  switch (descript[3])        {          case 'o': // обновление          glavnaia(); // отправка ответа          break; ... void glavnaia() // отправка данных  {       Serial.print(d2);//0       Serial.print(",");       Serial.print(d3);//1 ... 

Html-страничка разбирает ответ и выводит на экран нужную кнопку.

 ...  success: function(data){                                                                          var vars = data.split(","); /* разбор строки принятой от ардуино */                            if(vars.length == dlina){ /* проверка длины данных (количество блоков разделённых запятой) */                                                                /*d2*/                                if(vars[0] == 1) { $(".d2otkl").show(); $(".d2vkl").hide(); }  /* в зависимости от принятого флага скрывает/показывает кнопку вкл или откл */                                else if(vars[0] == 0) { $(".d2otkl").hide(); $(".d2vkl").show(); }  ... 

Если открыть ещё одну страничку (или зайти с другого устройства) и нажать какую-то кнопку, то на первой страничке (в течении 3 сек.) эта кнопка тоже станет включённой. Для этого обновление и нужно.

Нажатие на кнопку:

Нажатие на кнопку работает так же, как и «обновление», в ардуину отсылается символ включения или отключения (в зависимости от состояния), ардуина выполняет действие и посылает в ответ строку с флагами состояния (единица или ноль).

Ответ разбирается в html-странице и в зависимости от флагов выводит на экран нужную кнопку.

Для лучшего понимания откройте файл index.html из архива и посмотрите комментарии.


Если связи между ардуиной и роутером нет, то будет только красная надпись stDэто индикатор работы, когда связь установлена, надпись становится серой.

image

Подождите минуту и пообновляйте страницу. Если связь не устанавливается, то проверьте настройки роутера.
Проверьте права на файлы, правильность путей и устройств.


Внешний вид

Названия кнопок меняются в конце файла index.html

... <div class="knop d2vkl">D2</div> <!-- здесь менять названия кнопок --> <div class="knop d2otkl">D2</div> <!-- чтобы убрать кнопку удалите оба блока --> ... 

Позиция и размер кнопок задаются в файле knopki.css

... .d2vkl{ top: 20px; /*координаты кнопок*/ left: 20px; /*координаты кнопок*/ box-shadow: 0 0 10px 3px rgba(0,0,0,0.3); /*цвет и размер тени кнопки*/ -webkit-transition-duration: 0.6s; /*плавность появления*/ -o-transition-duration: 0.6s; -moz-transition-duration: 0.6s; transition-duration: 0.6s; }  .d2vkl:hover{ /*наведение мыши на кнопку*/ box-shadow: 0 0 2px 1px rgba(0,0,0,0.3); } ... 

... .knop { position: absolute; width: 200px; /*ширина для всех кнопок*/ height: 100px; /*высота для всех кнопок*/ display: none; cursor: pointer;  font-size: 30px; /*размер текста на кнопках*/ font-weight: 600; /*ширина текста на кнопках*/ font-family: Arial, Helvetica, sans-serif; /*шрифт*/ color: #161616; /*цвет текста на кнопках*/ text-shadow: 0px 1px 2px #7c7c7c; /*цвет и размер тени кнопок*/ text-align: center; line-height: 3.2; } 

Цвет фона меняется в файле style.css

body { background:#202020; /* цвет фона */ } ... 

На этом, пожалуй, стоит закончить. Спасибо тем, кто потратил своё драгоценное. Если где-то допустил неточность, то прошу поправить меня.

Статья писалась под великолепную музыку «Nirvana» и потрясающую скрипку Davida Garretta.

ссылка на оригинал статьи http://geektimes.ru/post/249958/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *