Онлайн-конструктор «умного дома»

в 19:40, , рубрики: diy или сделай сам, undefined, метки:

Онлайн-конструктор «умного дома» - 1

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

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

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

HomestD можно использовать на любом компьютере работающем под управлением Онлайн-конструктор «умного дома» - 2 Онлайн-конструктор «умного дома» - 3 Онлайн-конструктор «умного дома» - 4 или на роутере с прошивкой 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-интерфейса достаточно проста и минималистична.

Онлайн-конструктор «умного дома» - 5
Главный экран интерфейса.

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

Онлайн-конструктор «умного дома» - 6

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

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

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

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

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

Пример:

Онлайн-конструктор «умного дома» - 7

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

Онлайн-конструктор «умного дома» - 8

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

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

Онлайн-конструктор «умного дома» - 9

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

Конструктор

Конструктор прост и понятен.

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

Онлайн-конструктор «умного дома» - 10

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

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

Онлайн-конструктор «умного дома» - 11

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

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

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

Онлайн-конструктор «умного дома» - 12

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

Онлайн-конструктор «умного дома» - 13

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

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

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

Онлайн-конструктор «умного дома» - 14

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

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

Онлайн-конструктор «умного дома» - 15

Онлайн-конструктор «умного дома» - 16

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

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

HomestD

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

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

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

Онлайн-конструктор «умного дома» - 18 Откройте файл 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 OKrn"
"Content-Type: text/html; charset=UTF-8rnrn";

char response_css[] = "HTTP/1.1 200 OKrn"
"Content-Type: text/css; charset=UTF-8rnrn";

char response_js[] = "HTTP/1.1 200 OKrn"
"Content-Type: text/js; charset=UTF-8rnrn";

char response_text[] = "HTTP/1.1 200 OKrn"
"Content-Type: text/text; charset=UTF-8rnrn";

char response_403[] = "HTTP/1.1 200 OKrn"
"Content-Type: text/html; charset=UTF-8rnrn"
"<!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>rn";

#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.logn");
      exit(0);
    }

   fprintf(f, "%s", ctime( &t));
   fprintf(f, "%snn", my_error);
   printf("%snError 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, "%snn", war_ac);
   printf("%snWrite to %sAccess_Warning.log.nnn", 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 - %sn", 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: %snn", bRead);  
        continue;
      } 

     if(strcmp(bRead, str_iz_file)==0)
      { 
        printf("StrOK:%snn", 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:%snn", 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: %sn", 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.nn");
     }

    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.nn");
     }

    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.nn");
     }
 
    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*

Онлайн-конструктор «умного дома» - 19

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

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

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

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

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

Онлайн-конструктор «умного дома» - 20

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

Онлайн-конструктор «умного дома» - 21

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

Пояснения

К скетчу…

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

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

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

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

Данные, которые будут выводиться в полях для приёма каких-либо данных, нужно поместить в функцию «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(); }
...

Изменять названия кнопок (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.

Автор: stDistarik

Источник

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


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js