diff --git a/packages/common/src/lib/common/aliasProcessing.ts b/packages/common/src/lib/common/aliasProcessing.ts new file mode 100644 index 0000000000..a1aa578ebe --- /dev/null +++ b/packages/common/src/lib/common/aliasProcessing.ts @@ -0,0 +1,127 @@ +interface ApplyAliasTransformerOptions { + /** State used for calculations */ + state: ioBroker.State; + /** Properties from this StateCommon will be provided first to the conversion function */ + firstCommon: Partial; + /** Properties from this StateCommon will be provided second to the conversion function */ + secondCommon: Partial; + /** The actual transformer function as a string */ + transformer: string; + /** If this is a read function, determines the naming of the passed variables */ + isRead: boolean; +} + +interface ApplyAliasConvenienceConversionOptions { + /** State used for calculations */ + state: ioBroker.State; + /** The common attribute of the alias target */ + targetCommon?: Partial; +} + +interface ApplyAliasAutoScalingOptions extends ApplyAliasConvenienceConversionOptions { + /** The common attribute of the alias source */ + sourceCommon?: Partial; +} + +/** + * Applies a user-given transformer function and provides the type, min and max of the + * passed StateCommon variables as well as the state's value + * + * @param options state, common information and transformer function + */ +export function applyAliasTransformer(options: ApplyAliasTransformerOptions): ioBroker.StateValue { + const { state, firstCommon, secondCommon, transformer, isRead } = options; + + const prefix = isRead ? 's' : 't'; + + const func = new Function( + 'val', + 'type', + 'min', + 'max', + `${prefix}Type`, + `${prefix}Min`, + `${prefix}Max`, + `return ${transformer}` + ); + + return func( + state.val, + firstCommon.type, + firstCommon.min, + firstCommon.max, + secondCommon.type, + secondCommon.min, + secondCommon.max + ); +} + +/** + * Applies some convenience conversions of aliases, e.g. transforming string 'off' to a boolean false, if target is a boolean + * + * @param options state and target common information + */ +export function applyAliasConvenienceConversion(options: ApplyAliasConvenienceConversionOptions): ioBroker.StateValue { + const { targetCommon, state } = options; + + if (targetCommon && typeof state.val !== targetCommon.type && state.val !== null) { + if (targetCommon.type === 'boolean') { + const lowerVal = typeof state.val === 'string' ? state.val.toLowerCase() : state.val; + if (lowerVal === 'off' || lowerVal === 'aus' || state.val === '0') { + return false; + } else { + // this also handles strings like "EIN" or such that will be true + return !!state.val; + } + } else if (targetCommon.type === 'number' && typeof state.val === 'string') { + return parseFloat(state.val); + } else if (targetCommon.type === 'string') { + return state.val.toString(); + } + } + + return state.val; +} + +/** + * Applies autoscaling between alias source and target if one has % unit and the other not + * + * @param options state, source and target common information + */ +export function applyAliasAutoScaling(options: ApplyAliasAutoScalingOptions): ioBroker.StateValue { + const { state, sourceCommon, targetCommon } = options; + + // auto-scaling, only if val not null and unit for target (x)or source is % + if ( + ((targetCommon?.alias && !targetCommon.alias.read) || (sourceCommon?.alias && !sourceCommon.alias.write)) && + state.val !== null + ) { + if ( + targetCommon && + targetCommon.type === 'number' && + targetCommon.unit === '%' && + sourceCommon && + sourceCommon.type === 'number' && + sourceCommon.unit !== '%' && + sourceCommon.min !== undefined && + sourceCommon.max !== undefined + ) { + // scale target between 0 and 100 % based on sources min/max + return (((state.val as number) - sourceCommon.min) / (sourceCommon.max - sourceCommon.min)) * 100; + } else if ( + sourceCommon && + sourceCommon.type === 'number' && + sourceCommon.unit === '%' && + targetCommon && + targetCommon.unit !== '%' && + targetCommon.type === 'number' && + targetCommon.min !== undefined && + targetCommon.max !== undefined + ) { + // scale target based on its min/max by its source (assuming source is meant to be 0 - 100 %) + return ((targetCommon.max - targetCommon.min) * (state.val as number)) / 100 + targetCommon.min; + } + } + + return state.val; +} diff --git a/packages/common/src/lib/common/tools.ts b/packages/common/src/lib/common/tools.ts index ba9bdadefb..9d4e3e9830 100644 --- a/packages/common/src/lib/common/tools.ts +++ b/packages/common/src/lib/common/tools.ts @@ -22,6 +22,7 @@ import { maybeCallbackWithError } from './maybeCallback'; // eslint-disable-next-line @typescript-eslint/no-var-requires const extend = require('node.extend'); import { setDefaultResultOrder } from 'dns'; +import { applyAliasAutoScaling, applyAliasConvenienceConversion, applyAliasTransformer } from './aliasProcessing'; type DockerInformation = | { @@ -2658,7 +2659,8 @@ export function measureEventLoopLag(ms: number, cb: (eventLoopLag?: number) => v } /** - * This function convert state values by read and write of aliases. Function is synchron. + * This function convert state values by read and write of aliases. Function is synchronous. + * On errors, null is returned instead * * @param options */ @@ -2682,35 +2684,22 @@ export function formatAliasValue(options: FormatAliasValueOptions): ioBroker.Sta return null; } try { - // process the value here - const func = new Function( - 'val', - 'type', - 'min', - 'max', - 'sType', - 'sMin', - 'sMax', - `return ${targetCommon.alias.read}` - ); - state.val = func( - state.val, - targetCommon.type, - targetCommon.min, - targetCommon.max, - sourceCommon.type, - sourceCommon.min, - sourceCommon.max - ); + state.val = applyAliasTransformer({ + transformer: targetCommon.alias.read, + firstCommon: targetCommon, + secondCommon: sourceCommon, + isRead: true, + state + }); } catch (e) { logger.error( - `${logNamespace} Invalid read function for "${targetId}": "${targetCommon.alias.read}" => ${e.message}` + `${logNamespace}Invalid read function for "${targetId}": "${targetCommon.alias.read}" => ${e.message}` ); return null; } } - if (sourceCommon && sourceCommon.alias && sourceCommon.alias.write) { + if (sourceCommon?.alias?.write) { if (!targetCommon) { logger.error( `${logNamespace}target for "${sourceId}" does not exist for "write" function: "${sourceCommon.alias.write}"` @@ -2718,82 +2707,23 @@ export function formatAliasValue(options: FormatAliasValueOptions): ioBroker.Sta return null; } try { - // process the value here - const func = new Function( - 'val', - 'type', - 'min', - 'max', - 'tType', - 'tMin', - 'tMax', - `return ${sourceCommon.alias.write}` - ); - state.val = func( - state.val, - sourceCommon.type, - sourceCommon.min, - sourceCommon.max, - targetCommon.type, - targetCommon.min, - targetCommon.max - ); + state.val = applyAliasTransformer({ + transformer: sourceCommon.alias.write, + firstCommon: sourceCommon, + secondCommon: targetCommon, + isRead: false, + state + }); } catch (e) { logger.error( - `${logNamespace} Invalid write function for "${sourceId}": "${sourceCommon.alias.write}" => ${e.message}` + `${logNamespace}Invalid write function for "${sourceId}": "${sourceCommon.alias.write}" => ${e.message}` ); return null; } } - if (targetCommon && typeof state.val !== targetCommon.type && state.val !== null) { - if (targetCommon.type === 'boolean') { - const lowerVal = typeof state.val === 'string' ? state.val.toLowerCase() : state.val; - if (lowerVal === 'off' || lowerVal === 'aus' || state.val === '0') { - state.val = false; - } else { - // this also handles strings like "EIN" or such that will be true - state.val = !!state.val; - } - } else if (targetCommon.type === 'number') { - state.val = parseFloat(state.val as any); - } else if (targetCommon.type === 'string') { - state.val = state.val.toString(); - } - } - - // auto-scaling, only if val not null and unit for target (x)or source is % - if ( - ((targetCommon && targetCommon.alias && !targetCommon.alias.read) || - (sourceCommon && sourceCommon.alias && !sourceCommon.alias.write)) && - state.val !== null - ) { - if ( - targetCommon && - targetCommon.type === 'number' && - targetCommon.unit === '%' && - sourceCommon && - sourceCommon.type === 'number' && - sourceCommon.unit !== '%' && - sourceCommon.min !== undefined && - sourceCommon.max !== undefined - ) { - // scale target between 0 and 100 % based on sources min/max - state.val = (((state.val as any) - sourceCommon.min) / (sourceCommon.max - sourceCommon.min)) * 100; - } else if ( - sourceCommon && - sourceCommon.type === 'number' && - sourceCommon.unit === '%' && - targetCommon && - targetCommon.unit !== '%' && - targetCommon.type === 'number' && - targetCommon.min !== undefined && - targetCommon.max !== undefined - ) { - // scale target based on its min/max by its source (assuming source is meant to be 0 - 100 %) - state.val = ((targetCommon.max - targetCommon.min) * (state.val as any)) / 100 + targetCommon.min; - } - } + state.val = applyAliasConvenienceConversion({ state, targetCommon }); + state.val = applyAliasAutoScaling({ state, sourceCommon, targetCommon }); return state; } diff --git a/packages/controller/src/main.ts b/packages/controller/src/main.ts index 4932e60df1..3580d6c2e2 100644 --- a/packages/controller/src/main.ts +++ b/packages/controller/src/main.ts @@ -2972,9 +2972,9 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise { if (err) { - sendTo(msg.from, msg.command, { error: err }, msg.callback); + sendTo(msg.from, msg.command, { error: err.message }, msg.callback); } else { sendTo( msg.from, @@ -3049,7 +3049,7 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise msg.callback && msg.from && sendTo(msg.from, msg.command, { error }, msg.callback) + err => msg.callback && msg.from && sendTo(msg.from, msg.command, { error: err?.message }, msg.callback) ); break;