mirror of
				https://github.com/zyphlar/doorlock.git
				synced 2024-04-03 21:36:03 +00:00 
			
		
		
		
	Simplify code, get scanning working
This commit is contained in:
		
							parent
							
								
									07918feea8
								
							
						
					
					
						commit
						b3eda3a674
					
				| @ -5,5 +5,5 @@ COBOT_REDIRECT_URL='http://10.0.1.48:8080/success' | |||||||
| # COBOT_SCOPE='checkin_tokens' | # COBOT_SCOPE='checkin_tokens' | ||||||
| COBOT_USER_EMAIL='...' | COBOT_USER_EMAIL='...' | ||||||
| COBOT_USER_PASSWORD='...' | COBOT_USER_PASSWORD='...' | ||||||
| RFID_PRODUCT_NAME='Name of USB device' |  | ||||||
| # USB_MOUNT_PATH='...' | # USB_MOUNT_PATH='...' | ||||||
|  | RFID_READER_SERIAL_NUMBER='AH05Q18Z' | ||||||
|  | |||||||
| @ -1,9 +0,0 @@ | |||||||
| const HID = jest.fn() |  | ||||||
| HID.prototype.read = jest.fn() |  | ||||||
| 
 |  | ||||||
| const hid = { |  | ||||||
|   devices: jest.fn(), |  | ||||||
|   HID, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| module.exports = hid |  | ||||||
| @ -6,7 +6,7 @@ const { | |||||||
|   COBOT_SCOPE, |   COBOT_SCOPE, | ||||||
|   COBOT_USER_EMAIL, |   COBOT_USER_EMAIL, | ||||||
|   COBOT_USER_PASSWORD, |   COBOT_USER_PASSWORD, | ||||||
| } = require('../constants') | } = require('./constants') | ||||||
| 
 | 
 | ||||||
