├── .gitignore ├── LICENSE ├── README.md ├── forge.config.js ├── package.json ├── src ├── main │ ├── index.js │ ├── modules │ │ └── recognition.js │ └── sensor │ │ └── recognition.py └── renderer │ ├── images │ ├── revregistration.png │ └── wrongfinger.png │ ├── index.html │ ├── index.js │ ├── locales │ ├── en.js │ └── pl.js │ ├── modules │ └── recognition.js │ ├── preload.js │ └── style.css ├── webpack.main.config.js ├── webpack.renderer.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out 3 | .webpack 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Filip Drozd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | RevRegistration 3 |

Registration of presence using fingerprints. 4 |

5 | 6 | ## Table Of Contents 7 | - [Hardware](#hardware) 8 | - [Installation](#installation) 9 | - [XServer](#xserver) 10 | - [LCD screen](#lcd-screen) 11 | - [Fingerprint sensor](#fingerprint-sensor) 12 | - [RevRegistration](#revregistration) 13 | - [Configuration](#configuration) 14 | - [Server api](#server-api) 15 | 16 | ## Hardware 17 | ![](https://media.giphy.com/media/hvMZRz05ugyAeQybld/giphy.gif) 18 | #### Components that you need to create a device: 19 | - Raspberry Pi 3 B+ 20 | - Fingerprint sensor 21 | - USB - UART converter 22 | - 3.5 inch TFT LCD display 23 | 24 | ## Installation 25 | First you need to [install Raspbian Lite](https://www.raspberrypi.org/documentation/installation/installing-images/) and update it. 26 | ### XServer 27 | Then set up your Raspberry Pi as a Kiosk. [This](https://www.youtube.com/watch?v=I2laR5G5FFo) video explains how to do it exactly and below are the commands: 28 | ##### Install [xserver](https://en.wikipedia.org/wiki/X.Org_Server) so that Raspbian can display graphical interface: 29 | ``` 30 | ~$ sudo apt install --no-install-recommends xserver-xorg x11-xserver-utils xinit openbox 31 | ``` 32 | ##### Run xserver at system startup - add to the file: **/home/pi/.profile** 33 | ``` 34 | [[ -z $DISPLAY && $XDG_VTNR -eq 1 ]] && startx -- -nocursor 35 | ``` 36 | ##### Add the following lines to the **/etc/xdg/openbox/autostart** file to disable the screensaver. 37 | 38 | ``` 39 | xset s off 40 | xset s noblank 41 | xset -dpms 42 | ``` 43 | ### LCD screen 44 | For the screen we advise you to use the [LCD-show](https://github.com/goodtft/LCD-show) driver developed by goodtft. For more details visit the github repo. For the 3.5" display the commands are as follows: 45 | ``` 46 | ~$ git clone https://github.com/goodtft/LCD-show.git 47 | ~$ chmod -R 755 LCD-show 48 | ~$ cd LCD-show/ 49 | ~$ sudo ./LCD35-show 50 | ``` 51 | ##### To make the xserver work on the display you need to install the following driver: 52 | ``` 53 | ~$ sudo apt install xserver-xorg-video-fbturbo 54 | ``` 55 | And change it's config file: **/usr/share/X11/xorg.conf.d/99-fbturbo.conf** by setting the **fbdev** option to **/dev/fb1** (second frame buffer - your TFT display). 56 | ``` 57 | Option "fbdev" "/dev/fb1" 58 | ``` 59 | By default it is set to **/dev/fb0** (first frame buffer - HDMI). 60 | ### Fingerprint sensor 61 | To operate the fingerprint sensor RevRegistration uses the Python library called [pyfingerprint](https://github.com/bastianraschke/pyfingerprint) developed by Bastian Raschke. Check it out for more details on how to set it up. Also make sure to install the Python 3 version. Here are the commands: 62 | ``` 63 | ~$ sudo apt-get install git devscripts equivs 64 | ~$ git clone https://github.com/bastianraschke/pyfingerprint.git 65 | ~$ cd ./pyfingerprint/src/ 66 | ~$ sudo mk-build-deps -i debian/control 67 | ~$ dpkg-buildpackage -uc -us 68 | ``` 69 | ``` 70 | ~$ sudo dpkg -i ../python3-fingerprint*.deb 71 | ~$ sudo apt-get -f install 72 | ``` 73 | ``` 74 | ~$ sudo usermod -a -G dialout pi 75 | ~$ sudo reboot 76 | ``` 77 | For now, if you want to enroll a new finger you have to do it manually by running the **example_enroll.py** script. 78 | ### RevRegistration 79 | Now you are ready to install the RevRegistration app. First you have to build it using node.js and yarn (we recommend doing this on your desktop). You can download node.js with npm [here](https://nodejs.org/en/download/) and optionally yarn [here](https://yarnpkg.com/en/docs/getting-started). 80 | ##### Clone the repository: 81 | ``` 82 | ~$ git clone https://github.com/fpdrozd/revregistration-raspberry.git 83 | ``` 84 | ##### Install dependencies 85 | ``` 86 | ~$ cd revregistration 87 | ~$ yarn 88 | ``` 89 | ##### Build the package 90 | ``` 91 | ~$ yarn make 92 | ``` 93 | This might take a few minutes. 94 | 95 | ##### When it's done you need to copy the Debian package to your Raspberry Pi: 96 | ``` 97 | ~$ scp out/make/revregistration*.deb pi@192.168.0.100:/home/pi/ 98 | ``` 99 | 100 | ##### Then install the package: 101 | ``` 102 | ~$ sudo dpkg -i revregistration*.deb 103 | ``` 104 | ##### Install missing dependencies: 105 | ``` 106 | ~$ sudo apt -f install 107 | ``` 108 | And the installation is completed. 109 | 110 | ## Configuration 111 | The application is accessible in the system by the following command: 112 | 113 | ``` 114 | ~$ revregistration 115 | ``` 116 | You need to add it to the **/etc/xdg/openbox/autostart** file with the proper parameters. For example: 117 | ``` 118 | revregistration --lang en --api_host https://example.com --api_path /registration --raspberry_pwd !@#$%^& --sensor_id UNIQUE-USB-DEVICE-ID 119 | ``` 120 | #### Here is a description of all parameters: 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 |
OptionDefaultDescription
langenInterface language. Possible values are: en, pl
api_hostnoneThe hostname of your api.
api_pathnoneA path to your api.
raspberry_pwdnoneA password that will be used to authenticate your Raspberry Pi against the server.
sensor_idnoneA unique id of your uart converter that can be fined under the /dev/serial/by-id/ directory.
155 | 156 | ## Server api 157 | For the system to work correctly you need a server with some kind of database that will store and manage all the presences. Lets suppose that your server is running under the **https://example.com/registration**. 158 | ### Requests 159 | When the application starts up it's first trying to fetch all the users by requesting the **https://example.com/registration/api/raspberry/fetch**. In response it expects a JSON data in the following form: 160 | ``` 161 | { 162 | data: [ 163 | { email: 'peter@gmail.com', firstName: 'Peter', lastName: 'Griffin', department: 'sales', fingerprintIds: [0, 1] }, 164 | { email: 'brian@gmail.com', firstName: 'Brian', lastName: 'Griffin', department: 'accounting', fingerprintIds: [2, 3] } 165 | ... 166 | ] 167 | } 168 | ``` 169 | ### Socket.io 170 | Then it's trying to connect to the Socket.io server under the **https://example.com/registration/api/socket.io**. 171 | #### Here is the comprehensive list of all the Socket.io events: 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 |
Event nameDescription
newPresenceIt's triggered when the person places a finger and the fingerprint is recognized. It comes with the JSON object in the following form: { email: 'peter@gmail.com', department: 'sales' }
185 | 186 | ### This is how a Node.js api might look like: 187 | ```javascript 188 | const express = require('express'); 189 | const http = require('http'); 190 | const bodyParser = require('body-parser'); 191 | const socketIo = require('socket.io'); 192 | const jwt = require('jsonwebtoken'); 193 | 194 | // Express and Socket.io setup 195 | const app = express(); 196 | const server = http.createServer(app); 197 | const io = socketIo(server); 198 | 199 | app.use(bodyParser.urlencoded({ extended: false })); 200 | app.use(bodyParser.json()); 201 | 202 | // Handle a post request to fetch users 203 | app.post('/raspberry/fetch', (req, res) => { 204 | const pwd = req.body.password; 205 | 206 | if (pwd && pwd == process.env.RASPBERRY_PWD) { 207 | // Query to the database here 208 | 209 | const queryResult = [ 210 | { email: 'peter@gmail.com', firstName: 'Peter', lastName: 'Griffin', department: 'sales', fingerprintIds: [0, 1] }, 211 | { email: 'brian@gmail.com', firstName: 'Brian', lastName: 'Griffin', department: 'accounting', fingerprintIds: [2, 3] } 212 | ]; 213 | // Send a list of users in JSON 214 | res.json({ data: queryResult }); 215 | } 216 | }); 217 | 218 | // Set up "raspberry" namespace 219 | const raspberry = io.of('/raspberry'); 220 | 221 | // Namespace authentication middleware 222 | raspberry.use((socket, next) => { 223 | const query = socket.handshake.query; 224 | 225 | const verifyRaspberry = () => { 226 | if (query.raspberryPwd == process.env.RASPBERRY_PWD) next(); 227 | else next(new Error('Unauthorized')); 228 | }; 229 | 230 | if (query.raspberryPwd && typeof query.raspberryPwd == 'string') verifyRaspberry(); 231 | else next(new Error('Bad Request')); 232 | }); 233 | 234 | // Handle new connections 235 | raspberry.on('connection', (socket) => { 236 | // Handle "newPresence" event 237 | socket.on('newPresence', (person) => { 238 | // Save to the database here 239 | }); 240 | }); 241 | 242 | server.listen(3000); 243 | ``` 244 | #### And here is the Nginx configuration: 245 | ```nginx 246 | server { 247 | listen 80; 248 | server_name 127.0.0.1; 249 | 250 | location /registration/api/ { 251 | proxy_pass http://127.0.0.1:3000/; 252 | } 253 | location /registration/api/socket.io/ { 254 | proxy_http_version 1.1; 255 | proxy_set_header Upgrade $http_upgrade; 256 | proxy_set_header Connection "upgrade"; 257 | 258 | proxy_pass http://127.0.0.1:3000/socket.io/; 259 | } 260 | } 261 | ``` 262 | -------------------------------------------------------------------------------- /forge.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | packageConfig: {}, 3 | makers: [ 4 | { 5 | name: '@electron-forge/maker-deb', 6 | config: { 7 | name: 'revregistration' 8 | } 9 | } 10 | ], 11 | plugins: [ 12 | [ 13 | '@electron-forge/plugin-webpack', 14 | { 15 | mainConfig: './webpack.main.config.js', 16 | renderer: { 17 | config: './webpack.renderer.config.js', 18 | entryPoints: [ 19 | { 20 | html: './src/renderer/index.html', 21 | js: './src/renderer/index.js', 22 | name: 'main_window', 23 | preload: { 24 | js: './src/renderer/preload.js' 25 | } 26 | } 27 | ] 28 | } 29 | } 30 | ] 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "revregistration-raspberry", 3 | "productName": "revregistration-raspberry", 4 | "version": "0.1.0", 5 | "description": "Registration of presence using fingerprints.", 6 | "main": ".webpack/main", 7 | "scripts": { 8 | "start": "electron-forge start", 9 | "package": "electron-forge package --arch armv7l", 10 | "make": "electron-forge make --arch armv7l" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/fpdrozd/RevRegistration-raspberry.git" 15 | }, 16 | "keywords": [ 17 | "registration", 18 | "presence", 19 | "fingerprint" 20 | ], 21 | "author": "Filip Drozd", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/fpdrozd/RevRegistration-raspberry/issues" 25 | }, 26 | "homepage": "https://github.com/fpdrozd/RevRegistration-raspberry#readme", 27 | "config": { 28 | "forge": "./forge.config.js" 29 | }, 30 | "dependencies": { 31 | "axios": "^0.19.0", 32 | "electron-squirrel-startup": "^1.0.0", 33 | "i18next": "^15.1.3", 34 | "minimist": "^1.2.0", 35 | "socket.io-client": "^2.2.0" 36 | }, 37 | "devDependencies": { 38 | "@electron-forge/cli": "6.0.0-beta.39", 39 | "@electron-forge/maker-deb": "6.0.0-beta.39", 40 | "@electron-forge/maker-rpm": "6.0.0-beta.39", 41 | "@electron-forge/maker-squirrel": "6.0.0-beta.39", 42 | "@electron-forge/maker-zip": "6.0.0-beta.39", 43 | "@electron-forge/plugin-webpack": "6.0.0-beta.39", 44 | "@marshallofsound/webpack-asset-relocator-loader": "^0.5.0", 45 | "copy-webpack-plugin": "^5.0.3", 46 | "css-loader": "^2.1.1", 47 | "electron": "5.0.2", 48 | "file-loader": "^3.0.1", 49 | "node-loader": "^0.6.0", 50 | "style-loader": "^0.23.1" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/index.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow } = require('electron'); 2 | var minimist = require('minimist'); 3 | const recognition = require('./modules/recognition'); 4 | 5 | const isDev = process.env.NODE_ENV == 'development'; 6 | const argv = minimist(process.argv.slice(isDev ? 2 : 0)); 7 | process.env.APP_LANG = !!argv.lang ? argv.lang : 'en'; 8 | 9 | let mainWindow; 10 | 11 | const createWindow = () => { 12 | mainWindow = new BrowserWindow({ 13 | width: 480, 14 | height: 320 + (isDev ? 25 : 0), 15 | webPreferences: { 16 | nodeIntegration: false, 17 | preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY 18 | }, 19 | frame: isDev 20 | }); 21 | 22 | mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY); 23 | if (isDev) mainWindow.webContents.openDevTools(); 24 | 25 | recognition.init(mainWindow, argv); 26 | 27 | mainWindow.on('closed', () => mainWindow = null); 28 | }; 29 | 30 | app.on('ready', createWindow); 31 | -------------------------------------------------------------------------------- /src/main/modules/recognition.js: -------------------------------------------------------------------------------- 1 | const socketIoClient = require('socket.io-client'); 2 | const axios = require('axios'); 3 | const spawn = require('child_process').spawn; 4 | const { ipcMain } = require('electron'); 5 | 6 | const recognition = (mainWindow, argv) => { 7 | let users, sensorReady = false, socketReady = false; 8 | 9 | const emitReady = () => { 10 | if (sensorReady && socketReady) mainWindow.webContents.send('ready'); 11 | }; 12 | const emitError = () => mainWindow.webContents.send('error'); 13 | const emitUnknown = () => mainWindow.webContents.send('unknown'); 14 | const emitMatch = (person) => { 15 | mainWindow.webContents.send('match', person); 16 | socket.emit('newPresence', { 17 | email: person.email, 18 | department: person.department 19 | }); 20 | }; 21 | 22 | ipcMain.on('guiReady', emitReady); 23 | 24 | const socketConnect = () => { 25 | socketReady = true; 26 | emitReady(); 27 | }; 28 | const socketDisconnect = () => { 29 | socketReady = false; 30 | emitError(); 31 | }; 32 | const socket = socketIoClient(`${argv.api_host}/raspberry`, { 33 | path: `${argv.api_path != '/' ? argv.api_path : ''}/api/socket.io`, 34 | query: { raspberryPwd: argv.raspberry_pwd } 35 | }); 36 | socket.on('connect', socketConnect); 37 | socket.on('disconnect', socketDisconnect); 38 | 39 | const fingerprintReady = () => { 40 | sensorReady = true; 41 | emitReady(); 42 | }; 43 | const fingerprintError = () => { 44 | sensorReady = false; 45 | emitError(); 46 | }; 47 | const fingerprintMatch = (id) => { 48 | const person = users.find(user => user.fingerprintIds.includes(parseInt(id))); 49 | if (socketReady) emitMatch(person); 50 | }; 51 | 52 | const connectSensor = () => { 53 | const fingerprint = spawn('python3', ['-u', `${__dirname}/sensor/recognition.py`, argv.sensor_id]); 54 | fingerprint.stdout.on('data', (data) => { 55 | let msg = data.toString().replace(/\n/g, ""); 56 | 57 | if (msg == 'ready') fingerprintReady(); 58 | else if (msg == 'error') fingerprintError(); 59 | else if (msg == 'unknown') emitUnknown(); 60 | else fingerprintMatch(msg); 61 | }); 62 | }; 63 | 64 | const retryToFetchUsers = () => setTimeout(fetchUsers, 5000); 65 | const fetchUsers = () => axios({ 66 | method: 'post', 67 | url: `${argv.api_path != '/' ? argv.api_path : ''}/api/raspberry/fetch`, 68 | baseURL: argv.api_host, 69 | data: { 70 | password: argv.raspberry_pwd 71 | } 72 | }).then(res => res.data) 73 | .then(res => users = res.data) 74 | .then(connectSensor) 75 | .catch(err => { retryToFetchUsers(); }); 76 | 77 | fetchUsers(); 78 | }; 79 | 80 | module.exports = { init: recognition }; 81 | -------------------------------------------------------------------------------- /src/main/sensor/recognition.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import hashlib 4 | from pyfingerprint.pyfingerprint import PyFingerprint 5 | 6 | sensorId = sys.argv[1]; 7 | 8 | time.sleep(5) 9 | 10 | while True: 11 | try: 12 | f = PyFingerprint('/dev/serial/by-id/'+sensorId, 57600, 0xFFFFFFFF, 0x00000000) 13 | 14 | if ( f.verifyPassword() == False ): 15 | raise ValueError('The given fingerprint sensor password is wrong!') 16 | 17 | while True: 18 | print('ready') 19 | sys.stdout.flush() 20 | 21 | while ( f.readImage() == False ): 22 | pass 23 | 24 | f.convertImage(0x01) 25 | result = f.searchTemplate() 26 | positionNumber = result[0] 27 | 28 | if ( positionNumber == -1 ): 29 | print('unknown') 30 | sys.stdout.flush() 31 | time.sleep(2) 32 | else: 33 | print(positionNumber) 34 | sys.stdout.flush() 35 | time.sleep(3) 36 | 37 | except Exception as e: 38 | print('error') 39 | sys.stdout.flush() 40 | time.sleep(5) 41 | -------------------------------------------------------------------------------- /src/renderer/images/revregistration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpdrozd/RevRegistration-raspberry/af1539cfee976d56a4437cc7cab5850ece85462c/src/renderer/images/revregistration.png -------------------------------------------------------------------------------- /src/renderer/images/wrongfinger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fpdrozd/RevRegistration-raspberry/af1539cfee976d56a4437cc7cab5850ece85462c/src/renderer/images/wrongfinger.png -------------------------------------------------------------------------------- /src/renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | RevRegistration 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /src/renderer/index.js: -------------------------------------------------------------------------------- 1 | import i18next from 'i18next'; 2 | import en from './locales/en'; 3 | import pl from './locales/pl'; 4 | 5 | import './style.css'; 6 | import './modules/recognition'; 7 | 8 | import unknownFinger from './images/wrongfinger.png'; 9 | 10 | i18next.init({ 11 | lng: APP_LANG, 12 | resources: { 13 | en: { translation: en }, 14 | pl: { translation: pl } 15 | } 16 | }).then(() => { 17 | document.querySelector('#app').innerHTML = ` 18 |
19 |
20 |
21 |
22 |
23 | ${i18next.t('connecting')} 24 |
25 |
26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 |

