blob: 60b0afd6c0a0cca18c1d9808935ae821feecec61 [file] [log] [blame]
[email protected]e8a5608b2014-07-30 11:28:431// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5define('serial_service', [
6 'content/public/renderer/service_provider',
sammc93c5de27b2014-09-03 07:31:157 'data_receiver',
8 'data_sender',
[email protected]e8a5608b2014-07-30 11:28:439 'device/serial/serial.mojom',
[email protected]638a7d912014-08-14 15:05:1410 'mojo/public/js/bindings/core',
[email protected]e8a5608b2014-07-30 11:28:4311 'mojo/public/js/bindings/router',
sammc93c5de27b2014-09-03 07:31:1512], function(serviceProvider,
13 dataReceiver,
14 dataSender,
15 serialMojom,
16 core,
17 routerModule) {
[email protected]638a7d912014-08-14 15:05:1418 /**
19 * A Javascript client for the serial service and connection Mojo services.
20 *
21 * This provides a thick client around the Mojo services, exposing a JS-style
22 * interface to serial connections and information about serial devices. This
23 * converts parameters and result between the Apps serial API types and the
24 * Mojo types.
25 */
[email protected]e8a5608b2014-07-30 11:28:4326
[email protected]638a7d912014-08-14 15:05:1427 var service = new serialMojom.SerialServiceProxy(new routerModule.Router(
28 serviceProvider.connectToService(serialMojom.SerialServiceProxy.NAME_)));
[email protected]e8a5608b2014-07-30 11:28:4329
30 function getDevices() {
31 return service.getDevices().then(function(response) {
32 return $Array.map(response.devices, function(device) {
33 var result = {path: device.path || ''};
34 if (device.has_vendor_id)
35 result.vendorId = device.vendor_id;
36 if (device.has_product_id)
37 result.productId = device.product_id;
38 if (device.display_name)
39 result.displayName = device.display_name;
40 return result;
41 });
42 });
43 }
44
[email protected]638a7d912014-08-14 15:05:1445 var DEFAULT_CLIENT_OPTIONS = {
46 persistent: false,
47 name: '',
48 receiveTimeout: 0,
49 sendTimeout: 0,
50 bufferSize: 4096,
51 };
52
53 var DATA_BITS_TO_MOJO = {
54 undefined: serialMojom.DataBits.NONE,
55 'seven': serialMojom.DataBits.SEVEN,
56 'eight': serialMojom.DataBits.EIGHT,
57 };
58 var STOP_BITS_TO_MOJO = {
59 undefined: serialMojom.StopBits.NONE,
60 'one': serialMojom.StopBits.ONE,
61 'two': serialMojom.StopBits.TWO,
62 };
63 var PARITY_BIT_TO_MOJO = {
64 undefined: serialMojom.ParityBit.NONE,
65 'no': serialMojom.ParityBit.NO,
66 'odd': serialMojom.ParityBit.ODD,
67 'even': serialMojom.ParityBit.EVEN,
68 };
sammc93c5de27b2014-09-03 07:31:1569 var SEND_ERROR_TO_MOJO = {
70 undefined: serialMojom.SendError.NONE,
71 'disconnected': serialMojom.SendError.DISCONNECTED,
72 'pending': serialMojom.SendError.PENDING,
73 'timeout': serialMojom.SendError.TIMEOUT,
74 'system_error': serialMojom.SendError.SYSTEM_ERROR,
75 };
76 var RECEIVE_ERROR_TO_MOJO = {
77 undefined: serialMojom.ReceiveError.NONE,
78 'disconnected': serialMojom.ReceiveError.DISCONNECTED,
79 'device_lost': serialMojom.ReceiveError.DEVICE_LOST,
80 'timeout': serialMojom.ReceiveError.TIMEOUT,
81 'system_error': serialMojom.ReceiveError.SYSTEM_ERROR,
82 };
[email protected]638a7d912014-08-14 15:05:1483
84 function invertMap(input) {
85 var output = {};
86 for (var key in input) {
87 if (key == 'undefined')
88 output[input[key]] = undefined;
89 else
90 output[input[key]] = key;
91 }
92 return output;
93 }
94 var DATA_BITS_FROM_MOJO = invertMap(DATA_BITS_TO_MOJO);
95 var STOP_BITS_FROM_MOJO = invertMap(STOP_BITS_TO_MOJO);
96 var PARITY_BIT_FROM_MOJO = invertMap(PARITY_BIT_TO_MOJO);
sammc93c5de27b2014-09-03 07:31:1597 var SEND_ERROR_FROM_MOJO = invertMap(SEND_ERROR_TO_MOJO);
98 var RECEIVE_ERROR_FROM_MOJO = invertMap(RECEIVE_ERROR_TO_MOJO);
[email protected]638a7d912014-08-14 15:05:1499
100 function getServiceOptions(options) {
101 var out = {};
102 if (options.dataBits)
103 out.data_bits = DATA_BITS_TO_MOJO[options.dataBits];
104 if (options.stopBits)
105 out.stop_bits = STOP_BITS_TO_MOJO[options.stopBits];
106 if (options.parityBit)
107 out.parity_bit = PARITY_BIT_TO_MOJO[options.parityBit];
108 if ('ctsFlowControl' in options) {
109 out.has_cts_flow_control = true;
110 out.cts_flow_control = options.ctsFlowControl;
111 }
112 if ('bitrate' in options)
113 out.bitrate = options.bitrate;
114 return out;
115 }
116
117 function convertServiceInfo(result) {
118 if (!result.info)
119 throw new Error('Failed to get ConnectionInfo.');
120 return {
121 ctsFlowControl: !!result.info.cts_flow_control,
122 bitrate: result.info.bitrate || undefined,
123 dataBits: DATA_BITS_FROM_MOJO[result.info.data_bits],
124 stopBits: STOP_BITS_FROM_MOJO[result.info.stop_bits],
125 parityBit: PARITY_BIT_FROM_MOJO[result.info.parity_bit],
126 };
127 }
128
sammc93c5de27b2014-09-03 07:31:15129 function Connection(
130 remoteConnection, router, receivePipe, sendPipe, id, options) {
[email protected]638a7d912014-08-14 15:05:14131 this.remoteConnection_ = remoteConnection;
132 this.router_ = router;
[email protected]638a7d912014-08-14 15:05:14133 this.options_ = {};
134 for (var key in DEFAULT_CLIENT_OPTIONS) {
135 this.options_[key] = DEFAULT_CLIENT_OPTIONS[key];
136 }
137 this.setClientOptions_(options);
sammc93c5de27b2014-09-03 07:31:15138 this.receivePipe_ =
139 new dataReceiver.DataReceiver(receivePipe,
140 this.options_.bufferSize,
141 serialMojom.ReceiveError.DISCONNECTED);
142 this.sendPipe_ = new dataSender.DataSender(
143 sendPipe, this.options_.bufferSize, serialMojom.SendError.DISCONNECTED);
144 this.id_ = id;
145 getConnections().then(function(connections) {
146 connections[this.id_] = this;
147 }.bind(this));
148 this.paused_ = false;
149 this.sendInProgress_ = false;
150
151 // queuedReceiveData_ or queuedReceiveError will store the receive result or
152 // error, respectively, if a receive completes or fails while this
153 // connection is paused. At most one of the the two may be non-null: a
154 // receive completed while paused will only set one of them, no further
155 // receives will be performed while paused and a queued result is dispatched
156 // before any further receives are initiated when unpausing.
157 this.queuedReceiveData_ = null;
158 this.queuedReceiveError = null;
159
160 this.startReceive_();
[email protected]638a7d912014-08-14 15:05:14161 }
162
163 Connection.create = function(path, options) {
164 options = options || {};
165 var serviceOptions = getServiceOptions(options);
166 var pipe = core.createMessagePipe();
sammcd96dc3e2014-08-27 06:41:34167 var sendPipe = core.createMessagePipe();
168 var receivePipe = core.createMessagePipe();
169 service.connect(path,
170 serviceOptions,
171 pipe.handle0,
172 sendPipe.handle0,
173 receivePipe.handle0);
[email protected]638a7d912014-08-14 15:05:14174 var router = new routerModule.Router(pipe.handle1);
175 var connection = new serialMojom.ConnectionProxy(router);
sammc93c5de27b2014-09-03 07:31:15176 return connection.getInfo().then(convertServiceInfo).then(function(info) {
[email protected]638a7d912014-08-14 15:05:14177 return Promise.all([info, allocateConnectionId()]);
178 }).catch(function(e) {
179 router.close();
sammc93c5de27b2014-09-03 07:31:15180 core.close(sendPipe.handle1);
181 core.close(receivePipe.handle1);
[email protected]638a7d912014-08-14 15:05:14182 throw e;
183 }).then(function(results) {
184 var info = results[0];
185 var id = results[1];
sammc93c5de27b2014-09-03 07:31:15186 var serialConnectionClient = new Connection(connection,
187 router,
188 receivePipe.handle1,
189 sendPipe.handle1,
190 id,
191 options);
[email protected]638a7d912014-08-14 15:05:14192 var clientInfo = serialConnectionClient.getClientInfo_();
193 for (var key in clientInfo) {
194 info[key] = clientInfo[key];
195 }
196 return {
197 connection: serialConnectionClient,
198 info: info,
199 };
200 });
201 };
202
203 Connection.prototype.close = function() {
204 this.router_.close();
sammc93c5de27b2014-09-03 07:31:15205 this.receivePipe_.close();
206 this.sendPipe_.close();
207 clearTimeout(this.receiveTimeoutId_);
208 clearTimeout(this.sendTimeoutId_);
[email protected]638a7d912014-08-14 15:05:14209 return getConnections().then(function(connections) {
sammc93c5de27b2014-09-03 07:31:15210 delete connections[this.id_];
[email protected]638a7d912014-08-14 15:05:14211 return true;
212 }.bind(this));
213 };
214
215 Connection.prototype.getClientInfo_ = function() {
216 var info = {
217 connectionId: this.id_,
218 paused: this.paused_,
sammc93c5de27b2014-09-03 07:31:15219 };
[email protected]638a7d912014-08-14 15:05:14220 for (var key in this.options_) {
221 info[key] = this.options_[key];
222 }
223 return info;
224 };
225
226 Connection.prototype.getInfo = function() {
227 var info = this.getClientInfo_();
228 return this.remoteConnection_.getInfo().then(convertServiceInfo).then(
229 function(result) {
230 for (var key in result) {
231 info[key] = result[key];
232 }
233 return info;
234 }).catch(function() {
235 return info;
236 });
237 };
238
239 Connection.prototype.setClientOptions_ = function(options) {
240 if ('name' in options)
241 this.options_.name = options.name;
242 if ('receiveTimeout' in options)
243 this.options_.receiveTimeout = options.receiveTimeout;
244 if ('sendTimeout' in options)
245 this.options_.sendTimeout = options.sendTimeout;
246 if ('bufferSize' in options)
247 this.options_.bufferSize = options.bufferSize;
248 };
249
250 Connection.prototype.setOptions = function(options) {
251 this.setClientOptions_(options);
252 var serviceOptions = getServiceOptions(options);
253 if ($Object.keys(serviceOptions).length == 0)
254 return true;
255 return this.remoteConnection_.setOptions(serviceOptions).then(
256 function(result) {
257 return !!result.success;
258 }).catch(function() {
259 return false;
260 });
261 };
262
263 Connection.prototype.getControlSignals = function() {
264 return this.remoteConnection_.getControlSignals().then(function(result) {
265 if (!result.signals)
266 throw new Error('Failed to get control signals.');
267 var signals = result.signals;
268 return {
269 dcd: !!signals.dcd,
270 cts: !!signals.cts,
271 ri: !!signals.ri,
272 dsr: !!signals.dsr,
273 };
274 });
275 };
276
277 Connection.prototype.setControlSignals = function(signals) {
278 var controlSignals = {};
279 if ('dtr' in signals) {
280 controlSignals.has_dtr = true;
281 controlSignals.dtr = signals.dtr;
282 }
283 if ('rts' in signals) {
284 controlSignals.has_rts = true;
285 controlSignals.rts = signals.rts;
286 }
287 return this.remoteConnection_.setControlSignals(controlSignals).then(
288 function(result) {
289 return !!result.success;
290 });
291 };
292
293 Connection.prototype.flush = function() {
294 return this.remoteConnection_.flush().then(function(result) {
295 return !!result.success;
296 });
297 };
298
299 Connection.prototype.setPaused = function(paused) {
300 this.paused_ = paused;
sammc93c5de27b2014-09-03 07:31:15301 if (paused) {
302 clearTimeout(this.receiveTimeoutId_);
303 this.receiveTimeoutId_ = null;
304 } else if (!this.receiveInProgress_) {
305 this.startReceive_();
306 }
307 };
308
309 Connection.prototype.send = function(data) {
310 if (this.sendInProgress_)
311 return Promise.resolve({bytesSent: 0, error: 'pending'});
312
313 if (this.options_.sendTimeout) {
314 this.sendTimeoutId_ = setTimeout(function() {
315 this.sendPipe_.cancel(serialMojom.SendError.TIMEOUT);
316 }.bind(this), this.options_.sendTimeout);
317 }
318 this.sendInProgress_ = true;
319 return this.sendPipe_.send(data).then(function(bytesSent) {
320 return {bytesSent: bytesSent};
321 }).catch(function(e) {
322 return {
323 bytesSent: e.bytesSent,
324 error: SEND_ERROR_FROM_MOJO[e.error],
325 };
326 }).then(function(result) {
327 if (this.sendTimeoutId_)
328 clearTimeout(this.sendTimeoutId_);
329 this.sendTimeoutId_ = null;
330 this.sendInProgress_ = false;
331 return result;
332 }.bind(this));
333 };
334
335 Connection.prototype.startReceive_ = function() {
336 this.receiveInProgress_ = true;
337 var receivePromise = null;
338 // If we have a queued receive result, dispatch it immediately instead of
339 // starting a new receive.
340 if (this.queuedReceiveData_) {
341 receivePromise = Promise.resolve(this.queuedReceiveData_);
342 this.queuedReceiveData_ = null;
343 } else if (this.queuedReceiveError) {
344 receivePromise = Promise.reject(this.queuedReceiveError);
345 this.queuedReceiveError = null;
346 } else {
347 receivePromise = this.receivePipe_.receive();
348 }
349 receivePromise.then(this.onDataReceived_.bind(this)).catch(
350 this.onReceiveError_.bind(this));
351 this.startReceiveTimeoutTimer_();
352 };
353
354 Connection.prototype.onDataReceived_ = function(data) {
355 this.startReceiveTimeoutTimer_();
356 this.receiveInProgress_ = false;
357 if (this.paused_) {
358 this.queuedReceiveData_ = data;
359 return;
360 }
361 if (this.onData) {
362 this.onData(data);
363 }
364 if (!this.paused_) {
365 this.startReceive_();
366 }
367 };
368
369 Connection.prototype.onReceiveError_ = function(e) {
370 clearTimeout(this.receiveTimeoutId_);
371 this.receiveInProgress_ = false;
372 if (this.paused_) {
373 this.queuedReceiveError = e;
374 return;
375 }
376 var error = e.error;
377 this.paused_ = true;
378 if (this.onError)
379 this.onError(RECEIVE_ERROR_FROM_MOJO[error]);
380 };
381
382 Connection.prototype.startReceiveTimeoutTimer_ = function() {
383 clearTimeout(this.receiveTimeoutId_);
384 if (this.options_.receiveTimeout && !this.paused_) {
385 this.receiveTimeoutId_ = setTimeout(this.onReceiveTimeout_.bind(this),
386 this.options_.receiveTimeout);
387 }
388 };
389
390 Connection.prototype.onReceiveTimeout_ = function() {
391 if (this.onError)
392 this.onError('timeout');
393 this.startReceiveTimeoutTimer_();
[email protected]638a7d912014-08-14 15:05:14394 };
395
396 var connections_ = {};
397 var nextConnectionId_ = 0;
398
399 // Wrap all access to |connections_| through getConnections to avoid adding
400 // any synchronous dependencies on it. This will likely be important when
401 // supporting persistent connections by stashing them.
402 function getConnections() {
403 return Promise.resolve(connections_);
404 }
405
406 function getConnection(id) {
407 return getConnections().then(function(connections) {
408 if (!connections[id])
sammc93c5de27b2014-09-03 07:31:15409 throw new Error('Serial connection not found.');
[email protected]638a7d912014-08-14 15:05:14410 return connections[id];
411 });
412 }
413
414 function allocateConnectionId() {
415 return Promise.resolve(nextConnectionId_++);
416 }
417
[email protected]e8a5608b2014-07-30 11:28:43418 return {
419 getDevices: getDevices,
[email protected]638a7d912014-08-14 15:05:14420 createConnection: Connection.create,
421 getConnection: getConnection,
422 getConnections: getConnections,
sammc93c5de27b2014-09-03 07:31:15423 // For testing.
424 Connection: Connection,
[email protected]e8a5608b2014-07-30 11:28:43425 };
426});