Socket
Socket
Sign inDemoInstall

node-red-contrib-sun-position

Package Overview
Dependencies
1
Maintainers
1
Versions
136
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.2.11 to 0.3.0-beta

blind_control.md

922

nodes/blind-control.js

@@ -11,30 +11,30 @@ /********************************************

/**
* check if a position has a valid value
* check if a level has a valid value
* @param {*} node the node data
* @param {*} position the position to check
* @returns {boolean} true if the position is valid, otherwise false
* @param {*} level the level to check
* @returns {boolean} true if the level is valid, otherwise false
*/
function validPosition_(node, position) {
node.debug('validPosition_ position='+position);
if (isNaN(position)) {
node.debug('Position: "' + position + '" is NaN!');
function validPosition_(node, level) {
// node.debug('validPosition_ level='+level);
if (isNaN(level)) {
node.warn(`Position: "${level}" is NaN!`);
return false;
}
if (position < node.blindData.closedPos) {
node.debug('Position: "' + position + '" < ' + node.blindData.closedPos);
if (level < node.blindData.levelClosed) {
node.warn(`Position: "${level}" < levelClosed ${node.blindData.levelClosed}`);
return false;
}
if (node.blindData.position > node.blindData.openPos) {
node.debug('Position: "' + position + '" > ' + node.blindData.openPos);
if (level > node.blindData.levelOpen) {
node.warn(`Position: "${level}" > levelOpen ${node.blindData.levelOpen}`);
return false;
}
if (Number.isInteger(node.blindData.openPos) &&
Number.isInteger(node.blindData.closedPos) &&
if (Number.isInteger(node.blindData.levelOpen) &&
Number.isInteger(node.blindData.levelClosed) &&
Number.isInteger(node.blindData.increment) &&
((position % node.blindData.increment !== 0) ||
!Number.isInteger(position) )) {
node.debug('Position invalid "' + position + '" > ' + node.blindData.openPos);
((level % node.blindData.increment !== 0) ||
!Number.isInteger(level) )) {
node.warn(`Position invalid "${level}" not fit to increment ${node.blindData.increment}`);
return false;
}
return Number.isInteger(position / node.blindData.increment);
return Number.isInteger(Number((level / node.blindData.increment).toFixed(hlp.countDecimals(node.blindData.increment) + 2)));
}

@@ -48,27 +48,13 @@ /******************************************************************************************/

*/
function getNow_(node, msg, compareType) {
node.debug('getNow_');
let id = '';
function getNow_(node, msg) {
let value = '';
switch (compareType) {
case '1':
id = 'msg.ts';
value = msg.ts;
break;
case '2':
id = 'msg.lc';
value = msg.lc;
break;
case '3':
id = 'msg.time';
value = msg.time;
break;
case '4':
id = 'msg.value';
value = msg.value;
break;
default:
return new Date();
if (typeof msg.time === 'number') {
value = msg.time;
node.debug(`compare time to msg.time = "${value}"`);
} else if (typeof msg.ts === 'number') {
value = msg.ts;
node.debug(`compare time to msg.ts = "${value}"`);
} else {
return new Date();
}
node.debug('compare time to ' + id + ' = "' + value + '"');
const dto = new Date(msg.ts);

@@ -78,3 +64,3 @@ if (dto !== 'Invalid Date' && !isNaN(dto)) {

}
node.error('Error can not get a valide timestamp from ' + id + '="' + value + '"! Will use current timestamp!');
node.error('Error can not get a valide timestamp from "' + value + '"! Will use current timestamp!');
return new Date();

@@ -85,33 +71,45 @@ }

/**
* get the absolute position from percentage position
* get the absolute level from percentage level
* @param {*} node the node settings
* @param {*} percentPos the level in percentage
* @param {*} percentPos the level in percentage (0-1)
*/
function posPrcToAbs_(node, levelPercent) {
node.debug(`levelPrcToAbs_ ${levelPercent}`);
return posRound_(node, ((node.blindData.openPos - node.blindData.closedPos) * levelPercent) + node.blindData.closedPos);
return posRound_(node, ((node.blindData.levelOpen - node.blindData.levelClosed) * levelPercent) + node.blindData.levelClosed);
}
/**
* round a position to the next increment
* get the percentage level from absolute level (0-1)
* @param {*} node the node settings
* @param {*} levelAbsolute the level absolute
*/
function posAbsToPrc_(node, levelAbsolute) {
return (levelAbsolute - node.blindData.levelClosed) / (node.blindData.levelOpen - node.blindData.levelClosed);
}
/**
* get the absolute inverse level
* @param {*} node the node settings
* @param {*} levelAbsolute the level absolute
*/
function getInversePos_(node, levelAbsolute) {
return posPrcToAbs_(node, 1 - posAbsToPrc_(node, levelAbsolute));
}
/**
* round a level to the next increment
* @param {*} node node data
* @param {number} pos position
* @return {number} rounded position number
* @param {number} pos level
* @return {number} rounded level number
*/
function posRound_(node, pos) {
node.debug(`levelPrcToAbs_ ${pos} - increment is ${node.blindData.increment}`);
/* if (Number.isInteger(node.blindData.increment)) {
pos = Math.ceil(pos);
pos = Math.ceil(pos / node.blindData.increment) * node.blindData.increment;
return pos;
} */
pos = Math.ceil(pos / node.blindData.increment) * node.blindData.increment;
// node.debug(`levelPrcToAbs_ ${pos} - increment is ${node.blindData.increment}`);
// pos = Math.ceil(pos / node.blindData.increment) * node.blindData.increment;
// pos = Math.floor(pos / node.blindData.increment) * node.blindData.increment;
pos = Math.round(pos / node.blindData.increment) * node.blindData.increment;
pos = Number(pos.toFixed(hlp.countDecimals(node.blindData.increment)));
node.debug(`levelPrcToAbs_ 2 ${pos}`);
if (pos > node.blindData.openPos) {
pos = node.blindData.openPos;
if (pos > node.blindData.levelOpen) {
pos = node.blindData.levelOpen;
}
if (pos < node.blindData.closedPos) {
pos = node.blindData.closedPos;
if (pos < node.blindData.levelClosed) {
pos = node.blindData.levelClosed;
}
node.debug(`levelPrcToAbs_ result ${pos}`);
// node.debug(`levelPrcToAbs_ result ${pos}`);
return pos;

@@ -135,3 +133,3 @@ }

/**
* calculates the current sun position
* calculates the current sun level
* @param {*} node node data

@@ -151,27 +149,30 @@ * @param {*} now the current timestamp

/******************************************************************************************/
/**
* check the weather data
* @param {*} node node data
* @param {*} msg the message object
*/
function checkWeather(node, msg) {
node.debug('checkWeather');
// node.debug('checkWeather');
if (!node.cloudData.active) {
node.cloudData.isOperative = false;
return;
}
try {
const val = node.positionConfig.getFloatProp(node, msg, node.cloudData.valueType, node.cloudData.value);
if (val) {
node.cloudData.actValue = val;
node.cloudData.inLimit = (node.cloudData.threshold) ? val <= node.cloudData.threshold : true;
}
node.cloudData.isOperative = node.positionConfig.comparePropValue(node, msg, node.cloudData.valueType, node.cloudData.value,
node.cloudData.operator, node.cloudData.thresholdType, node.cloudData.thresholdValue, node.cloudData.temp);
} catch (err) {
node.error(RED._('blind-control.errors.getCloudData', err));
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.cloudData.isOperative = false;
}
msg.cloud = node.cloudData;
node.debug('node.cloudData=' + util.inspect(node.cloudData));
// node.debug('node.cloudData=' + util.inspect(node.cloudData));
}
/******************************************************************************************/
/**
* get the blind position from a typed input
* get the blind level from a typed input
* @param {*} node node data
* @param {*} type type field
* @param {*} value value field
* @returns blind position as number or NaN if not defined
* @returns blind level as number or NaN if not defined
*/

@@ -186,3 +187,3 @@ function getBlindPosFromTI(node, msg, type, value, def) {

if (value.includes('close')) {
return node.blindData.closedPos;
return node.blindData.levelClosed;
} else if (value === '75%') {

@@ -201,7 +202,7 @@ return posPrcToAbs_(node, 0.75);

} else if (value.includes('open')) {
return node.blindData.openPos;
return node.blindData.levelOpen;
}
throw new Error('unknown value "'+ value + '" of type "' + type + '"' );
}
return node.positionConfig.getFloatProp(node, msg, type, value);
return node.positionConfig.getFloatProp(node, msg, type, value, def);
} catch (err) {

@@ -214,27 +215,77 @@ node.error(RED._('blind-control.errors.getBlindPosData', err));

/******************************************************************************************/
/**
* reset any existing override
* @param {*} node node data
*/
function blindPosOverwriteReset(node) {
node.debug(`blindPosOverwriteReset expire=${node.blindData.overwrite.expires}`);
node.debug(`blindPosOverwriteReset expire=${node.blindData.overwrite.expireTs}`);
node.blindData.overwrite.active = false;
if (node.blindData.overwrite.expires) {
node.blindData.overwrite.priority = 0;
if (node.timeOutObj) {
clearTimeout(node.timeOutObj);
node.timeOutObj = null;
}
if (node.blindData.overwrite.expireTs || node.blindData.overwrite.expires) {
delete node.blindData.overwrite.expires;
delete node.blindData.overwrite.expireNever;
delete node.blindData.overwrite.expireTs;
delete node.blindData.overwrite.expireDate;
delete node.blindData.overwrite.expireDateISO;
delete node.blindData.overwrite.expireDateUTC;
delete node.blindData.overwrite.expireTimeLocal;
delete node.blindData.overwrite.expireDateLocal;
}
}
/**
* setup the expiring of n override or update an existing expiring
* @param {*} node node data
* @param {Date} now the current timestamp
* @param {number} expire the expiring time, (if it is NaN, default time will be tried to use) if it is not used, nor a Number or less than 1 no expiring activated
*/
function setExpiringOverwrite(node, now, expire) {
node.debug(`setExpiringOverwrite now=${now}, expire=${expire}`);
if (node.timeOutObj) {
node.debug('clearTimeout');
clearTimeout(node.timeOutObj);
node.timeOutObj = null;
}
if (isNaN(expire)) {
expire = node.blindData.overwrite.expireDuration;
node.debug(`using default expire value ${expire}`);
}
node.blindData.overwrite.expires = Number.isFinite(expire) && (expire > 0);
if (!node.blindData.overwrite.expires) {
node.debug(`expireNever expire=${expire}` + (typeof expire) + ' - isNaN=' + isNaN(expire) + ' - finite=' + !isFinite(expire) + ' - min=' + (expire < 100));
delete node.blindData.overwrite.expireTs;
delete node.blindData.overwrite.expireDate;
return;
}
node.blindData.overwrite.expireTs = (now.getTime() + expire);
node.blindData.overwrite.expireDate = new Date(node.blindData.overwrite.expireTs);
node.blindData.overwrite.expireDateISO = node.blindData.overwrite.expireDate.toISOString();
node.blindData.overwrite.expireDateUTC = node.blindData.overwrite.expireDate.toUTCString();
node.blindData.overwrite.expireDateLocal = node.blindData.overwrite.expireDate.toLocaleString();
node.blindData.overwrite.expireTimeLocal = node.blindData.overwrite.expireDate.toLocaleTimeString();
node.debug(`expires in ${expire}ms = ${node.blindData.overwrite.expireDate}`);
node.timeOutObj = setTimeout(() => {
node.debug('timeout - overwrite expired');
blindPosOverwriteReset(node);
node.emit('input', { payload: -1, topic: 'internal-trigger-overwriteExpired', force: false });
}, expire);
}
/**
* check if a manual overwrite of the blind position should be set
* check if an override can be reset
* @param {*} node node data
* @param {*} msg message object
* @param {*} now current timestamp
*/
function checkBlindPosOverwrite(node, msg) {
node.debug(`checkBlindPosOverwrite act=${node.blindData.overwrite.active}`);
hlp.getMsgBoolValue(msg, 'reset', 'reset',
function checkOverrideReset(node, msg, now) {
if (node.blindData.overwrite && node.blindData.overwrite.expires && (node.blindData.overwrite.expireTs < now.getTime())) {
blindPosOverwriteReset(node);
}
hlp.getMsgBoolValue(msg, 'reset', 'resetOverwrite',
(val) => {
node.debug('reset val="' + util.inspect(val, { colors: true, compact: 10 }) + '"');
if (val) {

@@ -244,55 +295,99 @@ blindPosOverwriteReset(node);

});
}
/**
* setting the reason for override
* @param {*} node node data
*/
function setOverwriteReason(node) {
if (node.blindData.overwrite.expireTs) {
node.reason.code = 3;
const obj = {
prio: node.blindData.overwrite.priority,
timeLocal: node.blindData.overwrite.expireTimeLocal,
dateLocal: node.blindData.overwrite.expireDateLocal,
dateISO: node.blindData.overwrite.expireDateISO,
dateUTC: node.blindData.overwrite.expireDateUTC
};
node.reason.state = RED._('blind-control.states.overwriteExpire', obj);
node.reason.description = RED._('blind-control.reasons.overwriteExpire', obj);
} else {
node.reason.code = 2;
node.reason.state = RED._('blind-control.states.overwriteNoExpire', { prio: node.blindData.overwrite.priority });
node.reason.description = RED._('blind-control.states.overwriteNoExpire', { prio: node.blindData.overwrite.priority });
}
node.debug(`overwrite exit true node.blindData.overwrite.active=${node.blindData.overwrite.active}`);
}
const nowTS = node.time.now.getTime();
const prio = hlp.getMsgBoolValue(msg, ['prio', 'priority', 'alarm'], ['prio', 'alarm']);
if (node.blindData.overwrite.active) {
if (!prio && node.blindData.overwrite.expireNever) {
return true;
/**
* check if a manual overwrite of the blind level should be set
* @param {*} node node data
* @param {*} msg message object
* @returns true if override is active, otherwise false
*/
function checkBlindPosOverwrite(node, msg, now) {
node.debug(`checkBlindPosOverwrite act=${node.blindData.overwrite.active} `);
checkOverrideReset(node, msg, now);
const prio = hlp.getMsgNumberValue(msg, ['prio', 'priority'], ['prio', 'alarm'], undefined, 0);
if (node.blindData.overwrite.active && (node.blindData.overwrite.priority > 0) && (node.blindData.overwrite.priority > prio)) {
setOverwriteReason(node);
node.debug(`overwrite exit true node.blindData.overwrite.active=${node.blindData.overwrite.active}, prio=${prio}, node.blindData.overwrite.priority=${node.blindData.overwrite.priority}`);
// if active, the prio must be 0 or given with same or higher as current overwrite otherwise this will not work
return true;
}
const newPos = hlp.getMsgNumberValue(msg, ['blindPosition', 'position', 'level', 'blindLevel'], ['manual', 'levelOverwrite']);
const expire = hlp.getMsgNumberValue(msg, 'expire', 'expire');
if (node.blindData.overwrite.active && isNaN(newPos)) {
node.debug(`overwrite active, check of prio=${prio} or expire=${expire}, newPos=${newPos}`);
if (Number.isFinite(expire)) {
// set to new expiring time
setExpiringOverwrite(node, now, expire);
}
if (!node.blindData.overwrite.expires || (node.blindData.overwrite.expires > nowTS)) {
blindPosOverwriteReset(node);
if (prio > 0) {
// set to new priority
node.blindData.overwrite.priority = prio;
}
}
const newPos = hlp.getMsgNumberValue(msg, ['blindPosition', 'position'], ['manual', 'overwrite'], undefined, -1);
let expire = hlp.getMsgNumberValue(msg, 'expire', 'expire', undefined, -10);
if (prio || (newPos > -1) || (expire > -10)) {
setOverwriteReason(node);
node.debug(`overwrite exit true node.blindData.overwrite.active=${node.blindData.overwrite.active}, newPos=${newPos}, expire=${expire}`);
return true;
} else if (!isNaN(newPos)) {
node.debug(`needOverwrite prio=${prio} expire=${expire} newPos=${newPos}`);
if (newPos > -1) {
if (newPos === -1) {
node.blindData.level = NaN;
node.blindData.levelInverse = NaN;
} else if (!isNaN(newPos)) {
if (!validPosition_(node, newPos)) {
node.error(RED._('invalid-blind-position', { pos: newPos }));
node.error(RED._('blind-control.errors.invalid-blind-level', { pos: newPos }));
return false;
}
node.debug(`overwrite newPos=${newPos}`);
node.blindData.position = newPos;
}
if (prio || (expire === 0) || (expire === -1)) {
node.blindData.overwrite.expireNever = true;
node.blindData.overwrite.active = true;
node.reason.Code = 2;
node.reason.State = RED._('blind-control.states.overwritePrio');
node.reason.Description = RED._('blind-control.reasons.overwritePrio');
} else {
if (node.timeOutObj) {
blindPosOverwriteReset(node);
const noSameValue = hlp.getMsgBoolValue(msg, 'ignoreSameValue');
if (noSameValue && (node.previousData.level === newPos)) {
setOverwriteReason(node);
node.debug(`overwrite exit true noSameValue=${noSameValue}, newPos=${newPos}`);
return true;
}
expire = (expire < 10) ? node.blindData.overwrite.expireDuration : expire;
node.blindData.overwrite.active = true;
node.blindData.overwrite.expires = (nowTS + expire);
node.debug('expires in ' + expire + 'ms = ' + node.blindData.overwrite.expireDate);
node.blindData.overwrite.expireDate = new Date(node.blindData.overwrite.expires);
node.timeOutObj = setTimeout(() => {
node.debug('timeout overwrites expires');
blindPosOverwriteReset(node);
node.emit('input', {});
}, expire);
node.reason.Code = 3;
node.reason.State = RED._('blind-control.states.overwrite', {
time: node.blindData.overwrite.expireDate.toLocaleTimeString()
});
node.reason.Description = RED._('blind-control.reasons.overwrite', {
time: node.blindData.overwrite.expireDate.toISOString()
});
node.blindData.level = newPos;
node.blindData.levelInverse = newPos;
}
if (Number.isFinite(expire) || (prio <= 0)) {
// will set expiring if prio is 0 or if expire is explizit defined
setExpiringOverwrite(node, now, expire);
} else if ((prio > node.blindData.overwrite.priority) || (!node.blindData.overwrite.expireTs)) {
// no expiring on prio change or no existing expiring
setExpiringOverwrite(node, now, -1);
}
if (prio > 0) {
node.blindData.overwrite.priority = prio;
}
node.blindData.overwrite.active = true;
}
if (node.blindData.overwrite.active) {
setOverwriteReason(node);
node.debug(`overwrite exit true node.blindData.overwrite.active=${node.blindData.overwrite.active}`);
return true;
}
node.debug(`overwrite exit false node.blindData.overwrite.active=${node.blindData.overwrite.active}`);
return false;

@@ -303,63 +398,100 @@ }

/**
* calculates for the blind the new position
* calculates for the blind the new level
* @param {*} node the node data
* @param {*} msg the message object
* @returns the sun position object
*/
function calcBlindPosition(node, msg) {
node.debug('calcBlindPosition');
if (node.time.operator === 0 || !node.sunData.active) {
return;
}
function calcBlindSunPosition(node, msg, now) {
// node.debug('calcBlindSunPosition: calculate blind position by sun');
// sun control is active
const sunPosition = getSunPosition_(node, node.time.now);
msg.sunPosition = sunPosition;
const sunPosition = getSunPosition_(node, now);
if (!sunPosition.InWindow) {
node.blindData.position = node.time.level;
node.reason.Code = 6;
node.reason.State = RED._('blind-control.states.sunNotInWin');
node.reason.Description = RED._('blind-control.reasons.sunNotInWin');
return;
if (node.sunData.mode === 1) {
node.blindData.level = node.blindData.levelMin;
node.blindData.levelInverse = node.blindData.levelMax;
node.reason.code = 13;
node.reason.state = RED._('blind-control.states.sunNotInWinMin');
node.reason.description = RED._('blind-control.reasons.sunNotInWin');
} else {
node.reason.code = 8;
node.reason.state = RED._('blind-control.states.sunNotInWin');
node.reason.description = RED._('blind-control.reasons.sunNotInWin');
}
return sunPosition;
}
// to be able to store values
checkWeather(node, msg);
const previousLevel = node.blindData.position;
if (node.cloudData.inLimit &&
(!node.sunData.minAltitude ||
(sunPosition.altitudeDegrees >= node.sunData.minAltitude))) {
node.debug('node.windowSettings: ' + util.inspect(node.windowSettings, Object.getOwnPropertyNames(node.windowSettings)));
const height = Math.tan(sunPosition.altitudeRadians) * node.sunData.floorLength;
node.debug(`height=${height} - ${sunPosition.altitudeDegrees} - ${node.sunData.floorLength}` );
if (height <= node.windowSettings.bottom) {
node.debug('set to closed position ' + node.blindData.closedPos);
node.blindData.position = node.blindData.closedPos;
} else if (height >= node.windowSettings.top) {
node.debug('set to closed position ' + node.blindData.openPos);
node.blindData.position = node.blindData.openPos;
} else {
// node.blindData.position = roundPos_(node, 100 * (1 - (height - node.windowSettings.bottom) / (node.windowSettings.top - node.windowSettings.bottom)));
node.blindData.position = posPrcToAbs_(node, (1 - (height - node.windowSettings.bottom) / (node.windowSettings.top - node.windowSettings.bottom)));
node.debug('set to calced position ' + node.blindData.position);
}
if (node.hysteresis > 0 && node.blindData.nextchange > node.time.now.getTime() && node.blindData.position > previousLevel) {
node.blindData.position = previousLevel;
node.reason.Code = 10;
node.reason.State = RED._('blind-control.states.hysteresis');
node.reason.Description = RED._('blind-control.reasons.hysteresis');
} else {
node.reason.Code = 7;
node.reason.State = RED._('blind-control.states.sunCtrl');
node.reason.Description = RED._('blind-control.reasons.sunCtrl');
node.blindData.nextchange = node.time.now.getTime() + node.hysteresis;
}
} else if (node.sunData.minAltitude && (sunPosition.altitudeDegrees < node.sunData.minAltitude)) {
node.reason.Code = 5;
node.reason.State = RED._('blind-control.states.sunMinAltitude');
node.reason.Description = RED._('blind-control.reasons.sunMinAltitude');
} else if (!node.cloudData.inLimit) {
node.blindData.position = node.cloudData.blindPos;
node.reason.Code = 8;
node.reason.State = RED._('blind-control.states.cloudExceeded');
node.reason.Description = RED._('blind-control.reasons.cloudExceeded');
if ((node.sunData.mode === 2) && node.sunData.minAltitude && (sunPosition.altitudeDegrees < node.sunData.minAltitude)) {
node.reason.code = 7;
node.reason.state = RED._('blind-control.states.sunMinAltitude');
node.reason.description = RED._('blind-control.reasons.sunMinAltitude');
return sunPosition;
}
node.debug(`calcBlindPosition end pos=${node.blindData.position} reason=${node.reason.Code} description=${node.reason.Description}`);
if (node.cloudData.isOperative) {
node.blindData.level = node.cloudData.blindPos;
node.reason.code = 10;
node.reason.state = RED._('blind-control.states.cloudExceeded');
node.reason.description = RED._('blind-control.reasons.cloudExceeded');
return sunPosition;
}
if (node.sunData.mode === 1) {
node.blindData.level = node.blindData.levelMax;
node.blindData.levelInverse = node.blindData.levelMin;
node.reason.code = 12;
node.reason.state = RED._('blind-control.states.sunInWinMax');
node.reason.description = RED._('blind-control.reasons.sunInWinMax');
return sunPosition;
}
// node.debug('node.windowSettings: ' + util.inspect(node.windowSettings, Object.getOwnPropertyNames(node.windowSettings)));
const height = Math.tan(sunPosition.altitudeRadians) * node.sunData.floorLength;
// node.debug(`height=${height} - altitude=${sunPosition.altitudeRadians} - floorLength=${node.sunData.floorLength}`);
if (height <= node.windowSettings.bottom) {
node.blindData.level = node.blindData.levelClosed;
node.blindData.levelInverse = node.blindData.levelOpen;
} else if (height >= node.windowSettings.top) {
node.blindData.level = node.blindData.levelOpen;
node.blindData.levelInverse = node.blindData.levelClosed;
} else {
node.blindData.level = posPrcToAbs_(node, (height - node.windowSettings.bottom) / (node.windowSettings.top - node.windowSettings.bottom));
node.blindData.levelInverse = getInversePos_(node, node.blindData.level);
}
if ((node.smoothTime > 0) && (node.sunData.changeAgain > now.getTime())) {
// node.debug(`no change smooth - smoothTime= ${node.smoothTime} changeAgain= ${node.sunData.changeAgain}`);
node.reason.code = 11;
node.reason.state = RED._('blind-control.states.smooth', { pos: node.blindData.level.toString()});
node.reason.description = RED._('blind-control.reasons.smooth', { pos: node.blindData.level.toString()});
node.blindData.level = node.previousData.level;
node.blindData.levelInverse = node.previousData.levelInverse;
} else {
node.reason.code = 9;
node.reason.state = RED._('blind-control.states.sunCtrl');
node.reason.description = RED._('blind-control.reasons.sunCtrl');
node.sunData.changeAgain = now.getTime() + node.smoothTime;
// node.debug(`set next time - smoothTime= ${node.smoothTime} changeAgain= ${node.sunData.changeAgain} now=` + now.getTime());
}
if (node.blindData.level < node.blindData.levelMin) {
// min
// node.debug(`${node.blindData.level} is below ${node.blindData.levelMin} (min)`);
node.reason.code = 5;
node.reason.state = RED._('blind-control.states.sunCtrlMin', {org: node.reason.state});
node.reason.description = RED._('blind-control.reasons.sunCtrlMin', {org: node.reason.description, level:node.blindData.level});
node.blindData.level = node.blindData.levelMin;
node.blindData.levelInverse = node.blindData.levelMax;
} else if (node.blindData.level > node.blindData.levelMax) {
// max
// node.debug(`${node.blindData.level} is above ${node.blindData.levelMax} (max)`);
node.reason.code = 6;
node.reason.state = RED._('blind-control.states.sunCtrlMax', {org: node.reason.state});
node.reason.description = RED._('blind-control.reasons.sunCtrlMax', {org: node.reason.description, level:node.blindData.level});
node.blindData.level = node.blindData.levelMax;
node.blindData.levelInverse = node.blindData.levelMin;
}
// node.debug(`calcBlindSunPosition end pos=${node.blindData.level} reason=${node.reason.code} description=${node.reason.description}`);
return sunPosition;
}

@@ -372,100 +504,140 @@ /******************************************************************************************/

* @param {*} config the configuration object
* @returns the active rule or null
*/
function calcTimes(node, msg, config, now) {
node.debug('calcTimes');
function checkRules(node, msg, now) {
const livingRuleData = {};
const nowNr = hlp.getTimeNumber(now);
const rulesLength = node.timeRules.length;
node.debug(`calcTimes now=${nowNr}`);
for (let i = 0; i < rulesLength; ++i) {
const rule = node.timeRules[i];
node.debug('rule ' + i + ' ' + util.inspect(rule, Object.getOwnPropertyNames(rule)));
let operatorValid = true;
node.time.switchProp = rule.propertyType !== 'none';
if (node.time.switchProp) {
// node.debug(`checkRules nowNr=${nowNr}, node.rulesCount=${node.rulesCount}`); // {colors:true, compact:10}
const fkt = (rule, cmp) => {
// node.debug('rule ' + util.inspect(rule, {colors:true, compact:10}));
if (rule.conditional) {
try {
const res = RED.util.evaluateNodeProperty(rule.propertyValue, rule.propertyType, node, msg);
operatorValid = hlp.isTrue(res);
rule.conditonData = {
operandAName: rule.validOperandAType + '.' + rule.validOperandAValue,
operator: rule.validOperator
};
if (!node.positionConfig.comparePropValue(node, msg, rule.validOperandAType, rule.validOperandAValue, rule.validOperator, rule.validOperandBType, rule.validOperandBValue, rule.temp, rule.conditonData)) {
return null;
}
rule.conditonData.operatorText = RED._('node-red-contrib-sun-position/position-config:common.comparators.' + rule.validOperator);
rule.conditonData.text = rule.conditonData.operandAName + ' ' + rule.conditonData.operatorText;
if (rule.conditonData.operandB) {
rule.conditonData.operandBName = rule.validOperandBType + '.' + rule.validOperandBValue;
rule.conditonData.text += ' ' + rule.conditonData.operandB;
}
} catch (err) {
node.warn(RED._('blind-control.errors.getPropertyData', err));
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
continue;
return null;
}
}
if (operatorValid) {
let isTime = true;
if (rule.timeType !== 'none') {
node.time.switchTime = node.positionConfig.getTimeProp(node, msg, rule.timeType, rule.timeValue, rule.offsetValue, rule.offsetType, rule.multiplier);
if (node.time.switchTime.error) {
hlp.handleError(node, RED._('blind-control.errors.error-time', { message: node.time.switchTime.error }), undefined, node.time.switchTime.error);
continue;
} else if (!node.time.switchTime.value) {
throw new Error('Error can not calc time!');
}
node.time.switchTime.num = hlp.getTimeNumber(node.time.switchTime.value);
node.debug('nowNr=' + nowNr + ' switchTimeNum=' + node.time.switchTime.num + ' - end time value = ' + node.time.switchTime.value);
isTime = (node.time.switchTime.num >= nowNr);
}
if (isTime) {
node.time.text = (node.time.switchTime) ? node.time.switchTime.value.toLocaleTimeString() : RED._('blind-control.label.endOfDay');
if (node.time.switchProp) {
node.time.text += '*';
}
node.debug(node.time.text);
node.time.operator = Number(rule.operator);
node.time.rule = i+1;
node.time.level = getBlindPosFromTI(node, msg, rule.levelType, rule.levelValue, node.blindData.defaultPos);
node.blindData.position = node.time.level;
node.reason.Code = 4;
node.reason.State = RED._('blind-control.states.time', node.time);
node.reason.Description = RED._('blind-control.reasons.time', node.time);
if (!rule.timeLimited) {
return rule;
}
rule.timeData = node.positionConfig.getTimeProp(node, msg, rule.timeType, rule.timeValue, rule.offsetValue, rule.offsetType, rule.multiplier);
if (rule.timeData.error) {
hlp.handleError(node, RED._('blind-control.errors.error-time', { message: rule.timeData.error }), undefined, rule.timeData.error);
return null;
} else if (!rule.timeData.value) {
throw new Error('Error can not calc time!');
}
rule.timeData.num = hlp.getTimeNumber(rule.timeData.value);
if ((node.time.operator === 1) && (node.blindData.position <= node.blindData.openPos)) {
// min
node.time.operator = 0;
node.blindData.position = node.blindData.openPos;
}
if ((node.time.operator === 2) && (node.blindData.position >= node.blindData.closedPos)) {
// max
node.time.operator = 0;
node.blindData.position = node.blindData.closedPos;
}
return;
if (cmp(rule.timeData.num, nowNr)) {
return rule;
}
return null;
};
let ruleSel = null;
// node.debug('first loop ' + node.rulesCount);
for (let i = 0; i < node.rulesCount; ++i) {
const rule = node.rulesData[i];
// node.debug('rule ' + rule.timeOp + ' - ' + (rule.timeOp !== 1) + ' - ' + util.inspect(rule, {colors:true, compact:10}));
if (rule.timeOp !== 0) { continue; }
const res = fkt(rule, (r,h) => (r >= h));
if (res) {
ruleSel = res;
break;
}
}
if (!ruleSel || ruleSel.timeLimited) {
// node.debug('second loop ' + node.rulesCount);
for (let i = (node.rulesCount -1); i >= 0; --i) {
const rule = node.rulesData[i];
// node.debug('rule ' + rule.timeOp + ' - ' + (rule.timeOp !== 1) + ' - ' + util.inspect(rule, {colors:true, compact:10}));
if (rule.timeOp === 0) { continue; }
const res = fkt(rule, (r,h) => (r <= h));
if (res) {
ruleSel = res;
break;
}
}
}
node.time.operator = 2; // max
if (node.blindData.defaultPos === node.blindData.closedPos) {
node.time.operator = 1; // min
if (ruleSel) {
ruleSel.text = '';
// node.debug('ruleSel ' + util.inspect(ruleSel, {colors:true, compact:10}));
livingRuleData.id = ruleSel.pos;
livingRuleData.active = true;
livingRuleData.level = getBlindPosFromTI(node, msg, ruleSel.levelType, ruleSel.levelValue, node.blindData.levelDefault);
livingRuleData.conditional = ruleSel.conditional;
livingRuleData.timeLimited = ruleSel.timeLimited;
node.blindData.level = livingRuleData.level;
node.blindData.levelInverse = getInversePos_(node, livingRuleData.level);
node.reason.code = 4;
const data = { number: ruleSel.pos };
let name = 'rule';
if (ruleSel.conditional) {
livingRuleData.conditon = ruleSel.conditonData;
data.text = ruleSel.conditonData.text;
data.operatorText = ruleSel.conditonData.operatorText;
name = 'ruleCond';
}
if (ruleSel.timeLimited) {
livingRuleData.time = ruleSel.timeData;
livingRuleData.time.timeLocal = ruleSel.timeData.value.toLocaleTimeString();
livingRuleData.time.dateISO= ruleSel.timeData.value.toISOString();
livingRuleData.time.dateUTC= ruleSel.timeData.value.toUTCString();
data.timeOp = ruleSel.timeOpText;
data.timeLocal = livingRuleData.time.timeLocal;
data.time = livingRuleData.time.dateISO;
name = (ruleSel.conditional) ? 'ruleTimeCond' : 'ruleTime';
}
node.reason.state= RED._('blind-control.states.'+name, data);
node.reason.description = RED._('blind-control.reasons.'+name, data);
// node.debug('checkRules end: livingRuleData=' + util.inspect(livingRuleData,{colors:true, compact:10}));
return livingRuleData;
}
node.time.level = node.blindData.defaultPos;
node.blindData.position = node.blindData.defaultPos;
node.reason.Code = 1;
node.reason.State = RED._('blind-control.states.default');
node.reason.Description = RED._('blind-control.reasons.default');
delete node.time.rule;
delete node.time.switchTime;
livingRuleData.active = false;
livingRuleData.id = -1;
node.blindData.level = node.blindData.levelDefault;
node.blindData.levelInverse = getInversePos_(node, node.blindData.levelDefault);
node.reason.code = 1;
node.reason.state = RED._('blind-control.states.default');
node.reason.description = RED._('blind-control.reasons.default');
// node.debug('checkRules end default: livingRuleData=' + util.inspect(livingRuleData, {colors:true, compact:10}));
return livingRuleData;
}
/******************************************************************************************/
/******************************************************************************************/
/**
* standard Node-Red Node handler for the sunBlindControlNode
* @param {*} config the Node-Red Configuration property of the Node
*/
function sunBlindControlNode(config) {
RED.nodes.createNode(this, config);
this.positionConfig = RED.nodes.getNode(config.positionConfig);
this.interval = config.interval || 0;
this.once = config.once;
this.onceDelay = (config.onceDelay || 0);
this.hysteresis = config.hysteresis || 0;
this.outputs = Number(config.outputs || 1);
this.smoothTime = (parseFloat(config.smoothTime) || 0);
const node = this;
if (node.interval >= 0x7FFFFFFF) {
node.error(RED._('blind-control.errors.intervaltoolong', this));
delete node.interval;
if (node.smoothTime >= 0x7FFFFFFF) {
node.error(RED._('blind-control.errors.smoothTimeToolong', this));
delete node.smoothTime;
}
if (node.hysteresis >= 0x7FFFFFFF) {
node.error(RED._('blind-control.errors.hysteresistoolong', this));
delete node.hysteresis;
}
node.reason = {
Code : 0,
State: '',
Description: ''
code : 0,
state: '',
description: ''
};

@@ -475,9 +647,12 @@ // Retrieve the config node

/** Defines if the sun control is active or not */
active: hlp.chkValueFilled(config.sunControlActive,true),
active: false,
mode: Number(hlp.chkValueFilled(config.sunControlMode, 0)),
/** define how long could be the sun on the floor **/
floorLength: Number(hlp.chkValueFilled(config.sunFloorLength,0)),
/** minimum altitude of the sun */
minAltitude: Number(hlp.chkValueFilled(config.sunMinAltitude,0)),
isDay: false
minAltitude: Number(hlp.chkValueFilled(config.sunMinAltitude, 0)),
changeAgain: 0
};
node.sunData.active = node.sunData.mode > 0;
node.sunData.modeMax = node.sunData.mode;
node.windowSettings = {

@@ -494,20 +669,21 @@ /** The top of the window */

node.blindData = {
position: -1, // unknown
increment: Number(hlp.chkValueFilled(config.blindIncrement,1)),
openPos: Number(hlp.chkValueFilled(config.blindOpenPos, 100)),
closedPos: Number(hlp.chkValueFilled(config.blindClosedPos, 0)),
defaultPos: NaN,
/** position if sun is not in window */
overwrite : {
/** The Level of the window */
level: NaN, // unknown
levelInverse: NaN,
levelOpen: Number(hlp.chkValueFilled(config.blindOpenPos, 100)),
levelClosed: Number(hlp.chkValueFilled(config.blindClosedPos, 0)),
increment: Number(hlp.chkValueFilled(config.blindIncrement, 1)),
levelDefault: NaN,
levelMin: NaN,
levelMax: NaN,
/** The override settings */
overwrite: {
active: false,
value: config.overwriteValue || '',
valueType: config.overwriteValueType || 'none',
valuePrio: config.overwritePrioValue || '',
valuePrioType: config.overwritePrioValueType || 'none',
expireDuration: Number(hlp.chkValueFilled(config.overwriteExpire, 0)),
alarm: false
},
nextchange:0
expireDuration: parseFloat(hlp.chkValueFilled(config.overwriteExpire, NaN)),
priority: 0
}
};
node.blindData.defaultPos = getBlindPosFromTI(node, undefined, config.blindDefaultPosType, config.blindDefaultPos, node.blindData.openPos);
node.blindData.levelDefault = getBlindPosFromTI(node, undefined, config.blindPosDefaultType, config.blindPosDefault, node.blindData.levelOpen);
node.blindData.levelMin = getBlindPosFromTI(node, undefined, config.blindPosMinType, config.blindPosMin, node.blindData.levelClosed);
node.blindData.levelMax = getBlindPosFromTI(node, undefined, config.blindPosMaxType, config.blindPosMax, node.blindData.levelOpen);
node.cloudData = {

@@ -517,24 +693,53 @@ active: (typeof config.cloudValueType !== 'undefined') && (config.cloudValueType !== 'none'),

valueType: config.cloudValueType || 'none',
actValue: 100,
threshold: config.cloudThreshold,
thresholdValue: 100,
blindPos: getBlindPosFromTI(node, undefined, config.cloudBlindPosType, config.cloudBlindPos, node.blindData.openPos),
inLimit: true
operator: config.cloudCompare,
thresholdValue: config.cloudThreshold,
thresholdType: config.cloudThresholdType,
blindPos: getBlindPosFromTI(node, undefined, config.cloudBlindPosType, config.cloudBlindPos, node.blindData.levelOpen),
isOperative: false,
temp: {}
};
node.timeRules = config.rules || [];
node.time = {};
node.rulesData = config.rules || [];
node.previousData = {
level: NaN,
reasonCode: -1,
usedRule: NaN
};
/**
* set the state of the node
*/
function setState() {
let code = node.reason.code;
let shape = 'ring';
let fill = 'yellow';
if (code === 10) { // smooth;
code = node.previousData.reasonCode;
}
if (node.blindData.level === node.blindData.levelOpen) {
shape = 'dot';
}
if (code <= 3) {
fill = 'blue'; // override
} else if (code === 4) {
fill = 'grey'; // rule
} else if (code === 1 || code === 8) {
fill = 'green'; // not in window or cloudExceeded
}
node.status({
fill: (node.reason.Code <= 3) ? 'blue' :
((node.reason.Code === 4) ? 'grey' :
((node.reason.Code >= 8) ? 'green' : 'yellow')),
shape: node.blindData.position === node.blindData.openPos ? 'dot' : 'ring',
text: node.blindData.position.toString() + ' - ' + node.reason.State
fill: fill,
shape: shape,
text: (isNaN(node.blindData.level)) ? node.reason.state : node.blindData.level.toString() + ' - ' + node.reason.state
});
}
/**
* handles the input of a message object to the node
*/
this.on('input', function (msg) {
try {
this.debug('input ' + util.inspect(msg, Object.getOwnPropertyNames(msg)));
node.debug(`input msg.topic=${msg.topic} msg.payload=${msg.payload}`);
// node.debug('input ' + util.inspect(msg, {colors:true, compact:10})); // Object.getOwnPropertyNames(msg)
if (!this.positionConfig) {

@@ -549,50 +754,80 @@ node.error(RED._('node-red-contrib-sun-position/position-config:errors.pos-config'));

}
const blindCtrl = {
reason : node.reason,
blind: node.blindData
};
msg.reason = node.reason;
node.time.now = getNow_(node, msg, config.tsCompare);
node.debug('node.time=' + util.inspect(node.time));
node.previousData.level = node.blindData.level;
node.previousData.levelInverse = node.blindData.levelInverse;
node.previousData.reasonCode= node.reason.code;
node.previousData.reasonState= node.reason.state;
node.previousData.reasonDescription= node.reason.description;
node.reason.code = NaN;
const now = getNow_(node, msg);
// check if the message contains any weather data
let ruleId = NaN;
const previous = {
position: node.blindData.position,
reasonCode: node.reason.Code
};
const newMode = hlp.getMsgNumberValue(msg, ['mode'], ['setMode']);
if (Number.isFinite(newMode) && newMode >= 0 && newMode <= node.sunData.modeMax) {
node.sunData.mode = newMode;
}
// node.debug(`start pos=${node.blindData.level} manual=${node.blindData.overwrite.active} reasoncode=${node.reason.code} description=${node.reason.description}`);
// check for manual overwrite
if (!checkBlindPosOverwrite(this, msg, node.time.now)) {
if (!checkBlindPosOverwrite(node, msg, now)) {
// calc times:
calcTimes(this, msg, config, node.time.now);
// calc sun position:
calcBlindPosition(node, msg);
if ((node.time.operator === 1) && (node.blindData.position < node.time.level)) {
// min
node.debug(`${node.blindData.position} is below ${node.time.level} (min)`);
node.blindData.position = node.time.level;
} else if ((node.time.operator === 2) && (node.blindData.position > node.time.level)) {
// max
node.debug(`${node.blindData.position} is above ${node.time.level} (max)`);
node.blindData.position = node.time.level;
blindCtrl.rule = checkRules(node, msg, now);
ruleId = blindCtrl.rule.id;
if (!blindCtrl.rule.active && node.sunData.active) {
// calc sun position:
blindCtrl.sunPosition = calcBlindSunPosition(node, msg, now);
if (node.cloudData.active) {
blindCtrl.cloud = node.cloudData;
}
}
if (node.blindData.position < node.blindData.closedPos) {
node.debug(`${node.blindData.position} is below ${node.blindData.closedPos}`);
node.blindData.position = node.blindData.closedPos;
if (node.blindData.level < node.blindData.levelClosed) {
node.debug(`${node.blindData.level} is below ${node.blindData.levelClosed}`);
node.blindData.level = node.blindData.levelClosed;
node.blindData.levelInverse = node.blindData.levelOpen;
}
if (node.blindData.position > node.blindData.openPos) {
node.debug(`${node.blindData.position} is above ${node.blindData.closedPos}`);
node.blindData.position = node.blindData.openPos;
if (node.blindData.level > node.blindData.levelOpen) {
node.debug(`${node.blindData.level} is above ${node.blindData.levelClosed}`);
node.blindData.level = node.blindData.levelOpen;
node.blindData.levelInverse = node.blindData.levelClosed;
}
}
node.debug(`result pos=${node.blindData.position} manual=${node.blindData.overwrite.active} reasoncode=${node.reason.Code} description=${node.reason.Description}`);
node.debug(`result pos=${node.blindData.level} manual=${node.blindData.overwrite.active} reasoncode=${node.reason.code} description=${node.reason.description}`);
setState();
if (node.blindData.position !== previous.position ||
node.reason.Code !== previous.reasonCode) {
msg.payload = node.blindData.position;
msg.blind = node.blindData;
msg.time = node.time;
if (config.topic) {
msg.topic = config.topic;
let topic = config.topic;
if (topic) {
const topicAttrs = {
name: node.name,
level: node.blindData.level,
levelInverse: node.blindData.levelInverse,
code: node.reason.code,
state: node.reason.state,
rule: ruleId,
mode: node.sunData.mode,
topic: msg.topic,
payload: msg.payload
};
topic = hlp.topicReplace(config.topic, topicAttrs);
}
if ((!isNaN(node.blindData.level)) &&
(node.blindData.level !== node.previousData.level ||
node.reason.code !== node.previousData.reasonCode ||
ruleId !== node.previousData.usedRule)) {
msg.payload = node.blindData.level;
if (node.outputs > 1) {
node.send([msg, { topic: topic, payload: blindCtrl}]);
} else {
msg.topic = topic || msg.topic;
msg.blindCtrl = blindCtrl;
node.send(msg, null);
}
setState();
node.send(msg);
} else if (node.outputs > 1) {
node.send([null, { topic: topic, payload: blindCtrl}]);
}
node.previousData.usedRule = ruleId;
return null;

@@ -609,39 +844,22 @@ } catch (err) {

});
node.repeaterSetup = function () {
if (this.interval && !isNaN(this.interval) && this.interval > 200) {
this.interval_id = setInterval(() => {
node.emit('input', {});
}, this.interval);
// ####################################################################################################
/**
* initializes the node
*/
function initialize() {
node.debug('initialize');
node.rulesCount = node.rulesData.length;
for (let i = 0; i < node.rulesCount; ++i) {
const rule = node.rulesData[i];
rule.timeOp = Number(rule.timeOp);
rule.pos = i + 1;
rule.conditional = (rule.validOperandAType !== 'none');
rule.timeLimited = (rule.timeType !== 'none');
rule.temp = {};
}
};
if (this.once) {
this.onceTimeout = setTimeout(() => {
node.emit('input',{});
node.repeaterSetup();
}, this.onceDelay);
} else {
node.repeaterSetup();
}
sunBlindControlNode.prototype.close = function() {
if (this.onceTimeout) {
clearTimeout(this.onceTimeout);
}
if (this.interval_id !== null) {
clearInterval(this.interval_id);
}
};
initialize();
}
RED.nodes.registerType('blind-control', sunBlindControlNode);
RED.httpAdmin.get('/sun-position/js/*', RED.auth.needsPermission('sun-position.read'), (req, res) => {
const options = {
root: __dirname + '/static/',
dotfiles: 'deny'
};
res.sendFile(req.params[0], options);
});
};

@@ -29,3 +29,4 @@ /********************************************

getFormattedDateOut,
parseDateFromFormat
parseDateFromFormat,
topicReplace
};

@@ -99,16 +100,2 @@

/*******************************************************************************************************/
/**
* gets a comparable date Format
* @param {Date} date - Date to format
* @return {string} number in Format YYYYMMDDHHMMSS
*/
function _getComparableDateFormat(date) {
return Number(date.getFullYear() +
pad2(date.getMonth() + 1) +
pad2(date.getDate()) +
pad2(date.getHours()) +
pad2(date.getMinutes()) +
pad2(date.getSeconds()));
}
/*******************************************************************************************************/
/* Node-Red Helper functions */

@@ -258,2 +245,16 @@ /*******************************************************************************************************/

* @param {Date} date - Date to format
* @return {string} number in Format YYYYMMDDHHMMSS
*/
function _getComparableDateFormat(date) {
return Number(date.getFullYear() +
pad2(date.getMonth() + 1) +
pad2(date.getDate()) +
pad2(date.getHours()) +
pad2(date.getMinutes()) +
pad2(date.getSeconds()));
}
/**
* gets a comparable date Format
* @param {Date} date - Date to format
* @return {string} number in Format YYYYMMDD.HHMMSS

@@ -307,2 +308,3 @@ */

function checkLimits(num, low, high) {
// console.debug('checkLimits num=' + num + ' low=' + low + ' high=' + high); // eslint-disable-line
if (typeof low !== 'undefined' && low !== '' && !isNaN(low) && low >= 0) {

@@ -313,6 +315,4 @@ if (typeof high !== 'undefined' && high !== '' && !isNaN(high) && high >= 0) {

}
return (num > low) || (num < high);
}
return (num > low);

@@ -324,3 +324,2 @@ }

}
return false;

@@ -341,4 +340,4 @@ }

const id = ids[i];
if (msg.payload && (typeof msg.payload[id] !== 'undefined')) {
const res = parseFloat(msg.payload[id]);
if (msg.payload && (typeof msg.payload[id] !== 'undefined') && (msg.payload[id] !== '')) {
const res = Number(msg.payload[id]); // Number() instead of parseFloat() to also parse boolean
if (!isNaN(res)) {

@@ -351,4 +350,4 @@ if (typeof isFound === 'function') {

}
if (typeof msg[id] !== 'undefined') {
const res = parseFloat(msg[id]);
if ((typeof msg[id] !== 'undefined') && (msg[id] !== '')) {
const res = Number(msg[id]);
if (!isNaN(res)) {

@@ -363,5 +362,4 @@ if (typeof isFound === 'function') {

}
// includes
if (names && msg && msg.topic && msg.payload) {
const res = parseFloat(msg.payload);
if (names && msg && msg.topic) {
const res = Number(msg.payload);
if (!isNaN(res)) {

@@ -372,10 +370,7 @@ if (!Array.isArray(names)) {

for (let i = 0; i < names.length; i++) {
if (String(msg.topic).toLowerCase().includes(names[i])) {
const res = parseFloat(msg.payload);
if (!isNaN(res)) {
if (typeof isFound === 'function') {
return isFound(res);
}
return res;
if (String(msg.topic).includes(String(names[i]))) {
if (typeof isFound === 'function') {
return isFound(res);
}
return res;
}

@@ -387,4 +382,6 @@ }

return notFound(msg);
} else if (typeof notFound === 'undefined') {
return NaN;
}
return notFound || NaN;
return notFound;
}

@@ -406,3 +403,3 @@

if (typeof isFound === 'function') {
return isFound(isTrue(msg.payload[id]));
return isFound(isTrue(msg.payload[id]), msg.payload[id], msg.topic);
}

@@ -413,3 +410,3 @@ return isTrue(msg.payload[id]);

if (typeof isFound === 'function') {
return isFound(isTrue(msg[id]));
return isFound(isTrue(msg[id]), msg[id], msg.topic);
}

@@ -420,4 +417,3 @@ return isTrue(msg[id]);

}
if (names && msg && msg.topic && ((typeof msg.payload === 'string') || (typeof msg.payload === 'number'))) {
if (names && msg && msg.topic) {
if (!Array.isArray(names)) {

@@ -427,7 +423,7 @@ names = [names];

for (let i = 0; i < names.length; i++) {
if (String(msg.topic).toLowerCase().includes(names[i])) {
if (String(msg.topic).includes(String(names[i]))) {
if (typeof isFound === 'function') {
return isFound(isTrue(msg.payload));
return isFound(true, msg.payload, msg.topic);
}
return isTrue(msg.payload);
return true;
}

@@ -438,7 +434,6 @@ }

return notFound(msg);
} else if (typeof notFound === 'undefined') {
return false;
}
if (typeof notFound === 'boolean') {
return notFound;
}
return false;
return notFound;
}

@@ -1419,2 +1414,25 @@

return res;
}
function topicReplace(topic, topicAttrs) {
if (!topic || typeof topicAttrs !== 'object') {
return topic;
}
const topicAttrsLower = {};
Object.keys(topicAttrs).forEach(k => {
topicAttrsLower[k.toLowerCase()] = topicAttrs[k];
});
const match = topic.match(/\${[^}]+}/g);
if (match) {
match.forEach(v => {
const key = v.substr(2, v.length - 3);
const rx = new RegExp('\\${' + key + '}', 'g');
const rkey = key.toLowerCase();
topic = topic.replace(rx, topicAttrsLower[rkey] || '');
});
}
return topic;
}
{
"moon-position": {
"label": {
"azimuthpos":"azimuth Position",
"between":"zwischen",

@@ -5,0 +6,0 @@ "and":"und"

@@ -160,11 +160,27 @@ {

],
"blindOperatorGroups": [
"Rollladen Position einschränken",
"feste position"
"comparatorGroups": [
"einfach",
"mehr",
"erweitert"
],
"blindOperator": [
"minimal",
"maximal",
"absolut"
"comparator": [
"ist wahr",
"ist falsch",
"ist null",
"ist nicht null",
"ist leer",
"ist nicht leer",
"ist ein wahr Ausdruck (true, on, wahr, 1)",
"ist ein falsch Ausdruck (false, off, falsch, 0)",
"ist kein wahr Ausdruck (true, on, wahr, 1)",
"ist kein falsch Ausdruck (false, off, falsch, 0)",
"gleich",
"ungleich",
"kleiner als",
"kleiner oder gleich",
"größer",
"größer oder gleich",
"enthält"
],
"days": [

@@ -193,3 +209,22 @@ "Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Sonnabend",

"in 6 Tagen"
]
],
"comparators" : {
"true": "ist wahr",
"false": "ist falsch",
"null": "ist null",
"nnull": "ist nicht null",
"empty": "ist leer",
"nempty": "ist nicht empty",
"true_expr": "ist wahr*",
"false_expr": "ist falsch*",
"ntrue_expr": "ist nicht wahr*",
"nfalse_expr": "ist nicht falsch*",
"equal": "=",
"nequal": "!=",
"lt": "<",
"lte": "<=",
"gt": ">",
"gte": ">=",
"contain": "enthält"
}
},

@@ -200,3 +235,10 @@ "errors": {

"error-init": "Fehler '__message__', wiederhole in __time__min",
"warn-init": "Warnung '__message__', wiederhole in __time__min"
"warn-init": "Warnung '__message__', wiederhole in __time__min",
"pos-config": "Node ist nicht richtig konfiguriert!! Fehlende oder flasche positionsangaben!",
"pos-config-state": "Node ist nicht richtig konfiguriert!!",
"unknownPropertyOperator": "Fehler, der verwendete Operator __propertyOp__=\"__propertyOpText__\" ist unbekannt!",
"unknownCompareOperator": "Fehler, der verwendete vergleichs Operator \"__operator__\" ist unbekannt!",
"notEvaluableProperty":"Fehler: kann den Wert von __type__.__value__ nicht ermitteln",
"notEvaluablePropertyDefault": "Fehler: kann den Wert von __type__.__value__ nicht ermitteln, verwende \"__usedValue__\"!",
"notEvaluablePropertyAdd":"Fehler \"__err__\", kann den Wert von __type__.__value__ nicht ermitteln!"
},

@@ -214,2 +256,10 @@ "position-config": {

},
"placeholder": {
"positionConfig": "wähle oder erstelle die Konfiguration",
"latitude": "51.025",
"longitude": "-1.4",
"angleType": "deg",
"timezoneOffset": "0",
"name": "Name"
},
"tips": {

@@ -216,0 +266,0 @@ "config": "Starting from Version 2.0 the coordinates are not saved as credentials due to privacy reasons. So they no longer part of the regular flow and will not part of the export! To update from a previous version save and re-deploy is necessary.",

@@ -7,3 +7,2 @@ {

"and":"und",
"name": "Name",
"start": "Start",

@@ -10,0 +9,0 @@ "startOffset":"Start Offset",

@@ -5,2 +5,3 @@ {

"property": "Property",
"propertyThreshold": "Schwelle",
"time": "Zeit",

@@ -36,2 +37,3 @@ "offset": "Offset",

"property": "Property",
"propertyThreshold": "schwellwert",
"payloadStart": "",

@@ -56,2 +58,3 @@ "payloadEnd": "",

"errors": {
"failed": "inject fehlgeschlagen, sehen sie ins Log für mehr Informationen",
"invalid-property-type": "Ungültiger Datentyp: __type__"

@@ -58,0 +61,0 @@ }

@@ -6,5 +6,7 @@ {

"propertyStart": "Alt. Start",
"propertyStartThreshold": "Schwelle",
"startTime": "Start",
"startTimeAlt": "Start",
"propertyEnd": "Alt. Ende",
"propertyEndThreshold": "Schwelle",
"endTime": "Ende",

@@ -27,5 +29,9 @@ "endTimeAlt": "Ende",

"compareTime":"Vergleichs Basis"
"showEnhSettings":"Erweiterte Einstellungen"
},
"placeholder": {
"property": "Property",
"propertyStart": "Wert",
"propertyStartThreshold": "schwellwert",
"propertyEnd": "Wert",
"propertyEndThreshold": "schwellwert",
"startTime": "7:40",

@@ -32,0 +38,0 @@ "startOffset": "0",

{
"blind-control": {
"label": {
"windowPosText": "window settings, measurement from the floor to bottom and top of the window covered by the blind",
"blind-control":"blind-control",
"blind-control-palette": "blind",
"output1":"new blind level",
"output2": "state object",
"windowTop": "window top",
"windowBottom": "window bottom",
"windowAzimuthText": "representing the orientation of the window to geographical north (in degrees) when the sun falls into the window (sun azimuth).",
"windowAzimuthStart":"start",
"windowAzimuthEnd": "end",
"blindText": "blind settings",
"blindIncrement": "Increment",
"blindOpenPos": "open position (max)",
"blindClosedPos": "closed position (min)",
"blindDefaultPosText":"if none matches the following position will be used",
"blindDefaultPos": "default position",
"overwriteText": "manual overwrite of the blind position",
"blindLevelDefault": "default position",
"blindLevelMin": "min position",
"blindLevelMax": "max position",
"overwriteExpire": "expire",
"addTimes": "alternate start or end time",
"TimeText": "Time control: starting from midnight, the first matching line will used, where time is <b>not used</b> or time is less than <b>now</b> and where property is <b>not used</b> or <em>true</em>",
"compareTime": "base time",
"onlyIf": "only if",
"blindLevel": "Blind level",
"sunControlActive": "active",
"sunControlActiveText": "Restricting the extent to which direct sunlight may shine into the room",
"ruleCondition": "only if",
"ruleConditionThreshold": "threshold",
"ruleTimeFrom": "from",
"ruleTimeUntil": "until",
"ruleBlindLevel": "blind position",
"sunControlMode": "mode",
"sunControlOff": "no restriction, no sun control",
"sunControlRestrict": "restrict sunlight (Summer)",
"sunControlMaximize": "maximize sunlight (Winter)",
"sunFloorLength": "length on the floor",
"sunMinAltitude": "min altitude threshold",
"tempValue": "Temperature",
"tempThreshold": "threshold",
"tempBlindPos": "blind position",
"cloudValue": "Cloud",
"cloudThreshold": "Threshold",
"cloudBlindPos": "blind position",
"now": "date.now",
"msgts": "msg.ts",
"msglc": "msg.lc",
"msgtime": "msg.time",
"msgvalue": "msg.value",
"smoothTime": "smoothTime",
"showEnhSettings": "Enhanced settings",
"oncePre": "Interval",
"onstart":"nach Start verzögert",
"onceDelay":"seconds",
"interval":"re-calc interval",
"hysteresis":"hysteresis",
"endOfDay": "end of day"
"outputs": "Outputs",
"singleOutput": "single (1)",
"splitOutput": "dual (2)"
},

@@ -54,46 +47,65 @@ "placeholder": {

"blindClosedPos": "0",
"blindDefaultPos": "position if no other used",
"OverwriteExpire": "(opt) the duration a manual setting will remain is place",
"sunFloorLength": "the extent to which direct sunlight is to be allowed into the room through the window, defined as a length on the floor",
"blindLevelDefault": "position if no other used",
"blindLevelMin": "maximum position if sun calculated position",
"blindLevelMax": "minimum position if sun calculated position",
"sunControlMode": "select if sun control is active and how it should working",
"OverwriteExpire": "(opt) the duration a manual setting will remain",
"sunFloorLength": "(opt) the extent to which direct sunlight is to be allowed into the room through the window, defined as a length on the floor",
"sunMinAltitude": "(opt) minimum altitude of the sun for determination of blind position",
"cloudValue": "percentage of sky occluded by clouds",
"cloudThreshold": "threshold for set blind position",
"cloudBlindPos": "blind position when cloud-value is below threshold",
"interval":"(opt) recalculation interval",
"hysteresis":"(opt) prevents changes due to sun in the given time",
"cloudValue": "value to compare",
"cloudThreshold": "threshold for the compare",
"cloudBlindPos": "blind position when the compare be the case",
"smoothTime":"(opt) prevents changes due to sun in the given time",
"name": "Name",
"nightposition": "100",
"expiryperiod": "120",
"property": "Property",
"startOffset": "0",
"endOffset": "0",
"startOffsetAlt": "0",
"endOffsetAlt": "0"
"outputs": "number of outputs"
},
"text": {
"blind": "blind settings",
"time": "Time control:Only rows will be evaluated, where property is <b>not used</b> or matches the selected value. <br><b>until</b>:The first matching line will used, where defined time is <b>not used</b> or greater than <b>now</b>.<br><b>from</b>:The last matching line will used, where defined time is <b>not used</b> or less than <b>now</b>.<br>",
"blindLevelDefault":"if no rule matches the following position will be used, On sun light restriction this is the blind level wenn sun is not in the window.",
"overwrite": "manual overwrite of the blind position",
"sunControlNotActive": "Only rule control",
"sunControlMode": "Instead of the default blind position there can be restricted the extent to which direct sunlight may shine into the room.",
"windowAzimuth": "representing the orientation of the window to geographical north (in degrees) when the sun falls into the window (sun azimuth).",
"windowPos": "window settings, measurement from the floor to bottom and top of the window covered by the blind"
},
"tips": {
"addTimes": "..."
"sunPosControl": "..."
},
"reasons": {
"overwritePrio": "position is overwritten by priority, no expire",
"overwrite": "position is overwritten, will expire __time__",
"overwriteNoExpire": "position is overwritten with priority __prio__",
"overwriteExpire": "position is overwritten with priority __prio__, will expire __dateISO__",
"default": "position is set to default position because no other rule matches",
"time": "position by time [rule __rule__] until __text__",
"ruleTime": "position by time - rule __timeOp__ __timeLocal__ [rule __number__]",
"ruleCond": "position by conditional rule __text__ [rule __number__]",
"ruleTimeCond": "position by time rule __timeOp__ __timeLocal__ and condition __text__ [rule __number__]",
"rule": "position by fixed rule __number__",
"sunMinAltitude": "sun below minimum altitude",
"sunNotInWin": "Sun not in window",
"sunCtrl": "sun control",
"sunCtrlMin": "__org__ (__level__ is below minimum)",
"sunCtrlMax": "__org__ (__level__ is above maximum)",
"cloudExceeded": "Cloud overcast conditions",
"tempExceeded": "temp exceeded",
"hysteresis": "Position is not changed because not enough time has passed since the last change"
"smooth": "Position is not changed to __pos__ because not enough time has passed since the last change (time smooth)",
"sunInWinMax": "Sun in window (Level is maximum)",
"sunNotInWinMin": "Sun not in window (Level is minimum)"
},
"states": {
"overwritePrio": "overwritten",
"overwrite": "overwritten till __time__",
"overwriteNoExpire": "overwritten [prio=__prio__]",
"overwriteExpire": "overwritten [prio=__prio__], till __timeLocal__",
"default": "default",
"time": "by time [__rule__] until __text__",
"ruleTime": "__timeOp__ __timeLocal__ [rule __number__]",
"ruleCond": "__text__ [rule __number__]",
"ruleTimeCond": "__timeOp__ __timeLocal__ + cond __operatorText__ [rule __number__]",
"rule": "by fixed rule __number__",
"sunMinAltitude": "min altitude",
"sunNotInWin": "no sun in window",
"sunCtrl": "sun control",
"sunCtrlMin": "__org__ (min)",
"sunCtrlMax": "__org__ (max)",
"cloudExceeded": "cloud",
"tempExceeded": "temp",
"hysteresis": "no change - hysteresis"
"smooth": "block change to __pos__",
"sunInWinMax":"Sun in window (max)",
"sunNotInWinMin": "Sun not in window (min)"
},

@@ -103,10 +115,9 @@ "errors": {

"error-time": "Error get time: __message__",
"invalid-blind-position": "Given Blind-Position __pos__ is not a valid Position!",
"invalid-blind-level": "Given Blind-Position __pos__ is not a valid Position!",
"getCloudData": "error getting cloudData: __message__",
"getBlindPosData": "error getting blind position: __message__",
"getPropertyData": "error getting property data: \"__message__\" skipping time",
"intervaltoolong":"The selected interval is too long!!",
"hysteresistoolong":"The selected hysteresis is too long!!"
"getBlindPosData": "error getting blind level: __message__",
"getPropertyData": "error getting condition data: \"__message__\" skipping time",
"smoothTimeToolong":"The selected smooth is too long!!"
}
}
}
{
"moon-position": {
"label": {
"azimuthpos":"azimuth Position",
"between":"between",

@@ -5,0 +6,0 @@ "and":"and"

@@ -160,10 +160,25 @@ {

],
"blindOperatorGroups": [
"limit blind position",
"fixed position"
"comparatorGroups": [
"simple",
"medium",
"enhanced"
],
"blindOperator": [
"minimum",
"maximum",
"absolute"
"comparator": [
"is true",
"is false",
"is null",
"is not null",
"is empty",
"is not empty",
"is a true expression (true, on, 1)",
"is a false expression (false, off, 0)",
"is not a true expression",
"is not a false expression",
"equal",
"not equal",
"less than",
"less than or equal",
"greater than",
"greater than or equal",
"contain"
],

@@ -207,2 +222,21 @@ "days": [

"isoUtcDateTime": "UTC:yyyy-MM-dd'T'HH:mm:ss'Z'"
},
"comparators" : {
"true": "is true",
"false": "is false",
"null": "is null",
"nnull": "is not null",
"empty": "is empty",
"nempty": "is not empty",
"true_expr": "is true*",
"false_expr": "is false*",
"ntrue_expr": "is not true*",
"nfalse_expr": "is not false*",
"equal": "=",
"nequal": "!=",
"lt": "<",
"lte": "<=",
"gt": ">",
"gte": ">=",
"contain": "contain"
}

@@ -216,3 +250,8 @@ },

"pos-config": "Node not properly configured!! Missing or wrong position configuration!",
"pos-config-state": "Node not properly configured!!"
"pos-config-state": "Node not properly configured!!",
"unknownPropertyOperator": "error, the unsed property operator __propertyOp__=\"__propertyOpText__\" is unknown!",
"unknownCompareOperator": "error, the unsed compare operator \"__operator__\" is unknown!",
"notEvaluableProperty":"Error: could not evaluate __type__.__value__!",
"notEvaluablePropertyDefault": "Error: could not evaluate __type__.__value__, using \"__usedValue__\"!",
"notEvaluablePropertyAdd":"Error \"__err__\", could not evaluate __type__.__value__"
},

@@ -231,2 +270,3 @@ "position-config": {

"placeholder": {
"positionConfig":"select or create generic configuration",
"latitude": "51.025",

@@ -233,0 +273,0 @@ "longitude": "-1.4",

{
"sun-position": {
"label": {
"azimuthpos":"azimuth position",
"azimuthpos":"azimuth Position",
"between":"between",
"and":"and",
"name": "Name",
"start": "Start",

@@ -9,0 +8,0 @@ "startOffset":"Start Offset",

@@ -17,16 +17,12 @@ {

"placeholder": {
"position": "Position",
"name": "Name",
"topic": "Name",
"property": "Property",
"payloadStart": "",
"payloadEnd": "",
"startTime": "7:40",
"startOffset": "0",
"time": "18:15",
"offset": "0",
"timeAlt": "18:15",
"timeAltOffset": "0"
"input": "Input",
"inputFormat": "parse format",
"inputOffset": "Offset",
"result": "result",
"resultValue": "value",
"resultFormat": "output format",
"resultOffset": "Offset"
}
}
}

@@ -5,2 +5,3 @@ {

"property": "Property",
"propertyThreshold": "Threshold",
"time": "Time",

@@ -36,2 +37,3 @@ "timeOffset": "Offset",

"property": "Property",
"propertyThreshold": "threshold",
"payloadStart": "",

@@ -42,5 +44,12 @@ "payloadEnd": "",

"time": "18:15",
"offset": "0",
"timeOffset": "0",
"timeAlt": "18:15",
"timeAltOffset": "0"
"timeAltOffset": "0",
"days":"select days wherfor it should be valid",
"addPayload": "allow to add additional property to the send message",
"addPayloadValue": "value of the additional property",
"addPayloadOffset": "Offset to the value",
"addPayloadFormat": "format of the output",
"recalcTime": "recalculation interval of the defined times",
"once": "define if the node should emit a message independend of the time a message on flow start"
},

@@ -47,0 +56,0 @@ "tips": {

@@ -21,6 +21,13 @@ {

"placeholder": {
"name": "Name",
"offset": "0"
"operand": "Input",
"operandFormat": "parse format",
"operandOffset": "Offset",
"operator": "calculation",
"result": "result",
"resultValue": "value",
"resultTSFormat": "output format",
"resultFormat": "output format",
"resultOffset": "Offset"
}
}
}

@@ -6,5 +6,7 @@ {

"propertyStart": "Alt. Start on",
"propertyStartThreshold": "Threshold",
"startTime": "Start time",
"startTimeAlt": "Start time",
"propertyEnd": "Alt. End on",
"propertyEndThreshold": "Threshold",
"endTime": "End time",

@@ -35,3 +37,6 @@ "endTimeAlt": "End time",

"placeholder": {
"property": "Property",
"propertyStart": "Property",
"propertyStartThreshold": "threshold",
"propertyEnd": "Property",
"propertyEndThreshold": "threshold",
"startTime": "7:40",

@@ -38,0 +43,0 @@ "startOffset": "0",

@@ -65,3 +65,5 @@ /********************************************

ports[i + 1].payload.posChanged = chg;
ports[i + 1].moonPos = chk;
ports[i + 1].posChanged = chg;
ports[i + 1].azimuth = ports[0].payload.azimuth;
}

@@ -107,25 +109,10 @@ }

function getNumProp(srcNode, msg, vType, value) {
// srcNode.debug('getNumProp vType=' + vType + ' value=' + value);
const now = new Date();
let result = -1;
if (vType === '' || vType === 'none') {
// nix
} else if (vType === 'num') {
result = Number(now);
} else {
try {
// evaluateNodeProperty(value, type, srcNode, msg, callback)
const res = RED.util.evaluateNodeProperty(value, vType, srcNode, msg);
if (res && !isNaN(res)) {
result = Number(now);
} else {
srcNode.error('could not evaluate ' + vType + '.' + value);
}
} catch (err) {
srcNode.error('could not evaluate ' + vType + '.' + value + ': ' + err.message);
srcNode.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
try {
if (vType === 'none') {
return undefined;
}
return node.positionConfig.getFloatProp(node, msg, vType, value, 0);
} catch (err) {
return undefined;
}
return result;
}

@@ -132,0 +119,0 @@ }

@@ -72,31 +72,72 @@ /********************************************

function positionConfigurationNode(n) {
RED.nodes.createNode(this, n);
// this.debug('load position-config ' + n.name);
this.name = n.name;
this.longitude = parseFloat(this.credentials.posLongitude || n.longitude);
this.latitude = parseFloat(this.credentials.posLatitude || n.latitude);
this.angleType = n.angleType;
this.tzOffset = (n.timezoneOffset * -60) || 0;
// this.debug('load position-config ' + this.name + ' long:' + this.longitude + ' latitude:' + this.latitude + ' angelt:' + this.angleType + ' TZ:' + this.tzOffset);
class positionConfigurationNode {
/**
*
* @param config
*/
constructor(config) {
RED.nodes.createNode(this, config);
try {
this.debug('initialize');
this.name = config.name;
this.longitude = parseFloat(this.credentials.posLongitude || config.longitude);
this.latitude = parseFloat(this.credentials.posLatitude || config.latitude);
this.angleType = config.angleType;
this.tzOffset = (config.timezoneOffset * -60) || 0;
// this.debug('load position-config ' + this.name + ' long:' + this.longitude + ' latitude:' + this.latitude + ' angelt:' + this.angleType + ' TZ:' + this.tzOffset);
this.lastSunCalc = {
ts: 0
};
this.lastMoonCalc = {
ts: 0
};
this.lastSunCalc = {
ts: 0
};
this.lastMoonCalc = {
ts: 0
};
const node = this;
this.getSunTime = (now, value, offset, multiplier, next, days) => {
// node.debug('getSunTime value=' + value + ' offset=' + offset + ' multiplier=' + multiplier + ' next=' + next + ' days=' + days);
let result = sunTimesCheck(node, now);
result = Object.assign(result, node.sunTimesToday[value]);
const today = new Date();
const dayId = this._getUTCDayId(today);
const tomorrow = today.addDays(1);
this._sunTimesRefresh(today, tomorrow, dayId);
this._moonTimesRefresh(today, tomorrow, dayId);
hlp.initializeParser(RED._('common.days', { returnObjects: true}), RED._('common.month', { returnObjects: true}), RED._('common.dayDiffNames', { returnObjects: true}));
} catch (err) {
this.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
this.status({
fill: 'red',
shape: 'ring',
text: RED._('errors.error-title')
});
throw err;
}
}
/**
*
* @param node
*/
register(node) {
this.users[node.id] = node;
}
/**
*
* @param node
* @param done
* @returns {*}
*/
deregister(node, done) {
delete node.users[node.id];
return done();
}
/*******************************************************************************************************/
getSunTime(now, value, offset, multiplier, next, days) {
// this.debug('getSunTime value=' + value + ' offset=' + offset + ' multiplier=' + multiplier + ' next=' + next + ' days=' + days);
let result = this._sunTimesCheck(now);
result = Object.assign(result, this.sunTimesToday[value]);
result.value = hlp.addOffset(new Date(result.value), offset, multiplier);
if (next && !isNaN(next) && result.value.getTime() <= now.getTime()) {
if (next === 1) {
result = Object.assign(result, node.sunTimesTomorow[value]);
result = Object.assign(result, this.sunTimesTomorow[value]);
} else if (next > 1) {
checkCoordinates(node);
this._checkCoordinates();
const date = (new Date()).addDays(next);
result = Object.assign(result, sunCalc.getTimes(date, node.latitude, node.longitude)[value]);
result = Object.assign(result, sunCalc.getTimes(date, this.latitude, this.longitude)[value]);
}

@@ -108,12 +149,12 @@

if (days && (days !== '*') && (days !== '')) {
// node.debug('move days ' + days + ' result=' + util.inspect(result));
// this.debug('move days ' + days + ' result=' + util.inspect(result));
const dayx = hlp.calcDayOffset(days, result.value.getDay());
if (dayx > 0) {
checkCoordinates(node);
this._checkCoordinates();
const date = result.value.addDays(dayx);
// let times = sunCalc.getTimes(date, node.latitude, node.longitude);
result = Object.assign(result, sunCalc.getTimes(date, node.latitude, node.longitude)[value]);
// let times = sunCalc.getTimes(date, this.latitude, this.longitude);
result = Object.assign(result, sunCalc.getTimes(date, this.latitude, this.longitude)[value]);
result.value = hlp.addOffset(new Date(result.value), offset, multiplier);
} else if (dayx < 0) {
// node.debug('getSunTime - no valid day of week found value=' + value + ' - next=' + next + ' - days=' + days + ' result=' + util.inspect(result));
// this.debug('getSunTime - no valid day of week found value=' + value + ' - next=' + next + ' - days=' + days + ' result=' + util.inspect(result));
result.error = 'No valid day of week found!';

@@ -123,21 +164,20 @@ }

// node.debug('getSunTime result=' + util.inspect(result));
// this.debug('getSunTime result=' + util.inspect(result));
return result;
};
this.getMoonTime = (now, value, offset, multiplier, next, days) => {
// node.debug('getMoonTime value=' + value + ' offset=' + offset + ' next=' + next + ' days=' + days);
const result = moonTimesCheck(node, now);
// node.debug('Moon Times today =' + util.inspect(node.moonTimesToday));
result.value = hlp.addOffset(new Date(node.moonTimesToday[value]), offset, multiplier);
}
/*******************************************************************************************************/
getMoonTime(now, value, offset, multiplier, next, days) {
// this.debug('getMoonTime value=' + value + ' offset=' + offset + ' next=' + next + ' days=' + days);
const result = this._moonTimesCheck( now);
result.value = hlp.addOffset(new Date(this.moonTimesToday[value]), offset, multiplier);
if (next && !isNaN(next) && result.value.getTime() <= now.getTime()) {
if (next === 1) {
result.value = hlp.addOffset(new Date(node.moonTimesTomorow[value]), offset, multiplier);
// node.debug('Moon Times tomorrow =' + util.inspect(node.moonTimesTomorrow));
result.value = hlp.addOffset(new Date(this.moonTimesTomorow[value]), offset, multiplier);
// this.debug('Moon Times tomorrow =' + util.inspect(this.moonTimesTomorrow));
} else if (next > 1) {
checkCoordinates(node);
this._checkCoordinates();
const date = (new Date()).addDays(next);
const times = sunCalc.getMoonTimes(date, node.latitude, node.longitude, true);
const times = sunCalc.getMoonTimes(date, this.latitude, this.longitude, true);
result.value = hlp.addOffset(new Date(times[value]), offset, multiplier);
// node.debug('Moon Times for ' + date + ' =' + util.inspect(times));
// this.debug('Moon Times for ' + date + ' =' + util.inspect(times));
}

@@ -149,30 +189,28 @@ }

if (dayx > 0) {
checkCoordinates(node);
this._checkCoordinates();
const date = (new Date()).addDays(dayx);
const times = sunCalc.getMoonTimes(date, node.latitude, node.longitude, true);
const times = sunCalc.getMoonTimes(date, this.latitude, this.longitude, true);
result.value = hlp.addOffset(new Date(times[value]), offset, multiplier);
// node.debug('Moon Times for ' + date + ' =' + util.inspect(times));
} else if (dayx < 0) {
result.error = 'No valid day of week found!';
// node.debug('getMoonTime - no valid week day found value=' + value + ' - next=' + next + ' - days=' + days + ' result=' + result.value);
// this.debug('getMoonTime - no valid week day found value=' + value + ' - next=' + next + ' - days=' + days + ' result=' + result.value);
}
}
// node.debug('getMoonTime result' + util.inspect(result));
// this.debug('getMoonTime result' + util.inspect(result));
return result;
};
this.getFloatProp = (_srcNode, msg, type, value) => {
}
/*******************************************************************************************************/
getFloatProp(_srcNode, msg, type, value, def) {
// _srcNode.debug('getFloatProp type='+type+' value='+value);
let data;
// 'msg', 'flow', 'global', 'num', 'bin', 'env', 'jsonata'
let data; // 'msg', 'flow', 'global', 'num', 'bin', 'env', 'jsonata'
if (type === 'num') {
data = value;
} else if (type === '' || typeof type === 'undefined' || type === null) {
} else if (type === '' || (typeof type === 'undefined') || type === null) {
if (isNaN(value)) {
return 0;
return def || NaN;
}
data = value;
} else if (type === 'none') {
return 0;
return def || NaN;
} else if (type === 'msgPayload') {

@@ -186,3 +224,3 @@ data = msg.payload;

if (data === null || typeof data === 'undefined') {
throw new Error('could not evaluate ' + type + '.' + value);
throw new Error(RED._('errors.notEvaluableProperty', {type:type, value:value}));
}

@@ -194,5 +232,5 @@ data = parseFloat(data);

return data;
};
this.getOutDataProp = (_srcNode, msg, vType, value, format, offset, offsetType, multiplier, days) => {
}
/*******************************************************************************************************/
getOutDataProp(_srcNode, msg, vType, value, format, offset, offsetType, multiplier, days) {
// _srcNode.debug('getOutDataProp type='+vType+' value='+value+' format='+format+' offset='+offset+' offset='+offsetType+' multiplier='+multiplier);

@@ -202,3 +240,3 @@ let result = null;

if (value === '' || (typeof value === 'undefined')) {
const offsetX = node.getFloatProp(node, msg, offsetType, offset);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result = hlp.addOffset((new Date()), offsetX, multiplier);

@@ -211,3 +249,3 @@ return hlp.getFormattedDateOut(result, format);

} else if (vType === 'dateSpecific') {
const offsetX = node.getFloatProp(node, msg, offsetType, offset);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result = hlp.addOffset((new Date()), offsetX, multiplier);

@@ -224,12 +262,12 @@ return hlp.getFormattedDateOut(result, format);

} else if (vType === 'pdsCalcData') {
return node.getSunCalc(msg.ts);
return this.getSunCalc(msg.ts);
} else if (vType === 'pdmCalcData') {
return node.getMoonCalc(msg.ts);
return this.getMoonCalc(msg.ts);
} else if ((vType === 'pdsTime') || (vType === 'pdmTime')) {
if (vType === 'pdsTime') { // sun
const offsetX = node.getFloatProp(node, msg, offsetType, offset);
result = node.getSunTime((new Date()), value, offsetX, multiplier, undefined, days);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result = this.getSunTime((new Date()), value, offsetX, multiplier, undefined, days);
} else if (vType === 'pdmTime') { // moon
const offsetX = node.getFloatProp(node, msg, offsetType, offset);
result = node.getMoonTime((new Date()), value, offsetX, multiplier, undefined, days);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result = this.getMoonTime((new Date()), value, offsetX, multiplier, undefined, days);
}

@@ -242,3 +280,3 @@ if (result && result.value && !result.error) {

result = hlp.getDateOfText(String(value));
const offsetX = node.getFloatProp(node, msg, offsetType, offset);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result = hlp.normalizeDate(result, offsetX, multiplier, undefined, days);

@@ -250,3 +288,3 @@ return hlp.getFormattedDateOut(result, format);

if (result !== null && typeof result !== 'undefined') {
const offsetX = node.getFloatProp(node, msg, offsetType, offset);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result = hlp.addOffset(result, offsetX, multiplier);

@@ -258,5 +296,5 @@ return hlp.getFormattedDateOut(result, format);

return RED.util.evaluateNodeProperty(value, vType, _srcNode, msg);
};
this.getDateFromProp = (_srcNode, msg, vType, value, format, offset, offsetType, multiplier) => {
}
/*******************************************************************************************************/
getDateFromProp(_srcNode, msg, vType, value, format, offset, offsetType, multiplier) {
// _srcNode.debug('getDateFromProp type='+vType+' value='+value+' format='+format+' offset='+offset+ ' offsetType=' + offsetType +' multiplier='+multiplier);

@@ -270,3 +308,3 @@ let result = null;

} else if (vType === 'dateSpecific') {
const offsetX = node.getFloatProp(node, msg, offsetType, offset);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
return hlp.addOffset((new Date()), offsetX, multiplier);

@@ -276,13 +314,13 @@ } else if (vType === 'dayOfMonth') {

d = hlp.getSpecialDayOfMonth(d.getFullYear(),d.getMonth(), value);
const offsetX = node.getFloatProp(node, msg, offsetType, offset);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
return hlp.addOffset(d, offsetX, multiplier);
} else if ((vType === 'pdsTime') || (vType === 'pdmTime')) {
const offsetX = node.getFloatProp(node, msg, offsetType, offset);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
if (vType === 'pdsTime') {
// sun
result = node.getSunTime((new Date()), value, offsetX, multiplier);
result = this.getSunTime((new Date()), value, offsetX, multiplier);
result.fix = true;
} else if (vType === 'pdmTime') {
// moon
result = node.getMoonTime((new Date()), value, offsetX, multiplier);
result = this.getMoonTime((new Date()), value, offsetX, multiplier);
result.fix = true;

@@ -293,6 +331,6 @@ }

}
throw new Error('could not evaluate ' + vType + '.' + value + ' - ' + result.error);
throw new Error(RED._('errors.notEvaluablePropertyAdd', {type:vType, value:value, err:result.error}));
} else if (vType === 'entered' || vType === 'dateEntered') {
result = hlp.getDateOfText(String(value));
const offsetX = node.getFloatProp(node, msg, offsetType, offset);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
return hlp.addOffset(result, offsetX, multiplier);

@@ -312,3 +350,3 @@ } else if (vType === 'msgPayload') {

if (result !== null && typeof result !== 'undefined') {
const offsetX = node.getFloatProp(node, msg, offsetType, offset);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result = hlp.parseDateFromFormat(result, format, RED._('position-config.days'), RED._('position-config.month'), RED._('position-config.dayDiffNames'));

@@ -318,3 +356,3 @@ return hlp.addOffset(result, offsetX, multiplier);

} catch (err) {
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
this.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
const e = new Error(`Exception "${err.message}", on try to evaluate ${vType}.${value}`);

@@ -325,6 +363,6 @@ e.original = err;

}
};
this.getTimeProp = (_srcNode, msg, vType, value, offset, offsetType, multiplier, next, days) => {
node.debug('getTimeProp [' + hlp.getNodeId(_srcNode) + '] vType=' + vType + ' value=' + value + ' offset=' + offset + ' offsetType=' + offsetType + ' multiplier=' + multiplier + ' next=' + next + ' days=' + days);
}
/*******************************************************************************************************/
getTimeProp(_srcNode, msg, vType, value, offset, offsetType, multiplier, next, days) {
// this.debug('getTimeProp [' + hlp.getNodeId(_srcNode) + '] vType=' + vType + ' value=' + value + ' offset=' + offset + ' offsetType=' + offsetType + ' multiplier=' + multiplier + ' next=' + next + ' days=' + days);
let result = {

@@ -344,3 +382,3 @@ value: null,

} else if (vType === 'dateSpecific') {
const offsetX = node.getFloatProp(node, msg, offsetType, offset);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result.value = hlp.normalizeDate((new Date()), offsetX, multiplier, next, days);

@@ -351,3 +389,3 @@ result.fix = true;

if (result.value !== null) {
const offsetX = node.getFloatProp(node, msg, offsetType, offset);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result.value = hlp.normalizeDate(result.value, offsetX, multiplier, next, days);

@@ -358,9 +396,9 @@ }

// sun
const offsetX = node.getFloatProp(node, msg, offsetType, offset);
result = node.getSunTime((new Date()), value, offsetX, multiplier, next, days);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result = this.getSunTime((new Date()), value, offsetX, multiplier, next, days);
result.fix = true;
} else if (vType === 'pdmTime') {
// moon
const offsetX = node.getFloatProp(node, msg, offsetType, offset);
result = node.getMoonTime((new Date()), value, offsetX, multiplier, next, days);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result = this.getMoonTime((new Date()), value, offsetX, multiplier, next, days);
result.fix = true;

@@ -373,12 +411,12 @@ } else {

result.value = hlp.getDateOfText(res);
const offsetX = node.getFloatProp(node, msg, offsetType, offset);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result.value = hlp.normalizeDate(result.value, offsetX, multiplier, next, days);
// node.debug(String(res) + ' -- ' + result.value);
// this.debug(String(res) + ' -- ' + result.value);
} else {
result.error = 'could not evaluate ' + vType + '.' + value;
result.error = RED._('errors.notEvaluableProperty', {type:vType, value:value});
}
}
} catch (err) {
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
const e = new Error('Error "${err.message}", could not evaluate ' + vType + '.' + value);
this.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
const e = new Error(RED._('errors.notEvaluablePropertyAdd', {type:vType, value:value, err:result.error}));
e.original = err;

@@ -396,11 +434,108 @@ e.stack = e.stack.split('\n').slice(0,2).join('\n')+'\n'+err.stack;

// node.debug('getTimeProp result' + util.inspect(result));
// this.debug('getTimeProp result' + util.inspect(result));
return result;
};
}
/*******************************************************************************************************/
comparePropValue (_srcNode, msg, opTypeA, opValueA, compare, opTypeB, opValueB, tempStorage, outReason) {
// _srcNode.debug(`getComparablePropValue opTypeA='${opTypeA}' opValueA='${opValueA}' compare='${compare}' opTypeB='${opTypeB}' opValueB='${opValueB}'`);
if (opTypeA === 'none' || opTypeA === '' || typeof opTypeA === 'undefined' || opTypeA === null) {
return false;
}
const opVal = (type, value, opName) => {
// _srcNode.debug(`getting ${opName} = ${type}.${value}`);
let opData = null;
try {
if (type === '' || type === 'none' || typeof type === 'undefined' || type === null) {
_srcNode.warn(RED._('errors.notEvaluablePropertyDefault', { type: type, value: value, usedValue:'null' }));
return null;
} else if (type === 'num') {
return Number(value);
} else if (type === 'msgPayload') {
return msg.payload;
} else if (type === 'msgValue') {
return msg.value;
}
opData = RED.util.evaluateNodeProperty(value, type, _srcNode, msg);
if (opData === null || typeof opData === 'undefined') {
throw new Error(opData);
}
if (typeof tempStorage !== 'undefined' && type === 'msg') {
tempStorage[opName] = opData;
}
if (typeof outReason === 'object') {
outReason[opName] = opData;
// _srcNode.debug('opData=' + opData + ' outReason= ' + util.inspect(outReason, Object.getOwnPropertyNames(outReason)));
}
return opData;
} catch (err) {
_srcNode.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
if (tempStorage && (type === 'msg') && tempStorage[opName]) {
_srcNode.log(RED._('errors.notEvaluablePropertyDefault', { type: type, value: value, usedValue: tempStorage[opName] }));
return tempStorage[opName];
}
_srcNode.warn(RED._('errors.notEvaluablePropertyDefault', { type: type, value: value, usedValue: 'null' }));
if (typeof outReason === 'object') {
outReason[opName] = null;
outReason[opName + '_error'] = err;
}
return null;
}
};
const a = opVal(opTypeA, opValueA, 'operandA');
switch (compare) {
case 'true':
return (a === true);
case 'false':
return (a === false);
case 'null':
return (typeof a == 'undefined' || a === null); // eslint-disable-line eqeqeq
case 'nnull':
return (typeof a != 'undefined' && a !== null); // eslint-disable-line eqeqeq
case 'empty':
if (typeof a === 'string' || Array.isArray(a) || Buffer.isBuffer(a)) {
return a.length === 0;
} else if (typeof a === 'object' && a !== null) {
return Object.keys(a).length === 0;
}
return false;
case 'nempty':
if (typeof a === 'string' || Array.isArray(a) || Buffer.isBuffer(a)) {
return a.length !== 0;
} else if (typeof a === 'object' && a !== null) {
return Object.keys(a).length !== 0;
}
return false;
case 'true_expr':
return hlp.isTrue(a);
case 'false_expr':
return hlp.isFalse(a);
case 'ntrue_expr':
return !hlp.isTrue(a);
case 'nfalse_expr':
return !hlp.isFalse(a);
case 'equal':
return (a == opVal(opTypeB, opValueB, 'operandB')); // eslint-disable-line eqeqeq
case 'nequal':
return (a != opVal(opTypeB, opValueB, 'operandB')); // eslint-disable-line eqeqeq
case 'lt':
return (a < opVal(opTypeB, opValueB, 'operandB'));
case 'lte':
return (a <= opVal(opTypeB, opValueB, 'operandB'));
case 'gt':
return (a > opVal(opTypeB, opValueB, 'operandB'));
case 'gte':
return (a >= opVal(opTypeB, opValueB, 'operandB'));
case 'contain':
return ((a + '').indexOf(opVal(opTypeB, opValueB, 'operandB')) !== -1);
default:
_srcNode.error(RED._('errors.unknownCompareOperator', { operator: compare }));
return hlp.isTrue(a);
}
}
/**************************************************************************************************************/
this.getSunCalc = (date, noTimes) => {
// node.debug(`getSunCalc for date="${date}" noTimes="${noTimes}"`);
getSunCalc(date, noTimes) {
// this.debug(`getSunCalc for date="${date}" noTimes="${noTimes}"`);
if (typeof date === 'string') {
node.debug('getSunCalc for date ' + date);
// this.debug('getSunCalc for date ' + date);
const dto = new Date(date);

@@ -413,6 +548,6 @@ if (dto !== 'Invalid Date' && !isNaN(dto)) {

if ((typeof date === 'undefined') || !(date instanceof Date)) {
node.debug('getSunCalc, no valid date ' + date + ' given');
this.debug('getSunCalc, no valid date ' + date + ' given');
date = new Date();
if (this.lastSunCalc && (Math.abs(date.getTime() - this.lastSunCalc.ts) < 4000)) {
node.debug('getSunCalc, time difference since last output to low, do no calculation');
this.debug('getSunCalc, time difference since last output to low, do no calculation');
return this.lastSunCalc;

@@ -422,3 +557,3 @@ }

const sunPos = sunCalc.getPosition(date, node.latitude, node.longitude);
const sunPos = sunCalc.getPosition(date, this.latitude, this.longitude);
const azimuthDegrees = 180 + 180 / Math.PI * sunPos.azimuth;

@@ -430,7 +565,7 @@ const altitudeDegrees = 180 / Math.PI * sunPos.altitude; // elevation = altitude

lastUpdate: date,
latitude: node.latitude,
longitude: node.longitude,
angleType: node.angleType,
azimuth: (node.angleType === 'deg') ? azimuthDegrees : sunPos.azimuth,
altitude: (node.angleType === 'deg') ? altitudeDegrees : sunPos.altitude, // elevation = altitude
latitude: this.latitude,
longitude: this.longitude,
angleType: this.angleType,
azimuth: (this.angleType === 'deg') ? azimuthDegrees : sunPos.azimuth,
altitude: (this.angleType === 'deg') ? altitudeDegrees : sunPos.altitude, // elevation = altitude
altitudeDegrees: altitudeDegrees,

@@ -443,14 +578,14 @@ azimuthDegrees: azimuthDegrees,

if (noTimes) {
// node.debug('no times result= ' + util.inspect(result));
// this.debug('no times result= ' + util.inspect(result));
return result;
}
sunTimesCheck(node);
result.times = node.sunTimesToday;
this._sunTimesCheck();
result.times = this.sunTimesToday;
this.lastSunCalc = result;
// node.debug('result= ' + util.inspect(result));
// this.debug('result= ' + util.inspect(result));
return result;
};
}
/**************************************************************************************************************/
this.getMoonCalc = (date, noTimes) => {
getMoonCalc(date, noTimes) {
if (typeof date === 'string') {

@@ -470,3 +605,3 @@ const dto = new Date(date);

const moonPos = sunCalc.getMoonPosition(date, node.latitude, node.longitude);
const moonPos = sunCalc.getMoonPosition(date, this.latitude, this.longitude);
const moonIllum = sunCalc.getMoonIllumination(date);

@@ -477,14 +612,14 @@

lastUpdate: date,
latitude: node.latitude,
longitude: node.longitude,
angleType: node.angleType,
azimuth: (node.angleType === 'deg') ? 180 + 180 / Math.PI * moonPos.azimuth : moonPos.azimuth,
altitude: (node.angleType === 'deg') ? 180 / Math.PI * moonPos.altitude : moonPos.altitude, // elevation = altitude
latitude: this.latitude,
longitude: this.longitude,
angleType: this.angleType,
azimuth: (this.angleType === 'deg') ? 180 + 180 / Math.PI * moonPos.azimuth : moonPos.azimuth,
altitude: (this.angleType === 'deg') ? 180 / Math.PI * moonPos.altitude : moonPos.altitude, // elevation = altitude
distance: moonPos.distance,
parallacticAngle: (node.angleType === 'deg') ? 180 / Math.PI * moonPos.parallacticAngle : moonPos.parallacticAngle,
parallacticAngle: (this.angleType === 'deg') ? 180 / Math.PI * moonPos.parallacticAngle : moonPos.parallacticAngle,
illumination: {
angle: (node.angleType === 'deg') ? 180 / Math.PI * moonIllum.angle : moonIllum.angle,
angle: (this.angleType === 'deg') ? 180 / Math.PI * moonIllum.angle : moonIllum.angle,
fraction: moonIllum.fraction,
phase: {},
zenithAngle: (node.angleType === 'deg') ? 180 / Math.PI * (moonIllum.angle - moonPos.parallacticAngle) : moonIllum.angle - moonPos.parallacticAngle
zenithAngle: (this.angleType === 'deg') ? 180 / Math.PI * (moonIllum.angle - moonPos.parallacticAngle) : moonIllum.angle - moonPos.parallacticAngle
}

@@ -520,7 +655,7 @@ };

result.illumination.phase.value = moonIllum.phase;
result.illumination.phase.angle = (node.angleType === 'rad') ? (moonIllum.phase * 360) / (180 / Math.PI) : moonIllum.phase * 360;
result.illumination.phase.angle = (this.angleType === 'rad') ? (moonIllum.phase * 360) / (180 / Math.PI) : moonIllum.phase * 360;
if (noTimes) { return result; }
sunTimesCheck(node);
result.times = node.moonTimesToday;
this._sunTimesCheck();
result.times = this.moonTimesToday;
// getAngle : angle / 57.2957795130823209 //angle(rad) * (180° / Pi) = angle(deg)

@@ -541,31 +676,12 @@

return result;
};
/**************************************************************************************************************/
try {
node.debug('initialize');
const today = new Date();
const dayId = getUTCDayId(today);
const tomorrow = today.addDays(1);
sunTimesRefresh(node, today, tomorrow, dayId);
moonTimesRefresh(node, today, tomorrow, dayId);
hlp.initializeParser(RED._('common.days', { returnObjects: true}), RED._('common.month', { returnObjects: true}), RED._('common.dayDiffNames', { returnObjects: true}));
} catch (err) {
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.status({
fill: 'red',
shape: 'ring',
text: RED._('errors.error-title')
});
throw err;
}
/**************************************************************************************************************/
function checkCoordinates(node) {
if (isNaN(node.longitude) || (node.longitude < -180) || (node.longitude > 180)) {
_checkCoordinates() {
if (isNaN(this.longitude) || (this.longitude < -180) || (this.longitude > 180)) {
throw new Error(RED._('position-config.errors.longitude-missing'));
}
if (isNaN(node.latitude) || (node.latitude < -90) || (node.latitude > 90)) {
if (isNaN(this.latitude) || (this.latitude < -90) || (this.latitude > 90)) {
throw new Error(RED._('position-config.errors.latitude-missing'));
}
if ((node.latitude === 0) && (node.longitude === 0)) {
if ((this.latitude === 0) && (this.longitude === 0)) {
throw new Error(RED._('position-config.errors.coordinates-missing'));

@@ -575,17 +691,17 @@ }

function sunTimesRefresh(node, today, tomorrow, dayId) {
checkCoordinates(node);
// node.debug('sunTimesRefresh - calculate sun times');
node.sunTimesToday = sunCalc.getTimes(today, node.latitude, node.longitude);
node.sunTimesTomorow = sunCalc.getTimes(tomorrow, node.latitude, node.longitude);
node.sunDayId = dayId;
_sunTimesRefresh(today, tomorrow, dayId) {
this._checkCoordinates();
// this.debug('sunTimesRefresh - calculate sun times');
this.sunTimesToday = sunCalc.getTimes(today, this.latitude, this.longitude);
this.sunTimesTomorow = sunCalc.getTimes(tomorrow, this.latitude, this.longitude);
this.sunDayId = dayId;
}
function sunTimesCheck(node, today, dayId) {
// node.debug('sunTimesCheck');
_sunTimesCheck(today, dayId) {
// this.debug('_sunTimesCheck');
const dateb = today || new Date();
const day_id = dayId || getUTCDayId(dateb);
if (node.sunDayId !== day_id) {
const day_id = dayId || this._getUTCDayId(dateb);
if (this.sunDayId !== day_id) {
const tomorrow = (new Date()).addDays(1);
sunTimesRefresh(node, dateb, tomorrow, day_id);
this._sunTimesRefresh(dateb, tomorrow, day_id);
}

@@ -599,37 +715,37 @@

function moonTimesRefresh(node, today, tomorrow, dayId) {
checkCoordinates(node);
// node.debug('moonTimesRefresh - calculate moon times');
node.moonTimesToday = sunCalc.getMoonTimes(today, node.latitude, node.longitude, true);
if (!node.moonTimesToday.alwaysUp) {
_moonTimesRefresh(today, tomorrow, dayId) {
this._checkCoordinates();
// this.debug('moonTimesRefresh - calculate moon times');
this.moonTimesToday = sunCalc.getMoonTimes(today, this.latitude, this.longitude, true);
if (!this.moonTimesToday.alwaysUp) {
// true if the moon never rises/sets and is always above the horizon during the day
node.moonTimesToday.alwaysUp = false;
this.moonTimesToday.alwaysUp = false;
}
if (!node.moonTimesToday.alwaysDown) {
if (!this.moonTimesToday.alwaysDown) {
// true if the moon is always below the horizon
node.moonTimesToday.alwaysDown = false;
this.moonTimesToday.alwaysDown = false;
}
node.moonTimesTomorow = sunCalc.getMoonTimes(tomorrow, node.latitude, node.longitude, true);
if (!node.moonTimesTomorow.alwaysUp) {
this.moonTimesTomorow = sunCalc.getMoonTimes(tomorrow, this.latitude, this.longitude, true);
if (!this.moonTimesTomorow.alwaysUp) {
// true if the moon never rises/sets and is always above the horizon during the day
node.moonTimesTomorow.alwaysUp = false;
this.moonTimesTomorow.alwaysUp = false;
}
if (!node.moonTimesTomorow.alwaysDown) {
if (!this.moonTimesTomorow.alwaysDown) {
// true if the moon is always below the horizon
node.moonTimesTomorow.alwaysDown = false;
this.moonTimesTomorow.alwaysDown = false;
}
node.moonDayId = dayId;
this.moonDayId = dayId;
}
function moonTimesCheck(node, today, dayId) {
// node.debug('moonTimesCheck');
_moonTimesCheck(today, dayId) {
// this.debug('moonTimesCheck');
const dateb = today || new Date();
const day_id = dayId || getUTCDayId(dateb);
if (node.moonDayId !== day_id) {
const day_id = dayId || this._getUTCDayId(dateb);
if (this.moonDayId !== day_id) {
const tomorrow = (new Date()).addDays(1);
moonTimesRefresh(node, dateb, tomorrow, day_id);
this._moonTimesRefresh(dateb, tomorrow, day_id);
}

@@ -643,3 +759,3 @@

function getUTCDayId(d) {
_getUTCDayId(d) {
return d.getUTCDay() + (d.getUTCMonth() * 31) + (d.getUTCFullYear() * 372);

@@ -649,2 +765,3 @@ }

/**************************************************************************************************************/
RED.nodes.registerType('position-config', positionConfigurationNode, {

@@ -656,2 +773,53 @@ credentials: {

});
RED.httpAdmin.get('/sun-position/js/*', RED.auth.needsPermission('sun-position.read'), (req, res) => {
const options = {
root: __dirname + '/static/',
dotfiles: 'deny'
};
res.sendFile(req.params[0], options);
});
RED.httpAdmin.get('/sun-position/data', RED.auth.needsPermission('sun-position.read'), (req, res) => {
if (req.query.config && req.query.config !== '_ADD_') {
const posConfig = RED.nodes.getNode(req.query.config);
if (!posConfig) {
res.status(500).send(JSON.stringify({}));
return;
}
const obj = {};
switch (req.query.type) {
case 'getFloatProp': {
try {
obj.value = posConfig.getFloatProp(posConfig, undefined, req.query.type, req.query.value, NaN);
} catch (err) {
obj.value = NaN;
obj.error = err;
}
res.status(200).send(JSON.stringify(obj));
break;
}
case 'getTimeProp': {
try {
obj.value = posConfig.getTimeProp(posConfig, undefined, req.query.type, req.query.valueoffset, req.query.offsetType, req.query.multiplier, req.query.next, req.query.days);
} catch(err) {
obj.value = NaN;
obj.error = err;
}
res.status(200).send(JSON.stringify(obj));
break;
}
case 'getDateFromProp': {
try {
obj.value = posConfig.getDateFromProp(posConfig, undefined, req.query.type, req.query.format, req.query.valueoffset, req.query.offsetType, req.query.multiplier);
} catch(err) {
obj.value = NaN;
obj.error = err;
}
res.status(200).send(JSON.stringify(obj));
break;
}
}
}
});
};

@@ -98,24 +98,31 @@ /************************************************************************/

{id: -2, group: 'other', label: 'year'}
], blindOperatorGroups: [
{ id: 'limit', label: 'Limit' },
{ id: 'other', label: 'fix' }
], blindOperator: [
{ id: 1, group: 'limit', label: 'min' },
{ id: 2, group: 'limit', label: 'max' },
{ id: 0, group: 'other', label: 'abs' }
], comparatorGroups: [
{ id: 'simple', label: 'simple' },
{ id: 'compare', label: 'compare' },
{ id: 'enhanced', label: 'enhanced' }
], comparator: [
{ id: 'true', group: 'simple', label: 'true', operatorCount: 1 },
{ id: 'false', group: 'simple', label: 'false', operatorCount: 1 },
{ id: 'null', group: 'simple', label: 'null', operatorCount: 1 },
{ id: 'nnull', group: 'simple', label: 'not null', operatorCount: 1 },
{ id: 'empty', group: 'simple', label: 'empty', operatorCount: 1 },
{ id: 'nempty', group: 'simple', label: 'not empty', operatorCount: 1 },
{ id: 'true_expr', group: 'enhanced', label: 'true_expr', operatorCount: 1 },
{ id: 'false_expr', group: 'enhanced', label: 'false_expr', operatorCount: 1 },
{ id: 'ntrue_expr', group: 'enhanced', label: 'not true_expr', operatorCount: 1 },
{ id: 'nfalse_expr', group: 'enhanced', label: 'not false_expr', operatorCount: 1 },
{ id: 'equal', group: 'compare', label: 'equal', operatorCount: 2 },
{ id: 'nequal', group: 'compare', label: 'not equal', operatorCount: 2 },
{ id: 'lt', group: 'compare', label: 'less than', operatorCount: 2 },
{ id: 'lte', group: 'compare', label: 'less than or equal', operatorCount: 2 },
{ id: 'gt', group: 'compare', label: 'greater than', operatorCount: 2 },
{ id: 'gte', group: 'compare', label: 'greater than or equal', operatorCount: 2 },
{ id: 'contain', group: 'enhanced', label: 'contain', operatorCount: 2 }
]
};
function getOperator(nr) { // eslint-disable-line no-unused-vars
return SelectFields.operators[nr];
function getSelectFields() { // eslint-disable-line no-unused-vars
return SelectFields;
}
function getMultiplier(nr) { // eslint-disable-line no-unused-vars
return SelectFields.multiplier[nr];
}
function getParseFormats(nr) { // eslint-disable-line no-unused-vars
return SelectFields.parseFormats[nr];
}
function getTypes(node) { // eslint-disable-line no-unused-vars

@@ -245,6 +252,2 @@ return {

function getSelectFields() { // eslint-disable-line no-unused-vars
return SelectFields;
}
const autocompleteFormats = {

@@ -448,3 +451,3 @@ dateParseFormat : [

if (limit) {
if (limit(elements[eIndex].id)) {
if (limit(elements[eIndex])) {
group.append($('<option></option>').val(elements[eIndex].id).text(node._('node-red-contrib-sun-position/position-config:common.' + elementName + '.' + eIndex)).attr('addText', elements[eIndex].add));

@@ -463,19 +466,19 @@ }

const typeField = $('#node-input-' + data.typeProp);
if (typeof node[data.typeProp] !== 'undefined') {
if (node[data.typeProp] === null ||
typeof node[data.typeProp] === 'undefined') {
if (typeof node[data.typeProp] === 'undefined' ||
node[data.typeProp] === null) {
if (typeof data.defaultType !== 'undefined') {
node[data.typeProp] = data.defaultType;
typeField.val(data.defaultType);
} else {
typeField.val(node[data.typeProp]);
}
} else {
typeField.val(node[data.typeProp]);
}
if (typeof node[data.valueProp] !== 'undefined') {
if (node[data.valueProp] === null ||
typeof node[data.valueProp] === 'undefined') {
if (typeof node[data.valueProp] === 'undefined' ||
node[data.valueProp] === null) {
if (typeof data.defaultValue !== 'undefined') {
node[data.valueProp] = data.defaultValue;
tInputField.val(node[data.defaultValue]);
} else {
tInputField.val(node[data.valueProp]);
}
} else {
tInputField.val(node[data.valueProp]);
}

@@ -482,0 +485,0 @@ tInputField.typedInput({

@@ -102,5 +102,5 @@ /********************************************

ports[i + 1] = RED.util.cloneMessage(msg);
ports[i + 1].payload.sunPos = chk;
ports[i + 1].payload.posChanged = chg;
ports[i + 1].sunPos = chk;
ports[i + 1].posChanged = chg;
ports[i + 1].azimuth = ports[0].payload.azimuth;
}

@@ -162,25 +162,10 @@ }

function getNumProp(srcNode, msg, vType, value) {
// srcNode.debug('getNumProp vType=' + vType + ' value=' + value);
const now = new Date();
let result = -1;
if (vType === '' || vType === 'none') {
// nix
} else if (vType === 'num') {
result = Number(now);
} else {
try {
// evaluateNodeProperty(value, type, srcNode, msg, callback)
const res = RED.util.evaluateNodeProperty(value, vType, srcNode, msg);
if (res && !isNaN(res)) {
result = Number(now);
} else {
srcNode.error('could not evaluate ' + vType + '.' + value);
}
} catch (err) {
srcNode.error('could not evaluate ' + vType + '.' + value + ': ' + err.message);
srcNode.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
try {
if (vType === 'none') {
return undefined;
}
return node.positionConfig.getFloatProp(node, msg, vType, value, 0);
} catch (err) {
return undefined;
}
return result;
}

@@ -187,0 +172,0 @@ }

@@ -6,5 +6,3 @@ /********************************************

const util = require('util');
const path = require('path');
const hlp = require(path.join(__dirname, '/lib/dateTimeHelper.js'));

@@ -39,3 +37,3 @@

if (config.result1ValueType === 'input') {
resultObj = node.positionConfig.formatOutDate(inputData, config.result1Format);
resultObj = hlp.getFormattedDateOut(inputData, config.result1Format);
} else {

@@ -214,10 +212,2 @@ resultObj = node.positionConfig.getOutDataProp(node, msg, config.result1ValueType, config.result1Value, config.result1Format, config.result1Offset, config.result1OffsetType, config.result1Multiplier);

RED.nodes.registerType('time-comp', timeCompNode);
RED.httpAdmin.get('/sun-position/js/*', RED.auth.needsPermission('sun-position.read'), (_req,_res) => {
const options = {
root: __dirname + '/static/',
dotfiles: 'deny'
};
_res.sendFile(_req.params[0], options);
});
};

@@ -74,2 +74,5 @@ /********************************************

this.propertyType = config.propertyType || 'none';
this.propertyOperator = config.propertyCompare || 'true';
this.propertyThresholdValue = config.propertyThreshold;
this.propertyThresholdType = config.propertyThresholdType;
this.timeAlt = config.timeAlt || '';

@@ -168,4 +171,4 @@ this.timeAltType = config.timeAltType || 'none';

try {
const res = RED.util.evaluateNodeProperty(node.property, node.propertyType, node, msg);
useAlternateTime = hlp.isTrue(res);
useAlternateTime = node.positionConfig.comparePropValue(node, msg, node.propertyType, node.property,
node.propertyOperator, node.propertyThresholdType, node.propertyThresholdValue);
needsRecalc = (isAltFirst && !useAlternateTime) || (!isAltFirst && useAlternateTime);

@@ -353,10 +356,2 @@ } catch (err) {

RED.httpAdmin.get('/sun-position/js/*', RED.auth.needsPermission('sun-position.read'), (req,res) => {
const options = {
root: __dirname + '/static/',
dotfiles: 'deny'
};
res.sendFile(req.params[0], options);
});
RED.httpAdmin.post('/time-inject/:id', RED.auth.needsPermission('time-inject.write'), (req,res) => {

@@ -363,0 +358,0 @@ const node = RED.nodes.getNode(req.params.id);

@@ -6,2 +6,4 @@ /********************************************

const util = require('util');
const path = require('path');
const hlp = require(path.join(__dirname, '/lib/dateTimeHelper.js'));

@@ -267,5 +269,5 @@ // const path = require('path');

} else if (config.result1ValueType === 'operand1') {
resultObj = node.positionConfig.formatOutDate(operand1, config.result1Format);
resultObj = hlp.getFormattedDateOut(operand1, config.result1Format);
} else if (config.result1ValueType === 'operand2') {
resultObj = node.positionConfig.formatOutDate(operand2, config.result1Format);
resultObj = hlp.getFormattedDateOut(operand2, config.result1Format);
} else {

@@ -302,3 +304,3 @@ resultObj = node.positionConfig.getOutDataProp(node, msg, config.result1ValueType, config.result1Value, config.result1Format, config.result1Offset, config.result1OffsetType, config.result1Multiplier);

try {
let ruleoperand = node.positionConfig.getFloatProp(node, msg, rule.operandType, rule.operandValue);
let ruleoperand = node.positionConfig.getFloatProp(node, msg, rule.operandType, rule.operandValue, 0);
if (!isNaN(rule.multiplier) && rule.multiplier !== 0) {

@@ -378,10 +380,2 @@ ruleoperand = ruleoperand * rule.multiplier;

RED.nodes.registerType('time-span', timeSpanNode);
RED.httpAdmin.get('/sun-position/js/*', RED.auth.needsPermission('sun-position.read'), (_req,_res) => {
const options = {
root: __dirname + '/static/',
dotfiles: 'deny'
};
_res.sendFile(_req.params[0], options);
});
};

@@ -105,5 +105,4 @@ /********************************************

try {
// evaluateNodeProperty(node.property, type, node, msg, callback)
const res = RED.util.evaluateNodeProperty(node.propertyStart, node.propertyStartType, node, msg);
result.altStartTime = hlp.isTrue(res);
result.altStartTime = node.positionConfig.comparePropValue(node, msg, node.propertyStartType, node.propertyStart,
node.propertyStartOperator, node.propertyStartThresholdType, node.propertyStartThresholdValue);
} catch (err) {

@@ -122,5 +121,4 @@ result.altStartTime = false;

try {
// evaluateNodeProperty(node.property, type, node, msg, callback)
const res = RED.util.evaluateNodeProperty(node.propertyEnd, node.propertyEndType, node, msg);
result.altEndTime = hlp.isTrue(res);
result.altEndTime = node.positionConfig.comparePropValue(node, msg, node.propertyEndType, node.propertyEnd,
node.propertyEndOperator, node.propertyEndThresholdType, node.propertyEndThresholdValue);
} catch (err) {

@@ -197,5 +195,12 @@ result.altEndTime = false;

this.propertyStart = config.propertyStart || '';
this.propertyStartType = config.propertyStartType || 'none';
this.propertyStartOperator = config.propertyStartCompare || 'true';
this.propertyStartThresholdValue = config.propertyStartThreshold;
this.propertyStartThresholdType = config.propertyStartThresholdType;
this.propertyEnd = config.propertyEnd || '';
this.propertyStartType = config.propertyStartType || 'none';
this.propertyEndType = config.propertyEndType || 'none';
this.propertyEndOperator = config.propertyEndCompare || 'true';
this.propertyEndThresholdValue = config.propertyEndThreshold;
this.propertyEndThresholdType = config.propertyEndThresholdType;
this.timeOutObj = null;

@@ -315,10 +320,2 @@ this.lastMsgObj = null;

RED.nodes.registerType('within-time-switch', withinTimeSwitchNode);
RED.httpAdmin.get('/sun-position/js/*', RED.auth.needsPermission('sun-position.read'), (req,res) => {
const options = {
root: __dirname + '/static/',
dotfiles: 'deny'
};
res.sendFile(req.params[0], options);
});
};
{
"name": "node-red-contrib-sun-position",
"version": "0.2.11",
"version": "0.3.0-beta",
"description": "NodeRED nodes to get sun and moon position",

@@ -50,3 +50,4 @@ "keywords": [

"scripts": {
"test": "camo-purge; eslint \"./**/*.js\" \"./**/*.html\"",
"test": "eslint \"./**/*.js\" \"./**/*.html\"",
"camo-purg": "camo-purge; eslint \"./**/*.js\" \"./**/*.html\"",
"lintfix": "eslint --fix \"./**/*.js\" \"./**/*.html\"",

@@ -86,3 +87,4 @@ "lint": "eslint \"./**/*.js\" \"./**/*.html\"",

"time-comp": "nodes/time-comp.js",
"time-span": "nodes/time-span.js"
"time-span": "nodes/time-span.js",
"blind-control": "nodes/blind-control.js"
}

@@ -101,3 +103,3 @@ },

"eslint-plugin-jsdoc": "^4.4.3"
},
},
"plugins-disabled": [

@@ -116,4 +118,3 @@ "jsdoc"

"plugins": [
"html",
"jsdoc"
"html"
],

@@ -120,0 +121,0 @@ "extends": "eslint:recommended",

@@ -95,3 +95,3 @@ # node-red-contrib-sun-position for NodeRED

![sun-position](images/sun-position-example.png?raw=true)
![sun-position-example](https://user-images.githubusercontent.com/12692680/57134504-aa429600-6da6-11e9-9e36-7d1eb80f5589.png)

@@ -104,3 +104,3 @@ ```json

![sun-position](images/sun-position-settings.png?raw=true)
![sun-position-settings](https://user-images.githubusercontent.com/12692680/57134503-aa429600-6da6-11e9-945b-aa7b6d750267.png)

@@ -194,3 +194,3 @@ - **Position** connects to the central configuration node, which contains the current position, but also handles internal shared functions

![moon-position](images/moon-position-example.png?raw=true)
![moon-position-example](https://user-images.githubusercontent.com/12692680/57134922-f6daa100-6da7-11e9-91f7-6f33ab997df5.png)

@@ -203,3 +203,3 @@ ```json

![moon-position](images/sun-position-settings.png?raw=true)
![moon-position-settings](https://user-images.githubusercontent.com/12692680/57134923-f6daa100-6da7-11e9-9e1d-a49ecfc4bbcc.png)

@@ -276,3 +276,3 @@ - **Position** connects to the central configuration node, which contains the current position, but also handles internal shared functions

![time-inject](images/time-inject-example.png?raw=true)
![time-inject-example](https://user-images.githubusercontent.com/12692680/57134517-b4fd2b00-6da6-11e9-914a-30741c285426.png)

@@ -285,3 +285,3 @@ ```json

![time-inject](images/time-inject-settings.png?raw=true)
![time-inject-settings](https://user-images.githubusercontent.com/12692680/57134518-b4fd2b00-6da6-11e9-891a-d62b271b1de3.png)

@@ -304,5 +304,5 @@ - **Position** connects to the central configuration node, which contains the current position, but also handles internal shared functions

- **set additional timestamp**:
![time-inject](images/time-inject-settings-addProp1.png?raw=true)
!![time-inject-settings-addProp1](https://user-images.githubusercontent.com/12692680/57134519-b4fd2b00-6da6-11e9-843a-dd1d83555b8b.png)
- **set additional sun timestamp**:
![time-inject](images/time-inject-settings-addProp2.png?raw=true)
![time-inject-settings-addProp2](https://user-images.githubusercontent.com/12692680/57134520-b4fd2b00-6da6-11e9-9832-a3d8beda4897.png)
- **possible formats of timestamp output**

@@ -337,3 +337,3 @@ - number - milliseconds UNIX timestamp

![within-time](images/within-time-example.png?raw=true)
![within-time-example](https://user-images.githubusercontent.com/12692680/57134524-b595c180-6da6-11e9-9208-a5c7cb89d2db.png)

@@ -348,3 +348,3 @@ ```json

![within-time](images/within-time-settings.png?raw=true)
![within-time-settings](https://user-images.githubusercontent.com/12692680/57134525-b62e5800-6da6-11e9-9946-2c6873592026.png)

@@ -366,9 +366,9 @@ - **Position** connects to the central configuration node, which contains the current position, but also handles internal shared functions

- **none** - no status will be displayed - **only errors** - if an error occurs it will be displayed
![within-time-status-error](images/within-time-status-error.png?raw=true)
![within-time-status-error](https://user-images.githubusercontent.com/12692680/57134527-b62e5800-6da6-11e9-946a-677044d25655.png)
- **time limits** - the time limits will be displayed. An `⎇` sign after a time will show that an alternate time is used.
![within-time-status-time](images/within-time-status-time.png?raw=true)
![within-time-status-time](https://user-images.githubusercontent.com/12692680/57134513-b4649480-6da6-11e9-9bb2-3acda84b8ef8.png)
- **last message** - the time limits will be shown and if the last message was blocked. An `⎇` sign after a time will show that an alternate time is used.
![within-time-status-error](images/within-time-status-message-block.png?raw=true)
![within-time-status-message-block](https://user-images.githubusercontent.com/12692680/57134528-b6c6ee80-6da6-11e9-90be-e3b15c2b2bff.png)
if the message was pass through the timestamp of this message will be shown.
![within-time-status-send](images/within-time-status-message-send.png?raw=true)
![within-time-status-message-send](https://user-images.githubusercontent.com/12692680/57134529-b6c6ee80-6da6-11e9-8c71-7245dda4b6ee.png)
- **time limits or last message** - on deploy/start until a message arrives the same behavior as `time limits` options, otherwise the `last message` status display.

@@ -382,3 +382,3 @@ - **resend start** If this checkbox is checked and a message arrived outside of time, this message will be additional send again some milliseconds after next start time point. This option is only for fixed time definitions available.

![time-comp](images/time-comp-example.png?raw=true)
![time-comp-example](https://user-images.githubusercontent.com/12692680/57134514-b4649480-6da6-11e9-9321-9fd1a2927a9b.png)

@@ -395,3 +395,3 @@ **This node is in development and has a pre release state!!**

![time-comp](images/time-comp-settings.png?raw=true)
![time-comp-settings](https://user-images.githubusercontent.com/12692680/57134515-b4fd2b00-6da6-11e9-9d04-0cafb0721ba8.png)

@@ -415,3 +415,3 @@ - **Position** connects to the central configuration node, which contains the current position, but also handles internal shared functions

![time-span](images/time-span-example.png?raw=true)
![time-span-example](https://user-images.githubusercontent.com/12692680/57134521-b595c180-6da6-11e9-82fe-01ddaaba0d7b.png)

@@ -428,3 +428,3 @@ **This node is in development and has a pre release state!!**

![time-span](images/time-span-settings.png?raw=true)
![time-span-settings](https://user-images.githubusercontent.com/12692680/57134523-b595c180-6da6-11e9-8146-ce1d7a59792a.png)

@@ -447,2 +447,9 @@ - **Position** connects to the central configuration node, which contains the current position, but also handles internal shared functions

### blind-control
Used to control a blind with many possibilities. This can be time-dependent and it can calculate the blind position based on the current position of the sun to limit the sun light To limit the sunlight on the floor of a window.
[blind-control](blind_control.md)
### Times definitions

@@ -452,3 +459,3 @@

![within-time start Time](images/within-time-startTime.png?raw=true)
![within-time-startTime](https://user-images.githubusercontent.com/12692680/57134526-b62e5800-6da6-11e9-9b95-b0e9998c41c4.png)

@@ -491,3 +498,3 @@ manual timestamps can be entered as one of the following formats:

![sun times](images/sun-times.png?raw=true)
![sun-times](https://user-images.githubusercontent.com/12692680/57134546-c6dece00-6da6-11e9-8a32-c3517c5211fe.png)

@@ -494,0 +501,0 @@ ##### remarks

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc