node-red-contrib-sun-position
Version:
NodeRED nodes to get sun and moon position
1,074 lines (1,037 loc) • 319 kB
HTML
<!DOCTYPE HTML>
<!--
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.
-->
<script type="text/javascript">
/* eslint-env browser es6 */
// Isolate this code
(function() {
'use strict';
/// <reference path="./types/typedefs.js" />
/// <reference lib="./static/htmlglobal.js" />
/** --- Type Defs ---
* @typedef {import("node-red").Red} Red
*/
const cNBC_MODE_OFF = 0;
const cNBC_MODE_MAXIMIZE = 1;
const cNBC_MODE_MINIMIZE = 3;
const cNBC_MODE_SUN_CONTROL = 16;
const cNBC_RULE_EXEC = {
auto: 0,
first:1,
last:2
};
const cNBC_RULE_TYPE_UNTIL = 0;
const cNBC_RULE_TYPE_FROM = 1;
const cNBC_RULE_OP = {
levelAbsolute : 0,
levelMinOversteer : 1, // ⭳❗ minimum (oversteer)
levelMaxOversteer : 2, // ⭱️❗ maximum (oversteer)
slatOversteer : 5,
topicOversteer : 8,
off : 9
};
/**
* get a value
* @param {*} node - the node representation
* @param {string} name - name of the value
* @returns {*} the value of th element
*/
function getVal(node,name) {
const v = $('#node-input-' + name).val();
if (typeof v === 'undefined') {
return node[name];
}
return v;
}
/**
* get the min and maximum blind positions
* @param {*} node - the node representation
* @returns {object} object with property __min__ and __max__
*/
function getMinMaxBlindPos(node) {
const max = parseFloat(getVal(node, 'blindOpenPos')) || 0;
const min = parseFloat(getVal(node, 'blindClosedPos')) || 0;
return { min:Math.min(min, max), max:Math.max(min, max) };
}
/** @type {Red} */
// @ts-ignore
RED.nodes.registerType('blind-control', {
category: 'time and astro',
color: '#FFCC66',
icon: 'blind-white.svg',
inputs: 1,
outputs: 2,
defaults: {
name: {
value: '',
required: false
},
topic: {
value: '',
required: false
},
addIdType: {
value: 'none'
},
addId: {
value: '',
// @ts-ignore
validate: RED.validators.typedInput('addIdType')
},
positionConfig: {
value: '',
type: 'position-config',
required: true
},
autoTrigger: { value: true },
autoTriggerTime: {
value: 3600000,
validate(v) {
const n = parseFloat(v);
return (typeof v === 'undefined') ||
// @ts-ignore
(RED.validators.number()(v) &&
(n >= 60000) && (n < 0x7FFFFFFF));
}
},
startDelayTime: {
value: 10000,
validate(v) {
const n = parseFloat(v);
return (typeof v === 'undefined') ||
// @ts-ignore
(RED.validators.number()(v) &&
(n >= 0) && (n < 0x7FFFFFFF));
},
required: false
},
contextStore: {value: ''},
results: {
value: [
{
p: '',
pt: 'msgTopic',
v: '',
vt: 'topic'
},
{
p: '',
pt: 'msgPayload',
v: '',
vt: 'level'
},
{
p: 'slat',
pt: 'msg',
v: '',
vt: 'slat'
},
{
p: 'blindCtrl',
pt: 'msg',
v: '',
vt: 'ctrlObj'
}
]
},
// #region blind settings
blindIncrement: {
value: 0.01,
required: true,
validate(v) {
const n = parseFloat(v);
const mm = getMinMaxBlindPos(this);
// @ts-ignore
return (RED.validators.number()(v) &&
(n < mm.max) &&
(n > 0));
}
},
blindOpenPos: {
value: 1.00,
required: true,
validate(v) {
const n = parseFloat(v);
// @ts-ignore
return RED.validators.number()(v) &&
(Number.isInteger(n / parseFloat(getVal(this, 'blindIncrement')))) &&
(n !== parseFloat(getVal(this, 'blindClosedPos')));
}
},
blindClosedPos: {
value: 0.00,
required: true,
validate(v) {
const n = parseFloat(v);
// @ts-ignore
return RED.validators.number()(v) &&
(Number.isInteger(n / parseFloat(getVal(this, 'blindIncrement')))) &&
(n !== parseFloat(getVal(this, 'blindOpenPos')));
}
},
blindPosReverse: {
value: false
},
blindPosDefault: {
value: 'open (max)',
// @ts-ignore
validate: RED.validators.typedInput('blindPosDefaultType')
},
blindPosDefaultType: {
value: 'levelFixed'
},
slatPosDefault: {
value: '',
// @ts-ignore
validate: RED.validators.typedInput('slatPosDefaultType')
},
slatPosDefaultType: {
value: 'none'
},
// #endregion blind settings
// #region overwrite settings
overwriteExpire: {
value: '',
required: false,
validate(v) {
const n = parseFloat(v);
return (typeof v === 'undefined') ||
(v === '') ||
// @ts-ignore
(RED.validators.number()(v) && (n >= 200) && (n < 0x7FFFFFFF));
}
},
// #endregion overwrite settings
// #region rule settings
rules: {
value: {}
},
// #endregion rule settings
// #region sun settings
sunControlMode: {
value: '0',
required: true,
validate(v) {
const n = Number(v);
// @ts-ignore
return (RED.validators.number()(v) &&
((n === cNBC_MODE_OFF) || (n === cNBC_MODE_MINIMIZE) || (n === cNBC_MODE_MAXIMIZE) || (n === cNBC_MODE_SUN_CONTROL)));
}
},
sunFloorLengthType: {
value: 'num'
},
sunFloorLength: {
value: '',
required: false,
validate(v) {
const mode = Number(getVal(this, 'sunControlMode'));
return (mode === cNBC_MODE_OFF) ||
(mode === cNBC_MODE_MAXIMIZE) ||
(mode === cNBC_MODE_MINIMIZE) ||
// @ts-ignore
RED.validators.typedInput('sunFloorLengthType')(v);
}
},
sunMinDelta: {
value: '',
required: false,
validate(v) {
const n = parseFloat(v);
const mm = getMinMaxBlindPos(this);
const mode = Number(getVal(this, 'sunControlMode'));
return (mode === cNBC_MODE_OFF) ||
(mode === cNBC_MODE_MAXIMIZE) ||
(mode === cNBC_MODE_MINIMIZE) ||
(v === '') ||
((n <= mm.max) &&
(n >= mm.min) &&
(n >= parseFloat(getVal(this, 'blindIncrement'))) );
}
},
blindPosMin: {
value: 'closed (min)',
validate(v) {
return ((Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) ||
// @ts-ignore
RED.validators.typedInput('blindPosMinType')(v));
}
},
blindPosMinType: {
value: 'levelFixed'
},
blindPosMax: {
value: 'open (max)',
validate(v) {
return ((Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) ||
// @ts-ignore
RED.validators.typedInput('blindPosMaxType')(v));
}
},
blindPosMaxType: {
value: 'levelFixed'
},
blindOpenPosOffset: {
value: 0.00,
required: false,
validate(v) {
if (typeof v === 'undefined' || v === '') return true;
const n = parseFloat(v);
const mm = getMinMaxBlindPos(this);
return n === 0.00 ||
// @ts-ignore
(RED.validators.number()(v) &&
(Number.isInteger(n / parseFloat(getVal(this, 'blindIncrement')))) &&
(n < mm.max) &&
(n > mm.min));
}
},
blindClosedPosOffset: {
value: 0.00,
required: false,
validate(v) {
if (typeof v === 'undefined' || v === '') return true;
const n = parseFloat(v);
const mm = getMinMaxBlindPos(this);
return n === 0.00 ||
// @ts-ignore
(RED.validators.number()(v) &&
(Number.isInteger(n / parseFloat(getVal(this, 'blindIncrement')))) &&
(n < mm.max) &&
(n > mm.min));
}
},
sunSlat: {
value: '',
// @ts-ignore
validate: RED.validators.typedInput('sunSlatType')
},
sunSlatType: {
value: 'none'
},
smoothTime: {
value: '',
validate(v) {
const n = parseFloat(v);
return ((Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) ||
(v === '') ||
// @ts-ignore
(RED.validators.number()(v) && (n > -2147483647) && (n < 0x7FFFFFFF)));
}
},
sunTopic: {
value: '',
required: false
},
// #endregion sun settings
// #region window settings
windowTopType: {
value: 'num'
},
windowTop: {
value: '',
validate(v) {
return (Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) ||
// @ts-ignore
RED.validators.typedInput('blindPosMaxType')(v);
}
},
windowBottomType: {
value: 'num'
},
windowBottom: {
value: '',
validate(v) {
return (Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) ||
// @ts-ignore
RED.validators.typedInput('blindPosMaxType')(v);
}
},
windowSetMode: {
value: 'startEnd'
},
windowAzimuthStartType: {
value: 'numAzimuth'
},
windowAzimuthStart: {
value: '',
validate(v) {
return (Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) ||
getVal(this, 'windowSetMode') !== 'startEnd' ||
// @ts-ignore
RED.validators.typedInput('windowAzimuthStartType')(v);
}
},
windowAzimuthEndType: {
value: 'numAzimuth'
},
windowAzimuthEnd: {
value: '',
validate(v) {
return (Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) ||
getVal(this, 'windowSetMode') !== 'startEnd' ||
// @ts-ignore
RED.validators.typedInput('windowAzimuthEndType')(v);
}
},
windowOrientationType: {
value: 'numAzimuth'
},
windowOrientation: {
value: '',
validate(v) {
return (Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) ||
getVal(this, 'windowSetMode') !== 'orientation' ||
// @ts-ignore
RED.validators.typedInput('windowOrientationType')(v);
}
},
windowOffsetN: {
value: '',
validate(v) {
const n = parseFloat(v);
return (Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) ||
getVal(this, 'windowSetMode') !== 'orientation' ||
v==='' ||
// @ts-ignore
RED.validators.number()(v) && (n >= 0) && (n <= 90);
}
},
windowOffsetP: {
value: '',
validate(v) {
const n = parseFloat(v);
return (Number(getVal(this, 'sunControlMode')) === cNBC_MODE_OFF) ||
getVal(this, 'windowSetMode') !== 'orientation' ||
v==='' ||
// @ts-ignore
RED.validators.number()(v) && (n >= 0) && (n <= 90);
}
},
// #endregion window settings
// #region oversteering settings
oversteers: {
value: {}
},
oversteerTopic: {
value: '',
required: false
},
oversteerValue : { value: ''},
oversteerValueType : { value: ''},
oversteerCompare : { value: ''},
oversteerThreshold : { value: ''},
oversteerThresholdType : { value: ''},
oversteerBlindPos : { value: ''},
oversteerBlindPosType : { value: ''},
oversteer2Value : { value: ''},
oversteer2ValueType : { value: ''},
oversteer2Compare : { value: ''},
oversteer2Threshold : { value: ''},
oversteer2ThresholdType : { value: ''},
oversteer2BlindPos : { value: ''},
oversteer2BlindPosType : { value: ''},
oversteer3Value : { value: ''},
oversteer3ValueType : { value: ''},
oversteer3Compare : { value: ''},
oversteer3Threshold : { value: ''},
oversteer3ThresholdType : { value: ''},
oversteer3BlindPos : { value: ''},
oversteer3BlindPosType : { value: ''},
sunMinAltitude : { value: ''}
// #endregion oversteering settings
},
outputLabels(index) {
if (index > 0) {
return this._('blind-control.label.output' + (index + 1));
}
const types = {
msgPayload : 'msg.payload',
msgTopic : 'msg.topic',
msgValue : 'msg.value'
};
const labels = {
level: 'blind position',
slat: 'slat position',
topic: 'topic',
ctrlObj: 'status object',
str: 'string',
num: 'number',
bool: 'boolean',
flow: 'flow context',
global: 'global context',
bin: 'binary data',
date: 'timestamp',
dateSpecific: 'special timestamp',
entered: 'timestamp',
dateEntered: 'timestamp',
env: 'Environment variable',
jsonata: 'result of jsonata expression',
nodeId: 'additional node ID',
nodeName: this.name,
nodePath: this._path || this.name
};
// if only payload and topic - display payload type
// if only one property - show it's type
// if more than one property (other than payload and topic) - show "x properties" where x is the number of properties.
// this.results will not be an array for legacy inject nodes until they are re-deployed
//
let results = this.results;
if (!results || !Array.isArray(results)) {
results = [
{
p: '',
pt: 'msgTopic',
v: '',
vt: 'topic'
},
{
p: '',
pt: 'msgPayload',
v: '',
vt: 'level'
},
{
p: 'slat',
pt: 'msg',
v: '',
vt: 'slat'
},
{
p: 'blindCtrl',
pt: 'msg',
v: '',
vt: 'ctrlObj'
}];
}
let lab = '';
if (results) {
let cnt = -1;
for (let i=0, l=results.length; i<l; i++) {
if (!results[i].pt.includes('msg')) { continue; }
cnt++;
if (cnt > 0) lab += '\n';
if (cnt === 5) {
lab += ' + ' + (results.length - 4);
break;
}
lab += (types[results[i].pt] ? types[results[i].pt] : (results[i].pt + '.' + results[i].p)) + ': ';
let propType = labels[results[i].vt] ? labels[results[i].vt] : results[i].vt;
if (propType === 'json' || propType === 'object') {
try {
const parsedProp = JSON.parse(results[i].v);
propType = typeof parsedProp;
if (propType === 'object' && Array.isArray(parsedProp)) {
propType = 'Array';
}
} catch (ex) {
propType = 'invalid';
}
}
lab += propType;
}
}
return lab || '?';
},
label() {
if (this.name) {
return this.name;
}
const result = this._('blind-control.label.blind-control');
if (this.topic && (this.topic.length + result.length <= 32)) {
return this.topic + ':' + result;
}
return result;
},
labelStyle() {
return this.name ? 'node_label_italic' : '';
},
paletteLabel() { return this._('blind-control.label.blind-control-palette'); },
oneditprepare() {
// @ts-check
/** resizes a rule container */
function resizeRuleContainer() {
try {
const editorRow = $('.node-input-rule-container-row ol');
const height = editorRow.outerHeight(true);
const rowCount = $('#node-input-rule-container').editableList('length');
if (rowCount > 0) {
// $('#node-input-rule-container').editableList('height', height + 40);
$('#node-input-rule-container').css('min-height', height + 'px');
} else {
// $('#node-input-rule-container').editableList('height', 40);
$('#node-input-rule-container').css('min-height', '40px');
}
} catch (ex) {
console.log(ex); // eslint-disable-line
}
}
/** resizes the rule container */
function resizeRuleContainerCheck(node) {
if (node.dialogAddData.resizeRuleContainerTO) { clearTimeout(node.dialogAddData.resizeRuleContainerTO); }
node.dialogAddData.resizeRuleContainerTO = setTimeout(() => {
delete node.dialogAddData.resizeRuleContainerTO;
$('#node-input-sunControlMode').change();
resizeRuleContainer();
}, 600);
}
setTimeout(() => {
$('.is-to-show-initially').show();
$('.is-to-hide-initially').hide();
}, 300);
$('.rdg-ui-btn').button();
const $nodeConfig = $('#node-input-positionConfig');
const node = this;
const $nodeInputContextStore = $('#node-input-contextStore');
// @ts-ignore
RED.settings.context.stores.forEach(store => {
$nodeInputContextStore.append('<option value="' + store + '"' + (this.contextStore === store ? ' selected' : '') + '>' + store + '</option>');
});
// @ts-ignore
const tabs = RED.tabs.create({
id: 'node-config-blind-control-tabs',
onchange(tab) {
$('#node-config-blind-control-tabs-content').children().hide();
$('#' + tab.id).show();
resizeRuleContainerCheck(node);
},
scrollable: true,
collapsible: true
});
this.sunControlMode = this.sunControlMode || cNBC_MODE_OFF;
if (this.sunControlMode === 2 || this.sunControlMode === '2') {
this.sunControlMode = cNBC_MODE_SUN_CONTROL;
$('#node-input-sunControlMode').val(cNBC_MODE_SUN_CONTROL);
}
node.dialogAddData = { time:{ start:{ min:{}, max:{} }, end:{ min:{}, max:{} } }, timeReg:{} };
const setup = function(node) {
/* global getTypes getSelectFields setupTInput appendOptions addLabel getBackendData bdDateToTime initCheckboxesBlock getCheckboxesStr setTInputValue */
// #region initialize
const types = getTypes(node, () => $nodeConfig.val());
types.LevelEntered = {
value: 'num',
label: node._('node-red-contrib-sun-position/position-config:common.types.levelfree'),
icon: 'red/images/typedInput/09.svg',
validate(v) {
if (!RED.validators.number()(v) || (v === '')) {
return false;
}
const inc = parseFloat(getVal(this, 'blindIncrement'));
if (isNaN(inc)) {
return true;
}
const mm = getMinMaxBlindPos(this);
const n = parseFloat(v);
if (isNaN(n) || (n > mm.max) || (n < mm.min)) {
return false;
}
return Number.isInteger(Number((n / inc).toFixed((inc === inc>> 0 ? 0 : inc.toString().split('.')[1].length || 0) + 2)));
}
};
types.LevelFix = {
value: 'levelFixed',
label: node._('node-red-contrib-sun-position/position-config:common.types.levelfix'),
icon: 'icons/node-red-contrib-sun-position/inputTypeLevel.svg',
options: [{
value: 'open (max)',
label: node._('blind-control.typeOptions.100')
}, {
value: '75%',
label: node._('blind-control.typeOptions.75')
}, {
value: '66%',
label: node._('blind-control.typeOptions.66')
}, {
value: '50%',
label: node._('blind-control.typeOptions.50')
}, {
value: '33%',
label: node._('blind-control.typeOptions.33')
}, {
value: '25%',
label: node._('blind-control.typeOptions.25')
}, {
value: '10%',
label: node._('blind-control.typeOptions.10')
}, {
value: 'closed (min)',
label: node._('blind-control.typeOptions.0')
}]
};
types.LevelND= {
value: 'levelND',
label: node._('node-red-contrib-sun-position/position-config:common.types.levelND'),
icon: 'icons/node-red-contrib-sun-position/inputTypeNone2.svg',
hasValue: false
};
types.SunControlModeType = {
value: 'sunControlMode',
label: node._('node-red-contrib-sun-position/position-config:common.types.sunControlMode','moon phase'),
icon: 'icons/node-red-contrib-sun-position/inputTypeSunControl.svg',
options: [{
value: cNBC_MODE_OFF + '-off',
label: node._('node-red-contrib-sun-position/position-config:common.types.sunControlOff')
}, {
value: cNBC_MODE_MAXIMIZE + '-maximize',
label: node._('node-red-contrib-sun-position/position-config:common.types.sunControlMaximize')
}, {
value: cNBC_MODE_MINIMIZE + '-minimize',
label: node._('node-red-contrib-sun-position/position-config:common.types.sunControlMinimize')
}, {
value: cNBC_MODE_SUN_CONTROL + '-restrict',
label: node._('node-red-contrib-sun-position/position-config:common.types.sunControlRestrict')
}]
};
types.OutputLevel = {
value: 'level',
label: node._('node-red-contrib-sun-position/position-config:common.types.level'),
hasValue: false
};
types.OutputLevelInverse = {
value: 'levelInverse',
label: node._('node-red-contrib-sun-position/position-config:common.types.levelInverse'),
hasValue: false
};
types.OutputSlat = {
value: 'slat',
label: node._('node-red-contrib-sun-position/position-config:common.types.slat'),
hasValue: false
};
types.OutputTopic = {
value: 'topic',
label: node._('node-red-contrib-sun-position/position-config:common.types.topic'),
hasValue: false
};
types.OutputCtrlObj = {
value: 'ctrlObj',
label: node._('node-red-contrib-sun-position/position-config:common.types.ctrlObj'),
hasValue: false
};
types.OutputCtrlObj = {
value: 'ctrlObj',
label: node._('node-red-contrib-sun-position/position-config:common.types.ctrlObj'),
hasValue: false
};
types.OutputCtrlObj = {
value: 'ctrlObj',
label: node._('node-red-contrib-sun-position/position-config:common.types.ctrlObj'),
hasValue: false
};
types.UndefinedTimeFrom = Object.assign({}, types.Undefined, {
label: node._('node-red-contrib-sun-position/position-config:common.types.undefinedTimeFrom')
});
types.UndefinedTimeUntil = Object.assign({}, types.Undefined, {
label: node._('node-red-contrib-sun-position/position-config:common.types.undefinedTimeUntil')
});
const selFields = getSelectFields();
/**
* save rules
* @param {*} node - the node representation
* @param {string} name - the node representation
* @param {*} element - the node representation
* @param {function} validate - ovalidation function of the input
* @param {number} defMultiplier - default multiplier (in ms)
* @param {number} minMultiplier - minimum multiplier (in ms)
*/
function setMultiplier(node, name, element, validate, defMultiplier, minMultiplier) {
const c = Number(node[name]);
if (node[name] !== '' && !isNaN(c) && c !== 0) {
let r = minMultiplier || 1000;
if (c % 86400000 === 0) {
r = 86400000; // Days
} else if (c % 3600000 === 0) {
r = 3600000; // Hours
} else if (c % 60000 === 0) {
r = 60000; // Minutes
}
$('#blind-control-' + name).val(c/r);
$('#blind-control-' + name + '-multiplier').val(r);
} else {
$('#blind-control-' + name).val('');
$('#blind-control-' + name + '-multiplier').val(defMultiplier || 1000);
}
$('#blind-control-' + name).on('change focus focusout', () => {
const el = $('#blind-control-' + name);
let val = el.val();
const mp = $('#blind-control-' + name + '-multiplier').val();
if (isNaN(val) || val === '' || val === 0) {
val = '';
$('node-input-' + name).val('');
} else {
val = val * mp;
$('#node-input-' + name).val(val);
}
if (typeof validate === 'function') {
if (validate(val)) {
el.removeClass( 'input-error' );
} else {
el.addClass( 'input-error' );
}
}
});
$('#blind-control-' + name + '-multiplier').change( () => {
$('#blind-control-' + name).change();
});
$('#blind-control-' + name).change();
}
/**
* get the default level
* @param {string} type - the type value
* @param {string} value - value
* @param {string} def - the default value
* @returns {string} the default value for the level
*/
function getDefLevel(type, value, def) {
if (value) {
return value;
}
if (!type || type === types.LevelFix.value) {
return def;
}
return def;
}
// #endregion initialize
// #region blind
setMultiplier(node,'overwriteExpire', v => {
const n = parseFloat(v);
return (v === '') || (RED.validators.number()(v) && (n >= 200) && (n < 0x7FFFFFFF));
}, 3600000, 1000);
setMultiplier(node,'autoTriggerTime', v => {
const n = parseFloat(v);
return (RED.validators.number()(v) && (n >= 60000) && (n < 0x7FFFFFFF));
}, 3600000, 60000);
setMultiplier(node,'startDelayTime', v => {
const n = parseFloat(v);
return (RED.validators.number()(v) && (n >= 0) && (n < 0x7FFFFFFF));
}, 600000, 0);
setupTInput(node, {
typeProp: 'blindPosDefaultType',
valueProp: 'blindPosDefault',
width: 'calc(100% - 110px)',
defaultType: types.LevelFix.value,
defaultValue: getDefLevel(node.blindPosDefaultType, node.blindPosDefault, 'open (max)'),
types: [
types.LevelFix,
types.LevelEntered,
'env',
'flow',
'global']
});
setupTInput(node, {
typeProp: 'slatPosDefaultType',
valueProp: 'slatPosDefault',
width: 'calc(100% - 110px)',
defaultType: types.Undefined.value,
defaultValue: '',
types: [
types.Undefined,
'str',
'num',
'bool',
types.MsgPayload,
types.MsgValue,
'msg',
'flow',
'global',
'json',
'bin',
'env',
'jsonata',
types.randomNumber,
types.randmNumCachedDay,
types.randmNumCachedWeek
]
});
setupTInput(node, {
typeProp: 'sunFloorLengthType',
valueProp: 'sunFloorLength',
width: 'calc(100% - 110px)',
defaultType: 'num',
defaultValue: '0',
types: ['num', 'msg', 'flow', 'global', 'env']
});
setupTInput(node, {
typeProp: 'windowTopType',
valueProp: 'windowTop',
width: 'calc(100% - 110px)',
defaultType: 'num',
defaultValue: '',
types: ['num', types.MsgValue, types.MsgPayloadByTopic, 'msg', 'flow', 'global', 'env']
});
setupTInput(node, {
typeProp: 'windowBottomType',
valueProp: 'windowBottom',
width: 'calc(100% - 110px)',
defaultType: 'num',
defaultValue: '',
types: ['num', types.MsgValue, types.MsgPayloadByTopic, 'msg', 'flow', 'global', 'env']
});
$('#node-input-windowSetMode').on('change focus focusout', () => {
$('#node-input-sunControlMode').change();
});
if (node.windowSetMode !== 'orientation' && node.windowSetMode !== 'startEnd') {
node.windowSetMode = 'startEnd';
$('#node-input-windowSetMode').val('startEnd');
}
setupTInput(node, {
typeProp: 'windowAzimuthStartType',
valueProp: 'windowAzimuthStart',
width: 'calc(100% - 110px)',
defaultType: types.numAzimuth.value,
defaultValue: '',
types: [types.numAzimuth, types.numAnglePreDef, types.MsgValue, types.MsgPayloadByTopic, 'msg', 'flow', 'global', 'env']
});
setupTInput(node, {
typeProp: 'windowAzimuthEndType',
valueProp: 'windowAzimuthEnd',
width: 'calc(100% - 110px)',
defaultType: types.numAzimuth.value,
defaultValue: '',
types: [types.numAzimuth, types.numAnglePreDef, types.MsgValue, types.MsgPayloadByTopic, 'msg', 'flow', 'global', 'env']
});
setupTInput(node, {
typeProp: 'windowOrientationType',
valueProp: 'windowOrientation',
width: 'calc(100% - 110px)',
defaultType: types.numAzimuth.value,
defaultValue: '',
types: [types.numAzimuth, types.numAnglePreDef, types.MsgValue, types.MsgPayloadByTopic, 'msg', 'flow', 'global', 'env']
});
setupTInput(node, {
typeProp: 'addIdType',
valueProp: 'addId',
width: 'calc(100% - 110px)',
defaultType: node.addIdType || (node.addId ? 'str' : types.Undefined.value),
defaultValue: '',
types: [types.Undefined, 'str', 'flow', 'global', 'env'] // types.MsgPayload, types.MsgTopic, types.MsgPayloadByTopic, types.MsgValue, 'msg',
});
// #endregion blind
// #region rules
const ruleVersion = 4;
/**
* get a time in millisecond as string of the format hh:mm, hh:mm:ss or hh:mm:ss.mss
* @param {number} s - milisecond
* @returns {string} time as string
*/
function msToTime(s) {
// Pad to 2 or 3 digits, default is 2
const pad = (n, z = 2) => ('00' + n).slice(-z);
const sec = (s%6e4)/1000|0;
const msec = s%1000;
return pad(s/3.6e6|0) + ':' + pad((s%3.6e6)/6e4 | 0) + ((msec >0) ? (':' + pad(sec) +'.' + pad(msec, 3) ) : ((sec>0) ? ':' + pad(sec) : ''));
}
/**
* get the time offset text
*/
function _getOffsetText(node, t,v,m) {
let result = '';
if (t && t !== 'none') {
if (t === 'num') {
const osmillis = v * m;
if (osmillis>0) {
result += ' + ' + msToTime(osmillis);
} else {
result += ' - ' +msToTime(osmillis * -1);
}
} else {
result += ' offset <var>' + RED.nodes.getType('position-config').getRDGNodeValLbl(node, t, v) + '</var>';
// result += ' * ' + data.multiplier/1000 + 's';
}
}
return result;
}
/**
* get a formated date limit
*/
function getDateShort(value) {
if (value) {
const val = value.split('-');
if (val.length > 2) {
return val[1] + '-' + val[2];
} else if (val.length === 2) {
return val[0] + '-' + val[1];
}
}
return '';
}
/**
* sets a property of an object
* @param {object} obj - object to set property
* @param {string|array} arr - property of object to set 'sub1.sub2.propA' or ['sub1', 'sub2', 'propA']
* @param {*} val - value to set
* @returns {object} description of the rule
*/
function addProps(obj, arr, val) {
if (typeof arr === 'string')
arr = arr.split('.');
obj[arr[0]] = obj[arr[0]] || {};
const tmpObj = obj[arr[0]];
if (arr.length > 1) {
arr.shift();
addProps(tmpObj, arr, val); // eslint-disable-line no-unused-vars
} else {
obj[arr[0]] = val;
}
return obj;
}
/**
* get number of operand for a comparision opeator
* @param {object} selFields - fields object
* @param {string} operator - value of comperator selection
* @returns {number} description of the rule
*/
function getOperandCount(selFields, operator) {
operator = operator || selFields.comparator[0].id;
const fields = selFields.comparator;
const fieldsLength = fields.length;
for (let index = 0; index < fieldsLength; index++) {
const el = fields[index];
if (el.id === operator) {
return el.operandCount;
}
}
return 1;
}
/**
* get a description of the rule rules
* @param {object} data - a rule description
* @returns {string} description of the rule
*/
function getRuleDescription(node, selFields, data, enh) {
let result = '';
const length = (enh) ? 60 : 30;
try {
if (typeof data.isValid === 'undefined' || typeof data.version === 'undefined' || (!data.isValid && !data.validationError && typeof data.validation === 'undefined')) {
result += '<div>';
result += node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.outDated');
result += '</div>';
} else if (!data.isValid) {
result += '<div>';
result += node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.isInValid');
result += '</div><div>';
result += node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.ruleInValidText');
result += data.validationError;
result += '</div>';
if (data.validation && Object.keys(data.validation).length > 0) {
const lines = JSON.stringify(data.validation,
(key, value) => {
if (value === true || (Array.isArray(value) && value.length === 0) || (typeof value === 'object' && Object.keys(value).length === 0))
return undefined;
if (value === false)
return node._('node-red-contrib-sun-position/position-config:ruleCtrl.label.ruleInValidElement');
return value;
}, '\t').split('\n');
lines.splice(0,1); // remove first line