Advertisement
Guest User

Untitled

a guest
Oct 19th, 2019
181
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.21 KB | None | 0 0
  1. /**
  2. * Bluetooth Terminal class.
  3. */
  4. class BluetoothTerminal {
  5. /**
  6. * Create preconfigured Bluetooth Terminal instance.
  7. * @param {!(number|string)} [serviceUuid=0xFFE0] - Service UUID
  8. * @param {!(number|string)} [characteristicUuid=0xFFE1] - Characteristic UUID
  9. * @param {string} [receiveSeparator='\n'] - Receive separator
  10. * @param {string} [sendSeparator='\n'] - Send separator
  11. */
  12. constructor(serviceUuid = 0xFFE0, characteristicUuid = 0xFFE1,
  13. receiveSeparator = '\n', sendSeparator = '\n') {
  14. // Used private variables.
  15. this._receiveBuffer = ''; // Buffer containing not separated data.
  16. this._maxCharacteristicValueLength = 20; // Max characteristic value length.
  17. this._device = null; // Device object cache.
  18. this._characteristic = null; // Characteristic object cache.
  19.  
  20. // Bound functions used to add and remove appropriate event handlers.
  21. this._boundHandleDisconnection = this._handleDisconnection.bind(this);
  22. this._boundHandleCharacteristicValueChanged =
  23. this._handleCharacteristicValueChanged.bind(this);
  24.  
  25. serviceUuid = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
  26. characteristicUuid = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
  27. // Configure with specified parameters.
  28. this.setServiceUuid(serviceUuid);
  29. this.setCharacteristicUuid(characteristicUuid);
  30. this.setReceiveSeparator(receiveSeparator);
  31. this.setSendSeparator(sendSeparator);
  32. }
  33.  
  34. /**
  35. * Set number or string representing service UUID used.
  36. * @param {!(number|string)} uuid - Service UUID
  37. */
  38. setServiceUuid(uuid) {
  39. if (!Number.isInteger(uuid) &&
  40. !(typeof uuid === 'string' || uuid instanceof String)) {
  41. throw new Error('UUID type is neither a number nor a string');
  42. }
  43.  
  44. if (!uuid) {
  45. throw new Error('UUID cannot be a null');
  46. }
  47.  
  48. this._serviceUuid = uuid;
  49. }
  50.  
  51. /**
  52. * Set number or string representing characteristic UUID used.
  53. * @param {!(number|string)} uuid - Characteristic UUID
  54. */
  55. setCharacteristicUuid(uuid) {
  56. if (!Number.isInteger(uuid) &&
  57. !(typeof uuid === 'string' || uuid instanceof String)) {
  58. throw new Error('UUID type is neither a number nor a string');
  59. }
  60.  
  61. if (!uuid) {
  62. throw new Error('UUID cannot be a null');
  63. }
  64.  
  65. this._characteristicUuid = uuid;
  66. }
  67.  
  68. /**
  69. * Set character representing separator for data coming from the connected
  70. * device, end of line for example.
  71. * @param {string} separator - Receive separator with length equal to one
  72. * character
  73. */
  74. setReceiveSeparator(separator) {
  75. if (!(typeof separator === 'string' || separator instanceof String)) {
  76. throw new Error('Separator type is not a string');
  77. }
  78.  
  79. if (separator.length !== 1) {
  80. throw new Error('Separator length must be equal to one character');
  81. }
  82.  
  83. this._receiveSeparator = separator;
  84. }
  85.  
  86. /**
  87. * Set string representing separator for data coming to the connected
  88. * device, end of line for example.
  89. * @param {string} separator - Send separator
  90. */
  91. setSendSeparator(separator) {
  92. if (!(typeof separator === 'string' || separator instanceof String)) {
  93. throw new Error('Separator type is not a string');
  94. }
  95.  
  96. if (separator.length !== 1) {
  97. throw new Error('Separator length must be equal to one character');
  98. }
  99.  
  100. this._sendSeparator = separator;
  101. }
  102.  
  103. /**
  104. * Launch Bluetooth device chooser and connect to the selected device.
  105. * @return {Promise} Promise which will be fulfilled when notifications will
  106. * be started or rejected if something went wrong
  107. */
  108. connect() {
  109. return this._connectToDevice(this._device);
  110. }
  111.  
  112. /**
  113. * Disconnect from the connected device.
  114. */
  115. disconnect() {
  116. this._disconnectFromDevice(this._device);
  117.  
  118. if (this._characteristic) {
  119. this._characteristic.removeEventListener('characteristicvaluechanged',
  120. this._boundHandleCharacteristicValueChanged);
  121. this._characteristic = null;
  122. }
  123.  
  124. this._device = null;
  125. }
  126.  
  127. /**
  128. * Data receiving handler which called whenever the new data comes from
  129. * the connected device, override it to handle incoming data.
  130. * @param {string} data - Data
  131. */
  132. receive(data) {
  133. // Handle incoming data.
  134.  
  135. }
  136.  
  137. /**
  138. * Send data to the connected device.
  139. * @param {string} data - Data
  140. * @return {Promise} Promise which will be fulfilled when data will be sent or
  141. * rejected if something went wrong
  142. */
  143. send(data) {
  144. // Convert data to the string using global object.
  145. data = String(data || '');
  146.  
  147. // Return rejected promise immediately if data is empty.
  148. if (!data) {
  149. return Promise.reject(new Error('Data must be not empty'));
  150. }
  151.  
  152. data += this._sendSeparator;
  153.  
  154. // Split data to chunks by max characteristic value length.
  155. const chunks = this.constructor._splitByLength(data,
  156. this._maxCharacteristicValueLength);
  157.  
  158. // Return rejected promise immediately if there is no connected device.
  159. if (!this._characteristic) {
  160. return Promise.reject(new Error('There is no connected device'));
  161. }
  162.  
  163. // Write first chunk to the characteristic immediately.
  164. let promise = this._writeToCharacteristic(this._characteristic, chunks[0]);
  165.  
  166. // Iterate over chunks if there are more than one of it.
  167. for (let i = 1; i < chunks.length; i++) {
  168. // Chain new promise.
  169. promise = promise.then(() => new Promise((resolve, reject) => {
  170. // Reject promise if the device has been disconnected.
  171. if (!this._characteristic) {
  172. reject(new Error('Device has been disconnected'));
  173. }
  174.  
  175. // Write chunk to the characteristic and resolve the promise.
  176. this._writeToCharacteristic(this._characteristic, chunks[i]).
  177. then(resolve).
  178. catch(reject);
  179. }));
  180. }
  181.  
  182. return promise;
  183. }
  184.  
  185. /**
  186. * Get the connected device name.
  187. * @return {string} Device name or empty string if not connected
  188. */
  189. getDeviceName() {
  190. if (!this._device) {
  191. return '';
  192. }
  193.  
  194. return this._device.name;
  195. }
  196.  
  197. /**
  198. * Connect to device.
  199. * @param {Object} device
  200. * @return {Promise}
  201. * @private
  202. */
  203. _connectToDevice(device) {
  204. return (device ? Promise.resolve(device) : this._requestBluetoothDevice()).
  205. then((device) => this._connectDeviceAndCacheCharacteristic(device)).
  206. then((characteristic) => this._startNotifications(characteristic)).
  207. catch((error) => {
  208. this._log(error);
  209. return Promise.reject(error);
  210. });
  211. }
  212.  
  213. /**
  214. * Disconnect from device.
  215. * @param {Object} device
  216. * @private
  217. */
  218. _disconnectFromDevice(device) {
  219. if (!device) {
  220. return;
  221. }
  222.  
  223. this._log('Disconnecting from "' + device.name + '" bluetooth device...');
  224.  
  225. device.removeEventListener('gattserverdisconnected',
  226. this._boundHandleDisconnection);
  227.  
  228. if (!device.gatt.connected) {
  229. this._log('"' + device.name +
  230. '" bluetooth device is already disconnected');
  231. return;
  232. }
  233.  
  234. device.gatt.disconnect();
  235.  
  236. this._log('"' + device.name + '" bluetooth device disconnected');
  237. }
  238.  
  239. /**
  240. * Request bluetooth device.
  241. * @return {Promise}
  242. * @private
  243. */
  244. _requestBluetoothDevice() {
  245. this._log('Requesting bluetooth device...');
  246.  
  247. return navigator.bluetooth.requestDevice({
  248. filters: [{
  249. name: 'UART Service'
  250. }],
  251. optionalServices: ['6e400001-b5a3-f393-e0a9-e50e24dcca9e',
  252. '6e400002-b5a3-f393-e0a9-e50e24dcca9e',
  253. '6e400003-b5a3-f393-e0a9-e50e24dcca9e']
  254. }).
  255. then((device) => {
  256. this._log('"' + device.name + '" bluetooth device selected');
  257.  
  258. this._device = device; // Remember device.
  259. this._device.addEventListener('gattserverdisconnected',
  260. this._boundHandleDisconnection);
  261.  
  262. return this._device;
  263. });
  264.  
  265.  
  266. /*
  267. return navigator.bluetooth.requestDevice({
  268. filters: [{services: [this._serviceUuid]}],
  269. }).
  270. then((device) => {
  271. this._log('"' + device.name + '" bluetooth device selected');
  272.  
  273. this._device = device; // Remember device.
  274. this._device.addEventListener('gattserverdisconnected',
  275. this._boundHandleDisconnection);
  276.  
  277. return this._device;
  278. });
  279. */
  280. }
  281.  
  282. /**
  283. * Connect device and cache characteristic.
  284. * @param {Object} device
  285. * @return {Promise}
  286. * @private
  287. */
  288. _connectDeviceAndCacheCharacteristic(device) {
  289. // Check remembered characteristic.
  290. if (device.gatt.connected && this._characteristic) {
  291. return Promise.resolve(this._characteristic);
  292. }
  293.  
  294. this._log('Connecting to GATT server...');
  295.  
  296. return device.gatt.connect().
  297. then((server) => {
  298. this._log('GATT server connected', 'Getting service...');
  299.  
  300. return server.getPrimaryService(this._serviceUuid);
  301. }).
  302. then((service) => {
  303. this._log('Service found', 'Getting characteristic...' + this._characteristicUuid);
  304.  
  305. return service.getCharacteristic(this._characteristicUuid);
  306. }).
  307. then((characteristic) => {
  308. this._log('Characteristic found');
  309.  
  310. this._characteristic = characteristic; // Remember characteristic.
  311.  
  312. return this._characteristic;
  313. });
  314. }
  315.  
  316. /**
  317. * Start notifications.
  318. * @param {Object} characteristic
  319. * @return {Promise}
  320. * @private
  321. */
  322. _startNotifications(characteristic) {
  323. this._log('Starting notifications...');
  324.  
  325. return characteristic.startNotifications().
  326. then(() => {
  327. this._log('Notifications started');
  328.  
  329. characteristic.addEventListener('characteristicvaluechanged',
  330. this._boundHandleCharacteristicValueChanged);
  331. });
  332. }
  333.  
  334. /**
  335. * Stop notifications.
  336. * @param {Object} characteristic
  337. * @return {Promise}
  338. * @private
  339. */
  340. _stopNotifications(characteristic) {
  341. this._log('Stopping notifications...');
  342.  
  343. return characteristic.stopNotifications().
  344. then(() => {
  345. this._log('Notifications stopped');
  346.  
  347. characteristic.removeEventListener('characteristicvaluechanged',
  348. this._boundHandleCharacteristicValueChanged);
  349. });
  350. }
  351.  
  352. /**
  353. * Handle disconnection.
  354. * @param {Object} event
  355. * @private
  356. */
  357. _handleDisconnection(event) {
  358. const device = event.target;
  359.  
  360. this._log('"' + device.name +
  361. '" bluetooth device disconnected, trying to reconnect...');
  362.  
  363. this._connectDeviceAndCacheCharacteristic(device).
  364. then((characteristic) => this._startNotifications(characteristic)).
  365. catch((error) => this._log(error));
  366. }
  367.  
  368. /**
  369. * Handle characteristic value changed.
  370. * @param {Object} event
  371. * @private
  372. */
  373. _handleCharacteristicValueChanged(event) {
  374. const value = new TextDecoder().decode(event.target.value);
  375.  
  376. for (const c of value) {
  377. if (c === this._receiveSeparator) {
  378. const data = this._receiveBuffer.trim();
  379. this._receiveBuffer = '';
  380.  
  381. if (data) {
  382. this.receive(data);
  383. }
  384. } else {
  385. this._receiveBuffer += c;
  386. }
  387. }
  388. }
  389.  
  390. /**
  391. * Write to characteristic.
  392. * @param {Object} characteristic
  393. * @param {string} data
  394. * @return {Promise}
  395. * @private
  396. */
  397. _writeToCharacteristic(characteristic, data) {
  398. return characteristic.writeValue(new TextEncoder().encode(data));
  399. }
  400.  
  401. /**
  402. * Log.
  403. * @param {Array} messages
  404. * @private
  405. */
  406. _log(...messages) {
  407. console.log(...messages); // eslint-disable-line no-console
  408. }
  409.  
  410. /**
  411. * Split by length.
  412. * @param {string} string
  413. * @param {number} length
  414. * @return {Array}
  415. * @private
  416. */
  417. static _splitByLength(string, length) {
  418. return string.match(new RegExp('(.|[\r\n]){1,' + length + '}', 'g'));
  419. }
  420. }
  421.  
  422. // Export class as a module to support requiring.
  423. /* istanbul ignore next */
  424. if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
  425. module.exports = BluetoothTerminal;
  426. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement