UNPKG

@iotize/tap

Version:

IoTize Device client for Javascript

782 lines (765 loc) 29.8 kB
import { TapStreamWriter, TapStreamReader } from '@iotize/tap/client/impl'; import { CodeError } from '@iotize/common/error'; import { listEnumValues } from '@iotize/common/utility'; import { converters } from '@iotize/tap/service/core'; import { ResultCode } from '@iotize/tap/client/api'; import '@iotize/tap/service/impl/target'; import '@iotize/tap/service/impl/variable'; import '@iotize/tap/service/impl/interface'; import { Observable } from 'rxjs'; import { share } from 'rxjs/operators'; var ASCIIControl; (function (ASCIIControl) { ASCIIControl[ASCIIControl["DEVICE_CONTROL_1"] = 17] = "DEVICE_CONTROL_1"; })(ASCIIControl || (ASCIIControl = {})); class TlvJsonCompressorError extends CodeError { static maxDataLengthReach(length, maxLength) { return new TlvJsonCompressorError(`Cannot write ${length} bytes. Maximum field length is ${maxLength} bytes.`, TlvJsonCompressorError.Code.MaxDataLengthReach); } static unknownFieldName(fieldName) { return new TlvJsonCompressorError(`Cannot find definition for field name "${fieldName}"`, TlvJsonCompressorError.Code.UnknownFieldName); } static unknownFieldCode(code) { return new TlvJsonCompressorError(`Cannot find definition for code "${code}"`, TlvJsonCompressorError.Code.UnknownFieldCode); } static unexpectedCharacter(msg) { return new TlvJsonCompressorError(msg, TlvJsonCompressorError.Code.UnexpectedCharacter); } static invalidEnumKey(key, mapping) { return new TlvJsonCompressorError(`Invalid enum key "${key}". Authorized keys are ${listEnumValues(mapping) .map((key) => `${key}: ${mapping[key]}`) .join(', ')}`, TlvJsonCompressorError.Code.InvalidEnumKey); } } (function (TlvJsonCompressorError) { let Code; (function (Code) { Code["UnknownFieldCode"] = "TlvJsonCompressorErrorUnknownFieldCode"; Code["UnknownFieldName"] = "TlvJsonCompressorErrorUnknownFieldName"; Code["UnexpectedCharacter"] = "TlvJsonCompressorErrorUnexpectedCharacter"; Code["MaxDataLengthReach"] = "TlvJsonCompressorErrorMaxDataLengthReach"; Code["InvalidEnumKey"] = "TlvJsonCompressorErrorInvalidEnumKey"; })(Code = TlvJsonCompressorError.Code || (TlvJsonCompressorError.Code = {})); })(TlvJsonCompressorError || (TlvJsonCompressorError = {})); function getRequiredByteLengthToEncodeInteger(input, signed) { const bits = Math.ceil(Math.log2(Math.abs(input) + 1)) + (signed ? 1 : 0); return Math.ceil(bits / 8); } class TLVMappedJsonIntegerCompressor { constructor(signed) { this.signed = signed; } encode(input, stream = new TapStreamWriter()) { let bytesLength; if (input === 0) { bytesLength = 1; } else { bytesLength = getRequiredByteLengthToEncodeInteger(input, this.signed); if (bytesLength <= 0) { throw new Error('Invalid byte length ' + bytesLength); } } if (bytesLength === 3) { bytesLength = 4; } writeFieldLength(stream, bytesLength); if (this.signed) { stream.writeS(input, bytesLength); } else { stream.writeU(input, bytesLength); } return stream.toBytes; } decode(streamOrBuffer) { const stream = TapStreamReader.create(streamOrBuffer); const length = readFieldLength(stream); if (length === 0) { return 0; } if (this.signed) { return stream.readS(length); } else { return stream.readU(length); } } } class TLVMappedFloat64Compressor { encode(input, stream = new TapStreamWriter()) { if (input === 0) { writeFieldLength(stream, 0); return stream.toBytes; } if (Number.isInteger(input)) { const length = getRequiredByteLengthToEncodeInteger(input, true); if (length < 3) { writeFieldLength(stream, length); stream.writeS(input, length); return stream.toBytes; } } let floatLength = Math.fround(input) === input ? 4 : 8; writeFieldLength(stream, floatLength); stream.writeF(input, floatLength); return stream.toBytes; } decode(streamOrBuffer) { const stream = TapStreamReader.create(streamOrBuffer); const length = readFieldLength(stream); if (length === 0) { return 0; } else if (length === 4 || length == 8) { return stream.readFloat(length); } else if (length < 3) { return stream.readS(length); } else { console.warn(`failed to decode float64 number with length "${length}"`); return 0; } } } class TLVMappedBooleanValueCompressor { constructor() { } encode(input, stream = new TapStreamWriter()) { if (input) { writeFieldLength(stream, 0); } else { writeFieldLength(stream, 1); stream.writeU1(0); } return stream.toBytes; } decode(streamOrBuffer) { const stream = TapStreamReader.create(streamOrBuffer); const length = readFieldLength(stream); if (length === 0) { return true; } else { return stream.readU(length) !== 0; } } } class TLVMappedJsonValueCompressor { constructor(converter) { this.converter = converter; } encode(input, stream = new TapStreamWriter()) { const encodedValue = this.converter.encode(input); writeFieldLength(stream, encodedValue.length); stream.writeBytes(encodedValue); return stream.toBytes; } decode(streamOrBuffer) { const stream = TapStreamReader.create(streamOrBuffer); const length = readFieldLength(stream); return this.converter.decode(stream.readBytes(length)); } } const DBIOT_UTF8_CONVERTER = new TLVMappedJsonValueCompressor(converters.utf8); const DBIOT_BOOLEAN_CONVERTER = new TLVMappedBooleanValueCompressor(); const DBIOT_FLOAT32_CONVERTER = new TLVMappedJsonValueCompressor(converters.float); const DBIOT_UINT_CONVERTER = new TLVMappedJsonIntegerCompressor(false); const DBIOT_INT32_CONVERTER = new TLVMappedJsonIntegerCompressor(true); const DBIOT_FLOAT64_CONVERTER = new TLVMappedFloat64Compressor(); function findCodeDefinition(code, mapping) { return Object.entries(mapping).find(([_, value]) => value.code === String.fromCharCode(code)); } function writeFieldLength(stream, length) { if (length > 0b01111111) { if (length > 0x7fff) { throw TlvJsonCompressorError.maxDataLengthReach(length, 0x7fff); } stream.writeBits(1, 1); stream.writeBits(length, 15); } else { stream.writeBits(0, 1); stream.writeBits(length, 7); } } function readFieldLength(stream) { const extendedFieldLength = stream.readBits(1); return stream.readBits(extendedFieldLength ? 15 : 7); } class TLVMappedJsonObjectCompressor { constructor(mapping) { this.mapping = mapping; this.strictMode = false; } encode(input, stream = new TapStreamWriter()) { const entries = Object.entries(input); const fieldsStream = new TapStreamWriter(); entries.forEach(([fieldName, value]) => { const definition = this.mapping[fieldName]; if (!definition) { throw TlvJsonCompressorError.unknownFieldName(fieldName); } fieldsStream.writeU1(definition.code.charCodeAt(0)); const encodedValue = definition.converter.encode(value); writeFieldLength(fieldsStream, encodedValue.length); fieldsStream.writeBytes(encodedValue); }); const bytes = fieldsStream.toBytes; writeFieldLength(stream, bytes.length); stream.writeBytes(bytes); return stream.toBytes; } decode(streamOrBuffer) { const stream = TapStreamReader.create(streamOrBuffer); const result = {}; const totalLength = readFieldLength(stream); const initialPosition = stream.pos; while (stream.pos - initialPosition < totalLength) { const tag = stream.readU1(); const entry = findCodeDefinition(tag, this.mapping); const length = readFieldLength(stream); if (!entry) { if (this.strictMode) { throw TlvJsonCompressorError.unknownFieldCode(tag); } else { stream.forward(length); } } else { const [fieldName, definition] = entry; result[fieldName] = definition.converter.decode(stream); } } return result; } } class TLVMappedJsonArrayCompressor { constructor(options) { this.options = options; } encode(input, stream = new TapStreamWriter()) { const itemsStream = new TapStreamWriter(); input.forEach((v) => { this.options.itemConverter.encode(v, itemsStream); }); const encodedItems = itemsStream.toBytes; writeFieldLength(stream, encodedItems.length); stream.writeBytes(encodedItems); return stream.toBytes; } decode(streamOrBuffer) { const stream = TapStreamReader.create(streamOrBuffer); const length = readFieldLength(stream); const itemsStream = stream.subStream(length); const result = []; while (!itemsStream.isEof()) { const item = this.options.itemConverter.decode(itemsStream); result.push(item); } return result; } } /** * Optimization for an array of object, * It will not duplicate object/field information for each items */ class TLVMappedJsonArrayOfObjectCompressor { constructor(options) { this.options = options; } encode(input, stream = new TapStreamWriter()) { const itemsStream = new TapStreamWriter(); DBIOT_UINT_CONVERTER.encode(input.length, itemsStream); this.options.fields.forEach(({ fieldName, converter }) => { input.forEach((item, index) => { if (!(fieldName in item)) { writeFieldLength(itemsStream, 0); // throw new Error(`Field ${fieldName} is missing for item ${index}`); } else { converter.encode(item[fieldName], itemsStream); } }); }); const encodedItems = itemsStream.toBytes; writeFieldLength(stream, encodedItems.length); stream.writeBytes(encodedItems); return stream.toBytes; } decode(streamOrBuffer) { const stream = TapStreamReader.create(streamOrBuffer); const length = readFieldLength(stream); const itemCount = DBIOT_UINT_CONVERTER.decode(stream); const result = new Array(itemCount) .fill(undefined) .map(() => ({})); this.options.fields.forEach(({ fieldName, converter }) => { result.forEach((value, index) => { const fieldValue = converter.decode(stream); value[fieldName] = fieldValue; }); }); return result; } } const POS_BIT_SIZE = 4; const ɵ0 = function (input, stream = new TapStreamWriter()) { const subStream = new TapStreamWriter(); const itemBitLength = POS_BIT_SIZE; const itemCount = input.length; // writeFieldLength(subStream, itemBitLength); writeFieldLength(subStream, itemCount); for (const value of input) { subStream.writeBitsInt(value, itemBitLength); } const encoded = subStream.toBytes; writeFieldLength(stream, encoded.length); stream.writeBytes(encoded); return stream.toBytes; }, ɵ1 = function (stream) { stream = TapStreamReader.create(stream); const _byteLength = readFieldLength(stream); const subStream = stream.subStream(_byteLength); const itemBitSize = POS_BIT_SIZE; //readFieldLength(subStream); const itemCount = readFieldLength(subStream); const result = []; for (let i = 0; i < itemCount; i++) { result.push(subStream.readBitsInt(itemBitSize)); } return result; }; const DBIOT_BYTE_ORDER_CONVERTER = { encode: ɵ0, decode: ɵ1, }; var ButtonSizeEnum; (function (ButtonSizeEnum) { ButtonSizeEnum[ButtonSizeEnum["DEFAULT"] = 0] = "DEFAULT"; ButtonSizeEnum[ButtonSizeEnum["SMALL"] = 1] = "SMALL"; ButtonSizeEnum[ButtonSizeEnum["LARGE"] = 2] = "LARGE"; })(ButtonSizeEnum || (ButtonSizeEnum = {})); const DBIOT_INNER_CONVERTER = new TLVMappedJsonObjectCompressor({ encoding: { code: 'E', converter: new TLVMappedJsonObjectCompressor({ scaling: { code: 'x', converter: DBIOT_FLOAT32_CONVERTER, }, bitLength: { code: 'b', converter: DBIOT_UINT_CONVERTER, }, offset: { code: 'o', converter: DBIOT_FLOAT32_CONVERTER, }, }), }, tap: { code: 'T', converter: new TLVMappedJsonObjectCompressor({ address: { code: 'a', converter: DBIOT_UINT_CONVERTER, }, valueAcquisitionPeriod: { code: 's', converter: DBIOT_UINT_CONVERTER, }, }), }, dashboard: { code: 'D', converter: new TLVMappedJsonObjectCompressor({ offset: { code: 'O', converter: DBIOT_FLOAT64_CONVERTER, }, scaling: { code: 'X', converter: DBIOT_FLOAT64_CONVERTER, }, readable: { code: 'R', converter: DBIOT_BOOLEAN_CONVERTER, }, writable: { code: 'W', converter: DBIOT_BOOLEAN_CONVERTER, }, format: { code: 'F', converter: DBIOT_UTF8_CONVERTER, }, name: { code: 'N', converter: DBIOT_UTF8_CONVERTER, }, unit: { code: 'U', converter: DBIOT_UTF8_CONVERTER, }, byteOrder: { code: 'B', converter: DBIOT_BYTE_ORDER_CONVERTER, }, component: { code: 'C', converter: new TLVMappedJsonObjectCompressor({ 'tap-variable-number': { code: 't', converter: new TLVMappedJsonObjectCompressor({}), }, 'tap-variable-range': { code: 'r', converter: new TLVMappedJsonObjectCompressor({ min: { code: 'm', converter: DBIOT_FLOAT64_CONVERTER, }, max: { code: 'M', converter: DBIOT_FLOAT64_CONVERTER, }, step: { code: 's', converter: DBIOT_FLOAT64_CONVERTER, }, writeValueForEveryChange: { code: 'w', converter: DBIOT_BOOLEAN_CONVERTER, }, }), }, 'tap-variable-linear-gauge': { code: 'G', converter: new TLVMappedJsonObjectCompressor({ min: { code: 'm', converter: DBIOT_FLOAT64_CONVERTER, }, max: { code: 'M', converter: DBIOT_FLOAT64_CONVERTER, }, }), }, 'tap-variable-gauge': { code: 'g', converter: new TLVMappedJsonObjectCompressor({ min: { code: 'm', converter: DBIOT_FLOAT64_CONVERTER, }, max: { code: 'M', converter: DBIOT_FLOAT64_CONVERTER, }, }), }, 'tap-variable-line-chart': { code: 'l', converter: new TLVMappedJsonObjectCompressor({}), }, 'tap-variable-bar-chart': { code: 'B', converter: new TLVMappedJsonObjectCompressor({}), }, 'tap-variable-table': { code: 'T', converter: new TLVMappedJsonObjectCompressor({}), }, 'tap-variable-bits-editor': { code: 'b', converter: new TLVMappedJsonObjectCompressor({ bitsTemplate: { code: 'T', converter: new TLVMappedJsonArrayOfObjectCompressor({ fields: [ { fieldName: 'index', converter: DBIOT_UINT_CONVERTER, }, { fieldName: 'label', converter: DBIOT_UTF8_CONVERTER, }, ], }), }, }), }, 'tap-variable-buttons': { code: 'U', converter: new TLVMappedJsonObjectCompressor({ buttons: { code: 'b', converter: new TLVMappedJsonArrayOfObjectCompressor({ fields: [ { fieldName: 'value', converter: DBIOT_FLOAT64_CONVERTER, }, { fieldName: 'label', converter: DBIOT_UTF8_CONVERTER, }, ], }), }, }), }, 'tap-variable-push-button': { code: 'P', converter: new TLVMappedJsonObjectCompressor({ mouseDownValue: { code: 'd', converter: DBIOT_FLOAT64_CONVERTER, }, mouseUpValue: { code: 'U', converter: DBIOT_FLOAT64_CONVERTER, }, value: { code: 'u', converter: DBIOT_FLOAT64_CONVERTER, }, buttonText: { code: 'l', converter: DBIOT_UTF8_CONVERTER, }, // size: { // code: 's', // converter: new TLVMappedEnumConverter(ButtonSizeEnum), // }, icon: { code: 'i', converter: DBIOT_UTF8_CONVERTER, }, }), }, 'tap-variable-select': { code: 'S', converter: new TLVMappedJsonObjectCompressor({ options: { code: 'o', converter: new TLVMappedJsonArrayOfObjectCompressor({ fields: [ { fieldName: 'value', converter: DBIOT_FLOAT64_CONVERTER, }, { fieldName: 'text', converter: DBIOT_UTF8_CONVERTER, }, ], }), }, }), }, }), }, }), }, cloud: { code: 'C', converter: new TLVMappedJsonObjectCompressor({ name: { code: 'N', converter: DBIOT_UTF8_CONVERTER, }, optional: { code: 'p', converter: DBIOT_UINT_CONVERTER, }, uploadPeriod: { code: 't', converter: DBIOT_UINT_CONVERTER, }, valueDelta: { code: 'd', converter: DBIOT_FLOAT32_CONVERTER, }, alarmMinValue: { code: 'l', converter: DBIOT_FLOAT32_CONVERTER, }, alarmMaxValue: { code: 'h', converter: DBIOT_FLOAT32_CONVERTER, }, }), }, }); class DBIOTVarConfigConverter { encode(mainConfig) { if (!mainConfig) { return new Uint8Array(); } const writter = new TapStreamWriter(); writter.writeU1(ASCIIControl.DEVICE_CONTROL_1); DBIOT_INNER_CONVERTER.encode(mainConfig, writter); return writter.toBytes; } decode(streamOrBuffer) { const stream = TapStreamReader.create(streamOrBuffer); if (stream.byteLeft === 0) { return undefined; } let char; if (!stream.isEof() && char !== ASCIIControl.DEVICE_CONTROL_1) { char = stream.readU1(); } if (char === ASCIIControl.DEVICE_CONTROL_1) { try { return DBIOT_INNER_CONVERTER.decode(stream); } catch (err) { console.warn(`Failed to restore variable configuration`, err); } } return undefined; } } const DBIOT_VARIABLE_CONFIG_CONVERTER = new DBIOTVarConfigConverter(); class TLVMappedEnumConverter { constructor(mapping) { this.mapping = mapping; } encode(keyValue, stream = new TapStreamWriter()) { if (!(keyValue in this.mapping)) { throw TlvJsonCompressorError.invalidEnumKey(keyValue, this.mapping); } return TLVMappedEnumConverter._valueConverter.encode(keyValue, stream); } decode(streamOrBuffer) { const keyValue = TLVMappedEnumConverter._valueConverter.decode(streamOrBuffer); if (!(keyValue in this.mapping)) { throw TlvJsonCompressorError.invalidEnumKey(keyValue, this.mapping); } return keyValue; } } TLVMappedEnumConverter._valueConverter = new TLVMappedJsonIntegerCompressor(false); 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()); }); }; /** * Create shared observable that read DBIOT configuration from tap * @param tap * @param maxVar * @returns */ function createReadDBIOTConfigurationFromTapObservable(tap, maxVar) { const obs = new Observable((emitter) => { let stopLoop = false; const progressEvent = { type: 'start', data: { total: maxVar, }, }; emitter.next(progressEvent); (() => __awaiter(this, void 0, void 0, function* () { try { const targetProtocol = (yield tap.service.target.getProtocol()).body(); const variables = []; const progressEvent = { type: 'progress', data: { current: 0, total: maxVar, }, }; emitter.next(progressEvent); for (let variableId = 0; variableId < maxVar && !stopLoop; variableId++) { const variableConfig = yield readTapVariableConfigWithDBIOTuration(tap, variableId); if (variableConfig) { variables.push(variableConfig); } const progressEvent = { type: 'progress', data: { current: variableId + 1, total: maxVar, variable: variableConfig, }, }; emitter.next(progressEvent); } if (!stopLoop) { const result = { type: 'complete', data: { bundles: [], variables, targetProtocol, }, }; emitter.next(result); } emitter.complete(); } catch (err) { emitter.error(err); } }))(); return () => { stopLoop = true; }; }); return obs.pipe(share()); } function readTapVariableConfigWithDBIOTuration(tap, id) { return __awaiter(this, void 0, void 0, function* () { const [lengthRes, typeRes, bundleIdRes] = yield tap.service.interface.executeMultipleCalls([ tap.service.variable.getNumberOfElementsCall(id), tap.service.variable.getTypeCall(id), tap.service.variable.getBundleIdCall(id), ]); if (!lengthRes.isSuccessful()) { if (lengthRes.codeRet() === ResultCode.NOT_FOUND) { return undefined; } } const length = lengthRes.body(); let dbiot = undefined; if (length > 0) { const rawMetaResponse = yield tap.service.variable.getRawMeta(id); const rawData = rawMetaResponse.body(); if (rawData.length > 0) { try { dbiot = DBIOT_VARIABLE_CONFIG_CONVERTER.decode(rawData); } catch (err) { // Ignore if dbiot variable is not set property console.warn(`Cannot parse dbiot info for variable "${id}"`, err); return; } } } return { id, bundleId: bundleIdRes.body(), length, type: typeRes.body(), dbiot, }; }); } /** * Generated bundle index. Do not edit. */ export { DBIOTVarConfigConverter, DBIOT_VARIABLE_CONFIG_CONVERTER, TLVMappedEnumConverter, TLVMappedJsonArrayCompressor, createReadDBIOTConfigurationFromTapObservable, TLVMappedJsonIntegerCompressor as ɵa }; //# sourceMappingURL=iotize-tap-extra-dbiot.js.map