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
|
13758
package-lock.json
generated
13758
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…
Reference in New Issue
Block a user