| class Cobot { | class Cobot { | ||||||
|   constructor(token) { |   constructor(token) { | ||||||
							
								
								
									
										39
									
								
								constants.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								constants.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | const path = require('path') | ||||||
|  | 
 | ||||||
|  | require('dotenv').config({ path: path.join(__dirname, '..', '.env') }) | ||||||
|  | 
 | ||||||
|  | const ENV = process.env.NODE_ENV || 'development' | ||||||
|  | const USB_MOUNT_PATH = process.env.USB_MOUNT_PATH || '/mnt/sda1' | ||||||
|  | const CARDS_PATH = path.join( | ||||||
|  |   USB_MOUNT_PATH, | ||||||
|  |   process.env.CARDS_PATH || 'cards.json' | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | module.exports = { | ||||||
|  |   CARDS_PATH, | ||||||
|  |   CARD_UPDATE_INTERVAL: process.env.CARD_UPDATE_INTERVAL || 30 * 1000, | ||||||
|  | 
 | ||||||
|  |   // Cobot
 | ||||||
|  |   COBOT_CARDS_API: | ||||||
|  |     process.env.COBOT_CARDS_API || | ||||||
|  |     'https://chimera.cobot.me/api/check_in_tokens', | ||||||
|  |   COBOT_CLIENT_ID: | ||||||
|  |     process.env.COBOT_CLIENT_ID || '7c3e837a765a28c1a47d24599695bd0a', | ||||||
|  |   COBOT_CLIENT_SECRET: | ||||||
|  |     process.env.COBOT_CLIENT_SECRET || | ||||||
|  |     'e4da8ce36c6cb4f792390166a82841c209bc53339e796f59edd3739e6b069889', | ||||||
|  |   COBOT_SCOPE: process.env.COBOT_SCOPE || 'checkin_tokens', | ||||||
|  |   COBOT_USER_EMAIL: process.env.COBOT_USER_EMAIL || 'dana@chimeraarts.org', | ||||||
|  |   COBOT_USER_PASSWORD: process.env.COBOT_USER_PASSWORD || 'save me time', | ||||||
|  | 
 | ||||||
|  |   DOOR_OPEN_DELAY: ENV === 'test' ? 1 : 6000, | ||||||
|  |   ENV, | ||||||
|  | 
 | ||||||
|  |   // Nexudus
 | ||||||
|  |   NEXUDUS_APPLICATION_KEY: process.env.NEXUDUS_APPLICATION_KEY, | ||||||
|  |   NEXUDUS_SECRET_KEY: process.env.NEXUDUS_SECRET_KEY, | ||||||
|  | 
 | ||||||
|  |   RFID_READER_SERIAL_NUMBER: process.env.RFID_READER_SERIAL_NUMBER, | ||||||
|  | 
 | ||||||
|  |   USB_MOUNT_PATH, | ||||||
|  | } | ||||||
							
								
								
									
										179
									
								
								doorlock.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								doorlock.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,179 @@ | |||||||
|  | const chalk = require('chalk') | ||||||
|  | const Cobot = require('./cobot') | ||||||
|  | const fs = require('fs') | ||||||
|  | const SerialPort = require('serialport') | ||||||
|  | const tessel = require('tessel') | ||||||
|  | const { | ||||||
|  |   CARD_UPDATE_INTERVAL, | ||||||
|  |   CARDS_PATH, | ||||||
|  |   DOOR_OPEN_DELAY, | ||||||
|  |   RFID_READER_SERIAL_NUMBER, | ||||||
|  | } = require('./constants') | ||||||
|  | 
 | ||||||
|  | const TEST = process.env.NODE_ENV === 'test' | ||||||
|  | 
 | ||||||
|  | class DoorLock { | ||||||
|  |   constructor() { | ||||||
|  |     this.log(chalk.gray('Initializing doorlock')) | ||||||
|  | 
 | ||||||
|  |     if (!RFID_READER_SERIAL_NUMBER) { | ||||||
|  |       throw new Error('No serial number set!') | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this.cards = [] | ||||||
|  |     // this.fetchCardListFromCobot()
 | ||||||
|  |     this.initializeRFIDReader() | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   initializeRFIDReader() { | ||||||
|  |     this.getListOfSerialDevices() | ||||||
|  |       .then(devices => { | ||||||
|  |         const device = this.findRFIDReaderDevice(devices) | ||||||
|  |         const reader = this.connectToRFIDReaderDevice(device.comName) | ||||||
|  |         reader.on('data', number => this.validateCard(number)) | ||||||
|  |       }) | ||||||
|  |       .catch(this.logErrorMessage) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getListOfSerialDevices() { | ||||||
|  |     return (SerialPort.list() || Promise.resolve([])).then(devices => { | ||||||
|  |       this.log(chalk.green('Available serial devices:')) | ||||||
|  |       this.log(chalk.gray(JSON.stringify(devices.map(d => d.comName), null, 2))) | ||||||
|  |       return devices | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   findRFIDReaderDevice(devices) { | ||||||
|  |     const device = devices.find( | ||||||
|  |       d => d.serialNumber === RFID_READER_SERIAL_NUMBER | ||||||
|  |     ) | ||||||
|  |     if (!device) { | ||||||
|  |       throw new Error( | ||||||
|  |         `No RFID card reader connected with the serial number "${RFID_READER_SERIAL_NUMBER}"!` | ||||||
|  |       ) | ||||||
|  |     } | ||||||
|  |     return device | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   connectToRFIDReaderDevice(port) { | ||||||
|  |     this.log(chalk.green('Connected to device:'), chalk.gray(port)) | ||||||
|  |     const device = new SerialPort(port, { baudRate: 9600 }) | ||||||
|  |     const Readline = SerialPort.parsers.Readline | ||||||
|  |     const parser = new Readline() | ||||||
|  |     return device.pipe(parser) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   validateCard(number) { | ||||||
|  |     console.log('raw:', JSON.stringify(number.toString().trim())) | ||||||
|  |     const scanned = parseInt( | ||||||
|  |       number | ||||||
|  |         .toString('hex') | ||||||
|  |         .trim() // Remove any whiespace or newlines
 | ||||||
|  |         .replace('\u0003', '') // Remove "end of text" character
 | ||||||
|  |         .replace('\u0002', '') // Remove "start of text" character
 | ||||||
|  |         .substring(3) // Strip off con
 | ||||||
|  |         .slice(0, -2), // Strip off checksum
 | ||||||
|  |       16 | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     this.log(chalk.green('Scanned card:'), chalk.gray(scanned)) | ||||||
|  | 
 | ||||||
|  |     return this.readCardsFromSDCard().then(cards => { | ||||||
|  |       const card = cards.find(c => parseInt(c.number) === scanned) | ||||||
|  | 
 | ||||||
|  |       if (card) { | ||||||
|  |         const name = card.name.split(' ')[0] | ||||||
|  |         this.log(chalk.green(`Welcome in ${name}!`), chalk.gray(scanned)) | ||||||
|  |         this.openDoor() | ||||||
|  |       } else { | ||||||
|  |         this.log(chalk.red('Card is invalid:'), chalk.gray(scanned)) | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   openDoor() { | ||||||
|  |     return new Promise(resolve => { | ||||||
|  |       this.log(chalk.green('Opening door!')) | ||||||
|  | 
 | ||||||
|  |       // TODO: trigger door opening...
 | ||||||
|  |       if (tessel.led) tessel.led[3].on() | ||||||
|  | 
 | ||||||
|  |       setTimeout(() => { | ||||||
|  |         // TODO: trigger door closing
 | ||||||
|  |         this.closeDoor() | ||||||
|  |         resolve() | ||||||
|  |       }, DOOR_OPEN_DELAY) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   closeDoor() { | ||||||
|  |     this.log(chalk.green('Closing door!')) | ||||||
|  |     if (tessel.led) tessel.led[3].off() | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   fetchCardListFromCobot() { | ||||||
|  |     this.log(chalk.gray('Updating cards...')) | ||||||
|  |     Cobot.authorize() | ||||||
|  |       .then(cobot => cobot.cards()) | ||||||
|  |       .then(cards => { | ||||||
|  |         this.log(chalk.green('UPDATED CARDS:', cards.length, 'cards')) | ||||||
|  |         this.writeCardsToSDCard(cards) | ||||||
|  |         this.cards = cards | ||||||
|  |       }) | ||||||
|  |       .then(() => { | ||||||
|  |         this.log( | ||||||
|  |           chalk.gray( | ||||||
|  |             'Updating card list in', | ||||||
|  |             CARD_UPDATE_INTERVAL / 1000, | ||||||
|  |             'seconds...' | ||||||
|  |           ) | ||||||
|  |         ) | ||||||
|  |         setTimeout(this.fetchCardListFromCobot.bind(this), CARD_UPDATE_INTERVAL) | ||||||
|  |       }) | ||||||
|  |       .catch(this.logErrorMessage) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   sortCardsByName(cards) { | ||||||
|  |     const sorted = cards.sort( | ||||||
|  |       (a, b) => (a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1) | ||||||
|  |     ) | ||||||
|  |     this.log(chalk.green('CARDS:'), sorted) | ||||||
|  |     return sorted | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   writeCardsToSDCard(cards) { | ||||||
|  |     const json = JSON.stringify(this.sortCardsByName(cards)) | ||||||
|  |     return new Promise((resolve, reject) => { | ||||||
|  |       fs.writeFile(CARDS_PATH, json, err => { | ||||||
|  |         console.log(json) | ||||||
|  |         console.log('JSON', json) | ||||||
|  |         if (err) return reject(err) | ||||||
|  |         resolve() | ||||||
|  |       }) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   readCardsFromSDCard() { | ||||||
|  |     return new Promise((resolve, reject) => { | ||||||
|  |       fs.readFile(CARDS_PATH, (err, data) => { | ||||||
|  |         if (err) return reject(err) | ||||||
|  |         resolve(JSON.parse(data)) | ||||||
|  |       }) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   logErrorMessage(error) { | ||||||
|  |     if (TEST) return | ||||||
|  |     console.error(chalk.red(error.message)) | ||||||
|  |     console.error(chalk.gray(error.stack)) | ||||||
|  |     process.exit | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   log() { | ||||||
|  |     if (TEST) return | ||||||
|  |     console.log(...arguments) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Start up the doorlock
 | ||||||
|  | new DoorLock() | ||||||
							
								
								
									
										85
									
								
								doorlock.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								doorlock.test.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | |||||||
|  | const DoorLock = require('./doorlock') | ||||||
|  | const tessel = require('tessel') | ||||||
|  | const fs = require('fs') | ||||||
|  | const { CARDS_PATH, RFID_READER_SERIAL_NUMBER } = require('./constants') | ||||||
|  | 
 | ||||||
|  | jest.mock('tessel') | ||||||
|  | jest.mock('fs') | ||||||
|  | 
 | ||||||
|  | describe('models/doorlock', () => { | ||||||
|  |   const filePath = '/path/to/cards.json' | ||||||
|  |   let doorLock | ||||||
|  | 
 | ||||||
|  |   beforeEach(() => { | ||||||
|  |     doorLock = new DoorLock() | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|  |   describe('.findRFIDReaderDevice', () => { | ||||||
|  |     test('should find a device by its serial number', () => { | ||||||
|  |       const device = { serialNumber: RFID_READER_SERIAL_NUMBER } | ||||||
|  |       const devices = [device, { serialNumber: 'foo' }] | ||||||
|  |       const found = doorLock.findRFIDReaderDevice(devices) | ||||||
|  |       expect(device).toBe(found) | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|  |   describe('.validateCard', () => { | ||||||
|  |     test('should match existing card', () => { | ||||||
|  |       const expected = 5109933 | ||||||
|  |       const scanned = '3F004DF8AD27' | ||||||
|  |       const cards = [{ name: 'John', number: `0000${expected}}` }] | ||||||
|  |       jest | ||||||
|  |         .spyOn(doorLock, 'readCardsFromSDCard') | ||||||
|  |         .mockImplementationOnce(() => Promise.resolve(cards)) | ||||||
|  |       jest | ||||||
|  |         .spyOn(doorLock, 'openDoor') | ||||||
|  |         .mockImplementationOnce(() => Promise.resolve()) | ||||||
|  |       return doorLock | ||||||
|  |         .validateCard(scanned) | ||||||
|  |         .then(() => expect(doorLock.openDoor).toBeCalled()) | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|  |   describe('.openDoor', () => { | ||||||
|  |     test('Should turn on success LED', () => { | ||||||
|  |       jest.spyOn(doorLock, 'closeDoor') | ||||||
|  |       return doorLock.openDoor().then(() => { | ||||||
|  |         expect(tessel.led[3].on).toBeCalled() | ||||||
|  |         expect(doorLock.closeDoor).toBeCalled() | ||||||
|  |       }) | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|  |   describe('.closeDoor', () => { | ||||||
|  |     test('should turn off success LED', () => { | ||||||
|  |       doorLock.closeDoor() | ||||||
|  |       expect(tessel.led[3].off).toBeCalled() | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|  |   describe('.readCardsFromSDCard', () => { | ||||||
|  |     test('should read and convert file data to JSON', () => { | ||||||
|  |       const cards = [{ name: 'John', number: '123' }] | ||||||
|  |       jest | ||||||
|  |         .spyOn(fs, 'readFile') | ||||||
|  |         .mockImplementationOnce((p, cb) => cb(null, JSON.stringify(cards))) | ||||||
|  |       return doorLock.readCardsFromSDCard(filePath).then(actual => { | ||||||
|  |         expect(actual).toEqual(cards) | ||||||
|  |       }) | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|  |   describe('.writeCardsToSDCard', () => { | ||||||
|  |     xtest('should write data to SD card', () => { | ||||||
|  |       const cards = [{ name: 'John', number: '123' }] | ||||||
|  |       jest.spyOn(fs, 'writeFile').mockImplementationOnce((path, text, cb) => { | ||||||
|  |         console.log('TEXT', text) | ||||||
|  |         console.log('PATH', path) | ||||||
|  |         expect(path).toBe(CARDS_PATH) | ||||||
|  |         expect(text).toBe(JSON.stringify(cards)) | ||||||
|  |         cb() | ||||||
|  |       }) | ||||||
|  |       return expect(doorLock.writeCardsToSDCard(cards)).resolves.toBe() | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | }) | ||||||
							
								
								
									
										44
									
								
								nexudus.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								nexudus.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | |||||||
|  | const axios = require('axios') | ||||||
|  | const { NEXUDUS_APPLICATION_KEY, NEXUDUS_SECRET_KEY } = require('./constants') | ||||||
|  | 
 | ||||||
|  | class Nexudus { | ||||||
|  |   constructor() { | ||||||
|  |     if (!NEXUDUS_APPLICATION_KEY) | ||||||
|  |       throw new Error('missing "Nexudus_CLIENT_ID" env variable!') | ||||||
|  |     if (!NEXUDUS_SECRET_KEY) | ||||||
|  |       throw new Error('missing "Nexudus_CLIENT_SECRET" env variable!') | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   cards() { | ||||||
|  |     return axios | ||||||
|  |       .get(NEXUDUS_, { | ||||||
|  |         auth: { | ||||||
|  |           password: NEXUDUS_SECRET_KEY, | ||||||
|  |           username: NEXUDUS_APPLICATION_KEY, | ||||||
|  |         }, | ||||||
|  |       }) | ||||||
|  |       .then( | ||||||
|  |         resp => console.log('RESP:', resp) | ||||||
|  |         // resp.data.map(card => ({
 | ||||||
|  |         //   name: card.membership.name,
 | ||||||
|  |         //   number: card.token,
 | ||||||
|  |         // }))
 | ||||||
|  |       ) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static authorize() { | ||||||
|  |     const qs = [ | ||||||
|  |       `scope=${Nexudus_SCOPE}`, | ||||||
|  |       `grant_type=password`, | ||||||
|  |       `username=${NEXUDUS_USER_EMAIL}`, | ||||||
|  |       `password=${NEXUDUS_USER_PASSWORD}`, | ||||||
|  |       `client_id=${NEXUDUS_CLIENT_ID}`, | ||||||
|  |       `client_secret=${NEXUDUS_CLIENT_SECRET}`, | ||||||
|  |     ].join('&') | ||||||
|  |     return axios | ||||||
|  |       .post(`https://www.Nexudus.me/oauth/access_token?${qs}`) | ||||||
|  |       .then(resp => new Nexudus(resp.data.access_token)) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports = Nexudus | ||||||
							
								
								
									
										13754
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										13754
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -12,14 +12,16 @@ | |||||||
|     "axios": "0.18.0", |     "axios": "0.18.0", | ||||||
|     "chalk": "2.3.1", |     "chalk": "2.3.1", | ||||||
|     "dotenv": "5.0.1", |     "dotenv": "5.0.1", | ||||||
|     "eslint": "4.17.0", |     "serialport": "6.2.0", | ||||||
|     "node-hid": "0.5.4", |  | ||||||
|     "tessel": "0.3.25" |     "tessel": "0.3.25" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "babel-eslint": "8.2.2", |     "babel-eslint": "8.2.2", | ||||||
|  |     "eslint": "4.19.1", | ||||||
|     "eslint-plugin-react": "7.7.0", |     "eslint-plugin-react": "7.7.0", | ||||||
|     "jest": "22.4.2" |     "jest": "22.4.2", | ||||||
|  |     "nodemon": "1.17.5", | ||||||
|  |     "t2-cli": "0.1.18" | ||||||
|   }, |   }, | ||||||
|   "optionalDependencies": { |   "optionalDependencies": { | ||||||
|     "fsevents": "*" |     "fsevents": "*" | ||||||
|  | |||||||
| @ -1,3 +0,0 @@ | |||||||
| const DoorLock = require('./models/doorlock') |  | ||||||
| 
 |  | ||||||
| DoorLock.initialize() |  | ||||||
| @ -1,26 +0,0 @@ | |||||||
| require('dotenv').config() |  | ||||||
| 
 |  | ||||||
| const path = require('path') |  | ||||||
| 
 |  | ||||||
| const ENV = process.env.NODE_ENV || 'development' |  | ||||||
| const USB_MOUNT_PATH = process.env.USB_MOUNT_PATH || '/mnt/sda1' |  | ||||||
| const CARDS_PATH = path.join( |  | ||||||
|   USB_MOUNT_PATH, |  | ||||||
|   process.env.CARDS_PATH || 'cards.json' |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| module.exports = { |  | ||||||
|   CARDS_PATH, |  | ||||||
|   COBOT_CARDS_API: |  | ||||||
|     process.env.COBOT_CARDS_API || |  | ||||||
|     'https://chimera.cobot.me/api/check_in_tokens', |  | ||||||
|   COBOT_CLIENT_ID: process.env.COBOT_CLIENT_ID, |  | ||||||
|   COBOT_CLIENT_SECRET: process.env.COBOT_CLIENT_SECRET, |  | ||||||
|   COBOT_SCOPE: process.env.COBOT_SCOPE || 'checkin_tokens', |  | ||||||
|   COBOT_USER_EMAIL: process.env.COBOT_USER_EMAIL, |  | ||||||
|   COBOT_USER_PASSWORD: process.env.COBOT_USER_PASSWORD, |  | ||||||
|   DOOR_OPEN_DELAY: ENV === 'test' ? 1 : 6000, |  | ||||||
|   ENV, |  | ||||||
|   RFID_PRODUCT_NAME: process.env.RFID_PRODUCT_NAME, |  | ||||||
|   USB_MOUNT_PATH, |  | ||||||
| } |  | ||||||
| @ -1,7 +0,0 @@ | |||||||
| class AccessLog { |  | ||||||
|   static log(member) { |  | ||||||
|     console.log('LOGGING', member) |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| module.exports = AccessLog |  | ||||||
| @ -1,25 +0,0 @@ | |||||||
| const axios = require('axios') |  | ||||||
| const { CARDS_PATH, COBOT_CARDS_API } = require('../constants') |  | ||||||
| const SDCard = require('./sd-card') |  | ||||||
| 
 |  | ||||||
| class Cards { |  | ||||||
|   static update() { |  | ||||||
|     return axios.get(COBOT_CARDS_API) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static read() { |  | ||||||
|     return SDCard.read(CARDS_PATH) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static write(cards) { |  | ||||||
|     return SDCard.write(CARDS_PATH, cards) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   // static validate(number) {
 |  | ||||||
|   //   return this.read().then(cards => {
 |  | ||||||
|   //     return cards.find(c => c.number === number)
 |  | ||||||
|   //   })
 |  | ||||||
|   // }
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| module.exports = Cards |  | ||||||
| @ -1,13 +0,0 @@ | |||||||
| const Cards = require('./cards') |  | ||||||
| 
 |  | ||||||
| describe('models/cards', () => { |  | ||||||
|   describe('.update', () => { |  | ||||||
|     test.skip('should fetch list of RFID cards', () => { |  | ||||||
|       return Cards.update().then(cards => expect(cards).toEqual()) |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   describe('.validate', () => { |  | ||||||
|     test.skip('should check card against list', () => {}) |  | ||||||
|   }) |  | ||||||
| }) |  | ||||||
| @ -1,11 +0,0 @@ | |||||||
| class Display { |  | ||||||
|   static successMessage(msg) { |  | ||||||
|     console.log('SUCCESS', msg) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static failureMessage(msg) { |  | ||||||
|     console.log('FAILURE', msg) |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| module.exports = Display |  | ||||||
| @ -1,27 +0,0 @@ | |||||||
| const { DOOR_OPEN_DELAY } = require('../constants') |  | ||||||
| const logger = require('../utils/logger') |  | ||||||
| const tessel = require('tessel') |  | ||||||
| 
 |  | ||||||
| class Door { |  | ||||||
|   static open() { |  | ||||||
|     logger.log('OPEN DOOR') |  | ||||||
| 
 |  | ||||||
|     // TODO: trigger door opening...
 |  | ||||||
|     tessel.led[3].on() |  | ||||||
| 
 |  | ||||||
|     return new Promise(resolve => { |  | ||||||
|       setTimeout(() => { |  | ||||||
|         // TODO: trigger door closing
 |  | ||||||
|         Door.close() |  | ||||||
|         resolve() |  | ||||||
|       }, DOOR_OPEN_DELAY) |  | ||||||
|     }) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static close() { |  | ||||||
|     logger.log('CLOSE DOOR') |  | ||||||
|     tessel.led[3].off() |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| module.exports = Door |  | ||||||
| @ -1,23 +0,0 @@ | |||||||
| const Door = require('./door') |  | ||||||
| const tessel = require('tessel') |  | ||||||
| 
 |  | ||||||
| jest.mock('tessel') |  | ||||||
| 
 |  | ||||||
| describe('models/door', () => { |  | ||||||
|   describe('.open', () => { |  | ||||||
|     test('opens door', () => { |  | ||||||
|       jest.spyOn(Door, 'close') |  | ||||||
|       return Door.open().then(() => { |  | ||||||
|         expect(tessel.led[3].on).toBeCalled() |  | ||||||
|         expect(Door.close).toBeCalled() |  | ||||||
|       }) |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   describe('.close', () => { |  | ||||||
|     test('closes door', () => { |  | ||||||
|       Door.close() |  | ||||||
|       expect(tessel.led[3].off).toBeCalled() |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| }) |  | ||||||
| @ -1,28 +0,0 @@ | |||||||
| const Cobot = require('./cobot') |  | ||||||
| const Cards = require('./cards') |  | ||||||
| const logger = require('../utils/logger') |  | ||||||
| 
 |  | ||||||
| class DoorLock { |  | ||||||
|   static initializeRFIDReader() { |  | ||||||
|     console.log('TODO: initialize RFID reader...') |  | ||||||
|     Cards.read().then(cards => logger.log('EXISTING:', cards.length, 'cards')) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static updateCards() { |  | ||||||
|     logger.log('UPDATING CARDS!') |  | ||||||
|     Cobot.authorize().then(cobot => { |  | ||||||
|       cobot.cards().then(cards => { |  | ||||||
|         logger.log('NEW:', cards.length, 'cards') |  | ||||||
|         Cards.write(cards) |  | ||||||
|       }) |  | ||||||
|     }) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static initialize() { |  | ||||||
|     logger.log('INITIALIZING DOORLOCK!') |  | ||||||
|     this.updateCards() |  | ||||||
|     this.initializeRFIDReader() |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| module.exports = DoorLock |  | ||||||
| @ -1,42 +0,0 @@ | |||||||
| const hid = require('node-hid') |  | ||||||
| const { RFID_PRODUCT_NAME } = require('../constants') |  | ||||||
| 
 |  | ||||||
| class RFIDReader { |  | ||||||
|   static devices() { |  | ||||||
|     return hid.devices() || [] |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static reader() { |  | ||||||
|     const device = this.devices().find(d => d.product === RFID_PRODUCT_NAME) |  | ||||||
| 
 |  | ||||||
|     if (!device) { |  | ||||||
|       throw new Error( |  | ||||||
|         `no RFID device found with the "RFID_PRODUCT_NAME" matching "${RFID_PRODUCT_NAME}"` |  | ||||||
|       ) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return new hid.HID(device.path) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static read() { |  | ||||||
|     const device = this.reader() |  | ||||||
|     device.read((err, data) => console.log(err, data)) |  | ||||||
|     return device |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // const RFIDReader = require('./models/rfid-reader')
 |  | ||||||
| 
 |  | ||||||
| // console.log(RFIDReader.devices())
 |  | ||||||
| // const reader = RFIDReader.reader()
 |  | ||||||
| 
 |  | ||||||
| // reader.on('data', data => console.log(data))
 |  | ||||||
| // reader.close()
 |  | ||||||
| 
 |  | ||||||
| // new Promise(resolve => {
 |  | ||||||
| //   setTimeout(() => resolve(), 10000)
 |  | ||||||
| // })
 |  | ||||||
| 
 |  | ||||||
| // console.log('READER:', reader)
 |  | ||||||
| 
 |  | ||||||
| module.exports = RFIDReader |  | ||||||
| @ -1,47 +0,0 @@ | |||||||
| const hid = require('node-hid') |  | ||||||
| const RFIDReader = require('./rfid-reader') |  | ||||||
| 
 |  | ||||||
| jest.mock('node-hid') |  | ||||||
| 
 |  | ||||||
| const rfidDevice = { |  | ||||||
|   interface: -1, |  | ||||||
|   manufacturer: 'Apple', |  | ||||||
|   path: |  | ||||||
|     'IOService:/IOResources/IOBluetoothHCIController/AppleBroadcomBluetoothHostController/IOBluetoothDevice/IOBluetoothL2CAPChannel/AppleHSBluetoothDevice/Keyboard / Boot@1/AppleHSBluetoothHIDDriver', |  | ||||||
|   product: 'KB800HM Kinesis Freestyle2 for Mac', |  | ||||||
|   productId: 615, |  | ||||||
|   release: 0, |  | ||||||
|   serialNumber: '04-69-f8-c6-d2-c2', |  | ||||||
|   usage: 6, |  | ||||||
|   usagePage: 1, |  | ||||||
|   vendorId: 76, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| describe('models/rfid-reader', () => { |  | ||||||
|   const devices = [rfidDevice] |  | ||||||
| 
 |  | ||||||
|   beforeAll(() => { |  | ||||||
|     hid.devices.mockReturnValue(devices) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   describe('.devices', () => { |  | ||||||
|     test('lists devices', () => { |  | ||||||
|       const actual = RFIDReader.devices() |  | ||||||
|       expect(actual).toEqual(devices) |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   describe('.reader', () => { |  | ||||||
|     test('should connect to the RFID reader and return it', () => { |  | ||||||
|       const actual = RFIDReader.reader() |  | ||||||
|       expect(actual).toBeInstanceOf(hid.HID) |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   describe('.read', () => { |  | ||||||
|     test('it should listen for keyboard input', () => { |  | ||||||
|       const device = RFIDReader.read() |  | ||||||
|       expect(device.read).toBeCalled() |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| }) |  | ||||||
| @ -1,27 +0,0 @@ | |||||||
| const fs = require('fs') |  | ||||||
| const logger = require('../utils/logger') |  | ||||||
| 
 |  | ||||||
| class SDCard { |  | ||||||
|   static read(filePath) { |  | ||||||
|     return new Promise((resolve, reject) => { |  | ||||||
|       fs.readFile(filePath, (err, data) => { |  | ||||||
|         if (err) return reject(err) |  | ||||||
|         // logger.log('READ:', data.toString())
 |  | ||||||
|         resolve(JSON.parse(data)) |  | ||||||
|       }) |  | ||||||
|     }) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static write(filePath, json) { |  | ||||||
|     const text = JSON.stringify(json) |  | ||||||
|     return new Promise((resolve, reject) => { |  | ||||||
|       fs.writeFile(filePath, text, err => { |  | ||||||
|         if (err) return reject(err) |  | ||||||
|         // logger.log('WROTE:', json)
 |  | ||||||
|         resolve() |  | ||||||
|       }) |  | ||||||
|     }) |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| module.exports = SDCard |  | ||||||
| @ -1,31 +0,0 @@ | |||||||
| const fs = require('fs') |  | ||||||
| const SDCard = require('./sd-card') |  | ||||||
| 
 |  | ||||||
| jest.mock('fs') |  | ||||||
| 
 |  | ||||||
| describe('models/sd-card', () => { |  | ||||||
|   const filePath = '/path/to/cards.json' |  | ||||||
|   const expected = [{ name: 'John', number: '123' }] |  | ||||||
| 
 |  | ||||||
|   describe('.read', () => { |  | ||||||
|     test('should read and convert file data to JSON', () => { |  | ||||||
|       jest |  | ||||||
|         .spyOn(fs, 'readFile') |  | ||||||
|         .mockImplementationOnce((p, cb) => cb(null, JSON.stringify(expected))) |  | ||||||
|       return SDCard.read(filePath).then(actual => { |  | ||||||
|         expect(actual).toEqual(expected) |  | ||||||
|       }) |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| 
 |  | ||||||
|   describe('.write', () => { |  | ||||||
|     test('should write data to SD card', () => { |  | ||||||
|       jest.spyOn(fs, 'writeFile').mockImplementationOnce((path, text, cb) => { |  | ||||||
|         expect(path).toBe(filePath) |  | ||||||
|         expect(text).toBe(JSON.stringify(expected)) |  | ||||||
|         cb() |  | ||||||
|       }) |  | ||||||
|       return expect(SDCard.write(filePath, expected)).resolves.toBe() |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| }) |  | ||||||
| @ -1,9 +0,0 @@ | |||||||
| const { ENV } = require('../constants') |  | ||||||
| 
 |  | ||||||
| class Logger { |  | ||||||
|   log() { |  | ||||||
|     ENV !== 'test' && console.log(...arguments) |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| module.exports = new Logger() |  | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dana Woodman
						Dana Woodman