Онлайн конструктор веб-интерфейса для управления Raspberry Pi
В предыдущей части (рекомендую ознакомиться), был описан онлайн конструктор создающий web-интерфейс для управления «умным домом» и скетч для ардуино, для работы на разных компьютерах и роутерах с прошивкой
Это онлайн конструктор web-интерфейса для управления GPIO на Raspberry
Система очень простая, она позволяет управлять по сети (внешней или локальной) пинами GPIO настроенными на выход и получать их статус (то есть видеть, что включено-выключено). Отправлять какие-либо команды, которые можно обрабатывать внутри Raspberry. Получать состояние пинов GPIO (в виде 0 или 1) настроенных на вход. Конструктор сам настроет нужные вам пины GPIO на вход или на выход.
Если нужна более гибкая система, то лучше воспользоваться предыдущей статьей.
Пользователю нужно только лишь собрать интерфейс в конструкторе, скачать архив с файлами и рапаковать его на своём
В архиве будут лежать готовые файлы относящихся к веб-части (html, css, js), sh-скрипт для инициализации пинов и сервер для обмена данными Home
Внешний вид
Как уже говорилось в предыдущей части, идея web-интерфейса достаточно проста и в чём-то даже аскетична. Связано это с тем, что мне разонравились нарисованные комнаты с лампочками и прочие полумеры, поэтому выбран стиль «пульта от телевизора».
Работает на любом устройстве — компьютере, ноутбуке, планшете, мобильнике.
Главный экран интерфейса. Помещений может быть до пяти.
На основном экране расположены кнопки с названиями помещений, нажатие на которые открывает панель с органами управления соответствующим помещением.
Откроем прихожую:
Здесь могут располагаться: несколько кнопок настроенных на выход (
Несколько кнопок (SENTSIG1 и т.д) для отправки любой команды в RaspberryPi.
И несколько полей (IN_BCM2 и т.д) для получения состояние пинов GPIO (в виде 0 или 1) настроенных на вход. 0 — пин подтянут к «земле», 1 — на пине есть напряжение.
В дальнейшем можно изменять названия кнопок и менять их местами.
рестик справа-сверху — закрытие панели.
Под кнопкой Info скрывается панель с информацией о статусе системы.
Надпись Connect! говорит о том, что всё хорошо, а Count update: — это просто счётчик запросов (браузер с интервалом в ~700мс запрашивает у ардуины данные). Интервал можно менять.
Если произойдёт какая-то неполадка, тогда на экране появится сообщение ERROR, а в Info будет описана причина ошибки.
Весь алгоритм работы системы описан в конце статьи.
Конструктор
В браузере должны быть включены cookie. Впрочем они и так почти у всех включены.
Конструктор предельно прост и интуитивно понятен . Открыв в соседней вкладке вот эту ссылку, вы окажитесь на первой странице (всего их четыре):
Чтоб понять и потренироваться, проделайте всё как написано ниже.
Здесь нужно выбрать количество помещений (максимум 5). Предположим, что у нас будет два помещения (прихожая и кухня), тогда выберите 2 и нажмите «Далее».
В дальнейшем Вы можете это исправить в файле index.html.
На следующей странице нужно придумать название вашего «умного дома» (это то, что будет написано на вкладке браузера) и вписать его в поле Название страницы.
В поля Адрес сервера и Порт сервера ничего писать не нужно (сделано на будущее).
Названия помещений у нас уже придуманы (прихожая и кухня), вписываем их и нажимаем кнопку «Далее»…
Здесь Вы увидите главный экран своего будущего интерфейса:
Нажмите на кнопку «Прихожая»…
Выберите две кнопки для включения чего-либо с возвратом статуса (Количество кнопок вкл/откл).
Одну кнопку для отправки команды (Количество кнопок отправки сигнала).
И одно поле для приёма статуса с каких-либо пинов (Количество полей для приёма информации).
Максимум можно выбрать по пять кнопок.
Теперь закройте панель кнопкой , проделайте то же самое с «Кухней» и нажмите кнопку «Далее»…
Появится главный экран с кнопкой «Скачать архив»:
На этом работа с конструктором закончена, нажмите и переходите к следующей части.
Подключение
Соберём простенькую схему для испытания системы:
Подключите светодиод к пину BCM 1, через резистор 500-1000 Ом. При нажатии в интерфейсе кнопки BCM1, светик будет загораться/гаснуть, а надпись менять цвет.
Так же подключите проводок через резистор 500-1000 Ом к BCM 3, этим проводочком можно будет тыкать на
BCM 2 используйте только на выход. Если сделали как вход
Применение кнопок SENTSIG описано ниже.
HomestDRp
Распаковав архив, у Вас появится папка — mydomrpXXXXXXXXXX, переименуйте её так, чтоб получилось mydomrp и перейдите в неё.
Переименуйте файлы indexXXXXXXXXX.html в index.html и initXXXXXXXXX.sh в init.sh.
В папке mydomrp получатся файлы index.html, init.sh, jquery.js и style.css и программа homestdrp.
Откройте файл index.html и в двенадцатой строчке — var flagobnov = 0, переправьте нолик на единичку — var flagobnov = 1
Дополнительные пояснения к файлам, даны в конце статьи.
Теперь к программе homestdrp…
homestdrp — это web-сервер, который принимает запросы от клиента, передаёт команды RaspberryPi, считывает состояние пинов GPIO и отправляет обратно информацию/статус web-клиенту. Иными словами, его назначение — это обмен данными между web-клиентом (браузер) и RaspberryPi. Работает по протоколу ТСР, а в перспективе и по UDP.
Исходник
#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>
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 BSIZ 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;
unsigned int PORTW = 0;
int len_dat = 0;
int count_warning_log =0;
void error_log(char *my_error)
{
char file_error_log[32] = {0,};
snprintf(file_error_log, 26, "%s", "/var/log/ErrorhomeRp.log");
time_t t;
time(&t);
FILE *f;
f = fopen(file_error_log, "a");
if(f == NULL)
{
printf("Error open /var/log/ErrorhomeRp.log.\n");
exit(0);
}
fprintf(f, "%s", ctime( &t));
fprintf(f, "Error %s\n\n", my_error);
printf("Error %s Write to /var/log/ErrorhomestdRp.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, 31, "%s", "/var/log/Warning_AccessRp.log");
if(count_warning_log > 100)
{
system("gzip -f /var/log/Warning_AccessRp.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_AccessRp.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_AccessRp.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);
}
int main(int argc, char *argv[])
{
if(argc != 4) error_log("not argumets.");
PORTW = strtoul(argv[1], NULL, 0); // порт для web-сервера 80
strncpy(patch_to_dir, argv[2], 63); // путь к папке
len_dat = atoi(argv[3]); // кол-во символов
warning_access_log("START");
////////////////////////////////////////////// WEB ///////////////////////////////////////
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.");
}
////////////////////////////////////////// work file //////////////////////////////////////////////////
char flin_fldb[64] = {0,};
snprintf(flin_fldb, (int)strlen(patch_to_dir) + (int)strlen("init.sh 1 "), "%s%s", patch_to_dir, "init.sh 1");
system(flin_fldb);
char bufRec[BSIZ] = {0,};
char To_GPIO[64] = {0,};
FILE *mf;
char in_data[64] = {0,};
char gp_file[32] = {0,};
printf("Receive data from RPi\n\n");
for(;;)
{
client_fd = accept(sock, (struct sockaddr *) &cli_addr, &sin_len);
if(client_fd == -1) continue;
memset(buffer, 0, BUFSIZE * sizeof(char));
read(client_fd, buffer, BUFSIZE - 1);
if((strstr(buffer, "file.db")) != NULL)
{
memset(in_data, 0, 256 * sizeof(char));
memset(bufRec, 0, BSIZ * sizeof(char));
int i = 0;
int ip = 0;
for(i = 0; i < len_dat; i++)
{
memset(gp_file, 0, 32 * sizeof(char));
snprintf(gp_file, 29, "%s%d%s", "/sys/class/gpio/gpio", i, "/value");
if(ip > 54)
{
in_data[ip] = '0';
ip++;
in_data[ip] = ' ';
ip++;
}
else
{
mf = fopen (gp_file, "r");
if(mf == NULL) error_log("gpio, fail last argument.");
in_data[ip] = getc(mf);
ip++;
in_data[ip] = ' ';
ip++;
fclose (mf);
}
}
printf("Data:%s\n", in_data);
int len_ara = (int)strlen(in_data) + 59;
snprintf(bufRec, len_ara, "%s%s", response_text, in_data);
write(client_fd, bufRec, len_ara - 1);
close(client_fd);
}
else if((strstr(buffer, "comanda")) != NULL) /////////// comand
{
memset(To_GPIO, 0, 64);
snprintf(To_GPIO, (int)strlen(patch_to_dir) + (int)strlen("init.sh 0 ") + 4, "%sinit.sh 0 %c%c%c", patch_to_dir, buffer[13], buffer[14], buffer[15]);
system(To_GPIO);
close(client_fd);
warning_access_log(buffer);
printf("To Gpio:%s\n", To_GPIO);
}
else if((strstr(buffer, "GET / ")) != NULL)
{
read_in_file("index.html");
int len_ara = count_simvol + (int)strlen(response) + 1;
memset(send2_array, 0, len_ara * sizeof(char));
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)
{
read_in_file("style.css");
int len_ara = count_simvol + (int)strlen(response_css) + 1;
memset(send2_array, 0, len_ara * sizeof(char));
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)
{
read_in_file("jquery.js");
int len_ara = count_simvol + (int)strlen(response_js) + 1;
memset(send2_array, 0, len_ara * sizeof(char));
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 homestdrp.c -o homestdrp
Теперь копируем папку mydomrp в любое удобное место на RaspberryPi, например в корень, — /mydomrp, делаем файлы homestdrp и init.sh исполняемым…
sudo chmod +x /mydomrp/homestdrp
sudo chmod +x /mydomrp/init.sh
И заспускаем программу с тремя параметрами:
sudo /mydomrp/homestdrp 80 /mydomrp/ 6
Позже, для автоматизации запуска, добавите эту команду в файл rc.local
nano /etc/rc.local
Вписать надо до строчки exit 0, вот так:
...
(/mydomrp/homestdrp 80 /mydomrp/ 6)&
exit 0
О параметрах:
Первый параметр — TCP порт. Порт можно указать любой, однако если у Вас больше нет никаких серверов занимающих стандартный (80) порт, то укажите его, ну в ежели занят, то напишите что-нибудь другое, например 82 (заходить в «умный дом» будете так — адрес:82).
Второй параметр — путь к папке /mydomrp/ (с обязательным слешом
Четвёртый параметр — количество данных посылаемых клиену
...
show();
setInterval(show,680);
function show(){
if(flagobnov == 1) {
$.ajax({
type: "POST",
url: "file.db",
timeout:560,
cache: false,
success: function(data){
var vars = data.split(" ");
if(vars.length == 6) ЭТА ЦИфРА
{
count_obnov++;
...
После успешного старта, homestdrp первым делом запускает скрипт init.sh
На ошибки — sh: echo: I/O error не обращайте внимания, так и должно быть. Ведь скрипт очищает GPIO которых и так нет.
Теперь открыв страничку в браузере, вы увидите в терминале различные сообщения и отправку считанных данных GPIO:
Все действия homestdrp, сопровождаются записью в файл /var/log/Warning_AccessRp.log, и туда же пишутся предупреждения.
В случае критической ошибки
Теперь, если остановить homestdrp (
Пояснения
Как пользоваться кнопкой SENTSIGx?
Открыв файл init.sh
...
104 )
# reaction to the button SENTSIG1
;;
109 )
# reaction to the button SENTSIG2
;;
...
Вот сюда то
То есть, если вместо "# reaction to the button SENTSIG1" написать reboot (без #), сохранить файл и нажать в интерфейсе кнопку SENTSIG1, то малинка перезагрузится, или если вместо "# reaction to the button SENTSIG2" написать apt-get update && apt-get upgrade, то обновится.
В общем можно делать всё, что душе угодно.
Пояснения к файлу index.html…
Браузер с интервалом 680мс запрашивает данные у RaspberryPi…
...
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>
...
Изменять названия полей для приёма данных (IN_BCM3, IN_BCM5 и т.д.) можно здесь:
...
$('#indata3').html('IN_BCM3' + ' ' + vars[3]);
...
Браузер постоянно запрашивает данные и тем самым создаёт трафик. Чтобы этого избежать, можно либо закрыть страницу, либо раскомментировать этот блок:
/*slmode++;
if(slmode > 70)
{
$(".pansl").show(300);
flagobnov = 0;
slmode = 0;
}*/
Тогда через ~минуту, страница будет закрываться полупрозрачной панелью и обновления остановятся. Клик на панель, уберёт её и обновления возобновяться.
После внесения изменений в index.html
Все вопросы и пожелания пишите в комментах!
На этом пока всё, в следующей части будут объединена работа с GPIO Raspberry Pi и ардуиной, а позже добавлен UDP клиент/сервер.
- +135
- stD
20245
Поддержать автора
Комментарии (5)