1 // 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.
5 define('serial_service', [
6 'content/public/renderer/service_provider',
9 'device/serial/serial.mojom',
10 'device/serial/serial_serialization.mojom',
11 'mojo/public/js/core',
12 'mojo/public/js/router',
13 ], function(serviceProvider,
21 * A Javascript client for the serial service and connection Mojo services.
23 * This provides a thick client around the Mojo services, exposing a JS-style
24 * interface to serial connections and information about serial devices. This
25 * converts parameters and result between the Apps serial API types and the
29 var service = new serialMojom.SerialService.proxyClass(
30 new routerModule.Router(
31 serviceProvider.connectToService(serialMojom.SerialService.name)));
33 function getDevices() {
34 return service.getDevices().then(function(response) {
35 return $Array.map(response.devices, function(device) {
36 var result = {path: device.path};
37 if (device.has_vendor_id)
38 result.vendorId = device.vendor_id;
39 if (device.has_product_id)
40 result.productId = device.product_id;
41 if (device.display_name)
42 result.displayName = device.display_name;
48 var DATA_BITS_TO_MOJO = {
49 undefined: serialMojom.DataBits.NONE,
50 'seven': serialMojom.DataBits.SEVEN,
51 'eight': serialMojom.DataBits.EIGHT,
53 var STOP_BITS_TO_MOJO = {
54 undefined: serialMojom.StopBits.NONE,
55 'one': serialMojom.StopBits.ONE,
56 'two': serialMojom.StopBits.TWO,
58 var PARITY_BIT_TO_MOJO = {
59 undefined: serialMojom.ParityBit.NONE,
60 'no': serialMojom.ParityBit.NO,
61 'odd': serialMojom.ParityBit.ODD,
62 'even': serialMojom.ParityBit.EVEN,
64 var SEND_ERROR_TO_MOJO = {
65 undefined: serialMojom.SendError.NONE,
66 'disconnected': serialMojom.SendError.DISCONNECTED,
67 'pending': serialMojom.SendError.PENDING,
68 'timeout': serialMojom.SendError.TIMEOUT,
69 'system_error': serialMojom.SendError.SYSTEM_ERROR,
71 var RECEIVE_ERROR_TO_MOJO = {
72 undefined: serialMojom.ReceiveError.NONE,
73 'disconnected': serialMojom.ReceiveError.DISCONNECTED,
74 'device_lost': serialMojom.ReceiveError.DEVICE_LOST,
75 'timeout': serialMojom.ReceiveError.TIMEOUT,
76 'system_error': serialMojom.ReceiveError.SYSTEM_ERROR,
79 function invertMap(input) {
81 for (var key in input) {
82 if (key == 'undefined')
83 output[input[key]] = undefined;
85 output[input[key]] = key;
89 var DATA_BITS_FROM_MOJO = invertMap(DATA_BITS_TO_MOJO);
90 var STOP_BITS_FROM_MOJO = invertMap(STOP_BITS_TO_MOJO);
91 var PARITY_BIT_FROM_MOJO = invertMap(PARITY_BIT_TO_MOJO);
92 var SEND_ERROR_FROM_MOJO = invertMap(SEND_ERROR_TO_MOJO);
93 var RECEIVE_ERROR_FROM_MOJO = invertMap(RECEIVE_ERROR_TO_MOJO);
95 function getServiceOptions(options) {
98 out.data_bits = DATA_BITS_TO_MOJO[options.dataBits];
100 out.stop_bits = STOP_BITS_TO_MOJO[options.stopBits];
101 if (options.parityBit)
102 out.parity_bit = PARITY_BIT_TO_MOJO[options.parityBit];
103 if ('ctsFlowControl' in options) {
104 out.has_cts_flow_control = true;
105 out.cts_flow_control = options.ctsFlowControl;
107 if ('bitrate' in options)
108 out.bitrate = options.bitrate;
112 function convertServiceInfo(result) {
114 throw new Error('Failed to get ConnectionInfo.');
116 ctsFlowControl: !!result.info.cts_flow_control,
117 bitrate: result.info.bitrate || undefined,
118 dataBits: DATA_BITS_FROM_MOJO[result.info.data_bits],
119 stopBits: STOP_BITS_FROM_MOJO[result.info.stop_bits],
120 parityBit: PARITY_BIT_FROM_MOJO[result.info.parity_bit],
124 // Update client-side options |clientOptions| from the user-provided
126 function updateClientOptions(clientOptions, options) {
127 if ('name' in options)
128 clientOptions.name = options.name;
129 if ('receiveTimeout' in options)
130 clientOptions.receiveTimeout = options.receiveTimeout;
131 if ('sendTimeout' in options)
132 clientOptions.sendTimeout = options.sendTimeout;
133 if ('bufferSize' in options)
134 clientOptions.bufferSize = options.bufferSize;
137 function Connection(connection, router, receivePipe, sendPipe, id, options) {
138 var state = new serialization.ConnectionState();
139 state.connectionId = id;
140 updateClientOptions(state, options);
141 var receiver = new dataReceiver.DataReceiver(
142 receivePipe, state.bufferSize, serialMojom.ReceiveError.DISCONNECTED);
143 var sender = new dataSender.DataSender(
144 sendPipe, state.bufferSize, serialMojom.SendError.DISCONNECTED);
151 serialMojom.ReceiveError.NONE);
152 connections_.set(id, this);
153 this.startReceive_();
156 // Initializes this Connection from the provided args.
157 Connection.prototype.init_ = function(state,
163 queuedReceiveError) {
166 // queuedReceiveData_ or queuedReceiveError_ will store the receive result
167 // or error, respectively, if a receive completes or fails while this
168 // connection is paused. At most one of the the two may be non-null: a
169 // receive completed while paused will only set one of them, no further
170 // receives will be performed while paused and a queued result is dispatched
171 // before any further receives are initiated when unpausing.
172 if (queuedReceiveError != serialMojom.ReceiveError.NONE)
173 this.queuedReceiveError_ = {error: queuedReceiveError};
174 if (queuedReceiveData) {
175 this.queuedReceiveData_ = new ArrayBuffer(queuedReceiveData.length);
176 new Int8Array(this.queuedReceiveData_).set(queuedReceiveData);
178 this.router_ = router;
179 this.remoteConnection_ = connection;
180 this.receivePipe_ = receiver;
181 this.sendPipe_ = sender;
182 this.sendInProgress_ = false;
185 Connection.create = function(path, options) {
186 options = options || {};
187 var serviceOptions = getServiceOptions(options);
188 var pipe = core.createMessagePipe();
189 var sendPipe = core.createMessagePipe();
190 var receivePipe = core.createMessagePipe();
191 service.connect(path,
195 receivePipe.handle0);
196 var router = new routerModule.Router(pipe.handle1);
197 var connection = new serialMojom.Connection.proxyClass(router);
198 return connection.getInfo().then(convertServiceInfo).then(function(info) {
199 return Promise.all([info, allocateConnectionId()]);
200 }).catch(function(e) {
202 core.close(sendPipe.handle1);
203 core.close(receivePipe.handle1);
205 }).then(function(results) {
206 var info = results[0];
208 var serialConnectionClient = new Connection(connection,
214 var clientInfo = serialConnectionClient.getClientInfo_();
215 for (var key in clientInfo) {
216 info[key] = clientInfo[key];
219 connection: serialConnectionClient,
225 Connection.prototype.close = function() {
226 this.router_.close();
227 this.receivePipe_.close();
228 this.sendPipe_.close();
229 clearTimeout(this.receiveTimeoutId_);
230 clearTimeout(this.sendTimeoutId_);
231 connections_.delete(this.state_.connectionId);
235 Connection.prototype.getClientInfo_ = function() {
237 connectionId: this.state_.connectionId,
238 paused: this.state_.paused,
239 persistent: this.state_.persistent,
240 name: this.state_.name,
241 receiveTimeout: this.state_.receiveTimeout,
242 sendTimeout: this.state_.sendTimeout,
243 bufferSize: this.state_.bufferSize,
247 Connection.prototype.getInfo = function() {
248 var info = this.getClientInfo_();
249 return this.remoteConnection_.getInfo().then(convertServiceInfo).then(
251 for (var key in result) {
252 info[key] = result[key];
255 }).catch(function() {
260 Connection.prototype.setOptions = function(options) {
261 updateClientOptions(this.state_, options);
262 var serviceOptions = getServiceOptions(options);
263 if ($Object.keys(serviceOptions).length == 0)
265 return this.remoteConnection_.setOptions(serviceOptions).then(
267 return !!result.success;
268 }).catch(function() {
273 Connection.prototype.getControlSignals = function() {
274 return this.remoteConnection_.getControlSignals().then(function(result) {
276 throw new Error('Failed to get control signals.');
277 var signals = result.signals;
287 Connection.prototype.setControlSignals = function(signals) {
288 var controlSignals = {};
289 if ('dtr' in signals) {
290 controlSignals.has_dtr = true;
291 controlSignals.dtr = signals.dtr;
293 if ('rts' in signals) {
294 controlSignals.has_rts = true;
295 controlSignals.rts = signals.rts;
297 return this.remoteConnection_.setControlSignals(controlSignals).then(
299 return !!result.success;
303 Connection.prototype.flush = function() {
304 return this.remoteConnection_.flush().then(function(result) {
305 return !!result.success;
309 Connection.prototype.setPaused = function(paused) {
310 this.state_.paused = paused;
312 clearTimeout(this.receiveTimeoutId_);
313 this.receiveTimeoutId_ = null;
314 } else if (!this.receiveInProgress_) {
315 this.startReceive_();
319 Connection.prototype.send = function(data) {
320 if (this.sendInProgress_)
321 return Promise.resolve({bytesSent: 0, error: 'pending'});
323 if (this.state_.sendTimeout) {
324 this.sendTimeoutId_ = setTimeout(function() {
325 this.sendPipe_.cancel(serialMojom.SendError.TIMEOUT);
326 }.bind(this), this.state_.sendTimeout);
328 this.sendInProgress_ = true;
329 return this.sendPipe_.send(data).then(function(bytesSent) {
330 return {bytesSent: bytesSent};
331 }).catch(function(e) {
333 bytesSent: e.bytesSent,
334 error: SEND_ERROR_FROM_MOJO[e.error],
336 }).then(function(result) {
337 if (this.sendTimeoutId_)
338 clearTimeout(this.sendTimeoutId_);
339 this.sendTimeoutId_ = null;
340 this.sendInProgress_ = false;
345 Connection.prototype.startReceive_ = function() {
346 this.receiveInProgress_ = true;
347 var receivePromise = null;
348 // If we have a queued receive result, dispatch it immediately instead of
349 // starting a new receive.
350 if (this.queuedReceiveData_) {
351 receivePromise = Promise.resolve(this.queuedReceiveData_);
352 this.queuedReceiveData_ = null;
353 } else if (this.queuedReceiveError_) {
354 receivePromise = Promise.reject(this.queuedReceiveError_);
355 this.queuedReceiveError_ = null;
357 receivePromise = this.receivePipe_.receive();
359 receivePromise.then(this.onDataReceived_.bind(this)).catch(
360 this.onReceiveError_.bind(this));
361 this.startReceiveTimeoutTimer_();
364 Connection.prototype.onDataReceived_ = function(data) {
365 this.startReceiveTimeoutTimer_();
366 this.receiveInProgress_ = false;
367 if (this.state_.paused) {
368 this.queuedReceiveData_ = data;
374 if (!this.state_.paused) {
375 this.startReceive_();
379 Connection.prototype.onReceiveError_ = function(e) {
380 clearTimeout(this.receiveTimeoutId_);
381 this.receiveInProgress_ = false;
382 if (this.state_.paused) {
383 this.queuedReceiveError_ = e;
387 this.state_.paused = true;
389 this.onError(RECEIVE_ERROR_FROM_MOJO[error]);
392 Connection.prototype.startReceiveTimeoutTimer_ = function() {
393 clearTimeout(this.receiveTimeoutId_);
394 if (this.state_.receiveTimeout && !this.state_.paused) {
395 this.receiveTimeoutId_ = setTimeout(this.onReceiveTimeout_.bind(this),
396 this.state_.receiveTimeout);
400 Connection.prototype.onReceiveTimeout_ = function() {
402 this.onError('timeout');
403 this.startReceiveTimeoutTimer_();
406 Connection.prototype.serialize = function() {
407 connections_.delete(this.state_.connectionId);
410 var handle = this.router_.connector_.handle_;
411 this.router_.connector_.handle_ = null;
412 this.router_.close();
413 clearTimeout(this.receiveTimeoutId_);
414 clearTimeout(this.sendTimeoutId_);
416 // Serializing receivePipe_ will cancel an in-progress receive, which would
417 // pause the connection, so save it ahead of time.
418 var paused = this.state_.paused;
420 this.receivePipe_.serialize(),
421 this.sendPipe_.serialize(),
422 ]).then(function(serializedComponents) {
423 var queuedReceiveError = serialMojom.ReceiveError.NONE;
424 if (this.queuedReceiveError_)
425 queuedReceiveError = this.queuedReceiveError_.error;
426 this.state_.paused = paused;
427 var serialized = new serialization.SerializedConnection();
428 serialized.state = this.state_;
429 serialized.queuedReceiveError = queuedReceiveError;
430 serialized.queuedReceiveData =
431 this.queuedReceiveData_ ? new Int8Array(this.queuedReceiveData_) :
433 serialized.connection = handle;
434 serialized.receiver = serializedComponents[0];
435 serialized.sender = serializedComponents[1];
440 Connection.deserialize = function(serialized) {
441 var serialConnection = $Object.create(Connection.prototype);
442 var router = new routerModule.Router(serialized.connection);
443 var connection = new serialMojom.ConnectionProxy(router);
444 var receiver = dataReceiver.DataReceiver.deserialize(serialized.receiver);
445 var sender = dataSender.DataSender.deserialize(serialized.sender);
447 // Ensure that paused and persistent are booleans.
448 serialized.state.paused = !!serialized.state.paused;
449 serialized.state.persistent = !!serialized.state.persistent;
450 serialConnection.init_(serialized.state,
455 serialized.queuedReceiveData,
456 serialized.queuedReceiveError);
457 serialConnection.awaitingResume_ = true;
458 var connectionId = serialized.state.connectionId;
459 connections_.set(connectionId, serialConnection);
460 if (connectionId >= nextConnectionId_)
461 nextConnectionId_ = connectionId + 1;
462 return serialConnection;
465 // Resume receives on a deserialized connection.
466 Connection.prototype.resumeReceives = function() {
467 if (!this.awaitingResume_)
469 this.awaitingResume_ = false;
470 if (!this.state_.paused)
471 this.startReceive_();
474 // All accesses to connections_ and nextConnectionId_ other than those
475 // involved in deserialization should ensure that
476 // connectionDeserializationComplete_ has resolved first.
477 // Note: this will not immediately resolve once serial connection stashing and
478 // restoring is implemented.
479 var connectionDeserializationComplete_ = Promise.resolve();
481 // The map of connection ID to connection object.
482 var connections_ = new Map();
484 // The next connection ID to be allocated.
485 var nextConnectionId_ = 0;
487 function getConnections() {
488 return connectionDeserializationComplete_.then(function() {
489 return new Map(connections_);
493 function getConnection(id) {
494 return getConnections().then(function(connections) {
495 if (!connections.has(id))
496 throw new Error('Serial connection not found.');
497 return connections.get(id);
501 function allocateConnectionId() {
502 return connectionDeserializationComplete_.then(function() {
503 return nextConnectionId_++;
508 getDevices: getDevices,
509 createConnection: Connection.create,
510 getConnection: getConnection,
511 getConnections: getConnections,
513 Connection: Connection,