Add basic RFID reader scanning code.

Other cleanup and docs.
This commit is contained in:
Dana Woodman 2018-03-04 14:35:02 -08:00
parent bf358756e1
commit a66f602dc4
11 changed files with 4683 additions and 2231 deletions

View File

@ -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

View File

@ -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

1
.nvmrc
View File

@ -1 +1,2 @@
# Must match the supported Tessel node version!
v6.10.3 v6.10.3

9
__mocks__/node-hid.js Normal file
View 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

File diff suppressed because it is too large Load Diff

View File

@ -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": {

View File

@ -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

View File

@ -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,
} }

View File

@ -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
View 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

View 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()
})
})
})