Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /**
- * Bluetooth Terminal class.
- */
- class BluetoothTerminal {
- /**
- * Create preconfigured Bluetooth Terminal instance.
- * @param {!(number|string)} [serviceUuid=0xFFE0] - Service UUID
- * @param {!(number|string)} [characteristicUuid=0xFFE1] - Characteristic UUID
- * @param {string} [receiveSeparator='\n'] - Receive separator
- * @param {string} [sendSeparator='\n'] - Send separator
- */
- constructor(serviceUuid = 0xFFE0, characteristicUuid = 0xFFE1,
- receiveSeparator = '\n', sendSeparator = '\n') {
- // Used private variables.
- this._receiveBuffer = ''; // Buffer containing not separated data.
- this._maxCharacteristicValueLength = 20; // Max characteristic value length.
- this._device = null; // Device object cache.
- this._characteristic = null; // Characteristic object cache.
- // Bound functions used to add and remove appropriate event handlers.
- this._boundHandleDisconnection = this._handleDisconnection.bind(this);
- this._boundHandleCharacteristicValueChanged =
- this._handleCharacteristicValueChanged.bind(this);
- serviceUuid = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
- characteristicUuid = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
- // Configure with specified parameters.
- this.setServiceUuid(serviceUuid);
- this.setCharacteristicUuid(characteristicUuid);
- this.setReceiveSeparator(receiveSeparator);
- this.setSendSeparator(sendSeparator);
- }
- /**
- * Set number or string representing service UUID used.
- * @param {!(number|string)} uuid - Service UUID
- */
- setServiceUuid(uuid) {
- if (!Number.isInteger(uuid) &&
- !(typeof uuid === 'string' || uuid instanceof String)) {
- throw new Error('UUID type is neither a number nor a string');
- }
- if (!uuid) {
- throw new Error('UUID cannot be a null');
- }
- this._serviceUuid = uuid;
- }
- /**
- * Set number or string representing characteristic UUID used.
- * @param {!(number|string)} uuid - Characteristic UUID
- */
- setCharacteristicUuid(uuid) {
- if (!Number.isInteger(uuid) &&
- !(typeof uuid === 'string' || uuid instanceof String)) {
- throw new Error('UUID type is neither a number nor a string');
- }
- if (!uuid) {
- throw new Error('UUID cannot be a null');
- }
- this._characteristicUuid = uuid;
- }
- /**
- * Set character representing separator for data coming from the connected
- * device, end of line for example.
- * @param {string} separator - Receive separator with length equal to one
- * character
- */
- setReceiveSeparator(separator) {
- if (!(typeof separator === 'string' || separator instanceof String)) {
- throw new Error('Separator type is not a string');
- }
- if (separator.length !== 1) {
- throw new Error('Separator length must be equal to one character');
- }
- this._receiveSeparator = separator;
- }
- /**
- * Set string representing separator for data coming to the connected
- * device, end of line for example.
- * @param {string} separator - Send separator
- */
- setSendSeparator(separator) {
- if (!(typeof separator === 'string' || separator instanceof String)) {
- throw new Error('Separator type is not a string');
- }
- if (separator.length !== 1) {
- throw new Error('Separator length must be equal to one character');
- }
- this._sendSeparator = separator;
- }
- /**
- * Launch Bluetooth device chooser and connect to the selected device.
- * @return {Promise} Promise which will be fulfilled when notifications will
- * be started or rejected if something went wrong
- */
- connect() {
- return this._connectToDevice(this._device);
- }
- /**
- * Disconnect from the connected device.
- */
- disconnect() {
- this._disconnectFromDevice(this._device);
- if (this._characteristic) {
- this._characteristic.removeEventListener('characteristicvaluechanged',
- this._boundHandleCharacteristicValueChanged);
- this._characteristic = null;
- }
- this._device = null;
- }
- /**
- * Data receiving handler which called whenever the new data comes from
- * the connected device, override it to handle incoming data.
- * @param {string} data - Data
- */
- receive(data) {
- // Handle incoming data.
- }
- /**
- * Send data to the connected device.
- * @param {string} data - Data
- * @return {Promise} Promise which will be fulfilled when data will be sent or
- * rejected if something went wrong
- */
- send(data) {
- // Convert data to the string using global object.
- data = String(data || '');
- // Return rejected promise immediately if data is empty.
- if (!data) {
- return Promise.reject(new Error('Data must be not empty'));
- }
- data += this._sendSeparator;
- // Split data to chunks by max characteristic value length.
- const chunks = this.constructor._splitByLength(data,
- this._maxCharacteristicValueLength);
- // Return rejected promise immediately if there is no connected device.
- if (!this._characteristic) {
- return Promise.reject(new Error('There is no connected device'));
- }
- // Write first chunk to the characteristic immediately.
- let promise = this._writeToCharacteristic(this._characteristic, chunks[0]);
- // Iterate over chunks if there are more than one of it.
- for (let i = 1; i < chunks.length; i++) {
- // Chain new promise.
- promise = promise.then(() => new Promise((resolve, reject) => {
- // Reject promise if the device has been disconnected.
- if (!this._characteristic) {
- reject(new Error('Device has been disconnected'));
- }
- // Write chunk to the characteristic and resolve the promise.
- this._writeToCharacteristic(this._characteristic, chunks[i]).
- then(resolve).
- catch(reject);
- }));
- }
- return promise;
- }
- /**
- * Get the connected device name.
- * @return {string} Device name or empty string if not connected
- */
- getDeviceName() {
- if (!this._device) {
- return '';
- }
- return this._device.name;
- }
- /**
- * Connect to device.
- * @param {Object} device
- * @return {Promise}
- * @private
- */
- _connectToDevice(device) {
- return (device ? Promise.resolve(device) : this._requestBluetoothDevice()).
- then((device) => this._connectDeviceAndCacheCharacteristic(device)).
- then((characteristic) => this._startNotifications(characteristic)).
- catch((error) => {
- this._log(error);
- return Promise.reject(error);
- });
- }
- /**
- * Disconnect from device.
- * @param {Object} device
- * @private
- */
- _disconnectFromDevice(device) {
- if (!device) {
- return;
- }
- this._log('Disconnecting from "' + device.name + '" bluetooth device...');
- device.removeEventListener('gattserverdisconnected',
- this._boundHandleDisconnection);
- if (!device.gatt.connected) {
- this._log('"' + device.name +
- '" bluetooth device is already disconnected');
- return;
- }
- device.gatt.disconnect();
- this._log('"' + device.name + '" bluetooth device disconnected');
- }
- /**
- * Request bluetooth device.
- * @return {Promise}
- * @private
- */
- _requestBluetoothDevice() {
- this._log('Requesting bluetooth device...');
- return navigator.bluetooth.requestDevice({
- filters: [{
- name: 'UART Service'
- }],
- optionalServices: ['6e400001-b5a3-f393-e0a9-e50e24dcca9e',
- '6e400002-b5a3-f393-e0a9-e50e24dcca9e',
- '6e400003-b5a3-f393-e0a9-e50e24dcca9e']
- }).
- then((device) => {
- this._log('"' + device.name + '" bluetooth device selected');
- this._device = device; // Remember device.
- this._device.addEventListener('gattserverdisconnected',
- this._boundHandleDisconnection);
- return this._device;
- });
- /*
- return navigator.bluetooth.requestDevice({
- filters: [{services: [this._serviceUuid]}],
- }).
- then((device) => {
- this._log('"' + device.name + '" bluetooth device selected');
- this._device = device; // Remember device.
- this._device.addEventListener('gattserverdisconnected',
- this._boundHandleDisconnection);
- return this._device;
- });
- */
- }
- /**
- * Connect device and cache characteristic.
- * @param {Object} device
- * @return {Promise}
- * @private
- */
- _connectDeviceAndCacheCharacteristic(device) {
- // Check remembered characteristic.
- if (device.gatt.connected && this._characteristic) {
- return Promise.resolve(this._characteristic);
- }
- this._log('Connecting to GATT server...');
- return device.gatt.connect().
- then((server) => {
- this._log('GATT server connected', 'Getting service...');
- return server.getPrimaryService(this._serviceUuid);
- }).
- then((service) => {
- this._log('Service found', 'Getting characteristic...' + this._characteristicUuid);
- return service.getCharacteristic(this._characteristicUuid);
- }).
- then((characteristic) => {
- this._log('Characteristic found');
- this._characteristic = characteristic; // Remember characteristic.
- return this._characteristic;
- });
- }
- /**
- * Start notifications.
- * @param {Object} characteristic
- * @return {Promise}
- * @private
- */
- _startNotifications(characteristic) {
- this._log('Starting notifications...');
- return characteristic.startNotifications().
- then(() => {
- this._log('Notifications started');
- characteristic.addEventListener('characteristicvaluechanged',
- this._boundHandleCharacteristicValueChanged);
- });
- }
- /**
- * Stop notifications.
- * @param {Object} characteristic
- * @return {Promise}
- * @private
- */
- _stopNotifications(characteristic) {
- this._log('Stopping notifications...');
- return characteristic.stopNotifications().
- then(() => {
- this._log('Notifications stopped');
- characteristic.removeEventListener('characteristicvaluechanged',
- this._boundHandleCharacteristicValueChanged);
- });
- }
- /**
- * Handle disconnection.
- * @param {Object} event
- * @private
- */
- _handleDisconnection(event) {
- const device = event.target;
- this._log('"' + device.name +
- '" bluetooth device disconnected, trying to reconnect...');
- this._connectDeviceAndCacheCharacteristic(device).
- then((characteristic) => this._startNotifications(characteristic)).
- catch((error) => this._log(error));
- }
- /**
- * Handle characteristic value changed.
- * @param {Object} event
- * @private
- */
- _handleCharacteristicValueChanged(event) {
- const value = new TextDecoder().decode(event.target.value);
- for (const c of value) {
- if (c === this._receiveSeparator) {
- const data = this._receiveBuffer.trim();
- this._receiveBuffer = '';
- if (data) {
- this.receive(data);
- }
- } else {
- this._receiveBuffer += c;
- }
- }
- }
- /**
- * Write to characteristic.
- * @param {Object} characteristic
- * @param {string} data
- * @return {Promise}
- * @private
- */
- _writeToCharacteristic(characteristic, data) {
- return characteristic.writeValue(new TextEncoder().encode(data));
- }
- /**
- * Log.
- * @param {Array} messages
- * @private
- */
- _log(...messages) {
- console.log(...messages); // eslint-disable-line no-console
- }
- /**
- * Split by length.
- * @param {string} string
- * @param {number} length
- * @return {Array}
- * @private
- */
- static _splitByLength(string, length) {
- return string.match(new RegExp('(.|[\r\n]){1,' + length + '}', 'g'));
- }
- }
- // Export class as a module to support requiring.
- /* istanbul ignore next */
- if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
- module.exports = BluetoothTerminal;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement