UNPKG

6.4 kBJavaScriptView Raw
1import { packCAN, unpackCAN } from 'can-message';
2import Event from 'weakmap-event';
3import { partial } from 'ap';
4import now from 'performance-now';
5import raf from 'raf';
6import { timeout } from 'thyming';
7
8import wait from './delay';
9
10// how many messages to batch at maximum when reading as fast as we can
11const MAX_MESSAGE_QUEUE = 5000;
12
13const MessageEvent = Event();
14const ErrorEvent = Event();
15const ConnectEvent = Event();
16const DisconnectEvent = Event();
17
18export default class Panda {
19 constructor(options) {
20 // setup event handlers
21 this.onMessage = partial(MessageEvent.listen, this);
22 this.onError = partial(ErrorEvent.listen, this);
23 this.onConnect = partial(ConnectEvent.listen, this);
24 this.onDisconnect = partial(DisconnectEvent.listen, this);
25
26 // initialize device object
27 this.device = options.device;
28 this.device.onError(partial(ErrorEvent.broadcast, this));
29 this.device.onConnect(this.connectHandler.bind(this));
30 this.device.onDisconnect(this.disconnectHandler.bind(this));
31
32 // member variables
33 this.paused = true;
34 this.messageQueue = [];
35
36 // function binding
37 this.readLoop = this.readLoop.bind(this);
38 this.flushMessageQueue = this.flushMessageQueue.bind(this);
39 }
40
41 // state getters
42 isConnected() {
43 return !!this.connected;
44 }
45 isPaused() {
46 return !!this.paused;
47 }
48
49 // methods
50 async connect() {
51 if (this.isConnected()) {
52 return this.connected;
53 }
54 await this.device.connect();
55
56 var serialNumber = await this.getSerialNumber();
57 this.connectHandler(serialNumber);
58
59 return serialNumber;
60 }
61 async disconnect() {
62 if (!this.isConnected()) {
63 return false;
64 }
65 return this.device.disconnect();
66 }
67 async start() {
68 await this.connect();
69 return this.unpause();
70 }
71 async pause() {
72 var wasPaused = this.isPaused();
73 this.paused = true;
74
75 return !wasPaused;
76 }
77 async resume() {
78 return this.unpause();
79 }
80 async unpause() {
81 var wasPaused = this.isPaused();
82 if (!wasPaused) {
83 return false;
84 }
85
86 this.paused = false;
87 this.startReading();
88
89 return wasPaused;
90 }
91
92 // vendor API methods
93 async getHealth() {
94 let buf = await this.vendorRequest('health', {
95 request: 0xd2,
96 value: 0,
97 index: 0
98 }, 13);
99
100 let voltage = buf.readUInt32LE(0) / 1000;
101 let current = buf.readUInt32LE(4) / 1000;
102 let isStarted = buf.readInt8(8) === 1;
103 let controlsAreAllowed = buf.readInt8(9) === 1;
104 let isGasInterceptorDetector = buf.readInt8(10) === 1;
105 let isStartSignalDetected = buf.readInt8(11) === 1;
106 let isStartedAlt = buf.readInt8(12) === 1;
107
108 return {
109 voltage,
110 current,
111 isStarted,
112 controlsAreAllowed,
113 isGasInterceptorDetector,
114 isStartSignalDetected,
115 isStartedAlt
116 };
117 }
118 async getDeviceMetadata() {
119 let buf = await this.vendorRequest('getDeviceMetadata', {
120 request: 0xd0,
121 value: 0,
122 index: 0
123 }, 0x20);
124
125 let serial = buf.slice(0, 0x10); // serial is the wifi style serial
126 let secret = buf.slice(0x10, 0x10 + 10);
127 let hashSig = buf.slice(0x1c);
128
129 return [serial.toString(), secret.toString()];
130 }
131 async getSerialNumber() {
132 var [serial, secret] = await this.getDeviceMetadata();
133 return serial;
134 }
135 async getSecret() {
136 var [serial, secret] = await this.getDeviceMetadata();
137 return secret;
138 }
139 async getVersion() {
140 let buf = await this.vendorRequest('getVersion', {
141 request: 0xd6,
142 value: 0,
143 index: 0
144 }, 0x40);
145
146 return buf.toString();
147 }
148 async isGrey() {
149 let buf = await this.vendorRequest('isGrey', {
150 request: 0xc1,
151 value: 0,
152 index: 0
153 }, 0x40);
154
155 return !!(buf.length && buf[0] === 1);
156 }
157 async setSafetyMode(mode) {
158 let buf = await this.vendorWrite('setSafetyMode', {
159 request: 0xdc,
160 value: 0,
161 index: 0
162 });
163
164 return buf.toString();
165 }
166
167 // i/o wrappers
168 async vendorRequest (event, controlParams, length) {
169 try {
170 let result = await this.device.vendorRequest(controlParams, length);
171
172 return result.data;
173 } catch (err) {
174 ErrorEvent.broadcast(this, { event: 'Panda.' + event + ' failed', error: err });
175 throw err;
176 }
177 }
178 async vendorWrite (event, controlParams, message) {
179 if (!message || !message.length) {
180 message = Buffer.from([]);
181 }
182 try {
183 let result = await this.device.vendorWrite(controlParams, message);
184
185 return result.data;
186 } catch (err) {
187 ErrorEvent.broadcast(this, { event: 'Panda.' + event + ' failed', error: err });
188 throw err;
189 }
190 }
191
192 // event handlers
193 connectHandler(usbId) {
194 this.connected = usbId;
195 ConnectEvent.broadcast(this, usbId);
196 }
197 disconnectHandler() {
198 const previousConnection = this.connected;
199 this.connected = false;
200 this.paused = true;
201 DisconnectEvent.broadcast(this, previousConnection);
202 }
203
204 // message queueing and flushing
205 needsFlushMessageQueue() {
206 this.needsFlush = true;
207 if (this.flushEvent) {
208 return this.flushEvent;
209 }
210
211 var unlisten = raf(this.flushMessageQueue);
212
213 this.flushEvent = () => {
214 raf.cancel(unlisten);
215 this.flushEvent = false;
216 };
217
218 return this.flushEvent;
219 }
220 flushMessageQueue() {
221 this.flushEvent();
222
223 if (this.needsFlush && this.messageQueue.length) {
224 let messageQueue = this.messageQueue;
225 this.messageQueue = [];
226 this.needsFlush = false;
227 MessageEvent.broadcast(this, messageQueue);
228 }
229 }
230 // internal reading loop
231 startReading() {
232 if (this.isReading) {
233 return true;
234 }
235 if (this.isPaused()) {
236 return false;
237 }
238
239 // start loop!
240 this.isReading = true;
241 this.readLoop();
242 }
243 async readLoop() {
244 if (this.isPaused()) {
245 this.isReading = false;
246 return false;
247 }
248 this.isReading = true;
249
250 for (let i = 0; i < MAX_MESSAGE_QUEUE; ++i) {
251 let data = await this.device.nextMessage();
252 let receiptTime = now() / 1000
253 let canMessages = unpackCAN(data);
254 if (!canMessages.length) {
255 await wait(1);
256 continue;
257 }
258 this.messageQueue.push({
259 time: receiptTime,
260 canMessages
261 });
262 this.needsFlushMessageQueue();
263 }
264 this.needsFlushMessageQueue();
265
266 // repeat!
267 timeout(this.readLoop);
268 }
269}