@iotize/tap
Version:
IoTize Device client for Javascript
782 lines (765 loc) • 29.8 kB
JavaScript
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