Умный дом "HomestD" с онлайн конструктором




Онлайн конструктор создающий web-интерфейс для управления «умным домом» и скетч для ардуино.




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

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


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

Общий размер файлов составляет около 200кБ, так что для работы на роутере не потребуются дополнительные накопители (флешка, sd-карта). Ресурсоёмкость составляет 3-5% CPU (роутер mr3020).


Несмотря на, может быть, покажущуюся сложность, все очень просто и займёт минут 10-15.


Далее обо всём подробно...




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


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

Про подключение к UARTу на RaspberryPi, читайте в конце этой статьи, в главе UART.




Внешний вид


Ознакомительная часть.


Идея web-интерфейса достаточно проста и в чём-то даже аскетична. Связано это с тем, что мне разонравились нарисованные комнаты с лампочками и прочие полумеры, поэтому выбран стиль «пульта от телевизора».

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


Работает на любом устройстве (компьютере, ноутбуке, планшете, мобильнике) имеющем браузер.






homestd

Главный экран интерфейса. Помещений может быть до десяти.



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

Откроем прихожую:

homestd

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

Несколько кнопок для отправки сигнала (SENTSIG1 и т.д) не требующего подтверждения. Например пульнуть сигнал с ИК-передатчика (если таковой имеется) или увеличить/уменьшить значение диммера.

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

рестик справа-сверху — закрытие панели.

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



Пример:


homestd



Под кнопкой Info скрывается панель с информацией о статусе системы.




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



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


Error homestd


На мобильном устройстве:





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

Конец ознакомительной части.




Конструктор


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


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

В браузере должны быть включены 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 (отключено в конструкторе). Сохраните файл.

Дополнительные пояснения к скетчу и файлу index.html, можно с лёгкостью найти в конце статьи.


Идём далее…

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


У Вас получится вот такая программа:

Скетч

#include "CyberLib.h"

char array_in[5] = {0,};
int cod_comand = 0;
unsigned long timme; 

byte d2 = 0;
byte d3 = 0;


void setup()
 {
   timme = millis(); 
   Serial.begin(57600);
   D2_Out;
   D2_Low;
   D3_Out;
   D3_Low;

 }

void loop() 
{   
  if (Serial.available()>3) // приём команд от сервера
   {
    if (Serial.read()=='Y')
     {
       for (byte i=0; i < 3; i++)
        {
           array_in[i] = Serial.read(); 
        } 
      
       if((array_in[0] =='+') && (array_in[1] =='=') && (array_in[2] =='Z')) 
        {
         int i = 0;
         delay_ms(1);

         memset(array_in, 0, sizeof(array_in));
       
         while(Serial.available())
           {
              delay_ms(1);
              array_in[i++] = Serial.read(); 
              if(array_in[i] == '\r' || array_in[i] == '\n')
                {
                  array_in[i] = 0;
                  break;
                }
           }

         sscanf(array_in, "%d",&cod_comand);
         memset(array_in, 0, sizeof(array_in));
    
         switch(cod_comand) 
           {
		case 100:
		D2_High;
		d2 = 1;
		clear_port();
		break;

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

		case 103:
		D3_High;
		d3 = 1;
		clear_port();
		break;

		case 104:
		D3_Low;
		d3 = 0;
		clear_port();
		break;


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

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


                default:
                clear_port();
                break;
           }          
       }
   
      else 
       {
         clear_port();
       }       
   }   
 }  


 if((millis()-timme) > 440) // интервал отправки данных  
   { 
     timme = millis();
     trans();  
   }   
} //END loop


void clear_port() // очистка буфера
 {
   for(int i=0; i < 64; i++) 
    {
      Serial.read();    
    } 
 }


void trans() // отправка данных серверу 
 {
   Serial.print('A'); 
   Serial.print(' ');   
   Serial.print(0);
   Serial.print(' '); 
   Serial.print(d2);
   Serial.print(' ');
   Serial.print(0); // INDATA3
   Serial.print(' ');
   Serial.print(d3);
   Serial.print(' ');
   Serial.print(0); // INDATA5
   Serial.print(' ');
   Serial.print('Z');   
   Serial.print('\n');  
 }




Далее идёт пример с подключением лампочек и датчиков, если сейчас возится с этим лень, то можно пропустить эту часть и перейти сразу к запуску интерфейса. Позже вернётесь и прочтёте.



Программа предполагает две лампочки с возвратом статуса, два сигнала и два поля приёма информации.

