Создаёт web-интерфейс для управления и скетч для ардуино


Будучи не понаслышке знакомым с трудностями, которые испытывают строители «умных домов», решил запилить конструктор, который всё сделает сам, включая скетч для ардуины, и сервер HomestD для обмена данными. Подключение переферии к ардуине остаётся на совести хозяина, однако в скетче написано, что и куда.


Пользователю останется только скачать архив с файлами, распаковать его на целевом устройстве и загрузить в ардуину готовый скетч. Установка каких-либо дополнительных пакетов и серверов не требуется.

HomestD можно использовать на любом компьютере работающем под управлением или на роутере с прошивкой OpenWrt.

Для работы на роутере не потребуются дополнительные накопители (флешка, sd-карта).


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


… к компьютеру не должно вызвать затруднений, а о том, как подключить ардуину к роутеру (по USB или UARTу) можно прочесть в сети.
При подключении к UARTу никаких пакетов устанавливать не нужно, только подпаяться к контактам и отредактировать файл /etc/inittab.

Пример для TL-MR3020:

nano /etc/inittab


::sysinit:/etc/init.d/rcS S boot
::shutdown:/etc/init.d/rcS K shutdown
#ttyATH0::askfirst:/bin/ash --login





Внешний вид



Идея web-интерфейса достаточно проста и минималистична.


Главный экран интерфейса.

Если кому-то не по душе тёмные цвета, то подправить css-файл не составит труда.



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



Здесь могут располагаться несколько кнопок (D2, D3 и т.д.) для включения чего-либо с возвратом состояния.

Несколько кнопок для отправки сигнала (SENTSIG1 и т.д), не требующего подтверждения.

И несколько полей (INDATA1 и т.д) для приёма каких-либо данных/сигналов.

Крестит закрывает панель.

Названия кнопок можно изменять по своему усмотрению и менять местами.

Пример:



Кнопка Info скрывает панель с информацией о работоспособности системы.



Надпись Connect! говорит о том, что всё хорошо, а Count update: — счётчик запросов (браузер с определённым интервалом запрашивает у ардуины данные). Интервал можно менять.

Если что-то произойдёт, то на экране появится сообщение ERROR, а в Info будет описана ошибка.



Алгоритм работы описан в конце.


Конструктор



Я записал коротенькое видео по работе с конструктором:




Конструктор прост и понятен. Откройте конструктор в соседней вкладке:


В браузере должны быть включены cookie.

Конструктор работает не на всех мобильных устройствах, то есть «умный дом» будет работать на чём угодно, а вот конструировать надо на обычном компе.

Первая страница:



Здесь выбирается количество помещений (максимум 10). Предположим, что будет два помещения (прихожая и кухня), тогда выберите 2 и нажмите «Далее».

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



В поля Адрес сервера и Порт сервера ничего писать не нужно (сделано на будущее).

Названия помещений у нас уже придуманы (прихожая и кухня), вписываем их и нажимаем кнопку «Далее».

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

В дальнейшем Вы можете это исправить в файле index.html.


Здесь Вы увидите главный экран своего будущего интерфейса:



Нажмите «Прихожая»…



Выберите, сколько вы хотите кнопок для включения чего-либо с возвратом статуса (Количество кнопок вкл/откл), кнопок для отправки сигнала не требующего подтверждения (Количество кнопок отправки сигнала) и полей для приёма каких-либо данных (Количество полей для приёма информации).

Для примера выбрано по одной кнопке (максимум по пять).

Теперь закройте панель кнопкой Х, проделайте то же самое с «Кухней» и нажмите кнопку «Далее»…



Появится главный экран с кнопкой «Скачать архив».

Можно открыть «Прихожую» или «Кухню» и посмотреть, что получилось…





Поля для приёма данных заполняются при появлении сигнала.


На этом работа с конструктором закончена, нажмите и переходите к следующей части.



HomestD


Распаковав архив, у Вас появится папка — mydomXXXXXXXXXX, переименуйте её так, чтоб получилось mydom, и перейдите в неё.

Переименуйте файл indexXXXXXXXXX.html в index.html, а файл domXXXXXXXXX.ino переместите в папку со скетчами.

В папке mydom останутся файлы index.html, jquery.js и style.css.

Откройте файл index.html и в двенадцатой строчке — var flagobnov = 0, переправьте ноль на единицу — var flagobnov = 1.

Дополнительные пояснения в конце.

Скачайте и установите библиотеку CyberLib, а затем загрузите скетч (domXXXXXXXXX.ino) в ардуину.


И наконец остаётся последний шаг — скачать программу homestd для вашего устройства, переименовать (для удобства) homestdXXX в homestd и скопировать в папку mydom.

В итоге содержимое папки mydom будет выглядеть так: homestd, index.html, jquery.js и style.css.





HomestD — это web-сервер и сервер для ардуины. Назначение — это обмен данными между web-клиентом (браузер) и ардуиной. То есть homestd принимает запросы от клиента по протоколу TCP (протокол UDP будет добавлен в следующей версии) и передаёт их ардуине, и одновременно принимает данные от ардуины, которые забирает web-клиент.

Исходник
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/stat.h> 
#include <fcntl.h>  
#include <termios.h>  
#include <time.h>
#include <pthread.h>

char response[] = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=UTF-8\r\n\r\n";

char response_css[] = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/css; charset=UTF-8\r\n\r\n";

char response_js[] = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/js; charset=UTF-8\r\n\r\n";

char response_text[] = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/text; charset=UTF-8\r\n\r\n";

char response_403[] = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=UTF-8\r\n\r\n"
"<!DOCTYPE html><html><head><title>403</title>"
"<style>body { background-color: #312f2f }"
"h1 { font-size:4cm; text-align: center; color: #666;}</style></head>"
"<body><h1>403</h1></body></html>\r\n";

#define BUFSIZE 1024
#define ARRAY_SIZE 90000
#define BREADSIZE 512

