├── .gitignore ├── README.md ├── frontend ├── .gitignore ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── actions │ │ ├── index.js │ │ └── types.js │ ├── components │ │ ├── add-visitor.js │ │ ├── app.js │ │ ├── auth │ │ │ ├── require_auth.js │ │ │ ├── signin.js │ │ │ ├── signout.js │ │ │ └── signup.js │ │ ├── content.js │ │ ├── control-panel.js │ │ ├── header.js │ │ ├── log.js │ │ ├── stream.js │ │ ├── tab.js │ │ ├── tabs.js │ │ └── visitor.js │ ├── index.js │ └── reducers │ │ ├── bell_reducer.js │ │ └── index.js ├── style │ ├── static │ │ └── images │ │ │ ├── bg-main.png │ │ │ └── bg.jpg │ └── style.css └── webpack.config.js ├── python ├── .gitignore ├── collect.py ├── delete.py ├── faces_encodings.txt ├── log.py ├── recognition.py ├── save.py ├── send_email.py ├── usbcamera.py ├── videostream.py └── visit.py └── server ├── .gitignore ├── config.js ├── controllers ├── authentication.js ├── manage-logs.js ├── manage-photo-upload.js └── manage-visitors.js ├── index.js ├── models ├── log.js ├── user.js └── visitor.js ├── package-lock.json ├── package.json ├── router.js └── services └── passport.js /.gitignore: -------------------------------------------------------------------------------- 1 | /frontend/node_modules 2 | /server/node_modules 3 | /frontend/bundle.js 4 | /frontend/npm-debug.log 5 | /frontend/.DS_Store 6 | 7 | # IntelliJ 8 | *.iml 9 | /.idea 10 | python-stream/*.pyc 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Face recognition security system 2 | 3 | - Video demonstration of working application is available on the following link: https://youtu.be/qb1KZ4enTIA 4 | 5 | The main goal of creating such a system is to make security as convenient as possible. Granting access to various facilities and areas inside them without keys, cards, or passwords can make the presence of a security guard or continuous in-person monitoring obsolete. At the same time, it can eliminate inconvenience caused by carrying keys and paper ID. 6 | 7 | Smart security is the future, and with the help of the technology available today, an affordable intelligent security system is within our reach. This application is a low-cost, adaptive, and extensible surveillance system focused on identifying visitors. It can be integrated into an existing alarm system or be paired with a lock. It operates in real time and can distinguish between someone who is in the face database and someone who is not (a potential intruder). 8 | 9 | ## System architecture 10 | 11 | ![1](https://user-images.githubusercontent.com/18744749/31586280-737f11b6-b1d7-11e7-8e09-c4c9bff7cd9f.jpg) 12 | 13 | ## Application logic 14 | 15 | The system relies on a facial recognition technique and does it in real time. User simply walk up to the door that he/she wants to open or the room he/she wants to enter and presses the button: the system will either recognize user as a trusted visitor and let the user in, or it will identify user as a stranger and deny access. 16 | 17 | The system, for the most part, is looking for faces. To get clearance, each user needs a profile picture that the algorithm can analyze for unique features. Then, whenever the same user is trying to get into a building or area, the system steps in. If it recognizes that face, the LED blinks once and that person is cleared for entry or twice if access for this person is forbidden or no match is found within visitors’ database. 18 | 19 | The core of the application is face recognition algorithm which first detects a face, then encodes face features and save them in a text file to be later compared alongside with another visitors’ face features to any new person trying to access a building or a room. 20 | 21 | ![2](https://user-images.githubusercontent.com/18744749/31586324-f0b0e8f8-b1d7-11e7-902c-a526219f1d2f.jpg) 22 | 23 | ## Application features 24 | 25 | - Face recognition 26 | - Managing visitors 27 | - add/delete visitor 28 | - change the access right for visitor 29 | - add visitor's photo from camera or from local storage 30 | - History of entered visitors 31 | - Authentication for administrator 32 | - Video stream to web application 33 | - Email notifications of entering the place 34 | - Blocking the door via email link in case of cheating 35 | 36 | ![3](https://user-images.githubusercontent.com/18744749/31586456-fa48f930-b1d9-11e7-9839-93302146da40.jpg) 37 | 38 | ## System installation 39 | 40 | - Software requirements 41 | - Download the code from GitHub repository 42 | $ git clone https://github.com/dmitrykhramov/Smart-Bell.git 43 | 44 | - Install MongoDB 45 | Available for download from https://www.mongodb.com/. 46 | 47 | - Install Python libraries 48 | $ pip install -r requirements.txt 49 | 50 | - Install OpenCV 51 | 52 | Version 2.4.9.1 is available for download from: https://github.com/opencv/opencv. 53 | 54 | Guidelines for Raspberry Pi installation: https://www.pyimagesearch.com/2016/04/18/install-guide-raspberry-pi-3-raspbian-jessie-opencv-3/ 55 | 56 | - Install DLIB library 57 | 58 | Guidelines for Raspberry Pi installation: https://www.pyimagesearch.com/2017/05/01/install-dlib-raspberry-pi/ 59 | 60 | - Hardware requirements 61 | - RaspberryPi 62 | - USB camera 63 | - Breadboard 64 | - Button 65 | - LED 66 | 67 | - Install node modules 68 | 69 | Install node modules for NodeJS in server folder 70 | - $ ~/Smart-bell/server $ npm install 71 | 72 | Install node modules for ReactJS in frontend folder 73 | - $ ~/Smart-bell/frontend $ npm install 74 | 75 | - Starting program 76 | 77 | Start MongoDB database 78 | - $ ~ sudo service mongodb start 79 | 80 | Start NodeJS server 81 | - $ ~/Smart-bell/server $ npm run dev 82 | 83 | Start Python application 84 | - $ ~/Smart-bell/python $ python usbcamera.py 85 | 86 | Start ReactJS application 87 | - $ ~/Smart-bell/frontend $ npm start 88 | 89 | Open ‘localhost:8080’ in the browser. 90 | 91 | ## Next steps 92 | - Deploying web application and database to the cloud. It will improve the performance of the python application running on RaspberryPi and will make web application available from everywhere. 93 | - Improving the security of the face recognition algorithm, that it could not be cheated with pictures. 94 | - Replace normal camera with 3D camera. 95 | - Implement the door locker opening mechanism via Bluetooth. 96 | 97 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | /frontend/node_modules 2 | /server/node_modules 3 | /frontend/bundle.js 4 | /frontend/npm-debug.log 5 | /frontend/.DS_Store 6 | 7 | # IntelliJ 8 | *.iml 9 | /.idea 10 | python-stream/*.pyc 11 | 12 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend-doorbell", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "repository": "", 7 | "scripts": { 8 | "start": "node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js", 9 | "test": "mocha --compilers js:babel-core/register --require ./test/test_helper.js --recursive ./test", 10 | "test:watch": "npm run test -- --watch" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "babel-core": "^6.2.1", 16 | "babel-loader": "^6.2.0", 17 | "babel-preset-es2015": "^6.1.18", 18 | "babel-preset-react": "^6.1.18", 19 | "chai": "^3.5.0", 20 | "chai-jquery": "^2.0.0", 21 | "jquery": "^2.2.1", 22 | "jsdom": "^8.1.0", 23 | "mocha": "^2.4.5", 24 | "react-addons-test-utils": "^0.14.7", 25 | "webpack": "^1.12.9", 26 | "webpack-dev-server": "^1.14.0", 27 | "json-loader": "^0.5.7", 28 | "css-loader": "^0.28.4", 29 | "style-loader": "^0.18.2" 30 | }, 31 | "dependencies": { 32 | "array-slice": "^1.0.0", 33 | "axios": "^0.16.2", 34 | "babel-preset-stage-1": "^6.1.18", 35 | "lodash": "^3.10.1", 36 | "map-limit": "0.0.1", 37 | "moment": "^2.18.1", 38 | "mongoose-paginate": "^5.0.3", 39 | "monk": "^6.0.4", 40 | "react": "^0.14.3", 41 | "react-confirm-alert": "^1.0.7", 42 | "react-dom": "^0.14.3", 43 | "react-images-uploader": "^1.1.0", 44 | "react-js-pagination": "^2.2.0", 45 | "react-pager": "^1.3.2", 46 | "react-paginate-component": "0.0.3", 47 | "react-redux": "4.3.0", 48 | "react-router": "^2.0.1", 49 | "react-timeout": "^1.0.1", 50 | "react-toggle-button": "^2.1.0", 51 | "redux": "^3.0.4", 52 | "redux-form": "^5.3.3", 53 | "redux-thunk": "^2.2.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /frontend/src/actions/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import {browserHistory} from 'react-router'; 3 | import { 4 | AUTH_USER, 5 | UNAUTH_USER, 6 | AUTH_ERROR, 7 | FETCH_LOGS, 8 | FETCH_VISITORS, 9 | SOCKET_STATE, 10 | UPLOAD_DOCUMENT_SUCCESS, 11 | UPLOAD_DOCUMENT_FAIL, 12 | VISITOR_ADD_FAIL, 13 | VISITOR_ADD_SUCCESS, 14 | VISITOR_DELETE_FAIL, 15 | VISITOR_DELETE_SUCCESS, 16 | MAKE_PHOTO_FAIL, 17 | MAKE_PHOTO_SUCCESS, 18 | RESET_ADD_FORM 19 | } from './types.js'; 20 | 21 | const ROOT_URL = 'http://localhost:3090'; 22 | 23 | export function signinUser({email, password}) { 24 | return function (dispatch) { 25 | // Submit email/password to the server 26 | axios.post(`${ROOT_URL}/signin`, {email, password}) 27 | .then(response => { 28 | // If request is good 29 | // - update state to indicate user is authenticated 30 | dispatch({type: AUTH_USER}); 31 | // - save jwt token and id 32 | localStorage.setItem('token', response.data.token); 33 | localStorage.setItem('id', response.data.id); 34 | // redirect to another route 35 | browserHistory.push('/'); 36 | }) 37 | .catch(() => { 38 | // If request bad 39 | // - show an error to the user 40 | dispatch(authError('Invalid email or password')); 41 | }); 42 | 43 | // If request is successful 44 | } 45 | } 46 | 47 | export function signupUser({email, password}) { 48 | return function (dispatch) { 49 | axios.post(`${ROOT_URL}/signup`, {email, password}) 50 | .then(response => { 51 | dispatch({type: AUTH_USER}); 52 | localStorage.setItem('token', response.data.token); 53 | localStorage.setItem('id', response.data.id); 54 | browserHistory.push('/'); 55 | }) 56 | .catch(response => { 57 | dispatch(authError(response.response.data.error)); 58 | }); 59 | } 60 | } 61 | 62 | export function authError(error) { 63 | return { 64 | type: AUTH_ERROR, 65 | payload: error 66 | }; 67 | } 68 | 69 | export function signoutUser() { 70 | localStorage.removeItem('token'); 71 | localStorage.removeItem('id'); 72 | 73 | return {type: UNAUTH_USER}; 74 | } 75 | 76 | export function fetchLogs() { 77 | return function (dispatch) { 78 | axios.get(`${ROOT_URL}/logs`, { 79 | headers: {authorization: localStorage.getItem('token')} 80 | }) 81 | .then(response => { 82 | dispatch({ 83 | type: FETCH_LOGS, 84 | payload: response.data.logs 85 | }); 86 | }); 87 | } 88 | } 89 | 90 | export function fetchVisitors() { 91 | return function (dispatch) { 92 | axios.get(`${ROOT_URL}/visitors`, { 93 | headers: {authorization: localStorage.getItem('token')} 94 | }) 95 | .then(response => { 96 | dispatch({ 97 | type: FETCH_VISITORS, 98 | payload: response.data.visitors 99 | }); 100 | }); 101 | } 102 | } 103 | 104 | 105 | export function addVisitor({firstname, lastname, email}) { 106 | return function (dispatch) { 107 | axios.post(`${ROOT_URL}/add_visitor`, {firstname, lastname, email}) 108 | .then(response => { 109 | dispatch({ 110 | type: VISITOR_ADD_SUCCESS, 111 | payload: 'success' 112 | }); 113 | console.log("Visitor added"); 114 | }) 115 | .catch(response => { 116 | dispatch({ 117 | type: VISITOR_ADD_FAIL, 118 | payload: 'fail' 119 | }); 120 | console.log("Can't add a visitor"); 121 | }); 122 | } 123 | } 124 | 125 | export function deleteVisitor(id) { 126 | return function (dispatch) { 127 | axios.delete(`${ROOT_URL}/delete/${id}`, { 128 | headers: {authorization: localStorage.getItem('token')} 129 | }) 130 | .then(response => { 131 | dispatch({ 132 | type: VISITOR_DELETE_SUCCESS, 133 | payload: 'success' 134 | }); 135 | console.log("Visitor deleted"); 136 | }) 137 | .catch(response => { 138 | dispatch({ 139 | type: VISITOR_DELETE_FAIL, 140 | payload: 'fail' 141 | }); 142 | console.log("Can't delete a visitor"); 143 | }); 144 | } 145 | } 146 | 147 | export function toogleAccess(id, value) { 148 | return function (dispatch) { 149 | axios.patch(`${ROOT_URL}/toogle/${id}/${value}`, { 150 | headers: {authorization: localStorage.getItem('token')} 151 | }) 152 | .then(response => { 153 | console.log("Access changed"); 154 | }) 155 | .catch(response => { 156 | console.log("Can't change access"); 157 | }); 158 | } 159 | } 160 | 161 | export function addSocketToState(ws) { 162 | return { 163 | type: SOCKET_STATE, 164 | payload: ws 165 | }; 166 | } 167 | 168 | export function uploadDocument({file}, ws) { 169 | let data = new FormData(); 170 | data.append('file', file); 171 | return (dispatch) => { 172 | axios.post(`${ROOT_URL}/photo`, data) 173 | .then(response => { 174 | // it causes error when it sends success msg first before sending request to websocket when it isn't connected 175 | // that's why I changed the order // by jun 176 | ws.send("photo_upload"); 177 | dispatch({type: UPLOAD_DOCUMENT_SUCCESS, payload: 'success'}); 178 | }) 179 | .catch(error => dispatch({type: UPLOAD_DOCUMENT_FAIL, payload: 'fail'})); 180 | }; 181 | } 182 | 183 | export function photoMake(result) { 184 | return function (dispatch) { 185 | if (result == 'success') { 186 | dispatch({ 187 | type: MAKE_PHOTO_SUCCESS, 188 | payload: 'success' 189 | }); 190 | console.log('Photo make success'); 191 | } else if (result == 'fail') { 192 | dispatch({ 193 | type: MAKE_PHOTO_FAIL, 194 | payload: 'fail' 195 | }); 196 | console.log('Photo make fail'); 197 | } 198 | }; 199 | } 200 | 201 | export function resetAddForm() { 202 | return function (dispatch) { 203 | dispatch({ 204 | type: RESET_ADD_FORM, 205 | payload: 'reset' 206 | }); 207 | console.log('Form reset'); 208 | }; 209 | 210 | } 211 | -------------------------------------------------------------------------------- /frontend/src/actions/types.js: -------------------------------------------------------------------------------- 1 | export const AUTH_USER = 'auth_user'; 2 | export const UNAUTH_USER = 'unauth_user'; 3 | export const AUTH_ERROR = 'auth_error'; 4 | 5 | export const FETCH_MESSAGE = 'fetch_message'; 6 | export const FETCH_LOGS = 'fetch_logs'; 7 | export const FETCH_VISITORS = 'fetch_visitors'; 8 | export const SOCKET_STATE = 'add_socket_to_state'; 9 | export const MAKE_PHOTO_SUCCESS = 'make_photo_success'; 10 | export const MAKE_PHOTO_FAIL = 'make_photo_fail'; 11 | export const UPLOAD_DOCUMENT_SUCCESS = 'upload_document_success'; 12 | export const UPLOAD_DOCUMENT_FAIL = 'upload_document_fail'; 13 | export const VISITOR_ADD_SUCCESS = 'visitor_add_success'; 14 | export const VISITOR_ADD_FAIL = 'visitor_add_fail'; 15 | export const VISITOR_DELETE_SUCCESS = 'visitor_delete_success'; 16 | export const VISITOR_DELETE_FAIL = 'visitor_delete_fail'; 17 | export const RESET_ADD_FORM = 'reset_add_form'; 18 | 19 | 20 | -------------------------------------------------------------------------------- /frontend/src/components/add-visitor.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withRouter } from 'react-router'; 3 | import { reduxForm } from 'redux-form'; 4 | import * as actions from '../actions'; 5 | 6 | class AddVisitor extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.makePhoto = this.makePhoto.bind(this); 10 | } 11 | componentWillMount() { 12 | this.props.resetAddForm(); 13 | } 14 | 15 | componentWillUnmount() { 16 | //when user leaves the component without making photo or upload after adding visitor 17 | if(this.props.addFlag=='success' && this.props.photoMakeBell !='success'){ 18 | onClick: alert('Adding visitor has been canceled.') 19 | this.onClickFormCancel(); 20 | } 21 | } 22 | 23 | componentDidUpdate() { 24 | // form reset after adding visitor 25 | let addForm = document.getElementById('addVisitorForm'); 26 | setTimeout(() => { 27 | if(this.props.addFlag=='success'&&this.props.photoMakeBell =='success') { 28 | onClick: alert("Succeed to add the visitor."); 29 | this.props.resetAddForm(); 30 | this.resetFormValues(addForm); 31 | } 32 | else { 33 | } 34 | },100); 35 | 36 | 37 | } 38 | onClassAddForm(hideOrShow) { 39 | let newClass = "fadeIn" 40 | if (hideOrShow == 'success'){ 41 | newClass = 'displayNone fadeIn'; 42 | } 43 | else if (hideOrShow == 'fail') { 44 | onClick: alert('There is error while adding a visitor.\nPlease refresh the browser and do it again.') 45 | } 46 | return newClass; 47 | } 48 | 49 | onClassPhoto(hideOrShow) { 50 | let newClass = 'displayNone fadeIn'; 51 | if (hideOrShow == 'success'){ 52 | newClass = 'fadeIn'; 53 | } 54 | return newClass; 55 | } 56 | 57 | handleFormSubmit(formProps) { 58 | this.props.addVisitor(formProps); 59 | setTimeout(() => { 60 | this.props.fetchVisitors(); 61 | }, 100); 62 | } 63 | 64 | makePhoto() { 65 | this.props.ws.send("photo_make"); 66 | this.props.photoMake(); 67 | } 68 | 69 | onClassMakePhoto(hOs) { 70 | let newClass = "btn btn-primary"; 71 | if(hOs == 'success') { 72 | //onClick: alert("success"); 73 | } 74 | else if(hOs == 'fail') { 75 | onClick: alert("fail"); 76 | } 77 | return newClass; 78 | } 79 | 80 | handleFileUpload = e => { 81 | this.props.uploadDocument({ 82 | file: e.target.files[0] 83 | }, this.props.ws); 84 | } 85 | 86 | onClassFileUpload(hOs) { 87 | let newClass = ""; 88 | if(hOs == 'success') { 89 | //onClick: alert("success"); 90 | } 91 | else if(hOs == 'fail') { 92 | onClick: alert("fail"); 93 | 94 | } 95 | return newClass; 96 | } 97 | deleteLatestVisitor() { 98 | this.props.ws.send("cancel"); 99 | } 100 | onClickFormCancel = () => { 101 | if(this.props.addFlag == 'fail' || this.props.addFlag == 'none'){ 102 | onClick: alert("Cancelling without adding basic information is unavailable."); 103 | return 104 | } 105 | let addForm = document.getElementById('addVisitorForm'); 106 | this.props.fetchVisitors(); 107 | this.deleteLatestVisitor(); 108 | this.resetFormValues(addForm); 109 | } 110 | 111 | resetFormValues(addForm) { 112 | setTimeout(() => { 113 | this.props.resetAddForm(); 114 | addForm.reset(); 115 | const {resetForm} = this.props; 116 | resetForm(); 117 | console.log('Form value reset'); 118 | //console.log(addForm); 119 | /*console.log(this.props.values.firstname); 120 | this.props.setProps({ 121 | values: { 122 | firstname: "none" 123 | } 124 | }); 125 | console.log("change to"+this.props.firstname); 126 | console.log("change to"+this.props.values.firstname);*/ 127 | }, 100); 128 | } 129 | render() { 130 | const { handleSubmit, fields: { firstname, lastname, email }} = this.props; 131 | //console.log(this.props.values); 132 | return ( 133 |
134 |
135 |
136 | 137 | 138 | {firstname.touched && firstname.error &&
{firstname.error}
} 139 |
140 |
141 | 142 | 143 | {lastname.touched && lastname.error &&
{lastname.error}
} 144 |
145 |
146 | 147 | 148 | {email.touched && email.error &&
{email.error}
} 149 |
150 | 151 |
152 |
153 |

