@iotize/tap
Version:
IoTize Device client for Javascript
1,263 lines (1,248 loc) • 57.9 kB
JavaScript
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