-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathdoor.js
executable file
·195 lines (174 loc) · 4.62 KB
/
door.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#!/usr/bin/env node
'use strict';
const util = require('util');
const WebSocket = require('ws');
const gpio = require('rpi-gpio');
const express = require('express');
const errors = require('./lib/errors');
const options = {
server: 'localhost',
port: '3000',
door: '1',
gpio: '10',
locktime: 5000,
pingtime: 5000,
};
const getopts = require('node-getopt').create([
['x', 'dummy', "Don't use GPIO, print instead"],
['g', 'gpio=', 'GPIO pins to open the door'],
['d', 'door=', 'Connect to server with door_id'],
['t', 'token=', 'Connect to server with token (required)'],
['s', 'server=', 'Connect to server at address'],
['p', 'port=', 'Connect to server on port'],
['', 'keypad[=]', 'Enable Wiegand keypad on GPIO pins (default 11,12)'],
['', 'insecure', "Don't validate SSL"],
['h', 'help', ''],
]).bindHelp().setHelp(
'Doorbot: connects to a server and registers a door to open.\n' +
'Usage: node door [OPTIONS]\n\n' +
'[[OPTIONS]]\n\n' +
'Repository: https://github.com/Thann/Doorbot'
);
const opt = getopts.parseSystem();
if (opt.argv.length > 0) {
console.error('ERROR: Unexpected argument(s): ' + opt.argv.join(', '));
console.error(getopts.getHelp());
process.exit(1);
}
if (!opt.options.token) {
console.error('ERROR: token is required');
console.error(getopts.getHelp());
process.exit(1);
}
// Merge opts into options
Object.assign(options, opt.options);
// Handle errors
process.on('unhandledRejection', function(err, promise) {
if (!(err instanceof errors.HandledError))
console.error('UHR', err, promise);
});
let lock = false;
if (!options.dummy)
gpio.setup(parseInt(options.gpio));
function open() {
if (options.dummy)
return console.log('Dummy Open');
if (lock)
return console.warn('ERROR: already open');
lock = true;
gpio.write(options.gpio, true);
setTimeout(function() {
gpio.write(options.gpio, false);
lock = false;
}, options.locktime);
}
let ws;
let pongTimeout;
function connect() {
if (ws) ws.terminate();
ws = new WebSocket(
util.format('ws%s://%s:%s/api/v1/doors/%s/connect',
options.insecure?'':'s',
options.server,
options.port,
options.door
), {
headers: {
'Authorization': options.token,
},
perMessageDeflate: false,
handshakeTimeout: options.pingtime,
}
);
ws.on('error', function(e) {
console.log('Connection Error:', e.code);
});
ws.on('open', function() {
console.log('success');
});
ws.on('close', function(code, reason) {
console.log('CLOSE:', code, reason);
if (code === 1007)
safeExit();
});
ws.on('message', function(data) {
console.log('WS:', data);
open();
});
ws.on('pong', function() {
clearTimeout(pongTimeout);
});
}
// Wiegand keypad init
if (options.keypad !== undefined && !options.dummy) {
let waiting, index = 0;
const maxCodeLen = 8; // must be even
const keypadTimeout = 5; // seconds
const keycode = Buffer.alloc(maxCodeLen/2);
const pin = (options.keypad || '11,12').split(',').map(Number);
gpio.setup(pin[0], gpio.DIR_IN, gpio.EDGE_FALLING);
gpio.setup(pin[1], gpio.DIR_IN, gpio.EDGE_FALLING);
// TODO: unlocking the door interferes with this...
gpio.on('change', function(num, dir) {
const i = parseInt(index/8);
clearTimeout(waiting);
if (num === pin[1]) {
keycode[i] = keycode[i] | (1 << 7-index%8);
}
if (index >= maxCodeLen*4-1) { //TODO: pound sign?
ws.send('keycode:'+keycode.toString('hex'));
index = 0;
keycode.fill(0);
} else {
waiting = setTimeout(function() {
index = 0;
keycode.fill(0);
}, keypadTimeout*1000);
index += 1;
}
});
} else if (options.keypad !== undefined) {
setInterval(() => {
if (ws.readyState === 1) {
console.log('Sending dummy keycode to server...');
ws.send('keycode:00000000');
}
}, 10000);
}
function safeExit() {
if (!options.dummy) {
gpio.write(options.gpio, false, function() {
process.exit(0);
});
} else {
process.exit(0);
}
}
// Lock the door on quit
process.on('SIGINT', safeExit);
process.on('SIGTERM', safeExit);
setInterval(function() {
if (ws.readyState !== WebSocket.OPEN) {
console.log(`Retrying connection (${ws.readyState})`);
connect();
} else {
// listen for pong, close if we dont hear back
pongTimeout = setTimeout(function() {
ws.close();
}, options.pingtime/2);
ws.ping();
}
}, options.pingtime);
connect();
// Start Healthcheck server
const hcPort = 3000;
const router = express.Router();
require('./api/health')(router);
require('http')
.createServer(express().use('/api/v1', router))
.on('error', () => {
console.error(`Healthcheck failed to listen on port ${hcPort}`);
})
.listen(hcPort, () => {
console.log(`Healthcheck listening on port ${hcPort}`);
});