char send1_array[ARRAY_SIZE] = {0,};
char send2_array[ARRAY_SIZE] = {0,};
char patch_to_dir[64] = {0,};
char fpfile[64] = {0,};
char buffer[BUFSIZE] = {0,};
int count_simvol = 0;
char device[32]={0,};            
unsigned long int speedport = 0; 
unsigned int PORTW = 0;          
char bRead[BREADSIZE] = {0,}; 
int wr_fdb = 0;
char str_iz_file[BREADSIZE] = {0,};
int counterr = 0;
int count_reciv = 0;
int fd;


void error_log(char *my_error) 
{  
   memset(fpfile, 0, 64 * sizeof(char));
   snprintf(fpfile, (int)strlen(patch_to_dir) + (int)strlen("Error.log "), "%s%s", patch_to_dir, "Error.log");
   time_t t;
   time(&t);
   FILE *f;
   f = fopen(fpfile, "a"); 

   if(f == NULL)
    { 
      printf("Error open Error.log\n");
      exit(0);
    }

   fprintf(f, "%s", ctime( &t));
   fprintf(f, "%s\n\n", my_error);
   printf("%s\nError write to %sError.log.\n", my_error, patch_to_dir);
   fclose(f);
   exit(0);
}


void warning_access_log(char *war_ac) 
{  
   memset(fpfile, 0, 64 * sizeof(char));
   snprintf(fpfile, (int)strlen(patch_to_dir) + (int)strlen("Warning_Access.log "), "%s%s", patch_to_dir, "Warning_Access.log");
   time_t t;
   time(&t);
   FILE *f;
   f = fopen(fpfile, "a"); 
   fprintf(f, "%s", ctime( &t));
   fprintf(f, "%s\n\n", war_ac);
   printf("%s\nWrite to %sAccess_Warning.log.\n\n\n", war_ac, patch_to_dir);
   fclose(f);
}


void read_in_file(char *name_file) 
{ 
   count_simvol = 0;
   memset(send1_array, 0, ARRAY_SIZE * sizeof(char));
   memset(fpfile, 0, 64 * sizeof(char));
   snprintf(fpfile, (int)strlen(patch_to_dir) + (int)strlen(name_file) + 1, "%s%s", patch_to_dir, name_file);
   FILE *file; 
   file = fopen(fpfile,"r");
   if(file == NULL) error_log("Error open file");

   int ch;
   while(ch = getc(file), ch != EOF)
    {
      send1_array[count_simvol] = (char) ch;
      count_simvol++;
      if(count_simvol == ARRAY_SIZE - 2)  break;
    }

   fclose(file);
}


void error_to_filebd(char *db_error) 
 {
   if(wr_fdb == 1)
    {
      memset(fpfile, 0, 64 * sizeof(char));
      snprintf(fpfile, (int)strlen(patch_to_dir) + (int)strlen("file.db "), "%s%s", patch_to_dir, "file.db");
      FILE *f;
      f = fopen(fpfile, "w"); 
      fprintf(f, "%s", db_error);
      fclose(f);
      printf("Write to file.db - %s\n", db_error);
    }

   memset(str_iz_file, 0, BREADSIZE);
   strncpy(str_iz_file, db_error, 13); 
 }


void * thread_func() 
 { 
   int i = 0;
   int err_count1 = 0;

   for(;;) 
    { 
      int bytes = 0;
      memset(bRead, 0, BREADSIZE * sizeof(char));
      counterr = 0;

      if((bytes = read(fd, bRead, BREADSIZE - 1)) == -1) 
        {
          warning_access_log("Error_Read_from_Arduino.");
        }

      for(i = 0; i <= bytes; i++)
       {
         if(bRead[i] == '\n') break;
       }  
 
     if(bRead[0] == 'A' && bRead[strlen(bRead)-2] == 'Z')     
      {
        err_count1 = 0;
      }

     else
      { 
        tcflush(fd, TCIFLUSH); 
        err_count1++;
        if(err_count1 > 5) 
          {
            err_count1 = 0;
            error_to_filebd("NOT A_Z_SIM \n");
          }

        printf("Not_A-Z_bRead: %s\n\n", bRead);  
        continue;
      } 

     if(strcmp(bRead, str_iz_file)==0)
      { 
        printf("StrOK:%s\n\n", bRead); 
        continue;
      }

     else
      {  
        if(wr_fdb == 1)
         {
           char fpfile_2[64] = {0,};
           snprintf(fpfile_2, (int)strlen("file.db ") + (int)strlen(patch_to_dir), "%s%s", patch_to_dir, "file.db");
           FILE *f;
           f = fopen(fpfile_2, "w"); 
           if(f == 0) warning_access_log("NOT open file.db Arduina.");
           fprintf(f, "%s", bRead);
           fclose(f);
         }

        memcpy(str_iz_file, bRead, BREADSIZE);  
        printf("NotStr:%s\n\n", bRead); 
      }
    
    } // END (while) ardu

   return 0;

 } // END thread_func


void * thread2_func() 
 { 
   for(;;) 
    { 
      sleep(1);
      counterr++;
      if(counterr > 2) error_to_filebd("NOT CONNECT \n");
    } 

   return 0;

 } 