Basic information is saved.

154 |

Please save your photo via either 'Make photo' or 'File upload'

155 |
156 | 157 |
158 | 159 |
160 | 161 | 162 |
163 |
164 | {/*
165 | this is addflag: {this.props.addFlag} 166 |
167 | this is make: {this.props.photoMakeBell} 168 |
169 | this is upload: {this.props.photoUpload}*/} 170 |
171 | 172 | ); 173 | } 174 | } 175 | 176 | function validate(formProps) { 177 | const errors = {}; 178 | 179 | if (!formProps.firstname) { 180 | errors.firstname = 'Please enter firstname'; 181 | } 182 | 183 | if (!formProps.lastname) { 184 | errors.lastname = 'Please enter lastname'; 185 | } 186 | 187 | if (!formProps.email) { 188 | errors.email = 'Please enter email'; 189 | } 190 | 191 | return errors; 192 | } 193 | 194 | 195 | function mapStateToProps(state) { 196 | return { errorMessage: state.bell.error, ws: state.bell.socket, addFlag: state.bell.visitor_add, 197 | photoUpload: state.bell.photo_upload, photoMakeBell: state.bell.photo_make, /* photoMake was same name as the function photoMake in actions. so Jun changed the name*/ 198 | visitors: state.bell.visitors }; 199 | } 200 | 201 | export default reduxForm({ 202 | form: 'add_visitor', 203 | fields: ['firstname', 'lastname', 'email'], 204 | validate 205 | }, mapStateToProps, actions)(AddVisitor); 206 | -------------------------------------------------------------------------------- /frontend/src/components/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Component } from 'react'; 3 | 4 | import Header from './header'; 5 | 6 | export default class App extends Component { 7 | render() { 8 | return ( 9 |
10 |
11 | {this.props.children} 12 |
13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/components/auth/require_auth.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | export default function(ComposedComponent) { 5 | class Authentication extends Component { 6 | static contextTypes = { 7 | router: React.PropTypes.object 8 | }; 9 | 10 | componentWillMount() { 11 | if (!this.props.authenticated) { 12 | this.context.router.push('/signin'); 13 | } 14 | } 15 | 16 | componentWillUpdate(nextProps) { 17 | if (!nextProps.authenticated) { 18 | this.context.router.push('/signin'); 19 | } 20 | } 21 | 22 | render() { 23 | return 24 | } 25 | } 26 | 27 | function mapStateToProps(state) { 28 | return { authenticated: state.bell.authenticated }; 29 | } 30 | 31 | return connect(mapStateToProps)(Authentication); 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/components/auth/signin.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { reduxForm } from 'redux-form'; 3 | import * as actions from '../../actions'; 4 | 5 | class Signin extends Component { 6 | handleFormSubmit({ email, password }) { 7 | // Need to do something to log user in 8 | this.props.signinUser({ email, password }); 9 | } 10 | 11 | renderAlert() { 12 | if (this.props.errorMessage) { 13 | return ( 14 |
15 | Oops! {this.props.errorMessage} 16 |
17 | ); 18 | } 19 | } 20 | 21 | render() { 22 | const { handleSubmit, fields: { email, password }} = this.props; 23 | 24 | return ( 25 |
26 |
27 |
28 |
29 |

Smart Bell

30 |

The unique mechanism of our SmartBell (to trigger the lock you simply face the door while pressing the doorbell button) is nearly as fast as using a key but far more convenient. 31 | Granting access to the building depending on whether a person has been granted rights to enter makes it the best method for everyday use.

32 |
33 |
34 |

Sign in

35 |
36 |
37 | 38 | 39 |
40 |
41 | 42 | 43 |
44 | {this.renderAlert()} 45 | 46 |
47 |
48 |
49 |
50 |
51 | ); 52 | } 53 | } 54 | 55 | function mapStateToProps(state) { 56 | return { errorMessage: state.bell.error }; 57 | } 58 | 59 | export default reduxForm({ 60 | form: 'signin', 61 | fields: ['email', 'password'] 62 | }, mapStateToProps, actions)(Signin); 63 | -------------------------------------------------------------------------------- /frontend/src/components/auth/signout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import * as actions from '../../actions'; 4 | 5 | class Signout extends Component { 6 | componentWillMount() { 7 | this.props.signoutUser(); 8 | } 9 | 10 | render() { 11 | return
Sorry to see you go...{alert("You successfully signed out")}{window.location = "/"}
; 12 | } 13 | } 14 | 15 | export default connect(null, actions)(Signout); 16 | -------------------------------------------------------------------------------- /frontend/src/components/auth/signup.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { reduxForm } from 'redux-form'; 3 | import * as actions from '../../actions'; 4 | 5 | class Signup extends Component { 6 | handleFormSubmit(formProps) { 7 | // Call action creator to sign up the user! 8 | this.props.signupUser(formProps); 9 | } 10 | 11 | renderAlert() { 12 | if (this.props.errorMessage) { 13 | return ( 14 |
15 | Oops! {this.props.errorMessage} 16 |
17 | ); 18 | } 19 | } 20 | 21 | render() { 22 | const { handleSubmit, fields: { email, password, passwordConfirm }} = this.props; 23 | return ( 24 |
25 |
26 |
27 |
28 |

Smart Bell

29 |

The unique mechanism of our SmartBell (to trigger the lock you simply face the door while pressing the doorbell button) is nearly as fast as using a key but far more convenient. 30 | Granting access to the building depending on whether a person has been granted rights to enter makes it the best method for everyday use.

31 |
32 |
33 |

Create account

34 |
35 |
36 | 37 | 38 | {email.touched && email.error &&
{email.error}
} 39 |
40 |
41 | 42 | 43 | {password.touched && password.error &&
{password.error}
} 44 |
45 |
46 | 47 | 48 | {passwordConfirm.touched && passwordConfirm.error &&
{passwordConfirm.error}
} 49 |
50 | {this.renderAlert()} 51 | 52 |
53 |
54 |
55 |
56 |
57 | ); 58 | } 59 | } 60 | 61 | function validate(formProps) { 62 | const errors = {}; 63 | 64 | if (!formProps.email) { 65 | errors.email = 'Please enter an email'; 66 | } 67 | 68 | if (!formProps.password) { 69 | errors.password = 'Please enter a password'; 70 | } 71 | 72 | if (!formProps.passwordConfirm) { 73 | errors.passwordConfirm = 'Please enter a password confirmation'; 74 | } 75 | 76 | if (formProps.password !== formProps.passwordConfirm) { 77 | errors.password = 'Passwords must match'; 78 | } 79 | 80 | return errors; 81 | } 82 | 83 | function mapStateToProps(state) { 84 | return { errorMessage: state.bell.error }; 85 | } 86 | 87 | export default reduxForm({ 88 | form: 'signup', 89 | fields: ['email', 'password', 'passwordConfirm'], 90 | validate 91 | }, mapStateToProps, actions)(Signup); 92 | -------------------------------------------------------------------------------- /frontend/src/components/content.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import AddVisitor from './add-visitor'; 4 | import Log from './log'; 5 | import Visitor from './visitor'; 6 | import Tab from './tab'; 7 | import Tabs from './tabs'; 8 | 9 | class Content extends Component { 10 | render(){ 11 | return( 12 |
13 | {this.props.currentTab === 1 ? 14 |
15 | 16 |
17 | :null} 18 | 19 | {this.props.currentTab === 2 ? 20 |
21 | 22 |
23 | :null} 24 | 25 | {this.props.currentTab === 3 ? 26 |
27 | 28 |
29 | :null} 30 | 31 |
32 | ); 33 | } 34 | } 35 | export default Content; 36 | -------------------------------------------------------------------------------- /frontend/src/components/control-panel.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import Tab from './tab'; 4 | import Tabs from './tabs'; 5 | import Content from './content'; 6 | 7 | let tabList = [ 8 | { 'id': 1, 'name': 'Entrance log', 'url': '/log' }, 9 | { 'id': 2, 'name': 'Visitors', 'url': '/visitors' }, 10 | { 'id': 3, 'name': 'Add visitor', 'url': '/add' } 11 | ]; 12 | 13 | class ControlPanel extends Component { 14 | constructor(props) { 15 | super(props); 16 | 17 | this.state = { 18 | tabList: tabList, 19 | currentTab: 1 20 | } 21 | } 22 | 23 | changeTab = (tab) => { 24 | this.setState({ currentTab: tab.id }); 25 | } 26 | 27 | render(){ 28 | return( 29 |
30 | 35 |
36 | 37 |
38 |
39 | ); 40 | } 41 | } 42 | 43 | export default ControlPanel; -------------------------------------------------------------------------------- /frontend/src/components/header.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Link } from 'react-router'; 4 | 5 | class Header extends Component { 6 | renderLinks() { 7 | if (this.props.authenticated) { 8 | // show a link to sign out 9 | return 14 | } else { 15 | // show a link to sign in or sign up 16 | return [ 17 | 25 | ]; 26 | } 27 | } 28 | 29 | render() { 30 | return ( 31 | 40 | ); 41 | } 42 | } 43 | 44 | function mapStateToProps(state) { 45 | return { 46 | authenticated: state.bell.authenticated 47 | }; 48 | } 49 | 50 | export default connect(mapStateToProps)(Header); 51 | -------------------------------------------------------------------------------- /frontend/src/components/log.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import * as actions from '../actions'; 4 | 5 | class Log extends Component { 6 | constructor(props) { 7 | super(props); 8 | 9 | this.handlePageChanged = this.handlePageChanged.bind(this); 10 | 11 | this.state = { 12 | current: 1, 13 | perPage: 3, 14 | visiblePage: 5 15 | }; 16 | } 17 | 18 | componentWillMount() { 19 | this.props.fetchLogs(); 20 | } 21 | 22 | handlePageChanged(newPage) { 23 | console.log(newPage); 24 | this.setState({ current : newPage }); 25 | setTimeout(()=> { 26 | console.log(this.state.current); 27 | 28 | },500) 29 | } 30 | 31 | renderPage() { 32 | if(this.props.logs){ 33 | let i = 0; 34 | let itemsTotal = this.props.logs.length; 35 | let pagesTotal = Math.ceil( itemsTotal/this.state.perPage); 36 | let currPage = this.state.current; 37 | let visiblePage = this.state.visiblePage; 38 | 39 | let leftPages, rightPages; 40 | // console.log('total page is: ' + pagesTotal); 41 | return this.props.logs.map((log) => { 42 | let id = log._id; 43 | i++; 44 | // if the number of visible pages is bigger than total number of pages, show all 45 | if (visiblePage < pagesTotal){ 46 | // if currunt page is leftside than half page of the visible pages. 47 | if(currPage - 1 < Math.floor(visiblePage / 2)) { 48 | // console.log('smaller than half'); 49 | if(i <= visiblePage) 50 | { 51 | if(i==currPage){ 52 | return(
  • {i}
  • ) 53 | } 54 | else{ 55 | return(
  • this.handlePageChanged(e.currentTarget.value)} value={i} key={id}>{i}
  • ) 56 | } 57 | } 58 | } 59 | // if active page is closer to end of pages 60 | else if(currPage + (visiblePage/2) > pagesTotal) { 61 | leftPages = pagesTotal - visiblePage; 62 | rightPages = pagesTotal; 63 | // console.log('left is: ' + leftPages); 64 | // console.log('right is: ' + rightPages); 65 | if(i <= rightPages && i > leftPages) 66 | { 67 | if(i==currPage){ 68 | return(
  • {i}
  • ) 69 | } 70 | else{ 71 | return(
  • this.handlePageChanged(e.currentTarget.value)} value={i} key={id}>{i}
  • ) 72 | } 73 | } 74 | } 75 | // normal cases 76 | else{ 77 | leftPages = currPage - (visiblePage/2); 78 | rightPages = currPage + (visiblePage/2); 79 | 80 | // console.log('normal'); 81 | if(i > leftPages && i < rightPages ) 82 | { 83 | if(i==currPage){ 84 | return(
  • {i}
  • ) 85 | } 86 | else{ 87 | return(
  • this.handlePageChanged(e.currentTarget.value)} value={i} key={id}>{i}
  • ) 88 | } 89 | } 90 | } 91 | } 92 | else { 93 | if(i <= pagesTotal && i > 0) 94 | { 95 | if(i==currPage){ 96 | return(
  • {i}
  • ) 97 | } 98 | else{ 99 | return(
  • this.handlePageChanged(e.currentTarget.value)} value={i} key={id}>{i}
  • ) 100 | } 101 | } 102 | } 103 | }) 104 | } 105 | } 106 | handleFirst = (currPage) => { 107 | if(currPage!=1){ 108 | this.setState({ 109 | current: 1 110 | }); 111 | } 112 | } 113 | handlePrev = (currPage) => { 114 | if(currPage!=1){ 115 | this.setState({ 116 | current: currPage-1 117 | }); 118 | } 119 | } 120 | handleNext = (currPage) => { 121 | if(this.props.logs){ 122 | let itemsTotal = this.props.logs.length; 123 | let pagesTotal = Math.ceil( itemsTotal/this.state.perPage); 124 | if(currPage != pagesTotal){ 125 | this.setState({ 126 | current: currPage+1 127 | }); 128 | } 129 | } 130 | } 131 | handleLast = (currPage) => { 132 | if(this.props.logs){ 133 | let itemsTotal = this.props.logs.length; 134 | let pagesTotal = Math.ceil( itemsTotal/this.state.perPage); 135 | if(currPage != pagesTotal){ 136 | this.setState({ 137 | current: pagesTotal 138 | }); 139 | } 140 | } 141 | } 142 | renderPaginate() { 143 | if(this.props.logs){ 144 | let itemsTotal= this.props.logs.length; 145 | let pagesTotal = Math.ceil( itemsTotal/this.state.perPage); 146 | if (itemsTotal == 0){ 147 | return 'No entrance log' 148 | } 149 | else if (itemsTotal <= this.state.perPage){ 150 | return 151 | } 152 | return( 153 |
    154 | 161 |
    162 | ) 163 | } 164 | } 165 | renderLogs() { 166 | if (this.props.logs) { 167 | let i = 0; 168 | return this.props.logs.map((log) => { 169 | i++; 170 | if(i-1 >= (this.state.current-1)*this.state.perPage && i-1 173 |
    174 |

    Name: {log.firstname} {log.lastname}

    175 |

    Time: {log.time}

    176 |

    Permission: {log.access}

    177 |
    178 |
    179 | 180 |
    181 |
    182 | 183 | 184 | ); 185 | } 186 | }); 187 | } 188 | } 189 | handlePerPage(perPage){ 190 | this.setState({perPage}); 191 | } 192 | perPageRender(){ 193 | return( 194 |
    195 | 196 | 198 |
      199 |
    • this.handlePerPage(e.currentTarget.value)} value="3" role="presentation">3
    • 200 |
    • this.handlePerPage(e.currentTarget.value)} value="5" role="presentation">5
    • 201 |
    • this.handlePerPage(e.currentTarget.value)} value="10" role="presentation">10
    • 202 | {/*
    • */} 203 |
    • this.handlePerPage(e.currentTarget.value)} value="15" role="presentation">15
    • 204 |
    • this.handlePerPage(e.currentTarget.value)} value="20" role="presentation">20
    • 205 |
    206 |
    207 | ); 208 | } 209 | handleVisiblePage(visiblePage){ 210 | this.setState({visiblePage}); 211 | } 212 | visiblePageRender(){ 213 | return( 214 |
    215 | 216 | 218 |
      219 |
    • this.handleVisiblePage(e.currentTarget.value)} value="3" role="presentation">3
    • 220 |
    • this.handleVisiblePage(e.currentTarget.value)} value="5" role="presentation">5
    • 221 |
    • this.handleVisiblePage(e.currentTarget.value)} value="10" role="presentation">10
    • 222 | {/*
    • */} 223 |
    • this.handleVisiblePage(e.currentTarget.value)} value="15" role="presentation">15
    • 224 |
    • this.handleVisiblePage(e.currentTarget.value)} value="20" role="presentation">20
    • 225 |
    226 |
    227 | ); 228 | } 229 | render() { 230 | return ( 231 |
    232 |
    233 | {this.visiblePageRender()} 234 |
    235 |
    236 | {this.perPageRender()} 237 |
    238 |
    239 |
      240 | {this.renderLogs()} 241 |
    242 |
    243 | {this.renderPaginate()} 244 |
    245 |
    246 | ); 247 | } 248 | } 249 | 250 | function mapStateToProps(state) { 251 | return { logs: state.bell.logs }; 252 | } 253 | 254 | export default connect(mapStateToProps, actions)(Log); 255 | -------------------------------------------------------------------------------- /frontend/src/components/stream.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import ControlPanel from './control-panel'; 3 | import {connect} from 'react-redux'; 4 | import * as actions from '../actions'; 5 | 6 | class Stream extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = {url: ""}; 10 | } 11 | 12 | // Setting up websocket client connection 13 | componentWillMount() { 14 | this.ws = new WebSocket("ws://localhost:8000/ws"); 15 | this.ws.onopen = () => { 16 | this.ws.binaryType = "arraybuffer"; 17 | this.props.addSocketToState(this.ws); 18 | console.log("opened socket"); 19 | }; 20 | } 21 | 22 | componentDidMount() { 23 | this.ws.onmessage = msg => { 24 | if (msg.data == 'log') { 25 | this.props.fetchLogs(); 26 | } else if (msg.data == 'success' || msg.data == 'fail') { 27 | this.props.photoMake(msg.data); 28 | } else { 29 | var url = 'data:image/jpg;base64,'+msg.data 30 | this.setState({url: url}); 31 | } 32 | 33 | }; 34 | } 35 | 36 | componentWillUnmount(){ 37 | this.ws.close(); 38 | } 39 | 40 | render() { 41 | return ( 42 |
    43 |
    44 |
    45 |
    46 | 47 |
    48 |
    49 |
    50 | 51 |
    52 |
    53 |
    54 | ); 55 | } 56 | 57 | } 58 | 59 | export default connect(null, actions)(Stream); 60 | -------------------------------------------------------------------------------- /frontend/src/components/tab.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class Tab extends Component { 4 | handleClick = (e) => { 5 | e.preventDefault(); 6 | this.props.handleClick(); 7 | } 8 | 9 | render(){ 10 | return ( 11 |
  • 12 | 13 | {this.props.name} 14 | 15 |
  • 16 | ); 17 | } 18 | } 19 | 20 | export default Tab; -------------------------------------------------------------------------------- /frontend/src/components/tabs.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import Tab from './tab'; 4 | 5 | 6 | let tabList = [ 7 | { 'id': 1, 'name': 'Entrance log', 'url': '/log' }, 8 | { 'id': 2, 'name': 'Visitors', 'url': '/visitors' }, 9 | { 'id': 3, 'name': 'Add visitor', 'url': '/add' } 10 | ]; 11 | 12 | class Tabs extends Component{ 13 | handleClick = (tab) => { 14 | this.props.changeTab(tab); 15 | } 16 | 17 | render(){ 18 | return ( 19 | 34 | ); 35 | } 36 | } 37 | export default Tabs; 38 | -------------------------------------------------------------------------------- /frontend/src/components/visitor.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import * as actions from '../actions'; 4 | 5 | /* 6 | import ReactConfirmAlert, { confirmAlert } from 'react-confirm-alert'; // refer https://www.npmjs.com/package/react-confirm-alert 7 | import 'react-confirm-alert/src/react-confirm-alert.css'; 8 | */ 9 | class Visitor extends Component { 10 | 11 | constructor(props) { 12 | super(props); 13 | 14 | this.deleteVisitor = this.deleteVisitor.bind(this); 15 | this.handleClick = this.handleClick.bind(this); 16 | 17 | //pagination 18 | this.handlePageChanged = this.handlePageChanged.bind(this); 19 | 20 | this.state = { 21 | current: 1, 22 | perPage: 10, 23 | visiblePage: 5, 24 | delete: 0 25 | }; 26 | } 27 | /* // for better alerting dialog 28 | submit = (id) => e => { 29 | confirmAlert({ 30 | title: 'Confirm to delete.', // Title dialog 31 | message: 'Are you sure to delete this visitor?', // Message dialog 32 | childrenElement: () =>
    , // Custom UI or Component 33 | confirmLabel: 'Confirm', // Text button confirm 34 | cancelLabel: 'Cancel', // Text button cancel 35 | onConfirm: this.deleteVisitor(id), // Action after Confirm 36 | onCancel: () => close() // Action after Cancel 37 | }); 38 | 39 | };*/ 40 | componentWillMount() { 41 | this.props.fetchVisitors(); 42 | } 43 | 44 | componentDidUpdate() { 45 | if (this.props.deleteFlag == 'success') { 46 | this.props.fetchVisitors(); 47 | } 48 | } 49 | 50 | handleClick = (id, access) => e => { 51 | if (access == true) { 52 | this.props.toogleAccess(id, false); 53 | } else { 54 | this.props.toogleAccess(id, true); 55 | } 56 | 57 | setTimeout(() => { 58 | console.log('visitor reloaded'); 59 | let doorPermission = access == false? 'Allowed' : 'Denied'; 60 | let alertStr = 'Permission has been changed: ' + doorPermission; 61 | onClick: alert(alertStr); 62 | this.props.fetchVisitors(); 63 | },150); 64 | }; 65 | 66 | deleteVisitor = (id) => e => { 67 | onClick: if(confirm('Are you sure to delete this visitor?')){ 68 | this.props.deleteVisitor(id); 69 | this.props.ws.send(id); // when it doesn't connect to websocket, it makes fetchvisitors fn disable 70 | this.setState({delete: 1}); 71 | } 72 | }; 73 | 74 | 75 | renderVisitors() { 76 | if (this.props.visitors) { 77 | return this.props.visitors.map((visitor) => { 78 | let id = visitor._id; 79 | return ( 80 |
  • 81 | {visitor.firstname} {visitor.lastname} 82 | 83 | 84 |
  • 85 | ); 86 | }); 87 | } 88 | } 89 | 90 | //pagination start 91 | handlePageChanged(newPage) { 92 | console.log(newPage); 93 | this.setState({ current : newPage }); 94 | setTimeout(()=> { 95 | console.log(this.state.current); 96 | 97 | },500) 98 | } 99 | 100 | renderPage() { 101 | if(this.props.visitors){ 102 | let i = 0; 103 | let itemsTotal = this.props.visitors.length; 104 | let pagesTotal = Math.ceil( itemsTotal/this.state.perPage); 105 | let currPage = this.state.current; 106 | let visiblePage = this.state.visiblePage; 107 | 108 | let leftPages, rightPages; 109 | // console.log('total page is: ' + pagesTotal); 110 | return this.props.visitors.map((visitor) => { 111 | let id = visitor._id; 112 | i++; 113 | // if the number of visible pages is bigger than total number of pages, show all 114 | if (visiblePage < pagesTotal){ 115 | // if currunt page is leftside than half page of the visible pages. 116 | if(currPage - 1 < Math.floor(visiblePage / 2)) { 117 | // console.log('smaller than half'); 118 | if(i <= visiblePage) 119 | { 120 | if(i==currPage){ 121 | return(
  • {i}
  • ) 122 | } 123 | else{ 124 | return(
  • this.handlePageChanged(e.currentTarget.value)} value={i} key={id}>{i}
  • ) 125 | } 126 | } 127 | } 128 | // if active page is closer to end of pages 129 | else if(currPage + (visiblePage/2) > pagesTotal) { 130 | leftPages = pagesTotal - visiblePage; 131 | rightPages = pagesTotal; 132 | // console.log('left is: ' + leftPages); 133 | // console.log('right is: ' + rightPages); 134 | if(i <= rightPages && i > leftPages) 135 | { 136 | if(i==currPage){ 137 | return(
  • {i}
  • ) 138 | } 139 | else{ 140 | return(
  • this.handlePageChanged(e.currentTarget.value)} value={i} key={id}>{i}
  • ) 141 | } 142 | } 143 | } 144 | // normal cases 145 | else{ 146 | leftPages = currPage - (visiblePage/2); 147 | rightPages = currPage + (visiblePage/2); 148 | // console.log('normal'); 149 | if(i > leftPages && i < rightPages ) 150 | { 151 | if(i==currPage){ 152 | return(
  • {i}
  • ) 153 | } 154 | else{ 155 | return(
  • this.handlePageChanged(e.currentTarget.value)} value={i} key={id}>{i}
  • ) 156 | } 157 | } 158 | } 159 | } 160 | else { 161 | // console.log('here'); 162 | // console.log(pagesTotal); 163 | if(i <= pagesTotal && i > 0) 164 | { 165 | if(i==currPage){ 166 | return(
  • {i}
  • ) 167 | } 168 | else{ 169 | return(
  • this.handlePageChanged(e.currentTarget.value)} value={i} key={id}>{i}
  • ) 170 | } 171 | } 172 | } 173 | }) 174 | } 175 | } 176 | handleFirst = (currPage) => { 177 | if(currPage!=1){ 178 | this.setState({ 179 | current: 1 180 | }); 181 | } 182 | } 183 | handlePrev = (currPage) => { 184 | if(currPage!=1){ 185 | this.setState({ 186 | current: currPage-1 187 | }); 188 | } 189 | } 190 | handleNext = (currPage) => { 191 | if(this.props.visitors){ 192 | let itemsTotal = this.props.visitors.length; 193 | let pagesTotal = Math.ceil( itemsTotal/this.state.perPage); 194 | if(currPage != pagesTotal){ 195 | this.setState({ 196 | current: currPage+1 197 | }); 198 | } 199 | } 200 | } 201 | handleLast = (currPage) => { 202 | if(this.props.visitors){ 203 | let itemsTotal = this.props.visitors.length; 204 | let pagesTotal = Math.ceil( itemsTotal/this.state.perPage); 205 | if(currPage != pagesTotal){ 206 | this.setState({ 207 | current: pagesTotal 208 | }); 209 | } 210 | } 211 | } 212 | renderPaginate() { 213 | if(this.props.visitors){ 214 | let itemsTotal= this.props.visitors.length; 215 | let pagesTotal = Math.ceil( itemsTotal/this.state.perPage); 216 | if (itemsTotal == 0){ 217 | return 'No visitors registered' 218 | } 219 | else if (itemsTotal <= this.state.perPage){ 220 | return 221 | } 222 | return( 223 |
    224 |
      225 |
    • this.handleFirst(e.currentTarget.value)} value={this.state.current}>1
    • 226 |
    • this.handlePrev(e.currentTarget.value)} value={this.state.current}>Prev
    • 227 | {this.renderPage()} 228 |
    • this.handleNext(e.currentTarget.value)} value={this.state.current}>Next
    • 229 |
    • this.handleLast(e.currentTarget.value)} value={this.state.current}>{pagesTotal}
    • 230 |
    231 |
    232 | ) 233 | } 234 | } 235 | renderVisitorsPagination() { 236 | if (this.props.visitors) { 237 | let i = 0; 238 | return this.props.visitors.map((visitor) => { 239 | let id = visitor._id; 240 | i++; 241 | if(i-1 >= (this.state.current-1)*this.state.perPage && i-1 244 | {visitor.firstname} {visitor.lastname} 245 | 246 | 247 | 248 | ); 249 | } 250 | }); 251 | } 252 | } 253 | handlePerPage(perPage){ 254 | this.setState({perPage}); 255 | } 256 | perPageRender(){ 257 | return( 258 |
    259 | 260 | 262 |
      263 |
    • this.handlePerPage(e.currentTarget.value)} value="3" role="presentation">3
    • 264 |
    • this.handlePerPage(e.currentTarget.value)} value="5" role="presentation">5
    • 265 |
    • this.handlePerPage(e.currentTarget.value)} value="10" role="presentation">10
    • 266 | {/*
    • */} 267 |
    • this.handlePerPage(e.currentTarget.value)} value="15" role="presentation">15
    • 268 |
    • this.handlePerPage(e.currentTarget.value)} value="20" role="presentation">20
    • 269 |
    270 |
    271 | ); 272 | } 273 | handleVisiblePage(visiblePage){ 274 | this.setState({visiblePage}); 275 | } 276 | visiblePageRender(){ 277 | return( 278 |
    279 | 280 | 282 |
      283 |
    • this.handleVisiblePage(e.currentTarget.value)} value="3" role="presentation">3
    • 284 |
    • this.handleVisiblePage(e.currentTarget.value)} value="5" role="presentation">5
    • 285 |
    • this.handleVisiblePage(e.currentTarget.value)} value="10" role="presentation">10
    • 286 | {/*
    • */} 287 |
    • this.handleVisiblePage(e.currentTarget.value)} value="15" role="presentation">15
    • 288 |
    • this.handleVisiblePage(e.currentTarget.value)} value="20" role="presentation">20
    • 289 |
    290 |
    291 | ); 292 | } 293 | //pagination end 294 | 295 | render() { 296 | return ( 297 |
    298 |
    299 | {this.visiblePageRender()} 300 |
    301 |
    302 | {this.perPageRender()} 303 |
    304 |
    305 |
      306 | {this.renderVisitorsPagination()} 307 |
    308 |
    309 | {this.renderPaginate()} 310 |
    311 |
    312 | ); 313 | } 314 | } 315 | 316 | function mapStateToProps(state) { 317 | return { visitors: state.bell.visitors, ws: state.bell.socket, deleteFlag: state.bell.visitor_delete }; 318 | } 319 | 320 | export default connect(mapStateToProps, actions)(Visitor); 321 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { createStore, applyMiddleware } from 'redux'; 5 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 6 | import reduxThunk from 'redux-thunk'; 7 | 8 | import App from './components/app'; 9 | import Signin from './components/auth/signin'; 10 | import Signout from './components/auth/signout'; 11 | import Signup from './components/auth/signup'; 12 | import RequireAuth from './components/auth/require_auth'; 13 | import Stream from './components/stream'; 14 | import reducers from './reducers'; 15 | import { AUTH_USER } from './actions/types'; 16 | 17 | const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore); 18 | const store = createStoreWithMiddleware( 19 | reducers, 20 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 21 | ); 22 | 23 | const token = localStorage.getItem('token'); 24 | // If we have a token, consider the user to be signed in 25 | if (token) { 26 | // we need to update application state 27 | store.dispatch({ type: AUTH_USER }); 28 | } 29 | 30 | ReactDOM.render( 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | , document.querySelector('.mycontainer')); 42 | -------------------------------------------------------------------------------- /frontend/src/reducers/bell_reducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | AUTH_USER, 3 | UNAUTH_USER, 4 | AUTH_ERROR, 5 | FETCH_LOGS, 6 | FETCH_VISITORS, 7 | SOCKET_STATE, 8 | UPLOAD_DOCUMENT_FAIL, 9 | UPLOAD_DOCUMENT_SUCCESS, 10 | VISITOR_ADD_SUCCESS, 11 | VISITOR_ADD_FAIL, 12 | VISITOR_DELETE_SUCCESS, 13 | VISITOR_DELETE_FAIL, 14 | MAKE_PHOTO_SUCCESS, 15 | MAKE_PHOTO_FAIL, 16 | RESET_ADD_FORM 17 | } from '../actions/types'; 18 | 19 | export default function(state = {visitor_add: 'none', visitor_delete: 'none', photo_make: 'none', photo_upload: 'none'}, action) { 20 | switch(action.type) { 21 | case AUTH_USER: 22 | return { ...state, error: '', authenticated: true }; 23 | case UNAUTH_USER: 24 | return { ...state, authenticated: false }; 25 | case AUTH_ERROR: 26 | return { ...state, error: action.payload }; 27 | case SOCKET_STATE: 28 | return { ...state, socket: action.payload }; 29 | case FETCH_LOGS: 30 | return { ...state, logs: action.payload }; 31 | case FETCH_VISITORS: 32 | return { ...state, visitors: action.payload }; 33 | case VISITOR_ADD_SUCCESS: 34 | return { ...state, visitor_add: action.payload }; 35 | case VISITOR_ADD_FAIL: 36 | return { ...state, visitor_add: action.payload }; 37 | case VISITOR_DELETE_SUCCESS: 38 | return { ...state, visitor_delete: action.payload }; 39 | case VISITOR_DELETE_FAIL: 40 | return { ...state, visitor_delete: action.payload }; 41 | case MAKE_PHOTO_SUCCESS: 42 | return { ...state, photo_make: action.payload }; 43 | case MAKE_PHOTO_FAIL: 44 | return { ...state, photo_make: action.payload }; 45 | case UPLOAD_DOCUMENT_SUCCESS: 46 | return { ...state, photo_upload: action.payload }; 47 | case UPLOAD_DOCUMENT_FAIL: 48 | return { ...state, photo_upload: action.payload }; 49 | case RESET_ADD_FORM: 50 | return { ...state, visitor_add: 'none', photo_make: 'none', photo_upload: 'none' }; 51 | } 52 | 53 | return state; 54 | } -------------------------------------------------------------------------------- /frontend/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { reducer as form } from 'redux-form'; 3 | import authReducer from './bell_reducer'; 4 | 5 | const rootReducer = combineReducers({ 6 | form, 7 | bell: authReducer, 8 | }); 9 | 10 | export default rootReducer; -------------------------------------------------------------------------------- /frontend/style/static/images/bg-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmitrykhramov/Smart-Bell/4f3e74eac3bbe09d5286a9df0f1ecff5ad603418/frontend/style/static/images/bg-main.png -------------------------------------------------------------------------------- /frontend/style/static/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmitrykhramov/Smart-Bell/4f3e74eac3bbe09d5286a9df0f1ecff5ad603418/frontend/style/static/images/bg.jpg -------------------------------------------------------------------------------- /frontend/style/style.css: -------------------------------------------------------------------------------- 1 | .divisionLine { 2 | clear: both; 3 | } 4 | .error { 5 | color: red; 6 | } 7 | 8 | .nopadding{ 9 | margin: 0; 10 | padding: 0; 11 | line-height: 2 !important; 12 | } 13 | 14 | /*Custom CSS*/ 15 | .wrapper{ 16 | display: table; 17 | } 18 | 19 | .content-wrapper { 20 | height: 100vh; 21 | width: 100vw; 22 | display: table-cell; 23 | } 24 | 25 | .btn-custom { 26 | background-color: transparent !important; 27 | } 28 | 29 | .bg-signin { 30 | background-image: url('./static/images/bg.jpg'); 31 | background-size: cover; 32 | } 33 | 34 | .bg-main { 35 | background-image: url('./static/images/bg-main.png'); 36 | background-size: cover; 37 | } 38 | 39 | .visitor-line{ 40 | line-height: 3.0 !important; 41 | } 42 | 43 | 44 | .list-group-item{ 45 | font-size: 15px; 46 | background: transparent !important; 47 | } 48 | 49 | .navbar-slim{ 50 | margin-bottom: 0px !important; 51 | } 52 | 53 | .navbar-custom{ 54 | margin-bottom: 0px !important; 55 | font-size: 18px; 56 | background: black; 57 | } 58 | 59 | nav li { 60 | display: inline-block; 61 | margin-right: 5px; 62 | } 63 | nav li a { 64 | display: inline-block; 65 | padding: 8px 10px; 66 | color: #8c8caf; 67 | text-decoration: none; 68 | } 69 | nav li a:hover { 70 | color: #fff; 71 | background: #68686d; 72 | text-decoration: none; 73 | } 74 | nav li.current a { 75 | color: #fff; 76 | background: #b3b2ba; 77 | text-decoration: none; 78 | } 79 | 80 | .nav-link { 81 | font-size: 19px !important; 82 | } 83 | 84 | a { 85 | text-decoration: none; 86 | } 87 | 88 | .thumbnail img { 89 | width: 100%; 90 | height: auto; 91 | max-height: 100%; 92 | } 93 | .displayNone { 94 | display: none; 95 | } 96 | .fadeIn{ 97 | color:white; 98 | -webkit-animation: fadein 0.7s; /* Safari, Chrome and Opera > 12.1 */ 99 | -moz-animation: fadein 0.7s; /* Firefox < 16 */ 100 | -ms-animation: fadein 0.7s; /* Internet Explorer */ 101 | -o-animation: fadein 0.7s; /* Opera < 12.1 */ 102 | animation: fadein 0.7s; 103 | } 104 | 105 | @keyframes fadein { 106 | from { opacity: 0; } 107 | to { opacity: 1; } 108 | } 109 | 110 | /* Firefox < 16 */ 111 | @-moz-keyframes fadein { 112 | from { opacity: 0; } 113 | to { opacity: 1; } 114 | } 115 | 116 | /* Safari, Chrome and Opera > 12.1 */ 117 | @-webkit-keyframes fadein { 118 | from { opacity: 0; } 119 | to { opacity: 1; } 120 | } 121 | 122 | /* Internet Explorer */ 123 | @-ms-keyframes fadein { 124 | from { opacity: 0; } 125 | to { opacity: 1; } 126 | } 127 | 128 | /* Opera < 12.1 */ 129 | @-o-keyframes fadein { 130 | from { opacity: 0; } 131 | to { opacity: 1; } 132 | } 133 | .fadeOut{ 134 | -webkit-animation: fadeout 0.7s; /* Safari, Chrome and Opera > 12.1 */ 135 | -moz-animation: fadeout 0.7s; /* Firefox < 16 */ 136 | -ms-animation: fadeout 0.7s; /* Internet Explorer */ 137 | -o-animation: fadeout 0.7s; /* Opera < 12.1 */ 138 | animation: fadeout 0.7s; 139 | } 140 | 141 | @keyframes fadeout { 142 | from { opacity: 1; } 143 | to { opacity: 0; } 144 | } 145 | 146 | /* Firefox < 16 */ 147 | @-moz-keyframes fadeout { 148 | from { opacity: 1; } 149 | to { opacity: 0; } 150 | } 151 | 152 | /* Safari, Chrome and Opera > 12.1 */ 153 | @-webkit-keyframes fadeout { 154 | from { opacity: 1; } 155 | to { opacity: 0; } 156 | } 157 | 158 | /* Internet Explorer */ 159 | @-ms-keyframes fadeout { 160 | from { opacity: 1; } 161 | to { opacity: 0; } 162 | } 163 | 164 | /* Opera < 12.1 */ 165 | @-o-keyframes fadeout { 166 | from { opacity: 1; } 167 | to { opacity: 0; } 168 | } 169 | 170 | .pagination a:hover { 171 | cursor: pointer; 172 | } 173 | -------------------------------------------------------------------------------- /frontend/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: [ 3 | './src/index.js' 4 | ], 5 | output: { 6 | path: __dirname, 7 | publicPath: '/', 8 | filename: 'bundle.js' 9 | }, 10 | module: { 11 | loaders: [{ 12 | exclude: /node_modules/, 13 | loader: 'babel', 14 | query: { 15 | presets: ['react', 'es2015', 'stage-1'] 16 | }, 17 | }, 18 | { 19 | test: /\.css$/, 20 | loader: "style-loader!css-loader" 21 | }, 22 | { 23 | test: /\.json$/, 24 | loader: "json-loader" 25 | } 26 | ], 27 | }, 28 | resolve: { 29 | extensions: ['', '.js', '.jsx'] 30 | }, 31 | devServer: { 32 | historyApiFallback: true, 33 | contentBase: './' 34 | } 35 | }; -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | /frontend/node_modules 2 | /server/node_modules 3 | /frontend/bundle.js 4 | /frontend/npm-debug.log 5 | /frontend/.DS_Store 6 | 7 | # IntelliJ 8 | *.iml 9 | /.idea 10 | python-stream/*.pyc 11 | *.pyc 12 | pics 13 | log 14 | known_faces_encoding.py -------------------------------------------------------------------------------- /python/collect.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | import face_recognition 4 | import dlib 5 | import pickle 6 | import os 7 | import save 8 | from pymongo import MongoClient 9 | 10 | mongo_db = MongoClient('localhost',27017) 11 | db = mongo_db.smartbell.visitors 12 | 13 | def encoding_picture(picture_path, __id): 14 | ''' 15 | This function is to collect encoding face features. 16 | The photo is encoded and saved with associated visitor's id. 17 | If it's first time to dump, just dump it. 18 | Otherwise, load 'faces_encodings.txt', append it to registered data, then dump. 19 | ''' 20 | try: 21 | print("encoding") 22 | image = face_recognition.load_image_file(picture_path) 23 | small_image = cv2.resize(image, (0, 0), fx=0.25, fy=0.25) 24 | # Encode the visitor's face 25 | image_face_encoding = face_recognition.face_encodings(small_image)[0] 26 | # Associate visitor's id and encoding data of visitor's face 27 | data = [[__id],image_face_encoding] 28 | 29 | # Save the associated data 30 | # If it's first time, just dump 31 | if os.path.getsize('faces_encodings.txt') == 0: 32 | with open('faces_encodings.txt','wb') as f: 33 | pickle.dump(data,f) 34 | else: 35 | # Load the registered data 36 | with open('faces_encodings.txt','rb') as f: 37 | known_faces_encoding_data = pickle.load(f) 38 | # Append [[visitor's id],[the encoding data of visitor's face]] to the registered data 39 | known_faces_encoding_data = np.vstack((known_faces_encoding_data,data)) 40 | # Then, dump 41 | with open('faces_encodings.txt','wb') as f: 42 | pickle.dump(known_faces_encoding_data, f) 43 | return "success" 44 | 45 | except EOFError: 46 | return "fail" 47 | 48 | def upload_photo(): 49 | ''' 50 | This function is one of ways for collecting face features and is executed When administrator uploads the new visitor' photo. 51 | It saves the photo to static path which is 'pics/upload'. 52 | ''' 53 | print("upload photo func") 54 | save.make_directory('pics/upload') 55 | visitors = db.find_one(sort=[('_id',-1)]) 56 | 57 | return encoding_picture('pics/upload/img0.jpg', visitors['_id']) 58 | 59 | def cancel_add(): 60 | ''' 61 | This function is executed when administrator cancles to add a visitor. 62 | It finds last person in database and deletes it. 63 | ''' 64 | print("cancel add visitor") 65 | visitors = db.find_one(sort=[('_id',-1)]) 66 | db.remove({'_id':visitors['_id']}) 67 | print("cancel success") 68 | 69 | def make_photo(frame): 70 | ''' 71 | This function is one of ways for collecting face features and is exctued when administrator makes photo. 72 | It checks whether the photo is enough to encode or not. 73 | If the photo is enough, it saves the photo to static path which is 'pics/img0.jpg' and calls encoding_picture function. 74 | If you don't save the photo as .jpg format and immediately save the photo from usb camera, encoding face features should be different and incorrect. 75 | So, the photo has to be saved as .jpg format before excuting encoding function. 76 | Otherwise, it returns fail. 77 | ''' 78 | save.make_directory('pics') 79 | visitors = db.find_one(sort=[('_id',-1)]) 80 | 81 | # It affects dlib speed. If frame size is small, dlib would be faster than normal size frame 82 | small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25) 83 | # Detect face in the frame 84 | face_locations = face_recognition.face_locations(small_frame) 85 | 86 | # If not detect face 87 | if len(face_locations) == 0: 88 | print("face detection fails") 89 | return "fail" 90 | 91 | else: 92 | path = 'pics/img0.jpg' 93 | # Save the visitor's face as '.jpg' 94 | save.save_photo(path, frame) 95 | result = encoding_picture(path, visitors['_id']) 96 | 97 | if result == "success": 98 | print("Success to register") 99 | else: 100 | print("Chcek picture path") 101 | 102 | return result 103 | -------------------------------------------------------------------------------- /python/delete.py: -------------------------------------------------------------------------------- 1 | from bson.objectid import ObjectId 2 | import pickle 3 | import numpy as np 4 | 5 | def delete_face(visitor_id): 6 | ''' 7 | This function is to delete visitor. 8 | If you click delete button which is for deleting visitor, we would read 'faces_encodings.txt' which is registered data. 9 | And, check whether the visitor is deleted correctly. 10 | If not, we would returns delete_flag which is False. 11 | Or, we would find the data associated with the id and delete it at the registered data. 12 | Then, dump it and return delete_flag which is True. 13 | ''' 14 | try: 15 | # Load registered data 16 | with open('faces_encodings.txt','r') as f: 17 | known_faces_encoding = pickle.load(f) 18 | except EOFError: 19 | print("Failed to load faces_encodings.txt") 20 | return 0 21 | 22 | file_length = len(known_faces_encoding) 23 | 24 | # Flag to delete visitor correctly 25 | delete_flag = False 26 | 27 | # If only one visitor is registered at 'faces_encodings.txt', check id and just delete all 28 | if (file_length == 2 and len(known_faces_encoding[0]) == 1): 29 | __id, encodings = known_faces_encoding 30 | if __id == [ObjectId(visitor_id)]: 31 | delete_flag = True 32 | print("Delete {}".format(__id)) 33 | # Delete all 34 | with open('faces_encodings.txt', 'w'): 35 | pass 36 | 37 | # If two or more visitors are registered in 'faces_encodings.txt' 38 | else: 39 | for i in range(file_length): 40 | # Compare the id with each ids of registered data 41 | __id, encodings = known_faces_encoding[i] 42 | if __id == [ObjectId(visitor_id)]: 43 | delete_flag = True 44 | print("Delete {}".format(__id)) 45 | # Delete all data which we want to delete from 'faces_encodings.txt' 46 | known_faces_encoding = np.delete(known_faces_encoding, (i),0) 47 | with open('faces_encodings.txt','w') as wf: 48 | pickle.dump(known_faces_encoding, wf) 49 | break 50 | 51 | return delete_flag 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /python/faces_encodings.txt: -------------------------------------------------------------------------------- 1 | cnumpy.core.multiarray 2 | _reconstruct 3 | p0 4 | (cnumpy 5 | ndarray 6 | p1 7 | (I0 8 | tp2 9 | S'b' 10 | p3 11 | tp4 12 | Rp5 13 | (I1 14 | (I3 15 | I2 16 | tp6 17 | cnumpy 18 | dtype 19 | p7 20 | (S'O4' 21 | p8 22 | I0 23 | I1 24 | tp9 25 | Rp10 26 | (I3 27 | S'|' 28 | p11 29 | NNNI-1 30 | I-1 31 | I63 32 | tp12 33 | bI00 34 | (lp13 35 | (lp14 36 | ccopy_reg 37 | _reconstructor 38 | p15 39 | (cbson.objectid 40 | ObjectId 41 | p16 42 | c__builtin__ 43 | object 44 | p17 45 | Ntp18 46 | Rp19 47 | S'Y\xdc\x9aB\xc4"H\r\x8e\x1d7\x14' 48 | p20 49 | baag0 50 | (g1 51 | (I0 52 | tp21 53 | g3 54 | tp22 55 | Rp23 56 | (I1 57 | (I128 58 | tp24 59 | g7 60 | (S'f8' 61 | p25 62 | I0 63 | I1 64 | tp26 65 | Rp27 66 | (I3 67 | S'<' 68 | p28 69 | NNNI-1 70 | I-1 71 | I0 72 | tp29 73 | bI00 74 | S'\x00\x00\x00@\x92v\xb4\xbf\x00\x00\x00 F\x1a\xbb?\x00\x00\x00\xa0\x192\x91?\x00\x00\x00\x00\xf6I\xb4\xbf\x00\x00\x00\xe0\x91\xd0\xbe\xbf\x00\x00\x00 \x06\xc5\xb5\xbf\x00\x00\x00@dl\xb3\xbf\x00\x00\x00\xe09\x84\xb7\xbf\x00\x00\x00@\x01\x97\xc6?\x00\x00\x00\xc0\x143\xc2\xbf\x00\x00\x00\xe0\xd2\xc1\xc9?\x00\x00\x00@D%\xba\xbf\x00\x00\x00\x80\xf9\xe1\xc7\xbf\x00\x00\x00 \xa0\xf7\x96\xbf\x00\x00\x00\x80\xea\xb3\xb7\xbf\x00\x00\x00\x80\xec\xfc\xcb?\x00\x00\x00 \x10\xb2\xc9\xbf\x00\x00\x00\xa0\x0f\x1d\xbe\xbf\x00\x00\x00\xc0\x9d\x1e\x91?\x00\x00\x00 \xbeM\xae?\x00\x00\x00\x80\xa8\x8d\xc0?\x00\x00\x00\xa0w\x1c\x95?\x00\x00\x00\x00\xba<\x9f\xbf\x00\x00\x00@+\xe8\xa6?\x00\x00\x00 \xd7\x16\xba\xbf\x00\x00\x00\xe0bG\xd7\xbf\x00\x00\x00`\x01\xc7\xc1\xbf\x00\x00\x00@\t\x06\xb8\xbf\x00\x00\x00@Ql\x9e\xbf\x00\x00\x00\x00\xc8\xfb\xad\xbf\x00\x00\x00\x00%,\x9b\xbf\x00\x00\x00\x00\xe2\xbd\x97?\x00\x00\x00\xa0\xad\xf2\xc1\xbf\x00\x00\x00\x80(m\xaa\xbf\x00\x00\x00\xc0D\xb9\xae?\x00\x00\x00 \xccJ\xa9?\x00\x00\x00`<\x15\x89\xbf\x00\x00\x00 @\xb5\xbb\xbf\x00\x00\x00@}5\xc8?\x00\x00\x00@t\x14\xa0?\x00\x00\x00`\xf1 \xd1\xbf\x00\x00\x00@vY\xa7?\x00\x00\x00\xc0\x98>\xbc?\x00\x00\x00`\x86\x99\xce?\x00\x00\x00 R9\xc2?\x00\x00\x00\xe0\x7f\t\xa4?\x00\x00\x00\xe0\xc7\\\xad?\x00\x00\x00\x80\x07E\xc8\xbf\x00\x00\x00@q\xab\xc6?\x00\x00\x00 \xc3E\xc0\xbf\x00\x00\x00\xc0\x9d~\xaf\xbf\x00\x00\x00\xc0W\x85\xba?\x00\x00\x00\xc0\x92\xd9\xbd?\x00\x00\x00 \x16\xa8\xb0?\x00\x00\x00\xa0\xc2_\x8c?\x00\x00\x00@\x13e\xbc\xbf\x00\x00\x00 \x84\x87\xb5?\x00\x00\x00 \x9aX\xb3?\x00\x00\x00@\x14U\xca\xbf\x00\x00\x00\xe0\xb6b\x83?\x00\x00\x00`\xc2x\xc3?\x00\x00\x00\x80Qy\xb3\xbf\x00\x00\x00\x00\xbc\xb9\x9b\xbf\x00\x00\x00 \xd4\x1a\xb7\xbf\x00\x00\x00\xa0\xcdf\xcc?\x00\x00\x00`\xbd*\xab?\x00\x00\x00\x00y}\xbe\xbf\x00\x00\x00 _\x0f\xc8\xbf\x00\x00\x00`@\x84\xc2?\x00\x00\x00`\x9d\xe1\xc9\xbf\x00\x00\x00\xc0H%\xb8\xbf\x00\x00\x00\xa0\x85G\xa3?\x00\x00\x00`\x88M\xbb\xbf\x00\x00\x00@\xb2\x13\xc9\xbf\x00\x00\x00\x80\x1c\xb1\xd0\xbf\x00\x00\x00@Iy\xb1\xbf\x00\x00\x00\xa0\xa8\x89\xd4?\x00\x00\x00 \x0cc\xc4?\x00\x00\x00\x00\xfb\xb0\xc3\xbf\x00\x00\x00\x00y\xa6\xb4?\x00\x00\x00\xa0\x91\x17\x8f\xbf\x00\x00\x00\x80\x90\x1fV\xbf\x00\x00\x00\xe0\x18o\xb9?\x00\x00\x00`\x97\xbc\xca?\x00\x00\x00\x00$\xea\x7f?\x00\x00\x00\x00\xfb\x89\xb1?\x00\x00\x00@\xd7\xe3\xb1\xbf\x00\x00\x00\x00\x17\xe1\x99\xbf\x00\x00\x00`\x97\x05\xd1?\x00\x00\x00\xc0\xe9\x02\xb8\xbf\x00\x00\x00\xa0VE\xab?\x00\x00\x00\xa0k\x86\xcb?\x00\x00\x00\xc0\x067w?\x00\x00\x00@\xdak\xbb?\x00\x00\x00\xa0\x05`\x90\xbf\x00\x00\x00\xa0@\x1f\x9c\xbf\x00\x00\x00@\x9ak\xc1\xbf\x00\x00\x00 Y\xc4\xa6?\x00\x00\x00\xa0\xb6B\xb4\xbf\x00\x00\x00\xc0P\xcd\x9b?\x00\x00\x00@\x88\xa6\x98\xbf\x00\x00\x00\x80\xf6j\x9f\xbf\x00\x00\x00\x80\x90\xd5\x82?\x00\x00\x00\x00J\xef\xb8?\x00\x00\x00\x800\xa0\xc5\xbf\x00\x00\x00\x00\x19\x9a\xc1?\x00\x00\x00`\xe6\x91\xa8\xbf\x00\x00\x00 \x9d\x95\x82?\x00\x00\x00@\x93\xad\xa5\xbf\x00\x00\x00@\x1cu\x8f?\x00\x00\x00 \xc3$\xa9\xbf\x00\x00\x00\xe0\xd3w\xa2\xbf\x00\x00\x00\xe0\xb0\t\xba?\x00\x00\x00\xa0\x99*\xc7\xbf\x00\x00\x00\xe0r]\xc6?\x00\x00\x00\xc0?\xe7\xc2?\x00\x00\x00`\xea|\xa8?\x00\x00\x00 \xf7\xa1\xbb?\x00\x00\x00\x803\xcd\xbe?\x00\x00\x00\xe0\x97\xc8\xb5?\x00\x00\x00\x80a\x17\x9d\xbf\x00\x00\x00\xc0\xee\x07\x9b\xbf\x00\x00\x00 \x7f\x91\xcf\xbf\x00\x00\x00\x80\xbd\x01\xa7\xbf\x00\x00\x00\xc0e\xac\xac?\x00\x00\x00\xe0\xa4X\xaf\xbf\x00\x00\x00\x80\xb0\xd5\xb9?\x00\x00\x00\xa0\x1b\x88\xa5\xbf' 75 | p30 76 | tp31 77 | ba(lp32 78 | g15 79 | (g16 80 | g17 81 | Ntp33 82 | Rp34 83 | S'Y\xdfa(+\x8f\x1c\x0e.?\x82\xfa' 84 | p35 85 | baag0 86 | (g1 87 | (I0 88 | tp36 89 | g3 90 | tp37 91 | Rp38 92 | (I1 93 | (I128 94 | tp39 95 | g7 96 | (S'f8' 97 | p40 98 | I0 99 | I1 100 | tp41 101 | Rp42 102 | (I3 103 | S'<' 104 | p43 105 | NNNI-1 106 | I-1 107 | I0 108 | tp44 109 | bI00 110 | S'\x00\x00\x00`\x03L\xa1\xbf\x00\x00\x00`\xfe\xdd\xbe?\x00\x00\x00\xe0v\xfc\x87?\x00\x00\x00\xe0\xdb\xbf\xb0\xbf\x00\x00\x00\xc0\xa18J\xbf\x00\x00\x00\x00\x7f\xee\xa0\xbf\x00\x00\x00\x80\xc7\xee\x9d\xbf\x00\x00\x00\xa0\xfbw\xb6\xbf\x00\x00\x00\x80K:\xc0?\x00\x00\x00\x00\xfd\x96\xae\xbf\x00\x00\x00 \x86w\xc7?\x00\x00\x00@\xc5\x9e\xa3\xbf\x00\x00\x00@\xa1\x86\xce\xbf\x00\x00\x00\x00\x93\xf2\x8c\xbf\x00\x00\x00\x804\x9d\x96\xbf\x00\x00\x00\xc0\xd2\xf5\xb9?\x00\x00\x00\xc0\x16\xf2\xc1\xbf\x00\x00\x00\x80M\xc0\xbb\xbf\x00\x00\x00\x00s\x98\xc3\xbf\x00\x00\x00\xe0"\x80\xa3\xbf\x00\x00\x00\x80\xae\xee\x8d?\x00\x00\x00\xa0\xf0\x0e\xa1?\x00\x00\x00\xc0\x18\x0c\x97?\x00\x00\x00\xa0\xda%\xb0?\x00\x00\x00\x00xd\xa6\xbf\x00\x00\x00 \xa9a\xd7\xbf\x00\x00\x00 S\xd8\xc2\xbf\x00\x00\x00@9\xb9\x9e\xbf\x00\x00\x00\x800\xd8\x98?\x00\x00\x00 \xba\xfc\xb8\xbf\x00\x00\x00\x00\xc6\x0b\x92?\x00\x00\x00\x80\xf3\xbc\xb1?\x00\x00\x00`n\xf8\xb1\xbf\x00\x00\x00 \xcb\x94\xab\xbf\x00\x00\x00\xe0Uk\xb2?\x00\x00\x00 \xce\xaa\xad?\x00\x00\x00 \xde?\xb0\xbf\x00\x00\x00\xe0\x88\xe1\xaa\xbf\x00\x00\x00\x80\xc6\xca\xd0?\x00\x00\x00\x00Oa\xb5?\x00\x00\x00@\xb8\xf4\xbf\xbf\x00\x00\x00\xc0\xaa\x9a\xb6?\x00\x00\x00\xe0iT\x87?\x00\x00\x00@Vk\xd7?\x00\x00\x00@\x13\xe8\xcf?\x00\x00\x00`\xaf\x98\xab?\x00\x00\x00\xe0\x845\xa4\xbf\x00\x00\x00\x80U\xaf\xaf\xbf\x00\x00\x00 \xa1t\xbd?\x00\x00\x00\xe08\t\xd1\xbf\x00\x00\x00\x80K\xcf\xb6?\x00\x00\x00\xa0b+\xc8?\x00\x00\x00\x00l\xed\xc0?\x00\x00\x00\x80\xc0\xf0\xb5?\x00\x00\x00\xe0\x81\xe1\xbf?\x00\x00\x00\xc0\xdf\x96\xba\xbf\x00\x00\x00\xa0\x13.\xb5?\x00\x00\x00 s\xbe\xb8?\x00\x00\x00\x00\xd6e\xcc\xbf\x00\x00\x00\x80\xbb2\x90?\x00\x00\x00@A\xb8\x8e?\x00\x00\x00\xc0\xd4I\xc8\xbf\x00\x00\x00@Z\xfc\xa2\xbf\x00\x00\x00`\xd8\x94\x99\xbf\x00\x00\x00\xe0\xd5\xeb\xc0?\x00\x00\x00@o\xe7\xbe?\x00\x00\x00\x80\xf0\xf7\xb9\xbf\x00\x00\x00 3k\xbb\xbf\x00\x00\x00\x80\xf8\x93\xc4?\x00\x00\x00 ;C\xbf\xbf\x00\x00\x00\xa0.\xa0\xa5\xbf\x00\x00\x00\xc0\xf1\xce\x9e\xbf\x00\x00\x00`>%\xbf\xbf\x00\x00\x00@j\xb4\xbf\xbf\x00\x00\x00`\x00\t\xd1\xbf\x00\x00\x00\xe0\xb0\x9a\xb0?\x00\x00\x00\xe0-0\xd9?\x00\x00\x00 A\x1d\xc1?\x00\x00\x00\xe0\x8e\xdd\xce\xbf\x00\x00\x00\xa0\xc4|\xae?\x00\x00\x00`z\xf1\x99\xbf\x00\x00\x00\xc0\xca\r\x9a\xbf\x00\x00\x00\x00Xm\xb4?\x00\x00\x00\x80\xfd\n\xa9?\x00\x00\x00`\xaf\xf2\xc4\xbf\x00\x00\x00\x00\xaa\xb8\xc0\xbf\x00\x00\x00`\xf8\x0e\xba\xbf\x00\x00\x00\xc0\xe6\xbbz?\x00\x00\x00\xc0\x92\x8a\xb7?\x00\x00\x00\x80]\xddn?\x00\x00\x00\xe0\x02\xf4\xb4\xbf\x00\x00\x00\xa0\xbd\xb0\xc6?\x00\x00\x00\xc0<\xbb\x91\xbf\x00\x00\x00` \xab\xa2?\x00\x00\x00 \xb8\xc2\xc3?\x00\x00\x00\x00 \xee\xac?\x00\x00\x00@EQ\xbe\xbf\x00\x00\x00\x00\xe4\xf1\x97\xbf\x00\x00\x00\x00\xcf@\xc4\xbf\x00\x00\x00\xc0]\xeb\x8a\xbf\x00\x00\x00\x00\xc7\xdc\xb7?\x00\x00\x00 \x8b#\xc3\xbf\x00\x00\x00\x00\xb6\xc4\x8c\xbf\x00\x00\x00 \xef\x9b\xbc?\x00\x00\x00 j7\xc9\xbf\x00\x00\x00\xa0;S\xc8?\x00\x00\x00`\xf1`\x85?\x00\x00\x00@X3\xa4\xbf\x00\x00\x00\x00\xfc\xa7\xb0?\x00\x00\x00@#\xac\xa0?\x00\x00\x00@\x9c\xf7\xbd\xbf\x00\x00\x00 \xe3\xd3\xa0\xbf\x00\x00\x00\x80.\xa3\xc6?\x00\x00\x00\xe0\\2\xd1\xbf\x00\x00\x00 B-\xc7?\x00\x00\x00@\x95D\xc7?\x00\x00\x00\x80\x8fE\xa0\xbf\x00\x00\x00\x00~\x02\xc1?\x00\x00\x00 \xaf\x12\xb2?\x00\x00\x00\x80H\xdb\xb8?\x00\x00\x00`q\x19\xa1\xbf\x00\x00\x00\x00\xbb\x7f\x9a?\x00\x00\x00\x80\xee\x1d\xc3\xbf\x00\x00\x00\xc0\x08\x1a\xbc\xbf\x00\x00\x00\xa00\xb7\xa0?\x00\x00\x00@\xbd\xd2\xb7?\x00\x00\x00\xa0\x07\xb8\x86?\x00\x00\x00\x00\x80\'\x9d?' 111 | p45 112 | tp46 113 | ba(lp47 114 | g15 115 | (g16 116 | g17 117 | Ntp48 118 | Rp49 119 | S'Y\xdfc\x9b+\x8f\x1c\x0e.?\x82\xfc' 120 | p50 121 | baag0 122 | (g1 123 | (I0 124 | tp51 125 | g3 126 | tp52 127 | Rp53 128 | (I1 129 | (I128 130 | tp54 131 | g7 132 | (S'f8' 133 | p55 134 | I0 135 | I1 136 | tp56 137 | Rp57 138 | (I3 139 | S'<' 140 | p58 141 | NNNI-1 142 | I-1 143 | I0 144 | tp59 145 | bI00 146 | S'\x00\x00\x00 \xf6\xba\xb5\xbf\x00\x00\x00\x80y\x04\x97?\x00\x00\x00\x80\x88\xeb{\xbf\x00\x00\x00@\xb6\n\xa1\xbf\x00\x00\x00\x00U\xdd\xb0\xbf\x00\x00\x00@\x1a\xf2\x99\xbf\x00\x00\x00@\xe9{\xa5\xbf\x00\x00\x00\xc0\xdd+\xb8\xbf\x00\x00\x00\xe0\x08\xe9\xc3?\x00\x00\x00@\xe8\x80\xb1\xbf\x00\x00\x00\xc0\xac\\\xd3?\x00\x00\x00@\xe4Y\xae\xbf\x00\x00\x00 \xbe\xb5\xd0\xbf\x00\x00\x00`\x1d\xb7\xb9\xbf\x00\x00\x00\x00\n\x02~?\x00\x00\x00`\xac,\xb5?\x00\x00\x00\xa05&\xc6\xbf\x00\x00\x00@\x87\x12\xa2\xbf\x00\x00\x00\xc0\x0b?\xa7\xbf\x00\x00\x00@\x02\x8f\xbb\xbf\x00\x00\x00 ]\xe5\x8f?\x00\x00\x00\xa0\xdf\xff\xa7?\x00\x00\x00\xa0\xc2y\xa0?\x00\x00\x00\x80\x01\xfa\xc0?\x00\x00\x00@\x89\xc0\xc4\xbf\x00\x00\x00\xa0\x88\xd2\xcd\xbf\x00\x00\x00\x80\xd9\xbe\xaf\xbf\x00\x00\x00\xe0\xdb\xa3\xc6\xbf\x00\x00\x00@\x8e"\xaa?\x00\x00\x00`\x9cc\xc2\xbf\x00\x00\x00\x00$\xbe\x92?\x00\x00\x00`\xa9\x1f\xbb?\x00\x00\x00`ra\xa6\xbf\x00\x00\x00\x80\x02\xa3\x8d\xbf\x00\x00\x00\x000\xdfU\xbf\x00\x00\x00\x80t\x97\xad?\x00\x00\x00\xe0U^\xc2\xbf\x00\x00\x00 \x18Q\xb3\xbf\x00\x00\x00\xe0\xf7h\xd4?\x00\x00\x00\x00\x84P\xaf\xbf\x00\x00\x00 X"\xc5\xbf\x00\x00\x00\xc0\x06\xfd\xae\xbf\x00\x00\x00\xa0\x0f\x00\x92?\x00\x00\x00\x80{\xde\xce?\x00\x00\x00\x80\xbeJ\xca?\x00\x00\x00\xc0\xec\x82\x9d\xbf\x00\x00\x00\xc07D\xa2?\x00\x00\x00\xa0(>\xb0\xbf\x00\x00\x00\xc0\xb1-\xc8?\x00\x00\x00`\xaa\xe7\xcf\xbf\x00\x00\x00\xe0\xaa\xbd\xac?\x00\x00\x00`\xe3\x0e\xcc?\x00\x00\x00\xe0\xfc\xfa\xc0?\x00\x00\x00@MX\xb3?\x00\x00\x00\x80\'\x83l?\x00\x00\x00\x00C\xd7\xc1\xbf\x00\x00\x00\xc07:\x86\xbf\x00\x00\x00\xc0:V\xc9?\x00\x00\x00\x80B\x8b\xca\xbf\x00\x00\x00\x80?4\xb2?\x00\x00\x00@\xbb\xdd\xa6?\x00\x00\x00 \xbb\xcc\xc6\xbf\x00\x00\x00\x00\xe5_\xa0\xbf\x00\x00\x00\xa0\x18%\x97?\x00\x00\x00`D\x7f\xbb?\x00\x00\x00 \xdb8\xb8?\x00\x00\x00\x80\x89\x19\xba\xbf\x00\x00\x00@\xc0\xa8\xc4\xbf\x00\x00\x00\xc0\xef.\xc6?\x00\x00\x00\x80\x15\x7f\xcb\xbf\x00\x00\x00@w\xa9\xc0\xbf\x00\x00\x00 \x1a\xc6?\x00\x00\x00\xc0\xf4\xeb\xc2\xbf\x00\x00\x00`\xa3\xe7\xd1\xbf\x00\x00\x00\xa0\xb6B\xcf\xbf\x00\x00\x00@\x0f\x0b\xb0?\x00\x00\x00\x00\x8d\x92\xd6?\x00\x00\x00\x80R\'\xbc?\x00\x00\x00\x00\x9a}\xb6\xbf\x00\x00\x00\x00\x87\x1d\xb4?\x00\x00\x00\x80_\xba\x88?\x00\x00\x00 \x0f\x89\xbd\xbf\x00\x00\x00\xc0\x98*\xad?\x00\x00\x00\x00\x15\xe3\xc0?\x00\x00\x00\x00ym\xa5\xbf\x00\x00\x00\xa0~!\xc2\xbf\x00\x00\x00\x00\x00\xaf\xb2\xbf\x00\x00\x00 \xcd\xe5\x98?\x00\x00\x00 \x15\xe6\xc7?\x00\x00\x00@\xb8\xfd\xb1\xbf\x00\x00\x00\x00X\x15\x8a\xbf\x00\x00\x00\xe0\x84\x0e\xc8?\x00\x00\x00 \x10\xd0\xa6?\x00\x00\x00\xc0\xe6\x87\xa4?\x00\x00\x00 \xfb\xe2\xa9?\x00\x00\x00\x80\x19\xe0\xa4?\x00\x00\x00@m\x05\x9e\xbf\x00\x00\x00\xe0r\xc5\xb0?\x00\x00\x00\xc0\xb1\xe5\xa1\xbf\x00\x00\x00\x00()\xb4?\x00\x00\x00 \x03\xa3\xad?\x00\x00\x00\xc0l\x90\xb8\xbf\x00\x00\x00\x80\x90G\x9e?\x00\x00\x00\x00~\nm\xbf\x00\x00\x00\x80\xf5\xca\xc3\xbf\x00\x00\x00\xe0\xf9\x7f\xd0?\x00\x00\x00\xe0pZ\x96?\x00\x00\x00\xc0\xe6\xab\x9d\xbf\x00\x00\x00`}\xa4\x8e?\x00\x00\x00\x00\xe1\x8e\xb6\xbf\x00\x00\x00\xa0\x1f\x10\xb3\xbf\x00\x00\x00\xe0\xf0\xb9\x97?\x00\x00\x00`\x84f\xce?\x00\x00\x00\xa0\xef7\xd1\xbf\x00\x00\x00\x80\xc4\x0b\xcb?\x00\x00\x00`\x81\x0e\xbf?\x00\x00\x00\x00}I\xc2?\x00\x00\x00\xe0\xff\x7f\xca?\x00\x00\x00\x00\x1f\xba\xa8?\x00\x00\x00\x80\xdb|\xb6?\x00\x00\x00@S\x9c\xa4?\x00\x00\x00\x80\x9a/\xad\xbf\x00\x00\x00 l\xc4\xca\xbf\x00\x00\x00\xa0\x82\xc6\xaa\xbf\x00\x00\x00\xc0\xfd\xa4\xb1?\x00\x00\x00\xa0\xdc\xce\x98\xbf\x00\x00\x00\xc0M\xd3\x9e?\x00\x00\x00\x00Iyo?' 147 | p60 148 | tp61 149 | batp62 150 | b. -------------------------------------------------------------------------------- /python/log.py: -------------------------------------------------------------------------------- 1 | from pymongo import MongoClient 2 | import RPi.GPIO as GPIO 3 | from bson.objectid import ObjectId 4 | import time 5 | import send_email 6 | import cv2 7 | import base64 8 | 9 | GPIO.setmode(GPIO.BCM) 10 | GPIO.setwarnings(False) 11 | GPIO.setup(27, GPIO.OUT) 12 | 13 | mongo_db = MongoClient('localhost',27017) 14 | db_log = mongo_db.smartbell.logs 15 | 16 | def led(): 17 | ''' 18 | This function is to blink led when checking a permission to access. 19 | ''' 20 | GPIO.output(27,True) 21 | time.sleep(1) 22 | GPIO.output(27,False) 23 | time.sleep(1) 24 | 25 | 26 | def save_log(firstname, lastname, frame, __id, log_time, access): 27 | ''' 28 | This function is to save entrance log. 29 | 30 | ''' 31 | flag, jpeg = cv2.imencode('.jpg', frame) 32 | encoding_frame = base64.b64encode(jpeg) 33 | 34 | if access == True: 35 | access_log = "allowed" 36 | else: 37 | access_log = "denied" 38 | # Save the log 39 | db_log.insert_one({"firstname":firstname, "lastname":lastname, "time":log_time, "photo": encoding_frame, "access":access_log}) 40 | return log_time 41 | 42 | def permission_check(__id, frame,log_time): 43 | ''' 44 | This function is to check permission to access. 45 | It checks permission using visitor's id. (access value is 'true' or 'false') Then, it saves the log about all visitors. 46 | If the visitor doesn't have permission to access, led blinks 2 times and door stays close. 47 | Otherwise, if the visitor has permisson, it sends an email to the visitor about their entrance, then led blinks once. 48 | ''' 49 | # Check permission to access 50 | db_visitors = mongo_db.smartbell.visitors 51 | try: 52 | valid = db_visitors.find_one({"_id": ObjectId(__id[0])}) 53 | except EOFError: 54 | return 0 55 | 56 | save_log(valid['firstname'], valid['lastname'], frame, valid['_id'], log_time, valid['access']) 57 | 58 | # If permission to access is true, 59 | if valid['access']: 60 | print("Available face") 61 | send_email.email(valid['email'],valid['firstname'],valid['lastname'],log_time) 62 | led() 63 | 64 | else: 65 | print("No permission, closed") 66 | for n in range(2): 67 | led() 68 | 69 | -------------------------------------------------------------------------------- /python/recognition.py: -------------------------------------------------------------------------------- 1 | import face_recognition 2 | import numpy as np 3 | from pymongo import MongoClient 4 | import time 5 | import cv2 6 | import pickle 7 | 8 | def face_comparison(visitor_face): 9 | ''' 10 | This function is to recognize whether visitor's face is registered or not. 11 | First, we reads file which has encoding data of registered faces. 12 | we detects visitor's face and encoding that part. 13 | we compares to encoding data of registered faces and encoding data of visitor's face. 14 | Then, if visitor is registered, we would return visitor's id, or if not, it would return 0. 15 | ''' 16 | __id = 0 17 | 18 | # Read the text file which known faces are encoded 19 | with open('faces_encodings.txt','r') as f: 20 | try: 21 | image_face_encoding = pickle.load(f) 22 | except EOFError: 23 | return 0 24 | 25 | s_time = time.time() 26 | 27 | # Initialize some variables 28 | face_locations = [] 29 | face_encodings = [] 30 | 31 | # Detect visitor's face 32 | face_locations = face_recognition.face_locations(visitor_face) 33 | # Encode detected face 34 | face_encodings = face_recognition.face_encodings(visitor_face, face_locations) 35 | 36 | for i in range(len(image_face_encoding)): 37 | for face_encoding in face_encodings: 38 | # Exception : If there is registered face, the encoding data shape would be different between other cases. It's like (2,1). 39 | if (len(image_face_encoding) == 2 and len(image_face_encoding[0]) == 1): 40 | ret, encoding_data = image_face_encoding 41 | # Compare registered faces and visitor's face 42 | match = face_recognition.compare_faces([encoding_data], face_encoding) 43 | # If there are more than 2 registered faces, the encoding data shape would be like (2,2), (3,2), (4,2), etc. 44 | else: 45 | ret, encoding_data = image_face_encoding[i] 46 | # Compare registered faces and visitor's face 47 | match = face_recognition.compare_faces([encoding_data], face_encoding) 48 | 49 | # If visitor's face is registered 50 | if match[0].all() == True: 51 | __id = ret 52 | if __id != 0: 53 | print(time.time()-s_time) 54 | break 55 | 56 | return __id 57 | -------------------------------------------------------------------------------- /python/save.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import os 3 | 4 | def make_directory(path): 5 | ''' 6 | This function is to make directory for pictures. 7 | It checks whether a directory exists or not befor saving a picture. 8 | If it exists, just pass. If not, it makes the directory. 9 | ''' 10 | if not os.path.exists(path): 11 | os.makedirs(path) 12 | 13 | def save_photo(file_path, frame): 14 | ''' 15 | This function is to save photos. 16 | After making directory, it save a picture and returns path of the picture. 17 | ''' 18 | cv2.imwrite(file_path, frame) 19 | -------------------------------------------------------------------------------- /python/send_email.py: -------------------------------------------------------------------------------- 1 | import smtplib 2 | from email.mime.multipart import MIMEMultipart 3 | from email.mime.text import MIMEText 4 | 5 | 6 | 7 | def email(email_address, first_name, last_name, log_time): 8 | ''' 9 | This function is to send an email to the visitor after the visitor visited. 10 | If the visitor didn't visit, they click the link to block the door. 11 | ''' 12 | try: 13 | # gmail server login and connect 14 | gmail_user = 'faceit.metropolia@gmail.com' 15 | gmail_password = '@mUAS@FI' 16 | 17 | server = smtplib.SMTP('smtp.gmail.com', 587) 18 | server.starttls() 19 | server.login(gmail_user, gmail_password) 20 | 21 | # mail content 22 | msg = MIMEMultipart('alternative') 23 | msg['Subject'] = "Entrance Log" 24 | msg['From']='no-reply@gmail.com' 25 | msg['To'] = email_address 26 | text=str(first_name) +' '+str(last_name)+' visits at '+str(log_time)+'.' 27 | link_part = """\ 28 |

    %s

    29 | If is is not you, Emergency lock 30 | 31 | """%(text) 32 | part=MIMEText(link_part, 'html') 33 | msg.attach(part) 34 | 35 | # send an email to the visitor 36 | server.sendmail(gmail_user, email_address, msg.as_string()) 37 | print("Success to send") 38 | server.quit() 39 | 40 | except: 41 | print 'Something went wrong...' 42 | -------------------------------------------------------------------------------- /python/usbcamera.py: -------------------------------------------------------------------------------- 1 | from tornado import websocket, web, ioloop 2 | import base64 3 | import cv2 4 | import time 5 | from videostream import Stream 6 | from delete import delete_face 7 | import collect 8 | 9 | stream_thread = Stream() 10 | stream_thread.daemon = True 11 | stream_thread.start() 12 | 13 | class EmailRequestHandler(web.RequestHandler): 14 | def get(self): 15 | print("Emergency! Lock the door") 16 | self.write("Success to lock") 17 | 18 | class SocketHandler(websocket.WebSocketHandler): 19 | def check_origin(self, origin): 20 | return True 21 | 22 | def open(self): 23 | print("client connected") 24 | stream_thread.add_client(self) 25 | stream_thread.change_socket_flag() 26 | 27 | def on_message(self, message): 28 | # when making a photo 29 | if message == "photo_make": 30 | print("photo") 31 | stream_thread.change_capture_flag() 32 | # when uploading a photo 33 | elif message == "photo_upload": 34 | print("upload") 35 | result = collect.upload_photo() 36 | print(result) 37 | self.write_message(result) 38 | # when canceling to add visitor 39 | elif message == "cancel": 40 | collect.cancel_add() 41 | # when deleting a visitor 42 | else: 43 | print("delete") 44 | print(delete_face(message)) 45 | 46 | def on_close(self): 47 | print("client disconnected") 48 | stream_thread.change_socket_flag() 49 | stream_thread.remove_client(self) 50 | 51 | 52 | def main(): 53 | 54 | app = web.Application([ 55 | (r'/ws',SocketHandler), 56 | (r'/lock',EmailRequestHandler) 57 | ]) 58 | app.listen(8000) 59 | ioloop.IOLoop.instance().start() 60 | 61 | 62 | if __name__ == '__main__': 63 | main() 64 | -------------------------------------------------------------------------------- /python/videostream.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import cv2 3 | import time 4 | from threading import Thread 5 | import RPi.GPIO as GPIO 6 | import collect 7 | import visit 8 | import os 9 | #import save 10 | #from pymongo import MongoClient 11 | 12 | GPIO.setmode(GPIO.BCM) 13 | GPIO.setwarnings(False) 14 | GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP) 15 | GPIO.setup(27, GPIO.OUT) 16 | 17 | class Stream(Thread): 18 | ''' 19 | # Camera stream 20 | This class is to manage all matters related to stream of usb camera. We reads the camera and get frame in the infinite loop. 21 | If web browser opens, we would send the frame to web browser through web socket. 22 | If you click 'make photo' button which is for collecting visitor's face, we would make folder named visitor's id for saving the frame and send the frame which is a visitor's face to collect.py. 23 | If visitor pushes a button, we would send the frame which is a visitor's face to visit.py 24 | ''' 25 | def __init__(self): 26 | self.flag = [False] 27 | self.capture_flag = [False] 28 | #self.ws = ws 29 | self.clients = [] 30 | Thread.__init__(self, name=Stream.__name__) 31 | 32 | def run(self): 33 | 34 | self.camera = cv2.VideoCapture(0) 35 | 36 | prev_input = 1 37 | 38 | #mongo_db = MongoClient('localhost',27017) 39 | #db = mongo_db.smartbell.visitors 40 | 41 | while True: 42 | # Read camera and get frame 43 | rval, frame = self.camera.read() 44 | 45 | if frame is None: 46 | self.camera.release() 47 | self.camera = cv2.VideoCapture(0) 48 | continue 49 | 50 | # Send camera stream to web 51 | if self.flag[0]: 52 | rvel, jpeg = cv2.imencode('.jpg', frame) 53 | encode_string = base64.b64encode(jpeg) 54 | for client in self.clients: 55 | client.write_message(encode_string) 56 | 57 | # A visitor pushes button 58 | but = GPIO.input(17) 59 | if(not prev_input and but): 60 | # It affects dlib speed. If frame size is small, dlib would be faster than normal size frame 61 | small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25) 62 | visit.visit(small_frame) 63 | for client in self.clients: 64 | client.write_message("log") 65 | prev_input = but 66 | time.sleep(0.05) 67 | 68 | # Click makephotos on web browser 69 | if self.capture_flag[0] == True: 70 | 71 | enough_image = collect.make_photo(frame) 72 | 73 | if enough_image == "success": 74 | print("Success to register") 75 | else: 76 | print("Fail to register") 77 | for client in self.clients: 78 | client.write_message(enough_image) 79 | 80 | self.capture_flag[0] = False 81 | 82 | self.camera.release() 83 | 84 | def change_capture_flag(self): 85 | self.capture_flag[0] = True 86 | 87 | def change_socket_flag(self): 88 | self.flag[0] = not self.flag[0] 89 | 90 | def add_client(self, newclient): 91 | self.clients.append(newclient) 92 | 93 | def remove_client(self, client): 94 | self.clients.remove(client) 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /python/visit.py: -------------------------------------------------------------------------------- 1 | import recognition 2 | import log 3 | import face_recognition 4 | from datetime import datetime 5 | import time 6 | import pytz 7 | 8 | LOCAL_TZ = pytz.timezone('Europe/Helsinki') 9 | 10 | def visit(frame): 11 | ''' 12 | This function is excuted when a visitor pushes a button. 13 | It checks whether the frame is enough to detect. 14 | If the frame is enough, it calls recognition.py and check the visitor is registered. 15 | If the visitor is recognized by id, it calls log.py to save the log about the visitor. 16 | Otherwise, it calls log.py to save the log about that Unkown visitor tried to access the place. 17 | ''' 18 | # Detect face 19 | # If it cannot detect face, the library would return 0 20 | visitor_face = len(face_recognition.face_locations(frame)) 21 | 22 | log_time = datetime.now(pytz.utc).astimezone(LOCAL_TZ).strftime('%Y-%m-%d %H:%M:%S') 23 | 24 | if visitor_face == 0: 25 | print("Cannot detect face. Try again") 26 | else: 27 | # Compare to registered faces and visitor's face 28 | __id = recognition.face_comparison(frame) 29 | if __id == 0: 30 | print("Does not register") 31 | log.save_log("Unkown", "Visitor", frame, 0, log_time, False) 32 | for n in range(2): 33 | log.led() 34 | else: 35 | print("I see someone id {}!".format(__id)) 36 | log.permission_check(__id, frame, log_time) 37 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | /frontend/node_modules 2 | /server/node_modules 3 | /frontend/bundle.js 4 | /frontend/npm-debug.log 5 | /frontend/.DS_Store 6 | 7 | # IntelliJ 8 | *.iml 9 | /.idea 10 | python-stream/*.pyc 11 | 12 | -------------------------------------------------------------------------------- /server/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | secret: "saskjdjvhbacsajdcbjjh" 3 | }; 4 | -------------------------------------------------------------------------------- /server/controllers/authentication.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jwt-simple'); 2 | const User = require('../models/user'); 3 | const config = require('../config'); 4 | 5 | function tokenForUser(user) { 6 | const timestamp = new Date().getTime(); 7 | return jwt.encode({ sub: user.id, iat: timestamp }, config.secret); 8 | } 9 | 10 | exports.signin = function(req, res, next) { 11 | res.send( { token: tokenForUser(req.user), id: req.user._id }); 12 | }; 13 | 14 | exports.signup = function(req, res, next) { 15 | const email = req.body.email; 16 | const password = req.body.password; 17 | 18 | if (!email || !password) { 19 | return res.status(422).send( {error: 'You must provide email and password'}); 20 | } 21 | 22 | // Check for existing user 23 | User.findOne({ email: email }, function(err, existingUser) { 24 | if (err) { return next(err); } 25 | 26 | // If user exists return error 27 | if (existingUser) { 28 | return res.status(422).send({ error: 'Email is already used '}); 29 | } 30 | 31 | // If no user, create and save it to database 32 | const user = new User({ 33 | email: email, 34 | password: password 35 | }); 36 | 37 | user.save(function(err) { 38 | if (err) { return next(err); } 39 | console.log(user); 40 | 41 | // Respond the user was created 42 | res.json({ token: tokenForUser(user), id: user._id }); 43 | }); 44 | }); 45 | }; -------------------------------------------------------------------------------- /server/controllers/manage-logs.js: -------------------------------------------------------------------------------- 1 | const {Log} = require('../models/log'); 2 | 3 | exports.addLog = function(req, res, next) { 4 | const firstname = "test name1"; 5 | const lastname = "test name1"; 6 | 7 | const log = new Log({ 8 | firstname: firstname, 9 | lastname: lastname, 10 | }); 11 | 12 | log.save(function(err) { 13 | if (err) { return next(err); } 14 | console.log(log); 15 | res.send("Log successfully added"); 16 | }); 17 | }; 18 | 19 | exports.getLogs = function (req, res, next) { 20 | Log.find().sort({'_id': -1}).then((logs) => { 21 | console.log(logs); 22 | res.send({logs}); 23 | }, (e) => { 24 | res.status(400).send(e); 25 | }) 26 | }; 27 | 28 | -------------------------------------------------------------------------------- /server/controllers/manage-photo-upload.js: -------------------------------------------------------------------------------- 1 | const multer = require('multer'); 2 | const {Visitor} = require('../models/visitor'); 3 | const fs = require('fs'); 4 | 5 | const storage = multer.diskStorage({ 6 | destination: '../python/pics/upload', 7 | filename: function (req, file, cb) { 8 | cb(null, 'img0.jpg'); 9 | } 10 | }); 11 | const upload = multer({ storage: storage }); 12 | 13 | exports.photoUpload = function(req, res, next) { 14 | upload.single('file')(req, res, function (err) { 15 | if (err) { 16 | res.status(404).send("Server problem, try later"); 17 | } 18 | res.status(200).send("Photo uploaded"); 19 | }); 20 | }; 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /server/controllers/manage-visitors.js: -------------------------------------------------------------------------------- 1 | const {Visitor} = require('../models/visitor'); 2 | const {ObjectID} = require('mongodb'); 3 | 4 | exports.addVisitor = function(req, res, next) { 5 | const firstname = req.body.firstname; 6 | const lastname = req.body.lastname; 7 | const email = req.body.email; 8 | 9 | if (!firstname || !lastname || !email) { 10 | return res.status(422).send( {error: 'You must provide firstname, lastname and email'}); 11 | } 12 | 13 | const visitor = new Visitor({ 14 | firstname: firstname, 15 | lastname: lastname, 16 | email: email 17 | }); 18 | 19 | visitor.save(function(err) { 20 | if (err) { return next(err); } 21 | res.status(200).send("Visitor successfully added"); 22 | }); 23 | }; 24 | 25 | exports.getVisitors = function (req, res, next) { 26 | Visitor.find().sort({lastname: 1}).then((visitors) => { 27 | res.send({visitors}); 28 | }, (e) => { 29 | res.status(400).send(e); 30 | }) 31 | }; 32 | 33 | exports.toogleAccess = function (req, res, next) { 34 | let id = req.params.id; 35 | let access = req.params.value; 36 | 37 | if (!ObjectID.isValid(id)) { 38 | return res.status(404).send(); 39 | } 40 | 41 | Visitor.findByIdAndUpdate(id, {$set: {access: access}}).then((visitor) => { 42 | if (!visitor) { 43 | return res.status(404).send("Not valid id " + id); 44 | } 45 | res.status(200).send("Access changed"); 46 | }).catch((e) => { 47 | res.status(400).send("Error while changing access"); 48 | }); 49 | }; 50 | 51 | exports.deleteVisitor = function (req, res, next) { 52 | let id = req.params.id; 53 | 54 | if (!ObjectID.isValid(id)) { 55 | return res.status(404).send("Not valid id " + id); 56 | } 57 | 58 | Visitor.findByIdAndRemove(id).then((visitor) => { 59 | if (!visitor) { 60 | return res.status(404).send(); 61 | } 62 | res.status(200).send("Visitor deleted"); 63 | }).catch((e) => { 64 | res.status(400).send("Can not delete"); 65 | }); 66 | 67 | }; 68 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const http = require('http'); 3 | const bodyParser = require('body-parser'); 4 | const morgan = require('morgan'); 5 | const router = require('./router'); 6 | const mongoose = require('mongoose'); 7 | const cors = require('cors'); 8 | const app = express(); 9 | 10 | // default to a 'localhost' configuration: 11 | let connection_string = '127.0.0.1:27017/smartbell'; 12 | 13 | // DB Setup 14 | mongoose.connect('mongodb://'+ connection_string); 15 | 16 | // App setup 17 | app.use(morgan('combined')); 18 | app.use(cors()); 19 | //app.use(bodyParser.json({ type: '*/*' })); 20 | app.use(bodyParser.json({limit:'50mb'})); 21 | app.use(bodyParser.urlencoded({extended:true, limit:'50mb'})); 22 | router(app); 23 | 24 | // Server setup 25 | const port = 3090; 26 | const ip = '0.0.0.0'; 27 | const server = http.createServer(app); 28 | server.listen(port, ip); 29 | console.log("Listening on " + ip + ", port " + port ); -------------------------------------------------------------------------------- /server/models/log.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | var Log = mongoose.model('log', { 4 | firstname: { 5 | type: String, 6 | required: true 7 | }, 8 | lastname: { 9 | type: String, 10 | required: true 11 | }, 12 | time: { 13 | type: String 14 | } 15 | }); 16 | 17 | module.exports = {Log}; 18 | -------------------------------------------------------------------------------- /server/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | const bcrypt = require('bcrypt-nodejs'); 4 | 5 | // Define model 6 | const userSchema = new Schema({ 7 | email: { type: String, unique: true, lowercase: true }, 8 | password: String 9 | }); 10 | 11 | // On save hook, encrypt password 12 | userSchema.pre('save', function(next) { 13 | const user = this; 14 | 15 | bcrypt.genSalt(10, function(err, salt) { 16 | if (err) { return next(err); } 17 | 18 | bcrypt.hash(user.password, salt, null, function(err, hash) { 19 | if (err) { return next(err); } 20 | 21 | user.password = hash; 22 | next(); 23 | }); 24 | }); 25 | }); 26 | 27 | userSchema.methods.comparePassword = function(candidatePassword, callback) { 28 | bcrypt.compare(candidatePassword, this.password, function(err, isMatch) { 29 | if (err) { return callback(err); } 30 | 31 | callback(null, isMatch); 32 | }); 33 | }; 34 | 35 | // Create the model class 36 | const ModelClass = mongoose.model('user', userSchema); 37 | 38 | // Export the model 39 | module.exports = ModelClass; -------------------------------------------------------------------------------- /server/models/visitor.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | var Visitor = mongoose.model('visitor', { 4 | firstname: { 5 | type: String, 6 | required: true 7 | }, 8 | lastname: { 9 | type: String, 10 | required: true 11 | }, 12 | email: { 13 | type: String 14 | }, 15 | access: { 16 | type: Boolean, 17 | default: true 18 | } 19 | }); 20 | 21 | module.exports = {Visitor}; -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server_doorbell", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "nodemon index.js", 9 | "start": "node index.js" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "axios": "^0.16.2", 15 | "bcrypt-nodejs": "0.0.3", 16 | "body-parser": "^1.17.2", 17 | "cors": "^2.8.3", 18 | "express": "^4.15.3", 19 | "images-upload-middleware": "^1.1.1", 20 | "jwt-simple": "^0.5.1", 21 | "mongodb": "^2.2.29", 22 | "mongoose": "^4.11.1", 23 | "mongoose-paginate": "^5.0.3", 24 | "mongoose-type-email": "^1.0.5", 25 | "morgan": "^1.8.2", 26 | "multer": "^1.3.0", 27 | "nodemon": "^1.11.0", 28 | "passport": "^0.4.0", 29 | "passport-jwt": "^2.2.1", 30 | "passport-local": "^1.0.0", 31 | "request": "^2.81.0" 32 | }, 33 | "devDependencies": { 34 | "webpack": "^3.6.0", 35 | "webpack-dev-middleware": "^1.12.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /server/router.js: -------------------------------------------------------------------------------- 1 | const Authentication = require('./controllers/authentication'); 2 | const ManageVisitors = require('./controllers/manage-visitors'); 3 | const ManageLogs = require('./controllers/manage-logs'); 4 | const ManagePhoto = require('./controllers/manage-photo-upload'); 5 | const passportService = require('./services/passport'); 6 | const passport = require('passport'); 7 | 8 | const requireAuth = passport.authenticate('jwt', { session: false }); 9 | const requireSignin = passport.authenticate('local', { session: false}); 10 | 11 | module.exports = function(app) { 12 | app.post('/signin', requireSignin, Authentication.signin); 13 | app.post('/signup', Authentication.signup); 14 | app.post('/add_visitor', ManageVisitors.addVisitor); 15 | app.get('/visitors', ManageVisitors.getVisitors); 16 | app.delete('/delete/:id', ManageVisitors.deleteVisitor); 17 | app.patch('/toogle/:id/:value', ManageVisitors.toogleAccess); 18 | app.get('/logs', ManageLogs.getLogs); 19 | app.post('/logpost', ManageLogs.addLog); 20 | app.post('/photo', ManagePhoto.photoUpload); 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /server/services/passport.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | const User = require('../models/user'); 3 | const config = require('../config'); 4 | const JwtStrategy = require('passport-jwt').Strategy; 5 | const ExtractJwt = require('passport-jwt').ExtractJwt; 6 | const LocalStrategy = require('passport-local'); 7 | 8 | // Create local strategy 9 | const localOptions = { usernameField: 'email' }; 10 | const localLogin = new LocalStrategy (localOptions, function(email, password, done) { 11 | User.findOne( { email: email }, function(err, user) { 12 | if (err) { return done(err); } 13 | if (!user) { return done(null, false); } 14 | 15 | user.comparePassword(password, function(err, isMatch) { 16 | if (err) { return done(err); } 17 | if (!isMatch) { return done(null, false); } 18 | 19 | return done(null, user); 20 | }); 21 | }); 22 | }); 23 | 24 | // Setup JWT Strategy options 25 | const jwtOptions = { 26 | jwtFromRequest: ExtractJwt.fromHeader('authorization'), 27 | secretOrKey: config.secret 28 | }; 29 | 30 | // Create JWT strategy 31 | const jwtLogin = new JwtStrategy(jwtOptions, function(payload, done) { 32 | User.findById(payload.sub, function(err, user) { 33 | if (err) { return done(err, false); } 34 | 35 | if (user) { 36 | done(null, user); 37 | } else { 38 | done(null, false); 39 | } 40 | }); 41 | }); 42 | 43 | // Tell passport to use strategies 44 | passport.use(jwtLogin); 45 | passport.use(localLogin); --------------------------------------------------------------------------------