2551e1d3fb9df9c09cccee2fe3cff2b701aaa246
[platform/upstream/iotjs.git] / tools / src / js / ble_hci_socket_bindings.js
1 /* Copyright 2016-present Samsung Electronics Co., Ltd. and other contributors
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 /* Copyright (C) 2015 Sandeep Mistry sandeep.mistry@gmail.com
17  *
18  * Permission is hereby granted, free of charge, to any person obtaining a copy
19  * of this software and associated documentation files (the "Software"), to deal
20  * in the Software without restriction, including without limitation the rights
21  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22  * copies of the Software, and to permit persons to whom the Software is
23  * furnished to do so, subject to the following conditions:
24  *
25  * The above copyright notice and this permission notice shall be included in
26  * all copies or substantial portions of the Software.
27  *
28  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
34  * SOFTWARE.
35  */
36
37 var debug = console.log; //requir('debug')('ble_hci-socket_bindings');
38
39 var events = require('events');
40 var util = require('util');
41
42 var AclStream = require('ble_hci_socket_acl_stream');
43 var Hci = require('ble_hci_socket_hci');
44 var Gap = require('ble_hci_socket_gap');
45 var Gatt = require('ble_hci_socket_gatt');
46
47 var BlenoBindings = function() {
48   this._state = null;
49
50   this._advertising = false;
51
52   this._hci = new Hci();
53   this._gap = new Gap(this._hci);
54   this._gatt = new Gatt(this._hci);
55
56   this._address = null;
57   this._handle = null;
58   this._aclStream = null;
59 };
60
61 util.inherits(BlenoBindings, events.EventEmitter);
62
63 BlenoBindings.prototype.startAdvertising = function(name, serviceUuids) {
64   this._advertising = true;
65
66   this._gap.startAdvertising(name, serviceUuids);
67 };
68
69 BlenoBindings.prototype.startAdvertisingIBeacon = function(data) {
70   this._advertising = true;
71
72   this._gap.startAdvertisingIBeacon(data);
73 };
74
75 BlenoBindings.prototype.startAdvertisingWithEIRData = function(advertisementData, scanData) {
76   this._advertising = true;
77
78   this._gap.startAdvertisingWithEIRData(advertisementData, scanData);
79 };
80
81 BlenoBindings.prototype.stopAdvertising = function() {
82   this._advertising = false;
83
84   this._gap.stopAdvertising();
85 };
86
87 BlenoBindings.prototype.setServices = function(services) {
88   this._gatt.setServices(services);
89
90   this.emit('servicesSet');
91 };
92
93 BlenoBindings.prototype.disconnect = function() {
94   if (this._handle) {
95     debug('disconnect by server');
96
97     this._hci.disconnect(this._handle);
98   }
99 };
100
101 BlenoBindings.prototype.updateRssi = function() {
102   if (this._handle) {
103     this._hci.readRssi(this._handle);
104   }
105 };
106
107 BlenoBindings.prototype.init = function() {
108   this.onSigIntBinded = this.onSigInt.bind(this);
109
110   process.on('SIGINT', this.onSigIntBinded);
111   process.on('exit', this.onExit.bind(this));
112
113   this._gap.on('advertisingStart', this.onAdvertisingStart.bind(this));
114   this._gap.on('advertisingStop', this.onAdvertisingStop.bind(this));
115
116   this._gatt.on('mtuChange', this.onMtuChange.bind(this));
117
118   this._hci.on('stateChange', this.onStateChange.bind(this));
119   this._hci.on('addressChange', this.onAddressChange.bind(this));
120   this._hci.on('readLocalVersion', this.onReadLocalVersion.bind(this));
121
122   this._hci.on('leConnComplete', this.onLeConnComplete.bind(this));
123   this._hci.on('leConnUpdateComplete', this.onLeConnUpdateComplete.bind(this));
124   this._hci.on('rssiRead', this.onRssiRead.bind(this));
125   this._hci.on('disconnComplete', this.onDisconnComplete.bind(this));
126   this._hci.on('encryptChange', this.onEncryptChange.bind(this));
127   this._hci.on('leLtkNegReply', this.onLeLtkNegReply.bind(this));
128   this._hci.on('aclDataPkt', this.onAclDataPkt.bind(this));
129
130   this.emit('platform', process.platform);
131
132   this._hci.init();
133 };
134
135 BlenoBindings.prototype.onStateChange = function(state) {
136   if (this._state === state) {
137     return;
138   }
139   this._state = state;
140
141   if (state === 'unauthorized') {
142     console.log('bleno warning: adapter state unauthorized, please run as root or with sudo');
143     console.log('               or see README for information on running without root/sudo:');
144     console.log('               https://github.com/sandeepmistry/bleno#running-on-linux');
145   } else if (state === 'unsupported') {
146     console.log('bleno warning: adapter does not support Bluetooth Low Energy (BLE, Bluetooth Smart).');
147     console.log('               Try to run with environment variable:');
148     console.log('               [sudo] BLENO_HCI_DEVICE_ID=x node ...');
149   }
150
151   this.emit('stateChange', state);
152 };
153
154 BlenoBindings.prototype.onAddressChange = function(address) {
155   this.emit('addressChange', address);
156 };
157
158 BlenoBindings.prototype.onReadLocalVersion = function(hciVer, hciRev, lmpVer, manufacturer, lmpSubVer) {
159   if (manufacturer === 93) {
160     // Realtek Semiconductor Corporation
161     this._gatt.maxMtu = 23;
162   }
163 };
164
165 BlenoBindings.prototype.onAdvertisingStart = function(error) {
166   this.emit('advertisingStart', error);
167 };
168
169 BlenoBindings.prototype.onAdvertisingStop = function() {
170   this.emit('advertisingStop');
171 };
172
173 BlenoBindings.prototype.onLeConnComplete = function(status, handle, role, addressType, address, interval, latency, supervisionTimeout, masterClockAccuracy) {
174   if (role !== 1) {
175     // not slave, ignore
176     return;
177   }
178
179   this._address = address;
180   this._handle = handle;
181   this._aclStream = new AclStream(this._hci, handle, this._hci.addressType, this._hci.address, addressType, address);
182   this._gatt.setAclStream(this._aclStream);
183
184   this.emit('accept', address);
185 };
186
187 BlenoBindings.prototype.onLeConnUpdateComplete = function(handle, interval, latency, supervisionTimeout) {
188   // no-op
189 };
190
191 BlenoBindings.prototype.onDisconnComplete = function(handle, reason) {
192   if (this._aclStream) {
193     this._aclStream.push(null, null);
194   }
195
196   var address = this._address;
197
198   this._address = null;
199   this._handle = null;
200   this._aclStream = null;
201
202   if (address) {
203     this.emit('disconnect', address); // TODO: use reason
204   }
205
206   if (this._advertising) {
207     this._gap.restartAdvertising();
208   }
209 };
210
211 BlenoBindings.prototype.onEncryptChange = function(handle, encrypt) {
212   if (this._handle === handle && this._aclStream) {
213     this._aclStream.pushEncrypt(encrypt);
214   }
215 };
216
217 BlenoBindings.prototype.onLeLtkNegReply = function(handle) {
218   if (this._handle === handle && this._aclStream) {
219     this._aclStream.pushLtkNegReply();
220   }
221 };
222
223 BlenoBindings.prototype.onMtuChange = function(mtu) {
224   this.emit('mtuChange', mtu);
225 };
226
227 BlenoBindings.prototype.onRssiRead = function(handle, rssi) {
228   this.emit('rssiUpdate', rssi);
229 };
230
231 BlenoBindings.prototype.onAclDataPkt = function(handle, cid, data) {
232   if (this._handle === handle && this._aclStream) {
233     this._aclStream.push(cid, data);
234   }
235 };
236
237 BlenoBindings.prototype.onSigInt = function() {
238   var sigIntListeners = process.listeners('SIGINT');
239
240   if (sigIntListeners[sigIntListeners.length - 1] === this.onSigIntBinded) {
241     // we are the last listener, so exit
242     // this will trigger onExit, and clean up
243     process.exit(1);
244   }
245 };
246
247 BlenoBindings.prototype.onExit = function() {
248   this._gap.stopAdvertising();
249
250   this.disconnect();
251 };
252
253 module.exports = new BlenoBindings();