Skip to content

Commit

Permalink
aliases: only perform parseFloat if the value is a string (#2542)
Browse files Browse the repository at this point in the history
* only perform parseFloat if the value is a string

- on converting aliases
- closes #2541
- and refactor alias transformation

* fix passed param names of transformer function

* ensure sent error is a string

- sending instances of a class lead to empty objects
  • Loading branch information
foxriver76 authored Nov 28, 2023
1 parent a61e7bd commit e7a768e
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 99 deletions.
127 changes: 127 additions & 0 deletions packages/common/src/lib/common/aliasProcessing.ts
Original file line number Diff line number Diff line change
@@ -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<ioBroker.StateCommon>;
/** Properties from this StateCommon will be provided second to the conversion function */
secondCommon: Partial<ioBroker.StateCommon>;
/** 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<ioBroker.StateCommon>;
}

interface ApplyAliasAutoScalingOptions extends ApplyAliasConvenienceConversionOptions {
/** The common attribute of the alias source */
sourceCommon?: Partial<ioBroker.StateCommon>;
}

/**
* 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;
}
114 changes: 22 additions & 92 deletions packages/common/src/lib/common/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
| {
Expand Down Expand Up @@ -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
*/
Expand All @@ -2682,118 +2684,46 @@ 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}"`
);
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;
}
Expand Down
14 changes: 7 additions & 7 deletions packages/controller/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2972,9 +2972,9 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
);

msg.callback && msg.from && sendTo(msg.from, msg.command, {}, msg.callback);
} catch (error) {
logger.error(`${hostLogPrefix} Cannot write zip file as folder: ${error}`);
msg.callback && msg.from && sendTo(msg.from, msg.command, { error }, msg.callback);
} catch (e) {
logger.error(`${hostLogPrefix} Cannot write zip file as folder: ${e.message}`);
msg.callback && msg.from && sendTo(msg.from, msg.command, { error: e.message }, msg.callback);
}
break;

Expand All @@ -2989,7 +2989,7 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
msg.message.options
);
} catch (e) {
sendTo(msg.from, msg.command, { error: e }, msg.callback);
sendTo(msg.from, msg.command, { error: e.message }, msg.callback);
return;
}

Expand All @@ -3004,7 +3004,7 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
buff
);
} catch (e) {
sendTo(msg.from, msg.command, { error: e }, msg.callback);
sendTo(msg.from, msg.command, { error: e.message }, msg.callback);
return;
}

Expand All @@ -3023,7 +3023,7 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi

states!.setBinaryState(`${hostObjectPrefix}.zip.${msg.message.link}`, buff, err => {
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,
Expand All @@ -3049,7 +3049,7 @@ async function processMessage(msg: ioBroker.SendableMessage): Promise<null | voi
msg.message.adapter,
Buffer.from(msg.message.data || '', 'base64'),
msg.message.options,
error => 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;

Expand Down

0 comments on commit e7a768e

Please sign in to comment.