UNPKG

@iotile/iotile-device

Version:

A typescript library for interfacing with IOTile BLE devices

200 lines 8.06 kB
"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