void open_port()  
 {   
   fd = open(device, O_RDWR | O_NOCTTY); 
   if(fd == -1) error_log("Error - NOT open /dev/ttyX");
   else  
     {  
       struct termios options;  
       tcgetattr(fd, &options);   

       switch(speedport)
       {
        case 4800:       
          cfsetispeed(&options, B4800); 
          cfsetospeed(&options, B4800); 
        break;

        case 9600:       
          cfsetispeed(&options, B9600); 
          cfsetospeed(&options, B9600); 
        break;

        case 19200:       
          cfsetispeed(&options, B19200); 
          cfsetospeed(&options, B19200); 
        break;

        case 38400:       
          cfsetispeed(&options, B38400); 
          cfsetospeed(&options, B38400); 
        break;

        case 57600:       
          cfsetispeed(&options, B57600); 
          cfsetospeed(&options, B57600); 
        break;

        case 115200:       
          cfsetispeed(&options, B115200); 
          cfsetospeed(&options, B115200); 
        break;

        default: 
          error_log("Error - Speed_port");
        break;
       }

       options.c_cflag |= (CLOCAL | CREAD); 
       options.c_iflag = IGNCR;
       options.c_cflag &= ~PARENB;  
       options.c_cflag &= ~CSTOPB;  
       options.c_cflag &= ~CSIZE;  
       options.c_cflag |= CS8;  
       options.c_cc[VMIN] = 1;  
       options.c_cc[VTIME] = 1;  
       options.c_lflag = ICANON;  
       options.c_oflag = 0;  
       options.c_oflag &= ~OPOST; 
       tcflush(fd, TCIFLUSH);
       tcsetattr(fd, TCSANOW, &options);  
     }  
 }


int main(int argc, char *argv[])  
{  
  if(argc != 6) error_log("Not argumets.");
     
  strncpy(device, argv[1], 31); 
  speedport = strtoul(argv[2], NULL, 0); 
  PORTW = strtoul(argv[3], NULL, 0); 
  strncpy(patch_to_dir, argv[4], 63); 
  wr_fdb = atoi(argv[5]); 

  open_port(); 
  sleep(2);
  tcflush(fd, TCIFLUSH);
  warning_access_log("START");

  int pt1 = 1; 
  pthread_t ardu_thread;
  int result = pthread_create(&ardu_thread, NULL, &thread_func, &pt1); 
  if(result != 0) error_log("Error - creating thread.");

  int pt2 = 1; 
  pthread_t counterr_thread;
  int result2 = pthread_create(&counterr_thread, NULL, &thread2_func, &pt2); 
  if(result2 != 0) error_log("Error - creating thread2.");

  int one = 1, client_fd;
  struct sockaddr_in svr_addr, cli_addr;
  socklen_t sin_len = sizeof(cli_addr);
 
  int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (sock < 0) error_log("Not socket.");
 
  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int));
 
  svr_addr.sin_family = AF_INET;
  svr_addr.sin_addr.s_addr = INADDR_ANY;
  svr_addr.sin_port = htons(PORTW);
 
  if(bind(sock, (struct sockaddr *) &svr_addr, sizeof(svr_addr)) == -1) 
   {
     close(sock);
     error_log("Error bind.");
   }
 
  if(listen(sock, 10) == -1) 
   {
     close(sock);
     error_log("Error listen.");
   }

  int dev_echo = strlen(device) + 18;
  char otvet[BREADSIZE] = {0,};
  char to_Ardu[64] = {0,};

  
  for(;;) 
  {
    client_fd = accept(sock, (struct sockaddr *) &cli_addr, &sin_len);
 
    if(client_fd == -1) continue;
  
    memset(buffer, 0, BUFSIZE);
    read(client_fd, buffer, BUFSIZE - 1);

    if((strstr(buffer, "file.db")) != NULL) 
     {
       memset(otvet, 0, BREADSIZE);

       int c_sim = 0;
       for(c_sim = 0; c_sim <= BREADSIZE - 1; c_sim++)
        {
          if(str_iz_file[c_sim] == '\n') break;
        } 

       snprintf(otvet, 59 + c_sim, "%s%s", response_text, str_iz_file);
       write(client_fd, otvet, c_sim + 58);
       close(client_fd);
       printf("Trans otvet.\n");
     }

    else if((strstr(buffer, "comanda")) != NULL) 
     {
       memset(to_Ardu, 0, 64);
       snprintf(to_Ardu, dev_echo, "echo 'Y+=Z%c%c%c' > %s", buffer[13], buffer[14], buffer[15], device);
       system(to_Ardu);
       close(client_fd);
       warning_access_log(buffer);
       printf("To Ardu: %s\n", to_Ardu);
     }

    else if((strstr(buffer, "GET / ")) != NULL) 
     {
       memset(send2_array, 0, ARRAY_SIZE * sizeof(char));
       read_in_file("index.html");
       int len_ara = count_simvol + (int)strlen(response) + 1;

       snprintf(send2_array, len_ara, "%s%s", response, send1_array);
       write(client_fd, send2_array, count_simvol  + 59); 
       close(client_fd);
       warning_access_log(buffer);
       printf("Trans index.html.\n\n");
     }

    else if((strstr(buffer, "style.css")) != NULL) 
     {
       memset(send2_array, 0, ARRAY_SIZE * sizeof(char));
       read_in_file("style.css");
       int len_ara = count_simvol + (int)strlen(response_css) + 1;

       snprintf(send2_array, len_ara, "%s%s", response_css, send1_array);
       write(client_fd, send2_array, count_simvol + 58);
       close(client_fd);
       warning_access_log(buffer);
       printf("Trans style.css.\n\n");
     }

    else if((strstr(buffer, "jquery.js")) != NULL) 
     {
       memset(send2_array, 0, ARRAY_SIZE * sizeof(char));
       read_in_file("jquery.js");
       int len_ara = count_simvol + (int)strlen(response_js) + 1;
 
       snprintf(send2_array, len_ara, "%s%s", response_js, send1_array);
       write(client_fd, send2_array, count_simvol + 57);
       close(client_fd);
       warning_access_log(buffer);
       printf("Trans jquery.js.\n\n");
     }
 
    else 
     {
       write(client_fd, response_403, sizeof(response_403) - 1);
       close(client_fd);
       warning_access_log(buffer);
     }
  }

} //END main

// gcc -Wall -Wextra -Werror homestd.c -o homestd -lpthread
// ./homestd /dev/ttyUSB0 57600 80 /var/www/vse/tpl/mydom2/ 0
//  make package/homestd/compile V=s





