산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Manufacturing Technology >> 제조공정

아두이노 웹 패턴 언락으로 보안 강화하기 – 간편 설정 및 원격 제어

부품 및 소모품

부품 수량
아두이노 우노 (Arduino UNO) 1
아두이노용 PHPoC 와이파이 실드 (PHPoC WiFi Shield for Arduino) 1
Seeed 서보 모터 SG92R 1
점퍼 와이어 1

프로젝트 소개

초보자인 경우 다음 튜토리얼을 먼저 읽어보시길 권장합니다:

스마트폰의 잠금 해제 패턴에 익숙하실 겁니다. 이제 이 기능을 아두이노에서도 사용할 수 있습니다. 이 기능은 무단 사용자가 아두이노를 제어하거나 모니터링하는 것을 방지합니다.

이 프로젝트의 코드는 다른 애플리케이션에서도 자유롭게 재사용할 수 있습니다. 설명의 편의를 위해 서보 모터 제어를 예시로 들었습니다.

이 아이디어는 PHPoC 플랫폼용으로 작성된 두 가지 프로젝트에서 유래했습니다:

이들을 아두이노 환경에 맞게 적용했습니다.

PHPoC 와이파이 실드를 사용한 이유

PHPoC 실드에는 몇 가지 내장 웹 애플리케이션이 있어, 웹 프로그래밍 지식 없이도 임베디드 웹 애플리케이션을 통해 아두이노를 제어/모니터링할 수 있습니다.

또한, 웹 프로그래밍이 가능한 사용자는 직접 웹 애플리케이션을 개발하여 PHPoC 실드에 저장할 수도 있습니다.

시연

웹 브라우저에서 패턴을 그려 아두이노를 언락하고 서보 모터를 제어하는 과정을 시연합니다.

데이터 흐름

웹 브라우저 ↔ PHPoC 와이파이 실드 ↔ 아두이노

아두이노 웹 패턴 언락으로 보안 강화하기 – 간편 설정 및 원격 제어

작동 원리

사용자가 웹 브라우저에서 패턴을 그리면, 해당 패턴이 문자열로 매핑됩니다. 이 패턴 문자열은 웹소켓을 통해 PHPoC 실드를 거쳐 아두이노로 전송됩니다.

아두이노가 입력 패턴 문자열을 수신하면, 아두이노에 하드코딩된 패턴 문자열과 비교합니다. 일치하면 아두이노는 클라이언트(웹 브라우저)에 ACCEPTED(202) 코드를 전송하고 인증 변수(authenticated variable)를 true로 설정합니다. 일치하지 않으면 DENIED(401) 코드를 전송하고 인증 변수를 false로 설정합니다.

이후 사용자로부터 제어 명령이 수신되면, 아두이노는 먼저 인증 변수의 값을 확인합니다. 값이 true이면 해당 명령에 따른 작업을 수행하고, false이면 클라이언트에 DENIED 코드를 전송합니다.

패턴 매핑

아두이노 웹 패턴 언락으로 보안 강화하기 – 간편 설정 및 원격 제어

패턴은 문자열로 매핑됩니다. 위 이미지 예시에서 패턴 문자열은 "1, 4, 8, 6, 3"입니다.

타임아웃이 설정되어 있어, 일정 시간 동안 사용자 활동이 없으면 인증이 만료되며 사용자는 다시 패턴을 입력해 아두이노를 언락해야 합니다.

소스 코드 구성

진행 단계

  1. PHPoC 실드의 와이파이 정보 설정 (SSID 및 비밀번호)
  2. PHPoC 실드에 새로운 UI 업로드
  3. 아두이노 코드 작성

PHPoC 실드 와이파이 정보 설정

이 지침을 참조하세요.

PHPoC 실드에 새 웹 UI 업로드

아두이노 웹 패턴 언락으로 보안 강화하기 – 간편 설정 및 원격 제어
아두이노 웹 패턴 언락으로 보안 강화하기 – 간편 설정 및 원격 제어

아두이노 코드 작성

테스트 방법

  1. 아두이노 IDE의 시리얼 버튼을 클릭해 IP 주소 확인
  2. 웹 브라우저에서 https://IP_주소_입력/unlock.php 접속
  3. Connect 버튼 클릭 후 테스트

초보자를 위한 추천 아두이노 스타터 키트

아두이노 키트를 찾고 계신다면 초보자를 위한 최고의 아두이노 키트를 참고하세요.

함수 레퍼런스

코드

unlock.php (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 { 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; /* 언락 변수 */ 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; /* 제어 변수: 애플리케이션에 맞게 변경 */ 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() { /* 언락 부분 초기화 */ 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"; /* 제어 부분 초기화 */ 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> 

ArduinoUnlockExample (아두이노)

/* 아두이노 웹 서버 - 패턴 언락 */ #include "SPI.h" #include "Phpoc.h" #include <Servo.h> #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; unsigned long lastActiveTime; 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 = String("1,4,8,6,3"); authenticated = false; timeout = 10000; 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; sendResponse(ACCEPTED, 3); lastActiveTime = millis(); } else { authenticated = false; sendResponse(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 { sendResponse(UNAUTHORIZED, 3); } } } } if (authenticated && ((millis() - lastActiveTime) > timeout)){ authenticated = false; sendResponse(UNAUTHORIZED, 3); } } void sendResponse(char *data, int len) { server.write(data, len); } 

회로도

아두이노 웹 패턴 언락으로 보안 강화하기 – 간편 설정 및 원격 제어

제조공정

  1. 대량 생산 의류 패턴의 진화와 제작
  2. 웹 제어 DMX 조명 시스템 - 아두이노 마스터 컨트롤러
  3. 블루투스 제어 아두이노 스파이봇(Spybot) 만들기
  4. 플릭모트(FlickMote): 스마트 홈을 위한 제스처 제어 IR 리모컨
  5. DIY 아두이노 TV B-Gone: 모든 TV를 끄는 만능 리모컨
  6. 아두이노로 만드는 대형 LED 마스터 시계 (알람 기능 포함, 12m 거리에서도 식별 가능)
  7. 아두이노와 블루투스로 만드는 스마트 물건 찾기 'Find Me'
  8. 아두이노 프로젝트를 위한 최적화된 전원 솔루션
  9. 아두이노 틱택토 게임 - MAX7219 LED 매트릭스와 골판지 케이스
  10. 스마트 아두이노 4족 보행 로봇 제작: 단계별 완벽 가이드