В примере будет использованы: четыре LED-диода (два для прихожей и два для кухни) и два датчика DHT22.

Прихожая — первый лед подключаем к пину D2 (это свет с возвратом статуса), второй лед поключаем к любому цифровому пину (кроме D3, он у нас зарезервирован для кухни), например к D4, а DHT22 подключаем к D5.

Кухня — первый лед подключаем к пину D3 (это свет с возвратом статуса), второй лед поключаем, опять же, к любому цифровому пину (кроме D2, D4 и D5, они у нас уже заняты), например к D6, а DHT22 подключаем к D7.


Схема подключения будет такова:


Вместо лед-диодов и датчиков DHT может быть что угодно.


Теперь скачайте и установите библиотеку stDHT для использования нескольких датчиков DHT (здесь про неё можно почитать) и отредактируйте сформированный скетч вот так (пояснения ниже):

#include "CyberLib.h"
#include "stDHT.h"
DHT sens(DHT22); // Указать датчик DHT11, DHT21 или DHT22. (несколько датчиков вписывать не нужно)
// Подключать можно только одинаковые датчики, то есть нельзя использовать одновременно DHT11 и DHT22

char array_in[5] = {0,};
int cod_comand = 0;
unsigned long timme; 

byte d2 = 0;
byte d3 = 0;

int count = 0; // Добавили
int t = 0; // Добавили
int t2 = 0; // Добавили

void setup()
 {
   timme = millis(); 
   Serial.begin(57600);
   D2_Out;
   D2_Low;
   D3_Out;
   D3_Low;

   D4_Out; // Добавили для сигнала SENTSIG1
   D4_Low; 
   D6_Out; // Добавили для сигнала SENTSIG2
   D6_Low; 

 }


void loop() 
{   
  if (Serial.available()>3) // приём команд от сервера
   {
    if (Serial.read()=='Y')
     {
       for (byte i=0; i < 3; i++)
        {
           array_in[i] = Serial.read(); 
        } 
      
       if((array_in[0] =='+') && (array_in[1] =='=') && (array_in[2] =='Z')) 
        {
         int i = 0;
         delay_ms(1);

         memset(array_in, 0, sizeof(array_in));
       
         while(Serial.available())
           {
              delay_ms(1);
              array_in[i++] = Serial.read(); 
              if(array_in[i] == '\r' || array_in[i] == '\n')
                {
                  array_in[i] = 0;
                  break;
                }
           }

         sscanf(array_in, "%d",&cod_comand);
         memset(array_in, 0, sizeof(array_in));
    
         switch(cod_comand) 
           {
                case 100:
                D2_High;
                d2 = 1;
                clear_port();
                break;

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

                case 103:
                D3_High;
                d3 = 1;
                clear_port();
                break;

                case 104:
                D3_Low;
                d3 = 0;
                clear_port();
                break;



                case 102:
                // какая-то реакция на кнопку SENTSIG1
                D4_High; // добавили
                delay(200); // добавили
                D4_Low; // добавили
                clear_port();
                break;

                case 105:
                // какая-то реакция на кнопку SENTSIG2
                D6_High; // добавили
                delay(200); // добавили
                D6_Low; // добавили
                clear_port();
                break;


                default:
                clear_port();
                break;
           }          
       }
   
      else 
       {
         clear_port();
       }       
   }   
 }  


 if((millis()-timme) > 440) // интервал отправки данных  
   { 
     timme = millis();

     // Добавили
     count++; 
     if(count == 5) 
      {
        t = sens.readTemperature(5); // чтение датчика на пине 5
      }

     if(count > 9) 
      {
        t2 = sens.readTemperature(7); // чтение датчика на пине 7
        count = 0;
      }
     ////////////

     trans();  
   }   
} //END loop


void clear_port() // очистка буфера
 {
   for(int i=0; i < 64; i++) 
    {
      Serial.read();    
    } 
 }


void trans() // отправка данных серверу 
 {
   Serial.print('A'); 
   Serial.print(' ');   
   Serial.print(0);
   Serial.print(' '); 
   Serial.print(d2);
   Serial.print(' ');
   Serial.print(t); // INDATA3
   Serial.print(' ');
   Serial.print(d3);
   Serial.print(' ');
   Serial.print(t2); // INDATA5
   Serial.print(' ');
   Serial.print('Z');   
   Serial.print('\n');  
 }




