Amankan Arduino dengan Pola Unlock Web – Pengaturan Mudah dan Kendali Jarak Jauh
Komponen dan Perlengkapan
Arduino UNO – 1 unit
PHPoC WiFi Shield for Arduino – 1 unit
Seeed Servo Motor SG92R – 1 unit
Kabel jumper – 1 set
Tentang Proyek Ini
Pendahuluan
Jika Anda pemula, saya merekomendasikan membaca tutorial berikut:
Arduino - Motor
Arduino - Servo Motor
Arduino - Wifi
Anda mungkin familiar dengan pola unlock saat mengakses ponsel Anda. Sekarang fitur ini tersedia di Arduino. Ini mencegah orang tidak berwenang untuk mengontrol/memantau Arduino.
Pengguna bebas menggunakan ulang kode dalam proyek ini untuk aplikasi lain. Untuk kesederhanaan, saya mengambil kontrol servo motor sebagai contoh.
Ide ini berasal dari dua proyek, yang ditulis untuk platform PHPoC:
Dalam proyek ini, saya menggunakan PHPoC WiFi Shield untuk menghubungkan Arduino ke Internet karena:
PHPoC Shield mendukung websocket, yang nyaman untuk proyek ini.
PHPoC Shield memiliki web server embedded khusus, yang memungkinkan menyimpan aplikasi web embedded pada shield.
PHPoC Shield memiliki beberapa aplikasi web bawaan yang memungkinkan pengguna menggunakan aplikasi web embedded untuk mengontrol/memantau Arduino tanpa memerlukan pengetahuan pemrograman web.
Juga, PHPoC shield memungkinkan pengguna yang dapat memprogram aplikasi web untuk mengembangkan aplikasi web mereka sendiri dan menyimpannya pada PHPoC Shield.
Demonstrasi
Alur Data
Web browser <---> PHPoC WiFi Shield <---> Arduino
Cara Kerja
Ketika pengguna menggambar pola di web browser, pola dipetakan ke dalam string. String pola ini dikirim ke Arduino melalui WebSocket (melalui PHPoC Shield).
Ketika Arduino menerima string pola yang diterima, itu akan membandingkan string yang diterima dengan string pola yang hard-coded di Arduino. Jika cocok, Arduino mengirim kode ACCEPTED kembali ke klien (Web browser) dan mengatur variabel terautentikasi ke true. Jika tidak, Arduino mengirim kode DENIED ke klien dan mengatur variabel terautentikasi ke false.
Ketika Arduino menerima perintah kontrol dari pengguna, itu akan memeriksa nilai variabel terautentikasi terlebih dahulu. Jika nilainya true, Ini melakukan tugas yang sesuai dengan perintah. Jika nilainya false, Ini mengirim kode DENIED ke klien.
Pemetaan Pola
Pola akan dipetakan ke dalam string. Misalnya, pada gambar di atas, string pola adalah "1, 4, 8, 6, 3".
Sebuah timeout diatur. Setelah sekian waktu, jika pengguna tidak memiliki aktivitas apa pun, otentikasi kedaluwarsa, pengguna perlu memasukkan pola lagi untuk membuka kunci Arduino.
Kode sumber mencakup dua file:
ArduinoUnlockExample.ino: dikompilasi dan diunggah ke Arduino melalui Arduino IDE
unlock.php: ini adalah kode aplikasi web, diunggah ke PHPoC shield melalui PHPoC Debugger.
Apa yang Harus Kita Lakukan
Setel informasi WiFi untuk PHPoC shield (SSID dan password)
Unggah UI baru ke PHPoC shield
Tulis kode Arduino
Mengatur Informasi WiFi untuk PHPoC Shield
Lihat instruksi ini.
Unggah Web UI baru ke PHPoC Shield
Unduh kode sumber PHP unlock.php (di bagian kode).
Unduh dua gambar berikut untuk kontrol servo motor
Unggah ke PHPoC shield menggunakan PHPoC debugger sesuai instruksi ini (catat bahwa JANGAN menghapus file yang ada pada PHPoC Shield)
Tulis Kode Arduino
Instal library PHPoC untuk Arduino di Arduino IDE (lihat instruksi)
Lihat kode sumber di bagian kode.
Kompilasi dan unggah ke Arduino melalui Arduino IDE
Coba
Klik tombol serial di Arduino IDE untuk melihat alamat IP.
Buka web browser, ketik https://ganti_alamat_ip/unlock.php
Klik tombol connect dan coba.
Kit Starter Arduino Terbaik untuk Pemula
Jika Anda mencari kit Arduino, lihat The Best Arduino Kit for Beginners
Referensi Fungsi
Arduino - Servo Library
Servo.attach()
Servo.write()
Servo.writeMicroseconds()
Servo.read()
Servo.attached()
Servo.detach()
Serial.begin()
Serial.println()
delay()
millis()
for loop
while loop
if else
loop()
setup()
String.toInt()
String.substring()
String.indexOf()
String.remove()
String.equals()
Kode
unlock.phpPHP
Ini adalah antarmuka pengguna web
<!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 { font-weight: bold; font-size: width/2pt; } h2 { font-weight: bold; font-size: width/2pt; } button { font-weight: bold; font-size: width/2pt; } </style> <script> var CMD_AUTH = 0; var CMD_CTRL = 1; var ws; var authorized = false; /* unlock variable */ var unlock_width = 400, unlock_height = 400; var unlock_inner_radius = 14; var unlock_middle_radius = 22; var unlock_outer_radius = 34; var unlock_gap = 140; var unlock_touch_state = 0; var unlock_touch_x = 0, unlock_touch_y = 0; var unlock_touch_list = new Array(); var unlock_ratio = 1; /* control variable: change as you want, as your application */ 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; var 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() { /* init unlock part */ var unlock = document.getElementById("unlock"); unlock.width = unlock_width; unlock.height = unlock_height; unlock.addEventListener("touchstart", unlock_mouse_down); unlock.addEventListener("touchend", unlock_mouse_up); unlock.addEventListener("touchmove", unlock_mouse_move); unlock.addEventListener("mousedown", unlock_mouse_down); unlock.addEventListener("mouseup", unlock_mouse_up); unlock.addEventListener("mousemove", unlock_mouse_move); 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"; /* init control part */ var servo = document.getElementById("servo"); servo.width = servo_width; servo.height = servo_height; servo.style.backgroundImage = "url('/servo_body.png')"; servo.addEventListener("touchstart", servo_mouse_down); servo.addEventListener("touchend", servo_mouse_up); servo.addEventListener("touchmove", servo_mouse_move); servo.addEventListener("mousedown", servo_mouse_down); servo.addEventListener("mouseup", servo_mouse_up); servo.addEventListener("mousemove", servo_mouse_move); ctx = servo.getContext("2d"); ctx.translate(servo_pivot_x, servo_pivot_y); update_view(); } function connect_onclick() { if(ws == null) { var ws_host_addr = "<?echo _SERVER("HTTP_HOST")?>"; if((navigator.platform.indexOf("Win") != -1) && (ws_host_addr.charAt(0) == "[") ) { ws_host_addr = ws_host_addr.replace(/[\[\]]/g, ''); ws_host_addr = ws_host_addr.replace(/:/g, "-"); ws_host_addr += ".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 = null; ws.onclose = null; ws.onmessage = null; ws = null; authorized = false; update_view(); } function ws_onmessage(e_msg) { e_msg = e_msg || window.event; var resp = parseInt(e_msg.data); if(resp == 202) authorized = true; else if(resp == 401) authorized = false; else console.log("unknown:" + resp); update_view(); } function update_view() { if(!authorized) unlock_update_view(); else servo_update_view(); } function unlock_update_view() { document.body.style.backgroundColor = "black"; document.body.style.color = "white"; var unlock_area = document.getElementById('unlock_area'); var control_area = document.getElementById('control_area'); unlock_area.style.display = 'block'; control_area.style.display = 'none'; var unlock = document.getElementById("unlock"); var ctx = 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 < unlock_touch_list.length; i++) { var temp = unlock_touch_list[i] - 1; var x = temp % 3 - 1; var y = Math.floor(temp / 3) - 1; ctx.lineTo(x*unlock_gap, y*unlock_gap); } if(unlock_touch_state) ctx.lineTo(unlock_touch_x, unlock_touch_y); ctx.stroke(); for (var i = 0; i < unlock_touch_list.length; i++) { var temp = unlock_touch_list[i] - 1; var x = temp % 3 - 1; var y = Math.floor(temp / 3) - 1; ctx.globalAlpha=0.2; ctx.fillStyle = "white"; ctx.beginPath(); ctx.arc(x*unlock_gap, y*unlock_gap, unlock_outer_radius, 0, 2 * Math.PI); ctx.fill(); } for(var y = -1; y <= 1; y++) { for(var x = -1; x <= 1; x++) { ctx.globalAlpha=0.5; ctx.fillStyle = "white"; ctx.beginPath(); ctx.arc(x*unlock_gap, y*unlock_gap, unlock_middle_radius, 0, 2 * Math.PI); ctx.fill(); ctx.globalAlpha=1; ctx.fillStyle = "Cyan"; ctx.beginPath(); ctx.arc(x*unlock_gap, y*unlock_gap, unlock_inner_radius, 0, 2 * Math.PI); ctx.fill(); } } } function unlock_process_event(event) { if(event.offsetX) { unlock_touch_x = event.offsetX - unlock_width/2; unlock_touch_y = event.offsetY - unlock_height/2; } else if(event.layerX) { unlock_touch_x = event.layerX - unlock_width/2; unlock_touch_y = event.layerY - unlock_height/2; } else { unlock_touch_x = (Math.round(event.touches[0].pageX - event.touches[0].target.offsetLeft)) - unlock_width/2; unlock_touch_y = (Math.round(event.touches[0].pageY - event.touches[0].target.offsetTop)) - unlock_height/2; } for(var i = 1; i <= 9; i++) { if(i == unlock_touch_list[unlock_touch_list.length - 1]) continue; var idx_x = (i-1)%3 - 1; var idx_y = Math.floor((i-1)/3) - 1; var center_x = idx_x*unlock_gap; var center_y = idx_y*unlock_gap; var dist = Math.sqrt( (unlock_touch_x - center_x)*(unlock_touch_x - center_x) + (unlock_touch_y - center_y)*(unlock_touch_y - center_y) ); if(dist < unlock_outer_radius) { unlock_touch_list.push(i); unlock_touch_state = 1; break; } } update_view(); } function unlock_mouse_down() { if(ws == null || authorized) return; event.preventDefault(); unlock_process_event(event); } function unlock_mouse_up() { if(ws == null || authorized) return; event.preventDefault(); if(ws != null && authorized == false) send_to_Arduino(CMD_AUTH, unlock_touch_list.toString()); unlock_touch_state = 0; unlock_touch_list.splice(0, unlock_touch_list.length); update_view(); } function unlock_mouse_move() { if(ws == null || authorized) return; event.preventDefault(); if(authorized) return; unlock_process_event(event); } function servo_update_view() { document.body.style.backgroundColor = "white"; document.body.style.color = "black"; var unlock_area = document.getElementById('unlock_area'); var control_area = document.getElementById('control_area'); unlock_area.style.display = 'none'; control_area.style.display = 'block'; var servo = document.getElementById("servo"); var ctx = servo.getContext("2d"); ctx.clearRect(-servo_pivot_x, -servo_pivot_y, servo_width, servo_height); ctx.rotate(servo_bracket_angle / 180 * Math.PI); ctx.drawImage(servo_bracket_img, -servo_pivot_x, -servo_pivot_y); ctx.rotate(-servo_bracket_angle / 180 * Math.PI); } function check_range_xyra(event, servo_mouse_xyra) { var x, y, r, a, rc_x, rc_y, radian; var min_r, max_r, width; if(event.touches) { var touches = event.touches; x = (touches[0].pageX - touches[0].target.offsetLeft) - servo_pivot_x; y = servo_pivot_y - (touches[0].pageY - touches[0].target.offsetTop); min_r = 60; max_r = servo_pivot_x; width = 40; } else { x = event.offsetX - servo_pivot_x; y = servo_pivot_y - event.offsetY; min_r = 60; max_r = servo_bracket_radius; width = 20; } r = Math.sqrt(x * x + y * y); a = Math.atan2(y, x); servo_mouse_xyra.x = x; servo_mouse_xyra.y = y; servo_mouse_xyra.r = r; servo_mouse_xyra.a = a; radian = servo_bracket_angle / 180 * Math.PI; rc_x = x * Math.cos(radian) - y * Math.sin(radian); rc_y = x * Math.sin(radian) + y * Math.cos(radian); if((r < min_r) || (r > 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() { var angle; 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; } angle = servo_mouse_xyra.a / Math.PI * 180.0; if((Math.abs(angle) > 90) && (angle * servo_last_angle < 0)) { if(servo_last_angle > 0) servo_last_angle = -180; else servo_last_angle = 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)) debug = document.getElementById("debug"); 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> <p> <h1>Arduino - Web Pattern Unlock</h1> </p> <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> <p>WebSocket : <span id="ws_state">null</span></p> <button id="bt_connect" type="button" onclick="connect_onclick();">Connect</button> </h2> </body> </html>