UNPKG

@iotize/tap

Version:

IoTize Device client for Javascript

1,263 lines (1,248 loc) 57.9 kB
import { BehaviorSubject, never, timer, empty, combineLatest, Subject, Observable } from 'rxjs'; import { flatMap, share, switchMap, map, startWith, first } from 'rxjs/operators'; import { CodeError, isCodeError } from '@iotize/common/error'; import { VariableFormat, TapError } from '@iotize/tap'; import { StringConverter, ArrayConverter, FloatConverter, NumberConverter, BooleanConverter, TlvBundleConverter, TapStreamWriter, TapStreamReader } from '@iotize/tap/client/impl'; import { VariableType } from '@iotize/tap/service/impl/variable'; import { ResultCode } from '@iotize/tap/client/api'; import '@iotize/tap/service/impl/target'; import '@iotize/tap/service/impl/interface'; var MonitorEngine; (function (MonitorEngine) { let State; (function (State) { State[State["START"] = 0] = "START"; State[State["PAUSE"] = 1] = "PAUSE"; State[State["STOP"] = 2] = "STOP"; })(State = MonitorEngine.State || (MonitorEngine.State = {})); })(MonitorEngine || (MonitorEngine = {})); var __awaiter$a = (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()); }); }; const TAG$1 = 'TapMonitorEngine'; class MonitoringController { constructor(initalState = INITAL_MONITORING_CONFIG) { this.initalState = initalState; this.subject = new BehaviorSubject(this.initalState); } get state() { return this.subject.value.state; } start(options) { this.subject.next(Object.assign({ state: MonitorEngine.State.START, }, options)); return this; } stop() { this.subject.next({ state: MonitorEngine.State.STOP, }); return this; } pause() { this.subject.next({ state: MonitorEngine.State.PAUSE, }); return this; } setPeriod(period) { this.subject.next({ period, }); } asSubject() { return this.subject; } } function createDataStreamMonitor(stream, ticker, eventSubject) { return ticker.pipe(flatMap((tickerState) => __awaiter$a(this, void 0, void 0, function* () { // debug(TAG, `Running call at time ${time}`); return stream .readRaw() .then((value) => { // TODO event ? return value; }) .catch((error) => { eventSubject.next({ type: 'error', error, stream, timerState: tickerState, }); }); })), // filter<DataType | undefined, DataType>((value): value is DataType => { // return value !== undefined; // }), share()); } const INITAL_MONITORING_CONFIG = { dueTime: 0, period: 1000, refresh: false, state: MonitorEngine.State.STOP, }; function createMonitoringTicker( // call: (time: number) => Promise<DataType>, controller) { const controllerSubject = controller instanceof MonitoringController ? controller.asSubject() : controller; let lastState = Object.assign(Object.assign({}, INITAL_MONITORING_CONFIG), controllerSubject.value); return controllerSubject.pipe(switchMap((partialState) => { const newState = Object.assign(Object.assign({}, lastState), partialState); lastState = newState; // console.log(TAG, 'Apply new monitoring state: ', newState); switch (newState.state) { case MonitorEngine.State.STOP: return empty(); case MonitorEngine.State.START: return timer(newState.dueTime, newState.period).pipe(map((time) => ({ time, state: newState, }))); case MonitorEngine.State.PAUSE: return never(); default: throw new Error(`InternalErorr: Not implemented monitoring state: ${newState.state}`); } })); } class TapDataError extends CodeError { constructor(msg, code, cause) { super(msg, code); this.cause = cause; } static unexpectedEncodedValueError(variable, rawData, err) { return new TapDataError(`An error occured while encoding value for variable ${variable.id}. ${err.message}`, TapDataError.Code.UnexpectedEncodedValueError); } static unexpectedVariableDataRead(variable, rawData, err) { return new TapDataError(`An error occured while decoding value for variable ${variable.id}. ${err.message}`, TapDataError.Code.UnexpectedVariableData); } static bundleNotFound(key) { return new TapDataError(`Bundle with identifier "${key.toString()}" is not registered`, TapDataError.Code.BundleNotFound); } static variableNotFound(key) { return new TapDataError(`Variable with identifier "${key.toString()}" is not registered`, TapDataError.Code.VariableNotFound); } static bundleNotConfigured(config, err) { return new TapDataError(`Bundle with id "${config.id}" does not exist on this Tap. You should reconfigure your Tap to add this bundle.`, TapDataError.Code.BundleNotConfigured, err); } static variableNotConfigured(config, err) { return new TapDataError(`Variable with id "${config.id}" does not exist on this Tap. You should reconfigure your Tap to add this variable.`, TapDataError.Code.VariableNotConfigured, err); } } (function (TapDataError) { let Code; (function (Code) { Code["UnexpectedVariableData"] = "TapDataErrorUnexpectedVariableData"; Code["UnexpectedEncodedValueError"] = "TapDataErrorUnexpectedEncodedValueError"; Code["BundleNotFound"] = "TapDataErrorBundleNotFound"; Code["VariableNotFound"] = "TapDataErrorVariableNotFound"; Code["BundleNotConfigured"] = "TapDataErrorBundleNotConfigured"; Code["VariableNotConfigured"] = "TapDataErrorVariableNotConfigured"; })(Code = TapDataError.Code || (TapDataError.Code = {})); })(TapDataError || (TapDataError = {})); const ɵ0 = (v) => v, ɵ1 = (v) => v; const BLANK_CONVERTER = { encode: ɵ0, decode: ɵ1, }; const ASCII_WITH_END_OF_STRING_CHAR_CONVERTER = new StringConverter('ascii', { addEndOfStringCharacter: true, replaceEmptyStringWithEndOfLineChar: true, stripEndOfStringCharacter: true, }); function computeByteLengthFromFormat(info) { return Math.ceil(computeBitLengthFromFormat(info) / 8); } function computeBitLengthFromFormat(info) { return info.length * variableFormatToBitSize(info.format); } function variableFormatToByteSize(format) { return Math.ceil(variableFormatToBitSize(format) / 8); } function variableFormatToBitSize(format) { switch (format) { case VariableFormat._1_BIT: return 1; case VariableFormat._8_BITS: return 8; case VariableFormat._16_BITS: return 16; case VariableFormat._32_BITS: return 32; default: // TODO better error throw new Error(`InternalError: missing bit length for format value value "${format}" (name: ${VariableFormat[format]})`); } } function variableDataTypeToByteSize(dataType) { return typeConfig[dataType].size; } /** * Map Variable Type to a byte size when encoded as a Tap frame. */ const typeConfig = { [VariableType.Data.BOOLEAN]: { size: 1, }, [VariableType.Data.UINT8]: { size: 1, }, [VariableType.Data.UINT16]: { size: 2, }, [VariableType.Data.UINT32]: { size: 4, }, [VariableType.Data.INT8]: { size: 1, }, [VariableType.Data.INT16]: { size: 2, }, [VariableType.Data.INT32]: { size: 4, }, [VariableType.Data.FLOAT32]: { size: 4, }, [VariableType.Data.ASCII]: { size: 1, }, }; function getConverterFromVariableConfig(variableConfig, stringConverter = ASCII_WITH_END_OF_STRING_CHAR_CONVERTER) { const typeKey = variableConfig.dataType; if (!(typeKey in VariableType.Data)) { throw new Error(`Unknown variable type key "${typeKey}"`); // TODO better error } return getConverterFromVariableType(VariableType.Data[typeKey], variableConfig.length || 1, stringConverter); } /** * Get converter from variable type and length (number of element) * @param dataType * @param length > 1 if it's an array. * * @return undefined if converter does not exist for the given type */ function getConverterFromVariableType(dataType, length = 1, stringConverter = ASCII_WITH_END_OF_STRING_CHAR_CONVERTER) { if (dataType === VariableType.Data.ASCII) { return stringConverter; } const converter = getRawConverterFromVariableDataType(dataType); if (converter && length > 1) { return new ArrayConverter(converter, { sizeOfItem: variableDataTypeToByteSize(dataType), }); } else { return converter; } } // type TypeMapping = { // [VariableType.Data.ASCII]: string; // [VariableType.Data.FLOAT32]: number; // [VariableType.Data.INT32]: number; // [VariableType.Data.INT16]: number; // [VariableType.Data.INT8]: number; // [VariableType.Data.UINT8]: number; // [VariableType.Data.UINT16]: number; // [VariableType.Data.UINT32]: number; // } /** * Get raw converter instance from variable type * TODO improve output typing with mapping * @param format * @param length */ function getRawConverterFromVariableDataType(type) { switch (type) { case VariableType.Data.BOOLEAN: return BooleanConverter.instanceBit0(); case VariableType.Data.UINT8: return NumberConverter.uint8(); case VariableType.Data.UINT16: return NumberConverter.uint16(); case VariableType.Data.UINT32: return NumberConverter.uint32(); case VariableType.Data.INT8: return NumberConverter.int8(); case VariableType.Data.INT16: return NumberConverter.int16(); case VariableType.Data.INT32: return NumberConverter.int32(); case VariableType.Data.FLOAT32: return FloatConverter.instance32(); case VariableType.Data.ASCII: return StringConverter.ascii(); default: return undefined; } } var __awaiter$9 = (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()); }); }; class AbstractBundleDataStream { constructor(_variables) { this._variables = _variables; } get variables() { return Object.values(this._variables); } readRaw() { return __awaiter$9(this, void 0, void 0, function* () { const raw = yield this.readRawInner(); this.notifyRawValue(raw); return raw; }); } // abstract read(): Promise<DataType>; read() { return __awaiter$9(this, void 0, void 0, function* () { yield this.readRaw(); return this.valueSnapshot; }); } write(value) { return __awaiter$9(this, void 0, void 0, function* () { // if (typeof value !== 'object') { // throw TapDataError.illegalParameter(); // } for (const key in value) { yield this.variable(key).writeRaw(value[key]); } }); } variable(id) { if (!(id in this._variables)) { throw new Error(`Variable "${id}" not found in bundle "${this.id.toString()}"`); } return this._variables[id]; } get rawValues() { const variables = Object.values(this._variables); return combineLatest(variables.map((v) => v.rawValues.pipe(startWith(undefined)))).pipe(map((values) => { return values.reduce((accumulator, v, index) => { accumulator[variables[index].id] = v; // TODO return accumulator; }, {}); })); } get rawValueSnaphot() { const variables = Object.values(this._variables); return variables.reduce((accumulator, v, index) => { accumulator[variables[index].id] = v.rawValueSnaphot; return accumulator; }, {}); } get valueSnapshot() { const variables = Object.values(this._variables); return variables.reduce((accumulator, v, index) => { accumulator[variables[index].id] = v.valueSnapshot; return accumulator; }, {}); } get values() { const variables = Object.values(this._variables); return combineLatest(variables.map((v) => v.values.pipe(startWith(undefined)))).pipe(map((values) => { return values.reduce((accumulator, v, index) => { accumulator[variables[index].id] = v; return accumulator; }, {}); })); } notifyRawValue(v) { var _a; return __awaiter$9(this, void 0, void 0, function* () { for (const id in v) { const value = v[id]; if (value !== undefined) { (_a = this._variables[id]) === null || _a === void 0 ? void 0 : _a.notifyRawValue(value); } } }); } findVariable(id) { return id in this._variables ? this._variables[id] : undefined; } hasVariable(id) { return !!this.findVariable(id); } writeRaw(values) { return __awaiter$9(this, void 0, void 0, function* () { for (const key in values) { const value = values[key]; if (value !== undefined) { yield this.variable(key).writeRaw(value); } } }); } } class AbstractEditableDataStream { constructor() { // static fromCustom(arg0: CustomVariableConfig) { // return new Variable<T>(config, ); // } // public static fromConfig<T>(config: VariableConfig) { // return new Variable<T>(config, ); // } // public static fromMemory(config: MemoryInfoConfig) { // return new Variable<T>(config, ); // } this.rawDataStream = new Subject(); // = new Subject<RawType>(); // private selfReadStream = new Subject<RawType>(); this.values = this.rawDataStream.pipe(map((v) => this.decodeValue(v))); // if (dataStream) { // this.dataStream = merge( // dataStream, // this.selfReadStream // ); // } // else { // this.dataStream = this.selfReadStream; // } // this.dataStream = this.dataStream // .pipe( // tap(v => this.lastRawValue = v) // ); // // Do we need to subscribe ? // this.dataStream.subscribe(); } get lastRawValue() { return this._lastRawValue; } get rawValueSnaphot() { return this.lastRawValue; } get valueSnapshot() { if (this.lastRawValue === undefined) { return undefined; } return this.decodeValue(this.lastRawValue); } get rawValues() { return this.rawDataStream; } // { // const value = await this.rawReader(this.context); // this.notifyRawValue(value); // return value; // } notifyRawValue(newValue) { return __awaiter$9(this, void 0, void 0, function* () { this._lastRawValue = newValue; this.rawDataStream.next(newValue); }); } readRaw() { return __awaiter$9(this, void 0, void 0, function* () { const value = yield this.readRawInner(); this.notifyRawValue(value); return value; }); } read() { return __awaiter$9(this, void 0, void 0, function* () { const p = this.values.pipe(first()).toPromise(); yield this.readRaw(); return p; }); } write(value) { return __awaiter$9(this, void 0, void 0, function* () { // if (!this.rawWritter) { // throw new Error(`Cannot write value for ${this.id}`); // } const encoded = this.encodeValue(value); return this.writeRaw(encoded); }); } encodeValue(value) { try { return this.converter.encode(value); } catch (err) { throw TapDataError.unexpectedEncodedValueError(this, value, err); } } decodeValue(data) { try { return this.converter.decode(data); } catch (err) { throw TapDataError.unexpectedVariableDataRead(this, data, err); } } } var __awaiter$8 = (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()); }); }; /** * You should use ConverterVariableView instead */ class VariableView { constructor(id, context) { this.id = id; this.context = context; } get converter() { return this.context.converter; } get indexes() { return this.context.indexes; } get rawValues() { return this.context.variable.rawValues.pipe(map((rawData) => { return this.subDataView(rawData); })); } get valueSnapshot() { const lastRawValue = this.rawValueSnaphot; return lastRawValue !== undefined ? this.decodeValue(this.subDataView(lastRawValue)) : undefined; } get values() { return this.rawValues.pipe(map((v) => this.decodeValue(v))); } get rawValueSnaphot() { const parentValueSnapshot = this.context.variable.rawValueSnaphot; return parentValueSnapshot !== undefined ? this.subDataView(parentValueSnapshot) : parentValueSnapshot; } notifyRawValue(newValue) { } read() { return __awaiter$8(this, void 0, void 0, function* () { return this.decodeValue(yield this.readRaw()); }); } write(value) { return this.writeRaw(this.encodeValue(value)); } readRaw() { return __awaiter$8(this, void 0, void 0, function* () { const rawData = yield this.context.variable.readRaw(); return this.subDataView(rawData); }); } writeRaw(value) { return __awaiter$8(this, void 0, void 0, function* () { let existingData; if (this.context.useLastKnownValueBeforeWrite) { existingData = this.context.variable.rawValueSnaphot || (yield this.context.variable.readRaw()); } else { existingData = yield this.context.variable.readRaw(); } this.indexes.forEach((subIndex, index) => { existingData[subIndex] = value[index]; }); yield this.context.variable.writeRaw(existingData); }); } subDataView(rawData) { return this.indexes.reduce((acc, subIndex, index) => { acc[index] = rawData[subIndex]; return acc; }, new Uint8Array(this.indexes.length)); } encodeValue(value) { try { return this.converter.encode(value); } catch (err) { throw TapDataError.unexpectedEncodedValueError(this, value, err); } } decodeValue(data) { try { return this.converter.decode(data); } catch (err) { throw TapDataError.unexpectedVariableDataRead(this, data, err); } } } class AbstractVariable extends AbstractEditableDataStream { /** * Create a subvariable instance from the given variable index * @param key the uniq variable identifier to use for data manager * @param rawIndexes list of array index (starting from 0) to extract. Each index represents 1 byte of data * * @deprecated manually create your instance */ variableView(key, rawIndexes, options) { return new VariableView(key, { converter: (options === null || options === void 0 ? void 0 : options.converter) || BLANK_CONVERTER, indexes: rawIndexes, variable: this, useLastKnownValueBeforeWrite: options === null || options === void 0 ? void 0 : options.useLastKnownValueBeforeWrite, }); } } var __awaiter$7 = (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()); }); }; class ModbusAccessVariable extends AbstractVariable { constructor(id, context) { super(); this.id = id; this.context = context; } get converter() { return this.context.converter || BLANK_CONVERTER; } get config() { return this.context.config; } readRawInner() { return __awaiter$7(this, void 0, void 0, function* () { return (yield this.context.targetService.modbusRead(this.context.config)).body(); }); } writeRaw(value) { return __awaiter$7(this, void 0, void 0, function* () { if (!value || !(value instanceof Uint8Array)) { throw new Error(`Bad parameter for ModbusAccessVariable.writeRaw(). Expecting an Uint8Array.`); } const expectedByteSize = this.config.length * variableFormatToByteSize(this.config.format); if (value.length !== expectedByteSize) { throw new Error(`Encoded value does not have the expected byte size. Expecting ${expectedByteSize} byte(s) but given ${value.length} byte(s)`); } return (yield this.context.targetService.modbusWrite({ data: value, options: this.context.config, })).body(); }); } } var __awaiter$6 = (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()); }); }; function createTapVariableFromConfig(variableService, key, value) { return new TapVariable(key, { variableService: variableService, config: { id: value.id, dataType: value.dataType, length: value.length, }, converter: value.converter || (value.dataType !== undefined ? getConverterFromVariableType(value.dataType, value.length || 1) : BLANK_CONVERTER), }); } class TapVariable extends AbstractVariable { constructor( /** * Variable name identifier. */ id, context) { super(); this.id = id; this.context = context; } get variableId() { return this.context.config.id; } get converter() { return this.context.converter || BLANK_CONVERTER; } get config() { return this.context.config; } set converter(converter) { this.context.converter = converter; } readRawInner() { return __awaiter$6(this, void 0, void 0, function* () { try { let rawData = (yield this.context.variableService.getValue(this.context.config.id)).body(); if (this.context.subIndex) { rawData = this.context.subIndex.reduce((acc, subIndex, index) => { acc[index] = rawData[subIndex]; return acc; }, new Uint8Array(this.context.subIndex.length)); } return rawData; } catch (err) { if (isCodeError(TapError.Code.ResponseStatusError, err)) { switch (err.response.status) { case ResultCode.NOT_FOUND: throw TapDataError.variableNotConfigured(this.context.config, err); } } throw err; } }); } writeRaw(value) { return __awaiter$6(this, void 0, void 0, function* () { try { if (this.context.subIndex) { const existingData = (yield this.context.variableService.getValue(this.context.config.id)).body(); this.context.subIndex.forEach((subIndex, index) => { existingData[subIndex] = value[index]; }); value = existingData; } (yield this.context.variableService.setValue(this.context.config.id, value)).body(); } catch (err) { if (isCodeError(TapError.Code.ResponseStatusError, err)) { switch (err.response.status) { case ResultCode.NOT_FOUND: throw TapDataError.variableNotConfigured(this.context.config, err); } } throw err; } }); } } // TODO add Sub variable data stream (IE read/write index ) var __awaiter$5 = (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()); }); }; const TLV_BUNDLE_CONVERTER = new TlvBundleConverter(); function createTapBundleFromConfig(tap, name, config) { const variables = Object.keys(config.variables).reduce((acc, key) => { const value = config.variables[key]; acc[key] = createTapVariableFromConfig(tap.service.variable, key, value); return acc; }, {}); return new TapBundle(name, { tap, config: { id: config.id, }, }, variables); } class TapBundle extends AbstractBundleDataStream { constructor(_id, context, _variables) { super(_variables); this._id = _id; this.context = context; // this.converter = TLV_BUNDLE_CONVERTER; // super( // context, // undefined, // ); } get id() { return this._id; } get bundleId() { return this.context.config.id; } // addConfiguredVariable<T>(name: string, options: { // id: number; // dataType: VariableType.Data, // length?: number // }) { // if (this.hasVariable(options.id)) { // throw new Error(`Alredy has variable ${name}`); // } // // this.context.config.variables.push({ // // name, // // id: options.id // // }); // const variable = new ConfiguredVariableDataStream<DataType>( // // config as VariableConfigWithName, // { // tap: this.context.tap, // config: { // id: options.id, // name // } // }, // getConverterFromVariableType( // options.dataType, // options.length || 1 // ), // // dataStream, // ); // this._variables.push(variable); // } readRawInner() { return __awaiter$5(this, void 0, void 0, function* () { try { const variableIdMapping = (yield this.context.tap.service.bundle.getValues(this.bundleId)).body(TLV_BUNDLE_CONVERTER); const streamIdMapping = {}; // Partial<RawBundleType<DataType>> // for (const tapVariableId of vara) for (const variable of this.variables) { if (variable instanceof TapVariable) { if (variable.variableId in variableIdMapping) { streamIdMapping[variable.id] = variableIdMapping[variable.variableId]; } else { streamIdMapping[variable.id] = undefined; // if (!(variable.id in streamIdMapping)) { // streamIdMapping[variable.id] = variableIdMapping[] // } // (streamIdMapping as any)[variable.id] // TODO un mapped ? // throw new Error(`${variable.id} is not mapped`) } } } return streamIdMapping; } catch (err) { if (isCodeError(TapError.Code.ResponseStatusError, err)) { switch (err.response.status) { case ResultCode.NOT_FOUND: throw TapDataError.bundleNotConfigured(this.context.config, err); } } throw err; } }); } } var __awaiter$4 = (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()); }); }; class TargetMemoryVariable extends AbstractVariable { constructor(id, context) { super(); this.id = id; this.context = context; } get converter() { return this.context.converter || BLANK_CONVERTER; } get config() { return this.context.config; } readRawInner() { return __awaiter$4(this, void 0, void 0, function* () { return (yield this.context.targetService.readAddress(this.context.config)).body(); }); } writeRaw(value) { return __awaiter$4(this, void 0, void 0, function* () { if (!value || !(value instanceof Uint8Array)) { throw new Error(`Bad parameter for TargetMemoryVariable.writeRaw(). Expecting an Uint8Array.`); } const expectedByteSize = this.config.length * variableFormatToByteSize(this.config.format); if (value.length !== expectedByteSize) { // TODO better error throw new Error(`Encoded value does not have the expected byte size. Expecting ${expectedByteSize} byte(s) but given ${value.length} byte(s)`); } return (yield this.context.targetService.writeAddress({ data: value, info: this.context.config, })).body(); }); } } function createDataManagerConfigFromTapDataConfig(config) { var _a; const bundles = (_a = config === null || config === void 0 ? void 0 : config.bundles) === null || _a === void 0 ? void 0 : _a.reduce((acc, bundle) => { var _a; const bundleName = (bundle.name || bundle.id); const variablesConfig = (_a = bundle.variables) === null || _a === void 0 ? void 0 : _a.reduce((acc, v) => { const variableName = (typeof v.meta === 'object' ? v.meta.name : v.id) || v.id; acc[variableName] = { id: v.id, dataType: VariableType.Data[v.dataType], length: v.length || 1, converter: getConverterFromVariableConfig(v), }; return acc; }, {}); const bundleConfig = { id: bundle.id, variables: variablesConfig || {}, }; acc[bundleName] = bundleConfig; return acc; }, {}); return { bundles, }; } var __awaiter$3 = (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()); }); }; function createTapBundlesFromConfig(tap, bundles) { const bundleDataStreams = {}; for (const name in bundles) { bundleDataStreams[name] = createTapBundleFromConfig(tap, name, bundles[name]); } return bundleDataStreams; } function createDataManagerFromTapConfig(tap, config) { const { bundles } = config; return new TypedDataManager(tap, createTapBundlesFromConfig(tap, bundles)); } function readDataManagerTapConfigFromTap(tap, config = { bundleIdNameMap: {}, variableIdNameMap: {}, }) { return __awaiter$3(this, void 0, void 0, function* () { const { bundleIdNameMap, variableIdNameMap, observer } = config; (yield tap.service.target.connect()).successful(); const readProfileResponse = yield tap.service.variable.readProfile(); // if (!readProfileResponse.isSuccessful()) { // switch (readProfileResponse.status) { // case ResultCode.SERVICE_UNAVAILABLE: // // If service is unavailable, it probably means that target is not connected yet // (await tap.service.target.connect()).successful(); // readProfileResponse = await tap.service.variable.readProfile(); // break; // default: // // Throw error otherwise // readProfileResponse.successful(); // } // } readProfileResponse.successful(); const rawBundleData = readProfileResponse.rawBody(); const profileData = TLV_BUNDLE_CONVERTER.decode(rawBundleData); const bundles = {}; const variableCount = Object.keys(profileData).length; let index = 0; observer === null || observer === void 0 ? void 0 : observer.next({ step: 'start-read-variable-config', profileData, progress: { loaded: index, total: variableCount, }, }); for (const variableId in profileData) { const variableIdInt = parseInt(variableId, 10); if (isNaN(variableIdInt)) { throw new Error(`Invalid variable integer identifier: ${variableId}`); continue; } observer === null || observer === void 0 ? void 0 : observer.next({ step: 'read-variable', variableId: variableIdInt, progress: { loaded: index + 1, total: variableCount, }, }); const { bundleId, variableName, config: variableInfo, } = yield readTapVariableConfigFromTap(tap, variableIdInt, variableIdNameMap ? variableIdNameMap[variableIdInt] : undefined); let bundleName; if (bundleIdNameMap && bundleId in bundleIdNameMap) { bundleName = bundleIdNameMap[bundleId]; } else { let readBundleName = (yield tap.service.bundle.getName(bundleId)).body(); // Fix bug on firmware versions that returns fixed size for bundle name (at least until firmware v1.106) const endOfStringIndex = readBundleName.indexOf('\u0000'); if (endOfStringIndex >= 0) { readBundleName = readBundleName.substr(0, endOfStringIndex); } if (!readBundleName) { bundleName = bundleId; } else { bundleName = readBundleName; } } if (!(bundleName in bundles)) { bundles[bundleName] = { id: bundleId, variables: {}, }; } else { // TODO what to do when duplicated bundle name ? } bundles[bundleName].variables[variableName] = variableInfo; index++; } observer === null || observer === void 0 ? void 0 : observer.next({ step: 'end-read-variable-config', progress: { loaded: variableCount, total: variableCount, }, }); return bundles; }); } function readTapVariableConfigFromTap(tap, variableId, variableName) { return __awaiter$3(this, void 0, void 0, function* () { const [typeResponse, bundleIdResponse, lengthResponse, metaResponse] = yield tap.service.interface.executeMultipleCalls([ tap.service.variable.getTypeCall(variableId), tap.service.variable.getBundleIdCall(variableId), tap.service.variable.getNumberOfElementsCall(variableId), tap.service.variable.getMetaCall(variableId), ]); const bundleId = bundleIdResponse.body(); const variableType = typeResponse.body(); const length = lengthResponse.body(); const dataType = variableType.data; if (!variableName) { const meta = metaResponse.body(); variableName = meta.name || variableId.toString(); } return { bundleId, variableName, config: { id: variableId, dataType, length, }, }; }); } var __awaiter$2 = (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()); }); }; const TAG = 'DataManager'; /** * Typed data manager is usually build from Tap configuration * * It allow you to have compile time check for bundle/variable types. * However, you cannot add/remove/edit variables (as it would change types) * Use `DataManager` if you want this features */ class TypedDataManager { constructor(tap, bundles, // variables: Record<VariableKey, EditableDataStream<VariableByType[VariableKey]>> monitoring = new MonitoringController()) { // const variablesCache: Record<string, EditableDataStream<VariableByType[VariableKey], unknown>> = {}; // for (const bundleKey in this.bundles) { // const bundle = this.bundles[bundleKey]; // for (const variableKey in bundle.variables) { // const variable = bundle.variables[variableKey]; // variablesCache[variable.config.name] = variable; // } // } // this.variablesCache = variablesCache as Record<VariableKey, EditableDataStream<VariableByType[VariableKey], unknown>>; // this.setupStreams(); this.tap = tap; this.bundles = bundles; this.monitoring = monitoring; // switchableStream!: SwitchableStream; // dataStream: Observable<Record<string, any>>; // tlvConverter: TlvBundleConverter<Record<string, Uint8Array>>; // sourceControllers: Record<string, DataSourceController> = {}; // protected variablesCache: Record<VariableKey, EditableDataStream<VariableByType[VariableKey], unknown>>; // private tickers: Record<string, { // controller: MonitoringControllerSubject, // ticker: Observable<MonitoringTickerState> // }> = {}; // private ticker = createMonitoringTicker(this.monitoring); this._events = new Subject(); // this.variablesCache = {} as any; const ticker = createMonitoringTicker(this.monitoring); ticker.subscribe((newTick) => __awaiter$2(this, void 0, void 0, function* () { // console.log('New tick', newTick); yield this.refreshValues(); })); } get values() { const bundles = this.listBundles(); return combineLatest(bundles.map((v) => v.values.pipe(startWith(undefined)))).pipe(map((values) => { return values.reduce((accumulator, v, index) => { const bundleStreamId = bundles[index].id; accumulator[bundleStreamId] = v; return accumulator; }, {}); })); } get events() { return this._events.asObservable(); } listVariables() { return this.listBundles().reduce((acc, bundle) => { acc.push(...bundle.variables); return acc; }, []); } bundle(key) { if (!(key in this.bundles)) { throw TapDataError.bundleNotFound(key); } return this.bundles[key]; } // TODO fix typing variable(key) { const variable = this.listVariables().find((v) => v.id === key); if (!variable) { throw TapDataError.variableNotFound(key); } return variable; } // public addBundleFromConfig(key: BundleKey, bundleConfig: BundleConfig) { // // const key = bundleConfig.name || bundleConfig.id.toString(); // // this.bundles[key] = createTapBundleDataFromConfig(bundleConfig); // throw new Error(`Not implemented yet`); // return this; // } destroy() { this.monitoring.stop(); } refreshValues() { return __awaiter$2(this, void 0, void 0, function* () { const values = {}; for (const bundle of this.listBundles()) { values[bundle.id] = yield bundle .read() .catch((error) => { this._events.next({ error, type: DataManager.EventType.error, }); return error; }); } return values; }); } listBundles() { return Object.values(this.bundles); } // public setupStreams() { // // this.bundle('x').monitor.start(); // // this.variable('y').monitor. // const variableStream = this.ticker // .pipe( // switchMap(() => { // return merge( // this.listVariables().map(v => defer(() => v.read())) // ); // }) // ); // const bundleStream = this.ticker // .pipe( // switchMap(() => { // return merge( // this.listBundles().map(v => defer(() => v.read())) // ); // }) // ); // // const profileStream = this.ticker // // .pipe( // // switchMap(async () => { // // // debug(TAG, `New call ${time} to read profile`); // // const profileData = (await this.tap.service.variable.readProfile()).body(); // // const decoded = this.tlvConverter.decode(profileData); // // this.rawDataStream.next(decoded); // // }) // // ); // this.switchableStream // .addStream( // 'variable', // variableStream // ) // .addStream( // 'bundle', // bundleStream // ) // // .addStream( // // 'profile', // // profileStream // // ); // } hasBundle(bundleKey) { return bundleKey in this.bundles; } createConfiguredBundle(key, config) { return createTapBundleFromConfig(this.tap, key, config); } createConfiguredVariable(key, config) { return createTapVariableFromConfig(this.tap.service.variable, key, config); } /** * Create a variable with direct modbus access * May/May not be available according to the configured target protocol on your device * * @param id * @param options */ createModbusAccessVariable(id, options) { return new ModbusAccessVariable(id, { config: options.config, targetService: this.tap.service.target, converter: options.converter, }); } /** * Create direct target access to variable * May/May not be available according to configured target protocol on your device * @param id * @param options */ createTargetMemoryVariable(id, options) { return new TargetMemoryVariable(id, { config: options.config, targetService: this.tap.service.target, converter: options.converter, }); } } class DataManager extends TypedDataManager { constr