•В самом начале скетча добавили счётчик, он понадобится для работы с датчиками DHT (об этом ниже) и две переменные, в которые будет записываться температура:

...
int count = 0;
int t = 0;
int t2 = 0;
...




•Для включения, отключения D2 и D3 всё уже сделано.



•Для отсылки сигналов SENTSIG1 и SENTSIG2, в функцию void setup(), добавили инициализацию пинов на выход и подтянули их к «земле» внутренним резистором:

...
   D4_Out; // Добавили для сигнала SENTSIG1 
   D4_Low; //
   D6_Out; // Добавили для сигнала SENTSIG2
   D6_Low; //
...


А для выполнения действия (будут кратковременно вспыхивать лед-диоды D4 и D6), в функции switch(cod_comand), в case 102: и case 105:, добавили соответствующий код:

...
   case 102:
   // какая-то реакция на кнопку SENTSIG1
   D4_High; // добавили
   delay(200); // добавили
   D4_Low; // добавили
   clear_port();
   break;

   case 105:
   // какая-то реакция на кнопку SENTSIG2
   D6_High; // добавили
   delay(200); // добавили
   D6_Low; // добавили
   clear_port();
   break;




•Считывание показаний датчиков DHT (считываться будет только тепература), помещено в функцию if((millis()-timme) > 440).

Интервал между чтением датчиков, должен быть не меньше двух секунд, поэтому применён следующий код:

if((millis()-timme) > 440) // интервал отправки данных  
   { 
     timme = millis();
     count++;
     if(count == 5) 
      {
        t = sens.readTemperature(5); // чтение датчика на пине 5
      }

     if(count > 9) 
      {
        t2 = sens.readTemperature(7); // чтение датчика на пине 7
        count = 0;
      }

     trans();  
   } 

Первый датчик будет считываться каждые 2200мс, а второй каждые 4400мс.



•Данные (t и t2) с датчиков помещены в функцию void trans(), в соответствующие строки (INDATA3 и INDATA5):

void trans() // отправка данных серверу 
 {
   Serial.print('A'); 
   Serial.print(' ');   
   Serial.print(0);
   Serial.print(' '); 
   Serial.print(d2);
   Serial.print(' ');
   Serial.print(t); // INDATA3
   Serial.print(' ');
   Serial.print(d3);
   Serial.print(' ');
   Serial.print(t2); // INDATA5
   Serial.print(' ');
   Serial.print('Z');   
   Serial.print('\n');  
 }







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

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

•Для обычного компьютера (linux).

•Для OSX.

•Для RaspberryPi.

•Для роутера MR3020 (Atheros).

•Для роутера ASUS WL500gp2 (Broadcom).

•Для роутера Asus RT-N13 (Ralink).



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>

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

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";

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;
int count_warning_log =0;

void error_log(char *my_error) 
{  
   char file_error_log[32] = {0,};
   snprintf(file_error_log, 27, "%s", "/var/log/Errorhomestd.log");

   time_t t;
   time(&t);
   FILE *f;
   f = fopen(file_error_log, "a"); 

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

   fprintf(f, "%s", ctime( &t));
   fprintf(f, "Error %s\n\n", my_error);
   printf("Error %s Write to /var/log/Errorhomestd.log.\n", my_error);
   fclose(f);
   exit(0);
}


