@iotize/tap
Version:
IoTize Device client for Javascript
243 lines • 19.9 kB
JavaScript
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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