44 | 45 | 46 |

47 |
48 |
49 |
50 | `; 51 | 52 | document.addEventListener('DOMContentLoaded', () => { 53 | document.querySelector('.recognition_fingerprint_unknown').src = unknownFinger; 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /src/renderer/locales/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | connecting: 'Connecting', 3 | unknownFingerprint: { 4 | unknown: 'Unknown', 5 | fingerprint: 'fingerprint' 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /src/renderer/locales/pl.js: -------------------------------------------------------------------------------- 1 | export default { 2 | connecting: 'Łączenie', 3 | unknownFingerprint: { 4 | unknown: 'Nieznany', 5 | fingerprint: 'odcisk palca' 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /src/renderer/modules/recognition.js: -------------------------------------------------------------------------------- 1 | import i18next from 'i18next'; 2 | 3 | const recognition = () => { 4 | const box = document.querySelector('.box'); 5 | const recognition = document.querySelector('.recognition'); 6 | const fingerprintLines = document.querySelectorAll('.recognition_fingerprint_line'); 7 | const personFirstName = document.querySelector('.recognition_person_firstname'); 8 | const personLastName = document.querySelector('.recognition_person_lastname'); 9 | 10 | const showLoadingMsg = () => box.classList.add('box--loading'); 11 | const hideLoadingMsg = () => box.classList.remove('box--loading'); 12 | 13 | const goToReception = () => { 14 | box.classList.remove('box--recognition'); 15 | fingerprintLines.forEach((line) => { 16 | line.classList.remove('active'); 17 | }); 18 | }; 19 | const goToRecognition = (time) => { 20 | box.classList.add('box--recognition'); 21 | setTimeout(goToReception, time); 22 | }; 23 | 24 | const unknown = () => { 25 | recognition.classList.add('recognition--unknown'); 26 | personFirstName.textContent = i18next.t('unknownFingerprint.unknown'); 27 | personLastName.textContent = i18next.t('unknownFingerprint.fingerprint'); 28 | goToRecognition(2000); 29 | }; 30 | const fingerprintAnimation = () => { 31 | fingerprintLines.forEach((line) => { 32 | line.classList.add('active'); 33 | }); 34 | }; 35 | const match = (user) => { 36 | recognition.classList.remove('recognition--unknown'); 37 | personFirstName.textContent = user.firstName; 38 | personLastName.textContent = user.lastName; 39 | goToRecognition(3000); 40 | fingerprintAnimation(); 41 | }; 42 | 43 | ipcRenderer.on('ready', hideLoadingMsg); 44 | ipcRenderer.on('error', showLoadingMsg); 45 | ipcRenderer.on('unknown', unknown); 46 | ipcRenderer.on('match', (ev, msg) => match(msg)); 47 | 48 | ipcRenderer.send('guiReady'); 49 | }; 50 | 51 | document.addEventListener("DOMContentLoaded", recognition); 52 | -------------------------------------------------------------------------------- /src/renderer/preload.js: -------------------------------------------------------------------------------- 1 | window.ipcRenderer = require('electron').ipcRenderer; 2 | window.APP_LANG = process.env.APP_LANG; 3 | -------------------------------------------------------------------------------- /src/renderer/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Roboto', sans-serif; 3 | margin: 0; 4 | font-size: 20px; 5 | } 6 | 7 | .box { 8 | width: 100vw; 9 | height: 100vh; 10 | overflow: hidden; 11 | } 12 | .wrapper { 13 | clear: both; 14 | width: 200vw; 15 | height: 100vh; 16 | } 17 | 18 | .box--recognition .reception, 19 | .box--recognition .recognition { 20 | left: -100vw; 21 | } 22 | .reception, .recognition { 23 | float: left; 24 | left: 0; 25 | position: relative; 26 | width: 100vw; 27 | height: 100vh; 28 | } 29 | 30 | .reception { 31 | background-image: url("images/revregistration.png"); 32 | background-repeat: no-repeat; 33 | background-size: 30%; 34 | background-position: center; 35 | } 36 | .box--loading .reception { 37 | background-image: none; 38 | } 39 | 40 | .reception_loading { 41 | width: 100%; 42 | height: 100%; 43 | display: none; 44 | align-items: center; 45 | justify-content: center; 46 | } 47 | .box--loading .reception_loading { 48 | display: flex; 49 | } 50 | .reception_loading_text { 51 | padding: 20px; 52 | font-size: 1.1em; 53 | } 54 | 55 | .recognition { 56 | box-sizing: border-box; 57 | padding-top: calc((100vh - 120px) / 2); 58 | padding-left: 40px; 59 | } 60 | 61 | .recognition_fingerprint { 62 | float: left; 63 | width: 120px; 64 | height: 120px; 65 | } 66 | 67 | .recognition_fingerprint_match { 68 | cursor: pointer; 69 | margin-left: 50%; 70 | margin-top: 50%; 71 | transform: translate3d(-50%, -50%, 0); 72 | } 73 | .recognition--unknown .recognition_fingerprint_match { display: none; } 74 | .recognition_fingerprint_line { 75 | fill: none; 76 | stroke: #3871c2; 77 | stroke-dashoffset: 0; 78 | stroke-width: 1; 79 | transition: all 300ms ease; 80 | } 81 | .recognition_fingerprint_line.active { animation: drawFingerprint 6s forwards; } 82 | .recognition_fingerprint_line--a { stroke-dasharray: 12.15426; } 83 | .recognition_fingerprint_line--b { stroke-dasharray: 19.79116; } 84 | .recognition_fingerprint_line--c { stroke-dasharray: 53.00725; } 85 | .recognition_fingerprint_line--d { stroke-dasharray: 23.70178; } 86 | .recognition_fingerprint_line--e { stroke-dasharray: 8.83748; } 87 | @keyframes drawFingerprint { 88 | 20% { stroke-dashoffset: 40; } 89 | 40% { stroke-dashoffset: 0; } 90 | 100% { stroke-dashoffset: 0; } 91 | } 92 | 93 | .recognition_fingerprint_unknown { width: 120px; display: none; } 94 | .recognition--unknown .recognition_fingerprint_unknown { display: block; } 95 | 96 | .recognition_person { 97 | height: 100px; 98 | width: calc(100vw - 120px - 70px - 40px); 99 | float: left; 100 | margin: 0; 101 | padding-left: 30px; 102 | padding-top: 10px; 103 | font-size: 36px; 104 | line-height: 50px; 105 | display: flex; 106 | flex-wrap: wrap; 107 | align-items: center; 108 | } 109 | .recognition_person span { 110 | display: block; 111 | padding-right: 10px; 112 | } 113 | 114 | .loading { 115 | display: block; 116 | position: relative; 117 | width: 64px; 118 | height: 64px; 119 | } 120 | .loading::after, .loading::before { 121 | display: block; 122 | content: ''; 123 | position: absolute; 124 | border: 4px solid #fff; 125 | opacity: 1; 126 | border-radius: 50%; 127 | background-color: #0061ff; 128 | animation: loading 1s cubic-bezier(0, 0.2, 0.8, 1) infinite; 129 | } 130 | .loading::before { 131 | animation-delay: -0.5s; 132 | } 133 | @keyframes loading { 134 | 0% { 135 | top: 28px; 136 | left: 28px; 137 | width: 0; 138 | height: 0; 139 | opacity: 1; 140 | } 141 | 100% { 142 | top: -1px; 143 | left: -1px; 144 | width: 58px; 145 | height: 58px; 146 | opacity: 0; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /webpack.main.config.js: -------------------------------------------------------------------------------- 1 | const CopyPlugin = require('copy-webpack-plugin'); 2 | 3 | module.exports = { 4 | entry: './src/main/index.js', 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.node$/, 9 | use: 'node-loader', 10 | }, 11 | { 12 | test: /\.(m?js|node)$/, 13 | parser: { amd: false }, 14 | use: { 15 | loader: '@marshallofsound/webpack-asset-relocator-loader', 16 | options: { 17 | outputAssetBase: 'native_modules', 18 | }, 19 | }, 20 | } 21 | ] 22 | }, 23 | plugins: [ 24 | new CopyPlugin([ 25 | { from: 'src/main/sensor/', to: 'sensor' } 26 | ]) 27 | ] 28 | }; 29 | -------------------------------------------------------------------------------- /webpack.renderer.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | output: { 3 | publicPath: './../' 4 | }, 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.node$/, 9 | use: 'node-loader', 10 | }, 11 | { 12 | test: /\.(m?js|node)$/, 13 | parser: { amd: false }, 14 | use: { 15 | loader: '@marshallofsound/webpack-asset-relocator-loader', 16 | options: { 17 | outputAssetBase: 'native_modules', 18 | }, 19 | }, 20 | }, 21 | { 22 | test: /\.css$/, 23 | use: ['style-loader','css-loader'] 24 | }, 25 | { 26 | test: /\.(png|jpg|gif)$/, 27 | use: { 28 | loader: 'file-loader' 29 | } 30 | } 31 | ] 32 | } 33 | }; 34 | --------------------------------------------------------------------------------