UNPKG

@iotize/tap

Version:

IoTize Device client for Javascript

243 lines 19.9 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { ComProtocol, ConnectionState } from '@iotize/tap/protocol/api'; import { QueueComProtocol } from '@iotize/tap/protocol/core'; import { defer } from 'rxjs'; import { filter, first, map, share } from 'rxjs/operators'; import { BleConfig } from './ble-config'; import { BLEPacketBuilder } from './ble-packet-builder'; import { BLEPacketSplitter } from './ble-packet-splitter'; import { debug } from './debug'; import { BleComError } from './errors'; import { sanitizeUUID } from './util'; export const DEFAULT_BLE_OPTIONS = { mtu: BleConfig.maxPacketLengthWithoutOffset + 1, maximumBufferLength: 255, waitForWriteAcknowledge: true, preferedComServiceType: 'large-frame', sanitizeUUID: false, }; /** * BLE communication * * With ble communication, data is split into sub packets. * This class handles creation of packet chunks. * * - You must only implement the function to send one packet chunk writeLwm2mPacketChunk() * - */ export class UniversalBleProtocolAdapter extends QueueComProtocol { constructor(peripheral, bleOptions = {}) { super(); this.peripheral = peripheral; this._useSplitter = true; this._unexpectedBleDisconnection = this.peripheral.stateChange.pipe(filter((newState) => { const currentProtocolConnectionState = this.getConnectionState(); return (newState === ConnectionState.DISCONNECTED && currentProtocolConnectionState !== ConnectionState.DISCONNECTING && currentProtocolConnectionState !== ConnectionState.DISCONNECTED); }), share()); this.bleOptions = Object.assign(Object.assign({}, DEFAULT_BLE_OPTIONS), bleOptions); this.options.connect.timeout = 8000; this.options.send.timeout = 4000; this.options.disconnect.timeout = 8000; this._unexpectedBleDisconnection.subscribe(() => __awaiter(this, void 0, void 0, function* () { debug('unexpected BLE disconnection detected. Running proper BLE disconnection process'); yield this.disconnect() .toPromise() .catch((err) => { debug(`Proper BLE disconnection process failed with error: ${err.message}`); }); })); } get lwm2mCharc() { if (!this._lwm2mCharc) { this.setConnectionState(ConnectionState.DISCONNECTED); throw ComProtocol.Errors.notConnected({ protocol: this, }); } return this._lwm2mCharc; } sanitizeUUID(uuid) { return this.bleOptions.sanitizeUUID ? sanitizeUUID(uuid) : uuid; } _connect() { return defer(() => __awaiter(this, void 0, void 0, function* () { try { yield this.peripheral.connect(); this._lwm2mCharc = yield this.setupLwm2mCharacteristic(); } catch (err) { try { yield this.peripheral.disconnect(); } catch (err) { debug('Failed to propertly disconnect after connection failed', err.message); } throw err; } })).pipe(share()); } _disconnect() { return defer(() => __awaiter(this, void 0, void 0, function* () { if (this.peripheral) { try { yield this.peripheral.disconnect(); } catch (err) { console.warn(`Failed to properly disconnect from peripheral`, err); } } this._lwm2mCharc = undefined; })).pipe(share()); } setupLwm2mCharacteristic() { return __awaiter(this, void 0, void 0, function* () { const lwm2mServiceUUIDs = [ this.sanitizeUUID(BleConfig.services.lwm2m.service), ]; if (this.bleOptions.preferedComServiceType === 'large-frame') { lwm2mServiceUUIDs.push(this.sanitizeUUID(BleConfig.services.fastLwm2m.service)); } const serviceMap = yield this.peripheral.discoverServices(lwm2mServiceUUIDs); debug('Found services ', Object.keys(serviceMap).join(', '), 'asked for services: ', lwm2mServiceUUIDs.join(', ')); const charac = yield this._selectLwm2mCharacteristic(serviceMap); yield charac.enableNotifications(true); return charac; }); } _selectLwm2mCharacteristic(serviceMap) { return __awaiter(this, void 0, void 0, function* () { const largeFrameServiceUUID = this.sanitizeUUID(BleConfig.services.fastLwm2m.service); const legacyLwm2mServiceUUID = this.sanitizeUUID(BleConfig.services.lwm2m.service); if (this.bleOptions.preferedComServiceType === 'legacy' && serviceMap[legacyLwm2mServiceUUID]) { debug('Force usage of legacy lwm2m characteristic UUID: ' + legacyLwm2mServiceUUID); return this._getLegacyLwm2mCharacteristic(serviceMap[legacyLwm2mServiceUUID]); } else if (serviceMap[largeFrameServiceUUID]) { debug('Found fast lwm2m characteristic UUID: ' + largeFrameServiceUUID); return this._getLargeFrameLwm2mCharacteristic(serviceMap[largeFrameServiceUUID]); } else if (serviceMap[legacyLwm2mServiceUUID]) { debug('Found legacy lwm2m characteristic UUID: ' + legacyLwm2mServiceUUID); return this._getLegacyLwm2mCharacteristic(serviceMap[legacyLwm2mServiceUUID]); } else { debug(`No LwM2M service found. Available services: ${Object.keys(serviceMap).join(', ')}`); throw BleComError.serviceNotFound(legacyLwm2mServiceUUID); } }); } _getLargeFrameLwm2mCharacteristic(service) { return __awaiter(this, void 0, void 0, function* () { this.bleOptions.mtu = 255; // TODO read from device this._useSplitter = false; return yield service.getCharacteristic(this.sanitizeUUID(BleConfig.services.fastLwm2m.charac)); }); } _getLegacyLwm2mCharacteristic(service) { return __awaiter(this, void 0, void 0, function* () { this.bleOptions.mtu = BleConfig.maxPacketLengthWithoutOffset + 1; this._useSplitter = true; return yield service.getCharacteristic(this.sanitizeUUID(BleConfig.services.lwm2m.charac)); }); } read() { return __awaiter(this, void 0, void 0, function* () { // debug('read()...'); if (!this._readPromise) { this._readPromise = this._createReadPromise(); } return this._readPromise; }); } readUnit() { return __awaiter(this, void 0, void 0, function* () { try { const result = yield this.lwm2mCharc.data .pipe(first(), map((info) => info.data)) .toPromise(); if (!result) { return new Uint8Array(); } return result; } catch (err) { return Promise.reject(err); } }); } write(data) { return __awaiter(this, void 0, void 0, function* () { // debug('write()...'); this._readPromise = this._createReadPromise(); if (this.useSplitter) { const chunks = BLEPacketSplitter.wrapWithChecksum(data, this.chunkSize).getPackets(); for (const chunk of chunks) { yield this.writeUnit(chunk); } } else { return this.writeUnit(data); } }); } get useSplitter() { return this._useSplitter; } get chunkSize() { return this.bleOptions.mtu - 1; } writeUnit(data) { return __awaiter(this, void 0, void 0, function* () { try { if (data.length > this.bleOptions.mtu) { throw BleComError.writeSizeAboveMTU(data, this.bleOptions.mtu); } if (this.bleOptions.waitForWriteAcknowledge) { return this.lwm2mCharc.write(data, true); } else { this.lwm2mCharc.write(data, true).catch((err) => { console.warn(`Write error ignored`, err); }); } } catch (err) { return Promise.reject(err); } }); } _createReadPromise() { return __awaiter(this, void 0, void 0, function* () { // debug(`_createReadPromise()`); if (this.useSplitter) { const packetBuilder = new BLEPacketBuilder(this.bleOptions.maximumBufferLength); while (!packetBuilder.hasAllChunks()) { const chunk = yield this.readUnit(); packetBuilder.append(chunk); } if (packetBuilder.isChecksumValid()) { return packetBuilder.getData(); } else { throw BleComError.invalidBleChunkChecksum(packetBuilder); } } else { return this.readUnit(); } }); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidW5pdmVyc2FsLWJsZS1wcm90b2NvbC1hZGFwdGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vcHJvdG9jb2wvYmxlL2NvbW1vbi9zcmMvbGliL3VuaXZlcnNhbC1ibGUtcHJvdG9jb2wtYWRhcHRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7QUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFFLGVBQWUsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ3hFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQzdELE9BQU8sRUFBRSxLQUFLLEVBQWMsTUFBTSxNQUFNLENBQUM7QUFDekMsT0FBTyxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLEtBQUssRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzNELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDekMsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDeEQsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDMUQsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUNoQyxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sVUFBVSxDQUFDO0FBQ3ZDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxRQUFRLENBQUM7QUErQnRDLE1BQU0sQ0FBQyxNQUFNLG1CQUFtQixHQUF3QztJQUN0RSxHQUFHLEVBQUUsU0FBUyxDQUFDLDRCQUE0QixHQUFHLENBQUM7SUFDL0MsbUJBQW1CLEVBQUUsR0FBRztJQUN4Qix1QkFBdUIsRUFBRSxJQUFJO0lBQzdCLHNCQUFzQixFQUFFLGFBQWE7SUFDckMsWUFBWSxFQUFFLEtBQUs7Q0FDcEIsQ0FBQztBQUVGOzs7Ozs7OztHQVFHO0FBQ0gsTUFBTSxPQUFPLDJCQUVYLFNBQVEsZ0JBQWdCO0lB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