Remotely control door via web with pattern password protected. Your door is securely controlled from anywhere through Internet.
Click image for larger version  Name:	arduino-cover.png Views:	0 Size:	256.9 KB ID:	1594



Hardware components
× 1
× 1
× 1
Electromagnetic Lock
× 1



Demonstration








How It Works


1. Authentication via pattern password

see https://www.hackster.io/phpoc_man/ar...-unlock-7e1f07

2. Open the door
  • The password is checked by Arduino.
  • If password is correct, user can rotate the door handle on web.
  • Web send command to unlock the door to Arduino.
  • Arduino control Relay to deactivate EM lock


Wiring
  • Stack PHPoC WiFi shield on Arduino
  • Stack Relay shield on PHPoC Shield
  • Connect EM lock to Relay Shield
  • In relay shield, move jumper to set relay works as NC (Normal Closed) mode





How To
  • Compile Arduino code and upload to Arduino Uno
  • Upload door.php to PHPoC WiFi Shield via this instruction
  • Access web page of door: http://192.168.0.234/door.php (you need to replace your PHPoC Shield's IP address)
  • Draw the pattern to get the access permission
  • Rotate the door handle to open the door




Web User Interface








Code

Arduino code
Code:
/* arduino web server - pattern unlock */

#include <Phpoc.h>
#include <PhpocExpansion.h>

#define CMD_AUTH    0
#define CMD_CTRL    1
#define ACCESS_ACCEPTED        "0\r\n"
#define ACCESS_UNAUTHORIZED    "1\r\n"
#define DOOR_STATE_OPEN        "2\r\n"
#define DOOR_STATE_CLOSE    "3\r\n"
#define DOOR_TIMEOUT_MS        10000

PhpocServer server(80);
ExpansionRelayOutput relay(1, 0);
String pattern;
bool authenticated;

unsigned long lastActiveTime;

void setup() {
    Serial.begin(9600);
    while(!Serial)
        ;

    Phpoc.begin(PF_LOG_SPI | PF_LOG_NET);
    //Phpoc.begin();

    server.beginWebSocket("web_pattern");

    Serial.print("WebSocket server address: ");
    Serial.println(Phpoc.localIP());

    // initialize expansion board
    Expansion.begin();

    pattern = String("1,4,8,6,3");
    authenticated = false;
    lastActiveTime = 0;
}

void loop() {
    // wait for a new client:
    PhpocClient client = server.available();

    if (client) {
        String data = client.readLine();

        if(data) {
            int pos = data.indexOf(':');
            int cmd = data.substring(0, pos).toInt();

            Serial.print("<<");
            Serial.print(data);
            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(ACCESS_ACCEPTED, 3);
                    lastActiveTime = millis();
                }
                else {
                    authenticated = false;
                    sendResponse(ACCESS_UNAUTHORIZED, 3);
                }
            }
            else
            if(cmd == CMD_CTRL) {
                if(authenticated) {
                    int control = data.substring(pos+1).toInt();

                    if(!control){
                        relay.off();
                        sendResponse(DOOR_STATE_CLOSE, 3);
                    } else {
                        relay.on();
                        sendResponse(DOOR_STATE_OPEN, 3);
                    }

                    lastActiveTime = millis();
                }
                else {
                    sendResponse(ACCESS_UNAUTHORIZED, 3);
                }
            }
        }
    }

    if (authenticated && ((millis() - lastActiveTime) > DOOR_TIMEOUT_MS)){
        authenticated = false;
        sendResponse(ACCESS_UNAUTHORIZED, 3);
        relay.off();
        delay(500);
        sendResponse(DOOR_STATE_CLOSE, 3);
    }
}

void sendResponse(char *data, int len) {
    server.write(data, len);
    Serial.print(">>");
    Serial.print(data);
}


Web User Interface Code (door.php)
PHP Code:
<!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 width = window.innerWidth - 10;
var ratio = width / 800;
if(ratio > 1)
    ratio = 1;

var CMD_AUTH = 0;
var CMD_CTRL = 1;
var ACCESS_ACCEPTED        = 0;
var ACCESS_UNAUTHORIZED    = 1;
var DOOR_STATE_OPEN        = 2;
var DOOR_STATE_CLOSE    = 3;
var ws;
var authorized = false;

/* lock variable */
var canvas_width = 800 * ratio;
var canvas_height = 1300 * ratio;
var lock_edge = 40 * ratio;
var pad = 50 * ratio;
var handle_width  = 500 * ratio;
var handle_height = 120 * ratio;
var lock_width  = (canvas_width  -  2 * lock_edge - handle_width) * 2;
var lock_height =  canvas_height -  2 * lock_edge;

var pattern_width  = lock_width;
var pattern_height = lock_width;
var pattern_inner_radius  = 14 * ratio;
var pattern_middle_radius = 22 * ratio;
var pattern_outer_radius  = 34 * ratio;
var pattern_gap = lock_width / 3;

var body_width = lock_width;
var body_height = canvas_height - lock_edge * 2 - pattern_height - pad - (handle_width - lock_width / 2);
var knob_center_x = lock_edge + body_width / 2;
var knob_center_y = lock_edge + pattern_height + pad + body_height - body_width / 2;

var touch_x = 0, touch_y = 0;
var pattern_touch_state = 0;
var pattern_touch_list = new Array();
var handle_angle = 0;
var handle_last_angle = 0;
var handle_touch_state = 0;
var mouse_state = "MOUSE_UP";
var is_door_open = false;

function init()
{
    var lock = document.getElementById("lock");
    lock.width = canvas_width;
    lock.height = canvas_height;

    lock.addEventListener("touchstart", mouse_down);
    lock.addEventListener("touchend", mouse_up);
    lock.addEventListener("touchmove", mouse_move);
    lock.addEventListener("mousedown", mouse_down);
    lock.addEventListener("mouseup", mouse_up);
    lock.addEventListener("mousemove", mouse_move);

    var ctx = lock.getContext("2d");
    ctx.lineCap="round";
    ctx.lineJoin="round";

    update_view();

    var ws_host_addr = "<?echo _SERVER("HTTP_HOST")?>";
    if((navigator.platform.indexOf("Win") != -1) && (ws_host_addr.charAt(0) == "["))
    {
        // network resource identifier to UNC path name conversion
        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");
    ws.onopen = ws_onopen;
    ws.onclose = ws_onclose;
    ws.onmessage = ws_onmessage;
}

function ws_onopen()
{
    update_view();
}
function ws_onclose()
{
    alert("CANNOT connect to Arduino!");
    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; // MessageEvent

    var resp = parseInt(e_msg.data);

    if(resp == ACCESS_ACCEPTED)
        authorized = true;
    else if(resp == ACCESS_UNAUTHORIZED)
        authorized = false;
    else if(resp == DOOR_STATE_OPEN)
        is_door_open = true;
    else if(resp == DOOR_STATE_CLOSE)
    {
        is_door_open = false;
        handle_last_angle = 0;
        handle_angle = 0;
    }
    else
        console.log("unknown:" + resp);

    update_view();
}
function update_view()
{
    var pattern_area = document.getElementById('pattern_area');
    var control_area = document.getElementById('control_area');

    var lock = document.getElementById("lock");
    var ctx = lock.getContext("2d");

    ctx.clearRect(0, 0, canvas_width,  canvas_height);

    // draw boder
    ctx.shadowBlur = 10;
    ctx.shadowColor = "LightGray";
    ctx.fillStyle = "#6A4439";
    ctx.beginPath();
    ctx.lineTo(0, 0);
    ctx.lineTo(body_width + 2 * lock_edge , 0);
    ctx.arc(knob_center_x, knob_center_y, body_width / 2 + lock_edge, 0, Math.PI);
    ctx.closePath();
    ctx.fill();

    // draw pattern password background
    ctx.fillStyle = "black";
    ctx.fillRect(lock_edge, lock_edge, pattern_width, pattern_height);

    // draw status indicator
    if(authorized)
        ctx.fillStyle = "Cyan";
    else
        ctx.fillStyle = "white";
    ctx.fillRect(lock_edge, lock_edge + pattern_height, pattern_width, pad);

    ctx.font = "22px Arial"
    ctx.textBaseline = "middle";
    ctx.textAlign = "center";
    ctx.fillStyle = "black";

    var text = "";
    if(is_door_open)
        text += "Openned, ";
    else
        text += "Closed, ";

    if(authorized)
        text += "Access permitted!";
    else
        text += "Access denied!";

    ctx.fillText(text, lock_edge + pattern_width / 2, lock_edge + pattern_height + pad / 2);

    // draw body background
    ctx.fillStyle = "#A07B72";
    ctx.beginPath();
    ctx.lineTo(lock_edge,                lock_edge + pattern_height + pad);
    ctx.lineTo(lock_edge + body_width ,    lock_edge + pattern_height + pad);
    ctx.arc(knob_center_x, knob_center_y, body_width / 2, 0, Math.PI);
    ctx.closePath();
    ctx.fill();

    // draw knob
    ctx.save();
    ctx.translate(knob_center_x, knob_center_y);
    if(authorized)
        ctx.fillStyle = "Cyan";
    else
        ctx.fillStyle = "white";
    ctx.shadowBlur = 1;
    ctx.shadowColor = "black";
    ctx.beginPath();
    ctx.arc(0, 0, body_width / 2 * 0.8, 0, 2 * Math.PI);
    ctx.fill();

    ctx.fillStyle = "black";
    ctx.shadowBlur = 1;
    ctx.shadowColor = "black";
    ctx.beginPath();
    ctx.arc(0, 0, body_width / 2 * 0.6, 0, 2 * Math.PI);
    ctx.fill();

    // draw handle
    ctx.rotate(handle_angle * Math.PI / 180);
    var grd = ctx.createLinearGradient(0, 0, handle_width, 0);
    grd.addColorStop(0, "#6A4439");
    grd.addColorStop(0.2, "#A07B72");
    grd.addColorStop(0.8, "#A07B72");
    grd.addColorStop(1, "#6A4439");

    ctx.fillStyle = grd;
    ctx.shadowBlur = 10;
    ctx.shadowColor = "black";
    ctx.beginPath();
    ctx.arc(0, 0, handle_height / 2, 0.5 * Math.PI, 1.5 * Math.PI);
    ctx.arc(handle_width - handle_height / 2 * 0.7, 0, handle_height / 2 * 0.78, 1.5 * Math.PI, 0.5 * Math.PI);
    ctx.closePath();
    ctx.fill();
    ctx.restore();

    // draw touched point and line
    ctx.save();
    ctx.shadowBlur = 20;
    ctx.shadowColor = "LightGray";
    ctx.lineWidth = 10;
    ctx.strokeStyle="white";
    ctx.globalAlpha=1;
    ctx.beginPath();

    ctx.translate(pattern_width/2 + lock_edge, pattern_height/2 + lock_edge);

    for (var i = 0; i < pattern_touch_list.length; i++)
    {
        var temp = pattern_touch_list[i] - 1;
        var x =  temp % 3 - 1;
        var y = Math.floor(temp / 3) - 1;

        ctx.lineTo(x*pattern_gap, y*pattern_gap);
    }

    if(pattern_touch_state)
        ctx.lineTo(touch_x, touch_y);

    ctx.stroke();

    for (var i = 0; i < pattern_touch_list.length; i++)
    {
        var temp = pattern_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*pattern_gap, y*pattern_gap, pattern_outer_radius, 0, 2 * Math.PI);
        ctx.fill();
    }

    // draw base
    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*pattern_gap, y*pattern_gap, pattern_middle_radius, 0, 2 * Math.PI);
            ctx.fill();

            ctx.globalAlpha=1;
            ctx.fillStyle = "Cyan";
            ctx.beginPath();
            ctx.arc(x*pattern_gap, y*pattern_gap, pattern_inner_radius, 0, 2 * Math.PI);
            ctx.fill();
        }
    }

    ctx.restore();
}

