@iotile/iotile-device
Version:
A typescript library for interfacing with IOTile BLE devices
200 lines • 8.06 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const iotile_common_1 = require("@iotile/iotile-common");
const Errors = require("../common/error-space");
const IOTileTypes = require("../common/iotile-types");
const config_1 = require("../config");
var RPCError;
(function (RPCError) {
RPCError[RPCError["OK"] = 0] = "OK";
RPCError[RPCError["UnexpectedRPCTimeout"] = 1] = "UnexpectedRPCTimeout";
RPCError[RPCError["ErrorWritingRPC"] = 2] = "ErrorWritingRPC";
RPCError[RPCError["IncorrectReceivedLength"] = 3] = "IncorrectReceivedLength";
RPCError[RPCError["ResponseReceivedAtInvalidTime"] = 4] = "ResponseReceivedAtInvalidTime";
RPCError[RPCError["BluetoothErrorWritingRPC"] = 5] = "BluetoothErrorWritingRPC";
RPCError[RPCError["StoppedFromPreviousErrors"] = 6] = "StoppedFromPreviousErrors";
})(RPCError = exports.RPCError || (exports.RPCError = {}));
class IOTileRPCInterface {
constructor() {
this.removeReceiveHeaderHandler = null;
this.removeReceivePayloadHandler = null;
this.channel = null;
this.rpcQueue = [];
this.processing = false;
this.currentRPC = undefined;
this.stoppedFromErrors = false;
this.lastError = null;
}
async open(channel) {
this.channel = channel;
this.rpcQueue = [];
this.processing = false;
this.stoppedFromErrors = false;
this.lastError = RPCError.OK;
let that = this;
this.removeReceiveHeaderHandler = await this.channel.subscribe(IOTileTypes.IOTileCharacteristic.ReceiveHeader, function (value) {
that.receiveHeader(value);
});
this.removeReceivePayloadHandler = await this.channel.subscribe(IOTileTypes.IOTileCharacteristic.ReceivePayload, function (value) {
that.receivePayload(value);
});
}
async rpc(address, rpcID, payload, timeout) {
let queueItem = {
rpcID: rpcID,
address: address,
payload: payload,
timeoutHandler: null,
timeout: timeout || 1.0,
internalTimeout: 15,
success: function (value) { },
failure: function (err) { },
headerReceived: false,
expectedPayloadLength: 0
};
if (this.stoppedFromErrors) {
throw new Errors.RPCError(address, rpcID, RPCError.StoppedFromPreviousErrors);
}
let promise = new Promise(function (resolve, reject) {
queueItem.success = resolve;
queueItem.failure = reject;
});
this.rpcQueue.push(queueItem);
if (!this.processing) {
this.processOne();
}
return promise;
}
async close() {
if (this.removeReceiveHeaderHandler !== null) {
await this.removeReceiveHeaderHandler();
this.removeReceiveHeaderHandler = null;
}
if (this.removeReceivePayloadHandler !== null) {
await this.removeReceivePayloadHandler();
this.removeReceivePayloadHandler = null;
}
}
receiveHeader(value) {
if (this.stoppedFromErrors) {
return;
}
if (this.currentRPC === null || this.currentRPC === undefined) {
this.fatalRPCError(RPCError.ResponseReceivedAtInvalidTime);
return;
}
if (value.byteLength < 4) {
this.fatalRPCError(RPCError.IncorrectReceivedLength);
return;
}
let header = value.slice(0, 4);
let resp = iotile_common_1.unpackArrayBuffer("BBBB", header);
let status = resp[0];
let statusCode = status & 0b00111111;
let appDefined = !!(status & (1 << 6));
let hasData = !!(status & (1 << 7));
let payloadLength = resp[3];
this.currentRPC.headerReceived = true;
this.currentRPC.expectedPayloadLength = payloadLength;
let rpcFinished = false;
if (!appDefined || statusCode != 0) {
this.currentRPC.failure(new Errors.RPCError(this.currentRPC.address, this.currentRPC.rpcID, status));
rpcFinished = true;
}
else if (!hasData) {
this.currentRPC.success(new ArrayBuffer(0));
rpcFinished = true;
}
else {
}
if (rpcFinished) {
this.finishRPC();
}
}
finishRPC() {
if (this.currentRPC && this.currentRPC.timeoutHandler !== null) {
clearTimeout(this.currentRPC.timeoutHandler);
this.currentRPC.timeoutHandler = null;
}
this.currentRPC = undefined;
this.processing = false;
let that = this;
setTimeout(function () { that.processOne(); }, 0);
}
receivePayload(value) {
if (this.stoppedFromErrors) {
return;
}
if (this.currentRPC === undefined || !this.currentRPC.headerReceived) {
this.fatalRPCError(RPCError.ResponseReceivedAtInvalidTime);
return;
}
if (value.byteLength < this.currentRPC.expectedPayloadLength) {
this.fatalRPCError(RPCError.IncorrectReceivedLength);
return;
}
let truncatedValue = value.slice(0, this.currentRPC.expectedPayloadLength);
this.currentRPC.success(truncatedValue);
this.finishRPC();
}
fatalRPCError(code) {
this.stoppedFromErrors = true;
this.lastError = code;
this.processing = false;
if (this.currentRPC !== undefined) {
this.currentRPC.failure(new Errors.RPCError(this.currentRPC.address, this.currentRPC.rpcID, code));
if (this.currentRPC.timeoutHandler !== null) {
clearTimeout(this.currentRPC.timeoutHandler);
}
this.currentRPC = undefined;
}
while (this.rpcQueue.length > 0) {
let curr = this.rpcQueue.shift();
if (curr) {
curr.failure(new Errors.RPCError(curr.address, curr.rpcID, RPCError.StoppedFromPreviousErrors));
}
}
if (this.channel) {
this.channel.notify(IOTileTypes.AdapterEvent.UnrecoverableRPCError, new Errors.RPCError(0, 0, code));
}
}
async processOne() {
if (this.stoppedFromErrors) {
return;
}
if (this.processing === true) {
return;
}
if (this.rpcQueue.length === 0) {
return;
}
this.processing = true;
this.currentRPC = this.rpcQueue.shift();
if (this.currentRPC && this.channel) {
let header = iotile_common_1.packArrayBuffer("BBHB", this.currentRPC.payload.byteLength, 0, this.currentRPC.rpcID, this.currentRPC.address);
let that = this;
this.currentRPC.timeoutHandler = setTimeout(function () {
that.fatalRPCError(RPCError.UnexpectedRPCTimeout);
}, this.currentRPC.internalTimeout * 1000);
try {
let rpc = this.currentRPC;
let start = Date.now();
if (this.currentRPC.payload.byteLength > 0) {
await this.channel.write(IOTileTypes.IOTileCharacteristic.SendPayload, this.currentRPC.payload);
}
await this.channel.write(IOTileTypes.IOTileCharacteristic.SendHeader, header);
let end = Date.now();
let actual = (end - start) / 1000;
if (actual > rpc.timeout) {
config_1.catService.error(`Timeout in RPC ${rpc.rpcID} on tile ${rpc.address}. Expected to take ${rpc.timeout} s; took ${actual} s`, Error);
}
}
catch (err) {
config_1.catService.error(JSON.stringify(err), new Error(JSON.stringify(RPCError.BluetoothErrorWritingRPC)));
this.fatalRPCError(RPCError.BluetoothErrorWritingRPC);
}
}
}
}
exports.IOTileRPCInterface = IOTileRPCInterface;
//# sourceMappingURL=iotile-iface-rpc.js.map