1 | import { packCAN, unpackCAN } from 'can-message';
|
2 | import Event from 'weakmap-event';
|
3 | import { partial } from 'ap';
|
4 | import now from 'performance-now';
|
5 | import raf from 'raf';
|
6 | import { timeout } from 'thyming';
|
7 |
|
8 | import wait from './delay';
|
9 |
|
10 |
|
11 | const MAX_MESSAGE_QUEUE = 5000;
|
12 |
|
13 | const MessageEvent = Event();
|
14 | const ErrorEvent = Event();
|
15 | const ConnectEvent = Event();
|
16 | const DisconnectEvent = Event();
|
17 |
|
18 | export default class Panda {
|
19 | constructor(options) {
|
20 |
|
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 |
|
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 |
|
33 | this.paused = true;
|
34 | this.messageQueue = [];
|
35 |
|
36 |
|
37 | this.readLoop = this.readLoop.bind(this);
|
38 | this.flushMessageQueue = this.flushMessageQueue.bind(this);
|
39 | }
|
40 |
|
41 |
|
42 | isConnected() {
|
43 | return !!this.connected;
|
44 | }
|
45 | isPaused() {
|
46 | return !!this.paused;
|
47 | }
|
48 |
|
49 |
|
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 |
|
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);
|
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 |
|
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 |
|
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 |
|
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 |
|
231 | startReading() {
|
232 | if (this.isReading) {
|
233 | return true;
|
234 | }
|
235 | if (this.isPaused()) {
|
236 | return false;
|
237 | }
|
238 |
|
239 |
|
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 |
|
267 | timeout(this.readLoop);
|
268 | }
|
269 | }
|