node-red-contrib-sun-position
Version:
NodeRED nodes to get sun and moon position
1,054 lines (993 loc) • 62.2 kB
JavaScript
/*
* This code is licensed under the Apache License Version 2.0.
*
* Copyright (c) 2022 Robert Gester
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
*/
/********************************************
* time-inject:
*********************************************/
'use strict';
/** --- Type Defs ---
* @typedef {import('./types/typedefs.js').runtimeRED} runtimeRED
* @typedef {import('./types/typedefs.js').runtimeNode} runtimeNode
* @typedef {import('./types/typedefs.js').runtimeNodeConfig} runtimeNodeConfig
* @typedef {import("./lib/dateTimeHelper.js").ITimeObject} ITimeObject
* @typedef {import("./lib/dateTimeHelper.js").ILimitationsObj} ILimitationsObj
* @typedef {import("./10-position-config.js").ITypedValue} ITypedValue
* @typedef {import("./10-position-config.js").IValuePropertyType} IValuePropertyType
* @typedef {import("./10-position-config.js").ITimePropertyType} ITimePropertyType
* @typedef {import("./10-position-config.js").IPositionConfigNode} IPositionConfigNode
* @typedef {import("./10-position-config.js").IOffsetData} IOffsetData
*/
/**
* @typedef {Object} ITIPropertyTypeInt
* @property {string} outType - valid days
* @property {*} outValue - valid days
* @property {string} outType - valid days
* @property {string} [expr] - optional prepared Jsonata expression
*
* @typedef {ILimitationsObj & ITypedValue & IOffsetData & ITIPropertyTypeInt} ITIPropertyType
*/
/**
* @typedef {Object} ITimeInjectNodeInstance Extensions for the nodeInstance object type
* @property {IPositionConfigNode} positionConfig - tbd
* @property {string} addId internal used additional id
*
* @property {number} injType type of the inject node
* @property {number} intervalCount - count of the interval
* @property {string} intervalCountType - ??
* @property {number} intervalCountMultiplier - ??
* @property {Date} intervalStart - ??
* @property {number} intervalAmount - ??
* @property {number} intervalCountCurrent - ??
* @property {number} intervalCountMax - ??
* @property {string} intervalText - the text of the interval
*
* @property {ITimePropertyType} timeStartData - ??
* @property {IValuePropertyType} property - ??
* @property {ITypedValue} propertyThreshold - ??
* @property {string} propertyOperator - ??
* @property {ITimePropertyType} timeStartAltData - ??
* @property {ITimePropertyType} timeEndData - ??
* @property {*} cronJobObj - ??
* @property {string} cronExpr - ??
*
* @property {Array.<ITIPropertyType>} props - output data
*
* @property {number} recalcTime - ??
*
* @property {NodeJS.Timeout} timeOutStartObj - ??
* @property {NodeJS.Timeout} timeOutEndObj - ??
* @property {NodeJS.Timer} intervalObj - ??
* @property {NodeJS.Timeout} onceTimeOutObj - ??
*
* @property {number} intervalTime - ??
* @property {Date} nextStartTime - ??
* @property {Date} nextStartTimeAlt - ??
* @property {Date} nextEndTime - ??
* @property {number} onceDelay - ??
* @property {Date} timedatestart - ??
* @property {Date} timedateend - ??
* @property {boolean} isAltAvailable - ??
* @property {boolean} isAltFirst - ??
*
*
* @property {number} cacheYear - ??
* @property {Date} cacheStart - ??
* @property {Date} cacheEnd - ??
*
* @property {function} getTimeLimitation - get the limitation for time
* @property {function} initializeStartTimer - initializes the start timer
* @property {function} initialize - initializes the node itself
* @property {IGetTimeAsMillisecond} getMillisecEnd - get the end time in millisecond
* @property {function} doCreateStartTimeout - creates the start timeout
* @property {function} doCreateEndTimeout - creates the end timeout
* @property {function} doCreateCRONSetup - creates the CRON interval
* @property {function} doRecalcStartTimeOut - Recalculate the Start timeout
* @property {function} doStartInterval - start an Intervall
* @property {function} getIntervalText - creates the text for an interval
* @property {function} createNextInterval - Recalculate the Interval
* @property {function} prepOutMsg - Prepares a message object for sending
* @property {function} getIntervalTime - get and validate a given interval
* @property {function} doSetStatus - get and validate a given interval
* ... obviously there are more ...
*/
/**
* Description of the function
* @typedef {function} IGetTimeAsMillisecond
* @param {ITIPropertyType} node the node Data
* @return {number} the time in millisecond
*/
/**
* @typedef {ITimeInjectNodeInstance & runtimeNode} ITimeInjectNode Combine nodeInstance with additional, optional functions
*/
/******************************************************************************************/
/** Export the function that defines the node
* @type {runtimeRED} */
module.exports = function (/** @type {runtimeRED} */ RED) {
'use strict';
const util = require('util');
const {scheduleTask} = require('cronosjs');
const hlp = require('./lib/dateTimeHelper.js');
/******************************************************************************************/
/**
* standard Node-Red Node handler for the timeInjectNode
* @param {*} config the Node-Red Configuration property of the Node
*/
function timeInjectNode(config) {
/**
* get the output properies
* @param {object} props - the property array
* @returns {Array.<ITIPropertyType>} the nw property object
*/
function prepareProps(node, props) {
// node.debug('prepareProps ' + util.inspect(props, { colors: true, compact: 10, breakLength: Infinity }));
const outProps = [];
props.forEach( prop => {
const propNew = {
outType : prop.pt,
outValue : prop.p,
type : prop.vt,
value : prop.v,
format : prop.f,
offsetType : prop.oT,
offset : prop.o,
multiplier : prop.oM,
next : (typeof prop.next === 'undefined' || prop.next === null || hlp.isTrue(prop.next === true)) ? true : false,
days : prop.days,
months : prop.months,
onlyEvenDays: prop.onlyEvenDays,
onlyOddDays : prop.onlyOddDays,
onlyEvenWeeks: prop.onlyEvenWeeks,
onlyOddWeeks : prop.onlyOddWeeks
};
if (propNew.type === 'dateEntered' ||
propNew.type === 'dateSpecific' ||
propNew.type === 'dayOfMonth') {
propNew.next = false;
} else if (propNew.type === 'pdsTimeNow') {
// next sun time
propNew.next = true;
}
if (node.positionConfig && propNew.type === 'jsonata') {
try {
propNew.expr = node.positionConfig.getJSONataExpression(node, propNew.value);
} catch (err) {
node.error(RED._('node-red-contrib-sun-position/position-config:errors.invalid-expr', { error:err.message }));
propNew.expr = null;
}
}
outProps.push(propNew);
});
return outProps;
}
const tInj = {
none : 0,
timer : 1,
interval : 2,
intervalBtwStartEnd : 4,
intervalAmount : 5,
cron : 6
};
const intervalMax = hlp.TIME_3d;
RED.nodes.createNode(this, config);
/** Copy 'this' object in case we need it in context of callbacks of other functions.
* @type {ITimeInjectNode}
*/
// @ts-ignore
const node = this;
// Retrieve the config node
node.positionConfig = RED.nodes.getNode(config.positionConfig);
if (!node.positionConfig) {
node.error(RED._('node-red-contrib-sun-position/position-config:errors.config-missing'));
node.status({fill: 'red', shape: 'dot', text: RED._('node-red-contrib-sun-position/position-config:errors.config-missing-state') });
return;
} else if (node.positionConfig.checkNode(
error => {
const text = RED._('node-red-contrib-sun-position/position-config:errors.config-error', { error });
node.error(text);
node.status({fill: 'red', shape: 'dot', text });
return true;
}, false)) {
return;
}
// node.debug('initialize timeInjectNode ' + util.inspect(config, { colors: true, compact: 10, breakLength: Infinity }));
if (config.injectTypeSelect === 'interval-time') {
node.injType = tInj.intervalBtwStartEnd;
} else if (config.injectTypeSelect === 'interval') {
node.injType = tInj.interval;
} else if ((config.injectTypeSelect === 'time') && (config.timeType && config.timeType !== 'none')) {
node.injType = tInj.timer;
} else if (config.injectTypeSelect === 'interval-amount') {
node.injType = tInj.intervalAmount;
} else if (config.injectTypeSelect === 'cron') {
node.injType = tInj.cron;
} else {
node.injType = tInj.none;
}
node.intervalCount = config.intervalCount ? config.intervalCount : 0;
if (node.injType === tInj.interval ||
node.injType === tInj.intervalBtwStartEnd ||
node.injType === tInj.intervalAmount) {
node.intervalCountType = (config.intervalCountType || 'num');
} else {
node.intervalCountType = 'none';
}
node.intervalCountMultiplier = config.intervalCountMultiplier ? config.intervalCountMultiplier : hlp.TIME_1min;
if (node.injType === tInj.interval) {
if (config.intervalStart) {
node.intervalStart = hlp.isoStringToDate(config.intervalStart);
} else {
node.intervalStart = new Date();
}
}
if (node.injType === tInj.intervalBtwStartEnd ||
node.injType === tInj.intervalAmount ||
node.injType === tInj.timer) {
node.timeStartData = {
type: config.timeType,
value : config.time,
offsetType : config.offsetType,
offset : config.offset || config.timeOffset || 0,
multiplier : config.offsetMultiplier ? parseInt(config.offsetMultiplier) : (config.timeOffsetMultiplier ? parseInt(config.timeOffsetMultiplier) : 60),
next : true,
days : config.timeDays,
months : config.timeMonths,
onlyOddDays: hlp.isTrue(config.timeOnlyOddDays),
onlyEvenDays: hlp.isTrue(config.timeOnlyEvenDays),
onlyEvenWeeks: hlp.isTrue(config.timeOnlyEvenWeeks),
onlyOddWeeks : hlp.isTrue(config.timeOnlyOddWeeks)
};
if (!node.timeStartData.offsetType) {
// @ts-ignore
node.timeStartData.offsetType = ((node.timeStartData.offset === 0) ? 'none' : 'num');
}
if (node.timeStartData.days === '') {
throw new Error('No valid days given! Please check settings!');
}
if (node.timeStartData.months === '') {
throw new Error('No valid month given! Please check settings!');
}
if (node.timeStartData.onlyEvenDays && node.timeStartData.onlyOddDays) {
node.timeStartData.onlyEvenDays = false;
node.timeStartData.onlyOddDays = false;
}
if (node.timeStartData.onlyEvenWeeks && node.timeStartData.onlyOddWeeks) {
node.timeStartData.onlyEvenWeeks = false;
node.timeStartData.onlyOddWeeks = false;
}
if (config.propertyType && config.propertyType !== 'none' && config.timeAltType && config.timeAltType !== 'none') {
node.property = {
type: config.propertyType ? config.propertyType : 'none',
value: config.property ? config.property : ''
};
if (node.positionConfig && node.property.type === 'jsonata') {
try {
node.property.expr = node.positionConfig.getJSONataExpression(this, node.property.value);
} catch (err) {
node.error(RED._('node-red-contrib-sun-position/position-config:errors.invalid-expr', { error:err.message }));
node.property.expr = null;
}
}
if (config.propertyThresholdType && config.propertyThresholdType !== 'none') {
node.propertyThreshold = {
type: config.propertyThresholdType ? config.propertyThresholdType : 'none',
value: config.propertyThreshold ? config.propertyThreshold : ''
};
}
}
node.propertyOperator = config.propertyCompare ? config.propertyCompare : 'true';
if (node.injType === tInj.timer && node.property && config.timeAltType && config.timeAltType !== 'none') {
node.timeStartAltData = {
type: config.timeAltType ? config.timeAltType : 'none',
value : config.timeAlt ? config.timeAlt : '',
offsetType : config.timeAltOffsetType,
offset : config.timeAltOffset ? config.timeAltOffset : 0,
multiplier : config.timeAltOffsetMultiplier ? parseInt(config.timeAltOffsetMultiplier) : 60,
next : true,
days : config.timeAltDays,
months : config.timeAltMonths,
onlyOddDays: hlp.isTrue(config.timeAltOnlyOddDays),
onlyEvenDays: hlp.isTrue(config.timeAltOnlyEvenDays),
onlyEvenWeeks: hlp.isTrue(config.timeAltOnlyEvenWeeks),
onlyOddWeeks : hlp.isTrue(config.timeAltOnlyOddWeeks)
};
// @ts-ignore
if (!node.timeStartAltData.offsetType) { node.timeStartAltData.offsetType = ((node.timeStartAltData.offset === 0) ? 'none' : 'num'); }
if (node.timeStartAltData.days === '') {
throw new Error('No valid alternate days given! Please check settings!');
}
if (node.timeStartAltData.months === '') {
throw new Error('No valid alternate month given! Please check settings!');
}
if (node.timeStartAltData.onlyEvenDays && node.timeStartAltData.onlyOddDays) {
node.timeStartAltData.onlyEvenDays = false;
node.timeStartAltData.onlyOddDays = false;
}
if (node.timeStartAltData.onlyEvenWeeks && node.timeStartAltData.onlyOddWeeks) {
node.timeStartAltData.onlyEvenWeeks = false;
node.timeStartAltData.onlyOddWeeks = false;
}
} // timeAlt
} // timeStartData
if (node.injType === tInj.intervalBtwStartEnd ||node.injType === tInj.intervalAmount ) {
node.timeEndData = {
type: config.timeEndType,
value : config.timeEnd,
offsetType : config.timeEndOffsetType,
offset : config.timeEndOffset ? config.timeEndOffset : 0,
multiplier : config.timeEndOffsetMultiplier ? parseInt(config.timeEndOffsetMultiplier) : 60,
next : true,
days : config.timeDays,
months : config.timeMonths,
onlyOddDays: hlp.isTrue(config.timeOnlyOddDays),
onlyEvenDays: hlp.isTrue(config.timeOnlyEvenDays),
onlyEvenWeeks: hlp.isTrue(config.timeOnlyEvenWeeks),
onlyOddWeeks : hlp.isTrue(config.timeOnlyOddWeeks)
};
if (!node.timeEndData.offsetType) {
// @ts-ignore
node.timeEndData.offsetType = ((node.timeEndData.offset === 0) ? 'none' : 'num');
}
if (node.timeEndData.days === '') {
throw new Error('No valid days given! Please check settings!');
}
if (node.timeEndData.months === '') {
throw new Error('No valid month given! Please check settings!');
}
if (node.timeEndData.onlyEvenDays && node.timeEndData.onlyOddDays) {
node.timeEndData.onlyEvenDays = false;
node.timeEndData.onlyOddDays = false;
}
if (node.timeEndData.onlyEvenWeeks && node.timeEndData.onlyOddWeeks) {
node.timeEndData.onlyEvenWeeks = false;
node.timeEndData.onlyOddWeeks = false;
}
} // timeEndData
node.cronJobObj = null;
if (node.injType === tInj.cron) {
node.cronExpr = config.cron || '';
} // cron
/* Handle legacy */
if(!Array.isArray(config.props)){
config.props = [];
config.props.push({
p : '',
pt : 'msgPayload',
v : config.payload ? config.payload : '',
vt : config.payloadType ? ((config.payloadType === 'string') ? 'str' : config.payloadType) : (config.payload ? 'str' : 'date'),
o : config.payloadOffset ? config.payloadOffset : 1,
oT : (config.payloadOffset === 0 || config.payloadOffset === '') ? 'none' : (config.payloadOffsetType ? config.payloadOffsetType : 'num'),
oM : config.payloadOffsetMultiplier ? config.payloadOffsetMultiplier : hlp.TIME_1min,
f : config.payloadTimeFormat ? config.payloadTimeFormat : 0,
next : true,
days : '*',
months: '*',
onlyEvenDays: false,
onlyOddDays: false
});
if (config.topic) {
config.props.push({
p : '',
pt : 'msgTopic',
v : config.topic ? config.topic : '',
vt : 'str',
o : 1,
oT : 'none',
oM : 60000,
f : 0,
next : false,
days : '*',
months: '*',
onlyEvenDays: false,
onlyOddDays: false
});
}
if (typeof config.addPayload1Type !== 'undefined' &&
typeof config.addPayload1ValueType !== 'undefined' &&
config.addPayload1Type !== 'none' &&
config.addPayload1ValueType !== 'none') {
config.props.push({
p : config.addPayload1,
pt : config.addPayload1Type,
v : config.addPayload1Value,
vt : config.addPayload1ValueType ? ((config.addPayload1ValueType === 'string') ? 'str' : config.addPayload1ValueType) : (config.addPayload1Value ? 'str' : 'date'),
o : config.addPayload1Offset ? config.addPayload1Offset : 1,
oT : (config.addPayload1Offset === 0 || config.addPayload1Offset === '') ? 'none' : (config.addPayload1OffsetType ? config.addPayload1OffsetType : 'num'),
oM : config.addPayload1OffsetMultiplier ? config.addPayload1OffsetMultiplier : 60000,
f : config.addPayload1Format ? config.addPayload1Format : 0,
next : false,
days : config.addPayload1Days ? config.addPayload1Days : '*',
months: '*',
onlyEvenDays: false,
onlyOddDays: false
});
}
if (typeof config.addPayload2Type !== 'undefined' &&
typeof config.addPayload2ValueType !== 'undefined' &&
config.addPayload2Type !== 'none' &&
config.addPayload2ValueType !== 'none') {
config.props.push({
p : config.addPayload2,
pt : config.addPayload2Type,
v : config.addPayload2Value,
vt : config.addPayload2ValueType ? ((config.addPayload2ValueType === 'string') ? 'str' : config.addPayload2ValueType) : (config.addPayload2Value ? 'str' : 'date'),
o : config.addPayload2Offset ? config.addPayload2Offset : 1,
oT : (config.addPayload2Offset === 0 || config.addPayload2Offset === '') ? 'none' : (config.addPayload2OffsetType ? config.addPayload2OffsetType : 'num'),
oM : config.addPayload2OffsetMultiplier ? config.addPayload2OffsetMultiplier : 60000,
f : config.addPayload2Format ? config.addPayload2Format : 0,
next : false,
days : config.addPayload2Days ? config.addPayload2Days : '*',
months: '*',
onlyEvenDays: false,
onlyOddDays: false
});
}
if (typeof config.addPayload3Type !== 'undefined' &&
typeof config.addPayload3ValueType !== 'undefined' &&
config.addPayload3Type !== 'none' &&
config.addPayload3ValueType !== 'none') {
config.props.push({
p : config.addPayload3,
pt : config.addPayload3Type,
v : config.addPayload3Value,
vt : config.addPayload3ValueType ? ((config.addPayload3ValueType === 'string') ? 'str' : config.addPayload3ValueType) : (config.addPayload3Value ? 'str' : 'date'),
o : config.addPayload3Offset ? config.addPayload3Offset : 1,
oT : (config.addPayload3Offset === 0 || config.addPayload3Offset === '') ? 'none' : (config.addPayload3OffsetType ? config.addPayload3OffsetType : 'num'),
oM : config.addPayload3OffsetMultiplier ? config.addPayload3OffsetMultiplier : 60000,
f : config.addPayload3Format ? config.addPayload3Format : 0,
next : false,
days : config.addPayload3Days ? config.addPayload3Days : '*',
months: '*',
onlyEvenDays: false,
onlyOddDays: false
});
}
delete config.payload;
delete config.payloadType;
delete config.payloadTimeFormat;
delete config.payloadOffset;
delete config.payloadOffsetType;
delete config.payloadOffsetMultiplier;
delete config.topic;
delete config.addPayload1;
delete config.addPayload1Type;
delete config.addPayload1Value;
delete config.addPayload1ValueType;
delete config.addPayload1Format;
delete config.addPayload1Offset;
delete config.addPayload1OffsetType;
delete config.addPayload1OffsetMultiplier;
delete config.addPayload1Next;
delete config.addPayload1Days;
delete config.addPayload2;
delete config.addPayload2Type;
delete config.addPayload2Value;
delete config.addPayload2ValueType;
delete config.addPayload2Format;
delete config.addPayload2Offset;
delete config.addPayload2OffsetType;
delete config.addPayload2OffsetMultiplier;
delete config.addPayload2Next;
delete config.addPayload2Days;
delete config.addPayload3;
delete config.addPayload3Type;
delete config.addPayload3Value;
delete config.addPayload3ValueType;
delete config.addPayload3Format;
delete config.addPayload3Offset;
delete config.addPayload3OffsetType;
delete config.addPayload3OffsetMultiplier;
delete config.addPayload3Next;
delete config.addPayload3Days;
}
node.props = prepareProps(this, config.props);
node.recalcTime = (config.recalcTime || 2) * hlp.TIME_1h;
node.timeOutStartObj = null;
node.timeOutEndObj = null;
node.intervalObj = null;
node.intervalTime = null;
node.nextStartTime = null;
node.nextStartTimeAlt = null;
node.nextEndTime = null;
if (config.once === true || config.once === 'true') {
if (config.onceDelay > 2147483) {
node.onceDelay = 2147483;
} else {
node.onceDelay = config.onceDelay || 0.1;
}
}
if (config.timedatestart) {
node.timedatestart = new Date(config.timedatestart);
}
if (config.timedateend) {
node.timedateend = new Date(config.timedateend);
}
/**
* get the limitation for time
* @param {Date} dNow base Date
*/
node.getTimeLimitation = dNow => {
const result = {
value: null,
valid : true
};
if (node.timedatestart || node.timedateend) {
const year = dNow.getFullYear();
if (node.cacheYear !== year) {
node.cacheYear = year;
if (node.timedatestart) {
node.cacheStart = new Date(node.timedatestart);
node.cacheStart.setFullYear(year);
node.cacheStart.setHours(0, 0, 0, 0);
} else {
node.cacheStart = new Date(year, 0, 0, 0, 0, 0, 1);
}
if (node.timedateend) {
node.cacheEnd = new Date(node.timedateend);
node.cacheEnd.setFullYear(year);
node.cacheEnd.setHours(23, 59, 59, 999);
} else {
node.cacheEnd = new Date(year, 11, 31, 23, 59, 59, 999);
}
}
if (node.cacheStart < node.cacheEnd) {
// in the current year - e.g. 6.4. - 7.8.
if (dNow < node.cacheStart || dNow > node.cacheEnd) {
result.value = new Date(node.cacheYear + ((dNow >= node.cacheEnd) ? 1 : 0), node.cacheStart.getMonth(), node.cacheStart.getDate(), 0, 0, 1);
result.errorStatus = RED._('time-inject.errors.invalid-daterange') + ' [' + node.positionConfig.toDateString(result.value)+ ']';
result.valid = false;
return result;
}
} else {
// switch between year from end to start - e.g. 2.11. - 20.3.
if (dNow < node.cacheStart && dNow > node.cacheEnd) {
result.value = new Date(node.cacheYear, node.cacheStart.getMonth(), node.cacheStart.getDate(), 0, 0, 1);
result.errorStatus = RED._('time-inject.errors.invalid-daterange') + ' [' + node.positionConfig.toDateString(result.value)+ ']';
result.valid = false;
return result;
}
}
}
return result;
};
/**
* initializes the start timer
*/
node.initializeStartTimer = () => {
// node.debug(`initializeStartTimer`);
if (!node.timeStartData) {
// node.debug('initializeStartTimer - no start time data');
return false;
}
const dNow = new Date();
const nowTs = dNow.valueOf();
const startLimit = node.getTimeLimitation(dNow);
if (!startLimit.valid) {
// node.debug('initializeStartTimer - start limited');
return false;
}
const initStartData = Object.assign({}, node.timeStartData);
initStartData.next = false;
const nextStartTimeData = node.positionConfig.getTimeProp(node, {}, initStartData);
if (nextStartTimeData.error || !hlp.isValidDate(nextStartTimeData.value)) {
// node.debug(`initializeStartTimer - start time wrong ${ nextStartTimeData.error}`);
return false;
}
let millisecStart = nextStartTimeData.value.valueOf() - nowTs;
if (node.timeStartAltData) {
const initStartAltData = Object.assign({}, node.timeStartAltData);
initStartAltData.next = false;
const nextTimeAltData = node.positionConfig.getTimeProp(node, {}, initStartAltData);
if (!nextTimeAltData.error && hlp.isValidDate(nextTimeAltData.value)) {
const millisecAlt = nextTimeAltData.value.valueOf() - nowTs;
if (millisecAlt < millisecStart) {
millisecStart = millisecAlt;
}
}
}
if (millisecStart > 0 || !node.timeEndData) {
// node.debug(`initializeStartTimer - start ${ millisecStart } in future or no end time`);
return false;
}
const initEndTime = Object.assign({},node.timeEndData);
initEndTime.next = false;
const nextEndTimeData = node.positionConfig.getTimeProp(node, {}, initEndTime);
if (nextEndTimeData.error || !hlp.isValidDate(nextEndTimeData.value)) {
// node.debug(`initializeStartTimer - end time error ${ nextEndTimeData.error }`);
return false;
}
const millisecEnd = (nextEndTimeData.value.valueOf() - nowTs);
if (millisecEnd < 0) {
// node.debug(`initializeStartTimer - end time ${ millisecEnd } in past`);
return false;
}
// node.debug(`initializeStartTimer - starting interval!!`);
if (node.injType === tInj.intervalBtwStartEnd) {
node.getIntervalTime();
node.doStartInterval(); // starte Interval
} else if (node.injType === tInj.intervalAmount) {
node.intervalCountMax = node.positionConfig.getFloatProp(node, null, {
type: node.intervalCountType, value: node.intervalCount, def: 0
});
node.intervalTime = Math.floor((millisecEnd - millisecStart) / node.intervalCountMax);
node.intervalCountCurrent = 0;
node.doStartInterval(); // starte Interval
}
return true;
};
/**
* get the end time in millisecond
* @param {boolean} doEmit if true directly send Data
*/
node.initialize = doEmit => {
switch (node.injType) {
case tInj.interval:
if (doEmit === true) {
node.emit('input', {
_inject_type: 'once/startup'
}); // will create timeout
}
// node.debug('initialize - absolute Intervall');
node.send(node.prepOutMsg({ _inject_type: 'interval-start' }));
node.createNextInterval();
break;
case tInj.timer:
// node.debug('initialize - timer');
if (doEmit === true) {
node.emit('input', {
_inject_type: 'once/startup'
}); // will create timeout
} else {
node.doCreateStartTimeout('initial');
}
break;
case tInj.intervalBtwStartEnd:
case tInj.intervalAmount:
// node.debug('initialize - Intervall timer/amount/fromStart');
if (doEmit === true) {
node.emit('input', {
_inject_type: 'once/startup'
}); // will create timeout
}
if (!node.initializeStartTimer()) {
node.doCreateStartTimeout('initial');
}
break;
case tInj.cron:
// node.debug('initialize - Intervall cron');
if (doEmit === true) {
node.emit('input', {
_inject_type: 'once/startup'
}); // will create timeout
}
node.doCreateCRONSetup();
break;
default:
// node.debug('initialize - default');
node.doSetStatus('green');
if (doEmit === true) {
node.emit('input', {
_inject_type: 'once/startup'
}); // will create timeout
}
}
};
/**
* get the end time in millisecond
* @return {number} the time in millisecond
*/
node.getMillisecEnd = () => {
if (!node.timeEndData) {
return null;
}
// node.debug(`doCreateEndTimeout node.timeEndData=${util.inspect(node.timeEndData, { colors: true, compact: 10, breakLength: Infinity })}`);
if (node.timeOutEndObj) {
clearTimeout(node.timeOutEndObj);
node.timeOutEndObj = null;
}
const nextEndTimeData = node.positionConfig.getTimeProp(node, {}, node.timeEndData);
if (nextEndTimeData.error) {
node.nextEndTime = null;
node.debug('nextEndTimeData=' + util.inspect(nextEndTimeData, { colors: true, compact: 10, breakLength: Infinity }));
node.error(nextEndTimeData.error);
return null;
}
node.nextEndTime = nextEndTimeData.value;
let millisecEnd = hlp.TIME_24h;
if ((node.nextEndTime !== null) && (typeof node.nextEndTime !== 'undefined')) {
// node.debug('timeout ' + node.nextEndTime + ' is in ' + millisec + 'ms');
millisecEnd = hlp.getTimeOut(new Date(), node.nextEndTime);
}
return millisecEnd;
};
/**
* creates the end timeout
* @param {number} [millisecEnd] - the end timestamp
* @returns {object} state or error
*/
node.doCreateEndTimeout = millisecEnd => {
millisecEnd = millisecEnd || node.getMillisecEnd();
if (millisecEnd === null) {
return;
}
if (millisecEnd > hlp.TIME_5d) {
millisecEnd = Math.min((millisecEnd - hlp.TIME_36h), 2147483646);
node.timeOutEndObj = setTimeout(() => {
node.doCreateEndTimeout();
}, millisecEnd); // 1,5 days before
return;
}
node.timeOutEndObj = setTimeout(() => {
node.timeOutEndObj = null;
clearInterval(node.intervalObj);
node.intervalObj = null;
node.doCreateStartTimeout('timeOutEnd');
}, millisecEnd);
}; // doCreateEndTimeout
/**
* Recalculate the Start timeout
*/
node.doRecalcStartTimeOut = () => {
try {
node.debug('performing a recalc of the next inject time');
node.doCreateStartTimeout('timeOutRecalc');
} catch (err) {
node.error(err.message);
node.log(util.inspect(err));
node.status({
fill: 'red',
shape: 'dot',
text: RED._('node-red-contrib-sun-position/position-config:errors.error-title')
});
}
};
/**
* creates the text for an interval
* @param {number} mstime - time stamp
* @param {number} val - value
* @returns {string} the text of the interval
*/
node.getIntervalText = (mstime, val) => {
if (mstime === hlp.TIME_WEEK) {
if (val === 1) {
return String(val) + RED._('node-red-contrib-sun-position/position-config:common.units.week');
}
return String(val) + RED._('node-red-contrib-sun-position/position-config:common.units.weeks');
} else if (mstime === hlp.TIME_24h) {
if (val === 1) {
return String(val) + RED._('node-red-contrib-sun-position/position-config:common.units.day');
}
return String(val) + RED._('node-red-contrib-sun-position/position-config:common.units.days');
} else if (mstime === hlp.TIME_1h) {
return String(val) + RED._('node-red-contrib-sun-position/position-config:common.units.hour');
} else if (mstime === hlp.TIME_1min) {
return String(val) + RED._('node-red-contrib-sun-position/position-config:common.units.min');
} else if (mstime === hlp.TIME_1s) {
return String(val) + RED._('node-red-contrib-sun-position/position-config:common.units.sec');
}
return String(val) + RED._('node-red-contrib-sun-position/position-config:common.units.ms');
};
/**
* Recalculate the Interval
*/
node.createNextInterval = () => {
node.getIntervalTime();
clearInterval(node.intervalObj);
const dNow = (new Date()).valueOf();
const diff = Math.abs(dNow - node.intervalStart.valueOf());
const ivCount = Math.trunc(diff / node.intervalTime) + 1;
const tsStart = node.intervalStart.valueOf() + (node.intervalTime * ivCount); // Start Timestamp
const millisec = tsStart - dNow;
// node.debug(`createNextInterval start=${node.intervalStart}; interval=${node.intervalTime}; diff=${diff}; ivCount=${ivCount}; tsStart=${tsStart}; millisec=${millisec}`);
if (millisec > 2147483647) {
// there is a limitation of nodejs that the maximum setTimeout time
// should not more then 2147483647 ms (24.8 days).
node.timeOutStartObj = setTimeout(() => {
node.createNextInterval();
}, 2147483647);
return;
}
node.timeOutStartObj = setTimeout(() => {
node.timeOutStartObj = null;
if (node.intervalTime < intervalMax) {
node.debug(`interval is less than max=${intervalMax}ms, create absolute interval of ${node.intervalTime}ms`);
node.intervalObj = setInterval(() => {
node.send(node.prepOutMsg({ _inject_type: 'interval' }));
}, node.intervalTime);
if (node.intervalTime > hlp.TIME_12h) {
node.status({
text: '↻' + (node.intervalTime / hlp.TIME_1h).toFixed(1) + 'h'
});
} else if (node.intervalTime > hlp.TIME_1h) {
node.status({
text: '↻' + (node.intervalTime / hlp.TIME_1min).toFixed(2) + 'min'
});
} else {
node.status({
text: ' ↻' + node.intervalText
});
/*
node.status({
text: '↻' + Math.round(((node.intervalTime / 1000) + Number.EPSILON) * 10) / 10 + 's'
}); */
}
} else {
node.createNextInterval();
}
node.send(node.prepOutMsg({ _inject_type: 'interval' }));
}, millisec);
node.status({
text: node.positionConfig.toTimeString(new Date(tsStart)) + ' ↻' + node.intervalText
});
};
/**
* Prepares a message object for sending
* @param {*} msg - the message object
*/
node.prepOutMsg = msg => {
// node.debug(`prepOutMsg node.msg=${util.inspect(msg, { colors: true, compact: 10, breakLength: Infinity })}`);
const dNow = msg.__ts__input_date || new Date();
let props = node.props;
if (msg.__user_inject_props__ && msg.__user_inject_props__.props && Array.isArray(msg.__user_inject_props__.props)) {
props = prepareProps(node, msg.__user_inject_props__.props);
}
delete msg.__user_inject_props__;
delete msg.__ts__input_date;
// node.debug(`prepOutMsg props=${util.inspect(props, { colors: true, compact: 10, breakLength: Infinity })}`);
for (let i = 0; i < props.length; i++) {
// node.debug(`prepOutMsg-${i} props[${i}]=${util.inspect(props[i], { colors: true, compact: 10, breakLength: Infinity })}`);
const res = node.positionConfig.getOutDataProp(this, msg, props[i], dNow);
if (res === null || (typeof res === 'undefined')) {
node.error('Could not evaluate ' + props[i].type + '.' + props[i].value + '. - Maybe settings outdated (open and save again)!');
} else if (res.error) {
node.error('Error on getting additional payload: "' + res.error + '"');
} else {
node.positionConfig.setMessageProp(this, msg, props[i].outType, props[i].outValue, res);
}
// node.debug(`prepOutMsg-${i} msg=${util.inspect(msg, { colors: true, compact: 10, breakLength: Infinity })}`);
}
msg._srcid = node._path || node.id;
msg._ts = dNow.valueOf();
return msg;
};
/**
* get and validate a given interval
*/
node.getIntervalTime = () => {
node.intervalTime = node.positionConfig.getFloatProp(node, null, {
type: node.intervalCountType, value: node.intervalCount, def: 0
});
if (node.intervalTime <= 0) {
throw new Error('Interval wrong!');
} else {
node.intervalText = node.getIntervalText(node.intervalCountMultiplier, node.intervalTime);
if (node.intervalCountMultiplier > 0) {
node.intervalTime = Math.floor(node.intervalTime * node.intervalCountMultiplier);
}
}
};
/**
* start an Intervall
*/
node.doStartInterval = () => {
node.timeOutStartObj = null;
node.doCreateEndTimeout();
clearInterval(node.intervalObj);
node.doSetStatus('green');
node.send(node.prepOutMsg({ _inject_type: 'interval-time-start' }));
node.intervalObj = setInterval(() => {
node.intervalCountCurrent++;
if (node.injType !== node.intervalAmount) {
node.send(node.prepOutMsg({ _inject_type: 'interval-time' }));
} else if (node.intervalCountCurrent < node.intervalCountMax) {
node.send(node.prepOutMsg({ _inject_type: 'interval-amount' }));
}
}, node.intervalTime);
};
/** creates the CRON interval */
node.doCreateCRONSetup = function () {
node.cronJobObj = scheduleTask(node.cronExpr, () => {
node.emit('input', { _inject_type: 'cron' });
node.doSetStatus();
}, {});
node.doSetStatus();
};
/**
* creates the timeout
* @param {string} [reason] - name of the reason
* @returns {object} state or error
*/
node.doCreateStartTimeout = reason => {
// node.debug(`doCreateStartTimeout ${reason} node.timeStartData=${util.inspect(node.timeStartData, { colors: true, compact: 10, breakLength: Infinity })}`);
if (node.injType === tInj.none ||
node.injType === tInj.interval) {
return;
}
node.nextStartTime = null;
node.nextStartTimeAlt = null;
if (node.timeOutStartObj) {
clearTimeout(node.timeOutStartObj);
node.timeOutStartObj = null;
}
delete node.intervalTime;
if (node.injType === tInj.intervalBtwStartEnd) {
node.intervalCountMax = 0;
node.getIntervalTime();
}
if (node.injType === tInj.intervalAmount) {
node.intervalCountMax = node.positionConfig.getFloatProp(node, null, {
type: node.intervalCountType, value: node.intervalCount, def: 0
});
delete node.intervalTime;
}
let fill = 'yellow';
let shape = 'dot';
node.isAltAvailable = false;
node.isAltFirst = false;
let isFixedTime = true;
node.timeStartData.now = new Date();
const startLimit = node.getTimeLimitation(node.timeStartData.now);
if (startLimit.valid) {
// node.debug(`node.timeStartData=${util.inspect(node.timeStartData, { colors: true, compact: 10, breakLength: Infinity })}`);
const nextStartTimeData = node.positionConfig.getTimeProp(node, {}, node.timeStartData);
if (nextStartTimeData.error) {
node.debug('node.nextStartTimeData=' + util.inspect(nextStartTimeData, { colors: true, compact: 10, breakLength: Infinity }));
hlp.handleError(node, nextStartTimeData.error, null, 'could not evaluate time (' + reason +')');
return;
}
node.nextStartTime = nextStartTimeData.value;
isFixedTime = isFixedTime && nextStartTimeData.fix;
if (node.timeStartAltData) {
node.timeStartAltData.now = node.timeStartData.now;
// node.debug(`node.timeStartAltData=${util.inspect(node.timeStartAltData, { colors: true, compact: 10, breakLength: Infinity })}`);
const nextTimeAltData = node.positionConfig.getTimeProp(node, {}, node.timeStartAltData);
if (nextTimeAltData.error) {
isFixedTime = false;
// node.debug('nextTimeAltData=' + util.inspect(nextTimeAltData, { colors: true, compact: 10, breakLength: Infinity }));
hlp.handleError(node, nextTimeAltData.error, null, 'could not evaluate alternate time (' + reason +')');
return;
}
node.nextStartTimeAlt = nextTimeAltData.value;
isFixedTime = isFixedTime && nextTimeAltData.fix;
if (!hlp.isValidDate(node.nextStartTimeAlt)) {
hlp.handleError(this, 'Invalid time format of alternate time "' + node.nextStartTimeAlt + '"', undefined, 'internal error! (' + reason +')');
} else {
node.isAltAvailable = true;
}
}
} else {
node.debug(startLimit.errorStatus);
node.status({
fill: 'red',
shape: 'ring',
text: startLimit.errorStatus
});
return;
}
if (node.nextStartTime) {
if (!hlp.isValidDate(node.nextStartTime)) {
hlp.handleError(this, 'Invalid time format "' + node.nextStartTime + '"', undefined, 'internal error!');
return;
}
let millisec = hlp.getTimeOut(node.timeStartData.now, node.nextStartTime);
if (node.isAltAvailable) {
shape = 'ring';
const millisecAlt = hlp.getTimeOut(node.timeStartData.now, node.nextStartTimeAlt);
if