Защитите свой Arduino с помощью веб-разблокировки по шаблону — простое управление и удалённый контроль
Компоненты и материалы
| Arduino UNO | ×1 |
| PHPoC WiFi Shield для Arduino | ×1 |
| Сервопривод Seeed SG92R | ×1 |
| Перемычки | ×1 |
О проекте
Введение
Если вы новичок, рекомендую ознакомиться со следующими уроками:
- Arduino — работа с моторами
- Arduino — сервоприводы
- Arduino — WiFi
Вы, вероятно, знакомы с графическим ключом разблокировки на смартфоне. Теперь эту функцию можно реализовать на Arduino. Она предотвращает несанкционированный доступ к управлению и мониторингу платы.
Код этого проекта можно свободно адаптировать для других задач. Для наглядности в качестве примера используется управление сервоприводом.
Идея основана на двух проектах для платформы PHPoC:
- Веб-разблокировка по шаблону для IoT-устройств
- Управление сервоприводом через WebSocket с графическим интерфейсом
Я адаптировал их для Arduino.
Почему выбран PHPoC WiFi Shield
- Поддержка WebSocket — удобно для реального времени.
- Встроенный веб-сервер позволяет хранить веб-приложение непосредственно на шилде.
PHPoC Shield имеет встроенные веб-приложения для управления Arduino без знаний веб-программирования. Также он позволяет разработчикам создавать свои веб-приложения и загружать их на шилд.
Демонстрация
На рисунке показан интерфейс разблокировки и управления сервоприводом.
Поток данных
Веб-браузер ↔ PHPoC WiFi Shield ↔ Arduino
Принцип работы
Когда пользователь рисует шаблон в браузере, он преобразуется в строку и отправляется на Arduino через WebSocket (через PHPoC Shield).
Arduino сравнивает полученную строку с заданным в коде эталонным шаблоном. При совпадении отправляется код 202 (ACCEPTED), и переменная authenticated устанавливается в true. Иначе — код 401 (DENIED) и false.
При получении команды управления Arduino сначала проверяет флаг authenticated. Если true — выполняет действие, иначе — отправляет 401.
Отображение шаблона
Шаблон преобразуется в строку чисел от 1 до 9 (как на цифровой клавиатуре). Например, на изображении шаблон соответствует строке "1,4,8,6,3".
Установлен таймаут: после периода бездействия аутентификация сбрасывается, и пользователь должен ввести шаблон заново.
Исходный код состоит из двух файлов:
- ArduinoUnlockExample.ino — загружается в Arduino через Arduino IDE
- unlock.php — веб-приложение, загружается на PHPoC Shield через PHPoC Debugger
Что нужно сделать
- Настроить WiFi на PHPoC Shield (SSID и пароль)
- Загрузить веб-интерфейс на PHPoC Shield
- Написать код для Arduino
Настройка WiFi для PHPoC Shield
См. инструкцию.
Загрузка веб-интерфейса на PHPoC Shield
- Скачайте
unlock.php (в разделе кода). - Скачайте два изображения для управления сервоприводом (
servo_body.pngиservo_bracket.png). - Загрузите их на PHPoC Shield через PHPoC Debugger (инструкция). Важно: не удаляйте существующие файлы на шилде.
Написание кода для Arduino
- Установите библиотеку PHPoC для Arduino в Arduino IDE (инструкция).
- Используйте исходный код из раздела кода.
- Скомпилируйте и загрузите в Arduino через Arduino IDE.
Запуск и тест
- Откройте монитор порта в Arduino IDE — там будет выведен IP-адрес.
- В браузере перейдите по адресу
https://<IP-адрес>/unlock.php - Нажмите кнопку Connect и проверьте работу.
Рекомендуемый стартовый набор Arduino для начинающих
Если ищете набор, посмотрите Лучший стартовый набор Arduino для новичков.
Справочник функций
- Библиотека Servo:
attach(),write(),writeMicroseconds(),read(),attached(),detach() - Serial:
begin(),println() delay(),millis()- Циклы:
for,while - Условия:
if else - Основные функции:
loop(),setup() - Работа со строками:
toInt(),substring(),indexOf(),remove(),equals()
Код
unlock.php (веб-интерфейс)
<!DOCTYPE html>
<html>
<head>
<title>Arduino - PHPoC Shield</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=0.7">
<meta charset="utf-8">
<style>
body { text-align: center; font-size: width/2pt; }
h1, h2, button { font-weight: bold; font-size: width/2pt; }
</style>
<script>
var CMD_AUTH = 0, CMD_CTRL = 1;
var ws, authorized = false;
var unlock_width = 400, unlock_height = 400;
var unlock_inner_radius = 14, unlock_middle_radius = 22, unlock_outer_radius = 34;
var unlock_gap = 140;
var unlock_touch_state = 0, unlock_touch_x = 0, unlock_touch_y = 0;
var unlock_touch_list = [], unlock_ratio = 1;
var servo_width = 401, servo_height = 466;
var servo_pivot_x = 200, servo_pivot_y = 200;
var servo_bracket_radius = 160, servo_bracket_angle = 0;
var servo_bracket_img = new Image();
var servo_click_state = 0, servo_last_angle = 0;
var servo_mouse_xyra = {x:0, y:0, r:0.0, a:0.0};
servo_bracket_img.src = "servo_bracket.png";
function init() {
var unlock = document.getElementById("unlock");
unlock.width = unlock_width; unlock.height = unlock_height;
["touchstart","touchend","touchmove","mousedown","mouseup","mousemove"].forEach(function(evt) {
unlock.addEventListener(evt, window["unlock_"+evt.replace("touch","mouse")]);
});
var ctx = unlock.getContext("2d");
ctx.translate(unlock_width/2, unlock_height/2);
ctx.shadowBlur = 20; ctx.shadowColor = "LightGray";
ctx.lineCap = "round"; ctx.lineJoin = "round";
var servo = document.getElementById("servo");
servo.width = servo_width; servo.height = servo_height;
servo.style.backgroundImage = "url('/servo_body.png')";
["touchstart","touchend","touchmove","mousedown","mouseup","mousemove"].forEach(function(evt) {
servo.addEventListener(evt, window["servo_"+evt.replace("touch","mouse")]);
});
ctx = servo.getContext("2d");
ctx.translate(servo_pivot_x, servo_pivot_y);
update_view();
}
function connect_onclick() {
if(ws == null) {
var ws_host_addr = "";
if((navigator.platform.indexOf("Win") != -1) && (ws_host_addr.charAt(0) == "[")) {
ws_host_addr = ws_host_addr.replace(/[\[\]]/g, '').replace(/:/g, "-") + ".ipv6-literal.net";
}
ws = new WebSocket("ws://" + ws_host_addr + "/web_pattern", "text.phpoc");
document.getElementById("ws_state").innerHTML = "CONNECTING";
ws.onopen = ws_onopen;
ws.onclose = ws_onclose;
ws.onmessage = ws_onmessage;
} else ws.close();
}
function ws_onopen() {
document.getElementById("ws_state").innerHTML = "<font color='blue'>CONNECTED</font>";
document.getElementById("bt_connect").innerHTML = "Disconnect";
update_view();
}
function ws_onclose() {
document.getElementById("ws_state").innerHTML = "<font color='gray'>CLOSED</font>";
document.getElementById("bt_connect").innerHTML = "Connect";
ws.onopen = ws.onclose = ws.onmessage = null; ws = null;
authorized = false; update_view();
}
function ws_onmessage(e_msg) {
var resp = parseInt((e_msg || window.event).data);
if(resp == 202) authorized = true;
else if(resp == 401) authorized = false;
else console.log("unknown:"+resp);
update_view();
}
function update_view() {
authorized ? servo_update_view() : unlock_update_view();
}
function unlock_update_view() {
document.body.style.backgroundColor = "black";
document.body.style.color = "white";
document.getElementById('unlock_area').style.display = 'block';
document.getElementById('control_area').style.display = 'none';
var ctx = document.getElementById("unlock").getContext("2d");
ctx.clearRect(-unlock_width/2, -unlock_height/2, unlock_width, unlock_height);
ctx.lineWidth = 10; ctx.strokeStyle = "white"; ctx.globalAlpha = 1;
ctx.beginPath();
for(var i=0;i max_r) return false;
if(rc_y < -width || rc_y > width) return false;
return true;
}
function servo_mouse_down() {
if(event.touches && event.touches.length>1) servo_click_state = event.touches.length;
if(servo_click_state>1) return;
if(check_range_xyra(event, servo_mouse_xyra)) { servo_click_state=1; servo_last_angle = servo_mouse_xyra.a/Math.PI*180.0; }
}
function servo_mouse_up() { servo_click_state=0; }
function servo_mouse_move() {
if(event.touches && event.touches.length>1) servo_click_state = event.touches.length;
if(servo_click_state>1) return; if(!servo_click_state) return;
if(!check_range_xyra(event, servo_mouse_xyra)) { servo_click_state=0; return; }
var angle = servo_mouse_xyra.a/Math.PI*180.0;
if(Math.abs(angle)>90 && angle*servo_last_angle<0) {
servo_last_angle = (servo_last_angle>0) ? -180 : 180;
}
servo_bracket_angle += (servo_last_angle - angle);
servo_last_angle = angle;
if(servo_bracket_angle > 90) servo_bracket_angle = 90;
if(servo_bracket_angle < -90) servo_bracket_angle = -90;
servo_update_view();
send_to_Arduino(CMD_CTRL, Math.floor(servo_bracket_angle));
document.getElementById("debug").innerHTML = Math.floor(servo_bracket_angle);
event.preventDefault();
}
function send_to_Arduino(cmd, data) {
if(ws.readyState == 1) ws.send(cmd + ":" + data + "\r\n");
}
window.onload = init;
</script>
</head>
<body>
<h1>Arduino - Web Pattern Unlock</h1>
<div id="unlock_area" style="display:block;"><canvas id="unlock"></canvas></div>
<div id="control_area" style="display:none;"><canvas id="servo"></canvas><p>Angle: <span id="debug">0</span></p></div>
<h2>WebSocket: <span id="ws_state">null</span> <button id="bt_connect" onclick="connect_onclick();">Connect</button></h2>
</body>
</html>
ArduinoUnlockExample.ino
/* Arduino Web Server - Pattern Unlock */
#include "SPI.h"
#include "Phpoc.h"
#include
#define CMD_AUTH 0
#define CMD_CTRL 1
#define ACCEPTED "202"
#define UNAUTHORIZED "401"
PhpocServer server(80);
Servo servo;
String pattern;
bool authenticated;
unsigned long timeout = 10000;
unsigned long lastActiveTime = 0;
void setup() {
Serial.begin(9600);
while(!Serial);
Phpoc.begin(PF_LOG_SPI | PF_LOG_NET);
server.beginWebSocket("web_pattern");
Serial.print("WebSocket server address: ");
Serial.println(Phpoc.localIP());
servo.attach(8);
servo.write(90);
pattern = "1,4,8,6,3";
authenticated = false;
lastActiveTime = 0;
}
void loop() {
PhpocClient client = server.available();
if (client) {
String data = client.readLine();
if(data) {
int pos = data.indexOf(':');
int cmd = data.substring(0, pos).toInt();
if(cmd == CMD_AUTH) {
String reqPattern = data.substring(pos+1);
reqPattern.remove(reqPattern.indexOf(13));
reqPattern.remove(reqPattern.indexOf(10));
if(pattern.equals(reqPattern)) {
authenticated = true;
server.write(ACCEPTED, 3);
lastActiveTime = millis();
} else {
authenticated = false;
server.write(UNAUTHORIZED, 3);
}
} else if(cmd == CMD_CTRL) {
if(authenticated) {
int angle = data.substring(pos+1).toInt();
angle = map(angle, 90, -90, 0, 180);
servo.write(angle);
lastActiveTime = millis();
Serial.println(angle);
} else {
server.write(UNAUTHORIZED, 3);
}
}
}
}
if (authenticated && (millis() - lastActiveTime > timeout)) {
authenticated = false;
server.write(UNAUTHORIZED, 3);
}
}
Схема подключения
Схема показывает подключение PHPoC WiFi Shield к Arduino UNO и сервопривода к выводу 8.
Производственный процесс
- Эволюция и производство массовых выкроек одежды
- Веб-управляемая DMX-система освещения: Arduino Mega как мастер-контроллер
- Сборка Bluetooth-робота-шпиона на Arduino с управлением с телефона
- FlickMote: жестовое IR-пульт для умного дома на Arduino 101 и ультразвуковом датчике
- DIY Arduino TV-B-Gone: универсальный пульт для выключения любого телевизора
- Создание пользовательских LED-часов с будильником — видно с 12 метров
- Find Me: Умный поисковик вещей на Arduino и Bluetooth
- Оптимизированные решения питания для проектов на Arduino
- Крестики-нолики на Arduino с матрицей MAX7219 и картонным корпусом
- Пошаговое руководство: сборка умного четырёхногого робота на Arduino