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

в 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