iobroker.e3oncan
Version:
Collect data on CAN bus for Viessmann E3 devices, e.g. Vitocal, Vitocharge, Energy Meters E380CA and E3100CB
869 lines (838 loc) • 34.4 kB
JavaScript
const E3 = require('./codecs');
const E3DidsDict = require('./didsE3.json');
const E380DidsDict = require('./didsE380.json');
const E3100CBDidsDict = require('./didsE3100CB.json');
const enums = require('./enums');
/**
* Perform data storage for specific DID from and to ioBroker objects data base
*/
class storageDids {
/**
* @param {object} config Device UDS worker configuration
*/
constructor(config) {
this.config = config;
this.didsWritablesId = 'udsDidsWritable';
this.didsCommonId = 'udsDidsCommon';
this.didsSpecId = 'udsDidsSpecific';
this.didsMetaDataId = 'udsDidsMetaData';
this.didsE380Id = 'didsE380';
this.didsE3100cbId = 'didsE3100CB';
this.didsWritable = {};
this.didsDictE3 = {}; // Common dids imported from project open3e
this.didsDictDevCom = {}; // Dids of this device matching the E3 common list
this.didsDictDevSpec = {}; // Dids specific for this device
this.dids = {}; // Consolidated list of dids available for this device
this.didsDevSpecAvail = false; // true, if device specific dids are available
this.didsMetaDict = {}; // Colloction of meta data od dids, e.g. unit, descriptiom
this.didsScanDone = false; // true, if a data point scan has been performed
this.imperialStr = String(enums.enums['Units']['1']);
}
/**
* Setup states in ioBroker object tree
*
* @param {object} ctx Caller context
* @param {string} opMode Initial mode of operation of device worker
*/
async initStates(ctx, opMode) {
if (['standby', 'normal', 'udsDidScan', 'service77'].includes(opMode)) {
switch (this.config.device) {
case 'e380':
await ctx.setObjectNotExistsAsync(`${this.config.stateBase}.info.${this.didsE380Id}`, {
type: 'state',
common: {
name: `${this.config.stateBase} all available datapoints`,
role: 'state',
type: 'json',
read: true,
write: true,
def: JSON.stringify({}),
},
native: {},
});
break;
case 'e3100cb':
await ctx.setObjectNotExistsAsync(`${this.config.stateBase}.info.${this.didsE3100cbId}`, {
type: 'state',
common: {
name: `${this.config.stateBase} all available datapoints`,
role: 'state',
type: 'json',
read: true,
write: true,
def: JSON.stringify({}),
},
native: {},
});
break;
default:
await ctx.setObjectNotExistsAsync(`${this.config.stateBase}.info.${this.didsWritablesId}`, {
type: 'state',
common: {
name: `${this.config.stateBase} list of datapoints writable via WriteByDid`,
role: 'state',
type: 'json',
read: true,
write: true,
def: JSON.stringify({}),
},
native: {},
});
await ctx.setObjectNotExistsAsync(`${this.config.stateBase}.info.${this.didsCommonId}`, {
type: 'state',
common: {
name: `${this.config.stateBase} all available datapoints`,
role: 'state',
type: 'json',
read: true,
write: true,
def: JSON.stringify({}),
},
native: {},
});
await ctx.setObjectNotExistsAsync(`${this.config.stateBase}.info.${this.didsSpecId}`, {
type: 'state',
common: {
name: `${this.config.stateBase} available datapoints specific to this device`,
role: 'state',
type: 'json',
read: true,
write: true,
def: JSON.stringify({}),
},
native: {},
});
await ctx.setObjectNotExistsAsync(`${this.config.stateBase}.info.${this.didsMetaDataId}`, {
type: 'state',
common: {
name: `${this.config.stateBase} meta data of dids of this device`,
role: 'state',
type: 'json',
read: true,
write: true,
def: JSON.stringify({}),
},
native: {},
});
}
}
}
/**
* Merge common and device specific DIDs to one object
*
* @param {object} didsCommon Caller context
* @param {object} didsDevSpecific Initial mode of operation
*/
async mergeDids(didsCommon, didsDevSpecific) {
const dids = await JSON.parse(JSON.stringify(didsCommon));
for (const [id, did] of Object.entries(didsDevSpecific)) {
if (id != 'Version') {
dids[id] = did;
}
}
return dids;
}
/**
* Return known DIDs from data base
*
* @param {object} ctx Caller context
* @param {string} opMode Initial mode of operation
*/
async readKnownDids(ctx, opMode) {
// Read common and device specific dids list known from dids scan
// If scanned dids are avalilable: return complete list of dids for this device
// else return list of common dids for all devices and list of writable dids
switch (this.config.device) {
case 'e380':
try {
const baseId = `${this.config.stateBase}.info.`;
this.didsWritable = {};
this.didsDictDevCom = await JSON.parse((await ctx.getStateAsync(baseId + this.didsE380Id)).val);
this.didsDictDevSpec = {};
} catch {
this.didsWritable = {};
this.didsDictDevCom = JSON.parse(JSON.stringify(E380DidsDict));
this.didsDictDevSpec = {};
}
if (Object.keys(this.didsDictDevCom).length == 0) {
// No dids scan results available yet
this.didsDictDevCom = JSON.parse(JSON.stringify(E380DidsDict));
this.didsDictDevCom.Version = '20240218'; // First available version
}
if (this.didsDictDevCom.Version < '20240320') {
// Add data points for E380 on CAN address=98 (available since version 20240320):
ctx.log.info('Adding support for energy meter E380 with CAN-address=98');
this.didsDictDevCom = await this.mergeDids(this.didsDictDevCom, E380DidsDict);
}
this.didsDevSpecAvail = true; // Dids are available even on first start
this.dids = await this.mergeDids(this.didsDictDevCom, this.didsDictDevSpec);
break;
case 'e3100cb':
try {
const baseId = `${this.config.stateBase}.info.`;
this.didsWritable = {};
this.didsDictDevCom = await JSON.parse((await ctx.getStateAsync(baseId + this.didsE3100cbId)).val);
this.didsDictDevSpec = {};
} catch {
this.didsWritable = {};
this.didsDictDevCom = JSON.parse(JSON.stringify(E3100CBDidsDict));
this.didsDictDevSpec = {};
}
if (Object.keys(this.didsDictDevCom).length == 0) {
// No dids scan results available yet
this.didsDictDevCom = JSON.parse(JSON.stringify(E3100CBDidsDict));
}
this.didsDevSpecAvail = true; // Dids are available even on first start
this.dids = await this.mergeDids(this.didsDictDevCom, this.didsDictDevSpec);
break;
default:
if (opMode != 'udsDevScan') {
try {
const baseId = `${this.config.stateBase}.info.`;
this.didsWritable = await JSON.parse(
(await ctx.getStateAsync(baseId + this.didsWritablesId)).val,
);
this.didsDictDevCom = await JSON.parse(
(await ctx.getStateAsync(baseId + this.didsCommonId)).val,
);
this.didsDictDevSpec = await JSON.parse(
(await ctx.getStateAsync(baseId + this.didsSpecId)).val,
);
this.didsDevSpecAvail = true;
this.didsMetaDict = await JSON.parse(
(await ctx.getStateAsync(baseId + this.didsMetaDataId)).val,
);
} catch {
// Device specific data not available yet
this.didsWritable = {};
this.didsDictDevCom = E3DidsDict;
this.didsDictDevSpec = {};
this.didsDevSpecAvail = false;
this.didsMetaDict = {};
}
if (Object.keys(this.didsDictDevCom).length == 0) {
// No dids scan results available yet
this.didsDictDevCom = E3DidsDict;
}
} else {
// UDS device scan
this.didsWritable = {};
this.didsDictDevCom[ctx.udsDidForScan] = E3DidsDict[ctx.udsDidForScan];
this.didsDictDevCom[ctx.udsDidForUnits] = E3DidsDict[ctx.udsDidForUnits];
this.didsDictDevSpec = {};
this.didsMetaDict = {};
}
if (opMode == 'udsDidScan') {
this.didsDictDevCom = {};
this.didsMetaDict = {};
this.dids = E3DidsDict;
} else {
this.dids = await this.mergeDids(this.didsDictDevCom, this.didsDictDevSpec);
}
}
this.didsScanDone = Object.keys(this.didsWritable).length > 0;
}
/**
* Store known DIDs to data base
*
* @param {object} ctx Caller context
*/
async storeKnownDids(ctx) {
const baseId = `${this.config.stateBase}.info.`;
switch (this.config.device) {
case 'e380':
await ctx.setStateAsync(baseId + this.didsE380Id, {
val: JSON.stringify(this.didsDictDevCom),
ack: true,
});
break;
case 'e3100cb':
await ctx.setStateAsync(baseId + this.didsE3100cbId, {
val: JSON.stringify(this.didsDictDevCom),
ack: true,
});
break;
default:
await ctx.setStateAsync(baseId + this.didsWritablesId, {
val: JSON.stringify(this.didsWritable),
ack: true,
});
await ctx.setStateAsync(baseId + this.didsCommonId, {
val: JSON.stringify(this.didsDictDevCom),
ack: true,
});
await ctx.setStateAsync(baseId + this.didsSpecId, {
val: JSON.stringify(this.didsDictDevSpec),
ack: true,
});
await ctx.setStateAsync(baseId + this.didsMetaDataId, {
val: JSON.stringify(this.didsMetaDict),
ack: true,
});
}
}
/**
* Convert integer to hex string of length 2
*
* @param {number} d integer
*/
toHex(d) {
return `00${Number(d).toString(16)}`.slice(-2);
}
/**
* Convert byte array to hex string
*
* @param {Array} arr byte array
*/
arr2Hex(arr) {
let hs = '';
for (const v in arr) {
hs += this.toHex(arr[v]);
}
return hs;
}
/**
* Convert hex string, e.g. '21A8' to byte array: [33,168]
*
* @param {string} hs hex string
*/
toByteArray(hs) {
const ba = [];
for (let i = 0; i < hs.length / 2; i++) {
ba.push(parseInt(hs.slice(2 * i, 2 * i + 2), 16));
}
return ba;
}
/**
* Return DID as string
*
* @param {string} did DID
*/
getDidStr(did) {
let didStr = '';
if (this.config.device == 'e3100cb') {
didStr = String(did);
} else {
didStr = `000${String(did)}`;
didStr = didStr.slice(-4);
}
return didStr;
}
/**
* Return structure of definition of DID
*
* @param {object} ctx Caller context
* @param {Array} didStruct structure of DID
* @param {object} obj remaining part to be evaluated
*/
async getDidStruct(ctx, didStruct, obj) {
try {
if (typeof obj == 'object') {
if (obj.codec) {
await didStruct.push([obj.codec, obj.len]);
}
if (Object.keys(obj).length <= 100) {
for (const itm of Object.values(obj)) {
if (itm.codec) {
await didStruct.push([itm.codec, itm.len]);
}
await this.getDidStruct(ctx, didStruct, itm);
}
} else {
ctx.log.error(`Did valuation aborted. Too many members (${String(Object.keys(obj).length)})`);
}
}
return didStruct;
} catch (e) {
ctx.log.error(`Evaluation of did ${JSON.stringify(didStruct)} failed. err=${e.message}`);
}
}
/**
* Return parsed content of state
*
* @param {object} ctx Caller context
* @param {string} stateId id of requested state
*/
async getObjectVal(ctx, stateId) {
let val = null;
try {
val = await JSON.parse((await ctx.getStateAsync(stateId)).val);
} catch (e) {
ctx.log.silly(`Reading of did ${stateId} failed. err=${e.message}`);
}
return val;
}
/**
* Store content of DID to data base
*
* @param {object} ctx Caller context
* @param {string} did DID
* @param {string} idStr DID string
* @param {string} stateId id of affected state
* @param {object} obj DIDs content
* @param {string} type Type of content (number or object)
* @param {string} role role of object
* @param {boolean} forceExtendObject Force to override object data
*/
async storeObject(ctx, did, idStr, stateId, obj, type, role, forceExtendObject = false) {
try {
if (ctx.suppressStateStorage && !(await ctx.objectExists(stateId))) {
// During scan using suppressStateStorage=true only store (override) existing states
// This is to force metadata updates, if any
return;
}
if (forceExtendObject || !(await ctx.objectExists(stateId))) {
// Override object properties, e.g. data type
let metaData = { desc: '', unit: '' };
metaData = await this.getMetaData(this.didsMetaDict, stateId);
await ctx.extendObject(stateId, {
type: 'state',
common: {
name: idStr,
type: type,
role: role,
read: true,
write: true,
unit: metaData.unit ?? '',
desc: metaData.desc ?? '',
},
native: {},
});
}
if (type == 'number') {
await ctx.setStateAsync(stateId, obj, true);
} else {
await ctx.setStateAsync(stateId, JSON.stringify(obj), true);
}
} catch (e) {
ctx.log.error(`Storing of did ${stateId}.${String(did)} failed. err=${e.message}`);
}
}
/**
* Store content of DID to data base using json format
*
* @param {object} ctx Caller context
* @param {string} did DID
* @param {string} idStr DID string
* @param {string} stateId id of affected state
* @param {object} obj DIDs content
* @param {boolean} forceExtendObject Force to override object data
*/
async storeObjectJson(ctx, did, idStr, stateId, obj, forceExtendObject = false) {
await this.storeObject(ctx, did, idStr, stateId, obj, 'string', 'json', forceExtendObject);
}
/**
* Store meta data of a codec element to dictionary
*
* @param {object} idDict Dict of meta data of codec
* @param {string} key Dict key
*/
async getMetaData(idDict, key) {
const didKey = key.replace(/TopologyElement\.\d*/, 'TopologyElement').replace(/\.json|\.tree|\.raw/, '');
if (didKey in idDict) {
return idDict[didKey];
}
return { desc: '', unit: '' };
}
/**
* Store meta data of a codec element to dictionary
*
* @param {object} idDict Dict of meta data of codec
* @param {object} cdc Element of codec
* @param {string} key Dict key
* @param {string} unitsStr UnitAndFormats configImperialuration string of device
*/
async storeMetaData(idDict, cdc, key, unitsStr) {
idDict[key] = {};
idDict[key]['desc'] = cdc.desc;
if ('unit' in cdc) {
let unit = cdc.unit;
if (unit == '°C' && unitsStr && unitsStr.includes(this.imperialStr)) {
unit = '°F'; // Devices uses imperial units => use °F instead of °C
}
idDict[key]['unit'] = unit;
}
if ('acc' in cdc) {
idDict[key]['acc'] = cdc.acc;
}
}
/**
* Collect meta data of a codec to dictionary
*
* @param {object} didInfo Codec
* @param {string} key Dict key
* @param {string} unitsStr UnitAndFormats configuration string of device
*/
async setupCodecById(didInfo, key, unitsStr) {
if (Array.isArray(didInfo)) {
for (const e of didInfo.values()) {
await this.storeMetaData(this.didsMetaDict, e.args, `${key}${e.id}`, unitsStr);
if ('subTypes' in e.args) {
await this.setupCodecById(e.args.subTypes, `${key}${e.id}.`, unitsStr);
}
}
} else {
await this.storeMetaData(this.didsMetaDict, didInfo.args, `${key}${didInfo.id}`, unitsStr);
if ('subTypes' in didInfo.args) {
await this.setupCodecById(didInfo.args.subTypes, `${key}${didInfo.id}.`, unitsStr);
}
}
}
/**
* Store content of DID to data base using tree format, i.e. splitting object down to single elements
*
* @param {object} ctx Caller context
* @param {string} did DID
* @param {string} idStr DID string
* @param {string} stateId id of affected state
* @param {object} obj DIDs content
* @param {boolean} forceExtendObject Force to override object data
*/
async storeObjectTree(ctx, did, idStr, stateId, obj, forceExtendObject = false) {
if (typeof obj == 'object') {
if (Object.keys(obj).length <= 100) {
for (const [key, itm] of Object.entries(obj)) {
await this.storeObjectTree(
ctx,
did,
idStr,
`${String(stateId)}.${String(key).replace(ctx.FORBIDDEN_CHARS, '_').replace('.', '_')}`,
itm,
forceExtendObject,
);
// No FORBIDDEN_CHARS and no '.' in state id allowed
}
} else {
ctx.log.error(
`Did valuation aborted. Too many members (${String(Object.keys(obj).length)}) ${stateId}.${String(
did,
)}`,
);
}
} else {
const type = typeof obj === 'number' ? 'number' : 'string';
await this.storeObject(ctx, did, idStr, stateId, obj, type, 'state', forceExtendObject);
}
}
/**
* Store content of DID to data base using tree format, i.e. splitting object down to single elements
*
* @param {object} ctx Caller context
* @param {object} ctxWorker Worker context
* @param {string} did DID
* @param {object} cdi contect of codec
* @param {object} data raw data
*/
async decodeDid(ctx, ctxWorker, did, cdi, data) {
let codec;
const res = {};
try {
codec = await new E3.O3Ecodecs[cdi.codec](
cdi.len,
cdi.id,
cdi.args,
ctxWorker.config.devUnits == 'n/a' ? ctx.udsMasterDevUnits : ctxWorker.config.devUnits,
);
} catch (e) {
ctx.log.warn(`Could not retreive codec for ${ctxWorker.config.stateBase}.${String(did)}. err=${e.message}`);
codec = 'RawCodec';
}
// No FORBIDDEN_CHARS and no '.' in state allowed:
res.idStr = cdi.id.replace(ctx.FORBIDDEN_CHARS, '_').replace('.', '_');
try {
res.val = await codec.decode(data);
} catch (e) {
res.val = this.arr2Hex(data);
ctx.log.warn(`Codec failed: ${ctxWorker.config.stateBase}.${String(did)}. err=${e.message}`);
}
return res;
}
}
/**
* Perform data storage from and to ioBroker objects data base
*/
class storage {
/**
* @param {object} config Device UDS worker configuration
*/
constructor(config) {
this.config = config;
this.storageDids = new storageDids({
stateBase: this.config.stateBase,
device: this.config.device,
});
this.opModes = ['standby', 'udsDevScan', 'udsDidScan', 'normal', 'TEST', 'service77'];
this.opMode = this.opModes[0];
this.udsScanResult = null;
}
/**
* Setup states in ioBroker object tree
*
* @param {object} ctx Caller context
* @param {string} opMode Initial mode of operation of device worker
*/
async initStates(ctx, opMode) {
await this.setOpMode(opMode);
if (opMode != 'udsDevScan') {
await ctx.setObjectNotExistsAsync(this.config.stateBase, {
type: 'device',
common: {
name: this.config.stateBase,
},
native: {},
});
await ctx.setObjectNotExistsAsync(`${this.config.stateBase}.info`, {
type: 'channel',
common: {
name: `${this.config.stateBase} informations`,
},
native: {},
});
await ctx.setObjectNotExistsAsync(`${this.config.stateBase}.info.${this.config.statId}`, {
type: 'state',
common: {
name: `${this.config.stateBase} statistics about communication`,
role: 'info.status',
type: 'json',
read: true,
write: true,
def: JSON.stringify({}),
},
native: {},
});
await this.storageDids.initStates(ctx, opMode);
if (opMode != 'service77') {
await ctx.setObjectNotExistsAsync(`${this.config.stateBase}.json`, {
type: 'channel',
common: {
name: `${this.config.stateBase} JSON`,
},
native: {},
});
await ctx.setObjectNotExistsAsync(`${this.config.stateBase}.tree`, {
type: 'channel',
common: {
name: `${this.config.stateBase} TREE`,
},
native: {},
});
await ctx.setObjectNotExistsAsync(`${this.config.stateBase}.raw`, {
type: 'channel',
common: {
name: `${this.config.stateBase} RAW`,
},
native: {},
});
}
}
await this.storageDids.readKnownDids(ctx, opMode);
}
/**
* Set operation mode
*
* @param {string} mode Mode of operation of device worker
*/
async setOpMode(mode) {
if (this.opModes.includes(mode)) {
this.opMode = mode;
}
}
/**
* Return current operation mode
*/
async getOpMode() {
return this.opMode;
}
/**
* Store statistical data to data base
*
* @param {object} ctx Adapter context
* @param {object} ctxWorker Worker context
* @param {boolean} forceStore Force storage
*/
async storeStatistics(ctx, ctxWorker, forceStore) {
if (['standby', 'normal', 'udsDidScan', 'service77'].includes(await this.getOpMode())) {
const ts = new Date().getTime();
if (!forceStore && ts < ctxWorker.stat.nextTs) {
return;
} // Min. time step not reached. Do not store.
if (ctxWorker.stat) {
ctx.setStateAsync(
`${ctxWorker.config.stateBase}.info.${this.config.statId}`,
JSON.stringify(ctxWorker.stat),
true,
);
}
ctxWorker.stat.nextTs = ts + ctxWorker.stat.tsMinStep;
}
}
/**
* Encode data for given did, return raw data
*
* @param {object} ctx Caller context
* @param {object} ctxWorker Worker context
* @param {string} did DID
* @param {object} data raw data
*/
async encodeDataCAN(ctx, ctxWorker, did, data) {
// Encode data for given did
let val;
if (did in this.storageDids.dids) {
const cdi = this.storageDids.dids[did]; // Infos about did codec
try {
const codec = await new E3.O3Ecodecs[cdi.codec](
cdi.len,
cdi.id,
cdi.args,
ctxWorker.config.devUnits == 'n/a' ? ctx.udsMasterDevUnits : ctxWorker.config.devUnits,
);
val = await codec.encode(data);
} catch (e) {
await ctx.log.warn(
`encodeDataCAN(): Could not encode data for ${ctxWorker.config.stateBase}.${String(did)}. err=${
e.message
}`,
);
val = null;
}
} else {
await ctx.log.warn(`encodeDataCAN(): Did not found for ${ctxWorker.config.stateBase}.${String(did)}`);
val = null;
}
return val;
}
/**
* Decode CAN data for given did
*
* @param {object} ctx Caller context
* @param {object} ctxWorker Worker context
* @param {string} did DID
* @param {Array} data raw data
*/
async decodeDataCAN(ctx, ctxWorker, did, data) {
if (this.opMode == this.opModes[0]) {
return;
}
const raw = this.storageDids.arr2Hex(data);
let idStr, val, common, cdi;
if (did in this.storageDids.dids) {
cdi = this.storageDids.dids[did]; // Infos about did codec
if (cdi.len == data.length) {
const res = await this.storageDids.decodeDid(ctx, ctxWorker, did, cdi, data);
idStr = res.idStr;
val = res.val;
common = true;
} else {
// did length is diffetent from common did length => device specific did
idStr = 'DeviceSpecific';
val = raw;
common = false;
}
} else {
idStr = 'DeviceSpecific';
val = raw;
common = false;
}
if (val != null) {
let didStr;
let stateIdJson;
let stateIdTree;
let stateIdRaw;
didStr = this.storageDids.getDidStr(did);
stateIdJson = `${this.config.stateBase}.json.${didStr}_${idStr}`;
stateIdTree = `${this.config.stateBase}.tree.${didStr}_${idStr}`;
stateIdRaw = `${this.config.stateBase}.raw.${didStr}_${idStr}`;
let didInfo;
switch (this.opMode) {
case this.opModes[0]: // 'standby'
break;
case this.opModes[1]: // 'udsDevScan'
this.udsScanResult = {
did: did,
didInfo: {
id: idStr,
len: data.length,
},
val: val,
common: common,
};
if (ctxWorker.callback) {
await ctxWorker.callback(ctx, ctxWorker, ['ok', this.udsScanResult]);
}
break;
case this.opModes[2]: // 'udsDidScan'
if (common) {
didInfo = {
id: idStr,
len: cdi.len,
codec: cdi.codec,
args: cdi.args,
};
} else {
didInfo = {
id: idStr,
len: data.length,
codec: 'RawCodec',
args: {},
};
}
this.udsScanResult = {
did: did,
didInfo: didInfo,
val: val,
common: common,
};
if (ctxWorker.callback) {
await ctxWorker.callback(ctx, ctxWorker, ['ok', this.udsScanResult]);
// Redo decoding to take care on variant dids:
const rescb = await this.storageDids.decodeDid(
ctx,
ctxWorker,
did,
this.udsScanResult.didInfo,
data,
);
idStr = await rescb.idStr;
val = await rescb.val;
didStr = this.storageDids.getDidStr(did);
stateIdJson = `${this.config.stateBase}.json.${didStr}_${idStr}`;
stateIdTree = `${this.config.stateBase}.tree.${didStr}_${idStr}`;
stateIdRaw = `${this.config.stateBase}.raw.${didStr}_${idStr}`;
}
// Use the scan result didInfo (may have been updated by callback for variant DIDs)
// and ensure the id matches the sanitized idStr used for the state paths:
didInfo = { ...this.udsScanResult.didInfo, id: idStr };
await this.storageDids.setupCodecById(
didInfo,
`${this.config.stateBase}.${didStr}_`,
await (!this.config.devUnits || this.config.devUnits == 'n/a'
? ctx.udsMasterDevUnits
: this.config.devUnits), // Use udsMasterDevUnits if config.devUnits equals 'n/a' or is undefined
); // Add dids meta data to dictionary
await this.storageDids.storeObjectTree(ctx, did, idStr, stateIdTree, val, true);
await this.storageDids.storeObjectJson(ctx, did, idStr, stateIdJson, val, true);
await this.storageDids.storeObjectJson(ctx, did, idStr, stateIdRaw, raw, true);
await this.storeStatistics(ctx, ctxWorker, false);
break;
case this.opModes[3]: // 'normal'
await this.storageDids.storeObjectTree(ctx, did, idStr, stateIdTree, val);
await this.storageDids.storeObjectJson(ctx, did, idStr, stateIdJson, val);
await this.storageDids.storeObjectJson(ctx, did, idStr, stateIdRaw, raw);
await this.storeStatistics(ctx, ctxWorker, false);
break;
case this.opModes[4]: // 'TEST'
return val;
case this.opModes[5]: // 'service77' - user specific mode for writeDataByIdentifier
await this.storeStatistics(ctx, ctxWorker, true);
break;
default:
ctx.log.error('Invalid opMode at class storage. Change to "standby"');
this.opMode = this.opModes[0];
}
}
}
}
module.exports = {
storageDids,
storage,
};