iobroker.openmediavault
Version:
732 lines (731 loc) • 41.9 kB
JavaScript
import _ from 'lodash';
import * as myHelper from './helper.js';
import * as tree from './tree/index.js';
export class myIob {
adapter;
log;
utils;
statesUsingValAsLastChanged = [];
subscribedStates = [];
statesWithWriteFunction = {};
constructor(adapter, utils, statesUsingValAsLastChanged = []) {
this.adapter = adapter;
this.log = adapter.log;
this.utils = utils;
this.statesUsingValAsLastChanged = statesUsingValAsLastChanged;
}
/**
* create or update a device object, update will only be done on adapter start
*
* @param id
* @param name
* @param onlineId
* @param errorId
* @param icon
* @param updateObject
* @param logChanges
* @param native
*/
async createOrUpdateDevice(id, name, onlineId = undefined, errorId = undefined, icon = undefined, updateObject = false, logChanges = true, native = {}) {
const logPrefix = '[myIob.createOrUpdateDevice]:';
try {
if (typeof name === 'string') {
const translation = this.utils.I18n.getTranslatedObject(name);
name = translation && Object.keys(translation).length > 1 ? translation : name;
}
const common = {
name: name ?? 'undefined',
icon: icon,
};
if (onlineId) {
common.statusStates = {
onlineId: onlineId.startsWith(this.adapter.namespace) ? onlineId : `${this.adapter.namespace}.${onlineId}`,
};
}
if (errorId) {
if (!common.statusStates) {
common.statusStates = {};
}
common.statusStates.errorId = errorId.startsWith(this.adapter.namespace) ? errorId : `${this.adapter.namespace}.${errorId}`;
}
if (!(await this.adapter.objectExists(id))) {
this.log.debug(`${logPrefix} creating device '${id}'`);
await this.adapter.setObjectNotExistsAsync(id, {
type: 'device',
common: common,
native,
});
}
else {
if (updateObject) {
const obj = await this.adapter.getObjectAsync(id);
if (obj && obj.common) {
if (!this.isDeviceCommonEqual(obj.common, common)) {
await this.adapter.extendObject(id, { common: common });
const diff = this.deepDiffBetweenObjects(common, obj.common, this.adapter);
if (diff && diff.icon) {
diff.icon = _.truncate(diff.icon);
} // reduce base64 image string for logging
this.log.debug(`${logPrefix} device updated '${id}' ${logChanges ? `(updated properties: ${JSON.stringify(diff)})` : ''}`);
}
}
}
}
}
catch (error) {
this.log.error(`${logPrefix} error: ${error}, stack: ${error.stack}`);
}
}
/**
* create or update a channel object, update will only be done on adapter start
*
* @param id
* @param name
* @param icon
* @param updateObject
* @param native
*/
async createOrUpdateChannel(id, name, icon = undefined, updateObject = false, native = {}) {
const logPrefix = '[myIob.createOrUpdateChannel]:';
try {
if (typeof name === 'string') {
const translation = this.utils.I18n.getTranslatedObject(name);
name = translation && Object.keys(translation).length > 1 ? translation : name;
}
const common = {
name: name ?? 'undefined',
icon: icon,
};
if (!(await this.adapter.objectExists(id))) {
this.log.debug(`${logPrefix} creating channel '${id}'`);
await this.adapter.setObjectNotExistsAsync(id, {
type: 'channel',
common: common,
native,
});
}
else {
if (updateObject) {
const obj = await this.adapter.getObjectAsync(id);
if (obj && obj.common) {
if (!this.isChannelCommonEqual(obj.common, common)) {
await this.adapter.extendObject(id, { common: common });
const diff = this.deepDiffBetweenObjects(common, obj.common, this.adapter);
if (diff && diff.icon) {
diff.icon = _.truncate(diff.icon);
} // reduce base64 image string for logging
this.log.debug(`${logPrefix} channel updated '${id}' (updated properties: ${JSON.stringify(diff)})`);
}
}
}
}
}
catch (error) {
this.log.error(`${logPrefix} error: ${error}, stack: ${error.stack}`);
}
}
async createOrUpdateStates(idChannel, treeDefinition, partialData, fullData, blacklistFilter = undefined, isWhiteList = false, logDeviceName = 'not defined', updateObject = false) {
return await this._createOrUpdateStates(idChannel, this.getIdLastPart(idChannel), treeDefinition, partialData, blacklistFilter, isWhiteList, fullData, fullData, logDeviceName, updateObject);
}
async _createOrUpdateStates(channel, deviceId, treeDefinition, treeData, blacklistFilter, isWhiteList, fullData, channelData, logDeviceName = 'not defined', updateObject = false, filterId = '', isChannelOnWhitelist = false) {
const logPrefix = '[myIob.createOrUpdateStates]:';
let stateValueChanged = false;
try {
if (this.adapter.connected) {
for (const key in treeDefinition) {
let logMsgState = `${channel}.${key}`.split('.')?.slice(1)?.join('.');
const treeDef = treeDefinition[key];
const logDetails = `${fullData?.mac ? `mac: ${fullData?.mac}` : fullData?.ip ? `ip: ${fullData?.ip}` : fullData?._id ? `id: ${fullData?._id}` : ''}`;
try {
// if we have an own defined state which takes val from other property
let valKey = key;
if (this.hasKey(treeDef, 'valFromProperty') && treeDef.valFromProperty) {
if (this.hasKey(treeData, treeDef.valFromProperty)) {
valKey = treeDef.valFromProperty;
}
else {
this.log.silly(`${logPrefix} '${logDeviceName}' ${logDetails ? `(${logDetails}) ` : ''} key '${key}' has valFromProperty '${treeDef.valFromProperty}' not exist in data -> skipping!`);
continue;
}
}
const cond1 = (this.hasKey(treeData, valKey) && treeData[valKey] !== undefined) || (this.hasKey(treeDef, 'id') && !this.hasKey(treeDef, 'valFromProperty'));
const cond2 = this.hasKey(treeDef, 'iobType') && !this.hasKey(treeDef, 'object') && !this.hasKey(treeDef, 'array');
if (key && cond1 && cond2) {
// if we have a 'iobType' property, then it's a state
let stateId = key;
if (this.hasKey(treeDef, 'id')) {
// if we have a custom state, use defined id
stateId = treeDef.id;
}
if ((this.hasKey(treeDef, 'conditionToCreateState') && treeDef.conditionToCreateState?.(fullData, channelData, this.adapter) === true) || !this.hasKey(treeDef, 'conditionToCreateState')) {
logMsgState = `${channel}.${stateId}`.split('.')?.slice(1)?.join('.');
if ((!isWhiteList && !_.some(blacklistFilter, { id: `${filterId}${stateId}` })) || (isWhiteList && _.some(blacklistFilter, { id: `${filterId}${stateId}` })) || isChannelOnWhitelist || this.hasKey(treeDef, 'required')) {
if (!(await this.adapter.objectExists(`${channel}.${stateId}`))) {
// create State
this.log.silly(`${logPrefix} ${logDeviceName} - creating state '${logMsgState}'`);
const obj = {
type: 'state',
common: this.getCommonForState(key, treeDefinition, fullData, channelData, logMsgState, logDeviceName),
native: {},
};
await this.adapter.setObjectNotExistsAsync(`${channel}.${stateId}`, obj);
}
else {
// update State if needed (only on adapter start)
if (updateObject) {
const obj = await this.adapter.getObjectAsync(`${channel}.${stateId}`);
const commonUpdated = this.getCommonForState(key, treeDefinition, fullData, channelData, logMsgState, logDeviceName);
if (obj && obj.common) {
if (!this.isStateCommonEqual(obj.common, commonUpdated)) {
await this.adapter.extendObject(`${channel}.${stateId}`, { common: commonUpdated });
this.log.debug(`${logPrefix} ${logDeviceName} - updated common properties of state '${logMsgState}' (updated properties: ${JSON.stringify(this.deepDiffBetweenObjects(commonUpdated, obj.common, this.adapter))})`);
}
}
}
}
if (!this.subscribedStates.includes(`${channel}.${stateId}`) && ((treeDef.write && treeDef.write === true) || this.hasKey(treeDef, 'subscribeMe'))) {
// state is writeable or has subscribeMe Property -> subscribe it
this.log.silly(`${logPrefix} ${logDeviceName} - subscribing state '${logMsgState}'`);
await this.adapter.subscribeStatesAsync(`${channel}.${stateId}`);
this.subscribedStates.push(`${channel}.${stateId}`);
}
const writeValKey = `${channel}.${stateId}`.replace(`.${deviceId}.`, '.');
if (!this.statesWithWriteFunction[writeValKey] && treeDef.writeVal) {
// state has a write conversation function -> store function in 'list' to use it before writing data to ufp
this.statesWithWriteFunction[writeValKey] = treeDef.writeVal;
}
if (treeData && this.hasKey(treeData, valKey) && (this.hasKey(treeData, key) || this.hasKey(treeData, treeDef.valFromProperty))) {
const val = treeDef.readVal ? await treeDef.readVal(treeData[valKey], this.adapter, fullData, channelData, `${channel}.${stateId}`) : treeData[valKey];
let changedObj = undefined;
if (this.statesUsingValAsLastChanged.includes(key)) {
// set lc to last_seen value
changedObj = await this.adapter.setStateChangedAsync(`${channel}.${stateId}`, { val: val, lc: val }, true);
}
else {
changedObj = await this.adapter.setStateChangedAsync(`${channel}.${stateId}`, val, true);
}
if (!updateObject && changedObj && this.hasKey(changedObj, 'notChanged') && !changedObj.notChanged) {
stateValueChanged = true;
this.log.silly(`${logPrefix} value of state '${logMsgState}' changed to ${val}`);
}
if (!stateValueChanged && this.hasKey(treeDef, 'updateTs') && treeDef.updateTs === true) {
this.log.silly(`${logPrefix} timestamp of state '${logMsgState}' updated`);
await this.adapter.setState(`${channel}.${stateId}`, val, true);
}
}
else {
if (!this.hasKey(treeDef, 'id')) {
// only report it if it's not a custom defined state
this.log.debug(`${logPrefix} ${logDeviceName} - property '${logMsgState}' not exists in data (sometimes this option may first need to be activated / used or will update by an event)`);
}
}
}
else {
// channel is on blacklist
// delete also at runtime, because some properties are only available on websocket data
if (await this.adapter.objectExists(`${channel}.${stateId}`)) {
await this.adapter.delObjectAsync(`${channel}.${stateId}`);
this.log.info(`${logPrefix} '${logDeviceName}' ${logDetails ? `(${logDetails}) ` : ''}state '${channel}.${stateId}' delete, ${isWhiteList ? `it's not on the whitelist` : `it's on the blacklist`}`);
}
}
}
else {
if (updateObject) {
if (await this.adapter.objectExists(`${channel}.${stateId}`)) {
await this.adapter.delObjectAsync(`${channel}.${stateId}`);
this.log.info(`${logPrefix} '${logDeviceName}' ${logDetails ? `(${logDetails}) ` : ''}state '${channel}.${stateId}' delete, condition to create is 'false'`);
}
}
}
}
else {
// it's a channel from type object
if (this.hasKey(treeDef, 'object') && this.hasKey(treeData, key)) {
const treeObjectDef = treeDefinition[key];
const idChannelAppendix = this.hasKey(treeObjectDef, 'idChannel') ? treeObjectDef.idChannel : key;
const idChannel = `${channel}.${idChannelAppendix}`;
if ((this.hasKey(treeObjectDef, 'conditionToCreateState') && treeObjectDef.conditionToCreateState?.(fullData, channelData, this.adapter) === true) || !this.hasKey(treeObjectDef, 'conditionToCreateState')) {
if ((!isWhiteList && !_.some(blacklistFilter, { id: `${filterId}${idChannelAppendix}` })) || (isWhiteList && _.some(blacklistFilter, x => x.id && x.id.startsWith(`${filterId}${idChannelAppendix}`))) || this.hasKey(treeObjectDef, 'required')) {
await this.createOrUpdateChannel(`${idChannel}`, this.hasKey(treeObjectDef, 'name') ? (typeof treeObjectDef.name === 'function' ? treeObjectDef.name(fullData, channelData[key], this.adapter) : treeObjectDef.name) : key, this.hasKey(treeObjectDef, 'icon') ? treeObjectDef.icon : undefined, updateObject);
const result = await this._createOrUpdateStates(`${idChannel}`, deviceId, treeObjectDef.object, treeData[key], blacklistFilter, isWhiteList, fullData, channelData[key], logDeviceName, updateObject, `${filterId}${idChannelAppendix}.`, isWhiteList && _.some(blacklistFilter, { id: `${filterId}${idChannelAppendix}` }));
stateValueChanged = result ? result : stateValueChanged;
}
else {
// channel is on blacklist
if (await this.adapter.objectExists(idChannel)) {
await this.adapter.delObjectAsync(idChannel, { recursive: true });
this.log.info(`${logPrefix} '${logDeviceName}' ${logDetails ? `(${logDetails}) ` : ''}channel '${idChannel}' delete, ${isWhiteList ? `it's not on the whitelist` : `it's on the blacklist`}`);
}
}
}
else {
if (await this.adapter.objectExists(idChannel)) {
await this.adapter.delObjectAsync(idChannel, { recursive: true });
this.log.info(`${logPrefix} '${logDeviceName}' ${logDetails ? `(${logDetails}) ` : ''}channel '${idChannel}' delete, condition to create is 'false'`);
}
}
}
// it's a channel from type array
if (this.hasKey(treeDef, 'array') && this.hasKey(treeData, key)) {
//@ts-ignore
if (treeData[key] !== null && treeData[key].length > 0) {
const treeArrayDef = treeDefinition[key];
const idChannelAppendix = this.hasKey(treeArrayDef, 'idChannel') ? treeArrayDef.idChannel : key;
const idChannel = `${channel}.${idChannelAppendix}`;
if ((this.hasKey(treeArrayDef, 'conditionToCreateState') && treeArrayDef.conditionToCreateState?.(fullData, channelData, this.adapter) === true) || !this.hasKey(treeArrayDef, 'conditionToCreateState')) {
if ((!isWhiteList && !_.some(blacklistFilter, { id: `${filterId}${idChannelAppendix}` })) || (isWhiteList && _.some(blacklistFilter, x => x.id && x.id.startsWith(`${filterId}${idChannelAppendix}`))) || this.hasKey(treeArrayDef, 'required')) {
await this.createOrUpdateChannel(`${idChannel}`, this.hasKey(treeArrayDef, 'name') ? treeArrayDef.name : key, this.hasKey(treeArrayDef, 'icon') ? treeArrayDef.icon : undefined, updateObject);
const arrayNumberAdd = this.hasKey(treeArrayDef, 'arrayStartNumber') ? treeArrayDef.arrayStartNumber : 0;
//@ts-ignore
for (let i = 0; i <= treeData[key].length - 1; i++) {
const nr = i + arrayNumberAdd;
if (treeData[key][i] !== null && treeData[key][i] !== undefined) {
let idChannelArray = myHelper.zeroPad(nr, treeArrayDef.arrayChannelIdZeroPad || 0);
if (this.hasKey(treeArrayDef, 'arrayChannelIdFromProperty')) {
idChannelArray = treeArrayDef.arrayChannelIdFromProperty?.(fullData, channelData[key][i], i, this.adapter);
}
else if (this.hasKey(treeArrayDef, 'arrayChannelIdPrefix')) {
idChannelArray = treeArrayDef.arrayChannelIdPrefix + myHelper.zeroPad(nr, treeArrayDef.arrayChannelIdZeroPad || 0);
}
if (idChannelArray !== undefined) {
//@ts-ignore
await this.createOrUpdateChannel(`${idChannel}.${idChannelArray}`, this.hasKey(treeArrayDef, 'arrayChannelNameFromProperty') ? treeArrayDef.arrayChannelNameFromProperty?.(fullData, channelData[key][i], i, this.adapter) : treeArrayDef.arrayChannelNamePrefix + nr || nr.toString(), undefined, true);
const result = await this._createOrUpdateStates(`${idChannel}.${idChannelArray}`, deviceId, treeArrayDef.array, treeData[key][i], blacklistFilter, isWhiteList, fullData, channelData[key][i], logDeviceName, true, `${filterId}${idChannelAppendix}.`, isWhiteList && _.some(blacklistFilter, { id: `${filterId}${idChannelAppendix}` }));
stateValueChanged = result ? result : stateValueChanged;
}
}
}
}
else {
// channel is on blacklist, wlan is comming from realtime api
if (await this.adapter.objectExists(idChannel)) {
await this.adapter.delObjectAsync(idChannel, { recursive: true });
this.log.info(`${logPrefix} '${logDeviceName}' ${logDetails ? `(${logDetails}) ` : ''}channel '${idChannel}' delete, ${isWhiteList ? `it's not on the whitelist` : `it's on the blacklist`}`);
}
}
}
else {
if (await this.adapter.objectExists(idChannel)) {
await this.adapter.delObjectAsync(idChannel, { recursive: true });
this.log.info(`${logPrefix} '${logDeviceName}' ${logDetails ? `(${logDetails}) ` : ''}channel '${idChannel}' delete, condition to create is 'false'`);
}
}
}
}
}
}
catch (error) {
this.log.error(`${logPrefix} [id: ${key}, ${logDetails ? `${logDetails}, ` : ''}key: ${key}] error: ${error}, stack: ${error.stack}, data: ${this.hasKey(treeData, key) ? JSON.stringify(treeData[key]) : 'undefined'}`);
}
}
}
else {
if (this.log.level === 'debug') {
this.log.warn(`${logPrefix} adapter has no connection!`);
}
}
}
catch (error) {
this.log.error(`${logPrefix} error: ${error}, stack: ${error.stack}`);
}
return stateValueChanged;
}
getCommonForState(id, treeDefinition, fullData, channelData, logMsgState, logDeviceName) {
const logPrefix = '[myIob.getCommonForState]:';
try {
// i18x translation if exists
const i18n = this.utils.I18n.getTranslatedObject(treeDefinition[id].name || id);
const name = Object.keys(i18n).length > 1 ? i18n : treeDefinition[id].name || id;
const common = {
name: name,
type: treeDefinition[id].iobType,
read: treeDefinition[id].read !== undefined ? treeDefinition[id].read : true,
write: treeDefinition[id].write !== undefined ? treeDefinition[id].write : false,
role: 'state'
};
if (treeDefinition[id].unit) {
const unitTmp = treeDefinition[id]?.unit;
const unit = (typeof unitTmp === 'function') ? unitTmp(fullData, channelData, this.adapter) : unitTmp;
const unitI18 = this.utils.I18n.getTranslatedObject(unit);
common.unit = Object.keys(unitI18).length > 1 && this.adapter.language && unitI18[this.adapter.language] ? unitI18[this.adapter.language] : unit;
}
if (treeDefinition[id].min || treeDefinition[id].min === 0) {
common.min = treeDefinition[id].min;
}
if (treeDefinition[id].max || treeDefinition[id].max === 0) {
common.max = treeDefinition[id].max;
}
if (treeDefinition[id].step) {
common.step = treeDefinition[id].step;
}
if (treeDefinition[id].expert) {
common.expert = treeDefinition[id].expert;
}
common.role = treeDefinition[id].role ? treeDefinition[id].role : this.assignPredefinedRoles(common, id);
if (treeDefinition[id].def || treeDefinition[id].def === 0 || treeDefinition[id].def === false) {
common.def = treeDefinition[id].def;
}
if (treeDefinition[id].states) {
common.states = treeDefinition[id].states;
}
else if (this.hasKey(treeDefinition[id], 'statesFromProperty')) {
common.states = treeDefinition[id].statesFromProperty?.(fullData, channelData, this.adapter);
this.log.debug(`${logPrefix} ${logDeviceName} - set ${common.states?.length || Object.keys(common.states || {}).length} allowed common.states for '${logMsgState}'`);
this.log.silly(`${logPrefix} ${logDeviceName} - set allowed common.states for '${logMsgState}' (from: ${JSON.stringify(common.states)})`);
}
if (treeDefinition[id].desc) {
common.desc = treeDefinition[id].desc;
}
return common;
}
catch (error) {
this.log.error(`${logPrefix} error: ${error}, stack: ${error.stack}`);
}
return undefined;
}
assignPredefinedRoles(common, id) {
//https://github.com/ioBroker/ioBroker.docs/blob/master/docs/en/dev/stateroles.md
const logPrefix = '[myIob.assignPredefinedRoles]:';
try {
id = this.getIdLastPart(id);
if (common.type === 'boolean') {
if (common.read === true && common.write === true) {
if (id.toLocaleLowerCase().includes('enable')) {
return 'switch.enable';
}
if (id.toLocaleLowerCase().includes('light') || id.toLocaleLowerCase().includes('led')) {
return 'switch.light';
}
if (id.toLocaleLowerCase().includes('power') || id.toLocaleLowerCase().includes('poe')) {
return 'switch.power';
}
return 'switch';
}
if (common.read === true && common.write === false) {
if (id.toLocaleLowerCase().includes('connected') || id.toLocaleLowerCase().includes('reachable') || id.toLocaleLowerCase().includes('isonline')) {
return 'indicator.reachable';
}
if (id.toLocaleLowerCase().includes('error')) {
return 'indicator.error';
}
if (id.toLocaleLowerCase().includes('alarm')) {
return 'indicator.alarm';
}
if (id.toLocaleLowerCase().includes('maintenance')) {
return 'indicator.maintenance';
}
return 'sensor';
}
}
if (common.type === 'number') {
let suffix = '';
if (common.unit === '°C' || common.unit === '°F' || common.unit === 'K' || id.toLowerCase().includes('temperatur')) {
suffix = '.temperature';
}
if (common.unit === 'lux') {
suffix = '.brightness';
}
if (common.unit === 'ppm') {
suffix = '.co2';
}
if (common.unit === 'mbar') {
suffix = '.pressure';
}
if (common.unit === 'Wh' || common.unit === 'kWh') {
suffix = '.energy';
}
if (common.unit === 'W' || common.unit === 'kW') {
suffix = '.power';
}
if (common.unit === 'A') {
suffix = '.current';
}
if (common.unit === 'V') {
suffix = '.voltage';
}
if (common.unit === 'Hz') {
suffix = '.frequency';
}
if (id.toLocaleLowerCase().includes('longitude')) {
suffix = '.gps.longitude';
}
if (id.toLocaleLowerCase().includes('latitude')) {
suffix = '.gps.latitude';
}
if (id.toLowerCase().includes('humidity') && common.unit !== '') {
suffix = '.humidity';
}
if (id.toLowerCase().includes('battery') && common.unit === '%') {
suffix = '.battery';
}
if (id.toLowerCase().includes('volume') && common.unit === '%') {
suffix = '.volume';
}
if (common.read === true && common.write === true) {
return `level${suffix}`;
}
if (common.read === false && common.write === true) {
return `level${suffix}`;
}
if (common.read === true && common.write === false) {
return `value${suffix}`;
}
}
if (common.type === 'string') {
if (common.read === true && common.write === false) {
if (id.toLocaleLowerCase().includes('firmware') || id.toLocaleLowerCase().includes('version')) {
return 'info.firmware';
}
else if (id.toLocaleLowerCase().includes('status')) {
return 'info.status';
}
else if (id.toLocaleLowerCase().includes('model')) {
return 'info.model';
}
else if (id.toLocaleLowerCase().includes('mac')) {
return 'info.mac';
}
else if (id.toLocaleLowerCase().includes('name')) {
return 'info.name';
}
else if (id.toLocaleLowerCase().includes('hardware')) {
return 'info.hardware';
}
else if (id.toLocaleLowerCase().includes('serial')) {
return 'info.serial';
}
else {
return 'text';
}
}
else {
return 'text';
}
}
}
catch (error) {
this.log.error(`${logPrefix} error: ${error}, stack: ${error.stack}`);
}
return 'state';
}
/**
* check if state exists before setting value
*
* @param id
* @param val
* @param adapter
* @param onlyChanges
*/
async setStateExists(id, val, adapter, onlyChanges = false) {
const logPrefix = '[myIob.setStateExists]:';
try {
if (await adapter.objectExists(id)) {
if (!onlyChanges) {
await adapter.setState(id, val, true);
}
else {
await adapter.setStateChangedAsync(id, val, true);
}
}
}
catch (error) {
adapter.log.error(`${logPrefix} error: ${error}, stack: ${error.stack}`);
}
}
/**
* Id without last part
*
* @param id
* @returns
*/
getIdWithoutLastPart(id) {
const lastIndex = id.lastIndexOf('.');
return id.substring(0, lastIndex);
}
/**
* last part of id
*
* @param id
* @returns
*/
getIdLastPart(id) {
const result = id.split('.').pop();
return result ? result : '';
}
/**
* Compare common properties of device
*
* @param objCommon
* @param myCommon
* @returns
*/
isDeviceCommonEqual(objCommon, myCommon) {
return (myCommon.name !== 'undefined' && _.isEqual(objCommon.name, myCommon.name)) && (!myCommon.icon || objCommon.icon === myCommon.icon) && objCommon.desc === myCommon.desc && objCommon.role === myCommon.role && _.isEqual(objCommon.statusStates, myCommon.statusStates);
}
/**
* Compare common properties of channel
*
* @param objCommon
* @param myCommon
* @returns
*/
isChannelCommonEqual(objCommon, myCommon) {
return (myCommon.name !== 'undefined' && _.isEqual(objCommon.name, myCommon.name)) && (!myCommon.icon || objCommon.icon === myCommon.icon) && objCommon.desc === myCommon.desc && objCommon.role === myCommon.role;
}
/**
* Compare common properties of state
*
* @param objCommon
* @param myCommon
* @returns
*/
isStateCommonEqual(objCommon, myCommon) {
return _.isEqual(objCommon.name, myCommon?.name) && _.isEqual(objCommon.type, myCommon?.type) && _.isEqual(objCommon.read, myCommon?.read) && _.isEqual(objCommon.write, myCommon?.write) && _.isEqual(objCommon.role, myCommon?.role) && _.isEqual(objCommon.def, myCommon?.def) && _.isEqual(objCommon.unit, myCommon?.unit) && _.isEqual(objCommon.icon, myCommon?.icon) && _.isEqual(objCommon.desc, myCommon?.desc) && _.isEqual(objCommon.max, myCommon?.max) && _.isEqual(objCommon.min, myCommon?.min) && _.isEqual(objCommon.states, myCommon?.states);
}
/**
* Compare two objects and return properties that are diffrent
*
* @param object
* @param base
* @param adapter
* @param allowedKeys
* @param prefix
* @returns
*/
deepDiffBetweenObjects = (object, base, adapter, allowedKeys = undefined, prefix = '') => {
const logPrefix = '[myIob.deepDiffBetweenObjects]:';
try {
const changes = (object, base, prefixInner = '') => {
return _.transform(object, (result, value, key) => {
const fullKey = prefixInner ? `${prefixInner}.${key}` : key;
try {
if (!_.isEqual(value, base[key]) && ((allowedKeys && allowedKeys.includes(fullKey)) || allowedKeys === undefined)) {
if (_.isArray(value)) {
if (_.some(value, (item) => _.isObject(item))) {
// objects in array exists
const tmp = [];
let empty = true;
for (let i = 0; i <= value.length - 1; i++) {
const res = this.deepDiffBetweenObjects(value[i], base[key] && base[key][i] ? base[key][i] : {}, adapter, allowedKeys, fullKey);
if (!_.isEmpty(res) || res === 0 || res === false) {
// if (!_.has(result, key)) result[key] = [];
tmp.push(res);
empty = false;
}
else {
tmp.push(null);
}
}
if (!empty) {
result[key] = tmp;
}
}
else {
// is pure array
if (!_.isEqual(value, base[key])) {
result[key] = value;
}
}
}
else if (_.isObject(value) && _.isObject(base[key])) {
const res = changes(value, base[key] ? base[key] : {}, fullKey);
if (!_.isEmpty(res) || res === 0 || res === false) {
result[key] = res;
}
}
else {
result[key] = value;
}
}
}
catch (error) {
adapter.log.error(`${logPrefix} transform error: ${error}, stack: ${error.stack}, fullKey: ${fullKey}, object: ${JSON.stringify(object)}, base: ${JSON.stringify(base)}`);
}
});
};
return changes(object, base, prefix);
}
catch (error) {
adapter.log.error(`${logPrefix} error: ${error}, stack: ${error.stack}, object: ${JSON.stringify(object)}, base: ${JSON.stringify(base)}`);
}
return object;
};
findMissingTranslation() {
const logPrefix = '[myIob.findMissingTranslation]:';
try {
this._findMissingTranslation(tree);
}
catch (error) {
this.log.error(`${logPrefix} error: ${error}, stack: ${error.stack}`);
}
}
_findMissingTranslation(obj, logSuffix = undefined) {
const logPrefix = `[myIob.findMissingTranslation]:${logSuffix ? ` ${logSuffix}` : ''}`;
try {
for (const key in obj) {
if (_.isObject(obj[key]) && !key.includes('events')) {
if (this.hasKey(obj[key], 'get')) {
//@ts-ignore
const result = this.tree2Translation(obj[key].get(), this.adapter, this.utils.I18n);
if (result && Object.keys(result).length > 0) {
this.log.warn(`${logPrefix} ${key} - missing translations ${JSON.stringify(result)}`);
}
if (Object.keys(obj[key]).length > 0) {
this._findMissingTranslation(obj[key], logSuffix ? `${logSuffix}.${key}` : key);
}
}
else {
this._findMissingTranslation(obj[key], logSuffix ? `${logSuffix}.${key}` : key);
}
}
}
}
catch (error) {
this.log.error(`${logPrefix} error: ${error}, stack: ${error.stack}`);
}
}
/**
* generate a list with all defined names, that can be used for translation
*
* @param tree
* @param adapter
* @param i18n
*/
tree2Translation(tree, adapter, i18n) {
const result = this.getTreeNameOrKey(tree);
for (const key of Object.keys(result)) {
if (Object.keys(i18n.getTranslatedObject(key)).length > 1) {
delete result[key];
}
}
if (result && Object.keys(result).length > 0) {
return result;
}
else {
return null;
}
}
getTreeNameOrKey(obj, path = []) {
const result = {};
if (obj && typeof obj === "object") {
if ("iobType" in obj || 'array' in obj || 'object' in obj) {
const lastKey = path[path.length - 1];
if (typeof obj.name !== 'function') {
result[obj.name ?? lastKey] = obj.name ?? lastKey;
}
}
for (const [childKey, value] of Object.entries(obj)) {
if (value && typeof value === "object") {
Object.assign(result, this.getTreeNameOrKey(value, [...path, childKey]));
}
}
}
return result;
}
hasKey(obj, key) {
if (key) {
return Object.hasOwn(obj, key);
}
return false;
}
}