function process_event(event)
{
    if(event.offsetX)
    {
        touch_x = event.offsetX;
        touch_y = event.offsetY;
    }
    else if(event.layerX)
    {
        touch_x = event.layerX;
        touch_y = event.layerY;
    }
    else
    {
        touch_x = (Math.round(event.touches[0].pageX - event.touches[0].target.offsetLeft));
        touch_y = (Math.round(event.touches[0].pageY - event.touches[0].target.offsetTop));
    }

    if(touch_x > lock_edge && touch_x < (lock_edge + pattern_width) && touch_y > lock_edge && touch_y < (lock_edge + pattern_height))
    { // pattern password area
        touch_x -= (lock_edge + pattern_width / 2);
        touch_y -= (lock_edge + pattern_height / 2);

        for(var i = 1; i <= 9; i++)
        {
            if(i == pattern_touch_list[pattern_touch_list.length - 1])
                continue;

            var idx_x = (i-1)%3 - 1;
            var idx_y = Math.floor((i-1)/3) - 1;

            var knob_center_x = idx_x * pattern_gap;
            var knob_center_y = idx_y * pattern_gap;

            var dist = Math.sqrt( (touch_x - knob_center_x)*(touch_x - knob_center_x) + (touch_y - knob_center_y)*(touch_y - knob_center_y) );

            if(dist < pattern_outer_radius)
            {
                pattern_touch_list.push(i);
                break;
            }
        }

        pattern_touch_state = 1;
    }
    else
    {
        pattern_touch_state = 0;

        if(!authorized)
            return;

        var knob_center_x = lock_edge + body_width / 2;
        var knob_center_y = lock_edge + pattern_height + pad + body_height - body_width / 2;
        touch_x -= knob_center_x;
        touch_y -= knob_center_y;
        var current_angle = Math.atan2(touch_y, touch_x) * 180 / Math.PI;

        /* rotate coordinate */
        var radian = -handle_angle / 180 * Math.PI;
        var rc_x = touch_x * Math.cos(radian) - touch_y * Math.sin(radian);
        var rc_y = touch_x * Math.sin(radian) + touch_y * Math.cos(radian);

        if(mouse_state == "MOUSE_DOWN")
        {
            if(rc_x > (handle_width * 0.3) && rc_x < handle_width && rc_y > (-handle_height / 2) && rc_y < (handle_height / 2))
            {
                handle_last_angle = current_angle;
                handle_touch_state = 1;
            }
        }
        else
        if(mouse_state == "MOUSE_MOVE" && handle_touch_state == 1)
        {
            var angle = current_angle - handle_last_angle;
            if((handle_angle + angle) > 0 && (handle_angle + angle) <= 45)
            {
                handle_angle += angle;
                handle_last_angle = current_angle;
                if(handle_angle < 30)
                    send_to_Arduino(CMD_CTRL, 1);
            }
        }
    }

    update_view();
}
function mouse_down()
{
    if(ws == null)
        return;

    event.preventDefault();
    mouse_state = "MOUSE_DOWN";
    process_event(event);
}
function mouse_up()
{
    if(ws == null)
        return;

    event.preventDefault();

    if(ws != null && pattern_touch_state)
        send_to_Arduino(CMD_AUTH, pattern_touch_list.toString());

    pattern_touch_state = 0;
    mouse_state = "MOUSE_UP";
    pattern_touch_list.splice(0, pattern_touch_list.length);
    update_view();
}
function mouse_move()
{
    if(ws == null)
        return;

    event.preventDefault();
    mouse_state = "MOUSE_MOVE";
    process_event(event);
}

function send_to_Arduino(cmd, data)
{
    if(ws.readyState == 1)
    {
        ws.send(cmd + ":" + data + "\r\n");
    }
}

window.onload = init;
</script>
</head>

<body>
<div id="pattern_area" style="display:block;">
    <canvas id="lock"></canvas>
</div>
</body>
</html>