mirror of
https://github.com/zyphlar/doorlock.git
synced 2024-04-03 21:36:03 +00:00
Add basic RFID reader scanning code.
Other cleanup and docs.
This commit is contained in:
parent
bf358756e1
commit
a66f602dc4
|
@ -7,5 +7,4 @@ COBOT_SCOPE='checkin_tokens'
|
||||||
COBOT_USER_EMAIL='...'
|
COBOT_USER_EMAIL='...'
|
||||||
COBOT_USER_PASSWORD='...'
|
COBOT_USER_PASSWORD='...'
|
||||||
COBOT_CARDS_API='https://chimera.cobot.me/api/check_in_tokens'
|
COBOT_CARDS_API='https://chimera.cobot.me/api/check_in_tokens'
|
||||||
DOOR_OPEN_DELAY=6000
|
RFID_PRODUCT_NAME='Name of USB device'
|
||||||
PORT=8080
|
|
||||||
|
|
11
.env.test
11
.env.test
|
@ -1,11 +0,0 @@
|
||||||
COBOT_CLIENT_ID='fake-id'
|
|
||||||
COBOT_CLIENT_SECRET='fake-secret'
|
|
||||||
COBOT_REDIRECT_URL='http://localhost:8080/success'
|
|
||||||
COBOT_AUTHORIZE_URL='https://www.cobot.me/oauth/authorize'
|
|
||||||
COBOT_TOKEN_URL='https://www.cobot.me/oauth/access_token'
|
|
||||||
COBOT_SCOPE='checkin_tokens'
|
|
||||||
COBOT_USER_EMAIL='info@chimeraarts.org'
|
|
||||||
COBOT_USER_PASSWORD='somepassword'
|
|
||||||
COBOT_CARDS_API='https://chimera.cobot.me/api/check_in_tokens'
|
|
||||||
DOOR_OPEN_DELAY=1
|
|
||||||
PORT=8080
|
|
9
__mocks__/node-hid.js
Normal file
9
__mocks__/node-hid.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
const HID = jest.fn()
|
||||||
|
HID.prototype.read = jest.fn()
|
||||||
|
|
||||||
|
const hid = {
|
||||||
|
devices: jest.fn(),
|
||||||
|
HID,
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = hid
|
6765
package-lock.json
generated
6765
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -3,7 +3,8 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"main": "src/boot.js",
|
"main": "src/boot.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "t2 run src/boot.js",
|
"deploy": "t2 run src/boot.js",
|
||||||
|
"start": "node src/boot.js",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"watch-test": "npm test -- --watch"
|
"watch-test": "npm test -- --watch"
|
||||||
},
|
},
|
||||||
|
@ -12,7 +13,7 @@
|
||||||
"chalk": "2.3.1",
|
"chalk": "2.3.1",
|
||||||
"dotenv": "5.0.1",
|
"dotenv": "5.0.1",
|
||||||
"eslint": "4.17.0",
|
"eslint": "4.17.0",
|
||||||
"node-hid": "0.7.2",
|
"node-hid": "0.5.4",
|
||||||
"tessel": "0.3.25"
|
"tessel": "0.3.25"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
34
readme.md
34
readme.md
|
@ -4,11 +4,29 @@
|
||||||
|
|
||||||
Powered by a [Tessel][tessel]
|
Powered by a [Tessel][tessel]
|
||||||
|
|
||||||
|
## Technical Overview
|
||||||
|
|
||||||
|
The RFID doorlock consists of a few components that allow us to have a offline capable, yet up-to-date list of member's RFID cards.
|
||||||
|
|
||||||
|
Currently, we are using Cobot to manage our membership as well as RFID card numbers (checkin tokens in Cobot parlance).
|
||||||
|
|
||||||
|
The doorlock consists of a Tessel microcontroller powered by node.js (JavaScript). The doorlock has an attached USB adapter with an SD card to store the member's cards (in `json` format). A USB powered RFID card reader (125mhz) is plugged into the other Tessel USB port. This reader behaves like a keyboard; when a card is scanned it sends a string of card numbers as keys with a newline character.
|
||||||
|
|
||||||
|
The application listens for card scan events and when one if found, it looks the card number up in a local database (the above mentioned `json` file). If it finds a card, it opens the door, if not it shows an error message.
|
||||||
|
|
||||||
|
To open the door, we use a relay (or optionally a TIP120 transistor) which powers a 12v door latch. If no power is sent to the door latch, it remains locked. When it gets a 12v current it opens and allows the member entry.
|
||||||
|
|
||||||
|
Whether a success or failure, we should details on an attached OLED display as well as when the card list updates or other unexpected issues.
|
||||||
|
|
||||||
|
When the device first turns on it connects to WiFi and then fetches all the member RFID cards from the Cobot checkin token API and then updates the `json` card file. It completely overwrites the existing list of cards. If there is a failure getting the cards, we keep the original card list as a fallback.
|
||||||
|
|
||||||
|
Eventually, we can remove Cobot and swap it with our own service if we desire.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
Follow the [start guide][start] on [Tessel.io][tessel].
|
First, follow the [start guide][start] on [Tessel.io][tessel].
|
||||||
|
|
||||||
Install the correct version of node using nvm:
|
Next, install the correct version of node using nvm:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nvm install
|
nvm install
|
||||||
|
@ -27,9 +45,11 @@ Plug your Tessel in.
|
||||||
Now you can deploy to your connected Tessel device:
|
Now you can deploy to your connected Tessel device:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm start
|
npm run deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For local development, you can run `npm start` which will run the application locally as well as run the test suite, watching for changes.
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
We use [Jest][jest] to do testing of the core code in the library. Make sure to write tests for new code or update tests on existing code as needed. Test files are next to their source file named with a `.test.js` extension.
|
We use [Jest][jest] to do testing of the core code in the library. Make sure to write tests for new code or update tests on existing code as needed. Test files are next to their source file named with a `.test.js` extension.
|
||||||
|
@ -47,9 +67,6 @@ Read cards from Cobot:
|
||||||
### Useful Commands
|
### Useful Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Connect to WiFi
|
|
||||||
t2 wifi -n network-name -p "some password"
|
|
||||||
|
|
||||||
# List devices
|
# List devices
|
||||||
t2 list
|
t2 list
|
||||||
|
|
||||||
|
@ -62,6 +79,9 @@ t2 push index.js
|
||||||
# Clear code
|
# Clear code
|
||||||
t2 erase
|
t2 erase
|
||||||
|
|
||||||
|
# Connect to WiFi
|
||||||
|
t2 wifi -n network-name -p "some password"
|
||||||
|
|
||||||
# Create access point and server
|
# Create access point and server
|
||||||
t2 ap -n doorlock
|
t2 ap -n doorlock
|
||||||
```
|
```
|
||||||
|
@ -72,7 +92,7 @@ To find the IP address of your Tessel, download the iOS app Fing and look for a
|
||||||
|
|
||||||
### USB Storage
|
### USB Storage
|
||||||
|
|
||||||
* Make sure to format USB to be FAT32!
|
* Make sure to format micro SD to be FAT32!
|
||||||
|
|
||||||
[jest]: https://facebook.github.io/jest
|
[jest]: https://facebook.github.io/jest
|
||||||
[start]: http://tessel.github.io/t2-start
|
[start]: http://tessel.github.io/t2-start
|
||||||
|
|
|
@ -9,6 +9,7 @@ module.exports = {
|
||||||
COBOT_SCOPE: process.env.COBOT_SCOPE,
|
COBOT_SCOPE: process.env.COBOT_SCOPE,
|
||||||
COBOT_USER_EMAIL: process.env.COBOT_USER_EMAIL,
|
COBOT_USER_EMAIL: process.env.COBOT_USER_EMAIL,
|
||||||
COBOT_USER_PASSWORD: process.env.COBOT_USER_PASSWORD,
|
COBOT_USER_PASSWORD: process.env.COBOT_USER_PASSWORD,
|
||||||
DOOR_OPEN_DELAY: process.env.DOOR_OPEN_DELAY,
|
DOOR_OPEN_DELAY: ENV === 'test' ? 1 : 6000,
|
||||||
ENV,
|
ENV,
|
||||||
|
RFID_PRODUCT_NAME: process.env.RFID_PRODUCT_NAME,
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,13 +40,6 @@ class Cobot {
|
||||||
throw new Error('missing "COBOT_CLIENT_ID" env variable!')
|
throw new Error('missing "COBOT_CLIENT_ID" env variable!')
|
||||||
if (!COBOT_CLIENT_SECRET)
|
if (!COBOT_CLIENT_SECRET)
|
||||||
throw new Error('missing "COBOT_CLIENT_SECRET" env variable!')
|
throw new Error('missing "COBOT_CLIENT_SECRET" env variable!')
|
||||||
console.log(
|
|
||||||
COBOT_SCOPE,
|
|
||||||
COBOT_USER_EMAIL,
|
|
||||||
COBOT_USER_PASSWORD,
|
|
||||||
COBOT_CLIENT_ID,
|
|
||||||
COBOT_CLIENT_SECRET
|
|
||||||
)
|
|
||||||
const qs = [
|
const qs = [
|
||||||
`scope=${COBOT_SCOPE}`,
|
`scope=${COBOT_SCOPE}`,
|
||||||
`grant_type=password`,
|
`grant_type=password`,
|
||||||
|
|
28
src/models/rfid-reader.js
Normal file
28
src/models/rfid-reader.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = RFIDReader
|
48
src/models/rfid-reader.test.js
Normal file
48
src/models/rfid-reader.test.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
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()
|
||||||
|
console.log(device)
|
||||||
|
expect(device.read).toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user