Подключаем ардуину, копируем папку mydom в любое удобное место на целевом устройстве, например в корень (путь будет выглядеть так — /mydom) и запускаем командой:
sudo /mydom/homestd /dev/ttyUSB0 57600 80 /mydom/ 0

На роутере без sudo.



Первый параметр — /dev/ttyUSB0, путь к ардуине. Узнать можно так:
ls /dev/tty*



Второй параметр — 57600, скорость «сом»-порта.

Третий параметр — TCP порт. Порт можно указать любой, однако если у Вас больше нет никаких серверов занимающих стандартный (80) порт, то укажите его. Если система ставится на роутер, то скорее всего там есть «web-морда» и 80-ый порт будет занят. Тогда укажите что-нибудь другое, например 82 (заходить в «умный дом» так — адрес:82).

Четвёртый параметр — путь к папке mydom (слеш / в конце обязателен).

Пятый параметр — может быть 0 или 1. Если указать 1, тогда в папке mydom будет создаваться текстовый файл file.db, в который будут записываться данные полученные от ардуины. Это сделано для того, чтоб можно было забирать эти данные и заносить куда-либо.



Все действия homestd, сопровождаются записью в файл Access_Warning.log



Ошибки записываются в файл Error.log



Если всё заработало, то переходите в браузер и начинайте пользоваться. Если что-то не так, то приступайте к поиску ошибок и пишите в комментах…



Пояснения



К скетчу…

Задача ардуины — принимать команды от сервера, выполнять действие и через каждые 440мс отправлять статус/информацию обратно.

Для кнопок для включения чего-либо формируются флаги (d2, d3...) принимающие значения 1 или 0, эти значения присваиваются им в функции «switch(cod_comand)», во время включения/отключения чего-либо.

...
    case 100:
    D2_High;
    d2 = 1;
    clear_port();
    break;

    case 101:
    D2_Low;
    d2 = 0;
    clear_port();
    break;
...





Функция «void trans()» отправляет эти значения (вместе с другими данными) серверу.

