1 | 'use strict';
|
2 |
|
3 | var _ = require('lodash');
|
4 | var util = require('util');
|
5 | var when = require('when');
|
6 | var EventEmitter = require('events').EventEmitter;
|
7 |
|
8 | function resolveCallback(resolve, reject){
|
9 | return function(result){
|
10 | if(chrome.runtime.lastError){
|
11 | console.log('error', chrome.runtime.lastError);
|
12 | reject(new Error(chrome.runtime.lastError.message));
|
13 | }else{
|
14 | resolve(result);
|
15 | }
|
16 | };
|
17 | }
|
18 |
|
19 | function promisify(api){
|
20 | return _.reduce(api, function(result, method, name){
|
21 | if(_.isFunction(method)){
|
22 | result[name] = function(){
|
23 | var args = _.toArray(arguments);
|
24 | return when.promise(function(resolve, reject){
|
25 | method.apply(api, args.concat(resolveCallback(resolve, reject)));
|
26 | });
|
27 | };
|
28 | }
|
29 | return result;
|
30 | }, {});
|
31 | }
|
32 |
|
33 | var serial = promisify(chrome.serial);
|
34 |
|
35 | function toBuffer(ab) {
|
36 | var buffer = new Buffer(ab.byteLength);
|
37 | var view = new Uint8Array(ab);
|
38 | for (var i = 0; i < buffer.length; ++i) {
|
39 | buffer[i] = view[i];
|
40 | }
|
41 | return buffer;
|
42 | }
|
43 |
|
44 |
|
45 | function str2ab(str) {
|
46 | var buf = new ArrayBuffer(str.length);
|
47 | var bufView = new Uint8Array(buf);
|
48 | for (var i = 0; i < str.length; i++) {
|
49 | bufView[i] = str.charCodeAt(i);
|
50 | }
|
51 | return buf;
|
52 | }
|
53 |
|
54 |
|
55 | function buffer2ArrayBuffer(buffer) {
|
56 | var buf = new ArrayBuffer(buffer.length);
|
57 | var bufView = new Uint8Array(buf);
|
58 | for (var i = 0; i < buffer.length; i++) {
|
59 | bufView[i] = buffer[i];
|
60 | }
|
61 | return buf;
|
62 | }
|
63 |
|
64 |
|
65 | function Transport(options) {
|
66 | EventEmitter.call(this);
|
67 |
|
68 | this._connectionId = -1;
|
69 | this._paused = false;
|
70 | this.autoRecover = true;
|
71 |
|
72 | this._resuming = false;
|
73 |
|
74 | if(!options.path){
|
75 | throw new Error('Not path in Transport options');
|
76 | }
|
77 |
|
78 | this._options = _.cloneDeep(options);
|
79 |
|
80 | this.onError = this._onError.bind(this);
|
81 | this.onReceive = this._onReceive.bind(this);
|
82 | }
|
83 |
|
84 | util.inherits(Transport, EventEmitter);
|
85 |
|
86 | Transport.prototype._onReceive = function onReceive(info){
|
87 | this.emit('data', toBuffer(info.data));
|
88 | };
|
89 |
|
90 | Transport.prototype._onError = function onError(err){
|
91 | var self = this;
|
92 |
|
93 | switch(err.error){
|
94 | case 'disconnected':
|
95 | case 'device_lost':
|
96 | if(self._connectionId >= 0){
|
97 |
|
98 | self.close();
|
99 | }
|
100 | break;
|
101 | case 'timeout':
|
102 |
|
103 | break;
|
104 | case 'frame_error':
|
105 | case 'system_error':
|
106 | case 'overrun':
|
107 | case 'buffer_overflow':
|
108 | case 'parity_error':
|
109 | case 'break':
|
110 | self._paused = true;
|
111 | if(self.autoRecover){
|
112 |
|
113 | self.unpause();
|
114 | }
|
115 | break;
|
116 | }
|
117 | };
|
118 |
|
119 | Transport.prototype.isOpen = function isOpen(){
|
120 | return this._connectionId >= 0;
|
121 | };
|
122 |
|
123 | Transport.prototype.isPaused = function isPaused(){
|
124 | return this._paused;
|
125 | };
|
126 |
|
127 | Transport.prototype.open = function(){
|
128 | if(this.isOpen()){
|
129 | return when.resolve(true);
|
130 | }
|
131 |
|
132 | var self = this;
|
133 | var path = this._options.path;
|
134 | var opts = {
|
135 | bitrate: this._options.baudrate || 9600
|
136 | };
|
137 |
|
138 | return serial.connect(path, opts)
|
139 | .then(function(connInfo){
|
140 | self._connectionId = connInfo.connectionId;
|
141 | chrome.serial.onReceiveError.addListener(self.onError);
|
142 | chrome.serial.onReceive.addListener(self.onReceive);
|
143 | self.emit('open');
|
144 | });
|
145 | };
|
146 |
|
147 | Transport.prototype.close = function(){
|
148 | var self = this;
|
149 |
|
150 | return serial.disconnect(self._connectionId)
|
151 | .ensure(function(){
|
152 | self._connectionId = -1;
|
153 | chrome.serial.onReceiveError.removeListener(self.onError);
|
154 | chrome.serial.onReceive.removeListener(self.onReceive);
|
155 | self.emit('close');
|
156 | });
|
157 | };
|
158 |
|
159 | Transport.prototype.set = function(options){
|
160 | return serial.setControlSignals(this._connectionId, options);
|
161 | };
|
162 |
|
163 | Transport.prototype.setBreak = function(){
|
164 | return serial.setBreak(this._connectionId);
|
165 | };
|
166 |
|
167 | Transport.prototype.clearBreak = function(){
|
168 | return serial.clearBreak(this._connectionId);
|
169 | };
|
170 |
|
171 | Transport.prototype.flush = function(){
|
172 | return serial.flush(this._connectionId);
|
173 | };
|
174 |
|
175 | Transport.prototype.pause = function(){
|
176 | var self = this;
|
177 | return serial.setPaused(self._connectionId, true)
|
178 | .then(function(){
|
179 | self._paused = true;
|
180 | self._resuming = false;
|
181 | });
|
182 | };
|
183 |
|
184 | Transport.prototype.unpause = function(){
|
185 | var self = this;
|
186 | self._resuming = serial.setPaused(self._connectionId, false)
|
187 | .then(function(){
|
188 | self._paused = false;
|
189 | self._resuming = false;
|
190 | });
|
191 | return self._resuming;
|
192 | };
|
193 |
|
194 | Transport.prototype.send = function write(data){
|
195 | var self = this;
|
196 |
|
197 | if (typeof data === 'string') {
|
198 | data = str2ab(buffer);
|
199 | }
|
200 | if (data instanceof ArrayBuffer === false) {
|
201 | data = buffer2ArrayBuffer(data);
|
202 | }
|
203 |
|
204 | return serial.send(self._connectionId, data);
|
205 | };
|
206 |
|
207 | Transport.listPorts = function(){
|
208 | return serial.getDevices()
|
209 | .then(function(devices){
|
210 | return _.pluck(devices, 'path');
|
211 | });
|
212 | };
|
213 |
|
214 | module.exports = Transport;
|