├── .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 | 
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 | 
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 | 
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 |
152 |
153 |
Basic information is saved.
154 |
Please save your photo via either 'Make photo' or 'File upload'
155 |
156 | Photo Upload:
157 |
158 |
159 |
160 |
Make photo
161 |
{if(confirm('Are you sure to cancel adding the visitor?')){this.onClickFormCancel()}}} className='btn btn-danger'>Cancel Addition
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 |
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 |
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 |
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 |
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
10 |
11 | Sign Out
12 |
13 |
14 | } else {
15 | // show a link to sign in or sign up
16 | return [
17 |
18 |
19 | Sign In
20 | ,
21 |
22 | Sign Up
23 |
24 |
25 | ];
26 | }
27 | }
28 |
29 | render() {
30 | return (
31 |
32 |
33 | Smart bell
34 |
35 |
36 |
37 | {this.renderLinks()}
38 |
39 |
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 |
155 | this.handleFirst(e.currentTarget.value)} value={this.state.current}>1
156 | this.handlePrev(e.currentTarget.value)} value={this.state.current}>Prev
157 | {this.renderPage()}
158 | this.handleNext(e.currentTarget.value)} value={this.state.current}>Next
159 | this.handleLast(e.currentTarget.value)} value={this.state.current}>{pagesTotal}
160 |
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 |
Logs Per Page:
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 |
Pages:
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 |
20 |
21 | {this.props.tabList.map(function(tab) {
22 | return (
23 |
30 | );
31 | }.bind(this))}
32 |
33 |
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 | Delete
83 | {visitor.access == true ? 'Allowed' : 'Denied'}
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 | Delete
246 | {visitor.access == true ? 'Allowed' : 'Denied'}
247 |
248 | );
249 | }
250 | });
251 | }
252 | }
253 | handlePerPage(perPage){
254 | this.setState({perPage});
255 | }
256 | perPageRender(){
257 | return(
258 |
259 |
Visitors Per Page:
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 |
Pages:
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);
--------------------------------------------------------------------------------