├── entry-gate ├── README.md ├── audio │ ├── access-denied.mp3 │ └── access-granted.mp3 ├── Pipfile ├── Pipfile.lock ├── LICENSE ├── SimpleMFRC522.py ├── entry-gate.py └── MFRC522.py ├── sales-terminal ├── README.md ├── audio │ ├── select-a-pass.mp3 │ ├── mismatch-cancel.mp3 │ ├── tap-card-to-confirm.mp3 │ ├── card-already-has-pass.mp3 │ ├── selected-ten-trip-pass.mp3 │ ├── selected-two-hour-pass.mp3 │ ├── thank-you-ten-trip-pass.mp3 │ ├── thank-you-two-hour-pass.mp3 │ ├── selected-single-trip-pass.mp3 │ └── thank-you-single-trip-pass.mp3 ├── Pipfile ├── Pipfile.lock ├── LICENSE ├── SimpleMFRC522.py ├── sales-terminal.py └── MFRC522.py ├── system-monitor ├── README.md ├── package.json ├── LICENSE ├── server.js └── package-lock.json ├── .gitignore ├── images ├── architecture.png └── videothumb.png ├── LICENSE └── README.md /entry-gate/README.md: -------------------------------------------------------------------------------- 1 | TODO 2 | -------------------------------------------------------------------------------- /sales-terminal/README.md: -------------------------------------------------------------------------------- 1 | TODO 2 | -------------------------------------------------------------------------------- /system-monitor/README.md: -------------------------------------------------------------------------------- 1 | TODO... 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.tmp 3 | *.bak 4 | *.swp 5 | node_modules/ 6 | .vscode/ 7 | -------------------------------------------------------------------------------- /images/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonprickett/transit-pass-demo/HEAD/images/architecture.png -------------------------------------------------------------------------------- /images/videothumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonprickett/transit-pass-demo/HEAD/images/videothumb.png -------------------------------------------------------------------------------- /entry-gate/audio/access-denied.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonprickett/transit-pass-demo/HEAD/entry-gate/audio/access-denied.mp3 -------------------------------------------------------------------------------- /entry-gate/audio/access-granted.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonprickett/transit-pass-demo/HEAD/entry-gate/audio/access-granted.mp3 -------------------------------------------------------------------------------- /sales-terminal/audio/select-a-pass.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonprickett/transit-pass-demo/HEAD/sales-terminal/audio/select-a-pass.mp3 -------------------------------------------------------------------------------- /sales-terminal/audio/mismatch-cancel.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonprickett/transit-pass-demo/HEAD/sales-terminal/audio/mismatch-cancel.mp3 -------------------------------------------------------------------------------- /sales-terminal/audio/tap-card-to-confirm.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonprickett/transit-pass-demo/HEAD/sales-terminal/audio/tap-card-to-confirm.mp3 -------------------------------------------------------------------------------- /sales-terminal/audio/card-already-has-pass.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonprickett/transit-pass-demo/HEAD/sales-terminal/audio/card-already-has-pass.mp3 -------------------------------------------------------------------------------- /sales-terminal/audio/selected-ten-trip-pass.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonprickett/transit-pass-demo/HEAD/sales-terminal/audio/selected-ten-trip-pass.mp3 -------------------------------------------------------------------------------- /sales-terminal/audio/selected-two-hour-pass.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonprickett/transit-pass-demo/HEAD/sales-terminal/audio/selected-two-hour-pass.mp3 -------------------------------------------------------------------------------- /sales-terminal/audio/thank-you-ten-trip-pass.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonprickett/transit-pass-demo/HEAD/sales-terminal/audio/thank-you-ten-trip-pass.mp3 -------------------------------------------------------------------------------- /sales-terminal/audio/thank-you-two-hour-pass.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonprickett/transit-pass-demo/HEAD/sales-terminal/audio/thank-you-two-hour-pass.mp3 -------------------------------------------------------------------------------- /sales-terminal/audio/selected-single-trip-pass.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonprickett/transit-pass-demo/HEAD/sales-terminal/audio/selected-single-trip-pass.mp3 -------------------------------------------------------------------------------- /sales-terminal/audio/thank-you-single-trip-pass.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonprickett/transit-pass-demo/HEAD/sales-terminal/audio/thank-you-single-trip-pass.mp3 -------------------------------------------------------------------------------- /entry-gate/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | redis = "*" 8 | 9 | [dev-packages] 10 | 11 | [requires] 12 | python_version = "3.7" 13 | -------------------------------------------------------------------------------- /sales-terminal/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | redis = "*" 8 | 9 | [dev-packages] 10 | 11 | [requires] 12 | python_version = "3.7" 13 | -------------------------------------------------------------------------------- /system-monitor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "system-monitor", 3 | "version": "0.0.1", 4 | "description": "System Monitoring interface for Transit Pass Demo project.", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/simonprickett/transit-pass-demo.git" 12 | }, 13 | "author": "Simon Prickett", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/simonprickett/transit-pass-demo/issues" 17 | }, 18 | "homepage": "https://github.com/simonprickett/transit-pass-demo#readme", 19 | "dependencies": { 20 | "chalk": "^2.4.1", 21 | "node-redis-pubsub": "^3.0.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /entry-gate/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "c47d5226bb844598320fc1b1eb8e2cee1019a0ab4d64914a65492e2e65254c98" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "redis": { 20 | "hashes": [ 21 | "sha256:8a1900a9f2a0a44ecf6e8b5eb3e967a9909dfed219ad66df094f27f7d6f330fb", 22 | "sha256:a22ca993cea2962dbb588f9f30d0015ac4afcc45bee27d3978c0dbe9e97c6c0f" 23 | ], 24 | "index": "pypi", 25 | "version": "==2.10.6" 26 | } 27 | }, 28 | "develop": {} 29 | } 30 | -------------------------------------------------------------------------------- /sales-terminal/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "c47d5226bb844598320fc1b1eb8e2cee1019a0ab4d64914a65492e2e65254c98" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "redis": { 20 | "hashes": [ 21 | "sha256:8a1900a9f2a0a44ecf6e8b5eb3e967a9909dfed219ad66df094f27f7d6f330fb", 22 | "sha256:a22ca993cea2962dbb588f9f30d0015ac4afcc45bee27d3978c0dbe9e97c6c0f" 23 | ], 24 | "index": "pypi", 25 | "version": "==2.10.6" 26 | } 27 | }, 28 | "develop": {} 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Simon Prickett 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 | -------------------------------------------------------------------------------- /entry-gate/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Simon Prickett 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 | -------------------------------------------------------------------------------- /sales-terminal/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Simon Prickett 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 | -------------------------------------------------------------------------------- /system-monitor/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Simon Prickett 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 | -------------------------------------------------------------------------------- /system-monitor/server.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | const nrp = require('node-redis-pubsub') 3 | 4 | const r = new nrp({ 5 | host: process.env.TRANSIT_PASS_DEMO_REDIS_HOST, 6 | port: process.env.TRANSIT_PASS_DEMO_REDIS_PORT, 7 | auth: process.env.TRANSIT_PASS_DEMO_REDIS_PASSWORD 8 | }) 9 | 10 | const PASS_TYPE_SINGLE_USE = 'SINGLE_USE' 11 | const PASS_TYPE_TWO_HOUR = 'TWO_HOUR' 12 | const PASS_TYPE_TEN_TRIP = 'TEN_TRIP' 13 | 14 | const decodePassType = (passType) => { 15 | switch (passType) { 16 | case PASS_TYPE_SINGLE_USE: 17 | return 'single trip' 18 | case PASS_TYPE_TWO_HOUR: 19 | return 'two hour' 20 | case PASS_TYPE_TEN_TRIP: 21 | return 'ten trip' 22 | } 23 | } 24 | 25 | r.on('pass-issued:*', (msg) => { 26 | console.log(chalk.yellow(`Card ${msg.cardSerialNumber} was issued a ${decodePassType(msg.pass.passType)} pass.`)) 27 | }) 28 | 29 | r.on(`pass-activated:${PASS_TYPE_TWO_HOUR}`, (msg) => { 30 | console.log(chalk.magenta(`Card ${msg.cardSerialNumber} two hour pass activated.`)) 31 | }) 32 | 33 | r.on('pass-used:*', (msg) => { 34 | // some type of pass used 35 | console.log(chalk.blue(`Card ${msg.cardSerialNumber} started a journey.`)) 36 | }) 37 | 38 | r.on(`pass-used:${PASS_TYPE_TEN_TRIP}`, (msg) => { 39 | console.log(chalk.yellow(`Card ${msg.cardSerialNumber} has ${msg.remainingTrips} of 10 trips remaining.`)) 40 | }) 41 | 42 | r.on('pass-denied', (msg) => { 43 | console.log(chalk.red(`Card ${msg.cardSerialNumber} tried to start a journey without a valid pass!`)) 44 | }) -------------------------------------------------------------------------------- /entry-gate/SimpleMFRC522.py: -------------------------------------------------------------------------------- 1 | import MFRC522 2 | import RPi.GPIO as GPIO 3 | 4 | class SimpleMFRC522: 5 | 6 | READER = None; 7 | 8 | KEY = [0xFF,0xFF,0xFF,0xFF,0xFF,0xFF] 9 | BLOCK_ADDRS = [8, 9, 10] 10 | 11 | def __init__(self): 12 | self.READER = MFRC522.MFRC522() 13 | 14 | def read(self): 15 | id, text = self.read_no_block() 16 | while not id: 17 | id, text = self.read_no_block() 18 | return id, text 19 | 20 | def read_id(self): 21 | id, text = self.read_no_block() 22 | while not id: 23 | id, text = self.read_no_block() 24 | return id 25 | 26 | def read_id_no_block(self): 27 | id, text = self.read_no_block() 28 | return id 29 | 30 | def read_no_block(self): 31 | (status, TagType) = self.READER.MFRC522_Request(self.READER.PICC_REQIDL) 32 | if status != self.READER.MI_OK: 33 | return None, None 34 | (status, uid) = self.READER.MFRC522_Anticoll() 35 | if status != self.READER.MI_OK: 36 | return None, None 37 | id = self.uid_to_num(uid) 38 | self.READER.MFRC522_SelectTag(uid) 39 | status = self.READER.MFRC522_Auth(self.READER.PICC_AUTHENT1A, 11, self.KEY, uid) 40 | data = [] 41 | text_read = '' 42 | if status == self.READER.MI_OK: 43 | for block_num in self.BLOCK_ADDRS: 44 | block = self.READER.MFRC522_Read(block_num) 45 | if block: 46 | data += block 47 | if data: 48 | text_read = ''.join(chr(i) for i in data) 49 | self.READER.MFRC522_StopCrypto1() 50 | return id, text_read 51 | 52 | 53 | 54 | def write(self, text): 55 | id, text_in = self.write_no_block(text) 56 | while not id: 57 | id, text_in = self.write_no_block(text) 58 | return id, text_in 59 | 60 | 61 | def write_no_block(self, text): 62 | (status, TagType) = self.READER.MFRC522_Request(self.READER.PICC_REQIDL) 63 | if status != self.READER.MI_OK: 64 | return None, None 65 | (status, uid) = self.READER.MFRC522_Anticoll() 66 | if status != self.READER.MI_OK: 67 | return None, None 68 | id = self.uid_to_num(uid) 69 | self.READER.MFRC522_SelectTag(uid) 70 | status = self.READER.MFRC522_Auth(self.READER.PICC_AUTHENT1A, 11, self.KEY, uid) 71 | self.READER.MFRC522_Read(11) 72 | if status == self.READER.MI_OK: 73 | data = bytearray() 74 | data.extend(bytearray(text.ljust(len(self.BLOCK_ADDRS) * 16).encode('ascii'))) 75 | i = 0 76 | for block_num in self.BLOCK_ADDRS: 77 | self.READER.MFRC522_Write(block_num, data[(i*16):(i+1)*16]) 78 | i += 1 79 | self.READER.MFRC522_StopCrypto1() 80 | return id, text[0:(len(self.BLOCK_ADDRS) * 16)] 81 | 82 | def uid_to_num(self, uid): 83 | n = 0 84 | for i in range(0, 5): 85 | n = n * 256 + uid[i] 86 | return n 87 | -------------------------------------------------------------------------------- /sales-terminal/SimpleMFRC522.py: -------------------------------------------------------------------------------- 1 | import MFRC522 2 | import RPi.GPIO as GPIO 3 | 4 | class SimpleMFRC522: 5 | 6 | READER = None; 7 | 8 | KEY = [0xFF,0xFF,0xFF,0xFF,0xFF,0xFF] 9 | BLOCK_ADDRS = [8, 9, 10] 10 | 11 | def __init__(self): 12 | self.READER = MFRC522.MFRC522() 13 | 14 | def read(self): 15 | id, text = self.read_no_block() 16 | while not id: 17 | id, text = self.read_no_block() 18 | return id, text 19 | 20 | def read_id(self): 21 | id, text = self.read_no_block() 22 | while not id: 23 | id, text = self.read_no_block() 24 | return id 25 | 26 | def read_id_no_block(self): 27 | id, text = self.read_no_block() 28 | return id 29 | 30 | def read_no_block(self): 31 | (status, TagType) = self.READER.MFRC522_Request(self.READER.PICC_REQIDL) 32 | if status != self.READER.MI_OK: 33 | return None, None 34 | (status, uid) = self.READER.MFRC522_Anticoll() 35 | if status != self.READER.MI_OK: 36 | return None, None 37 | id = self.uid_to_num(uid) 38 | self.READER.MFRC522_SelectTag(uid) 39 | status = self.READER.MFRC522_Auth(self.READER.PICC_AUTHENT1A, 11, self.KEY, uid) 40 | data = [] 41 | text_read = '' 42 | if status == self.READER.MI_OK: 43 | for block_num in self.BLOCK_ADDRS: 44 | block = self.READER.MFRC522_Read(block_num) 45 | if block: 46 | data += block 47 | if data: 48 | text_read = ''.join(chr(i) for i in data) 49 | self.READER.MFRC522_StopCrypto1() 50 | return id, text_read 51 | 52 | 53 | 54 | def write(self, text): 55 | id, text_in = self.write_no_block(text) 56 | while not id: 57 | id, text_in = self.write_no_block(text) 58 | return id, text_in 59 | 60 | 61 | def write_no_block(self, text): 62 | (status, TagType) = self.READER.MFRC522_Request(self.READER.PICC_REQIDL) 63 | if status != self.READER.MI_OK: 64 | return None, None 65 | (status, uid) = self.READER.MFRC522_Anticoll() 66 | if status != self.READER.MI_OK: 67 | return None, None 68 | id = self.uid_to_num(uid) 69 | self.READER.MFRC522_SelectTag(uid) 70 | status = self.READER.MFRC522_Auth(self.READER.PICC_AUTHENT1A, 11, self.KEY, uid) 71 | self.READER.MFRC522_Read(11) 72 | if status == self.READER.MI_OK: 73 | data = bytearray() 74 | data.extend(bytearray(text.ljust(len(self.BLOCK_ADDRS) * 16).encode('ascii'))) 75 | i = 0 76 | for block_num in self.BLOCK_ADDRS: 77 | self.READER.MFRC522_Write(block_num, data[(i*16):(i+1)*16]) 78 | i += 1 79 | self.READER.MFRC522_StopCrypto1() 80 | return id, text[0:(len(self.BLOCK_ADDRS) * 16)] 81 | 82 | def uid_to_num(self, uid): 83 | n = 0 84 | for i in range(0, 5): 85 | n = n * 256 + uid[i] 86 | return n 87 | -------------------------------------------------------------------------------- /system-monitor/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "system-monitor", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ansi-styles": { 8 | "version": "3.2.1", 9 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 10 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 11 | "requires": { 12 | "color-convert": "^1.9.0" 13 | } 14 | }, 15 | "chalk": { 16 | "version": "2.4.1", 17 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", 18 | "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", 19 | "requires": { 20 | "ansi-styles": "^3.2.1", 21 | "escape-string-regexp": "^1.0.5", 22 | "supports-color": "^5.3.0" 23 | } 24 | }, 25 | "color-convert": { 26 | "version": "1.9.3", 27 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 28 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 29 | "requires": { 30 | "color-name": "1.1.3" 31 | } 32 | }, 33 | "color-name": { 34 | "version": "1.1.3", 35 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 36 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 37 | }, 38 | "double-ended-queue": { 39 | "version": "2.1.0-0", 40 | "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", 41 | "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" 42 | }, 43 | "escape-string-regexp": { 44 | "version": "1.0.5", 45 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 46 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 47 | }, 48 | "has-flag": { 49 | "version": "3.0.0", 50 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 51 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 52 | }, 53 | "node-redis-pubsub": { 54 | "version": "3.0.0", 55 | "resolved": "https://registry.npmjs.org/node-redis-pubsub/-/node-redis-pubsub-3.0.0.tgz", 56 | "integrity": "sha512-T5ef9LJrj9CPaobVdJ0IC+iqQMuzYKLxzLv40qkHdPtq7bL85SgpOrlHAwwO7kV3fKboRYID178LzlF936Xe9Q==", 57 | "requires": { 58 | "redis": "^2.7.1" 59 | } 60 | }, 61 | "redis": { 62 | "version": "2.8.0", 63 | "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", 64 | "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", 65 | "requires": { 66 | "double-ended-queue": "^2.1.0-0", 67 | "redis-commands": "^1.2.0", 68 | "redis-parser": "^2.6.0" 69 | } 70 | }, 71 | "redis-commands": { 72 | "version": "1.3.5", 73 | "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.5.tgz", 74 | "integrity": "sha512-foGF8u6MXGFF++1TZVC6icGXuMYPftKXt1FBT2vrfU9ZATNtZJ8duRC5d1lEfE8hyVe3jhelHGB91oB7I6qLsA==" 75 | }, 76 | "redis-parser": { 77 | "version": "2.6.0", 78 | "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", 79 | "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=" 80 | }, 81 | "supports-color": { 82 | "version": "5.5.0", 83 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 84 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 85 | "requires": { 86 | "has-flag": "^3.0.0" 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Transit Pass Demo 2 | 3 | ## Introduction 4 | 5 | A Transit pass demo system using Redis, Raspberry-Pi, Low Voltage Labs Traffic Light LEDs, RC522 RFID tags and associated reader hardware. The code is a mixture of Python 3 and Node.js. 6 | 7 | Click the image to view a complete video demonstration: 8 | 9 | [![video](images/videothumb.png)](https://www.youtube.com/watch?v=Q_lmcA1x_Vg) 10 | 11 | I also wrote an [article on Medium](https://medium.com/@simon_prickett/building-a-smart-card-transit-ticketing-system-with-redis-and-raspberry-pi-820473c0b0c) describing this project. 12 | 13 | ## Types of Transit Passes 14 | 15 | Imagine a transit system where users each have a smart card. These cards each have a unique serial number. In order to gain entry to the transit system a user's card must have a valid transit pass associated with it. Exits from the system are not tracked. In this example, the following types of pass exist: 16 | 17 | * **Single Trip Pass:** This can be used for one entry / journey only. It expires immediately after first use. 18 | * **Ten Trip Pass:** This can be used for ten entries and doesn't expire until all ten have been used. 19 | * **Two Hour Pass:** This can be used for an unlimited number of entries, and expires two hours after the first time it is used. 20 | 21 | ## Components and Architecture 22 | 23 | ![architecture](images/architecture.png) 24 | 25 | There are four main components to the system: 26 | 27 | ### Sales Terminal 28 | 29 | Built with: 30 | 31 | * Python 3 32 | * Raspberry Pi 33 | * Three Arcade Buttons 34 | * RFC522 Card Reader 35 | 36 | The Sales Terminal allows mass transit riders to tap their transit card on the reader and select which of the available transit pass types they would like to add to their card. They then tap the same card a second time to conclude the transaction. The sales terminal uses spoken prompts to instruct the user, and provides different colored buttons that the user can press to select the type of pass that they wish to add. The Sales Terminal will reject attempts to add a pass to a card that is already associated with a valid pass. The Sales Terminal reports each successful issuing of a pass to the System Monitor component. 37 | 38 | ### Entry Gate 39 | 40 | Built with: 41 | 42 | * Python 3 43 | * Raspberry Pi 44 | * Low Voltage Labs Traffic Light LEDs 45 | * RFC522 Card Reader 46 | 47 | The Entry Gate waits for a user to tap their card to the reader, indicating that they wish to enter the transit system and start a journey. If the user's card is associated with a valid pass, the Entry Gate turns the entry light green and reports a successful entry to the System Monitor component. For the first use of a two hour pass, the Entry Gate sets the pass expiry in the database to be 2 hours. For each use of a ten use pass, the Entry Gate reduces the number of remaining trips available on the pass by one. For a single use pass, the Entry Gate deletes the pass fom the database. If a user presents a card that does not have a valid pass associated with it, the Entry Gate sounds an alarm and flashes the entry light red. All attempts to enter the system are reported to the System Monitor component. 48 | 49 | ### System Monitor 50 | 51 | Built with: 52 | 53 | * Node.js 54 | 55 | The System Monitor component receives messages from the Entry Gate and Sales Terminal components, displaying them in a log like format. 56 | 57 | ### Database & Pub/Sub Broker 58 | 59 | Built with: 60 | 61 | * Redis Labs Redis in the cloud 62 | 63 | The Redis database is used to store card and pass details. It manages the expiry time on the two hour pass and is a central data repository that the Sales Terminal and Entry Gate components read and write from. The system also uses the Redis Pub/Sub messaging system to pass messages from the Sales Terminal and Entry Gate components which act as publishers to the System Monitor component which is a subscriber. 64 | -------------------------------------------------------------------------------- /sales-terminal/sales-terminal.py: -------------------------------------------------------------------------------- 1 | import json 2 | import platform 3 | import os 4 | import redis 5 | import RPi.GPIO as GPIO 6 | import SimpleMFRC522 7 | 8 | PASS_TYPE_SINGLE_USE = 'SINGLE_USE' 9 | PASS_TYPE_TWO_HOUR = 'TWO_HOUR' 10 | PASS_TYPE_TEN_TRIP = 'TEN_TRIP' 11 | 12 | GPIO_ONE_TRIP=19 13 | GPIO_TEN_TRIP=13 14 | GPIO_TWO_HOUR=26 15 | 16 | # Connect to Redis Instance 17 | r = redis.Redis( 18 | host = os.environ['TRANSIT_PASS_DEMO_REDIS_HOST'], 19 | port = os.environ['TRANSIT_PASS_DEMO_REDIS_PORT'], 20 | password = os.environ['TRANSIT_PASS_DEMO_REDIS_PASSWORD'], 21 | decode_responses = True 22 | ) 23 | 24 | # Configure GPIO for buttons 25 | GPIO.setmode(GPIO.BCM) 26 | GPIO.setwarnings(False) 27 | GPIO.setup(GPIO_ONE_TRIP, GPIO.IN, pull_up_down=GPIO.PUD_UP) 28 | GPIO.setup(GPIO_TEN_TRIP, GPIO.IN, pull_up_down=GPIO.PUD_UP) 29 | GPIO.setup(GPIO_TWO_HOUR, GPIO.IN, pull_up_down=GPIO.PUD_UP) 30 | 31 | # Set up card reader 32 | cardReader = SimpleMFRC522.SimpleMFRC522() 33 | 34 | def readFromCardReader(): 35 | print('Waiting for a card to be presented.') 36 | id, text = cardReader.read() 37 | 38 | cardSerialNumber = str(id) 39 | print('Card ' + cardSerialNumber + ' presented.') 40 | return cardSerialNumber 41 | 42 | def waitForButton(): 43 | oneTrip = True 44 | tenTrip = True 45 | twoHour = True 46 | 47 | while (oneTrip and tenTrip and twoHour): 48 | oneTrip = GPIO.input(GPIO_ONE_TRIP) 49 | tenTrip = GPIO.input(GPIO_TEN_TRIP) 50 | twoHour = GPIO.input(GPIO_TWO_HOUR) 51 | 52 | if (oneTrip == False): 53 | print('One trip button pressed.') 54 | return GPIO_ONE_TRIP 55 | elif (tenTrip == False): 56 | print('Ten trip button pressed.') 57 | return GPIO_TEN_TRIP 58 | elif (twoHour == False): 59 | print('Two hour button pressed.') 60 | return GPIO_TWO_HOUR 61 | 62 | def playAudio(audioFileName): 63 | if (platform.system() == 'Darwin'): 64 | # MacOS testing 65 | os.system('afplay audio/' + audioFileName + '.mp3') 66 | else: 67 | # Assume Linux 68 | os.system('mpg123 -q audio/' + audioFileName + '.mp3') 69 | 70 | def generatePass(passType): 71 | if (passType == PASS_TYPE_SINGLE_USE): 72 | # Use once, never expires. 73 | return { 74 | 'passType': PASS_TYPE_SINGLE_USE 75 | } 76 | elif (passType == PASS_TYPE_TWO_HOUR): 77 | # Use an unlimited amount of times within two hours of first use. 78 | return { 79 | 'passType': PASS_TYPE_TWO_HOUR 80 | } 81 | else: 82 | # Use ten times, never expires. 83 | return { 84 | 'passType': PASS_TYPE_TEN_TRIP, 85 | 'tripsRemaining': 10 86 | } 87 | 88 | def waitForCard(confirmingCard = False): 89 | if (confirmingCard == True): 90 | playAudio('tap-card-to-confirm') 91 | 92 | return readFromCardReader() 93 | 94 | def hasExistingPass(cardSerialNumber): 95 | return r.exists(cardSerialNumber) 96 | 97 | def waitForPassSelection(): 98 | print('Waiting for user to press a button.') 99 | playAudio('select-a-pass') 100 | passType = waitForButton() 101 | 102 | if (passType == GPIO_ONE_TRIP): 103 | return PASS_TYPE_SINGLE_USE 104 | elif (passType == GPIO_TWO_HOUR): 105 | return PASS_TYPE_TWO_HOUR 106 | elif (passType == GPIO_TEN_TRIP): 107 | return PASS_TYPE_TEN_TRIP 108 | 109 | def addPassToCard(cardSerialNumber, newPass): 110 | # Store in Redis. 111 | r.hmset(cardSerialNumber, newPass) 112 | 113 | passType = newPass.get('passType') 114 | 115 | if (passType == PASS_TYPE_SINGLE_USE): 116 | playAudio('thank-you-single-trip-pass') 117 | print('Thanks for buying a single trip pass.') 118 | elif (passType == PASS_TYPE_TWO_HOUR): 119 | playAudio('thank-you-two-hour-pass') 120 | print('Thanks for buying a two hour pass.') 121 | else: 122 | playAudio('thank-you-ten-trip-pass') 123 | print('Thanks for buying a ten trip pass.') 124 | 125 | # Publish a message saying that a pass was issued. 126 | msgPayload = { 127 | 'cardSerialNumber': cardSerialNumber, 128 | 'pass': newPass 129 | } 130 | 131 | r.publish('pass-issued:' + passType, json.dumps(msgPayload, separators=(',', ':'))) 132 | 133 | def cardHasPassError(): 134 | playAudio('card-already-has-pass') 135 | print('This card already has a valid pass associated with it.') 136 | 137 | def cardMismatch(): 138 | playAudio('mismatch-cancel') 139 | print('You presented a different card, transaction canceled.') 140 | 141 | while(True): 142 | # Wait for a card to be presented 143 | # Get the serial number from the card 144 | cardSerialNumber = waitForCard() 145 | 146 | # Check if this card has a pass on it already... 147 | if (hasExistingPass(cardSerialNumber)): 148 | # Cannot add more than one pass to a card 149 | cardHasPassError() 150 | else: 151 | # Wait for a pass type to be chosen 152 | passType = waitForPassSelection() 153 | 154 | # Wait for the card to be presented again 155 | confirmCardSerialNumber = waitForCard(True) 156 | 157 | # Check it is the same card 158 | if (cardSerialNumber == confirmCardSerialNumber): 159 | # Associate the pass with the card in Redis 160 | addPassToCard(cardSerialNumber, generatePass(passType)) 161 | else: 162 | cardMismatch() 163 | -------------------------------------------------------------------------------- /entry-gate/entry-gate.py: -------------------------------------------------------------------------------- 1 | import json 2 | import platform 3 | import os 4 | import redis 5 | import RPi.GPIO as GPIO 6 | import SimpleMFRC522 7 | import time 8 | 9 | PASS_TYPE_SINGLE_USE = 'SINGLE_USE' 10 | PASS_TYPE_TWO_HOUR = 'TWO_HOUR' 11 | PASS_TYPE_TEN_TRIP = 'TEN_TRIP' 12 | 13 | # GPIO Pins for the traffic light LEDs 14 | RED = 19 15 | YELLOW=13 16 | GREEN = 26 17 | 18 | # Seconds in two hours used when activating two hour pass 19 | TWO_HOURS = 60 * 60 * 2 20 | 21 | # Connect to Redis Instance 22 | r = redis.Redis( 23 | host = os.environ['TRANSIT_PASS_DEMO_REDIS_HOST'], 24 | port = os.environ['TRANSIT_PASS_DEMO_REDIS_PORT'], 25 | password = os.environ['TRANSIT_PASS_DEMO_REDIS_PASSWORD'], 26 | decode_responses = True 27 | ) 28 | 29 | # Set up Card Reader 30 | cardReader = SimpleMFRC522.SimpleMFRC522() 31 | 32 | def playAudio(audioFileName): 33 | if (platform.system() == 'Darwin'): 34 | # MacOS testing 35 | os.system('afplay audio/' + audioFileName + '.mp3') 36 | else: 37 | # Assume Linux 38 | os.system('mpg123 -q audio/' + audioFileName + '.mp3') 39 | 40 | def flashRedLight(numTimes): 41 | for x in range(numTimes - 1): 42 | GPIO.output(RED, True) 43 | time.sleep(0.5) 44 | GPIO.output(RED, False) 45 | time.sleep(0.5) 46 | 47 | def sendMessage(topic, msgPayload): 48 | r.publish(topic, json.dumps(msgPayload, separators=(',', ':'))) 49 | 50 | def waitForCard(): 51 | print('Hold a travel card close to the reader.') 52 | id, text = cardReader.read() 53 | cardSerialNumber = str(id) 54 | print('Card ' + cardSerialNumber + ' detected.') 55 | return cardSerialNumber 56 | 57 | def getPassForCard(cardSerialNumber): 58 | return r.hgetall(cardSerialNumber) 59 | 60 | def updatePass(cardSerialNumber, cardPass): 61 | passType = cardPass.get('passType') 62 | 63 | if (passType == PASS_TYPE_SINGLE_USE): 64 | # This is the only use allowed for this pass so delete it from Redis 65 | r.delete(cardSerialNumber) 66 | 67 | # Report this pass was used 68 | print('Single use pass used and deleted.') 69 | 70 | sendMessage('pass-used:' + PASS_TYPE_SINGLE_USE, { 71 | 'cardSerialNumber': cardSerialNumber, 72 | 'pass': cardPass 73 | }) 74 | elif (passType == PASS_TYPE_TWO_HOUR): 75 | # Check if this is first use and start expiring the pass if so... 76 | # Nothing to do except report this pass was used for a journey 77 | 78 | passTtl = r.ttl(cardSerialNumber) 79 | 80 | if (not passTtl): 81 | print('Two hour pass first use, setting time to live.') 82 | 83 | r.expire(cardSerialNumber, TWO_HOURS) 84 | sendMessage('pass-activated:' + PASS_TYPE_TWO_HOUR, { 85 | 'cardSerialNumber': cardSerialNumber, 86 | 'pass': cardPass, 87 | 'remainingTime': TWO_HOURS 88 | }) 89 | else: 90 | print('Two hour pass has ' + str(round((passTtl / 60), 1)) + ' minutes remaining.') 91 | 92 | sendMessage('pass-used:' + PASS_TYPE_TWO_HOUR, { 93 | 'cardSerialNumber': cardSerialNumber, 94 | 'pass': cardPass, 95 | 'remainingTime': passTtl 96 | }) 97 | else: 98 | # Ten trip pass, delete one from the number of remaining trips, delete from Redis if 0 99 | tripsRemaining = cardPass.get('tripsRemaining') 100 | 101 | # Note tripsRemaining comes back as a String 102 | if (tripsRemaining == '1'): 103 | # Final trip for this pass, so delete this key 104 | r.delete(cardSerialNumber) 105 | print('This ten trip pass has no more trips remaining, deleted it.') 106 | 107 | # Report that this pass used up all of its trips 108 | sendMessage('pass-used:' + PASS_TYPE_TEN_TRIP, { 109 | 'cardSerialNumber': cardSerialNumber, 110 | 'pass': cardPass, 111 | 'remainingTrips': 0 112 | }) 113 | else: 114 | # Remove one trip from this pass 115 | r.hincrby(cardSerialNumber, 'tripsRemaining', -1) 116 | 117 | newTripsRemaining = (int(tripsRemaining) - 1) 118 | 119 | print('This ten trip pass has ' + str(newTripsRemaining) + ' more trips remaining.') 120 | # Report that this pass was used and has trips remaining 121 | 122 | sendMessage('pass-used:' + PASS_TYPE_TEN_TRIP, { 123 | 'cardSerialNumber': cardSerialNumber, 124 | 'pass': cardPass, 125 | 'remainingTrips': newTripsRemaining 126 | }) 127 | 128 | GPIO.setmode(GPIO.BCM) 129 | GPIO.setup(RED, GPIO.OUT) 130 | GPIO.setup(YELLOW, GPIO.OUT) 131 | GPIO.setup(GREEN, GPIO.OUT) 132 | 133 | GPIO.output(YELLOW, False) 134 | 135 | try: 136 | while(True): 137 | # Set the light to red 138 | print('Light: Red') 139 | GPIO.output(RED, True) 140 | GPIO.output(GREEN, False) 141 | 142 | # Wait for a card to be presented 143 | cardSerialNumber = waitForCard() 144 | 145 | # Does this card have a pass associated with it? 146 | cardPass = getPassForCard(cardSerialNumber) 147 | 148 | if (cardPass): 149 | # Update this card's pass 150 | updatePass(cardSerialNumber, cardPass) 151 | 152 | playAudio('access-granted') 153 | print('Light: Solid Green') 154 | GPIO.output(RED, False) 155 | GPIO.output(GREEN, True) 156 | time.sleep(5) 157 | else: 158 | print('Light: Flashing Red') 159 | playAudio('access-denied') 160 | 161 | # Report unauthorized use attempt 162 | sendMessage('pass-denied', { 163 | 'cardSerialNumber': cardSerialNumber 164 | }) 165 | 166 | flashRedLight(4) 167 | 168 | finally: 169 | GPIO.output(RED, False) 170 | GPIO.output(GREEN, False) 171 | GPIO.cleanup() 172 | -------------------------------------------------------------------------------- /entry-gate/MFRC522.py: -------------------------------------------------------------------------------- 1 | # Modified from: https://github.com/mxgxw/MFRC522-python/blob/master/MFRC522.py 2 | # Trace commented out and the Read and Write methods modified to return values. 3 | # Also changed to use the Broadcom pin mode 4 | 5 | import RPi.GPIO as GPIO 6 | import spi 7 | import signal 8 | import time 9 | 10 | class MFRC522: 11 | NRSTPD = 25 12 | 13 | MAX_LEN = 16 14 | 15 | PCD_IDLE = 0x00 16 | PCD_AUTHENT = 0x0E 17 | PCD_RECEIVE = 0x08 18 | PCD_TRANSMIT = 0x04 19 | PCD_TRANSCEIVE = 0x0C 20 | PCD_RESETPHASE = 0x0F 21 | PCD_CALCCRC = 0x03 22 | 23 | PICC_REQIDL = 0x26 24 | PICC_REQALL = 0x52 25 | PICC_ANTICOLL = 0x93 26 | PICC_SElECTTAG = 0x93 27 | PICC_AUTHENT1A = 0x60 28 | PICC_AUTHENT1B = 0x61 29 | PICC_READ = 0x30 30 | PICC_WRITE = 0xA0 31 | PICC_DECREMENT = 0xC0 32 | PICC_INCREMENT = 0xC1 33 | PICC_RESTORE = 0xC2 34 | PICC_TRANSFER = 0xB0 35 | PICC_HALT = 0x50 36 | 37 | MI_OK = 0 38 | MI_NOTAGERR = 1 39 | MI_ERR = 2 40 | 41 | Reserved00 = 0x00 42 | CommandReg = 0x01 43 | CommIEnReg = 0x02 44 | DivlEnReg = 0x03 45 | CommIrqReg = 0x04 46 | DivIrqReg = 0x05 47 | ErrorReg = 0x06 48 | Status1Reg = 0x07 49 | Status2Reg = 0x08 50 | FIFODataReg = 0x09 51 | FIFOLevelReg = 0x0A 52 | WaterLevelReg = 0x0B 53 | ControlReg = 0x0C 54 | BitFramingReg = 0x0D 55 | CollReg = 0x0E 56 | Reserved01 = 0x0F 57 | 58 | Reserved10 = 0x10 59 | ModeReg = 0x11 60 | TxModeReg = 0x12 61 | RxModeReg = 0x13 62 | TxControlReg = 0x14 63 | TxAutoReg = 0x15 64 | TxSelReg = 0x16 65 | RxSelReg = 0x17 66 | RxThresholdReg = 0x18 67 | DemodReg = 0x19 68 | Reserved11 = 0x1A 69 | Reserved12 = 0x1B 70 | MifareReg = 0x1C 71 | Reserved13 = 0x1D 72 | Reserved14 = 0x1E 73 | SerialSpeedReg = 0x1F 74 | 75 | Reserved20 = 0x20 76 | CRCResultRegM = 0x21 77 | CRCResultRegL = 0x22 78 | Reserved21 = 0x23 79 | ModWidthReg = 0x24 80 | Reserved22 = 0x25 81 | RFCfgReg = 0x26 82 | GsNReg = 0x27 83 | CWGsPReg = 0x28 84 | ModGsPReg = 0x29 85 | TModeReg = 0x2A 86 | TPrescalerReg = 0x2B 87 | TReloadRegH = 0x2C 88 | TReloadRegL = 0x2D 89 | TCounterValueRegH = 0x2E 90 | TCounterValueRegL = 0x2F 91 | 92 | Reserved30 = 0x30 93 | TestSel1Reg = 0x31 94 | TestSel2Reg = 0x32 95 | TestPinEnReg = 0x33 96 | TestPinValueReg = 0x34 97 | TestBusReg = 0x35 98 | AutoTestReg = 0x36 99 | VersionReg = 0x37 100 | AnalogTestReg = 0x38 101 | TestDAC1Reg = 0x39 102 | TestDAC2Reg = 0x3A 103 | TestADCReg = 0x3B 104 | Reserved31 = 0x3C 105 | Reserved32 = 0x3D 106 | Reserved33 = 0x3E 107 | Reserved34 = 0x3F 108 | 109 | serNum = [] 110 | 111 | def __init__(self, dev='/dev/spidev0.0', spd=1000000): 112 | spi.openSPI(device=dev,speed=spd) 113 | GPIO.setmode(GPIO.BCM) 114 | GPIO.setup(25, GPIO.OUT) 115 | GPIO.output(self.NRSTPD, 1) 116 | self.MFRC522_Init() 117 | 118 | def MFRC522_Reset(self): 119 | self.Write_MFRC522(self.CommandReg, self.PCD_RESETPHASE) 120 | 121 | def Write_MFRC522(self, addr, val): 122 | spi.transfer(((addr<<1)&0x7E,val)) 123 | 124 | def Read_MFRC522(self, addr): 125 | val = spi.transfer((((addr<<1)&0x7E) | 0x80,0)) 126 | return val[1] 127 | 128 | def SetBitMask(self, reg, mask): 129 | tmp = self.Read_MFRC522(reg) 130 | self.Write_MFRC522(reg, tmp | mask) 131 | 132 | def ClearBitMask(self, reg, mask): 133 | tmp = self.Read_MFRC522(reg); 134 | self.Write_MFRC522(reg, tmp & (~mask)) 135 | 136 | def AntennaOn(self): 137 | temp = self.Read_MFRC522(self.TxControlReg) 138 | if(~(temp & 0x03)): 139 | self.SetBitMask(self.TxControlReg, 0x03) 140 | 141 | def AntennaOff(self): 142 | self.ClearBitMask(self.TxControlReg, 0x03) 143 | 144 | def MFRC522_ToCard(self,command,sendData): 145 | backData = [] 146 | backLen = 0 147 | status = self.MI_ERR 148 | irqEn = 0x00 149 | waitIRq = 0x00 150 | lastBits = None 151 | n = 0 152 | i = 0 153 | 154 | if command == self.PCD_AUTHENT: 155 | irqEn = 0x12 156 | waitIRq = 0x10 157 | if command == self.PCD_TRANSCEIVE: 158 | irqEn = 0x77 159 | waitIRq = 0x30 160 | 161 | self.Write_MFRC522(self.CommIEnReg, irqEn|0x80) 162 | self.ClearBitMask(self.CommIrqReg, 0x80) 163 | self.SetBitMask(self.FIFOLevelReg, 0x80) 164 | 165 | self.Write_MFRC522(self.CommandReg, self.PCD_IDLE); 166 | 167 | while(i self.MAX_LEN: 203 | n = self.MAX_LEN 204 | 205 | i = 0 206 | while i self.MAX_LEN: 203 | n = self.MAX_LEN 204 | 205 | i = 0 206 | while i