obniz
Version:
obniz sdk for javascript
734 lines (733 loc) • 32.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GattCentral = void 0;
const att_1 = require("../common/att");
const gatt_1 = require("../common/gatt");
const gatt_2 = require("../peripheral/gatt");
// let debug = require('debug')('att');
const debug = () => {
// do nothing.
};
/* eslint-disable no-unused-vars */
const eventemitter3_1 = __importDefault(require("eventemitter3"));
const ObnizError_1 = require("../../../../../ObnizError");
const bleHelper_1 = __importDefault(require("../../bleHelper"));
/**
* @ignore
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
var GATT;
(function (GATT) {
GATT.PRIM_SVC_UUID = 0x2800;
GATT.SECONDARY_SVC_UUID = 0x2801;
GATT.INCLUDE_UUID = 0x2802;
GATT.CHARAC_UUID = 0x2803;
GATT.CLIENT_CHARAC_CFG_UUID = 0x2902;
GATT.SERVER_CHARAC_CFG_UUID = 0x2903;
})(GATT || (GATT = {}));
/**
* @ignore
*/
class GattCentral extends eventemitter3_1.default {
constructor(address, aclStream) {
super();
this._services = {};
this._characteristics = {};
this._descriptors = {};
this._remoteMtuRequest = null;
this._address = address;
this._aclStream = aclStream;
this._services = {};
this._characteristics = {};
this._descriptors = {};
this._currentCommand = null;
this._commandQueue = [];
this._commandPromises = [];
this._mtu = 23;
this._security = 'low';
this.onAclStreamDataBinded = this.onAclStreamData.bind(this);
this.onAclStreamEndBinded = this.onAclStreamEnd.bind(this);
this._aclStream.on('data', this.onAclStreamDataBinded);
this._aclStream.on('end', this.onAclStreamEndBinded);
this._gattCommon = new gatt_1.GattCommon();
this._gattPeripheral = new gatt_2.GattPeripheral();
this._gattPeripheral.send = (data) => {
this._execNoRespCommandWait(data).catch((e) => {
// nothing to do
console.error('_execNoRespCommandWait error', e);
});
};
}
hasEncryptKeys() {
return this._aclStream._smp.hasKeys();
}
getEncryptKeys() {
if (!this.hasEncryptKeys()) {
return null;
}
return this._aclStream._smp.getKeys();
}
async encryptWait(options) {
const result = await this._serialPromiseQueueWait(async () => {
await this._aclStream.encryptWait(options);
this._security = 'medium';
return this._aclStream._smp.getKeys();
});
return result;
}
setEncryptOption(options) {
this._aclStream.setEncryptOption(options);
}
onEnd(reason) {
this.emit('end', reason);
}
async exchangeMtuWait(mtu) {
this._aclStream
.readWait(att_1.ATT.CID, att_1.ATT.OP_MTU_REQ)
.catch((e) => {
if (e instanceof ObnizError_1.ObnizTimeoutError) {
return null;
}
throw e;
})
.then((mtuRequestData) => {
// console.error('mtu request received');
if (!mtuRequestData) {
// throw timeout error and catched above
return;
}
const requestMtu = mtuRequestData.readUInt16LE(1);
// console.log(
// this._address + ': receive OP_MTU_REQ. new MTU is ' + requestMtu
// );
this._mtu = requestMtu;
return this._execNoRespCommandWait(this._gattCommon.mtuResponse(this._mtu));
})
.catch((e) => {
// ignore timeout error
console.error(e);
});
if (mtu === null) {
debug(this._address + ': no exchange MTU : ' + this._mtu);
}
else {
const data = await this._execCommandWait(this._gattCommon.mtuRequest(mtu), att_1.ATT.OP_MTU_RESP);
const opcode = data[0];
const newMtu = data.readUInt16LE(1);
debug(this._address + ': new MTU is ' + newMtu);
this._mtu = newMtu;
}
return this._mtu;
}
async discoverServicesWait(uuids) {
const pServices = await this.discoverPrimaryServicesWait(uuids);
const sServices = await this.discoverSecondaryServicesWait(uuids);
return [...pServices, ...sServices];
}
async discoverPrimaryServicesWait(uuids) {
const services = [];
let startHandle = 0x0001;
while (1) {
const data = await this._execCommandWait(this._gattCommon.readByGroupRequest(startHandle, 0xffff, GATT.PRIM_SVC_UUID), [att_1.ATT.OP_READ_BY_GROUP_RESP, att_1.ATT.OP_ERROR]);
const opcode = data[0];
let i = 0;
if (opcode === att_1.ATT.OP_READ_BY_GROUP_RESP) {
const type = data[1];
const num = (data.length - 2) / type;
for (i = 0; i < num; i++) {
services.push({
startHandle: data.readUInt16LE(2 + i * type + 0),
endHandle: data.readUInt16LE(2 + i * type + 2),
uuid: type === 6
? data.readUInt16LE(2 + i * type + 4).toString(16)
: bleHelper_1.default.buffer2reversedHex(data.slice(2 + i * type + 4).slice(0, 16)),
});
}
}
if (opcode !== att_1.ATT.OP_READ_BY_GROUP_RESP ||
services[services.length - 1].endHandle === 0xffff) {
const serviceUuids = [];
for (i = 0; i < services.length; i++) {
if (uuids.length === 0 || uuids.indexOf(services[i].uuid) !== -1) {
serviceUuids.push(services[i].uuid);
}
this._services[services[i].uuid] = services[i];
}
return serviceUuids;
}
startHandle = services[services.length - 1].endHandle + 1;
}
throw new ObnizError_1.ObnizBleGattHandleError('unreachable code');
}
async discoverSecondaryServicesWait(uuids) {
const services = [];
let startHandle = 0x0001;
while (1) {
const data = await this._execCommandWait(this._gattCommon.readByGroupRequest(startHandle, 0xffff, GATT.SECONDARY_SVC_UUID), [att_1.ATT.OP_READ_BY_GROUP_RESP, att_1.ATT.OP_ERROR]);
const opcode = data[0];
let i = 0;
if (opcode === att_1.ATT.OP_READ_BY_GROUP_RESP) {
const type = data[1];
const num = (data.length - 2) / type;
for (i = 0; i < num; i++) {
services.push({
startHandle: data.readUInt16LE(2 + i * type + 0),
endHandle: data.readUInt16LE(2 + i * type + 2),
uuid: type === 6
? data.readUInt16LE(2 + i * type + 4).toString(16)
: bleHelper_1.default.buffer2reversedHex(data.slice(2 + i * type + 4).slice(0, 16)),
});
}
}
if (opcode !== att_1.ATT.OP_READ_BY_GROUP_RESP ||
services[services.length - 1].endHandle === 0xffff) {
const serviceUuids = [];
for (i = 0; i < services.length; i++) {
if (uuids.length === 0 || uuids.indexOf(services[i].uuid) !== -1) {
serviceUuids.push(services[i].uuid);
}
this._services[services[i].uuid] = services[i];
}
return serviceUuids;
}
startHandle = services[services.length - 1].endHandle + 1;
}
throw new ObnizError_1.ObnizBleGattHandleError('unreachable code');
}
async discoverIncludedServicesWait(serviceUuid, uuids) {
const service = this.getService(serviceUuid);
const includedServices = [];
let startHandle = service.startHandle;
while (1) {
const data = await this._execCommandWait(this._gattCommon.readByTypeRequest(startHandle, service.endHandle, GATT.INCLUDE_UUID), [att_1.ATT.OP_READ_BY_TYPE_RESP, att_1.ATT.OP_ERROR]);
const opcode = data[0];
let i = 0;
if (opcode === att_1.ATT.OP_READ_BY_TYPE_RESP) {
const type = data[1];
const num = (data.length - 2) / type;
for (i = 0; i < num; i++) {
includedServices.push({
endHandle: data.readUInt16LE(2 + i * type + 0),
startHandle: data.readUInt16LE(2 + i * type + 2),
uuid: type === 8
? data.readUInt16LE(2 + i * type + 6).toString(16)
: bleHelper_1.default.buffer2reversedHex(data.slice(2 + i * type + 6).slice(0, 16)),
});
}
}
if (opcode !== att_1.ATT.OP_READ_BY_TYPE_RESP ||
includedServices[includedServices.length - 1].endHandle ===
service.endHandle) {
const includedServiceUuids = [];
for (i = 0; i < includedServices.length; i++) {
if (uuids.length === 0 ||
uuids.indexOf(includedServices[i].uuid) !== -1) {
includedServiceUuids.push(includedServices[i].uuid);
}
}
return includedServiceUuids;
}
startHandle = includedServices[includedServices.length - 1].endHandle + 1;
}
}
async discoverCharacteristicsWait(serviceUuid, characteristicUuids) {
const service = this.getService(serviceUuid);
const characteristics = [];
this._characteristics[serviceUuid] =
this._characteristics[serviceUuid] || {};
this._descriptors[serviceUuid] = this._descriptors[serviceUuid] || {};
let startHandle = service.startHandle;
while (1) {
const data = await this._execCommandWait(this._gattCommon.readByTypeRequest(startHandle, service.endHandle, GATT.CHARAC_UUID), [att_1.ATT.OP_READ_BY_TYPE_RESP, att_1.ATT.OP_ERROR]);
const opcode = data[0];
let i = 0;
if (opcode === att_1.ATT.OP_READ_BY_TYPE_RESP) {
const type = data[1];
const num = (data.length - 2) / type;
for (i = 0; i < num; i++) {
characteristics.push({
startHandle: data.readUInt16LE(2 + i * type + 0),
endHandle: 0,
properties: data.readUInt8(2 + i * type + 2),
valueHandle: data.readUInt16LE(2 + i * type + 3),
uuid: type === 7
? data.readUInt16LE(2 + i * type + 5).toString(16)
: bleHelper_1.default.buffer2reversedHex(data.slice(2 + i * type + 5).slice(0, 16)),
});
}
}
if (opcode !== att_1.ATT.OP_READ_BY_TYPE_RESP ||
characteristics[characteristics.length - 1].valueHandle ===
service.endHandle) {
const characteristicsDiscovered = [];
for (i = 0; i < characteristics.length; i++) {
const properties = characteristics[i].properties;
const characteristic = {
properties: [],
uuid: characteristics[i].uuid,
};
if (i !== 0) {
characteristics[i - 1].endHandle =
characteristics[i].startHandle - 1;
}
if (i === characteristics.length - 1) {
characteristics[i].endHandle = service.endHandle;
}
this._characteristics[serviceUuid][characteristics[i].uuid] =
characteristics[i];
if (properties & 0x01) {
characteristic.properties.push('broadcast');
}
if (properties & 0x02) {
characteristic.properties.push('read');
}
if (properties & 0x04) {
characteristic.properties.push('writeWithoutResponse');
}
if (properties & 0x08) {
characteristic.properties.push('write');
}
if (properties & 0x10) {
characteristic.properties.push('notify');
}
if (properties & 0x20) {
characteristic.properties.push('indicate');
}
if (properties & 0x40) {
characteristic.properties.push('authenticatedSignedWrites');
}
if (properties & 0x80) {
characteristic.properties.push('extendedProperties');
}
if (characteristicUuids.length === 0 ||
characteristicUuids.indexOf(characteristic.uuid) !== -1) {
characteristicsDiscovered.push(characteristic);
}
}
return characteristicsDiscovered;
}
startHandle = characteristics[characteristics.length - 1].valueHandle + 1;
}
throw new ObnizError_1.ObnizBleGattHandleError('no reachable code');
}
async readWait(serviceUuid, characteristicUuid) {
const characteristic = this.getCharacteristic(serviceUuid, characteristicUuid);
let readData = Buffer.alloc(0);
while (1) {
let data;
if (readData.length === 0) {
data = await this._execCommandWait(this._gattCommon.readRequest(characteristic.valueHandle), att_1.ATT.OP_READ_RESP);
}
else {
data = await this._execCommandWait(this._gattCommon.readBlobRequest(characteristic.valueHandle, readData.length), att_1.ATT.OP_READ_BLOB_RESP);
}
const opcode = data[0];
readData = Buffer.from(readData.toString('hex') + data.slice(1).toString('hex'), 'hex');
if (data.length === this._mtu) {
// do nothing.
}
else {
return readData;
}
}
// never reach
// eslint-disable-next-line no-unreachable
return readData;
}
async writeWait(serviceUuid, characteristicUuid, data, withoutResponse) {
const characteristic = this.getCharacteristic(serviceUuid, characteristicUuid);
if (withoutResponse) {
await this._execNoRespCommandWait(this._gattCommon.writeRequest(characteristic.valueHandle, data, true));
}
else if (data.length + 3 > this._mtu) {
await this.longWriteWait(serviceUuid, characteristicUuid, data, withoutResponse);
}
else {
await this._execCommandWait(this._gattCommon.writeRequest(characteristic.valueHandle, data, false), att_1.ATT.OP_WRITE_RESP);
}
}
async broadcastWait(serviceUuid, characteristicUuid, broadcast) {
const characteristic = this.getCharacteristic(serviceUuid, characteristicUuid);
const data = await this._execCommandWait(this._gattCommon.readByTypeRequest(characteristic.startHandle, characteristic.endHandle, GATT.SERVER_CHARAC_CFG_UUID), att_1.ATT.OP_READ_BY_TYPE_RESP);
const opcode = data[0];
// let type = data[1];
const handle = data.readUInt16LE(2);
let value = data.readUInt16LE(4);
if (broadcast) {
value |= 0x0001;
}
else {
value &= 0xfffe;
}
const valueBuffer = Buffer.alloc(2);
valueBuffer.writeUInt16LE(value, 0);
const _data = await this._execCommandWait(this._gattCommon.writeRequest(handle, valueBuffer, false), att_1.ATT.OP_WRITE_RESP);
}
async notifyWait(serviceUuid, characteristicUuid, notify) {
try {
const characteristic = this.getCharacteristic(serviceUuid, characteristicUuid);
const descriptor = this.getDescriptor(serviceUuid, characteristicUuid, '2902');
return await this.notifyByDescriptorWait(serviceUuid, characteristicUuid, notify);
}
catch (e) {
debug(`failed to handle descriptor`);
}
return await this.notifyByHandleWait(serviceUuid, characteristicUuid, notify);
}
async notifyByDescriptorWait(serviceUuid, characteristicUuid, notify) {
const characteristic = this.getCharacteristic(serviceUuid, characteristicUuid);
let value = 0;
const useNotify = characteristic.properties & 0x10;
const useIndicate = characteristic.properties & 0x20;
if (notify) {
if (useNotify) {
value |= 0x0001;
}
else if (useIndicate) {
value |= 0x0002;
}
}
else {
if (useNotify) {
value &= 0xfffe;
}
else if (useIndicate) {
value &= 0xfffd;
}
}
const valueBuffer = Buffer.alloc(2);
valueBuffer.writeUInt16LE(value, 0);
let _data = null;
// if (handle) {
// _data = await this._execCommandWait(
// this._gattCommon.writeRequest(handle, valueBuffer, false),
// ATT.OP_WRITE_RESP
// );
// } else {
_data = await this.writeValueWait(serviceUuid, characteristicUuid, '2902', valueBuffer);
// }
const _opcode = _data && _data[0];
debug('set notify write results: ' + (_opcode === att_1.ATT.OP_WRITE_RESP));
}
async notifyByHandleWait(serviceUuid, characteristicUuid, notify) {
const characteristic = this.getCharacteristic(serviceUuid, characteristicUuid);
// const descriptor: any = this.getDescriptor(serviceUuid, characteristicUuid, "2902");
let value = null;
let handle = null;
try {
value = await this.readValueWait(serviceUuid, characteristicUuid, '2902');
}
catch (e) {
// retry
const data = await this._execCommandWait(this._gattCommon.readByTypeRequest(characteristic.startHandle, characteristic.endHandle, GATT.CLIENT_CHARAC_CFG_UUID), att_1.ATT.OP_READ_BY_TYPE_RESP);
const opcode = data[0];
// let type = data[1];
handle = data.readUInt16LE(2);
value = data.readUInt16LE(4);
}
const useNotify = characteristic.properties & 0x10;
const useIndicate = characteristic.properties & 0x20;
if (notify) {
if (useNotify) {
value |= 0x0001;
}
else if (useIndicate) {
value |= 0x0002;
}
}
else {
if (useNotify) {
value &= 0xfffe;
}
else if (useIndicate) {
value &= 0xfffd;
}
}
const valueBuffer = Buffer.alloc(2);
valueBuffer.writeUInt16LE(value, 0);
let _data = null;
if (handle) {
_data = await this._execCommandWait(this._gattCommon.writeRequest(handle, valueBuffer, false), att_1.ATT.OP_WRITE_RESP);
}
else {
_data = await this.writeValueWait(serviceUuid, characteristicUuid, '2902', valueBuffer);
}
const _opcode = _data && _data[0];
debug('set notify write results: ' + (_opcode === att_1.ATT.OP_WRITE_RESP));
}
async discoverDescriptorsWait(serviceUuid, characteristicUuid) {
const characteristic = this.getCharacteristic(serviceUuid, characteristicUuid);
const descriptors = [];
this._descriptors[serviceUuid][characteristicUuid] = {};
let startHandle = characteristic.valueHandle + 1;
while (1) {
const data = await this._execCommandWait(this._gattCommon.findInfoRequest(startHandle, characteristic.endHandle), [att_1.ATT.OP_FIND_INFO_RESP, att_1.ATT.OP_ERROR]);
const opcode = data[0];
let i = 0;
if (opcode === att_1.ATT.OP_FIND_INFO_RESP) {
const num = data[1];
for (i = 0; i < num; i++) {
descriptors.push({
handle: data.readUInt16LE(2 + i * 4 + 0),
uuid: data.readUInt16LE(2 + i * 4 + 2).toString(16),
});
}
}
if (opcode !== att_1.ATT.OP_FIND_INFO_RESP ||
descriptors[descriptors.length - 1].handle === characteristic.endHandle) {
const descriptorUuids = [];
for (i = 0; i < descriptors.length; i++) {
descriptorUuids.push(descriptors[i].uuid);
this._descriptors[serviceUuid][characteristicUuid][descriptors[i].uuid] = descriptors[i];
}
return descriptorUuids;
}
startHandle = descriptors[descriptors.length - 1].handle + 1;
}
// never reach
// eslint-disable-next-line no-unreachable
return [];
}
async readValueWait(serviceUuid, characteristicUuid, descriptorUuid) {
const descriptor = this.getDescriptor(serviceUuid, characteristicUuid, descriptorUuid);
const data = await this._execCommandWait(this._gattCommon.readRequest(descriptor.handle), att_1.ATT.OP_READ_RESP);
return data.slice(1);
}
async writeValueWait(serviceUuid, characteristicUuid, descriptorUuid, data) {
const descriptor = this.getDescriptor(serviceUuid, characteristicUuid, descriptorUuid);
return await this._execCommandWait(this._gattCommon.writeRequest(descriptor.handle, data, false), att_1.ATT.OP_WRITE_RESP);
}
async readHandleWait(handle) {
const data = await this._execCommandWait(this._gattCommon.readRequest(handle), att_1.ATT.OP_READ_RESP);
return data.slice(1);
}
async writeHandleWait(handle, data, withoutResponse) {
if (withoutResponse) {
await this._execNoRespCommandWait(this._gattCommon.writeRequest(handle, data, true));
}
else {
await this._execCommandWait(this._gattCommon.writeRequest(handle, data, false), att_1.ATT.OP_WRITE_RESP);
}
}
onAclStreamData(cid, data) {
if (cid !== att_1.ATT.CID) {
return;
}
const requestType = data[0];
// notify / indicate
if (requestType === att_1.ATT.OP_HANDLE_NOTIFY ||
requestType === att_1.ATT.OP_HANDLE_IND) {
const valueHandle = data.readUInt16LE(1);
const valueData = data.slice(3);
this.emit('handleNotify', this._address, valueHandle, valueData);
if (data[0] === att_1.ATT.OP_HANDLE_IND) {
// background
this._execNoRespCommandWait(this._gattCommon.handleConfirmation())
.then(() => {
this.emit('handleConfirmation', this._address, valueHandle);
})
.catch((e) => {
console.warn(e);
});
}
for (const serviceUuid in this._services) {
for (const characteristicUuid in this._characteristics[serviceUuid]) {
if (this._characteristics[serviceUuid][characteristicUuid]
.valueHandle === valueHandle) {
this.emit('notification', this._address, serviceUuid, characteristicUuid, valueData);
}
}
}
}
else if (requestType === att_1.ATT.OP_FIND_INFO_REQ ||
requestType === att_1.ATT.OP_FIND_BY_TYPE_REQ ||
requestType === att_1.ATT.OP_READ_BY_TYPE_REQ ||
requestType === att_1.ATT.OP_READ_REQ ||
requestType === att_1.ATT.OP_READ_BLOB_REQ ||
requestType === att_1.ATT.OP_READ_BY_GROUP_REQ ||
requestType === att_1.ATT.OP_WRITE_REQ ||
requestType === att_1.ATT.OP_WRITE_CMD ||
requestType === att_1.ATT.OP_PREPARE_WRITE_REQ ||
requestType === att_1.ATT.OP_EXECUTE_WRITE_REQ ||
requestType === att_1.ATT.OP_HANDLE_CNF ||
requestType === att_1.ATT.OP_READ_MULTI_REQ ||
requestType === att_1.ATT.OP_SIGNED_WRITE_CMD) {
// console.error('_gattPeripheral.handleRequest', requestType);
this._gattPeripheral.handleRequest(data);
}
}
onAclStreamEnd() {
this._aclStream.removeListener('data', this.onAclStreamDataBinded);
this._aclStream.removeListener('end', this.onAclStreamEndBinded);
}
writeAtt(data) {
const opCode = data[0];
const handle = data.length > 3 ? data.readUInt16LE(1) : 'none';
debug(`ATT: opCode=${opCode}(${att_1.ATT_OP_READABLES[opCode]}) handle=${handle} address=` +
this._address +
': write: ' +
data.toString('hex'));
this._aclStream.write(att_1.ATT.CID, data);
}
/* Perform a "long write" as described Bluetooth Spec section 4.9.4 "Write Long Characteristic Values" */
async longWriteWait(serviceUuid, characteristicUuid, data, withoutResponse) {
const characteristic = this.getCharacteristic(serviceUuid, characteristicUuid);
const limit = this._mtu - 5;
/* split into prepare-write chunks and queue them */
let offset = 0;
while (offset < data.length) {
const end = offset + limit;
const chunk = data.slice(offset, end);
const _resp = await this._execCommandWait(this._gattCommon.prepareWriteRequest(characteristic.valueHandle, offset, chunk), att_1.ATT.OP_PREPARE_WRITE_RESP);
const expected_length = chunk.length + 5;
if (_resp.length !== expected_length) {
/* the response should contain the data packet echoed back to the caller */
throw new Error(`unexpected prepareWriteResponse length ${_resp.length} (expecting ${expected_length})`);
}
offset = end;
}
if (withoutResponse) {
await this._execNoRespCommandWait(this._gattCommon.executeWriteRequest(characteristic.valueHandle));
}
else {
await this._execCommandWait(this._gattCommon.executeWriteRequest(characteristic.valueHandle), att_1.ATT.OP_EXECUTE_WRITE_RESP);
}
}
getService(serviceUuid) {
if (!this._services[serviceUuid]) {
throw new ObnizError_1.ObnizBleUnknownServiceError(this._address, serviceUuid);
}
return this._services[serviceUuid];
}
getCharacteristic(serviceUuid, characteristicUuid) {
if (!this._characteristics[serviceUuid] ||
!this._characteristics[serviceUuid][characteristicUuid]) {
throw new ObnizError_1.ObnizBleUnknownCharacteristicError(this._address, serviceUuid, characteristicUuid);
}
return this._characteristics[serviceUuid][characteristicUuid];
}
getDescriptor(serviceUuid, characteristicUuid, descriptorUuid) {
if (!this._descriptors[serviceUuid] ||
!this._descriptors[serviceUuid][characteristicUuid] ||
!this._descriptors[serviceUuid][characteristicUuid][descriptorUuid]) {
throw new ObnizError_1.ObnizBleUnknownDescriptorError(this._address, serviceUuid, characteristicUuid, descriptorUuid);
}
return this._descriptors[serviceUuid][characteristicUuid][descriptorUuid];
}
_queueCommand(buffer, callback, writeCallback) {
this._commandQueue.push({
buffer,
callback,
writeCallback,
});
this._runQueueCommand();
}
_runQueueCommand() {
if (this._currentCommand === null) {
while (this._commandQueue.length) {
this._currentCommand = this._commandQueue.shift();
this.writeAtt(this._currentCommand.buffer);
if (this._currentCommand.callback) {
break;
}
else if (this._currentCommand.writeCallback) {
this._currentCommand.writeCallback();
this._currentCommand = null;
}
}
}
}
_serialPromiseQueueWait(func) {
const onfinish = () => {
this._commandPromises = this._commandPromises.filter((e) => e !== resultPromise);
if (disconnectReject) {
this.off('end', disconnectReject);
}
};
const errorForStacktrace = new Error('stacktrace');
let disconnectReject = null;
const doPromise = Promise.all(this._commandPromises)
.catch((error) => {
// nothing
})
.then(() => {
return func();
})
.catch((reason) => {
throw reason;
})
.then((result) => {
onfinish();
return Promise.resolve(result);
}, (error) => {
onfinish();
error.cause = errorForStacktrace;
return Promise.reject(error);
});
const disconnectPromise = new Promise((resolve, reject) => {
disconnectReject = (reason) => {
onfinish();
reason.cause = errorForStacktrace;
reject(reason);
};
this.on('end', disconnectReject);
});
const resultPromise = Promise.race([doPromise, disconnectPromise]);
this._commandPromises.push(resultPromise);
return resultPromise;
}
_execCommandWait(buffer, waitOpcode) {
const waitOpcodes = Array.isArray(waitOpcode)
? waitOpcode
: [waitOpcode];
let errorHandle = true;
if (!waitOpcodes.includes(att_1.ATT.OP_ERROR)) {
waitOpcodes.push(att_1.ATT.OP_ERROR);
errorHandle = false;
}
return this._serialPromiseQueueWait(async () => {
while (1) {
this.writeAtt(buffer);
const promises = [];
for (const code of waitOpcodes) {
promises.push(this._aclStream.readWait(att_1.ATT.CID, code));
}
debug(`ATT: wait for opcode=${waitOpcodes}`);
const data = await Promise.race(promises);
const opCode = data.readUInt8(0);
debug(`ATT: received opCode=${opCode}(${att_1.ATT_OP_READABLES[opCode]})`);
if (opCode === att_1.ATT.OP_ERROR) {
const errCode = data[4];
if ((errCode === att_1.ATT.ECODE_AUTHENTICATION ||
errCode === att_1.ATT.ECODE_AUTHORIZATION ||
errCode === att_1.ATT.ECODE_INSUFF_ENC) &&
this._security !== 'medium') {
// retry after encrypt
debug(`ATT: going to encrypt and try it later.`);
await this._aclStream.encryptWait();
continue;
}
if (errorHandle) {
return data;
}
const requestOpCode = data.readUInt8(1);
const attributeHandle = data.readUInt16LE(2);
throw new ObnizError_1.ObnizBleAttError(errCode, `errorCode=${errCode}(${att_1.ATT_ECODE_READABLES[errCode]}) for request_opcode=${requestOpCode}(${att_1.ATT_OP_READABLES[requestOpCode]}) atributeHandle=${attributeHandle} `);
}
return data;
}
// unreachable here
// eslint-disable-next-line no-unreachable
return Buffer.from([]);
});
}
_execNoRespCommandWait(buffer) {
return this._serialPromiseQueueWait(async () => {
this.writeAtt(buffer);
});
}
}
exports.GattCentral = GattCentral;