void trans() 
 {
   Serial.print('A'); 
   Serial.print(' ');   
   Serial.print(0);
   Serial.print(' '); 
   Serial.print(d2);
   Serial.print(' ');
   Serial.print(d3);
...




Команды от кнопок для отправки сигнала не требующего подтверждения просто обрабатываются в функции «switch(cod_comand)».

...
    case 106:
    // какая-то реакция на кнопку SENTSIG1
    clear_port();
    break;

    case 107:
    // какая-то реакция на кнопку SENTSIG2
    clear_port();
    break;
...




Данные, которые будут выводиться в полях для приёма каких-либо данных, нужно поместить в функцию «void trans()». Например, нужно отправить показания температуры, тогда пишем:

...
Serial.print(temp); // INDATA3
...

temp — это какая-то переменная, в которую вы записываете показания датчика.

В интерфейсе, в поле «INDATA3» будет Ваша температура. Также можно посылать какую-то строку, не разделённую пробелами, например, так:

...
Serial.print("okey"); // INDATA3
...



К файлу index.html…


Браузер с интервалом 680мс запрашивает данные у ардуины…
...
setInterval(show,680); 
...

… получает ответ в текстовом виде (данные разделены пробелами) и раскладывает их по переменным.
...
/* приём */
if(vars[2] == 1) { $('.d2otkl').show(); $('.d2vkl').hide(); }
else if(vars[2] == 0) { $('.d2otkl').hide(); $('.d2vkl').show(); }

$('#indata3').html('INDATA3' + '     ' + vars[3]);

if(vars[4] == 1) { $('.d3otkl').show(); $('.d3vkl').hide(); }
else if(vars[4] == 0) { $('.d3otkl').hide(); $('.d3vkl').show(); }
...


Если устанавливаете систему там, где качество связи плохое (например на даче), то есть пинги туда очень длинные, то появятся ошибки «timeout». Чтобы это исправить, нужно увеличить таймаут запроса:

...
show();
setInterval(show,680); 
function show(){  
    if(flagobnov == 1) { 
            $.ajax({ 
                type: "POST",
                url: "file.db", 
                timeout:560,  /* эта цифра (в миллисекундах)*/         
                cache: false,      
...

По умолчанию стоит 560мс, увеличивайте её с шагом в 100 мс и пробуйте. Соответственно нужно увеличивать и setInterval(show,680), так же на 100 мс.


Изменять названия кнопок (D2, D3, SENTSIG1 и т.д.) можно здесь:
...
        <div class='knop kon d2vkl'>D2</div>
        <div class='knop koff d2otkl'>D2</div>

        <div class='knop kon sent1'>SENTSIG1</div>
...



Изменять названия полей для приёма данных (INDATA3, INDATA5 и т.д.) можно здесь:
...
$('#indata3').html('INDATA3' + '     ' + vars[3]);
...




Браузер постоянно запрашивает данные и тем самым создаёт трафик. Чтобы этого избежать, можно либо закрывать страницу, либо раскомментировать этот блок:
/*slmode++;
   if(slmode > 70) 
    { 
      $(".pansl").show(300);
      flagobnov = 0;
      slmode = 0;
    }*/



Тогда через ~минуту страница будет закрываться полупрозрачной панелью и обновления остановятся. Клик на панель уберёт её и обновления возобновляться.


На этом пока всё, в следующей части будет добавлен UDP клиент/сервер и работа с GPIO на RPi.


П.С. Вначале с конструктором происходили непонятные вещи, но теперь всё должно работать исправно.
Поделиться с друзьями
-->

Комментарии (67)


  1. DEM_dwg
    11.09.2016 08:27
    +7

    Вы меня Дмитрий, конечно извините.
    Но все таки умный дом, это прежде всего выполнение каких то действий, самим оборудованием(в данном случае малинкой).
    У Вас же, просто интерфейс включения и выключения приборов.


    1. stDistarik
      11.09.2016 08:50

      Дык я и написал, что это конструктор интерфейса. Подключение же приборов к ардуине или другому МК, это задача хозяина. Я же не знаю, что Вы собираетесь подключать.

      Конструктор предоставляет Вам управление любыми подключёнными к МК устройствами (лампочки, диммеры, датчики, сенсоры и т.д.).

      Управление GPIO малины будет позже.


      1. stDistarik
        11.09.2016 09:06

        Правильнее будет объяснить мою идею так: большинство людей с лёгкостью подключают к ардуине различные датчики и реле, а вот как управлять этим удалённо для них проблема. Вот для этого и сделан конструктор, который создаёт интерфейс, скетч для ардуины и в придачу сервер.


  1. Opengamer
    11.09.2016 09:13

    Решал похожую задачу для автоматизации эскейп квестов, пришел к выводу, что эффективнее будет купить Lan Shield для Arduino (к примеру W5100 с аппаратной реализацией стека протоколов TCP/IP и UDP) и поднять на нем web сервер.
    А точнее два web сервера, один для периодических асинхронных запросов со стороны клиента, которому важно моментально обновление информации (реализуется запросом с web интерфейса с таймаутом ~ 12 секунд, web сервер отвечает на него JSONом или в конце таймаута ~ 10 секунд, или если в Arduino за счет изменения чего-либо устанавливается флаг somethingnew, после получения ответа тут же отправляется следующий аналогичный запрос). Второй web сервер на другом порту слушает get запросы, парсит их и преобразует в команду для выполнения на Arduino. Два web сервера нужны так как использованная Arduino библиотека не позволяет подключаться нескольким клиентам одновременно, а клиент на первом сервере висит постоянно.
    Уверен, что есть легковесные библиотеки позволяющие обойти это ограничение, но не было времени поискать и сравнить производительность. Если кто-нибудь подскажет — буду благодарен.


    1. stDistarik
      11.09.2016 09:23

      пришел к выводу, что эффективнее будет купить Lan Shield для Arduino (к примеру W5100 с аппаратной реализацией стека протоколов TCP/IP и UDP) и поднять на нем web сервер.


      В моём случае вместо W5100 выступает самый дешёвый роутер с OpenWrt. Во-первых есть возможность установки VPN (что в случае доступа извне просто необходимо), во-вторых неограниченое количество подключённых клиентов со скоростью запросов хоть каждые 100мс (всё упирается в пропускную способность инета), то есть при желании реакция будет мгновенная. Ну, а по цене, разница в ~500 рублей не так уж и обременительно.

      Ну и конечно же роутер выполняет свои основные функциии.


      1. Opengamer
        11.09.2016 09:56
        -1

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


        1. stDistarik
          11.09.2016 11:13

          не хотелось использовать дополнительных девайсов


          Роутер есть почти у всех, а W5100 не только дополнительный девайс, но ещё и лишнее звено в цепи.

          А вот по поводу длинных запросов вместо периодических коротких подумайте


          Нет никаких длинных запросов, если всё работает в локальной сети, то запросы можно делать с интервалом хоть в 50мс: ардуина спокойно получает и отдаёт запросы со скоростью хоть 50мс и даже не вспотеет. Главное чтоб длинна пакета успевала проходить.
          Уарт ардуины настроен на 57600, впустить она должна 4 символа, это ~1мс, выплюнуть должна, предположим 40 символом (ответ на двадцать кнопок или состояний каких-то концевиков, то есть 1 или 0), это около 8мс, ну и прибавьте сюда внутреннюю обработку — это от нескольких мкс до нескольких мс. То есть выйдет где-то 10-15мс.

          А если система стот за городом (на даче), то там инет медленный и надо увеличить интервал запросов, в примере стоит 680мс, этого хватает чтоб пакет добежал до посёлка МГА и венулся обратно.


          1. Opengamer
            11.09.2016 11:52

            Роутер есть почти у всех, в W5100 не только дополнительный девайс, но ещё и лишнее звено в цепи.

            Не у всех роутеры с USB, а расположить Arduino рядом с роутером проблематично. Если ни с первым ни со вторым проблем нет — ваше решение предпочтительнее.

            ардуина спокойно получает и отдаёт запросы со скоростью хоть 50мс и даже не вспотеет.

            Если на самой Arduino не крутится какой-то сложной, ресурсоемкой логики и нет необходимости пересылать значения аналоговых датчиков (10 разрядов для Arduino для каждого аналогового порта), тогда не вспотеет.
            Конечно логику в вашем подходе можно перенести в HomestD, используя Arduino только в качестве портов ввода\вывода (в случае если в логики нет ничего требующего очень быстрого реагирования), тогда она тоже все должна успевать.

            PS Я понимаю акцент на конструкторе веб интерфейса, если бы мне он попался на годик раньше — я был бы очень рад.


            1. stDistarik
              11.09.2016 12:06

              Не у всех роутеры с USB,


              USB не нужно.

              Arduino рядом с роутером проблематично


              Можно прямо в роутер запихать (тут всё от задачи зависит), а можно отнести ардуину куда угодно, хоть в кладовку, и подключить её по RS-485 в двунаправленном варианте (полная синхронность).

              Если на самой Arduino не крутится какой-то сложной, ресурсоемкой логики и нет необходимости пересылать значения аналоговых датчиков (10 разрядов для Arduino для каждого аналогового порта), тогда не вспотеет.


              Скорость считывания АЦП ардуины 100мкс (0.0001 с), куда уж быстрее. Вам вряд ли понадобится и сотая часть этой чудовищной скорости.

              Если на самой Arduino не крутится какой-то сложной, ресурсоемкой логики


              Вы ардуиной собираетесь сайты парсить? О какой ресурсоемкой логики Вы говорите?


              1. Opengamer
                11.09.2016 13:51

                Я понял, что я решал специфически совсем другую задачу и описывал все применительно к ней. Извините, что влез.

                Мне необходимо было сделать web интерфейс доступный только в LAN, для Arduino, которая управляла всем квестом, в том числе и воспроизведением звука и обработкой первичной информации и динамической индикацией на 4 4-х разрядных таймера, поэтому при частых запросах она вела себя некорректно. При этом оператору было критично получать информацию как можно быстрее. Поэтому я и решил воспользоваться долгими запросами.

                Передача всей информации по последовательному порту занимала около 70 миллисекунд, в том числе из-за передачи значений с аналоговых датчиков (4 символа против 1 у цифровых датчиков) и большого количества промежуточной информации о состоянии того или иного задания, хранимой в Arduino. Это было слишком много. Применение долгих запросов позволило занимать эти 70 мс только при изменении чего либо важного, что решило мою проблему.

                В моем контексте использование роутера мне в голову не пришло, и было бы неудобно, так как в одном месте располагается несколько квестов, и в каждом может быть не по одной Arduino. Причем мне при моем подходе достаточно html + javascript на локальной машине для запросов к любой Arduino и отображения всей необходимой информации и управления всем, чем я захочу.

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


                1. stDistarik
                  11.09.2016 14:06

                  Да, мне с самого начала показалось, что мы говорим на разных языках)

                  Причем мне при моем подходе достаточно html + javascript


                  У меня на клинте тоже используется только html + javascript (ajax).

                  А что такое квест? Это сленг какой-то или игра?


                  1. Opengamer
                    11.09.2016 14:14

                    Реалити квест, комната или несколько комнат в реальном мире, в которых от 2 до 4 игроков запирают на 60 минут, а им нужно решая разные загадки найти выход. Можно провести прямую аналогию с компьютерными квестами, только перенесенными в реальный мир. Википедия — Эскейп-рум
                    Не буду приводить конкретные ссылки на квесты, а то будет смотреться как реклама :)


                  1. Opengamer
                    11.09.2016 14:27

                    У меня на клинте тоже используется только html + javascript (ajax).

                    Да клиентская реализация у меня аналогичная.
                    Отличие на самом деле в том, что у меня обмен идет от локального компа\или смартфона напрямую до Arduino (с W5100 модулем), а у вас оно реализовано клиент — роутер — Arduino, так как взаимодействие идет еще и из интернета.


                    1. stDistarik
                      11.09.2016 14:36

                      Так точно!

                      В скором времени хочу на сокетах сделать.

                      Ну и UDP обязательно, но там много сложностей с конструктором для моб. девайсов.


  1. kalobyte
    11.09.2016 09:33

    как раз вчера искал что-то подобное типа веб сервер + скетч для ардуины
    хочу в одной конторе сделать открывалку двери с телефонов и расширением для браузера в виде кнопки на панели инструментов

    для телефонов есть хороший софт
    http://automagic4android.com/

    а если просто кнопки нужны, то есть попроще https://play.google.com/store/apps/details?id=com.idlegandalf.httprequestwidget


    1. doom369
      11.09.2016 20:29

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

      Blynk — делается за 5 мин.


  1. stDistarik
    11.09.2016 09:35

    Вопрос к модераторам, как быть со ссылкой на сам конструктор?


    1. alexpp
      11.09.2016 10:31

      Используйте гитхаб и его вики.


      1. stDistarik
        11.09.2016 10:46

        Я не могу установить конструктор на гитхаб или иной ресурс, там сложный механизм с кучей исполняемых файлов (РНР, СИ) и скриптов.


  1. x893
    11.09.2016 10:47
    +3

    Перечитал три раза — так и не понял как мне настроить, что бы холодильник сам заказывал помидоры когда осталось 2 помидора внутри. Так же интересует настройка для куриных яиц, колбасы и сыра.


    1. stDistarik
      11.09.2016 11:15

      Установите в холодильник устройство, которое будет подсчитывать яйца и помидоры, и сообщать об этом ардуине. Ардуина в свою очередь сообщит Вам об этом.


  1. GrishaTav
    11.09.2016 11:19

    А почему не используете какой-нибудь общеизвестный протокол между сервером и Ардуино, например, Modbus/TCP? К вашей системе ведь не подключишь ни одного «чужого» устройства.


    1. stDistarik
      11.09.2016 11:20

      Мне не очень нравится концепция — master-slave. Какое устройство Вы хотите подключить?


      1. GrishaTav
        11.09.2016 11:40

        Ну, например, промышленный модуль ввода-вывода. Я, собственно, уже давно сделал себе умный дом на двух модулях ввода-вывода дискретных сигналов + мобильной SCADA на телефоне. Все работает по Modbus/TCP, доступ снаружи через VPN.
        Но хочется добавить в систему что-то самодельное, чтобы поглубже изучить тему.


        1. stDistarik
          11.09.2016 11:52

          Ну, например, промышленный модуль ввода-вывода


          Я плохо знаком с этим девайсом, опишите его, что он должен делать?


          1. GrishaTav
            11.09.2016 12:16

            Мои железки — Moxa E2210, 12 входов, 8 выходов. К входам и выходам подключены разнообразные датчики и реле, есть порт Ethernet. Управляю с помощью SCADA с мобильника по протоколу Modbus/TCP. Плюс именно в этих модулях есть еще и своя простенькая программируемая логика на уровне «сработал датчик на входе 1 -> установить регистр 10 в 1 и подать импульс длительностью 30 сек на выход 5». Соотвественно, все входы, выходы и регистры доступны снаружи по Модбасу.


            1. stDistarik
              11.09.2016 12:35

              Написано, что у Вашей железяки есть HTTР протокол, значит можно подключится к серверу какому-либо.
              Можно по Модбасу подключится к ардуине.
              А у железки нет ннапример УАРТА?


              1. stDistarik
                11.09.2016 12:44

                Вобще конечно, еслиб у меня валялась подобная железка, я бы обязательно добавил возможность подключения по модбасу. Но железки нет, а на ардуине можно реализовать всё что угодно ИМХО.

                Да и цена Вашей железки 20000 рублей!


                1. GrishaTav
                  11.09.2016 13:24

                  Да, железка недешевая, но это же серьезное промышленное решение. Ну и купил я ее еще по старому курсу, тогда цена не так пугала.
                  На Ардуину я купил два шилда — RS-485 и Ethernet. В принципе, Modbus RTU через RS-485 я осилил, а вот с TCP пока не получилось.
                  Думаю, что Вам надо обязательно добавить Modbus как на сервер, так и на Ардуину. Свой протокол — это путь в никуда при наличии открытого аналога.


                  1. stDistarik
                    11.09.2016 13:44

                    Свой протокол — это путь в никуда при наличии открытого аналога


                    Мой протокол надёжный и работает уже года два-три, он простой, он двунаправленный и не требует денег.

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


                    1. GrishaTav
                      11.09.2016 14:04

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


                      1. stDistarik
                        11.09.2016 14:10

                        Думаю, поддержка этого протокола никак не сможет повредить вашему проекту, а скорее даже наоборот.


                        Вне всяких сомнений, вот только железяки нет.

                        Честно говоря, мне кажется в недалёком будущем (с полноценным приходом IOT) во все уголки мира, появятся какие-то протоколы отличные от нынешних.


                        1. GrishaTav
                          11.09.2016 15:34

                          Такие железяки промышленного уровня начинаются где-то от 10000 руб. Китайские поделия с Алиэкспресс — где-то от 3000-4000 руб, ну у которых заявлена какая-то поддержка Modbus/TCP и имеются входы/выходы, которыми можно управлять. Так что по деньгам все нормально.
                          Новые протоколы — да пожалуйста, Apple HomeKit. Управление голосом через Сири или с дисплея Айфончика через встроенное приложение, все круто и дорого, как и положено. Думаю прикупить себе какой-нибудь совместимый девайс и попробовать.


              1. GrishaTav
                11.09.2016 13:03

                HTTP у нее для встроенного веб-сервера, но он убогий, выводит не все функции. Конечно, можно смотреть входы и щелкать выходами. Но у меня две таких, поэтому я решил купить нормальную СКАДу для мобильника и сделать более-менее пристойный юзер-интерфейс.
                Насчет UART у этой — не помню, я специально покупал дорогую с Ethernet.
                С UART (RS-485) есть аналогичные и гораздо более дешевые железки в огромном ассортименте. В большинстве своем они поддерживают «классический» Modbus RTU. Я бы мог, конечно, поставить такие, а потом один шлюз из Modbus RTU в Modbus/TCP. Но для этого надо по всей квартире тянуть еще и двухпроводной кабель, да и хотелось более прогрессивного решения.


              1. GrishaTav
                11.09.2016 13:41

                Вспомнил, эта железка еще и работает по CGI-запросам и свой первый вариант я сделал как раз на этом интерфейсе. Тогда это был единственный вариант для мобильника на Windows Mobile. Но потом у меня появился Айфон, а под iOS появилась отличная SCADA с поддержкой Modbus/TCP.


        1. Bluefox
          12.09.2016 10:26

          Поставте ioBroker. Он поддерживает modbus/TCP, а ардуинки я советую подключать по MQTT. Ну или mySensors.


  1. stDistarik
    11.09.2016 11:25

    Странно, комментов уже не мало, а никто не поинтересовался самим конструктором. Просто получается парадокс, статья о конструкторе, а поюзать его нельзя.


    1. CyberBot
      11.09.2016 13:21

      Привет, Дмитрий!
      Если помнишь подобный проект http://cyber-place.ru/showthread.php?t=1854
      То судя по количеству установок модуля, людей не очень сильно интересуют подобные конструкторы


      1. stDistarik
        11.09.2016 13:34

        Привет! У меня немного другое, позже выложу у тебя.


  1. igor2503
    11.09.2016 12:23
    +1

    Дмитрий, исправьте ОПАСНЫЕ ошибки в Вашем проекте: логи в линуксе ДОЛЖНЫ писаться во вполне определённое место, это место "/var/log/___". Правило это «придумано» не просто так. OS должна знать куда можно вести постоянную запись. В OpenWrt это особо критично! Сбрасывая логи в /mydom/Access_Warning.log Вы каждый раз перезаписываете overlay( флэш память). Раз на тысячный( при 2х обращениях в секунду, это не долго) роутер вам скажет «пока».
    Если Вам нравится что бы всё было в одном месте, то можно сбрасывать логи в /var/log/mydom/, а у себя( в папке проекта) сделать симлинк на эту папку.


    1. stDistarik
      11.09.2016 12:25

      логи в линуксе ДОЛЖНЫ писаться во вполне определённое место


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

      if((strstr(buffer, "file.db")) != NULL) 
           {
             memset(otvet, 0, BREADSIZE);
      
             int c_sim = 0;
             for(c_sim = 0; c_sim <= BREADSIZE - 1; c_sim++)
              {
                if(str_iz_file[c_sim] == '\n') break;
              } 
      
             snprintf(otvet, 59 + c_sim, "%s%s", response_text, str_iz_file);
             write(client_fd, otvet, c_sim + 58);
             close(client_fd);
             printf("Trans otvet.\n");
           }
      


      Лог пишется только при запросе страниц (index,css,js), а это редко происходит, команда тоже пишется, но она оставлена на первое время, чтоб можно было посмотреть если что.


      1. igor2503
        11.09.2016 13:52

        И ещё пару советов, т.к. Вы базируетесь на роутере, как по мне, это хорошая идея,
        1) и даже если логов не много, сделайте контроль размера файлов. В роутере обычно не много свободной памяти. И растить tmpfs это есть плохо ;-). В идеале — логов быть не должно. Логи только для отладки — включаемой дополнительной командой.
        И ещё,
        2) сделайте поддержку нескольких «ардуин»( uart`ов). Очень удобно ардуины( микроконтролеры) располагать поближе к точкам коммутации электричества. А их обычно много. Перейти на RS485, это сложновато для большинства «ардуинщиков», а вот usb-hub это не сложно ;-).


        1. stDistarik
          11.09.2016 15:40

          Логи только для отладки — включаемой дополнительной командой


          Была такая мысль, но позже решил, что надо просто писать определённое время, скажем дво-трое суток, а потом удалять, оставляя последний.

          сделайте поддержку нескольких «ардуин»( uart`ов). Очень удобно ардуины( микроконтролеры) располагать поближе к точкам коммутации электричества. А их обычно много. Перейти на RS485, это сложновато для большинства «ардуинщиков», а вот usb-hub это не сложно ;-).


          Если я правильно понял, то Вы предлагаете втыкать несколько ардуин в один роутер через хаб?


          1. igor2503
            11.09.2016 19:08

            ДА, но только в данном контексте. И я понимаю какая это глупость не эфективность. Но всему своё время.

            Но насколько я понял из Вашей статьи Ваш проект нацелен на начинающих «ардуинщиков» для которых 485 шина это уже сложно, условно многозадачный скетч для ардуины — это «запредельно», а вот купить китайский усб-хаб, пяток переходников usb-uart и пяток самых дешёвых ардуин — не проблема. По началу и отлаживать это всё будет проще. И ограничение по длине проводов от переходника до микроконтроллера не сильно жесткое.
            Судя по картинкам — датчики уже появились, следующее, скорее всего, это управление IR устройствами (телек, кондей и.т.д.) — тут точно разные комнаты и разные места установки.


            1. stDistarik
              12.09.2016 05:28

              Можно прямо сейчас подключать несколько ардуин через хаб, делать несколько папок с интерфейсами (/mydom1, /mydom2, /mydom3), запускать несколько копий серверов на разных портах (и разных /dev/tty*) и заходить на ардуины так:

              1-я ардуина — 192.168.5.197
              2-я ардуина — 192.168.5.197:81
              3-я ардуина — 192.168.5.197:82
              И так далее…

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

              Длина USB — вот проблема, метра три не больше. А склепать двунаправленный RS-485 по моему не так уж и сложно, тогда длина станет не важна.


  1. stDistarik
    11.09.2016 12:24

    del


  1. bezdnacom
    11.09.2016 14:10

    Интересная статья! Вот только не покидает меня один вопрос: конструктор генерирует сайт только для десктопа? Или есть возможность генерации для мобильных браузеров?


    1. stDistarik
      11.09.2016 14:24

      Он сделан и под десктоп и под мобильный браузер. У меня работет на Маке, айпаде, айфоне, андройде. Он просто масштабируется.

      Вот на HTC one:



  1. DeEF_NSK
    13.09.2016 09:23
    +1

    похоже на упрощеную версию blynk.cc, или я ошибаюсь?


    1. stDistarik
      13.09.2016 09:47

      Я не знаю что такое blynk.cc.


      1. stDistarik
        13.09.2016 10:02

        Если Вы про интерфейс, то идея перекочевала вот отсюда.


        1. DeEF_NSK
          13.09.2016 10:15

          я про всю идею вашего конструктора


          1. stDistarik
            13.09.2016 11:14

            Покажите конструктор blynk.cc


            1. stDistarik
              13.09.2016 14:40

              Починю свой констркктор и ознакомлюсь с blynk.cc, впрочем, если вас не затруднит, расскажите вкратце, как он работает и что нужно для установки на роутер или иной комп?


              1. stDistarik
                13.09.2016 14:58

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


    1. doom369
      13.09.2016 10:05

      похоже на упрощеную версию blynk.cc, или я ошибаюсь?


      Упрощенную и страшную.


      1. stDistarik
        13.09.2016 14:51
        -2

        Упрощенную и страшную.


        Что касается внешнего вида, то я написал, что интерфейс предельно простой. Ну, а если Вам страшно, то проходите мимо. Или Вы просто подлизнуть хотели?


        1. stDistarik
          13.09.2016 16:27
          -1

          del


        1. stDistarik
          14.09.2016 14:14
          -1

          Интерфейс не страшный, он просто стилизован под телевизионный пульт.


      1. stDistarik
        13.09.2016 16:27
        -1

        Достоиная однобокость.


        1. stDistarik
          14.09.2016 14:12
          -1

          Ещё кому-нибудь лизни, чтоб он то же минус поставил.)


          1. stDistarik
            14.09.2016 15:12

            Как дети маленькие. )


  1. Phenixoid
    13.09.2016 10:40

    Не прочел комментарии выше, но из беглого теста по Вашей ссылке установлено, что поля названий помещений неправильно отрабатывают названия из более чем 1 слова. Например: «Комната родителей».

    Допустим, на этапе вбивки названий помещений набираем:
    • 1. Зал
    • 2. Кухня
    • 3. Комната родителей
    • 4. Детская
    • 5. Коридор
    • 6. Туалет


    1. stDistarik
      13.09.2016 10:44

      Я забыл написать, что нужно вместо пробелов ставить нижнее_подчёркивание. Извиняюсь. Попробуйте ещё раз.


  1. stDistarik
    13.09.2016 11:05

    Похоже, что конструктор работает не правильно, где-то я забыл разделить пользователей и всё смешивается в кучу. Будк починять.


  1. stDistarik
    13.09.2016 18:34

    Конструктор я починил, пробуйте пожалуйста.


  1. stDistarik
    14.09.2016 16:09

    Буду очень благодарен, если кто-нибудь будет оставлять фидбеки.