void warning_access_log(char *war_ac) 
{  
   count_warning_log++;

   char file_Warning_Access_log[32] = {0,};
   snprintf(file_Warning_Access_log, 29, "%s", "/var/log/Warning_Access.log");

   if(count_warning_log > 100)
     {
       system("gzip -f /var/log/Warning_Access.log");
       count_warning_log =0;
       time_t t;
       time(&t);
       FILE *f;
       f = fopen(file_Warning_Access_log, "w"); 
       fprintf(f, "%s", ctime( &t));
       fprintf(f, "%s\n\n", war_ac);
       printf("Write to /var/log/Warning_Access.log:\n%s\n", war_ac);
       fclose(f);
     }

    else
     {
       time_t t;
       time(&t);
       FILE *f;
       f = fopen(file_Warning_Access_log, "a"); 
       fprintf(f, "%s", ctime( &t));
       fprintf(f, "%s\n\n", war_ac);
       printf("Write to /var/log/Warning_Access.log:\n%s\n", war_ac);
       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("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)
    {
      FILE *f;
      f = fopen("/tmp/file.db", "w");
      fprintf(f, "%s", db_error);
      fclose(f);
      printf("Write to /tmp/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)
         {
           FILE *f;
           f = fopen("/tmp/file.db", "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;
 } 


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("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("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("creating thread.");

  int pt2 = 1; 
  pthread_t counterr_thread;
  int result2 = pthread_create(&counterr_thread, NULL, &thread2_func, &pt2); 
  if(result2 != 0) error_log("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("bind.");
   }
 
  if(listen(sock, 10) == -1) 
   {
     close(sock);
     error_log("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
// /mydom/homestd /dev/ttyUSB0 57600 80 /mydom/ 0
//  make package/homestd/compile V=s





Вернёмся к делу. Подключаем ардуину, копируем папку mydom в любое удобное место на целевом устройстве, например в корень, — /mydom, делаем файл homestd исполняемым…

sudo chmod +x /mydom/homestd


… и запускаем командой с пятью параметрами:

sudo /mydom/homestd /dev/ttyUSB0 57600 80 /mydom/ 0


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


Позже, для автоматизации запуска, добавите эту команду в файл rc.local

nano /etc/rc.local


Вписать надо до строчки exit 0, вот так:

...

(/mydom/homestd /dev/ttyUSB0 57600 80 /mydom/ 0)&

exit 0





О параметрах:

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

ls /dev/tty*




Второй параметр57600, скорость «сом»-порта. (4800, 9600, 19200, 38400, 57600, 115200)

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

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

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



Все действия homestd, сопровождаются записью в файл Access_Warning.log, и туда же пишутся не критиченые ошибки. Так что если прога робит некорректно, то глядите в него (лежит в папке /var/log/Warning_Access.log).





О скверных ошибках сообщается в файл Errorhomestd.log (появляется в папке /var/log/Errorhomestd.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;
    }*/


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



После внесения изменений в index.html обязательно обновите страничку в браузере.


Все вопросы и гневные послания пишите в комментах!





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


  • 0
  • 6560

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

0
Здравствуйте Дмитрий!
Вот и сюда, в конструктор, я добрался. Дмитрий подскажите как можно совместить файлы из этой темы с apache2+nginx из вашей предыдущей темы. Апач настроен на 85 порт (Listen localhost:85). В директории /var/wwww создал /mydom, скопировал в нее файлы, необходимые для работы сайта. homestd положил в /opt скрипт запуска в rc.local:(/opt/homestd /dev/ttyS0 57600 8080 /var/www/mydom/ 1)& Если в браузере захожу на страницу mydom через апач, ну или nginx, как правильно сказать не знаю, то страница загружается, но нет соединения с ардуиной и никакие данные не обновляются. Если ввожу адрес с портом 8080, как в скрипте, то связь с ардуиной налажена. Я что-то делаю не так? или такую работу сайта на апаче нельзя организовать с сервером homestd.
0
Упс. Вопрос снимаю. Странно, вчера всё настроил, но данные не получал. Сейчас захожу и всё работает. Прям мистика какая-то.
0
Мистики не бывает, если что-то не так, значит что-то не так сделано. Homestd не нуждается в каких-либо серверах, он сам сервер и работает на заданном порту из указанной папки.
0
Добрый день!
Я так понял homestd и веб-страничка тесно связаны между собой. Хотелось бы понять логику этой связи. На данный момент мой умный дом работает на вашем примере с PHP со своими доработками. Все, в принципе устраивает, но хотелось бы ведение логов. В этом проекте ведение логов можно включить, но нет той прозрачности и настройки под пользователя, которая была там. Или просто это уже гораздо дальше от моего понимания.
В общем на данный момент у меня имеется управление освещением и температурой в частном доме. Между ардуиной и веб-интерфейсом передаются данные о температуре в каждой комнате и на улице, заданной температурой, статусе выключателей (вкл/выкл), и значениями атмосферного давления для графика. Обратно уходят данные об изменении температуры и управлении светом. Строка такая: 0,0,0,26,26,26,28,26,-75,255,273,253,255,259,722.6,722.4,722.2,722.0,721.5,1,1. Далее страничка все переводит в приемлимый вид и раскладывает куда надо. Имеются основная страница и страница для мобильных устройств. Вот так выглядит страничка для телефонов: 1
Так же имеется страница настроек умного дома с настройками СМС-уведомлений и задержкой выключения света.
В общем, хотелось бы, чтобы ко всему этому добавилась возможность вести логи. Графики температуры, статистику использования выключателей и т.п., а как это реализовать с помощью схемы PHP — lighttpd, я придумать не могу, так же, как не могу понять как совместить homestd с моим проектом…
0
Я так понял homestd и веб-страничка тесно связаны между собой. Хотелось бы понять логику этой связи.

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



Все, в принципе устраивает, но хотелось бы ведение логов

Про какие логи Вы говорите?
Не совсем понимаю Ваших намерений.
0
Идея ведения логов пришла ко мне, когда после подключения RFID считывателя самопроизвольно в любой момент времени могло произойти включение сигнализации. То есть словно кто-то поднес запрограммированный в контроллере брелок. Приходилось отлавливать такие моменты подключая ардуину к компу и смотреть, что валится в терминал. А это, мягко говоря, неудобно.
То есть под логами я понимаю что-то, что шлет ардуино в терминал в ответ на какие-либо действия. Включил свет в комнате с выключателя — запись в лог, включил сигнализцию — запись, сигналка сработала — снова запись. И так далее. Плюс к этому ведение журналов изменения температуры в комнатах и на улице и давления. Ну и всего, чего угодно в будущем.
Как я себе это представляю: есть некая программа — сервер на OpenWRT, которая слушает порт и, в зависимости от заданных параметров, либо записывает данные в файлы, либо передает их на веб-страницу. Причем без всякой привязке к количеству или типу этих данных. То есть просто приняла — передала. Так же и в обратном направлении. Принимает данные от странички и передает их в порт.
Реализацию представляю примерно такую: ардуино в начале каждого пакета данных ставит тег — например #l — данные лога — пишутся в лог файл; #w — данные для передачи на веб-страничку; #j — журнал (изменение температуры). Возможно лог и журнал можно объединить.
Программа-сервер считывает тег и раскладывает данные по нужным направлениям. В общем, как-то так.
0
Часть этого уже реализовано.
Пятый параметр — может быть 0 или 1. Если указать 1, тогда в папке /tmp/ будет создаваться текстовый файл file.db, в который будут записываться данные полученые от ардуины. Это сделано для того, чтоб можно было забирать эти данные и заносить их куда-либо, например в базу.

Остаётся только написать скрипт, который скажем каждую секунду копирует данные из file.db в ваш журнал.
0
Я это видел. Почему я и задал вопрос: насколько тесно Homestd связан с веб-страничкой? А если у меня их три? И как минимум еще одна планируется в будущем? Плюс дальнейшее расширение функционала. Для меня все упирается как раз в программу-сервер, потому как я не программист и написание такой программы для меня невозможно. Тем более под OpenWRT… А то, что я описал выше — продукт универсальный. Он работает просто как ворота между ардуино и веб-интерфейсом, обладая полезной функцией записи данных в файлы. И ему без разницы какой функционал выполняет контроллер, что он передает в веб-интерфейс и как эти данные обрабатывает страничка. Он просто принимает и, в зависимости от условий, передает дальше.
0
Homestd тесно связан с ардуиной, он берёт/посылает из/в неё данные. Так же homestd рассчитан для отдачи файлов index.html, jquery.js и style.css, на любой другой запрос отдаст 403.
0
У Вас и сейчас есть файл file.db, из него и берите данные.
0
Файл — это да. Можно брать данные и оттуда, вот только для этих целей вовсе не обязательно использовать homestd, можно использовать cat /dev/ttyUSB0 > /tmp/arduino.dat
Я же говорю о сервере.
0
Добрый день!
Подскажите в ардуине можно использовать все 14 digital и 6 analog пинов?
И второй вопрос если я подключаю распберри к ардуине по UART то надо писать эту строчку
sudo /mydom/homestd /dev/ttyUSB0 57600 80 /mydom/ 0 я так понял тут прописано подключение по USB
0
Подскажите в ардуине можно использовать все 14 digital и 6 analog пинов?
Да.

И второй вопрос если я подключаю распберри к ардуине по UART то надо писать эту строчку
В статье дана ссылка.

«Уарт в Raspberry представляется как устройство /dev/ttyAMA0»
0
sudo /mydom/homestd /dev/ttyAMA0 57600 80 /mydom/ 0
так я правильно понял
0
Подскажите, а можно прикрутить MQTT протокол, чтобы с ESP8266 можно было общаться роутеру?
0
Подскажите как можно сделать аутентификацию на WEB сервере homestd ???
0
Никак, пользуйте VPN.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.