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.4.0 to 0.4.4-beta

nodes/icons/inputTypeMoonPhase.png

168

blind_control.md

@@ -11,7 +11,7 @@ # Blind Controller

* [Blind Controller](#blind-controller)
* [Blind Controller](#Blind-Controller)
* [blind-control](#blind-control)
* [Table of contents](#table-of-contents)
* [The node](#the-node)
* [Node settings](#node-settings)
* [Table of contents](#Table-of-contents)
* [The node](#The-node)
* [Node settings](#Node-settings)
* [general settings](#general-settings)

@@ -22,18 +22,20 @@ * [blind settings](#blind-settings)

* [sun settings](#sun-settings)
* [maximize sunlight (Winter)](#maximize-sunlight-winter)
* [restrict sunlight (Summer)](#restrict-sunlight-summer)
* [maximize sunlight (Winter)](#maximize-sunlight-Winter)
* [restrict sunlight (Summer)](#restrict-sunlight-Summer)
* [sun position settings](#sun-position-settings)
* [Node Input](#node-input)
* [Node Output](#node-output)
* [Node Status](#node-status)
* [Node Input](#Node-Input)
* [Node Output](#Node-Output)
* [Node Status](#Node-Status)
* [rules](#rules)
* [rules with blind position minimum or maximum](#rules-with-blind-position-minimum-or-maximum)
* [rules example](#rules-example)
* [Samples](#samples)
* [testing rules and overrides](#testing-rules-and-overrides)
* [Additional FAQ](#additional-faq)
* [Why is there no multi blind controller?](#why-is-there-no-multi-blind-controller)
* [How to define a Temperature Overwrite?](#how-to-define-a-temperature-overwrite)
* [How do I achieve that when opening a window the blind opens?](#how-do-i-achieve-that-when-opening-a-window-the-blind-opens)
* [Other](#other)
* [Samples](#Samples)
* [Example 1](#Example-1)
* [Example 2](#Example-2)
* [Example 3 - testing rules and overrides](#Example-3---testing-rules-and-overrides)
* [Example 4 - usage of message properties](#Example-4---usage-of-message-properties)
* [Additional FAQ](#Additional-FAQ)
* [Why is there no multi blind controller? (FAQ)](#Why-is-there-no-multi-blind-controller-FAQ)
* [How to define a Temperature Overwrite? (FAQ)](#How-to-define-a-Temperature-Overwrite-FAQ)
* [How do I achieve that when opening a window the blind opens? (FAQ)](#How-do-I-achieve-that-when-opening-a-window-the-blind-opens-FAQ)
* [Other](#Other)

@@ -48,6 +50,6 @@ ### The node

* The node is designed to determine the degree of opening, so the higher number is open and the lower number is closed.
* There is a `blindCtrl.blind.levelInverse` output who will have the inverse value if needed.
* There is a `blindCtrl.levelInverse` output who will have the inverse value if needed.
* The levels can be integer or floating point numbers. It is depending on the configuration of Open-level, closed-level and increment. This means the node can be configured to have 100=open, 0=closed with an increment of 1, but also 1=open, 0=closed with an increment of 0.01.
* This node is very flexible where information comes to the blind controller. So these do not always have to be part of the msg object, but can also come from environment variables or contexts.
* This node has the possibility for manual override with different priority. THis can be used to differentiate between manual operation, fire alarm, window knob handle, etc. ...
* This node has the possibility for manual override with different priority. This can be used to differentiate between manual operation, fire alarm, window knob handle, etc. ...
* Various conditions for the absolute position are selectable, which unfortunately does not make the configuration easy. An example is if in the morning the blind should open depending on the position of the sun, but at the earliest at a defined time, which must be different between week and weekend.

@@ -81,5 +83,5 @@

![blind-control-settings-4](https://user-images.githubusercontent.com/12692680/57134463-8d0dc780-6da6-11e9-9019-1cb4ed85e756.png)
![blind-control-settings-4](https://user-images.githubusercontent.com/12692680/59666684-9d8ecb80-91b5-11e9-8ea6-ddbe2293988b.png)
* If a rule applies, the blind position defined by the rule will be used.
* If a rule with a absolute blind position applies, the blind position defined by the rule will be used.
* sun control will then not be active

@@ -102,14 +104,12 @@ * If a rule has a condition, the rule only applies if the condition matches.

![blind-control-settings-6](https://user-images.githubusercontent.com/12692680/57134466-8da65e00-6da6-11e9-84d2-425ca0be5e3d.png)
#### sun settings
Sun control is only active if no override and no rule applies!
Sun control is only active if no override and no rule with an absolute blind position applies!
![blind-control-settings-6](https://user-images.githubusercontent.com/12692680/57134466-8da65e00-6da6-11e9-84d2-425ca0be5e3d.png)
If sun-control checkbox is not checked, the defined **default position** will be used.
![blind-control-settings-7](https://user-images.githubusercontent.com/12692680/57134469-8da65e00-6da6-11e9-979b-ca875da064d7.png)
The sun control (maximize or restrict sunlight) is only active, if no other rule (with an absolute blind position) or override matches.
The sun control (maximize or restrict sunlight) is only active, if no other rule or override matches.
* Requirements that should be valid with a higher priority should be set up as rules.

@@ -120,14 +120,17 @@ * Example: if the blind should set to a level if a temperature threshold exceeded, this could be setup as rule

In this mode if no rule or override matches:
![image](https://user-images.githubusercontent.com/12692680/59666961-34f41e80-91b6-11e9-8ad0-958a650565d1.png)
* If no other rule or override matches
* If the sun is *not* in the window the blind will set to defined **min position**. (oversteer will be ignored)
* If the sun is in the window
* If any oversteer data are setup and oversteer conditions are fulfilled the blind will set to the defined oversteer blind position.
* otherwise the blind level is set to defined **max position**.
In this mode if no override and no rule with an absolute blind position matches:
* If the sun is *not* in the window the blind will set to defined **min position**. (oversteer will be ignored)
* If the sun is in the window
* If any oversteer data are setup and oversteer conditions are fulfilled the blind will set to the defined oversteer blind position.
* otherwise the blind level is set to defined **max position**.
##### restrict sunlight (Summer)
In this mode if no rule or override matches, the node calculates the appropriate blind position to restrict the amount of direct sunlight entering the room.
![image](https://user-images.githubusercontent.com/12692680/59667118-797fba00-91b6-11e9-9b7f-5837c7fd4a29.png)
In this mode if no override and no rule with an absolute blind position matches, the node calculates the appropriate blind position to restrict the amount of direct sunlight entering the room.
This calculation includes:

@@ -172,2 +175,3 @@

* **oversteer2**, **oversteer2 Operator**, **Threshold** equal to **oversteer**, but an additional oversteer possibility. Lower priority than **oversteer**
* **oversteer3**, **oversteer3 Operator**, **Threshold** equal to **oversteer** and **oversteer2**, but an additional oversteer possibility. Lower priority than **oversteer2**

@@ -182,2 +186,3 @@ ### Node Input

* or when the `msg.topic` contains `prio` or `alarm` and the value of `msg.payload` is a valid numeric value
* a higher number is a higher priority. So prio 1 is the lowest priority
* **position** an incoming message with a numeric property of `msg.blindPosition`, `msg.position`, `msg.level`, `msg.blindLevel`, `msg.payload.blindPosition`, `msg.payload.position`, `msg.payload.level`, `msg.payload.blindLevel` or where the `msg.topic` contains `manual` or `levelOverwrite` and the value of `msg.payload` is a numeric value will override any of rule/sun/.. based level of the blind.

@@ -193,2 +198,3 @@ * If an override is already active a new message changes the blind level if the **priority** of the existing override allows this.

* A `boolean` value `true` is considered as numeric `1`
* a higher number is a higher priority. So prio 1 is the lowest priority
* **expire** (optional) Enables to define an override as automatically expiring. As default value for overrides of priority `0` the value in the settings is be used. Overrides with a priority higher than `0` will not expire by default.

@@ -244,5 +250,5 @@ * A message property `msg.expire` or `msg.payload.expire`

* `blindCtrl.reason.description` a text, describe the reason for the blind position
* `blindCtrl.level` - the new blind level (numeric value) - equal to `msg.payload` of the first output message.
* `blindCtrl.levelInverse` - if `blindCtrl.blind.overwrite.active` is true, the value of `blindCtrl.levelInverse` will be equal to the value of `blindCtrl.level`, otherwise it will be the inverse to `blindCtrl.level`. This means if `blindCtrl.level` indicates how much the blind is open, then `blindCtrl.levelInverse` indicates how much the blind is closed. So if `blindCtrl.level` is equal to **min position**, `blindCtrl.levelInverse` will be **max position**.
* `blindCtrl.blind` a object containing all blind settings, only the most interesting ones are explained here
* `blindCtrl.blind.level` - the new blind level (numeric value) - equal to `msg.payload` of the first output message.
* `blindCtrl.blind.levelInverse` - if `blindCtrl.blind.overwrite.active` is true, the value of `blindCtrl.blind.levelInverse` will be equal to the value of `blindCtrl.blind.level`, otherwise it will be the inverse to `blindCtrl.blind.level`. This means if `blindCtrl.blind.level` indicates how much the blind is open, then `blindCtrl.blind.levelInverse` indicates how much the blind is closed. So if `blindCtrl.blind.level` is equal to **min position**, `blindCtrl.blind.levelInverse` will be **max position**.
* `blindCtrl.blind.overwrite`

@@ -257,3 +263,3 @@ * `blindCtrl.blind.overwrite.active` - is `true` when overwrite is active, otherwise `false`

* `blindCtrl.rule.id` - number of the rule who applies (is `-1` if no rule has applied)
* `blindCtrl.rule.level` - the blind level defined by the rule [exists only if a rule applies]
* `blindCtrl.rule.level` - the blind level defined by the rule if level type is __absolute__, otherwise the defined default blind position [exists only if a rule applies]
* `blindCtrl.rule.conditional` - `true` if the rule has a condition [exists only if a rule applies]

@@ -263,12 +269,6 @@ * `blindCtrl.rule.timeLimited` - `true` if the rule has a time [exists only if a rule applies]

* `blindCtrl.rule.time` - __object__ with additional data about the time [exists only if `blindCtrl.rule.timeLimited` is true] - good for debugging purpose
* `blindCtrl.rule.mimimum` - exists only if a minimum rule exists
* `blindCtrl.rule.mimimum.id` - number of the minimum rule who applies
* `blindCtrl.rule.mimimum.level` - the minimum blind level defined by the rule
* `blindCtrl.rule.mimimum.conditon` - __object__ with additional data about the condition
* `blindCtrl.rule.mimimum.time` - __object__ with additional data about the time
* `blindCtrl.rule.maximum` - exists only if a maximum rule exists
* `blindCtrl.rule.maximum.id` - number of the maximum rule who applies
* `blindCtrl.rule.maximum.level` - the maximum blind level defined by the rule
* `blindCtrl.rule.maximum.conditon` - __object__ with additional data about the condition
* `blindCtrl.rule.maximum.time` - __object__ with additional data about the time
* `blindCtrl.rule.hasMinimum` - is __true__ if to the level of the rule an additional __minimum__ rule will be active, otherwise __false__
* `blindCtrl.rule.levelMinimum` - exists only if `blindCtrl.rule.hasMinimum` is __true__ and then contains then the blind level defined by the rule
* `blindCtrl.rule.hasMaximum` - is __true__ if to the level of the rule an additional __maximum__ rule will be active, otherwise __false__
* `blindCtrl.rule.levelMinimum` - exists only if `blindCtrl.rule.hasMaximum` is __true__ and then contains then the blind level defined by the rule
* `blindCtrl.sunPosition` - calculated sub-position data - exists only if sun position is calculated

@@ -293,11 +293,12 @@ * `blindCtrl.sunPosition.InWindow` - `true` if sun is in window, otherwise `false`

The rules are not easy to understand. For simplicity, in the following description, the type of the blind position is absolute.
The rules are not easy to understand.
There are basically 4 generic types of rules (absolute blind position):
There are basically 4 generic types of rules:
* absolute rule
* a rule with no time and no condition will be absolute. If such a rule is the first rule, no other rule will be active, no sun control will be done
* such rules not really makes sense
* no time and no condition rule
* a rule with no time and no condition will be always active if checked.
* such rules are evaluated in the order of time __until__ and time __from__ rules
* a rule with a condition - conditional rule
* a rule with a condition will only be active if the condition matches, otherwise the rule will be ignored
* rules with only a condition are evaluated in the order of time __until__ and time __from__ rules
* a rule with a given time - time rule

@@ -307,25 +308,32 @@ * time rules differ again in 2 ways

* rules will be active from Midnight __until__ the given time
* the first matching __until__ rule of will be considered
* the first matching __until__ rule with a time later than the current time will be selected
* __from__ time rules
* rules will be active __from__ given time to Midnight
* the last matching __from__ rule of will be considered
* the last matching __from__ rule with a time earlier than the current time will be considered
* __from__ rules only considered if no __until__ rule was selected
* a rule with a condition and a given time
* these type of rules are a combination. The rules will only be considered if the condition matches and then it act as a normal time rule. Otherwise it will be ignored.
a typically ruleset could be setup in a way like:
the blind level of a rule could have 3 options:
* 1 __until__ absolute time (e.g. early morning 6:00) blind will be closed
* 2 __until__ sun rise time (e.g. sunrise) blind will be closed
* __absolute__
* If a rule with a blind level of type absolute matches, the level would be set to the level defined in the rule. No sun control will be active as long this rule is active.
* __minimum (oversteer)__ / __maximum (oversteer)__
* a rule of this type makes a limitation of the blind position possible. Other __absolute__ rules still will be considered.
* Please aware that a rule of this type will not considered if
* it is of type __until__ and comes after an active absolute __until__ time rule
* it is of type __from__ and comes before an active absolute __from__ time rule
If there is a time where no rules matches, then as blind position the default defined blind position will be used or if sun control is active the blind position calculated by the sun will be used.
a typically easy ruleset could be setup in a way like:
* 1 __until__ absolute time (e.g. early morning 6:00) blind will be closed (absolute)
* 2 __until__ sun rise time (e.g. sunrise) blind will be closed (absolute)
* The previous absolute __until__ rule (rule 1) will consider that the blind is closed, even if the sun rise time (this rule 2) is earlier than the time of rule 1.
* 3 __from__ sun set time (e.g. sunset) blind will be closed
* 4 __from__ absolute time (e.g. late night 22:00) blind will be closed
* 3 __from__ sun set time (e.g. sunset) blind will be closed (absolute)
* 4 __from__ absolute time (e.g. late night 22:00) blind will be closed (absolute)
* This rule 4 will be consider that the blind is closed, even if the sun set time (rule 3) is later than this absolute time.
* In the time between the rule 2 (last __until__) and the rule 3 (first __from__ rule) the blind will set to the default position which is setup normally to open. Only in this time the blind position can be controlled by sun.
This simple example could be enhanced with additional conditional rules.
### rules with blind position minimum or maximum
### rules example

@@ -361,17 +369,21 @@

### Example 1
Example for a time-control to open blind on civilDawn, but not before 6 o'clock and close blind on civilDusk, but not later than 23:00 o clock:
![blind-control-example-1](https://user-images.githubusercontent.com/12692680/57134447-867f5000-6da6-11e9-81dc-24fbf58dcd15.png)
![blind-control-example-1](https://user-images.githubusercontent.com/12692680/59666579-61f40180-91b5-11e9-9d28-78e4060fb77d.png)
```json
[{"id":"c4660958.801288","type":"blind-control","z":"d7bd7fb6.a0c13","name":"","topic":"","positionConfig":"650223e.daba8dc","outputs":"1","blindIncrement":0.01,"blindOpenPos":1,"blindClosedPos":0,"blindPosReverse":false,"blindPosDefault":"open (max)","blindPosDefaultType":"levelFixed","overwriteExpire":"7200000","rules":[{"timeType":"entered","timeValue":"6:00","timeOp":"0","timeOpText":"until","levelType":"levelFixed","levelValue":"closed (min)","offsetType":"none","offsetValue":"","multiplier":"1","validOperandAType":"none","validOperandAValue":"","validOperator":"true","validOperatorText":"is true","validOperandBType":"num","validOperandBValue":""},{"timeType":"pdsTime","timeValue":"civilDawn","timeOp":"0","timeOpText":"until","levelType":"levelFixed","levelValue":"closed (min)","offsetType":"none","offsetValue":"","multiplier":"1","validOperandAType":"none","validOperandAValue":"","validOperator":"true","validOperatorText":"is true","validOperandBType":"num","validOperandBValue":""},{"timeType":"pdsTime","timeValue":"civilDusk","timeOp":"1","timeOpText":"from","levelType":"levelFixed","levelValue":"closed (min)","offsetType":"none","offsetValue":"","multiplier":"1","validOperandAType":"none","validOperandAValue":"","validOperator":"true","validOperatorText":"is true","validOperandBType":"num","validOperandBValue":""},{"timeType":"entered","timeValue":"23:00","timeOp":"1","timeOpText":"from","levelType":"levelFixed","levelValue":"closed (min)","offsetType":"none","offsetValue":"","multiplier":"1","validOperandAType":"none","validOperandAValue":"","validOperator":"true","validOperatorText":"is true","validOperandBType":"num","validOperandBValue":""}],"sunControlMode":"0","sunFloorLength":"","sunMinAltitude":"","blindPosMin":"closed (min)","blindPosMinType":"levelFixed","blindPosMax":"open (max)","blindPosMaxType":"levelFixed","smoothTime":"","windowTop":"","windowBottom":"","windowAzimuthStart":"","windowAzimuthEnd":"","oversteerValue":"","oversteerValueType":"none","oversteerCompare":"gte","oversteerThreshold":"","oversteerThresholdType":"num","oversteerBlindPos":"open (max)","oversteerBlindPosType":"levelFixed","x":415,"y":3135,"wires":[["51d5763f.b879e8"]]},{"id":"8a367fde.6639","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"","payloadType":"date","repeat":"600","crontab":"","once":false,"onceDelay":0.1,"x":210,"y":3135,"wires":[["c4660958.801288"]]},{"id":"51d5763f.b879e8","type":"debug","z":"d7bd7fb6.a0c13","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":630,"y":3135,"wires":[]},{"id":"c0eac267.02eb8","type":"comment","z":"d7bd7fb6.a0c13","name":"Example 1:","info":"","x":150,"y":3090,"wires":[]},{"id":"650223e.daba8dc","type":"position-config","z":"","name":"","isValide":"true","longitude":"0","latitude":"0","angleType":"deg","timezoneOffset":-60}]
[{"id":"d5d4fd69.fb24d","type":"blind-control","z":"d7bd7fb6.a0c13","name":"","topic":"","positionConfig":"","outputs":"1","blindIncrement":0.01,"blindOpenPos":1,"blindClosedPos":0,"blindPosReverse":false,"blindPosDefault":"open (max)","blindPosDefaultType":"levelFixed","overwriteExpire":"7200000","rules":[{"index":0,"timeValue":"6:00","timeType":"entered","timeOp":"0","timeOpText":"↥ bis","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"num"},{"index":1,"timeValue":"civilDawn","timeType":"pdsTime","timeOp":"0","timeOpText":"↥ bis","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"num"},{"index":2,"timeValue":"civilDusk","timeType":"pdsTime","timeOp":"1","timeOpText":"↧ von","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"num"},{"index":3,"timeValue":"23:00","timeType":"entered","timeOp":"1","timeOpText":"↧ von","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"num"}],"sunControlMode":"0","sunFloorLength":"","sunMinAltitude":"","sunMinDelta":"","blindPosMin":"closed (min)","blindPosMinType":"levelFixed","blindPosMax":"open (max)","blindPosMaxType":"levelFixed","smoothTime":"","windowTop":"","windowBottom":"","windowAzimuthStart":"","windowAzimuthEnd":"","oversteerValue":"","oversteerValueType":"none","oversteerCompare":"gte","oversteerThreshold":"","oversteerThresholdType":"num","oversteerBlindPos":"open (max)","oversteerBlindPosType":"levelFixed","oversteer2Value":"","oversteer2ValueType":"none","oversteer2Compare":"gte","oversteer2Threshold":"","oversteer2ThresholdType":"num","oversteer2BlindPos":"open (max)","oversteer2BlindPosType":"levelFixed","oversteer3Value":"","oversteer3ValueType":"none","oversteer3Compare":"gte","oversteer3Threshold":"","oversteer3ThresholdType":"num","oversteer3BlindPos":"open (max)","oversteer3BlindPosType":"levelFixed","x":1285,"y":3135,"wires":[["ca3c04f7.fd0c88"]]},{"id":"640b0526.42224c","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"","payloadType":"date","repeat":"600","crontab":"","once":false,"onceDelay":0.1,"x":1080,"y":3135,"wires":[["d5d4fd69.fb24d"]]},{"id":"ca3c04f7.fd0c88","type":"debug","z":"d7bd7fb6.a0c13","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1500,"y":3135,"wires":[]},{"id":"87589a53.5840b8","type":"comment","z":"d7bd7fb6.a0c13","name":"Example 1:","info":"","x":1020,"y":3090,"wires":[]}]
```
### Example 2
similar example with additional different times for weekend:
```json
[{"id":"42177438.2b8acc","type":"blind-control","z":"d7bd7fb6.a0c13","name":"","topic":"","positionConfig":"d2b0ae0f.90e0c","outputs":1,"blindIncrement":"0.01","blindOpenPos":"1","blindClosedPos":0,"blindPosDefault":"open (max)","blindPosDefaultType":"levelFixed","overwriteExpire":"14400000","rules":[{"timeValue":"6:30","timeType":"entered","timeOp":"0","timeOpText":"bis","levelValue":"closed (min)","levelType":"levelFixed","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist wahr","validOperandBValue":"","validOperandBType":"str"},{"timeValue":"7:25","timeType":"entered","timeOp":"0","timeOpText":"bis","levelValue":"closed (min)","levelType":"levelFixed","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"dayInfo.today.isWeekendOrHoliday","validOperandAType":"global","validOperator":"true","validOperatorText":"ist wahr","validOperandBValue":"","validOperandBType":"str"},{"timeValue":"civilDawn","timeType":"pdsTime","timeOp":"0","timeOpText":"bis","levelValue":"closed (min)","levelType":"levelFixed","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist wahr","validOperandBValue":"","validOperandBType":"str"},{"timeValue":"civilDusk","timeType":"pdsTime","timeOp":"1","timeOpText":"von","levelValue":"closed (min)","levelType":"levelFixed","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist wahr","validOperandBValue":"","validOperandBType":"str"},{"timeValue":"22:35","timeType":"entered","timeOp":"1","timeOpText":"von","levelValue":"closed (min)","levelType":"levelFixed","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"dayInfo.tomorrow.isWeekendOrHoliday","validOperandAType":"global","validOperator":"false","validOperatorText":"ist falsch","validOperandBValue":"","validOperandBType":"str"},{"timeValue":"23:15","timeType":"entered","timeOp":"1","timeOpText":"von","levelValue":"closed (min)","levelType":"levelFixed","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist wahr","validOperandBValue":"","validOperandBType":"str"}],"sunControlMode":"0","sunFloorLength":"","sunMinAltitude":"","blindPosMin":"closed (min)","blindPosMinType":"levelFixed","blindPosMax":"open (max)","blindPosMaxType":"levelFixed","smoothTime":"","windowTop":"","windowBottom":"","windowAzimuthStart":"","windowAzimuthEnd":"","oversteerValue":"","oversteerValueType":"none","oversteerCompare":"gte","oversteerThreshold":"50","oversteerThresholdType":"num","oversteerBlindPos":"open (max)","oversteerBlindPosType":"levelFixed","x":415,"y":3300,"wires":[["98c3eea0.6fb4b"]]},{"id":"8ad0c281.16c04","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"","payloadType":"date","repeat":"600","crontab":"","once":false,"onceDelay":0.1,"x":210,"y":3300,"wires":[["42177438.2b8acc"]]},{"id":"98c3eea0.6fb4b","type":"debug","z":"d7bd7fb6.a0c13","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":630,"y":3300,"wires":[]},{"id":"75d33e8e.0d3f6","type":"comment","z":"d7bd7fb6.a0c13","name":"Example 2:","info":"","x":150,"y":3255,"wires":[]},{"id":"49b7db3c.bbdb44","type":"change","z":"d7bd7fb6.a0c13","name":"dayInfo.today.isWeekendOrHoliday","rules":[{"t":"set","p":"dayInfo.today.isWeekendOrHoliday","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":605,"y":3360,"wires":[[]]},{"id":"cfb7c3ce.8f545","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":335,"y":3360,"wires":[["49b7db3c.bbdb44"]]},{"id":"a434dc44.c4da2","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":335,"y":3405,"wires":[["49b7db3c.bbdb44"]]},{"id":"c96a8cf.1e6ea7","type":"change","z":"d7bd7fb6.a0c13","name":"dayInfo.tomorrow.isWeekendOrHoliday","rules":[{"t":"set","p":"dayInfo.tomorrow.isWeekendOrHoliday","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":615,"y":3450,"wires":[[]]},{"id":"d23986f5.6512c8","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":335,"y":3450,"wires":[["c96a8cf.1e6ea7"]]},{"id":"5b57030c.952d9c","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":335,"y":3495,"wires":[["c96a8cf.1e6ea7"]]},{"id":"d2b0ae0f.90e0c","type":"position-config","z":"","name":"Kap-Halbinsel","isValide":"true","longitude":"0","latitude":"0","angleType":"deg","timezoneOffset":"1"}]
[{"id":"537e927c.e2f0ec","type":"blind-control","z":"d7bd7fb6.a0c13","name":"","topic":"","positionConfig":"","outputs":1,"blindIncrement":"0.01","blindOpenPos":"1","blindClosedPos":0,"blindPosDefault":"open (max)","blindPosDefaultType":"levelFixed","overwriteExpire":"14400000","rules":[{"index":0,"timeValue":"6:30","timeType":"entered","timeOp":"0","timeOpText":"↥ bis","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"str"},{"index":1,"timeValue":"7:25","timeType":"entered","timeOp":"0","timeOpText":"↥ bis","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"dayInfo.today.isWeekendOrHoliday","validOperandAType":"global","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"str"},{"index":2,"timeValue":"civilDawn","timeType":"pdsTime","timeOp":"0","timeOpText":"↥ bis","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"str"},{"index":3,"timeValue":"civilDusk","timeType":"pdsTime","timeOp":"1","timeOpText":"↧ von","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"str"},{"index":4,"timeValue":"22:35","timeType":"entered","timeOp":"1","timeOpText":"↧ von","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"dayInfo.tomorrow.isWeekendOrHoliday","validOperandAType":"global","validOperator":"false","validOperatorText":"ist false","validOperandBValue":"","validOperandBType":"str"},{"index":5,"timeValue":"23:15","timeType":"entered","timeOp":"1","timeOpText":"↧ von","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"str"}],"sunControlMode":"0","sunFloorLength":"","sunMinAltitude":"","sunMinDelta":"","blindPosMin":"closed (min)","blindPosMinType":"levelFixed","blindPosMax":"open (max)","blindPosMaxType":"levelFixed","smoothTime":"","windowTop":"","windowBottom":"","windowAzimuthStart":"","windowAzimuthEnd":"","oversteerValue":"","oversteerValueType":"none","oversteerCompare":"gte","oversteerThreshold":"50","oversteerThresholdType":"num","oversteerBlindPos":"open (max)","oversteerBlindPosType":"levelFixed","oversteer2Value":"","oversteer2ValueType":"none","oversteer2Compare":"gte","oversteer2Threshold":"","oversteer2ThresholdType":"num","oversteer2BlindPos":"open (max)","oversteer2BlindPosType":"levelFixed","oversteer3Value":"","oversteer3ValueType":"none","oversteer3Compare":"gte","oversteer3Threshold":"","oversteer3ThresholdType":"num","oversteer3BlindPos":"open (max)","oversteer3BlindPosType":"levelFixed","x":1330,"y":3300,"wires":[["9ad4681f.f62258"]]},{"id":"c662cbbb.15b448","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"","payloadType":"date","repeat":"600","crontab":"","once":false,"onceDelay":0.1,"x":1125,"y":3300,"wires":[["537e927c.e2f0ec"]]},{"id":"9ad4681f.f62258","type":"debug","z":"d7bd7fb6.a0c13","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1545,"y":3300,"wires":[]},{"id":"512ec61b.df68b8","type":"comment","z":"d7bd7fb6.a0c13","name":"Example 2:","info":"","x":1065,"y":3255,"wires":[]},{"id":"bff818e8.45ade8","type":"change","z":"d7bd7fb6.a0c13","name":"dayInfo.today.isWeekendOrHoliday","rules":[{"t":"set","p":"dayInfo.today.isWeekendOrHoliday","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1520,"y":3360,"wires":[[]]},{"id":"2b669f8c.3505d","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":1250,"y":3360,"wires":[["bff818e8.45ade8"]]},{"id":"b8a07258.c4de8","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":1250,"y":3405,"wires":[["bff818e8.45ade8"]]},{"id":"d4bee48f.1bc238","type":"change","z":"d7bd7fb6.a0c13","name":"dayInfo.tomorrow.isWeekendOrHoliday","rules":[{"t":"set","p":"dayInfo.tomorrow.isWeekendOrHoliday","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1530,"y":3450,"wires":[[]]},{"id":"3bd6b8b0.5459c8","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":1250,"y":3450,"wires":[["d4bee48f.1bc238"]]},{"id":"b1b1c2be.02208","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":1250,"y":3495,"wires":[["d4bee48f.1bc238"]]}]
```
### testing rules and overrides
### Example 3 - testing rules and overrides

@@ -385,8 +397,18 @@ The following example could be used for testing rules, overrides and sun-position. The function node with the start/stop inject will set `msg.ts` which will be used by the node as the current time. This time is increased every 2 seconds by 30 minutes (can be setup at the beginning of the function node). The given number by the inject will be used as the start hour for that time.

```json
[{"id":"133a6b14.ea2b85","type":"function","z":"d7bd7fb6.a0c13","name":"test","func":"\nconst minutesEachLoop = 30;\nconst loopCycle = 2; // seconds\nlet timeObj = context.get(\"timeObj\");\n\nif (timeObj && msg.topic.includes('stop')) {\n clearInterval(timeObj);\n context.set(\"timeObj\", null);\n let d = new Date(context.get(\"date\"));\n node.status({fill:\"red\",shape:\"ring\",text:\"stopped - \" + d.toLocaleTimeString()});\n return null;\n} else if (!timeObj && msg.topic.includes('start')) {\n context.set(\"message\", msg);\n let d = new Date();\n let num = Number(msg.payload) || 0;\n d.setHours(num);\n d.setMinutes(0);\n context.set(\"date\", d.getTime());\n msg.lts = d.toLocaleTimeString();\n msg.ts = d.getTime();\n node.log(\"sending \" + d.toLocaleTimeString() + ' ####################################### payload='+msg.payload+' topic='+msg.topic);\n node.send(msg);\n\n let timeObj = setInterval(function(){\n let msg = context.get(\"message\");\n let d = new Date(context.get(\"date\"));\n //d.setHours(d.getHours()+1);\n d.setMinutes(d.getMinutes() + minutesEachLoop)\n context.set(\"date\", d.getTime());\n msg.lts = d.toLocaleTimeString();\n msg.ts = d.getTime();\n node.status({fill:\"green\",shape:\"dot\",text:\"run - \" + d.toLocaleTimeString()});\n node.log(\"sending \" + d.toLocaleTimeString() + ' ####################################### payload='+msg.payload+' topic='+msg.topic);\n node.send(msg);\n\t}, (1000 * loopCycle));\n context.set(\"timeObj\", timeObj);\n node.status({fill:\"green\",shape:\"ring\",text:\"start - \" + d.toLocaleTimeString()});\n return null;\n}\n\nlet d = new Date(context.get(\"date\"));\nd.setMinutes(d.getMinutes() + 1)\n//d.setHours(d.getHours()+1);\nmsg.lts = d.toLocaleTimeString();\nmsg.ts = d.getTime();\nnode.status({fill:\"yellow\",shape:\"dot\",text:\"interposed - \" + d.toLocaleTimeString()});\nnode.log(\"sending interposed msg \" + d.toLocaleTimeString() + ' ####################################### payload='+msg.payload+' topic='+msg.topic);\nnode.send(msg);\nreturn null;","outputs":1,"noerr":0,"x":500,"y":2580,"wires":[["2f830c53.455b44"]]},{"id":"66a0fed2.d1c17","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"start/stop","payload":"0","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":225,"y":2535,"wires":[["133a6b14.ea2b85"]]},{"id":"36b5bf53.635a","type":"inject","z":"d7bd7fb6.a0c13","name":"reset","topic":"resetOverwrite","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":230,"y":2625,"wires":[["133a6b14.ea2b85"]]},{"id":"26e49680.865ada","type":"inject","z":"d7bd7fb6.a0c13","name":"0%","topic":"levelOverwrite","payload":"0","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":230,"y":2665,"wires":[["133a6b14.ea2b85"]]},{"id":"5d82a89c.499078","type":"inject","z":"d7bd7fb6.a0c13","name":"60%","topic":"levelOverwrite","payload":"0.6","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":230,"y":2710,"wires":[["133a6b14.ea2b85"]]},{"id":"e8ac078f.9bc308","type":"comment","z":"d7bd7fb6.a0c13","name":"manual overrides:","info":"","x":225,"y":2580,"wires":[]},{"id":"32306190.8018ee","type":"inject","z":"d7bd7fb6.a0c13","name":"90%, expire 2,5s","topic":"","payload":"{\"position\":0.9,\"expire\":2500}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":260,"y":2760,"wires":[["133a6b14.ea2b85"]]},{"id":"5c942931.d78168","type":"inject","z":"d7bd7fb6.a0c13","name":"30% Prio 1","topic":"","payload":"{\"position\":0.3,\"prio\":1}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":240,"y":2805,"wires":[["133a6b14.ea2b85"]]},{"id":"d21d3f4d.50916","type":"inject","z":"d7bd7fb6.a0c13","name":"100% prio 1","topic":"","payload":"{\"priority\":1, \"position\":1}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":250,"y":2850,"wires":[["133a6b14.ea2b85"]]},{"id":"2f830c53.455b44","type":"blind-control","z":"d7bd7fb6.a0c13","name":"","topic":"","positionConfig":"d9e9ca6a.952218","outputs":2,"blindIncrement":"0.01","blindOpenPos":"1","blindClosedPos":0,"blindPosDefault":"open (max)","blindPosDefaultType":"levelFixed","overwriteExpire":"7200000","rules":[{"timeValue":"6:30","timeType":"entered","timeOp":"0","timeOpText":"bis","levelValue":"closed (min)","levelType":"levelFixed","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist wahr","validOperandBValue":"","validOperandBType":"str"},{"timeValue":"7:25","timeType":"entered","timeOp":"0","timeOpText":"bis","levelValue":"closed (min)","levelType":"levelFixed","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"dayInfo.today.isWeekendOrHoliday","validOperandAType":"flow","validOperator":"true","validOperatorText":"ist wahr","validOperandBValue":"","validOperandBType":"str"},{"timeValue":"civilDawn","timeType":"pdsTime","timeOp":"0","timeOpText":"bis","levelValue":"closed (min)","levelType":"levelFixed","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist wahr","validOperandBValue":"","validOperandBType":"str"},{"timeValue":"civilDusk","timeType":"pdsTime","timeOp":"1","timeOpText":"von","levelValue":"closed (min)","levelType":"levelFixed","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist wahr","validOperandBValue":"","validOperandBType":"str"},{"timeValue":"22:35","timeType":"entered","timeOp":"1","timeOpText":"von","levelValue":"closed (min)","levelType":"levelFixed","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist wahr","validOperandBValue":"","validOperandBType":"str"},{"timeValue":"23:15","timeType":"entered","timeOp":"1","timeOpText":"von","levelValue":"closed (min)","levelType":"levelFixed","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"dayInfo.tomorrow.isWeekendOrHoliday","validOperandAType":"flow","validOperator":"true","validOperatorText":"ist wahr","validOperandBValue":"","validOperandBType":"str"}],"sunControlMode":"2","sunFloorLength":"0.6","sunMinAltitude":"","blindPosMin":"closed (min)","blindPosMinType":"levelFixed","blindPosMax":"open (max)","blindPosMaxType":"levelFixed","smoothTime":"","windowTop":"1.28","windowBottom":"0","windowAzimuthStart":"70","windowAzimuthEnd":"150","oversteerValue":"","oversteerValueType":"none","oversteerCompare":"gte","oversteerThreshold":"50","oversteerThresholdType":"num","oversteerBlindPos":"open (max)","oversteerBlindPosType":"levelFixed","x":740,"y":2580,"wires":[["12e898d.ca51e67"],["324e3f09.4452"]]},{"id":"12e898d.ca51e67","type":"debug","z":"d7bd7fb6.a0c13","name":"Blind position","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":985,"y":2565,"wires":[]},{"id":"324e3f09.4452","type":"debug","z":"d7bd7fb6.a0c13","name":"Blind status","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":985,"y":2625,"wires":[]},{"id":"4abea741.2fd968","type":"change","z":"d7bd7fb6.a0c13","name":"dayInfo.today.isWeekendOrHoliday","rules":[{"t":"set","p":"dayInfo.today.isWeekendOrHoliday","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":785,"y":2715,"wires":[[]]},{"id":"4548475.5b1feb8","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":515,"y":2715,"wires":[["4abea741.2fd968"]]},{"id":"fb328117.4c438","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":515,"y":2760,"wires":[["4abea741.2fd968"]]},{"id":"db519048.9483b","type":"change","z":"d7bd7fb6.a0c13","name":"dayInfo.tomorrow.isWeekendOrHoliday","rules":[{"t":"set","p":"dayInfo.tomorrow.isWeekendOrHoliday","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":795,"y":2805,"wires":[[]]},{"id":"1230ba43.d37cd6","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":515,"y":2805,"wires":[["db519048.9483b"]]},{"id":"d9e378ce.f2db68","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":515,"y":2850,"wires":[["db519048.9483b"]]},{"id":"d9e9ca6a.952218","type":"position-config","z":"","name":"Entenhausen","isValide":"true","longitude":"0","latitude":"0","angleType":"deg","timezoneOffset":"1"}]
[{"id":"96a001f5.c84d4","type":"function","z":"d7bd7fb6.a0c13","name":"30min 1sec","func":"\nconst minutesEachLoop = 30; // minutes to add\nconst loopCycle = 1; // seconds delay\nlet timeObj = context.get(\"timeObj\");\n\nif (timeObj && msg.topic.includes('stop')) {\n clearInterval(timeObj);\n context.set(\"timeObj\", null);\n context.set(\"orgtopic\", null);\n let d = new Date(context.get(\"date\"));\n node.log(\"STOP \" + d.toLocaleTimeString() + ' ####################################### payload='+msg.payload+' topic='+msg.topic);\n node.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ' + d.toISOString());\n node.status({fill:\"red\",shape:\"ring\",text:\"stopped - \" + d.toLocaleTimeString()});\n return null;\n} else if (!timeObj && msg.topic.includes('start')) {\n context.set(\"message\", msg);\n context.set(\"orgtopic\", msg.topic);\n let d = new Date();\n let num = Number(msg.payload);\n if (isNaN(num) && num < 24) {\n d.setHours(num);\n d.setMinutes(0);\n } else {\n let dt = new Date(msg.payload);\n if (dt instanceof Date && !isNaN(dt)) {\n d = dt;\n } else {\n d.setHours(0);\n d.setMinutes(0);\n }\n }\n context.set(\"date\", d.getTime());\n msg.tsISO = d.toISOString();\n msg.ts = d.getTime();\n msg.topic += ' ' + d.toLocaleTimeString();\n node.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>');\n node.log(\"START \" + d.toLocaleTimeString() + ' ####################################### payload='+msg.payload+' topic='+msg.topic);\n node.send(msg);\n\n let timeObj = setInterval(function(){\n let msg = context.get(\"message\");\n let topic = context.get(\"orgtopic\");\n let d = new Date(context.get(\"date\"));\n //d.setHours(d.getHours()+1);\n d.setMinutes(d.getMinutes() + minutesEachLoop)\n context.set(\"date\", d.getTime());\n msg.tsISO = d.toISOString();\n msg.ts = d.getTime();\n msg.topic = topic + ' ' + d.toLocaleTimeString();\n node.status({fill:\"green\",shape:\"dot\",text:\"run - \" + d.toLocaleTimeString()});\n node.log(\"sending \" + d.toLocaleTimeString() + ' ####################################### payload='+msg.payload+' topic='+msg.topic);\n node.send(msg);\n\t}, (1000 * loopCycle));\n context.set(\"timeObj\", timeObj);\n node.status({fill:\"green\",shape:\"ring\",text:\"start - \" + d.toLocaleTimeString()});\n return null;\n}\n\nlet d = new Date(context.get(\"date\"));\nif (!(d instanceof Date) || isNaN(d)) {\n d = new Date();\n}\nd.setMinutes(d.getMinutes() + 1)\n//d.setHours(d.getHours()+1);\nmsg.tsISO = d.toISOString();\nmsg.ts = d.getTime();\nmsg.topic += ' ' + d.toLocaleTimeString();\nnode.status({fill:\"yellow\",shape:\"dot\",text:\"interposed - \" + d.toLocaleTimeString()});\nnode.log(\"sending interposed msg \" + d.toLocaleTimeString() + ' ####################################### payload='+msg.payload+' topic='+msg.topic);\nnode.send(msg);\nreturn null;","outputs":1,"noerr":0,"x":505,"y":3780,"wires":[["933cb191.3b401"]]},{"id":"b81d29b5.2b5008","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"start/stop","payload":"0","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":210,"y":3735,"wires":[["96a001f5.c84d4"]]},{"id":"1709d674.52c8aa","type":"inject","z":"d7bd7fb6.a0c13","name":"reset","topic":"resetOverwrite","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":185,"y":3825,"wires":[["96a001f5.c84d4"]]},{"id":"1655f5d.e288c0a","type":"inject","z":"d7bd7fb6.a0c13","name":"0%","topic":"levelOverwrite","payload":"0","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":185,"y":3865,"wires":[["96a001f5.c84d4"]]},{"id":"f67eb1c0.2de9","type":"inject","z":"d7bd7fb6.a0c13","name":"60%","topic":"levelOverwrite","payload":"0.6","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":185,"y":3910,"wires":[["96a001f5.c84d4"]]},{"id":"89a24cfd.ea115","type":"comment","z":"d7bd7fb6.a0c13","name":"manual overrides:","info":"","x":180,"y":3780,"wires":[]},{"id":"e7489d31.4176e","type":"inject","z":"d7bd7fb6.a0c13","name":"90%, expire 2,5s","topic":"","payload":"{\"position\":0.9,\"expire\":2500}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":215,"y":3960,"wires":[["96a001f5.c84d4"]]},{"id":"ea03d309.e8674","type":"inject","z":"d7bd7fb6.a0c13","name":"30% Prio 1","topic":"","payload":"{\"position\":0.3,\"prio\":1}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":195,"y":4005,"wires":[["96a001f5.c84d4"]]},{"id":"b1587178.751b4","type":"inject","z":"d7bd7fb6.a0c13","name":"100% prio 1","topic":"","payload":"{\"priority\":1, \"position\":1}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":205,"y":4050,"wires":[["96a001f5.c84d4"]]},{"id":"933cb191.3b401","type":"blind-control","z":"d7bd7fb6.a0c13","name":"","topic":"","positionConfig":"","outputs":1,"blindIncrement":"0.01","blindOpenPos":"1","blindClosedPos":0,"blindPosDefault":"open (max)","blindPosDefaultType":"levelFixed","overwriteExpire":"7200000","rules":[{"index":0,"timeValue":"5:30","timeType":"entered","timeOp":"0","timeOpText":"↥ bis","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"str","timeLimited":true,"conditional":false},{"index":1,"timeValue":"7:25","timeType":"entered","timeOp":"0","timeOpText":"↥ bis","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"dayInfo.today.isWeekendOrHoliday","validOperandAType":"flow","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"str","timeLimited":true,"conditional":true},{"index":2,"timeValue":"civilDawn","timeType":"pdsTime","timeOp":"0","timeOpText":"↥ bis","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"str","timeLimited":true,"conditional":false},{"index":3,"timeValue":"civilDusk","timeType":"pdsTime","timeOp":"1","timeOpText":"↧ von","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"str","timeLimited":true,"conditional":false},{"index":4,"timeValue":"21:25","timeType":"entered","timeOp":"1","timeOpText":"↧ von","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"dayInfo.tomorrow.isWeekendOrHoliday","validOperandAType":"flow","validOperator":"false","validOperatorText":"ist false","validOperandBValue":"","validOperandBType":"str","timeLimited":true,"conditional":true},{"index":5,"timeValue":"23:15","timeType":"entered","timeOp":"1","timeOpText":"↧ von","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"str","timeLimited":true,"conditional":false}],"sunControlMode":"2","sunFloorLength":"0.6","sunMinAltitude":"","sunMinDelta":"","blindPosMin":"closed (min)","blindPosMinType":"levelFixed","blindPosMax":"open (max)","blindPosMaxType":"levelFixed","smoothTime":"","windowTop":"1.28","windowBottom":"0","windowAzimuthStart":"70","windowAzimuthEnd":"150","oversteerValue":"","oversteerValueType":"none","oversteerCompare":"gte","oversteerThreshold":"50","oversteerThresholdType":"num","oversteerBlindPos":"open (max)","oversteerBlindPosType":"levelFixed","oversteer2Value":"","oversteer2ValueType":"none","oversteer2Compare":"gte","oversteer2Threshold":"","oversteer2ThresholdType":"num","oversteer2BlindPos":"open (max)","oversteer2BlindPosType":"levelFixed","oversteer3Value":"","oversteer3ValueType":"none","oversteer3Compare":"gte","oversteer3Threshold":"","oversteer3ThresholdType":"num","oversteer3BlindPos":"open (max)","oversteer3BlindPosType":"levelFixed","x":725,"y":3780,"wires":[["d21dbfe1.20be7"]]},{"id":"d21dbfe1.20be7","type":"debug","z":"d7bd7fb6.a0c13","name":"Blind position","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":970,"y":3780,"wires":[]},{"id":"e92dc8a9.45fdd8","type":"change","z":"d7bd7fb6.a0c13","name":"dayInfo.today.isWeekendOrHoliday","rules":[{"t":"set","p":"dayInfo.today.isWeekendOrHoliday","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":770,"y":3915,"wires":[[]]},{"id":"a6dab1a.c4cb75","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":500,"y":3915,"wires":[["e92dc8a9.45fdd8"]]},{"id":"b5feeaad.c7f288","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":500,"y":3960,"wires":[["e92dc8a9.45fdd8"]]},{"id":"23f7b339.a5631c","type":"change","z":"d7bd7fb6.a0c13","name":"dayInfo.tomorrow.isWeekendOrHoliday","rules":[{"t":"set","p":"dayInfo.tomorrow.isWeekendOrHoliday","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":780,"y":4005,"wires":[[]]},{"id":"3aa6975a.2464e8","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":500,"y":4005,"wires":[["23f7b339.a5631c"]]},{"id":"ae69e36e.c32d7","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":500,"y":4050,"wires":[["23f7b339.a5631c"]]},{"id":"d511d5a0.064048","type":"comment","z":"d7bd7fb6.a0c13","name":"Example 3:","info":"","x":150,"y":3690,"wires":[]}]
```
### Example 4 - usage of message properties
As an enhancement of example 3 the example 4. It shows how message instead of contexts could be used and how rule based overrides can be used.
![blind-control-example-4](https://user-images.githubusercontent.com/12692680/60344573-24faec80-99b7-11e9-8b5c-772c7828047d.png)
```json
[{"id":"9f40dfd7.71532","type":"function","z":"d7bd7fb6.a0c13","name":"30min 0.5sec","func":"\nconst minutesEachLoop = 30; // minutes to add\nconst loopCycle = 0.5; // seconds delay\nlet timeObj = context.get(\"timeObj\");\n\nif (timeObj && msg.topic.includes('stop')) {\n clearInterval(timeObj);\n context.set(\"timeObj\", null);\n context.set(\"orgtopic\", null);\n let d = new Date(context.get(\"date\"));\n node.log(\"STOP \" + d.toLocaleTimeString() + ' ####################################### payload='+msg.payload+' topic='+msg.topic);\n node.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ' + d.toISOString());\n node.status({fill:\"red\",shape:\"ring\",text:\"stopped - \" + d.toLocaleTimeString()});\n return null;\n} else if (!timeObj && msg.topic.includes('start')) {\n context.set(\"message\", msg);\n context.set(\"orgtopic\", msg.topic);\n let d = new Date();\n let num = Number(msg.payload);\n if (isNaN(num) && num < 24) {\n d.setHours(num);\n d.setMinutes(0);\n } else {\n let dt = new Date(msg.payload);\n if (dt instanceof Date && !isNaN(dt)) {\n d = dt;\n } else {\n d.setHours(0);\n d.setMinutes(0);\n }\n }\n context.set(\"date\", d.getTime());\n msg.tsISO = d.toISOString();\n msg.ts = d.getTime();\n msg.topic += ' ' + d.toLocaleTimeString();\n node.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>');\n node.log(\"START \" + d.toLocaleTimeString() + ' ####################################### payload='+msg.payload+' topic='+msg.topic);\n node.send(msg);\n\n let timeObj = setInterval(function(){\n let msg = context.get(\"message\");\n let topic = context.get(\"orgtopic\");\n let d = new Date(context.get(\"date\"));\n //d.setHours(d.getHours()+1);\n d.setMinutes(d.getMinutes() + minutesEachLoop)\n context.set(\"date\", d.getTime());\n msg.tsISO = d.toISOString();\n msg.ts = d.getTime();\n msg.topic = topic + ' ' + d.toLocaleTimeString();\n node.status({fill:\"green\",shape:\"dot\",text:\"run - \" + d.toLocaleTimeString()});\n node.log(\"sending \" + d.toLocaleTimeString() + ' ####################################### payload='+msg.payload+' topic='+msg.topic);\n node.send(msg);\n\t}, (1000 * loopCycle));\n context.set(\"timeObj\", timeObj);\n node.status({fill:\"green\",shape:\"ring\",text:\"start - \" + d.toLocaleTimeString()});\n return null;\n}\n\nlet d = new Date(context.get(\"date\"));\nif (!(d instanceof Date) || isNaN(d)) {\n d = new Date();\n}\nd.setMinutes(d.getMinutes() + 1)\n//d.setHours(d.getHours()+1);\nmsg.tsISO = d.toISOString();\nmsg.ts = d.getTime();\nmsg.topic += ' ' + d.toLocaleTimeString();\nnode.status({fill:\"yellow\",shape:\"dot\",text:\"interposed - \" + d.toLocaleTimeString()});\nnode.log(\"sending interposed msg \" + d.toLocaleTimeString() + ' ####################################### payload='+msg.payload+' topic='+msg.topic);\nnode.send(msg);\nreturn null;","outputs":1,"noerr":0,"x":490,"y":4275,"wires":[["617f833.f37ee7c"]]},{"id":"94f315b8.1051f8","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"start/stop","payload":"0","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":195,"y":4230,"wires":[["9f40dfd7.71532"]]},{"id":"546da2c0.686c9c","type":"inject","z":"d7bd7fb6.a0c13","name":"reset","topic":"resetOverwrite","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":170,"y":4320,"wires":[["9f40dfd7.71532"]]},{"id":"d81e3831.29c088","type":"inject","z":"d7bd7fb6.a0c13","name":"0%","topic":"levelOverwrite","payload":"0","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":170,"y":4360,"wires":[["9f40dfd7.71532"]]},{"id":"450db993.b7bc68","type":"inject","z":"d7bd7fb6.a0c13","name":"60%","topic":"levelOverwrite","payload":"0.6","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":170,"y":4405,"wires":[["9f40dfd7.71532"]]},{"id":"cacc7caf.f49aa","type":"comment","z":"d7bd7fb6.a0c13","name":"manual overrides:","info":"","x":165,"y":4275,"wires":[]},{"id":"973d6032.62885","type":"inject","z":"d7bd7fb6.a0c13","name":"90%, expire 2,5s","topic":"","payload":"{\"position\":0.9,\"expire\":2500}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":200,"y":4455,"wires":[["9f40dfd7.71532"]]},{"id":"b86adfcb.7f703","type":"inject","z":"d7bd7fb6.a0c13","name":"30% Prio 1","topic":"","payload":"{\"position\":0.3,\"prio\":1}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":180,"y":4500,"wires":[["9f40dfd7.71532"]]},{"id":"a701e311.0594f","type":"inject","z":"d7bd7fb6.a0c13","name":"100% prio 1","topic":"","payload":"{\"priority\":1, \"position\":1}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":190,"y":4545,"wires":[["9f40dfd7.71532"]]},{"id":"617f833.f37ee7c","type":"blind-control","z":"d7bd7fb6.a0c13","name":"","topic":"","positionConfig":"","outputs":1,"blindIncrement":"0.01","blindOpenPos":"1","blindClosedPos":0,"blindPosDefault":"open (max)","blindPosDefaultType":"levelFixed","overwriteExpire":"7200000","rules":[{"index":0,"timeValue":"","timeType":"none","timeOp":"0","timeOpText":"↥ bis","levelValue":"25%","levelType":"levelFixed","levelOp":"1","levelOpText":"⭳ Minimum (übersteuernd)","offsetValue":"","offsetType":"none","multiplier":"60000","validOperandAValue":"windowOpen","validOperandAType":"msg","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"windowOpen","validOperandBType":"str"},{"index":1,"timeValue":"5:30","timeType":"entered","timeOp":"0","timeOpText":"↥ bis","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"str"},{"index":2,"timeValue":"7:25","timeType":"entered","timeOp":"0","timeOpText":"↥ bis","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"dayInfo.today.isWeekendOrHoliday","validOperandAType":"flow","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"str"},{"index":3,"timeValue":"civilDawn","timeType":"pdsTime","timeOp":"0","timeOpText":"↥ bis","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"str"},{"index":4,"timeValue":"","timeType":"none","timeOp":"0","timeOpText":"↥ bis","levelValue":"50%","levelType":"levelFixed","levelOp":"2","levelOpText":"⭱️ Maximum (übersteuernd)","offsetValue":"","offsetType":"none","multiplier":"60000","validOperandAValue":"raining","validOperandAType":"msg","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"num"},{"index":5,"timeValue":"civilDusk","timeType":"pdsTime","timeOp":"1","timeOpText":"↧ von","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"str"},{"index":6,"timeValue":"21:25","timeType":"entered","timeOp":"1","timeOpText":"↧ von","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"dayInfo.tomorrow.isWeekendOrHoliday","validOperandAType":"flow","validOperator":"false","validOperatorText":"ist false","validOperandBValue":"","validOperandBType":"str"},{"index":7,"timeValue":"23:15","timeType":"entered","timeOp":"1","timeOpText":"↧ von","levelValue":"closed (min)","levelType":"levelFixed","levelOp":"0","levelOpText":"↕ Absolut","offsetValue":"","offsetType":"none","multiplier":"1","validOperandAValue":"","validOperandAType":"none","validOperator":"true","validOperatorText":"ist true","validOperandBValue":"","validOperandBType":"str"}],"sunControlMode":"2","sunFloorLength":"0.6","sunMinAltitude":"","sunMinDelta":"","blindPosMin":"closed (min)","blindPosMinType":"levelFixed","blindPosMax":"open (max)","blindPosMaxType":"levelFixed","smoothTime":"","windowTop":"1.28","windowBottom":"0","windowAzimuthStart":"70","windowAzimuthEnd":"150","oversteerValue":"","oversteerValueType":"none","oversteerCompare":"gte","oversteerThreshold":"50","oversteerThresholdType":"num","oversteerBlindPos":"open (max)","oversteerBlindPosType":"levelFixed","oversteer2Value":"","oversteer2ValueType":"none","oversteer2Compare":"gte","oversteer2Threshold":"","oversteer2ThresholdType":"num","oversteer2BlindPos":"open (max)","oversteer2BlindPosType":"levelFixed","oversteer3Value":"","oversteer3ValueType":"none","oversteer3Compare":"gte","oversteer3Threshold":"","oversteer3ThresholdType":"num","oversteer3BlindPos":"open (max)","oversteer3BlindPosType":"levelFixed","x":710,"y":4275,"wires":[["e5ad9e89.91fca"]]},{"id":"e5ad9e89.91fca","type":"debug","z":"d7bd7fb6.a0c13","name":"Blind position","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":955,"y":4275,"wires":[]},{"id":"73586d22.519c84","type":"change","z":"d7bd7fb6.a0c13","name":"dayInfo.today.isWeekendOrHoliday","rules":[{"t":"set","p":"dayInfo.today.isWeekendOrHoliday","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":785,"y":4350,"wires":[[]]},{"id":"5b45699c.065d38","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":515,"y":4350,"wires":[["73586d22.519c84"]]},{"id":"2aa7fe06.4b3542","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":515,"y":4395,"wires":[["73586d22.519c84"]]},{"id":"672e362e.320458","type":"change","z":"d7bd7fb6.a0c13","name":"dayInfo.tomorrow.isWeekendOrHoliday","rules":[{"t":"set","p":"dayInfo.tomorrow.isWeekendOrHoliday","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":795,"y":4440,"wires":[[]]},{"id":"184622ac.87528d","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":515,"y":4440,"wires":[["672e362e.320458"]]},{"id":"c499f778.1f1e88","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":515,"y":4485,"wires":[["672e362e.320458"]]},{"id":"34c6b5d0.fd55ea","type":"comment","z":"d7bd7fb6.a0c13","name":"Example 4:","info":"","x":135,"y":4185,"wires":[]},{"id":"48e19f72.346a4","type":"change","z":"d7bd7fb6.a0c13","name":"","rules":[{"t":"set","p":"windowOpen","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":830,"y":4530,"wires":[["b0c3eec.cf4ac1"]]},{"id":"17b8a4d8.27a83b","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"window open","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":545,"y":4530,"wires":[["48e19f72.346a4"]]},{"id":"309856e0.51ed4a","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"window closed","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":565,"y":4575,"wires":[["48e19f72.346a4"]]},{"id":"4e797cd3.64c104","type":"link in","z":"d7bd7fb6.a0c13","name":"do_refreshBlind_state","links":["b0c3eec.cf4ac1","dba49bc7.8be158"],"x":390,"y":4200,"wires":[["9f40dfd7.71532"]]},{"id":"b0c3eec.cf4ac1","type":"link out","z":"d7bd7fb6.a0c13","name":"trigger_refreshBlind_state","links":["4e797cd3.64c104"],"x":1035,"y":4530,"wires":[]},{"id":"a9d231d9.a4d08","type":"change","z":"d7bd7fb6.a0c13","name":"","rules":[{"t":"set","p":"raining","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":810,"y":4620,"wires":[["b0c3eec.cf4ac1"]]},{"id":"1390bf63.8a3161","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"is raining","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":535,"y":4620,"wires":[["a9d231d9.a4d08"]]},{"id":"6e1753a9.3ce90c","type":"inject","z":"d7bd7fb6.a0c13","name":"","topic":"is raining","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":545,"y":4665,"wires":[["a9d231d9.a4d08"]]}]
```
## Additional FAQ
### Why is there no multi blind controller?
### Why is there no multi blind controller? (FAQ)

@@ -397,7 +419,7 @@ The approach is that there is a node for a blind. To reduce the setup overhead it is possible to create a sub-flow with the node per side of the house and thus only have to make the settings once. Settings such as overrides or times can still be configured individually, for example via sub-flow environment variables.

### How to define a Temperature Overwrite?
### How to define a Temperature Overwrite? (FAQ)
To Overwrite the sun-.calculation by a temperature threshold can be archived by using a conditional rule.
### How do I achieve that when opening a window the blind opens?
### How do I achieve that when opening a window the blind opens? (FAQ)

@@ -404,0 +426,0 @@ This can be archived in different ways:

@@ -0,1 +1,40 @@

#### 0.4.4-beta: Maintenance Release
- fixed critical problem in sun - calculating Julian cycle which leads into wrong sun times if it is calculated at certain times
- [#37](https://github.com/rdmtc/node-red-contrib-sun-position/issues/37)
- [#39](https://github.com/rdmtc/node-red-contrib-sun-position/issues/39)
- fixed problems on RedMatic call of getBackendData
- tooltip with resolved time on typeInput now also available in RedMatic
- i18N reworked
- for compares using mathematical signs for equal, unequal, greater than, less than
- added more Unicode symbols
- added direct moon phase output
- added compare to current moon phase for conditions
- blind control
- fixed error on empty ruleset
- add clear button for rules
- add sort button in UI editor
- reworked minimum/maximum blind position settings by rule, enhanced in documentation
- addresses [#36](https://github.com/rdmtc/node-red-contrib-sun-position/issues/36)
- enhanced documentation
- fix [#31](https://github.com/rdmtc/node-red-contrib-sun-position/issues/31)
- fix [#33](https://github.com/rdmtc/node-red-contrib-sun-position/issues/33)
- added possibility for inverted open / close settings [#40](https://github.com/rdmtc/node-red-contrib-sun-position/issues/40)
- changed lot of internal
- cleanup procedures
- fixed problems with test function [#32](https://github.com/rdmtc/node-red-contrib-sun-position/issues/32)
- optimized code to ES6 possibilities
- changed esLint rules to be more restrictive
- fixed a lot of wrong usages of arrow function for data validation
- optimized access to backend services
- changed lot of UTC time compare problems [#34](https://github.com/rdmtc/node-red-contrib-sun-position/issues/34)
#### 0.4.3: Maintenance Release
- Version was unpublished due to critical Bugs
#### 0.4.1 / 0.4.2: Maintenance Release
- Version was unpublished due to critical Bugs
#### 0.4.0: Maintenance Release

@@ -9,3 +48,3 @@

- added minimum delta for changes by sun control
- added minimum and maximum blind position settings by rule (only in enhanced mode)
- added minimum and maximum blind position settings by rule
- enhanced documentation

@@ -12,0 +51,0 @@ - start of changelog

@@ -13,3 +13,3 @@ /********************************************

* @param {*} node the node data
* @param {*} level the level to check
* @param {number} level the level to check
* @returns {boolean} true if the level is valid, otherwise false

@@ -27,12 +27,21 @@ */

}
if (level < node.blindData.levelClosed) {
node.warn(`Position: "${level}" < levelClosed ${node.blindData.levelClosed}`);
if (level < node.blindData.levelBottom) {
if (node.tempData.levelReverse) {
node.warn(`Position: "${level}" < open level ${node.blindData.levelBottom}`);
} else {
node.warn(`Position: "${level}" < closed level ${node.blindData.levelBottom}`);
}
return false;
}
if (level > node.blindData.levelOpen) {
node.warn(`Position: "${level}" > levelOpen ${node.blindData.levelOpen}`);
if (level > node.blindData.levelTop) {
if (node.tempData.levelReverse) {
node.warn(`Position: "${level}" > closed level ${node.blindData.levelTop}`);
} else {
node.warn(`Position: "${level}" > open level ${node.blindData.levelTop}`);
}
return false;
}
if (Number.isInteger(node.blindData.levelOpen) &&
Number.isInteger(node.blindData.levelClosed) &&
if (Number.isInteger(node.blindData.levelTop) &&
Number.isInteger(node.blindData.levelBottom) &&
Number.isInteger(node.blindData.increment) &&

@@ -67,7 +76,8 @@ ((level % node.blindData.increment !== 0) ||

}
const dto = new Date(msg.ts);
if (dto !== 'Invalid Date' && !isNaN(dto)) {
const dto = new Date(value);
if (hlp.isValidDate(dto)) {
node.debug(dto.toISOString());
return dto;
}
node.error('Error can not get a valide timestamp from "' + value + '"! Will use current timestamp!');
node.error(`Error can not get a valide timestamp from "${value}"! Will use current timestamp!`);
return new Date();

@@ -83,3 +93,3 @@ }

function posPrcToAbs_(node, levelPercent) {
return posRound_(node, ((node.blindData.levelOpen - node.blindData.levelClosed) * levelPercent) + node.blindData.levelClosed);
return posRound_(node, ((node.blindData.levelTop - node.blindData.levelBottom) * levelPercent) + node.blindData.levelBottom);
}

@@ -90,7 +100,7 @@ /**

* @param {*} levelAbsolute the level absolute
* @return {number} get the level percentage
*/
function posAbsToPrc_(node, levelAbsolute) {
return (levelAbsolute - node.blindData.levelClosed) / (node.blindData.levelOpen - node.blindData.levelClosed);
return (levelAbsolute - node.blindData.levelBottom) / (node.blindData.levelTop - node.blindData.levelBottom);
}
/**

@@ -100,7 +110,19 @@ * get the absolute inverse level

* @param {*} levelAbsolute the level absolute
* @return {number} get the inverse level
*/
function getInversePos_(node, levelAbsolute) {
return posPrcToAbs_(node, 1 - posAbsToPrc_(node, levelAbsolute));
function getInversePos_(node, level) {
return posPrcToAbs_(node, 1 - posAbsToPrc_(node, level));
}
/**
* get the absolute inverse level
* @param {*} node the node settings
* @return {number} get the current level
*/
function getRealLevel_(node) {
if (node.tempData.levelReverse) {
return node.tempData.levelInverse;
}
return node.tempData.level;
}
/**
* round a level to the next increment

@@ -117,7 +139,7 @@ * @param {*} node node data

pos = Number(pos.toFixed(hlp.countDecimals(node.blindData.increment)));
if (pos > node.blindData.levelOpen) {
pos = node.blindData.levelOpen;
if (pos > node.blindData.levelTop) {
pos = node.blindData.levelTop;
}
if (pos < node.blindData.levelClosed) {
pos = node.blindData.levelClosed;
if (pos < node.blindData.levelBottom) {
pos = node.blindData.levelBottom;
}

@@ -148,6 +170,7 @@ // node.debug(`levelPrcToAbs_ result ${pos}`);

function getSunPosition_(node, now) {
const sunPosition = node.positionConfig.getSunCalc(now);
// node.debug('sunPosition: ' + util.inspect(sunPosition, Object.getOwnPropertyNames(sunPosition)));
const sunPosition = node.positionConfig.getSunCalc(now, true);
// node.debug('sunPosition: ' + util.inspect(sunPosition, { colors: true, compact: 10, breakLength: Infinity }));
sunPosition.InWindow = (sunPosition.azimuthDegrees >= node.windowSettings.AzimuthStart) &&
(sunPosition.azimuthDegrees <= node.windowSettings.AzimuthEnd);
node.debug(`sunPosition: InWindow=${sunPosition.InWindow} azimuthDegrees=${sunPosition.azimuthDegrees} AzimuthStart=${node.windowSettings.AzimuthStart} AzimuthEnd=${node.windowSettings.AzimuthEnd}`);
return sunPosition;

@@ -158,9 +181,16 @@ }

'use strict';
/**
* evaluate temporary Data
* @param {*} node node Data
* @param {string} type type of type input
* @param {string} value value of typeinput
* @param {*} data data to cache
* @returns {*} data which was cached
*/
function evalTempData(node, type, value, data) {
// node.debug(`evalTempData type=${type} value=${value} data=${data}`);
if (data === null || typeof data === 'undefined') {
const name = type + '.' + value;
const name = `${type}.${value}`;
if (typeof node.tempData[name] !== 'undefined') {
node.log(RED._('blind-control.errors.usingTempValue', { type: type, value: value, usedValue: node.tempData[name] }));
node.log(RED._('blind-control.errors.usingTempValue', { type, value, usedValue: node.tempData[name] }));
return node.tempData[name];

@@ -171,7 +201,7 @@ }

}
node.warn(RED._('blind-control.errors.notEvaluableProperty', { type: type, value: value, usedValue: 'null' }));
node.warn(RED._('blind-control.errors.warning', { message: RED._('blind-control.errors.notEvaluableProperty', { type, value, usedValue: 'null' }) }));
node.nowarn[name] = true;
return null;
}
node.tempData[type + '.' + value] = data;
node.tempData[`${type}.${value}`] = data;
return data;

@@ -201,5 +231,5 @@ }

node.error(RED._('blind-control.errors.getOversteerData', err));
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.log(util.inspect(err, Object.getOwnPropertyNames(err)));
}
// node.debug('node.oversteerData=' + util.inspect(node.oversteerData));
// node.debug('node.oversteerData=' + util.inspect(node.oversteerData, { colors: true, compact: 10, breakLength: Infinity }));
return undefined;

@@ -216,2 +246,3 @@ }

function getBlindPosFromTI(node, msg, type, value, def) {
// node.debug(`getBlindPosFromTI - type=${type} value=${value} def=${def} `);
def = def || NaN;

@@ -226,20 +257,26 @@ if (type === 'none' || type === '') {

if (value.includes('close')) {
return node.blindData.levelClosed;
return node.blindData.levelBottom;
} else if (value.includes('open')) {
return node.blindData.levelOpen;
return node.blindData.levelTop;
} else if (val === '') {
return def;
}
} else {
if (val < 1) {
return node.blindData.levelClosed;
return node.blindData.levelBottom;
} else if (val > 99) {
return node.blindData.levelOpen;
return node.blindData.levelTop;
}
return (val / 100);
}
throw new Error('unknown value "'+ value + '" of type "' + type + '"' );
throw new Error(`unknown value "${value}" of type "${type}"` );
}
return node.positionConfig.getFloatProp(node, msg, type, value, def);
const res = node.positionConfig.getFloatProp(node, msg, type, value, def);
if (node.tempData.levelReverse) {
return getInversePos_(res);
}
return res;
} catch (err) {
node.error(RED._('blind-control.errors.getBlindPosData', err));
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.log(util.inspect(err, Object.getOwnPropertyNames(err)));
}

@@ -292,3 +329,3 @@ return def;

if (!node.blindData.overwrite.expires) {
node.debug(`expireNever expire=${expire}` + (typeof expire) + ' - isNaN=' + isNaN(expire) + ' - finite=' + !isFinite(expire) + ' - min=' + (expire < 100));
node.debug(`expireNever expire=${expire} ${ typeof expire } - isNaN=${ isNaN(expire) } - finite=${ !isFinite(expire) } - min=${ expire < 100}`);
delete node.blindData.overwrite.expireTs;

@@ -327,4 +364,4 @@ delete node.blindData.overwrite.expireDate;

hlp.getMsgBoolValue(msg, 'reset', 'resetOverwrite',
(val) => {
node.debug('reset val="' + util.inspect(val, { colors: true, compact: 10 }) + '"');
val => {
node.debug(`reset val="${util.inspect(val, { colors: true, compact: 10, breakLength: Infinity }) }"`);
if (val) {

@@ -368,3 +405,3 @@ blindPosOverwriteReset(node);

node.debug(`checkBlindPosOverwrite act=${node.blindData.overwrite.active} `);
const prio = hlp.getMsgNumberValue(msg, ['prio', 'priority'], ['prio', 'alarm'], (p) => {
const prio = hlp.getMsgNumberValue(msg, ['prio', 'priority'], ['prio', 'alarm'], p => {
checkOverrideReset(node, msg, now, p);

@@ -400,4 +437,4 @@ return p;

if (newPos === -1) {
node.blindData.level = NaN;
node.blindData.levelInverse = NaN;
node.tempData.level = NaN;
node.tempData.levelInverse = NaN;
} else if (!isNaN(newPos)) {

@@ -419,4 +456,4 @@ const allowRound = (msg.topic ? (msg.topic.includes('roundLevel') || msg.topic.includes('roundLevel')) : false);

}
node.blindData.level = newPos;
node.blindData.levelInverse = newPos;
node.tempData.level = newPos;
node.tempData.levelInverse = newPos;
}

@@ -453,3 +490,3 @@

function calcBlindSunPosition(node, msg, now) {
// node.debug('calcBlindSunPosition: calculate blind position by sun');
node.debug('calcBlindSunPosition: calculate blind position by sun');
// sun control is active

@@ -462,4 +499,4 @@ const sunPosition = getSunPosition_(node, now);

if (node.sunData.mode === winterMode) {
node.blindData.level = node.blindData.levelMin;
node.blindData.levelInverse = getInversePos_(node, node.blindData.level); // node.blindData.levelMax;
node.tempData.level = node.blindData.levelMin;
node.tempData.levelInverse = getInversePos_(node, node.tempData.level);
node.reason.code = 13;

@@ -486,3 +523,4 @@ node.reason.state = RED._('blind-control.states.sunNotInWinMin');

if (res) {
node.blindData.level = res.blindPos;
node.tempData.level = res.blindPos;
node.tempData.levelInverse = getInversePos_(node, node.tempData.level);
node.reason.code = 10;

@@ -499,4 +537,4 @@ node.reason.state = RED._('blind-control.states.oversteer');

if (node.sunData.mode === winterMode) {
node.blindData.level = node.blindData.levelMax;
node.blindData.levelInverse = getInversePos_(node, node.blindData.level); // node.blindData.levelMin;
node.tempData.level = node.blindData.levelMax;
node.tempData.levelInverse = getInversePos_(node, node.tempData.level);
node.reason.code = 12;

@@ -508,17 +546,17 @@ node.reason.state = RED._('blind-control.states.sunInWinMax');

// node.debug('node.windowSettings: ' + util.inspect(node.windowSettings, Object.getOwnPropertyNames(node.windowSettings)));
// node.debug('node.windowSettings: ' + util.inspect(node.windowSettings, { colors: true, compact: 10 }));
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;
node.tempData.level = node.blindData.levelBottom;
node.tempData.levelInverse = node.blindData.levelTop;
} else if (height >= node.windowSettings.top) {
node.blindData.level = node.blindData.levelOpen;
node.blindData.levelInverse = node.blindData.levelClosed;
node.tempData.level = node.blindData.levelTop;
node.tempData.levelInverse = node.blindData.levelBottom;
} else {
node.blindData.level = posPrcToAbs_(node, (height - node.windowSettings.bottom) / (node.windowSettings.top - node.windowSettings.bottom));
node.blindData.levelInverse = getInversePos_(node, node.blindData.level);
node.tempData.level = posPrcToAbs_(node, (height - node.windowSettings.bottom) / (node.windowSettings.top - node.windowSettings.bottom));
node.tempData.levelInverse = getInversePos_(node, node.tempData.level);
}
const delta = Math.abs(node.previousData.level - node.blindData.level);
const delta = Math.abs(node.previousData.level - node.tempData.level);

@@ -528,12 +566,12 @@ if ((node.smoothTime > 0) && (node.sunData.changeAgain > now.getTime())) {

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 if ((node.sunData.minDelta > 0) && (delta < node.sunData.minDelta) && (node.blindData.level > node.blindData.levelClosed) && (node.blindData.level < node.blindData.levelOpen)) {
node.reason.state = RED._('blind-control.states.smooth', { pos: getRealLevel_(node).toString()});
node.reason.description = RED._('blind-control.reasons.smooth', { pos: getRealLevel_(node).toString()});
node.tempData.level = node.previousData.level;
node.tempData.levelInverse = node.previousData.levelInverse;
} else if ((node.sunData.minDelta > 0) && (delta < node.sunData.minDelta) && (node.tempData.level > node.blindData.levelBottom) && (node.tempData.level < node.blindData.levelTop)) {
node.reason.code = 14;
node.reason.state = RED._('blind-control.states.sunMinDelta', { pos: node.blindData.level.toString()});
node.reason.description = RED._('blind-control.reasons.sunMinDelta', { pos: node.blindData.level.toString()});
node.blindData.level = node.previousData.level;
node.blindData.levelInverse = node.previousData.levelInverse;
node.reason.state = RED._('blind-control.states.sunMinDelta', { pos: getRealLevel_(node).toString()});
node.reason.description = RED._('blind-control.reasons.sunMinDelta', { pos: getRealLevel_(node).toString() });
node.tempData.level = node.previousData.level;
node.tempData.levelInverse = node.previousData.levelInverse;
} else {

@@ -546,20 +584,20 @@ node.reason.code = 9;

}
if (node.blindData.level < node.blindData.levelMin) {
if (node.tempData.level < node.blindData.levelMin) {
// min
node.debug(`${node.blindData.level} is below ${node.blindData.levelMin} (min)`);
node.debug(`${node.tempData.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 = getInversePos_(node, node.blindData.level); // node.blindData.levelMax;
} else if (node.blindData.level > node.blindData.levelMax) {
node.reason.description = RED._('blind-control.reasons.sunCtrlMin', {org: node.reason.description, level:node.tempData.level});
node.tempData.level = node.blindData.levelMin;
node.tempData.levelInverse = getInversePos_(node, node.tempData.level); // node.blindData.levelMax;
} else if (node.tempData.level > node.blindData.levelMax) {
// max
node.debug(`${node.blindData.level} is above ${node.blindData.levelMax} (max)`);
node.debug(`${node.tempData.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 = getInversePos_(node, node.blindData.level); // node.blindData.levelMin;
node.reason.description = RED._('blind-control.reasons.sunCtrlMax', {org: node.reason.description, level:node.tempData.level});
node.tempData.level = node.blindData.levelMax;
node.tempData.levelInverse = getInversePos_(node, node.tempData.level); // node.blindData.levelMin;
}
node.debug(`calcBlindSunPosition end pos=${node.blindData.level} reason=${node.reason.code} description=${node.reason.description}`);
node.debug(`calcBlindSunPosition end pos=${node.tempData.level} reason=${node.reason.code} description=${node.reason.description}`);
return sunPosition;

@@ -569,15 +607,9 @@ }

/**
* calculates the times
* @param {*} node node data
* @param {*} msg the message object
* @param {*} config the configuration object
* @returns the active rule or null
*/
function checkRules(node, msg, now) {
const livingRuleData = {};
const nowNr = now.getTime();
// node.debug(`checkRules nowNr=${nowNr}, node.rulesCount=${node.rulesCount}`); // {colors:true, compact:10}
// pre-checking conditions to may be able to store temp data
for (let i = 0; i < node.rulesCount; ++i) {
const rule = node.rulesData[i];
* pre-checking conditions to may be able to store temp data
* @param {*} node node data
* @param {*} msg the message object
*/
function prepareRules(node, msg) {
for (let i = 0; i < node.rules.count; ++i) {
const rule = node.rules.data[i];
if (rule.conditional) {

@@ -608,3 +640,17 @@ delete rule.conditonData.operandValue;

}
}
/**
* calculates the times
* @param {*} node node data
* @param {*} msg the message object
* @param {*} config the configuration object
* @returns the active rule or null
*/
function checkRules(node, msg, now) {
const livingRuleData = {};
const nowNr = now.getTime();
prepareRules(node,msg);
node.debug(`checkRules nowNr=${nowNr}, rules.count=${node.rules.count}, rules.lastUntil=${node.rules.lastUntil}`); // {colors:true, compact:10}
const fkt = (rule, cmp) => {

@@ -626,3 +672,12 @@ // node.debug('rule ' + util.inspect(rule, {colors:true, compact:10}));

}
rule.timeData = node.positionConfig.getTimeProp(node, msg, rule.timeType, rule.timeValue, rule.offsetType, rule.offsetValue, rule.multiplier, false);
rule.timeData = node.positionConfig.getTimeProp(node, msg, {
type: rule.timeType,
value : rule.timeValue,
offsetType : rule.offsetType,
offset : rule.offsetValue,
multiplier : rule.multiplier,
next : false,
now
});
if (rule.timeData.error) {

@@ -635,4 +690,4 @@ hlp.handleError(node, RED._('blind-control.errors.error-time', { message: rule.timeData.error }), undefined, rule.timeData.error);

rule.timeData.num = rule.timeData.value.getTime();
node.debug('rule.timeData=' + util.inspect(rule.timeData));
if (cmp(rule.timeData.num, nowNr)) {
// node.debug(`pos=${rule.pos} type=${rule.timeOpText} - ${rule.timeValue} - rule.timeData = ${ util.inspect(rule.timeData, { colors: true, compact: 40, breakLength: Infinity }) }`);
if (cmp(rule.timeData.num)) {
return rule;

@@ -646,9 +701,10 @@ }

let ruleSelMax = 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 === 1) { break; } // { continue; } - Until timeOp === 0
const res = fkt(rule, (r,h) => (r >= h));
// node.debug('first loop ' + node.rules.count);
for (let i = 0; i <= node.rules.lastUntil; ++i) {
const rule = node.rules.data[i];
// node.debug('rule ' + rule.timeOp + ' - ' + (rule.timeOp !== 1) + ' - ' + util.inspect(rule, {colors:true, compact:10, breakLength: Infinity }));
if (rule.timeOp === 1) { continue; } // - Until: timeOp === 0
const res = fkt(rule, r => (r >= nowNr));
if (res) {
node.debug('1. ruleSel ' + util.inspect(res, { colors: true, compact: 10, breakLength: Infinity }));
if (res.levelOp === 1 && (!ruleSelMin)) {

@@ -665,14 +721,25 @@ ruleSelMin = res;

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) { break; } // { continue; } - From timeOp === 1
const res = fkt(rule, (r,h) => (r <= h));
// node.debug('--------- starting second loop ' + node.rules.count);
for (let i = (node.rules.count - 1); i >= 0; --i) {
const rule = node.rules.data[i];
// node.debug('rule ' + rule.timeOp + ' - ' + (rule.timeOp !== 0) + ' - ' + util.inspect(rule, {colors:true, compact:10, breakLength: Infinity }));
if (rule.timeOp === 0) { continue; } // - From: timeOp === 1
const res = fkt(rule, r => (r <= nowNr));
if (res) {
if (res.levelOp === 1 && (!ruleSelMin)) {
ruleSelMin = res;
} else if (res.levelOp === 2 && (!ruleSelMax)) {
ruleSelMax = res;
node.debug('2. ruleSel ' + util.inspect(res, { colors: true, compact: 10, breakLength: Infinity }));
if (res.levelOp === 1) {
if (!ruleSelMin || ruleSelMin.timeOp === 0) {
ruleSelMin = res;
}
} else if (res.levelOp === 2) {
if (!ruleSelMax || ruleSelMax.timeOp === 0) {
ruleSelMax = res;
}
} else {
if (ruleSelMin && ruleSelMin.timeOp === 0) {
ruleSelMin = null;
}
if (ruleSelMax && ruleSelMax.timeOp === 0) {
ruleSelMax = null;
}
ruleSel = res;

@@ -685,5 +752,7 @@ break;

if (ruleSelMin) {
node.debug('ruleSelMin ' + util.inspect(ruleSelMin, { colors: true, compact: 10, breakLength: Infinity }));
livingRuleData.hasMinimum = true;
livingRuleData.levelMinimum = getBlindPosFromTI(node, msg, ruleSelMin.levelType, ruleSelMin.levelValue, node.blindData.levelDefault);
livingRuleData.minimum = {
id: ruleSelMin.pos,
level: getBlindPosFromTI(node, msg, ruleSelMin.levelType, ruleSelMin.levelValue, node.blindData.levelDefault),
conditional: ruleSelMin.conditional,

@@ -694,7 +763,11 @@ timeLimited: ruleSelMin.timeLimited,

};
} else {
livingRuleData.hasMinimum = false;
}
if (ruleSelMax) {
node.debug('ruleSelMax ' + util.inspect(ruleSelMax, { colors: true, compact: 10, breakLength: Infinity }));
livingRuleData.hasMaximum = true;
livingRuleData.levelMaximum = getBlindPosFromTI(node, msg, ruleSelMax.levelType, ruleSelMax.levelValue, node.blindData.levelDefault);
livingRuleData.maximum = {
id: ruleSelMax.pos,
level: getBlindPosFromTI(node, msg, ruleSelMax.levelType, ruleSelMax.levelValue, node.blindData.levelDefault),
conditional: ruleSelMax.conditional,

@@ -705,14 +778,24 @@ timeLimited: ruleSelMax.timeLimited,

};
} else {
livingRuleData.hasMaximum = false;
}
if (ruleSel) {
// ruleSel.text = '';
// node.debug('ruleSel ' + util.inspect(ruleSel, {colors:true, compact:10}));
node.debug('ruleSel ' + util.inspect(ruleSel, {colors:true, compact:10, breakLength: Infinity }));
node.reason.code = 4;
livingRuleData.id = ruleSel.pos;
livingRuleData.active = true;
livingRuleData.level = getBlindPosFromTI(node, msg, ruleSel.levelType, ruleSel.levelValue, node.blindData.levelDefault);
node.reason.code = 4;
if (ruleSel.levelOp === 0) { // absolute rule
livingRuleData.active = true;
livingRuleData.level = getBlindPosFromTI(node, msg, ruleSel.levelType, ruleSel.levelValue, node.blindData.levelDefault);
} else {
livingRuleData.active = false;
livingRuleData.level = 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;
node.tempData.level = livingRuleData.level;
node.tempData.levelInverse = getInversePos_(node, livingRuleData.level);
const data = { number: ruleSel.pos };

@@ -741,4 +824,3 @@ let name = 'rule';

node.reason.description = RED._('blind-control.reasons.'+name, data);
// node.debug('checkRules end: livingRuleData=' + util.inspect(livingRuleData,{colors:true, compact:10}));
node.debug(`checkRules end pos=${node.blindData.level} reason=${node.reason.code} description=${node.reason.description}`);
node.debug(`checkRules end pos=${node.tempData.level} reason=${node.reason.code} description=${node.reason.description} all=${util.inspect(livingRuleData, { colors: true, compact: 10, breakLength: Infinity })}`);
return livingRuleData;

@@ -748,9 +830,8 @@ }

livingRuleData.id = -1;
node.blindData.level = node.blindData.levelDefault;
node.blindData.levelInverse = getInversePos_(node, node.blindData.levelDefault);
node.tempData.level = node.blindData.levelDefault;
node.tempData.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}));
node.debug(`checkRules end pos=${node.blindData.level} reason=${node.reason.code} description=${node.reason.description}`);
node.debug(`checkRules end pos=${node.tempData.level} reason=${node.reason.code} description=${node.reason.description} all=${util.inspect(livingRuleData, { colors: true, compact: 10, breakLength: Infinity })}`);
return livingRuleData;

@@ -782,3 +863,7 @@ }

// temporary node Data
node.tempData = {};
node.tempData = {
level: NaN, // unknown
levelInverse: NaN,
levelReverse: false
};
// Retrieve the config node

@@ -810,6 +895,4 @@ node.sunData = {

/** The Level of the window */
level: NaN, // unknown
levelInverse: NaN,
levelOpen: Number(hlp.chkValueFilled(config.blindOpenPos, 100)),
levelClosed: Number(hlp.chkValueFilled(config.blindClosedPos, 0)),
levelTop: Number(hlp.chkValueFilled(config.blindOpenPos, 100)),
levelBottom: Number(hlp.chkValueFilled(config.blindClosedPos, 0)),
increment: Number(hlp.chkValueFilled(config.blindIncrement, 1)),

@@ -826,5 +909,11 @@ levelDefault: NaN,

};
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);
if (node.blindData.levelTop < node.blindData.levelBottom) {
[node.blindData.levelBottom, node.blindData.levelTop] = [node.blindData.levelTop, node.blindData.levelBottom];
node.tempData.levelReverse = true;
}
node.blindData.levelDefault = getBlindPosFromTI(node, undefined, config.blindPosDefaultType, config.blindPosDefault, node.blindData.levelTop);
node.blindData.levelMin = getBlindPosFromTI(node, undefined, config.blindPosMinType, config.blindPosMin, node.blindData.levelBottom);
node.blindData.levelMax = getBlindPosFromTI(node, undefined, config.blindPosMaxType, config.blindPosMax, node.blindData.levelTop);
node.oversteer = {

@@ -842,3 +931,3 @@ active: (typeof config.oversteerValueType !== 'undefined') && (config.oversteerValueType !== 'none'),

thresholdType: config.oversteerThresholdType,
blindPos: getBlindPosFromTI(node, undefined, config.oversteerBlindPosType, config.oversteerBlindPos, node.blindData.levelOpen)
blindPos: getBlindPosFromTI(node, undefined, config.oversteerBlindPosType, config.oversteerBlindPos, node.blindData.levelTop)
});

@@ -852,3 +941,3 @@ if ((typeof config.oversteer2ValueType !== 'undefined') && (config.oversteer2ValueType !== 'none')) {

thresholdType: config.oversteer2ThresholdType,
blindPos: getBlindPosFromTI(node, undefined, config.oversteer2BlindPosType, config.oversteer2BlindPos, node.blindData.levelOpen)
blindPos: getBlindPosFromTI(node, undefined, config.oversteer2BlindPosType, config.oversteer2BlindPos, node.blindData.levelTop)
});

@@ -863,3 +952,3 @@ }

thresholdType: config.oversteer3ThresholdType,
blindPos: getBlindPosFromTI(node, undefined, config.oversteer3BlindPosType, config.oversteer3BlindPos, node.blindData.levelOpen)
blindPos: getBlindPosFromTI(node, undefined, config.oversteer3BlindPosType, config.oversteer3BlindPos, node.blindData.levelTop)
});

@@ -869,3 +958,5 @@ }

node.rulesData = config.rules || [];
node.rules = {
data: config.rules || []
};
node.previousData = {

@@ -880,3 +971,3 @@ level: NaN,

*/
function setState() {
function setState(blindCtrl) {
let code = node.reason.code;

@@ -889,3 +980,3 @@ let shape = 'ring';

if (node.blindData.level === node.blindData.levelOpen) {
if (node.tempData.level === node.blindData.levelTop) {
shape = 'dot';

@@ -901,6 +992,6 @@ }

}
node.reason.stateComplete = (isNaN(node.blindData.level)) ? node.reason.state : node.blindData.level.toString() + ' - ' + node.reason.state;
node.reason.stateComplete = (isNaN(blindCtrl.level)) ? node.reason.state : getRealLevel_(node).toString() + ' - ' + node.reason.state;
node.status({
fill: fill,
shape: shape,
fill,
shape,
text: node.reason.stateComplete

@@ -916,3 +1007,3 @@ });

node.debug(`input msg.topic=${msg.topic} msg.payload=${msg.payload}`);
// node.debug('input ' + util.inspect(msg, {colors:true, compact:10})); // Object.getOwnPropertyNames(msg)
// node.debug('input ' + util.inspect(msg, { colors: true, compact: 10, breakLength: Infinity })); // Object.getOwnPropertyNames(msg)
if (!this.positionConfig) {

@@ -933,4 +1024,4 @@ node.error(RED._('node-red-contrib-sun-position/position-config:errors.pos-config'));

node.previousData.level = node.blindData.level;
node.previousData.levelInverse = node.blindData.levelInverse;
node.previousData.level = node.tempData.level;
node.previousData.levelInverse = node.tempData.levelInverse;
node.previousData.reasonCode= node.reason.code;

@@ -950,3 +1041,3 @@ node.previousData.reasonState= node.reason.state;

// node.debug(`start pos=${node.blindData.level} manual=${node.blindData.overwrite.active} reasoncode=${node.reason.code} description=${node.reason.description}`);
// node.debug(`start pos=${node.tempData.level} manual=${node.blindData.overwrite.active} reasoncode=${node.reason.code} description=${node.reason.description}`);
// check for manual overwrite

@@ -961,28 +1052,26 @@ if (!checkBlindPosOverwrite(node, msg, now)) {

}
if (blindCtrl.rule.minimum && (node.blindData.level < blindCtrl.rule.minimum.level)) {
// min
node.debug(`${node.blindData.level} is below rule minimum ${blindCtrl.rule.minimum.level}`);
if (blindCtrl.rule.hasMinimum && (node.tempData.level < blindCtrl.rule.levelMinimum)) {
node.debug(`${node.tempData.level} is below rule minimum ${blindCtrl.rule.levelMinimum}`);
node.reason.code = 15;
node.reason.state = RED._('blind-control.states.ruleMin', { org: node.reason.state, rule: blindCtrl.rule.minimum.id });
node.reason.description = RED._('blind-control.reasons.ruleMin', { org: node.reason.description, level: node.blindData.level, rule: blindCtrl.rule.minimum.id });
node.blindData.level = blindCtrl.rule.minimum.level;
node.blindData.levelInverse = getInversePos_(node, node.blindData.level);
} else if (blindCtrl.rule.maximum && (node.blindData.level > blindCtrl.rule.maximum.level)) {
// max
node.debug(`${node.blindData.level} is above rule maximum ${blindCtrl.rule.maximum.level}`);
node.reason.state = RED._('blind-control.states.ruleMin', { org: node.reason.state, number: blindCtrl.rule.minimum.id });
node.reason.description = RED._('blind-control.reasons.ruleMin', { org: node.reason.description, level: getRealLevel_(node), number: blindCtrl.rule.minimum.id });
node.tempData.level = blindCtrl.rule.levelMinimum;
node.tempData.levelInverse = getInversePos_(node, node.tempData.level);
} else if (blindCtrl.rule.hasMaximum && (node.tempData.level > blindCtrl.rule.levelMaximum)) {
node.debug(`${node.tempData.level} is above rule maximum ${blindCtrl.rule.levelMaximum}`);
node.reason.code = 26;
node.reason.state = RED._('blind-control.states.ruleMax', { org: node.reason.state, rule: blindCtrl.rule.maximum.id });
node.reason.description = RED._('blind-control.reasons.ruleMax', { org: node.reason.description, level: node.blindData.level, rule: blindCtrl.rule.maximum.id });
node.blindData.level = blindCtrl.rule.maximum.level;
node.blindData.levelInverse = getInversePos_(node, node.blindData.level);
node.reason.state = RED._('blind-control.states.ruleMax', { org: node.reason.state, number: blindCtrl.rule.maximum.id });
node.reason.description = RED._('blind-control.reasons.ruleMax', { org: node.reason.description, level: getRealLevel_(node), number: blindCtrl.rule.maximum.id });
node.tempData.level = blindCtrl.rule.levelMaximum;
node.tempData.levelInverse = getInversePos_(node, node.tempData.level);
}
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.tempData.level < node.blindData.levelBottom) {
node.debug(`${node.tempData.level} is below ${node.blindData.levelBottom}`);
node.tempData.level = node.blindData.levelBottom;
node.tempData.levelInverse = node.blindData.levelTop;
}
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;
if (node.tempData.level > node.blindData.levelTop) {
node.debug(`${node.tempData.level} is above ${node.blindData.levelBottom}`);
node.tempData.level = node.blindData.levelTop;
node.tempData.levelInverse = node.blindData.levelBottom;
}

@@ -993,16 +1082,25 @@ }

node.oversteerData.forEach(el => {
node.positionConfig.getPropValue(node, msg,
el.valueType,
el.value,
el.operator,
(type, value, data, _id) => {
node.positionConfig.getPropValue(node, msg, {
type: el.valueType,
value: el.value,
callback: (type, value, data, _ip) => {
if (data !== null && typeof data !== 'undefined') {
node.tempData[type + '.' + value] = data;
}
});
},
operator: el.operator
});
});
}
node.debug(`result pos=${node.blindData.level} manual=${node.blindData.overwrite.active} reasoncode=${node.reason.code} description=${node.reason.description}`);
setState();
if (node.tempData.levelReverse) {
blindCtrl.level = node.tempData.levelInverse;
blindCtrl.levelInverse = node.tempData.level;
} else {
blindCtrl.level = node.tempData.level;
blindCtrl.levelInverse = node.tempData.levelInverse;
}
node.debug(`result pos=${blindCtrl.level} manual=${node.blindData.overwrite.active} reasoncode=${node.reason.code} description=${node.reason.description}`);
setState(blindCtrl);
let topic = config.topic;

@@ -1012,4 +1110,4 @@ if (topic) {

name: node.name,
level: node.blindData.level,
levelInverse: node.blindData.levelInverse,
level: blindCtrl.level,
levelInverse: blindCtrl.levelInverse,
code: node.reason.code,

@@ -1024,9 +1122,9 @@ state: node.reason.state,

}
if ((!isNaN(node.blindData.level)) &&
(node.blindData.level !== node.previousData.level ||
if ((!isNaN(node.tempData.level)) &&
(node.tempData.level !== node.previousData.level ||
node.reason.code !== node.previousData.reasonCode ||
ruleId !== node.previousData.usedRule)) {
msg.payload = node.blindData.level;
msg.payload = blindCtrl.level;
if (node.outputs > 1) {
node.send([msg, { topic: topic, payload: blindCtrl}]);
node.send([msg, { topic, payload: blindCtrl}]);
} else {

@@ -1038,3 +1136,3 @@ msg.topic = topic || msg.topic;

} else if (node.outputs > 1) {
node.send([null, { topic: topic, payload: blindCtrl}]);
node.send([null, { topic, payload: blindCtrl}]);
}

@@ -1044,4 +1142,4 @@ node.previousData.usedRule = ruleId;

} catch (err) {
node.error(RED._('blind-control.errors.internal', err));
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.error(RED._('blind-control.errors.error', err));
node.log(util.inspect(err, Object.getOwnPropertyNames(err)));
node.status({

@@ -1053,2 +1151,3 @@ fill: 'red',

}
return null;
});

@@ -1096,10 +1195,14 @@ // ####################################################################################################

};
node.rulesCount = node.rulesData.length;
for (let i = 0; i < node.rulesCount; ++i) {
const rule = node.rulesData[i];
node.rules.count = node.rules.data.length;
node.rules.lastUntil = node.rules.count -1;
for (let i = 0; i < node.rules.count; ++i) {
const rule = node.rules.data[i];
rule.pos = i + 1;
rule.timeOp = Number(rule.timeOp) || 0;
rule.levelOp = Number(rule.levelOp) || 0;
rule.pos = i + 1;
rule.conditional = (rule.validOperandAType !== 'none');
rule.timeLimited = (rule.timeType !== 'none');
if (!rule.timeLimited) {
rule.timeOp = -1;
}
if (rule.conditional) {

@@ -1121,14 +1224,18 @@ rule.conditonData = {

}
if (rule.timeOp === 0) {
node.rules.lastUntil = i; // from rule
}
}
node.rulesData.sort((a, b) => {
const res = (a.timeLimited - b.timeLimited);
if (res !== 0) {
return res; // not timeLimited before timeLimited
}
if ((!a.timeLimited && !b.timeLimited) || (a.timeOp === b.timeOp)) {
/* if (node.rules.data) {
node.rules.data.sort((a, b) => {
if (a.timeLimited && b.timeLimited) { // both are time limited
const top = (a.timeOp - b.timeOp);
if (top !== 0) { // from/until type different
return top; // from before until
}
}
return a.pos - b.pos;
}
// both timeLimited:
return a.timeOp - b.timeOp;
});
});
node.debug('node.rules.data =' + util.inspect(node.rules.data, { colors: true, compact: 10, breakLength: Infinity }));
} */
}

@@ -1135,0 +1242,0 @@ initialize();

@@ -21,3 +21,3 @@ /*

const acos = Math.acos;
const rad = PI / 180;
const rad = Math.PI / 180;

@@ -30,2 +30,12 @@ // date/time constants and conversions

/**
* checks if a value is a valid Date object
* @param {*} d - a value to check
* @returns {boolean} returns __true__ if it is a valid Date, otherwhise __false__
*/
function isValidDate(d) {
return d instanceof Date && !isNaN(d);
// d !== 'Invalid Date' && !isNaN(d)
}
/**
* convert date from Julian calendar

@@ -41,3 +51,3 @@ * @param {*} date object to convert

* get number of days for a date since 2000
* @param {*} date date to get days
* @param {Date} date date to get days
* @param {boolean} [inUTC] defines if the calculation should be in utc or local time (default is UTC)

@@ -47,10 +57,10 @@ * @return {number} cont of days

function toDays(date, inUTC) {
date = date || new Date();
if (!isValidDate(date)) {
date = new Date();
}
let ms = date.valueOf();
if (inUTC === false) {
return ((Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) / dayMs) + J1970) - J2000;
ms = ms - date.getTimezoneOffset() * 60 * 1000;
}
return ((date.valueOf() / dayMs) + J1970) - J2000;
// return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()) / dayMs + J1970 - J2000;
// return toJulianDay(date) - J2000;
return ((ms / dayMs) + J1970) - J2000;
}

@@ -62,2 +72,8 @@

/**
* get right ascension
* @param {number} l
* @param {number} b
* @returns {number}
*/
function rightAscension(l, b) {

@@ -67,2 +83,8 @@ return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l));

/**
* get declination
* @param {number} l
* @param {number} b
* @returns {number}
*/
function declination(l, b) {

@@ -72,2 +94,9 @@ return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l));

/**
* get azimuth
* @param {number} H
* @param {number} phi
* @param {number} dec
* @returns {number}
*/
function azimuth(H, phi, dec) {

@@ -77,2 +106,9 @@ return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi));

/**
* get altitude
* @param {number} H
* @param {number} phi
* @param {number} dec
* @returns {number}
*/
function altitude(H, phi, dec) {

@@ -82,2 +118,8 @@ return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H));

/**
* side real time
* @param {number} d
* @param {number} lw
* @returns {number}
*/
function siderealTime(d, lw) {

@@ -87,2 +129,7 @@ return rad * (280.16 + 360.9856235 * d) - lw;

/**
* get astro refraction
* @param {number} h
* @returns {number}
*/
function astroRefraction(h) {

@@ -98,2 +145,7 @@ if (h < 0) { // the following formula works for positive altitudes only.

// general sun calculations
/**
* get solar mean anomaly
* @param {number} d
* @returns {number}
*/
function solarMeanAnomaly(d) {

@@ -103,2 +155,7 @@ return rad * (357.5291 + 0.98560028 * d);

/**
* ecliptic longitude
* @param {number} M
* @returns {number}
*/
function eclipticLongitude(M) {

@@ -111,2 +168,13 @@ const C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M));

/**
* @typedef {Object} sunCoordsObj
* @property {number} dec - The declination of the sun
* @property {number} ra - The right ascension of the sun
*/
/**
* sun coordinates
* @param {number} d
* @returns {sunCoordsObj}
*/
function sunCoords(d) {

@@ -145,3 +213,2 @@ const M = solarMeanAnomaly(d);

}
const lw = rad * -lng;

@@ -152,6 +219,11 @@ const phi = rad * lat;

const H = siderealTime(d, lw) - c.ra;
const azimuthr = azimuth(H, phi, c.dec);
const altituder = altitude(H, phi, c.dec);
// console.log(`getPosition date=${date}, M=${H}, L=${H}, c=${JSON.stringify(c)}, d=${d}, lw=${lw}, phi=${phi}`);
return {
azimuth: azimuth(H, phi, c.dec),
altitude: altitude(H, phi, c.dec),
azimuth: azimuthr,
altitude: altituder,
azimuthDegrees: 180 + 180 / PI * azimuthr,
altitudeDegrees: 180 / PI * altituder,
declination: c.dec

@@ -165,4 +237,7 @@ };

* @property {Date} value - Date object with the calculated sun-time
* @property {number} ts - The time as unix timestamp
* @property {number} pos - The position of the sun on the time
* @property {number} angle - Angle of the sun on the time
* @property {number} julian - The time as julian calendar
* @property {boolean} valid - indicates if the time is valid or not
*/

@@ -249,6 +324,19 @@

/**
* julian cycle
* @param {number} d
* @param {number} lw
* @returns {number}
*/
function julianCycle(d, lw) {
return Math.round(d - J0 - lw / (2 * PI));
return Math.round(d - J0 - lw / (2 * Math.PI));
}
/**
* approx transit
* @param {number} Ht
* @param {number} lw
* @param {number} n
* @returns {number}
*/
function approxTransit(Ht, lw, n) {

@@ -258,2 +346,9 @@ return J0 + (Ht + lw) / (2 * PI) + n;

/**
* solar transit in julian
* @param {number} ds
* @param {number} M
* @param {number} L
* @returns {number}
*/
function solarTransitJ(ds, M, L) {

@@ -263,2 +358,9 @@ return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L);

/**
* hour angle
* @param {number} h
* @param {number} phi
* @param {number} d
* @returns {number}
*/
function hourAngle(h, phi, d) {

@@ -268,6 +370,15 @@ return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d)));

/** returns set time for the given sun altitude */
/**
* returns set time for the given sun altitude
* @param {*} h
* @param {*} lw - rad * -lng
* @param {*} phi - rad * lat;
* @param {*} dec - declination
* @param {*} n - julian cycle
* @param {*} M - solar mean anomal
* @param {*} L - ecliptic longitude
* @returns
*/
function getSetJ(h, lw, phi, dec, n, M, L) {
const w = hourAngle(h, phi, dec);
const a = approxTransit(w, lw, n);

@@ -293,6 +404,4 @@ // console.log(`h=${h} lw=${lw} phi=${phi} dec=${dec} n=${n} M=${M} L=${L} w=${w} a=${a}`);

}
const lw = rad * -lng;
const phi = rad * lat;
const d = toDays(date, inUTC);

@@ -305,8 +414,8 @@ const n = julianCycle(d, lw);

const Jnoon = solarTransitJ(ds, M, L);
const noonVal = fromJulianDay(Jnoon);
const nadirVal = fromJulianDay(Jnoon + 0.5);
// console.log(`getSunTimes ${date.toISOString()} - Jnoon=${Jnoon} - JulianNoon=${noonVal.toISOString()} - JulianNadir=${nadirVal.toISOString()} ds=${ds}, M=${M}, L=${L}, n=${n}, d=${d}, lw=${lw}, phi=${phi}`);
const result = {
solarNoon : {
solarNoon: {
value: noonVal,

@@ -402,6 +511,4 @@ ts: noonVal.getTime(),

}
const lw = rad * -lng;
const phi = rad * lat;
const d = toDays(date, inUTC);

@@ -484,2 +591,9 @@ const n = julianCycle(d, lw);

SunCalc.getMoonPosition = function (date, lat, lng, inUTC) {
if (isNaN(lat)) {
throw new Error('latitude missing');
}
if (isNaN(lng)) {
throw new Error('longitude missing');
}
const lw = rad * -lng;

@@ -496,7 +610,12 @@ const phi = rad * lat;

const azimuthr = azimuth(H, phi, c.dec);
return {
azimuth: azimuth(H, phi, c.dec),
azimuth: azimuthr,
altitude: h,
azimuthDegrees: 180 + 180 / PI * azimuthr,
altitudeDegrees: 180 / PI * h,
distance: c.dist,
parallacticAngle: pa
parallacticAngle: pa,
parallacticAngleDegrees: 180 / PI * pa
};

@@ -522,16 +641,9 @@ };

const d = toDays(date, inUTC);
const s = sunCoords(d);
const m = moonCoords(d);
const sdist = 149598000;
// distance from Earth to Sun in km
const sdist = 149598000; // distance from Earth to Sun in km
const phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra));
const inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi));
const angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) -
cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra));
cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra));

@@ -545,2 +657,8 @@ return {

/**
* add hours to a date
* @param {date} date - date object to add hours
* @param {number} h - hours to add
* @returns {Date} new Date with added hours
*/
function hoursLater(date, h) {

@@ -567,2 +685,12 @@ return new Date(date.valueOf() + h * dayMs / 24);

SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
if (isNaN(lat)) {
throw new Error('latitude missing');
}
if (isNaN(lng)) {
throw new Error('longitude missing');
}
if (!isValidDate(date)) {
date = new Date();
}
const t = new Date(date);

@@ -646,3 +774,7 @@ if (inUTC === false) {

module.exports = SunCalc;
// export as Node module / AMD module / browser variable
if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc;
// else if (typeof define === 'function' && define.amd) define(SunCalc);
else window.SunCalc = SunCalc;
})();

@@ -21,8 +21,8 @@ {

"ruleConditionThreshold": "Schwelle",
"ruleTimeFrom": "von",
"ruleTimeUntil": "bis",
"ruleTimeFrom": "↧ von",
"ruleTimeUntil": "↥ bis",
"ruleBlindLevel": "Rollladenposition",
"ruleLevelAbs": "👌 - Absolut",
"ruleLevelMin": "👇 - Minimum",
"ruleLevelMax": "☝️ - Maximum",
"ruleLevelAbs": "↕ Absolut",
"ruleLevelMin": "⭳ Minimum (übersteuernd)",
"ruleLevelMax": "⭱️ Maximum (übersteuernd)",
"sunControlMode": "Modus",

@@ -45,3 +45,6 @@ "sunControlOff": "keine Beschränkung, Sonnenstands unabhängig",

"singleOutput": "einzeln (1)",
"splitOutput": "doppelt (2)"
"splitOutput": "doppelt (2)",
"btnAdd": "hinzufügen",
"btnSort": "sortieren",
"btnClear": "leeren"
},

@@ -70,3 +73,6 @@ "placeholder": {

"expiryperiod": "120",
"outputs": "Anzahl der Ausgänge"
"outputs": "Anzahl der Ausgänge",
"btnAdd": "neue leere Regel hinzufügen",
"btnSort": "alle Regeln sortieren, Achtung dies kann das Verhalten ändern!",
"btnClear": "alle Regeln entfernen"
},

@@ -99,3 +105,3 @@ "text": {

"tips": {
"sunPosControl": "<a href=\"https: //github.com/rdmtc/node-red-contrib-sun-position/blob/HEAD/blind_control.md\">Dokumentation und Beispiele</a>"
"sunPosControl": "<a href=\"https://github.com/rdmtc/node-red-contrib-sun-position/blob/HEAD/blind_control.md\">Dokumentation und Beispiele</a>"
},

@@ -141,2 +147,4 @@ "reasons": {

"errors": {
"error": "Fehler: __message__",
"warning": "Warnung: __message__",
"internal": "Fehler: __message__",

@@ -150,5 +158,5 @@ "error-time": "Fehler bei Ermittlung der Zeit: __message__",

"usingTempValue": "Wert von __type__.__value__ kann nicht ermitteln werden und es wird der gespeicherte Wert \"__usedValue__\" verwendet!",
"notEvaluableProperty": "Fehler: kann den Wert von __type__.__value__ nicht ermitteln, verwende \"__usedValue__\"!"
"notEvaluableProperty": "kann den Wert von __type__.__value__ nicht ermitteln, verwende \"__usedValue__\"!"
}
}
}

@@ -27,5 +27,55 @@ {

"mooncalc": "Mondposition",
"levelfix": "Position",
"moonPhase": "Mondphase",
"moonPhaseCheck": "Mondphase",
"levelfix": "Position",
"levelfree": "Position eingeben"
},
"typeOptions": {
"moonRise": "Mondaufgang",
"moonSet": "Monduntergang",
"astronomicalDawn": "astronomische Morgendämmerung",
"amateurDawn": "amateur Morgendämmerung",
"nauticalDawn": "nautische Morgendämmerung",
"blueHourDawnStart": "Blaue Morgenstunde Begin",
"civilDawn": "bürgerliche Morgendämmerung",
"blueHourDawnEnd": "Blaue Morgenstunde Ende",
"goldenHourDawnStart": "Goldene Morgenstunde Begin",
"sunrise": "Sonnenaufgang Begin",
"sunriseEnd": "Sonnenaufgang Ende",
"goldenHourDawnEnd": "Goldene Morgenstunde Ende",
"solarNoon": "Sonnenmittag",
"goldenHourDuskStart": "Goldene Abendstunde Begin",
"sunsetStart": "Sonnenuntergang Begin",
"sunset": "Sonnenuntergang Ende",
"goldenHourDuskEnd": "Goldene Abendstunde Ende",
"blueHourDuskStart": "Blaue Abendstunde Begin",
"civilDusk": "bürgerliche Abenddämmerung",
"blueHourDuskEnd": "Blaue Abendstunde Ende",
"nauticalDusk": "nautische Abenddämmerung",
"amateurDusk": "amateur Abenddämmerung",
"astronomicalDusk": "astronomische Abenddämmerung",
"nadir": "Sonnen Tiefstpunkt (nadir)",
"fMon": "erster Montag",
"fTue": "erster Dienstag",
"fWed": "erster Mittwoch",
"fThu": "erster Donnerstag",
"fFri": "erster Freitag",
"fSat": "erster Sonnabend",
"fSun": "erster Sonntag",
"lMon": "letzter Montag",
"lTue": "letzter Dienstag",
"lWed": "letzter Mittwoch",
"lThu": "letzter Donnerstag",
"lFri": "letzter Freitag",
"lSat": "letzter Sonnabend",
"lSun": "letzter Sonntag",
"newMoon": "Neumond 🌚",
"waxingCrescentMoon": "Zunehmender Sichelmond 🌒",
"firstQuarterMoon": "Zunehmender Halbmond 🌓",
"waxingGibbousMoon": "Zunehmender Dreiviertelmond 🌔",
"fullMoon": "Vollmond 🌝",
"waningGibbousMoon": "Abnehmender Dreiviertelmond 🌖",
"lastQuarterMoon": "Abnehmender Halbmond 🌗",
"waningCrescentMoon": "Abnehmender Sichelmond 🌘"
},
"multiselectLbl": {

@@ -211,3 +261,3 @@ "operatorTypes": "in Vergleich einbeziehen"

"errors": {
"error": "<strong>Fehler</strong>: __message__",
"error": "Fehler: __message__",
"error-title": "interner Fehler",

@@ -220,4 +270,4 @@ "error-init": "Fehler '__message__', wiederhole in __time__min",

"unknownCompareOperator": "Fehler, der verwendete vergleichs-Operator \"__operator__\" ist unbekannt!",
"notEvaluableProperty":"Fehler: kann den Wert von __type__.__value__ nicht ermitteln",
"notEvaluablePropertyAdd":"Fehler \"__err__\", kann den Wert von __type__.__value__ nicht ermitteln!"
"notEvaluableProperty":"Kann den Wert von __type__.__value__ nicht ermitteln",
"notEvaluablePropertyAdd":"\"__err__\", kann den Wert von __type__.__value__ nicht ermitteln!"
},

@@ -224,0 +274,0 @@ "position-config": {

@@ -21,12 +21,12 @@ {

"ruleConditionThreshold": "threshold",
"ruleTimeFrom": "from",
"ruleTimeUntil": "until",
"ruleTimeFrom": "↧ from",
"ruleTimeUntil": "↥ until",
"ruleBlindLevel": "blind position",
"ruleLevelAbs": "👌 - absolute",
"ruleLevelMin": "👇 - minimum",
"ruleLevelMax": "☝️ - maximum",
"ruleLevelAbs": "↕ absolute",
"ruleLevelMin": "⭳ minimum (oversteer)",
"ruleLevelMax": "⭱️ maximum (oversteer)",
"sunControlMode": "mode",
"sunControlOff": "no restriction, no sun control",
"sunControlRestrict": "restrict sunlight (Summer)",
"sunControlMaximize": "maximize sunlight (Winter)",
"sunControlRestrict": "restrict sunlight (🌞 Summer)",
"sunControlMaximize": "maximize sunlight (⛄ Winter)",
"sunFloorLength": "length on the floor",

@@ -45,3 +45,6 @@ "sunMinAltitude": "min altitude threshold",

"singleOutput": "single (1)",
"splitOutput": "dual (2)"
"splitOutput": "dual (2)",
"btnAdd": "add",
"btnSort": "sort",
"btnClear": "clear"
},

@@ -70,3 +73,6 @@ "placeholder": {

"expiryperiod": "120",
"outputs": "number of outputs"
"outputs": "number of outputs",
"btnAdd": "add new empty rule",
"btnSort": "sort all rules, Attention this can change the behavior!",
"btnClear": "removes all rules from list"
},

@@ -99,3 +105,3 @@ "text": {

"tips": {
"sunPosControl": "<a href=\"https: //github.com/rdmtc/node-red-contrib-sun-position/blob/HEAD/blind_control.md\">Documentation and examples</a>"
"sunPosControl": "<a href=\"https://github.com/rdmtc/node-red-contrib-sun-position/blob/HEAD/blind_control.md\">Documentation and examples</a>"
},

@@ -145,2 +151,4 @@ "reasons": {

"errors": {
"error": "Error: __message__",
"warning": "Warning: __message__",
"internal": "Error: __message__",

@@ -154,5 +162,5 @@ "error-time": "Error get time: __message__",

"usingTempValue": "Could not evaluate __type__.__value__ and using stored value \"__usedValue__\"!",
"notEvaluableProperty": "Error: could not evaluate __type__.__value__, using \"__usedValue__\"!"
"notEvaluableProperty": "could not evaluate __type__.__value__, using \"__usedValue__\"!"
}
}
}

@@ -27,6 +27,6 @@ {

"mooncalc":"moon calculation",
"moonPhase":"moon phase",
"moonPhaseCheck": "moon phase",
"levelfix":"Level",
"levelfree": "Level entered",
"levelfixMin": "minimum Level",
"levelfreeMin": "minimum Level entered"
"levelfree": "Level entered"
},

@@ -71,3 +71,11 @@ "typeOptions": {

"lSat": "last Saturday",
"lSun": "last Sunday"
"lSun": "last Sunday",
"newMoon" : "New Moon 🌚",
"waxingCrescentMoon": "Waxing Crescent 🌒",
"firstQuarterMoon": "First Quarter 🌓",
"waxingGibbousMoon": "Waxing Gibbous 🌔",
"fullMoon": "Full Moon 🌝",
"waningGibbousMoon": "Waning Gibbous 🌖",
"lastQuarterMoon": "Last Quarter 🌗",
"waningCrescentMoon": "Waning Crescent 🌘"
},

@@ -196,8 +204,8 @@ "multiselectLbl": {

"is not false expr.",
"==",
"!=",
"=",
"≠",
"<",
"<=",
"≤",
">",
">=",
"≥",
"contain",

@@ -269,3 +277,3 @@ "containSome",

"errors": {
"error": "<strong>Error</strong>: __message__",
"error": "Error: __message__",
"error-title": "internal error",

@@ -279,3 +287,4 @@ "error-init": "error '__message__', retry in __time__min",

"notEvaluableProperty":"Error: could not evaluate __type__.__value__!",
"notEvaluablePropertyAdd":"Error \"__err__\", could not evaluate __type__.__value__"
"notEvaluablePropertyAdd":"Error \"__err__\", could not evaluate __type__.__value__",
"invalidParameter":"Parameter \"__param__\" is invalid \"__type__\" (using __newValue__)"
},

@@ -282,0 +291,0 @@ "position-config": {

/********************************************
* moon-position:
*********************************************/
'use strict';
const path = require('path');

@@ -12,3 +12,6 @@

'use strict';
/**
* moonPositionNode
* @param {*} config - configuration
*/
function moonPositionNode(config) {

@@ -33,2 +36,8 @@ RED.nodes.createNode(this, config);

}
if (!hlp.isValidDate(now)) {
now = new Date();
node.error(RED._('node-red-contrib-sun-position/position-config:errors.invalidParameter', { param: 'msg.ts', type: 'Date', newValue:now}));
}
if (!this.positionConfig) {

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

this.send(ports);
return;
return null;
}

@@ -92,5 +101,5 @@

this.status({
fill: fill,
fill,
shape: 'dot',
text: text
text
});

@@ -102,3 +111,3 @@ }

node.error(err.message);
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.log(util.inspect(err, Object.getOwnPropertyNames(err)));
node.status({

@@ -110,4 +119,13 @@ fill: 'red',

}
return null;
});
/**
* get the value ofd a numeric property
* @param {*} srcNode - source node
* @param {*} msg - message object
* @param {string} vType - type
* @param {string} value - value
* @returns {number} the result value for the type and value
*/
function getNumProp(srcNode, msg, vType, value) {

@@ -114,0 +132,0 @@ try {

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

const moonPhases = [{
pos: 0,
id: 'newMoon',
emoji: '🌚',

@@ -22,2 +24,4 @@ code: ':new_moon_with_face:',

{
pos: 1,
id: 'waxingCrescentMoon',
emoji: '🌒',

@@ -29,2 +33,4 @@ code: ':waxing_crescent_moon:',

{
pos: 2,
id: 'firstQuarterMoon',
emoji: '🌓',

@@ -36,2 +42,4 @@ code: ':first_quarter_moon:',

{
pos: 3,
id: 'waxingGibbousMoon',
emoji: '🌔',

@@ -43,2 +51,4 @@ code: ':waxing_gibbous_moon:',

{
pos: 4,
id: 'fullMoon',
emoji: '🌝',

@@ -50,2 +60,4 @@ code: ':full_moon_with_face:',

{
pos: 5,
id: 'waningGibbousMoon',
emoji: '🌖',

@@ -57,2 +69,4 @@ code: ':waning_gibbous_moon:',

{
pos: 6,
id: 'lastQuarterMoon',
emoji: '🌗',

@@ -64,2 +78,4 @@ code: ':last_quarter_moon:',

{
pos: 7,
id: 'waningCrescentMoon',
emoji: '🌘',

@@ -80,6 +96,7 @@ code: ':waning_crescent_moon:',

/** generic configuration Node */
class positionConfigurationNode {
/**
*
* @param config
* creates a new instance of the settings node and initializes them
* @param {*} config - configuration of the node
*/

@@ -89,3 +106,2 @@ constructor(config) {

try {
this.debug('initialize');
this.name = config.name;

@@ -109,2 +125,3 @@ this.longitude = parseFloat(this.credentials.posLongitude || config.longitude);

}
this.debug(`initialize longitude=${this.longitude} latitude=${this.latitude} tzOffset=${this.tzOffset} tzDST=${this.tzDST}`);

@@ -122,3 +139,3 @@ this.stateTimeFormat = config.stateTimeFormat || '3';

const today = new Date();
const dayId = this._getUTCDayId(today);
const dayId = this._getDayId(today); // this._getUTCDayId(today);
const tomorrow = today.addDays(1);

@@ -141,3 +158,3 @@ this._sunTimesRefresh(today, tomorrow, dayId);

* register a node as child
* @param node node to register as child node
* @param {*} node node to register as child node
*/

@@ -150,5 +167,5 @@ register(node) {

* remove a previous registered node as child
* @param node node to remove
* @param done function which should be executed after deregister
* @returns {function}
* @param {*} node node to remove
* @param {function} done function which should be executed after deregister
* @returns {*} result of the function
*/

@@ -161,8 +178,17 @@ deregister(node, done) {

/**
* @typedef {Object} timeresult
* @property {Date} value - a Date object of the neesed date/time
* @property {string} [error] - string of an error message if an error occurs
*/
* @typedef {Object} timeresult
* @property {Date} value - a Date object of the neesed date/time
* @property {number} ts - The time as unix timestamp
* @property {number} pos - The position of the sun on the time
* @property {number} angle - Angle of the sun on the time
* @property {number} julian - The time as julian calendar
* @property {boolean} valid - indicates if the time is valid or not
*/
/**
* @typedef {Object} erroresult
* @property {string} error - string of an error message if an error occurs
*/
/**
* gets sun time

@@ -175,16 +201,37 @@ * @param {Date} now current time

* @param {string} [days] days for which should be calculated the sun time
* @return {timeresult} result object of sunTime
* @return {timeresult|erroresult} result object of sunTime
*/
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]);
let result;
const dayid = this._getDayId(now); // this._getUTCDayId(now);
const today = this._sunTimesCheck(); // refresh if needed, get dayId
// this.debug(`getSunTime value=${value} offset=${offset} multiplier=${multiplier} next=${next} days=${days} now=${now} dayid=${dayid} today=${util.inspect(today, { colors: true, compact: 10, breakLength: Infinity })}`);
if (dayid === today.dayId) {
this.debug('getSunTime sunTimesToday');
result = Object.assign({}, this.sunTimesToday[value]); // needed for a object copy
} else if (dayid === (today.dayId + 1)) {
this.debug('getSunTime sunTimesTomorow');
result = Object.assign({},this.sunTimesTomorow[value]); // needed for a object copy
} else {
this.debug('getSunTime calc extra time');
result = Object.assign({},sunCalc.getSunTimes(now, this.latitude, this.longitude, false)[value]); // needed for a object copy
}
result.value = hlp.addOffset(new Date(result.value), offset, multiplier);
if (next && result.value.getTime() <= now.getTime()) {
result = Object.assign(result, this.sunTimesTomorow[value]);
result.value = hlp.addOffset(new Date(result.value), offset, multiplier);
if (dayid === today.dayId) {
result = Object.assign(result, this.sunTimesTomorow[value]);
result.value = hlp.addOffset(new Date(result.value), offset, multiplier);
}
const datebase = new Date(now);
while (result.value.getTime() <= now.getTime()) {
datebase.setUTCDate(datebase.getUTCDate() + 1);
result = Object.assign(result, sunCalc.getSunTimes(datebase, this.latitude, this.longitude, false)[value]);
result.value = hlp.addOffset(new Date(result.value), offset, multiplier);
}
}
if (days && (days !== '*') && (days !== '')) {
// this.debug('move days ' + days + ' result=' + util.inspect(result));
// this.debug('move days ' + days + ' result=' + util.inspect(result, { colors: true, compact: 10, breakLength: Infinity }));
const dayx = hlp.calcDayOffset(days, result.value.getDay());

@@ -194,6 +241,6 @@ if (dayx > 0) {

const date = result.value.addDays(dayx);
result = Object.assign(result, sunCalc.getSunTimes(date, this.latitude, this.longitude)[value]);
result = Object.assign(result, sunCalc.getSunTimes(date, this.latitude, this.longitude, false)[value]);
result.value = hlp.addOffset(new Date(result.value), offset, multiplier);
} else if (dayx < 0) {
// this.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, { colors: true, compact: 10, breakLength: Infinity }));
result.error = 'No valid day of week found!';

@@ -203,3 +250,3 @@ }

this.debug('getSunTime result=' + util.inspect(result));
// this.debug('getSunTime result=' + util.inspect(result, { colors: true, compact: 10, breakLength: Infinity }));
return result;

@@ -209,2 +256,7 @@ }

/**
* @typedef {Object} moontime
* @property {Date|NaN} value - a Date object of the neesed date/time
*/
/**
* gets moon time

@@ -217,27 +269,54 @@ * @param {Date} now current time

* @param {string} [days] days for which should be calculated the moon time
* @return {timeresult} result object of moon time
* @return {moontime|erroresult} result object of moon time
*/
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 && result.value.getTime() <= now.getTime()) {
result.value = hlp.addOffset(new Date(this.moonTimesTomorow[value]), offset, multiplier);
const result = {};
const datebase = new Date(now);
const dayid = this._getDayId(now); // this._getUTCDayId(now);
const today = this._moonTimesCheck(); // refresh if needed, get dayId
// this.debug(`getMoonTime value=${value} offset=${offset} multiplier=${multiplier} next=${next} days=${days} now=${now} dayid=${dayid} today=${today}`);
if (dayid === today.dayId) {
result.value = this.moonTimesToday[value]; // needed for a object copy
} else if (dayid === (today.dayId + 1)) {
result.value = this.moonTimesTomorow[value]; // needed for a object copy
} else {
result.value = sunCalc.getMoonTimes(now, this.latitude, this.longitude, false)[value]; // needed for a object copy
}
if (hlp.isValidDate(result.value)) {
result.value = hlp.addOffset(new Date(result.value.getTime()), offset, multiplier);
if (next && result.value.getTime() <= now.getTime()) {
if (dayid === today.dayId) {
result.value = this.sunTimesTomorow[value];
result.value = hlp.addOffset(new Date(result.value), offset, multiplier);
}
while (hlp.isValidDate(result.value) && result.value.getTime() <= now.getTime()) {
datebase.setUTCDate(datebase.getUTCDate() + 1);
result.value = sunCalc.getMoonTimes(datebase, this.latitude, this.longitude, false)[value];
result.value = hlp.addOffset(new Date(result.value), offset, multiplier);
}
}
}
while (!hlp.isValidDate(result.value)) {
datebase.setUTCDate(datebase.getUTCDate() + 1);
result.value = sunCalc.getMoonTimes(datebase, this.latitude, this.longitude, false)[value];
}
result.value = new Date(result.value.getTime());
if (days && (days !== '*') && (days !== '')) {
// this.debug('move days ' + days + ' result=' + util.inspect(result, { colors: true, compact: 10, breakLength: Infinity }));
const dayx = hlp.calcDayOffset(days, result.value.getDay());
if (dayx > 0) {
this._checkCoordinates();
const date = (new Date()).addDays(dayx);
const times = sunCalc.getMoonTimes(date, this.latitude, this.longitude);
result.value = hlp.addOffset(new Date(times[value]), offset, multiplier);
const date = result.value.addDays(dayx);
result.value = new Date(sunCalc.getMoonTimes(date, this.latitude, this.longitude, false)[value]);
result.value = hlp.addOffset(new Date(result.value), offset, multiplier);
} else if (dayx < 0) {
// this.debug('getSunTime - no valid day of week found value=' + value + ' - next=' + next + ' - days=' + days + ' result=' + util.inspect(result, { colors: true, compact: 10, breakLength: Infinity }));
result.error = 'No valid day of week found!';
// this.debug('getMoonTime - no valid week day found value=' + value + ' - next=' + next + ' - days=' + days + ' result=' + result.value);
}
}
if (isNaN(result.value)) {
result.error = 'No valid time for moon ' + value + ' found!';
}
// this.debug('getMoonTime result=' + util.inspect(result, { colors: true, compact: 10, breakLength: Infinity }));
return result;

@@ -302,6 +381,6 @@ }

} else {
data = this.getPropValue(_srcNode, msg, type, value, opCallback);
data = this.getPropValue(_srcNode, msg, { type, value, callback:opCallback });
}
if (data === null || typeof data === 'undefined') {
throw new Error(RED._('errors.notEvaluableProperty', {type:type, value:value}));
throw new Error(RED._('errors.error', { message: RED._('errors.notEvaluableProperty', {type, value}) }));
}

@@ -315,13 +394,35 @@ data = parseFloat(data);

/*******************************************************************************************************/
getOutDataProp(_srcNode, msg, vType, value, format, offset, offsetType, multiplier, next, days) {
_srcNode.debug(`getOutDataProp type=${vType} value=${value} format=${format} offset=${offset} offsetType=${offsetType} multiplier=${multiplier} next=${next} days=${days} tzOffset=${this.tzOffset}`);
/**
* @typedef {Object} outPropType
* @property {string} type - type name of the type input
* @property {string} value - value of the type input
* @property {string|number} format - format of the input
* @property {string} [offset] - value of the offset type input
* @property {string} [offsetType] - type name of the offset type input
* @property {number} [multiplier] - multiplier to the time
* @property {boolean} [next] - if __true__ the next date will be delivered starting from now, otherwise the matching date of the date from now
* @property {string} [days] - valide days
* @property {Date} [now] - base date, current time as default
*/
/**
* get the time Data prepared for output
* @param {*} _srcNode - source node for logging
* @param {*} [msg] - the message object
* @param {outPropType} data - a Data object
* @returns {*} output Data
*/
getOutDataProp(_srcNode, msg, data) {
// _srcNode.debug(`getOutDataProp data=${util.inspect(data, { colors: true, compact: 10, breakLength: Infinity }) } tzOffset=${this.tzOffset}`);
let now = new Date(data.now);
if (!hlp.isValidDate(data.now)) { now = new Date(); }
let result = null;
if (vType === null || vType === 'none' || vType === '' || (typeof vType === 'undefined')) {
if (value === '' || (typeof value === 'undefined')) {
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result = hlp.addOffset((new Date()), offsetX, multiplier);
return hlp.getFormattedDateOut(result, format, (this.tzOffset === 0), this.tzOffset);
if (data.type === null || data.type === 'none' || data.type === '' || (typeof data.type === 'undefined')) {
if (data.value === '' || (typeof data.value === 'undefined')) {
const offsetX = this.getFloatProp(_srcNode, msg, data.offsetType, data.offset, 0);
result = hlp.addOffset(now, offsetX, data.multiplier);
return hlp.getFormattedDateOut(result, data.format, (this.tzOffset === 0), this.tzOffset);
}
return value;
} else if (vType === 'date') {
return data.value;
} else if (data.type === 'date') {
if (this.tzOffset) {

@@ -331,106 +432,66 @@ return hlp.convertDateTimeZone(Date.now(), this.tzOffset);

return Date.now();
} else if (vType === 'dateSpecific') {
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result = hlp.addOffset((new Date()), offsetX, multiplier);
return hlp.getFormattedDateOut(result, format, (this.tzOffset === 0), this.tzOffset);
} else if ((vType === 'pdsTime') || (vType === 'pdmTime')) {
if (vType === 'pdsTime') { // sun
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result = this.getSunTime((new Date()), value, offsetX, multiplier, next, days);
} else if (vType === 'pdmTime') { // moon
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result = this.getMoonTime((new Date()), value, offsetX, multiplier, next, days);
} else if (data.type === 'dateSpecific') {
const offsetX = this.getFloatProp(_srcNode, msg, data.offsetType, data.offset, 0);
result = hlp.addOffset(now, offsetX, data.multiplier);
return hlp.getFormattedDateOut(result, data.format, (this.tzOffset === 0), this.tzOffset);
} else if ((data.type === 'pdsTime') || (data.type === 'pdmTime')) {
if (data.type === 'pdsTime') { // sun
const offsetX = this.getFloatProp(_srcNode, msg, data.offsetType, data.offset, 0);
result = this.getSunTime(now, data.value, offsetX, data.multiplier, data.next, data.days);
} else if (data.type === 'pdmTime') { // moon
const offsetX = this.getFloatProp(_srcNode, msg, data.offsetType, data.offset, 0);
result = this.getMoonTime(now, data.value, offsetX, data.multiplier, data.next, data.days);
}
if (result && result.value && !result.error) {
return hlp.getFormattedDateOut(result.value, format, (this.tzOffset === 0), this.tzOffset);
return hlp.getFormattedDateOut(result.value, data.format, (this.tzOffset === 0), this.tzOffset);
}
return null;
} else if (vType === 'entered' || vType === 'dateEntered') {
result = hlp.getDateOfText(String(value), (this.tzOffset === 0), this.tzOffset);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result = hlp.normalizeDate(result, offsetX, multiplier, next, days);
return hlp.getFormattedDateOut(result, format, (this.tzOffset === 0), this.tzOffset);
} else if (vType === 'dayOfMonth') {
} else if (data.type === 'entered' || data.type === 'dateEntered') {
result = hlp.getDateOfText(String(data.value), (this.tzOffset === 0), this.tzOffset);
const offsetX = this.getFloatProp(_srcNode, msg, data.offsetType, data.offset, 0);
result = hlp.normalizeDate(result, offsetX, data.multiplier, data.next, data.days);
return hlp.getFormattedDateOut(result, data.format, (this.tzOffset === 0), this.tzOffset);
} else if (data.type === 'dayOfMonth') {
result = new Date();
result = hlp.getSpecialDayOfMonth(result.getFullYear(),result.getMonth(), value);
result = hlp.getSpecialDayOfMonth(result.getFullYear(),result.getMonth(), data.value);
if (result !== null && typeof result !== 'undefined') {
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result = hlp.addOffset(result, offsetX, multiplier);
return hlp.getFormattedDateOut(result, format, (this.tzOffset === 0), this.tzOffset);
const offsetX = this.getFloatProp(_srcNode, msg, data.offsetType, data.offset, 0);
result = hlp.normalizeDate(result, offsetX, data.multiplier, data.next, data.days);
return hlp.getFormattedDateOut(result, data.format, (this.tzOffset === 0), this.tzOffset);
}
return null;
}
return this.getPropValue(_srcNode, msg, vType, value);
// _srcNode.debug(`getOutDataProp data=${util.inspect(data, { colors: true, compact: 10, breakLength: Infinity })} tzOffset=${this.tzOffset} result=${util.inspect(result, { colors: true, compact: 10, breakLength: Infinity })}`);
return this.getPropValue(_srcNode, msg, { type: data.type, value: data.value });
}
/*******************************************************************************************************/
getDateFromProp(_srcNode, msg, vType, value, format, offset, offsetType, multiplier) {
_srcNode.debug(`getDateFromProp type=${vType} value=${value} format=${format} offset=${offset} offsetType=${offsetType} multiplier=${multiplier} tzOffset=${this.tzOffset}`);
let result = null;
try {
if (vType === null || vType === 'none' || vType === '' || vType === 'date') {
if (this.tzOffset) {
return hlp.convertDateTimeZone(new Date(), this.tzOffset);
}
return new Date();
} else if (vType === 'dateSpecific') {
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
let d = new Date();
if (this.tzOffset) {
d = hlp.convertDateTimeZone(d, this.tzOffset);
}
return hlp.addOffset(d, offsetX, multiplier);
} else if (vType === 'dayOfMonth') {
let d = new Date();
d = hlp.getSpecialDayOfMonth(d.getFullYear(),d.getMonth(), value);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
if (this.tzOffset) {
return hlp.addOffset(hlp.convertDateTimeZone((new Date()), this.tzOffset), offsetX, multiplier);
}
return hlp.addOffset(d, offsetX, multiplier);
} else if ((vType === 'pdsTime') || (vType === 'pdmTime')) {
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
if (vType === 'pdsTime') {
// sun
result = this.getSunTime((new Date()), value, offsetX, multiplier);
result.fix = true;
} else if (vType === 'pdmTime') {
// moon
result = this.getMoonTime((new Date()), value, offsetX, multiplier);
result.fix = true;
}
if (result && result.value && !result.error) {
if (this.tzOffset) {
return hlp.convertDateTimeZone(result.value, this.tzOffset);
}
return result.value;
}
throw new Error(RED._('errors.notEvaluablePropertyAdd', {type:vType, value:value, err:result.error}));
} else if (vType === 'entered' || vType === 'dateEntered') {
result = hlp.getDateOfText(String(value), (this.tzOffset === 0), this.tzOffset);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
return hlp.addOffset(result, offsetX, multiplier);
} else {
// msg, flow, global, str, num, env
result = this.getPropValue(_srcNode, msg, vType, value);
}
if (result !== null && typeof result !== 'undefined') {
_srcNode.log(result);
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'));
if (this.tzOffset) {
result = hlp.convertDateTimeZone(result, this.tzOffset);
}
return hlp.addOffset(result, offsetX, multiplier);
}
} catch (err) {
_srcNode.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
const e = new Error(`Exception "${err.message}", on try to evaluate ${vType}.${value}`);
e.original = err;
e.stack = e.stack.split('\n').slice(0,2).join('\n')+'\n'+err.stack;
throw e;
}
}
/*******************************************************************************************************/
getTimeProp(_srcNode, msg, vType, value, offsetType, offset, multiplier, next, days) {
_srcNode.debug(`getTimeProp vType=${vType} value=${value} offset=${offset} offsetType=${offsetType} multiplier=${multiplier} next=${next} days=${days} tzOffset=${this.tzOffset}`);
/**
* @typedef {Object} timePropType
* @property {string} type - type name of the type input
* @property {string} value - value of the type input
* @property {string|number} [format] - format of the input
* @property {string} [offset] - value of the offset type input
* @property {string} [offsetType] - type name of the offset type input
* @property {number} [multiplier] - multiplier to the time
* @property {boolean} [next] - if __true__ the next date will be delivered starting from now, otherwise the matching date of the date from now
* @property {string} [days] - valide days
* @property {Date} [now] - base date, current time as default
*/
/**
* @typedef {Object} timePropResultType
* @property {Date} value - the Date value
* @property {string} error - error message if an error has occured
* @property {boolean} fix - indicator if the given time value is a fix date
*/
/**
* get the time Data from a typed input
* @param {*} _srcNode - source node for logging
* @param {*} [msg] - the message object
* @param {timePropType} data - a Data object
* @returns {timePropResultType} value of the type input
*/
getTimeProp(_srcNode, msg, data) {
// _srcNode.debug(`getTimeProp data=${util.inspect(data, { colors: true, compact: 10, breakLength: Infinity })} tzOffset=${this.tzOffset}`);
let result = {

@@ -441,9 +502,11 @@ value: null,

};
let now = new Date(data.now);
if (!hlp.isValidDate(data.now)) { now = new Date(); }
try {
if (days === '') {
if (data.days === '') {
result.error = 'No valid Days given!';
} else if (vType === '' || vType === 'none') {
result.error = 'wrong type "' + vType + '"="' + value+'"';
} else if (vType === 'date') {
result.value = new Date();
} else if (data.type === '' || data.type === 'none' || data.type === null) {
result.error = 'wrong type "' + data.type + '"="' + data.value+'"';
} else if (data.type === 'date') {
result.value = now;
if (this.tzOffset) {

@@ -453,5 +516,5 @@ result.value = hlp.convertDateTimeZone(result.value, this.tzOffset);

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

@@ -461,13 +524,27 @@ result.value = hlp.convertDateTimeZone(result.value);

result.fix = true;
} else if (vType === 'entered') {
result.value = hlp.getTimeOfText(String(value), (new Date()), (this.tzOffset === 0), this.tzOffset);
} else if (data.type === 'dayOfMonth') {
result.value = hlp.getSpecialDayOfMonth(now.getFullYear(), now.getMonth(), data.value);
const offsetX = this.getFloatProp(_srcNode, msg, data.offsetType, data.offset, 0);
result.value = hlp.normalizeDate(result.value, offsetX, data.multiplier, data.next, data.days);
if (this.tzOffset) {
result.value = hlp.convertDateTimeZone(result.value);
}
} else if (data.type === 'entered') {
result.value = hlp.getTimeOfText(String(data.value), now, (this.tzOffset === 0), this.tzOffset);
if (result.value !== null) {
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result.value = hlp.normalizeDate(result.value, offsetX, multiplier, next, days);
const offsetX = this.getFloatProp(_srcNode, msg, data.offsetType, data.offset, 0);
result.value = hlp.normalizeDate(result.value, offsetX, data.multiplier, data.next, data.days);
}
result.fix = true;
} else if (vType === 'pdsTime') {
} else if (data.type === 'dateEntered') {
result.value = hlp.getDateOfText(String(data.value), (this.tzOffset === 0), this.tzOffset);
if (result.value !== null) {
const offsetX = this.getFloatProp(_srcNode, msg, data.offsetType, data.offset, 0);
result.value = hlp.normalizeDate(result.value, offsetX, data.multiplier, data.next, data.days);
}
result.fix = true;
} else if (data.type === 'pdsTime') {
// sun
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result = this.getSunTime((new Date()), value, offsetX, multiplier, next, days);
const offsetX = this.getFloatProp(_srcNode, msg, data.offsetType, data.offset, 0);
result = this.getSunTime(now, data.value, offsetX, data.multiplier, data.next, data.days);
if (this.tzOffset) {

@@ -477,6 +554,6 @@ result.value = hlp.convertDateTimeZone(result.value, this.tzOffset);

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

@@ -488,9 +565,12 @@ result.value = hlp.convertDateTimeZone(result.value, this.tzOffset);

// can handle context, json, jsonata, env, ...
result.fix = (vType === 'json'); // is not a fixed time if can be changed
const res = this.getPropValue(_srcNode, msg, vType, value);
result.fix = (data.type === 'json'); // is not a fixed time if can be changed
const res = this.getPropValue(_srcNode, msg, data);
if (res) {
result.value = hlp.getDateOfText(res, (this.tzOffset === 0), this.tzOffset);
const offsetX = this.getFloatProp(_srcNode, msg, offsetType, offset, 0);
result.value = hlp.normalizeDate(result.value, offsetX, multiplier, next, days);
if (data.format) {
result.value = hlp.parseDateFromFormat(result.value, data.format, RED._('position-config.days'), RED._('position-config.month'), RED._('position-config.dayDiffNames'));
} else {
result.value = hlp.getDateOfText(res, (this.tzOffset === 0), this.tzOffset);
}
const offsetX = this.getFloatProp(_srcNode, msg, data.offsetType, data.offset, 0);
result.value = hlp.normalizeDate(result.value, offsetX, data.multiplier, data.next, data.days);
if (this.tzOffset) {

@@ -501,3 +581,3 @@ result.value = hlp.convertDateTimeZone(result.value, this.tzOffset);

} else {
result.error = RED._('errors.notEvaluableProperty', {type:vType, value:value});
result.error = RED._('errors.notEvaluableProperty', {type:data.type, value: data.value});
}

@@ -507,3 +587,3 @@ }

_srcNode.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
const e = new Error(RED._('errors.notEvaluablePropertyAdd', {type:vType, value:value, err:result.error}));
const e = new Error(RED._('errors.error', { message: RED._('errors.notEvaluablePropertyAdd', {type:data.type, value: data.value, err:result.error}) }) );
e.original = err;

@@ -513,10 +593,9 @@ e.stack = e.stack.split('\n').slice(0,2).join('\n')+'\n'+err.stack;

}
if (!result.value) {
if (!hlp.isValidDate(result.value)) {
if (!result.error) {
result.error = 'Can not get time for ' + vType + '=' + value;
result.error = 'Can not get time for ' + data.type + '=' + data.value;
}
result.value = new Date();
result.value = new Date(now);
}
_srcNode.debug('getTimeProp result=' + util.inspect(result));
// _srcNode.debug(`getTimeProp data=${util.inspect(data, { colors: true, compact: 10, breakLength: Infinity })} tzOffset=${this.tzOffset} result=${ util.inspect(result, { colors: true, compact: 10, breakLength: Infinity }) }`);
return result;

@@ -526,41 +605,50 @@ }

/**
* @typedef {Object} propValueType
* @property {string} type - type name of the type input
* @property {string} value - value of the type input
* @property {function} [callback] - function which should be called after value was recived
*/
/**
* get a property value from a type input in Node-Red
* @param {*} _srcNode - source node information
* @param {*} msg - message object
* @param {string} type - type name of the type input
* @param {*} value - value of the type input
* @param {function} [callback] - function which should be called after value was recived
* @param {*} [addID] - additional parameter for the callback
* @param {propValueType} data - data object with more information
* @returns {*} value of the type input, return of the callback function if defined or __null__ if value could not resolved
*/
getPropValue(_srcNode, msg, type, value, callback, addID) {
// _srcNode.debug(`getPropValue ${type}.${value} (${addID})`);
getPropValue(_srcNode, msg, data) {
// _srcNode.debug(`getPropValue ${data.type}.${data.value} (${data.addID})`);
let result = null;
if (type === '' || type === 'none' || typeof type === 'undefined' || type === null) {
if (data.type === '' || data.type === 'none' || typeof data.type === 'undefined' || data.type === null) {
result = null;
} else if (type === 'num') {
result = Number(value);
} else if (type === 'str') {
result = ''+value;
} else if (type === 'bool') {
result = /^true$/i.test(value);
} else if (type === 'date') {
} else if (data.type === 'num') {
result = Number(data.value);
} else if (data.type === 'str') {
result = ''+data.value;
} else if (data.type === 'bool') {
result = /^true$/i.test(data.value);
} else if (data.type === 'date') {
result = Date.now();
} else if (type === 'msgPayload') {
} else if (data.type === 'msgPayload') {
result = msg.payload;
} else if (type === 'msgValue') {
} else if (data.type === 'msgValue') {
result = msg.value;
} else if (type === 'msgTs') {
} else if (data.type === 'msgTs') {
result = msg.ts;
} else if (type === 'msgLc') {
} else if (data.type === 'msgLc') {
result = msg.lc;
} else if (type === 'pdsCalcData') {
} else if (data.type === 'pdsCalcData') {
result = this.getSunCalc(msg.ts);
} else if (type === 'pdmCalcData') {
} else if (data.type === 'pdmCalcData') {
result = this.getMoonCalc(msg.ts);
} else if (type === 'entered' || type === 'dateEntered') {
result = hlp.getDateOfText(String(value), (this.tzOffset === 0), this.tzOffset);
} else if (data.type === 'pdmPhase') {
result = this.getMoonPhase(msg.ts);
} else if (data.type === 'pdmPhaseCheck') {
const pahse = this.getMoonPhase(msg.ts);
result = (pahse === data.value);
} else if (data.type === 'entered' || data.type === 'dateEntered') {
result = hlp.getDateOfText(String(data.value), (this.tzOffset === 0), this.tzOffset);
} else {
try {
result = RED.util.evaluateNodeProperty(value, type, _srcNode, msg);
result = RED.util.evaluateNodeProperty(data.value, data.type, _srcNode, msg);
} catch (err) {

@@ -570,19 +658,21 @@ _srcNode.debug(util.inspect(err, Object.getOwnPropertyNames(err)));

}
if (typeof callback === 'function') {
return callback(type, value, result, addID);
if (typeof data.callback === 'function') {
return data.callback(data.type, data.value, result, data);
} else if (result === null || typeof result === 'undefined') {
_srcNode.error(RED._('errors.notEvaluableProperty', { type: type, value: value }));
_srcNode.error(RED._('errors.error', { message: RED._('errors.notEvaluableProperty', data) }) );
return null;
}
_srcNode.debug('getPropValue result=' + util.inspect(result) + ' - ' + typeof result);
// _srcNode.debug('getPropValue result=' + util.inspect(result, { colors: true, compact: 10, breakLength: Infinity }) + ' - ' + typeof result);
return result;
}
/*******************************************************************************************************/
comparePropValue(_srcNode, msg, opTypeA, opValueA, compare, opTypeB, opValueB, opCallback) {
comparePropValue(_srcNode, msg, opTypeA, opValueA, compare, opTypeB, opValueB, opCallback) { // eslint-disable-line complexity
// _srcNode.debug(`getComparablePropValue opTypeA='${opTypeA}' opValueA='${opValueA}' compare='${compare}' opTypeB='${opTypeB}' opValueB='${opValueB}'`);
if (opTypeA === 'none' || opTypeA === '' || typeof opTypeA === 'undefined' || opTypeA === null) {
return false;
} else if (opTypeA === 'jsonata' || opTypeA === 'pdmPhaseCheck') {
compare = 'true';
}
const a = this.getPropValue(_srcNode, msg, opTypeA, opValueA, opCallback, 1);
const a = this.getPropValue(_srcNode, msg, { type: opTypeA, value: opValueA, callback: opCallback, addID: 1 });
switch (compare) {

@@ -620,17 +710,17 @@ case 'true':

case 'equal':
return (a == this.getPropValue(_srcNode, msg, opTypeB, opValueB, opCallback, 2)); // eslint-disable-line eqeqeq
return (a == this.getPropValue(_srcNode, msg, { type: opTypeB, value: opValueB, callback: opCallback, addID: 2 })); // eslint-disable-line eqeqeq
case 'nequal':
return (a != this.getPropValue(_srcNode, msg, opTypeB, opValueB, opCallback, 2)); // eslint-disable-line eqeqeq
return (a != this.getPropValue(_srcNode, msg, { type: opTypeB, value: opValueB, callback: opCallback, addID: 2 })); // eslint-disable-line eqeqeq
case 'lt':
return (a < this.getPropValue(_srcNode, msg, opTypeB, opValueB, opCallback, 2));
return (a < this.getPropValue(_srcNode, msg, { type: opTypeB, value: opValueB, callback: opCallback, addID: 2 }));
case 'lte':
return (a <= this.getPropValue(_srcNode, msg, opTypeB, opValueB, opCallback, 2));
return (a <= this.getPropValue(_srcNode, msg, { type: opTypeB, value: opValueB, callback: opCallback, addID: 2 }));
case 'gt':
return (a > this.getPropValue(_srcNode, msg, opTypeB, opValueB, opCallback, 2));
return (a > this.getPropValue(_srcNode, msg, { type: opTypeB, value: opValueB, callback: opCallback, addID: 2 }));
case 'gte':
return (a >= this.getPropValue(_srcNode, msg, opTypeB, opValueB, opCallback, 2));
return (a >= this.getPropValue(_srcNode, msg, { type: opTypeB, value: opValueB, callback: opCallback, addID: 2 }));
case 'contain':
return ((a + '').includes(this.getPropValue(_srcNode, msg, opTypeB, opValueB, opCallback, 2)));
return ((a + '').includes(this.getPropValue(_srcNode, msg, { type: opTypeB, value: opValueB, callback: opCallback, addID: 2 })));
case 'containSome': {
const vals = this.getPropValue(_srcNode, msg, opTypeB, opValueB, opCallback, 2).split(/,;\|/);
const vals = this.getPropValue(_srcNode, msg, { type: opTypeB, value: opValueB, callback: opCallback, addID: 2 }).split(/,;\|/);
const txt = (a + '');

@@ -640,3 +730,3 @@ return vals.some(v => txt.includes(v));

case 'containEvery': {
const vals = this.getPropValue(_srcNode, msg, opTypeB, opValueB, opCallback, 2).split(/,;\|/);
const vals = this.getPropValue(_srcNode, msg, { type: opTypeB, value: opValueB, callback: opCallback, addID: 2 }).split(/,;\|/);
const txt = (a + '');

@@ -652,23 +742,18 @@ return vals.every(v => txt.includes(v));

getSunCalc(date, noTimes) {
// this.debug(`getSunCalc for date="${date}" noTimes="${noTimes}"`);
if (typeof date === 'string') {
// this.debug('getSunCalc for date ' + date);
this.debug(`getSunCalc for date="${date}" noTimes="${noTimes}"`);
if (!hlp.isValidDate(date)) {
const dto = new Date(date);
if (dto !== 'Invalid Date' && !isNaN(dto)) {
if (hlp.isValidDate(dto)) {
date = dto;
} else {
date = new Date();
}
}
if ((typeof date === 'undefined') || !(date instanceof Date)) {
this.debug('getSunCalc, no valid date ' + date + ' given');
date = new Date();
if (this.lastSunCalc && (Math.abs(date.getTime() - this.lastSunCalc.ts) < 4000)) {
this.debug('getSunCalc, time difference since last output to low, do no calculation');
return this.lastSunCalc;
}
if (this.lastSunCalc && (Math.abs(date.getTime() - this.lastSunCalc.ts) < 3000)) {
this.log('getSunCalc, time difference since last output to low, do no calculation');
return this.lastSunCalc;
}
const sunPos = sunCalc.getPosition(date, this.latitude, this.longitude);
const azimuthDegrees = 180 + 180 / Math.PI * sunPos.azimuth;
const altitudeDegrees = 180 / Math.PI * sunPos.altitude; // elevation = altitude

@@ -681,6 +766,6 @@ const result = {

angleType: this.angleType,
azimuth: (this.angleType === 'deg') ? azimuthDegrees : sunPos.azimuth,
altitude: (this.angleType === 'deg') ? altitudeDegrees : sunPos.altitude, // elevation = altitude
altitudeDegrees: altitudeDegrees,
azimuthDegrees: azimuthDegrees,
azimuth: (this.angleType === 'deg') ? sunPos.azimuthDegrees : sunPos.azimuth,
altitude: (this.angleType === 'deg') ? sunPos.altitudeDegrees : sunPos.altitude, // elevation = altitude
altitudeDegrees: sunPos.altitudeDegrees,
azimuthDegrees: sunPos.azimuthDegrees,
altitudeRadians: sunPos.altitude,

@@ -691,3 +776,3 @@ azimuthRadians: sunPos.azimuth

if (noTimes) {
// this.debug('no times result= ' + util.inspect(result));
this.debug('getSunCalc - no times result= ' + util.inspect(result, { colors: true, compact: 10, breakLength: Infinity }));
return result;

@@ -698,3 +783,3 @@ }

this.lastSunCalc = result;
// this.debug('result= ' + util.inspect(result));
this.debug('getSunCalc result= ' + util.inspect(result, { colors: true, compact: 10, breakLength: Infinity }));
return result;

@@ -704,67 +789,87 @@ }

/**************************************************************************************************************/
getMoonCalc(date, noTimes) {
if (typeof date === 'string') {
getMoonIllumination(date) {
if (!hlp.isValidDate(date)) {
const dto = new Date(date);
if (dto !== 'Invalid Date' && !isNaN(dto)) {
if (hlp.isValidDate(dto)) {
date = dto;
} else {
date = new Date();
}
}
if ((typeof date === 'undefined') || !(date instanceof Date)) {
date = new Date();
if (this.lastMoonCalc (Math.abs(date.getTime() - this.lastMoonCalc.ts) < 3000)) {
return this.lastMoonCalc;
}
}
const moonPos = sunCalc.getMoonPosition(date, this.latitude, this.longitude);
const moonIllum = sunCalc.getMoonIllumination(date);
const result = Object.assign({}, moonIllum);
const result = {
ts: date.getTime(),
lastUpdate: date,
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: (this.angleType === 'deg') ? 180 / Math.PI * moonPos.parallacticAngle : moonPos.parallacticAngle,
illumination: {
angle: (this.angleType === 'deg') ? 180 / Math.PI * moonIllum.angle : moonIllum.angle,
fraction: moonIllum.fraction,
phase: {},
zenithAngle: (this.angleType === 'deg') ? 180 / Math.PI * (moonIllum.angle - moonPos.parallacticAngle) : moonIllum.angle - moonPos.parallacticAngle
}
};
if (moonIllum.phase < 0.01) {
// 0 New Moon - Neumond(Phasenwinkel = 0°)
result.illumination.phase = moonPhases[0];
result.phase = moonPhases[0];
} else if (moonIllum.phase < 0.25) {
// 0 - 0.25 Waxing Crescent - erstes Viertel bzw. zunehmende Sichel(0° < Phasenwinkel < 90°),
result.illumination.phase = moonPhases[1];
result.phase = moonPhases[1];
} else if (moonIllum.phase < 0.26) {
// 0.25 First Quarter - zunehmender Halbmond(astronomisch: erstes Viertel, Phasenwinkel = 90°),
result.illumination.phase = moonPhases[2];
result.phase = moonPhases[2];
} else if (moonIllum.phase < 0.50) {
// 0.25 - 0.5 Waxing Gibbous - zweites Viertel(90° < Phasenwinkel < 180°),
result.illumination.phase = moonPhases[3];
result.phase = moonPhases[3];
} else if (moonIllum.phase < 0.51) {
// 0.5 Full Moon - Vollmond(Phasenwinkel = 180°),
result.illumination.phase = moonPhases[4];
result.phase = moonPhases[4];
} else if (moonIllum.phase <= 0.75) {
// 0.5 - 0.75 Waning Gibbous - drittes Viertel (180° < Phasenwinkel < 270°),
result.illumination.phase = moonPhases[5];
result.phase = moonPhases[5];
} else if (moonIllum.phase < 0.76) {
// 0.75 Last Quarter - abnehmender Halbmond(astronomisch: letztes Viertel, Phasenwinkel = 270°),
result.illumination.phase = moonPhases[6];
result.phase = moonPhases[6];
} else {
// Waning Crescent - letztes Viertel bzw. abnehmende Sichel(Phasenwinkel > 270°).
result.illumination.phase = moonPhases[7];
result.phase = moonPhases[7];
}
result.phase.nameAlt = RED._('common.typeOptions.' + result.phase.id);
result.phase.value = moonIllum.phase;
result.phase.angle = (this.angleType === 'rad') ? (moonIllum.phase * 360) / (180 / Math.PI) : moonIllum.phase * 360;
result.illumination.phase.value = moonIllum.phase;
result.illumination.phase.angle = (this.angleType === 'rad') ? (moonIllum.phase * 360) / (180 / Math.PI) : moonIllum.phase * 360;
return result;
}
getMoonCalc(date, noTimes) {
if (!hlp.isValidDate(date)) {
const dto = new Date(date);
if (hlp.isValidDate(dto)) {
date = dto;
} else {
date = new Date();
}
}
if (this.lastMoonCalc && (Math.abs(date.getTime() - this.lastMoonCalc.ts) < 3000)) {
this.log('getMoonCalc, time difference since last output to low, do no calculation');
return this.lastMoonCalc;
}
const moonPos = sunCalc.getMoonPosition(date, this.latitude, this.longitude);
const moonIllum = this.getMoonIllumination(date);
const result = {
ts: date.getTime(),
lastUpdate: date,
latitude: this.latitude,
longitude: this.longitude,
angleType: this.angleType,
azimuth: (this.angleType === 'deg') ? moonPos.azimuthDegrees : moonPos.azimuth,
altitude: (this.angleType === 'deg') ? moonPos.altitudeDegrees : moonPos.altitude, // elevation = altitude
altitudeDegrees: moonPos.altitudeDegrees,
azimuthDegrees: moonPos.azimuthDegrees,
altitudeRadians: moonPos.altitude,
azimuthRadians: moonPos.azimuth,
distance: moonPos.distance,
parallacticAngle: (this.angleType === 'deg') ? moonPos.parallacticAngleDegrees : moonPos.parallacticAngle,
illumination: {
angle: (this.angleType === 'deg') ? 180 / Math.PI * moonIllum.angle : moonIllum.angle,
fraction: moonIllum.fraction,
phase: moonIllum.phase,
zenithAngle: (this.angleType === 'deg') ? 180 / Math.PI * (moonIllum.angle - moonPos.parallacticAngle) : moonIllum.angle - moonPos.parallacticAngle
}
};
if (noTimes) { return result; }

@@ -789,2 +894,31 @@ this._sunTimesCheck();

}
getMoonPhase(date) {
let result;
const now = new Date();
if (!hlp.isValidDate(date)) {
const dto = new Date(date);
if (hlp.isValidDate(dto)) {
date = dto;
} else {
date = now;
}
}
const dayidReq = this._getDayId(date); // this._getUTCDayId(now);
const dayIdNow = this._getDayId(now); // this._getUTCDayId(dateb);
if (dayidReq === dayIdNow) {
if (dayIdNow !== this.moonIlluDayId) {
this.moonIlluminationToday = this.getMoonIllumination(date, false);
this.moonIlluDayId = dayIdNow;
}
result = Object.assign({}, this.moonIlluminationToday.phase); // needed for a object copy
} else {
result = Object.assign({},this.getMoonIllumination(date, false).phase); // needed for a object copy
}
// this.debug('getMoonPhase result=' + util.inspect(result, { colors: true, compact: 10, breakLength: Infinity }));
return result;
}
/**************************************************************************************************************/

@@ -805,20 +939,22 @@ _checkCoordinates() {

this._checkCoordinates();
// this.debug('sunTimesRefresh - calculate sun times');
this.sunTimesToday = sunCalc.getSunTimes(today, this.latitude, this.longitude);
this.sunTimesTomorow = sunCalc.getSunTimes(tomorrow, this.latitude, this.longitude);
this.sunTimesToday = sunCalc.getSunTimes(today, this.latitude, this.longitude, false);
this.sunTimesTomorow = sunCalc.getSunTimes(tomorrow, this.latitude, this.longitude, false);
this.sunDayId = dayId;
// this.debug(`sunTimesRefresh - calculate sun times - dayId=${dayId}, today=${today.toISOString()}, tomorrow=${tomorrow.toISOString()} this.sunTimesToday=${util.inspect(this.sunTimesToday, { colors: true, compact: 10, breakLength: Infinity })}`);
}
_sunTimesCheck(today, dayId) {
_sunTimesCheck(force) {
// this.debug('_sunTimesCheck');
const dateb = today || new Date();
const day_id = dayId || this._getUTCDayId(dateb);
if (this.sunDayId !== day_id) {
const today = new Date();
const dayId = this._getDayId(today); // _getUTCDayId(today);
// this.debug(`_sunTimesCheck ${this.sunDayId} - ${dayId}`);
if (force || this.sunDayId !== dayId) {
this.debug(`_sunTimesCheck - need refresh - force=${force}, base-dayId=${this.sunDayId} current-dayId=${dayId} today=${today}`);
const tomorrow = (new Date()).addDays(1);
this._sunTimesRefresh(dateb, tomorrow, day_id);
this._sunTimesRefresh(today, tomorrow, dayId);
}
return {
calcDate: dateb,
dayId: day_id
today,
dayId
};

@@ -830,3 +966,3 @@ }

// this.debug('moonTimesRefresh - calculate moon times');
this.moonTimesToday = sunCalc.getMoonTimes(today, this.latitude, this.longitude);
this.moonTimesToday = sunCalc.getMoonTimes(today, this.latitude, this.longitude, false);
if (!this.moonTimesToday.alwaysUp) {

@@ -842,3 +978,3 @@ // true if the moon never rises/sets and is always above the horizon during the day

this.moonTimesTomorow = sunCalc.getMoonTimes(tomorrow, this.latitude, this.longitude);
this.moonTimesTomorow = sunCalc.getMoonTimes(tomorrow, this.latitude, this.longitude, false);
if (!this.moonTimesTomorow.alwaysUp) {

@@ -855,16 +991,18 @@ // true if the moon never rises/sets and is always above the horizon during the day

this.moonDayId = dayId;
// this.debug(`moonTimesRefresh - calculate sun times - dayId=${dayId}, today=${today.toISOString()}, tomorrow=${tomorrow.toISOString()} `); // + util.inspect(this.sunTimesToday, { colors: true, compact: 40 }));
}
_moonTimesCheck(today, dayId) {
_moonTimesCheck(force) {
// this.debug('moonTimesCheck');
const dateb = today || new Date();
const day_id = dayId || this._getUTCDayId(dateb);
if (this.moonDayId !== day_id) {
const today = new Date();
const dayId = this._getDayId(today); // this._getUTCDayId(dateb);
if (force || this.moonDayId !== dayId) {
this.debug(`_moonTimesCheck - need refresh - force=${ force }, base-dayId=${ this.moonDayId } current-dayId=${ dayId }`);
const tomorrow = (new Date()).addDays(1);
this._moonTimesRefresh(dateb, tomorrow, day_id);
this._moonTimesRefresh(today, tomorrow, dayId);
}
return {
calcDate: dateb,
dayId: day_id
today,
dayId
};

@@ -876,2 +1014,6 @@ }

}
_getDayId(d) {
return d.getDay() + (d.getMonth() * 31) + (d.getFullYear() * 372);
}
}

@@ -906,3 +1048,3 @@

try {
obj = posConfig.getTimeProp(posConfig, undefined, req.query.type, req.query.value, req.query.offsetType, req.query.offset, req.query.multiplier, req.query.next, req.query.days);
obj = posConfig.getTimeProp(posConfig, undefined, req.query); // req.query.type, req.query.value, req.query.offsetType, req.query.offset, req.query.multiplier, req.query.next, req.query.days);
} catch(err) {

@@ -915,15 +1057,5 @@ obj.value = NaN;

}
case 'getDateData': {
try {
obj = posConfig.getDateFromProp(posConfig, undefined, req.query.type, req.query.value, req.query.format, req.query.offset, req.query.offsetType, req.query.multiplier);
} catch(err) {
obj.value = NaN;
obj.error = err;
}
res.status(200).send(JSON.stringify(obj));
break;
}
case 'getOutDataData': {
try {
obj = posConfig.getOutDataProp(posConfig, undefined, req.query.type, req.query.value, req.query.format, req.query.offset, req.query.offsetType, req.query.multiplier, req.query.next, req.query.days);
obj = posConfig.getOutDataProp(posConfig, undefined, req.query); // req.query.type, req.query.value, req.query.format, req.query.offset, req.query.offsetType, req.query.multiplier, req.query.next, req.query.days);
} catch(err) {

@@ -937,4 +1069,6 @@ obj.value = NaN;

}
} else {
res.status(200).send(JSON.stringify({}));
}
});
};
/************************************************************************/
const globSelectFields = {
operatorsGroups: [
{id: 'default', label: 'compare Timestamp'}
],
operators: [
{id: 1, group: 'default', label: '==', text: 'equal'},
{id: 2, group: 'default', label: '!=', text: 'unequal'},
{id: 3, group: 'default', label: '>', text: 'greater'},
{id: 4, group: 'default', label: '>=', text: 'greater or equal'},
{id: 5, group: 'default', label: '<', text: 'lesser'},
{id: 6, group: 'default', label: '<=', text: 'lesser or equal'}
], operatorTypesGroups: [
{id: 'default', label: 'include into compare'},
{id: 'spec', label: 'special'}
], operatorTypes: [
{id: 11, group: 'default', label: 'Milliseconds'},
{id: 12, group: 'default', label: 'Seconds'},
{id: 13, group: 'default', label: 'Minutes'},
{id: 14, group: 'default', label: 'Hours'},
{id: 15, group: 'default', label: 'Day of Month'},
{id: 16, group: 'default', label: 'Month'},
{id: 17, group: 'default', label: 'Year'},
{id: 18, group: 'spec', label: 'Day of Week'}
], outputTSFormatsGroups: [
{id: 'time', label: 'timestamp (number)'},
{id: 'timeRounded', label: 'timestamp rounded (number)'},
{id: 'string', label: 'Text'}
], outputTSFormats: [
{id: 0, group: 'time', name: 'ms', label: 'milliseconds'},
{id: 1, group: 'time', name: 'sec', label: 'seconds'},
{id: 2, group: 'time', name: 'min', label: 'minutes'},
{id: 3, group: 'time', name: 'hour', label: 'hours'},
{id: 4, group: 'time', name: 'days', label: 'days'},
{id: 5, group: 'time', name: 'weeks', label: 'weeks'},
{id: 6, group: 'time', name: 'month', label: 'month'},
{id: 7, group: 'time', name: 'years', label: 'years'},
{id: 11, group: 'timeRounded', name: 'sec', label: 'seconds'},
{id: 12, group: 'timeRounded', name: 'min', label: 'minutes'},
{id: 13, group: 'timeRounded', name: 'hour', label: 'hours'},
{id: 14, group: 'timeRounded', name: 'days', label: 'days'},
{id: 15, group: 'timeRounded', name: 'weeks', label: 'weeks'},
{id: 16, group: 'timeRounded', name: 'month', label: 'month'},
{id: 17, group: 'timeRounded', name: 'years', label: 'years'},
{id: 99, group: 'string', name: 'Other', label: 'Other'}
], outputFormatsGroups: [
{id: 'number', label: 'Number'},
{id: 'string', label: 'Text (string)'},
{id: 'time', label: 'time (number) since emit'},
{id: 'dayOfWeek', label: 'day of week'},
{id: 'other', label: 'Other'}
], outputFormats: [
{id: 0, group: 'number', name: 'UNIX', label: 'milliseconds UNIX timestamp'},
{id: 10, group: 'number', name: 'YYYYMMDDHHMMSS', label: 'YYYYMMDDHHMMSS'},
{id: 11, group: 'number', name: 'YYYYMMDD_HHMMSS', label: 'YYYYMMDD.HHMMSS'},
{id: 1, group: 'string', name: 'UTC', label: 'UTC date and time' },
{id: 2, group: 'string', name: 'local', label: 'local date and time'},
{id: 3, group: 'string', name: 'localTime', label: 'local time'},
{id: 12, group: 'string', name: 'localDate', label: 'local date'},
{id: 5, group: 'string', name: 'ISO', label: 'ISO date and time'},
{id: 18, group: 'string', name: 'ISO-2', label: 'ISO date and time of local timezone' },
{id: 6, group: 'time', name: 'ms', label: 'milliseconds'},
{id: 7, group: 'time', name: 'sec', label: 'seconds'},
{id: 8, group: 'time', name: 'min', label: 'minutes'},
{id: 9, group: 'time', name: 'hour', label: 'hours'},
{id: 16, group: 'dayOfWeek', name: 'Day Name', label: 'Day Name, e.g. Monday, 22.12.'},
{id: 17, group: 'dayOfWeek', name: 'Day', label: 'Day in relative, e.g. Today, 22.12.'},
{id: -1, group: 'other', name: 'object', label: 'as object'},
{id: 99, group: 'other', name: 'free definition', label: 'Other'}
], parseFormatsGroups: [
{id: 'number', label: 'Number'},
{id: 'string', label: 'Text (string)'},
{id: 'other', label: 'Other'}
], parseFormats: [
{id: 0, group: 'number', label: 'milliseconds UNIX timestamp', add: 'xxx'},
{id: 1, group: 'string', label: 'ECMA-262', add: 'standard JSON Date representation'},
{id: 2, group: 'string', label: 'try different text Formats, prefer day first like d/M/y (e.g. European format)', add: 'will try different formats, prefer European formats'},
{id: 3, group: 'string', label: 'try different text Formats, prefer month first like M/d/y (e.g. American format)', add: 'will try different formats, prefer American formats'},
{id: 4, group: 'number', label: 'YYYYMMDDHHMMSS', add: 'xxx'},
{id: 5, group: 'number', label: 'YYYYMMDD.HHMMSS', add: 'xxx'},
{id: 98, group: 'other', label: 'various - try different Formats (object, number, text)', add: 'xxx'},
{id: 99, group: 'other', label: 'text - free definition', add: 'xxx'}
], multiplierGroups: [
{id: 'default', label: 'Standard'},
{id: 'other', label: 'Special'}
], multiplier: [
{id: 1, group: 'default', label: 'milliseconds'},
{id: 1000, group: 'default', label: 'seconds'},
{id: 60000, group: 'default', label: 'minutes'},
{id: 3600000, group: 'default', label: 'hours'},
{id: 86400000, group: 'default', label: 'days'},
// {id: 604800000, group: 'other', label: 'weeks'}, //maximum is 2147483647
{id: -1, group: 'other', label: 'month'},
{id: -2, group: 'other', label: 'year'}
], 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 },
{ id: 'containSome', group: 'enhanced', label: 'containSome', operatorCount: 2 },
{ id: 'containEvery', group: 'enhanced', label: 'containEvery', operatorCount: 2 }
]
};
/**
* get selection firlds
* @returns {Object} Object for selection fields
*/
function getSelectFields() { // eslint-disable-line no-unused-vars
return globSelectFields;
return {
operatorsGroups: [
{id: 'default', label: 'compare Timestamp'}
],
operators: [
{id: 1, group: 'default', label: '==', text: 'equal'},
{id: 2, group: 'default', label: '!=', text: 'unequal'},
{id: 3, group: 'default', label: '>', text: 'greater'},
{id: 4, group: 'default', label: '>=', text: 'greater or equal'},
{id: 5, group: 'default', label: '<', text: 'lesser'},
{id: 6, group: 'default', label: '<=', text: 'lesser or equal'}
], operatorTypesGroups: [
{id: 'default', label: 'include into compare'},
{id: 'spec', label: 'special'}
], operatorTypes: [
{id: 11, group: 'default', label: 'Milliseconds'},
{id: 12, group: 'default', label: 'Seconds'},
{id: 13, group: 'default', label: 'Minutes'},
{id: 14, group: 'default', label: 'Hours'},
{id: 15, group: 'default', label: 'Day of Month'},
{id: 16, group: 'default', label: 'Month'},
{id: 17, group: 'default', label: 'Year'},
{id: 18, group: 'spec', label: 'Day of Week'}
], outputTSFormatsGroups: [
{id: 'time', label: 'timestamp (number)'},
{id: 'timeRounded', label: 'timestamp rounded (number)'},
{id: 'string', label: 'Text'}
], outputTSFormats: [
{id: 0, group: 'time', name: 'ms', label: 'milliseconds'},
{id: 1, group: 'time', name: 'sec', label: 'seconds'},
{id: 2, group: 'time', name: 'min', label: 'minutes'},
{id: 3, group: 'time', name: 'hour', label: 'hours'},
{id: 4, group: 'time', name: 'days', label: 'days'},
{id: 5, group: 'time', name: 'weeks', label: 'weeks'},
{id: 6, group: 'time', name: 'month', label: 'month'},
{id: 7, group: 'time', name: 'years', label: 'years'},
{id: 11, group: 'timeRounded', name: 'sec', label: 'seconds'},
{id: 12, group: 'timeRounded', name: 'min', label: 'minutes'},
{id: 13, group: 'timeRounded', name: 'hour', label: 'hours'},
{id: 14, group: 'timeRounded', name: 'days', label: 'days'},
{id: 15, group: 'timeRounded', name: 'weeks', label: 'weeks'},
{id: 16, group: 'timeRounded', name: 'month', label: 'month'},
{id: 17, group: 'timeRounded', name: 'years', label: 'years'},
{id: 99, group: 'string', name: 'Other', label: 'Other'}
], outputFormatsGroups: [
{id: 'number', label: 'Number'},
{id: 'string', label: 'Text (string)'},
{id: 'time', label: 'time (number) since emit'},
{id: 'dayOfWeek', label: 'day of week'},
{id: 'other', label: 'Other'}
], outputFormats: [
{id: 0, group: 'number', name: 'UNIX', label: 'milliseconds UNIX timestamp'},
{id: 10, group: 'number', name: 'YYYYMMDDHHMMSS', label: 'YYYYMMDDHHMMSS'},
{id: 11, group: 'number', name: 'YYYYMMDD_HHMMSS', label: 'YYYYMMDD.HHMMSS'},
{id: 1, group: 'string', name: 'UTC', label: 'UTC date and time' },
{id: 2, group: 'string', name: 'local', label: 'local date and time'},
{id: 3, group: 'string', name: 'localTime', label: 'local time'},
{id: 12, group: 'string', name: 'localDate', label: 'local date'},
{id: 5, group: 'string', name: 'ISO', label: 'ISO date and time'},
{id: 18, group: 'string', name: 'ISO-2', label: 'ISO date and time of local timezone' },
{id: 6, group: 'time', name: 'ms', label: 'milliseconds'},
{id: 7, group: 'time', name: 'sec', label: 'seconds'},
{id: 8, group: 'time', name: 'min', label: 'minutes'},
{id: 9, group: 'time', name: 'hour', label: 'hours'},
{id: 16, group: 'dayOfWeek', name: 'Day Name', label: 'Day Name, e.g. Monday, 22.12.'},
{id: 17, group: 'dayOfWeek', name: 'Day', label: 'Day in relative, e.g. Today, 22.12.'},
{id: -1, group: 'other', name: 'object', label: 'as object'},
{id: 99, group: 'other', name: 'free definition', label: 'Other'}
], parseFormatsGroups: [
{id: 'number', label: 'Number'},
{id: 'string', label: 'Text (string)'},
{id: 'other', label: 'Other'}
], parseFormats: [
{id: 0, group: 'number', label: 'milliseconds UNIX timestamp', add: 'xxx'},
{id: 1, group: 'string', label: 'ECMA-262', add: 'standard JSON Date representation'},
{id: 2, group: 'string', label: 'try different text Formats, prefer day first like d/M/y (e.g. European format)', add: 'will try different formats, prefer European formats'},
{id: 3, group: 'string', label: 'try different text Formats, prefer month first like M/d/y (e.g. American format)', add: 'will try different formats, prefer American formats'},
{id: 4, group: 'number', label: 'YYYYMMDDHHMMSS', add: 'xxx'},
{id: 5, group: 'number', label: 'YYYYMMDD.HHMMSS', add: 'xxx'},
{id: 98, group: 'other', label: 'various - try different Formats (object, number, text)', add: 'xxx'},
{id: 99, group: 'other', label: 'text - free definition', add: 'xxx'}
], multiplierGroups: [
{id: 'default', label: 'Standard'},
{id: 'other', label: 'Special'}
], multiplier: [
{id: 1, group: 'default', label: 'milliseconds'},
{id: 1000, group: 'default', label: 'seconds'},
{id: 60000, group: 'default', label: 'minutes'},
{id: 3600000, group: 'default', label: 'hours'},
{id: 86400000, group: 'default', label: 'days'},
// {id: 604800000, group: 'other', label: 'weeks'}, //maximum is 2147483647
{id: -1, group: 'other', label: 'month'},
{id: -2, group: 'other', label: 'year'}
], 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 },
{ id: 'containSome', group: 'enhanced', label: 'containSome', operatorCount: 2 },
{ id: 'containEvery', group: 'enhanced', label: 'containEvery', operatorCount: 2 }
]
};
}
/**
* get types for typeInputs
* @param {*} node - node representation for access to i18N function (node._())
* @returns {Object} object of types
*/
function getTypes(node) { // eslint-disable-line no-unused-vars

@@ -326,2 +333,38 @@ return {

hasValue: false
},
MoonPhase: {
value: 'pdmPhase',
label: node._('node-red-contrib-sun-position/position-config:common.types.moonPhase','moon phase'),
icon: 'icons/node-red-contrib-sun-position/inputTypeMoonPhase.png',
hasValue: false
},
PhaseMoon: {
value: 'pdmPhaseCheck',
label: node._('node-red-contrib-sun-position/position-config:common.types.moonPhaseCheck','moon phase'),
icon: 'icons/node-red-contrib-sun-position/inputTypeMoonPhase.png',
options: [{
value: 'newMoon',
label: node._('node-red-contrib-sun-position/position-config:common.typeOptions.newMoon')
}, {
value: 'waxingCrescentMoon',
label: node._('node-red-contrib-sun-position/position-config:common.typeOptions.waxingCrescentMoon')
}, {
value: 'firstQuarterMoon',
label: node._('node-red-contrib-sun-position/position-config:common.typeOptions.firstQuarterMoon')
}, {
value: 'waxingGibbousMoon',
label: node._('node-red-contrib-sun-position/position-config:common.typeOptions.waxingGibbousMoon')
}, {
value: 'fullMoon',
label: node._('node-red-contrib-sun-position/position-config:common.typeOptions.fullMoon')
}, {
value: 'waningGibbousMoon',
label: node._('node-red-contrib-sun-position/position-config:common.typeOptions.waningGibbousMoon')
}, {
value: 'lastQuarterMoon',
label: node._('node-red-contrib-sun-position/position-config:common.typeOptions.lastQuarterMoon')
}, {
value: 'waningCrescentMoon',
label: node._('node-red-contrib-sun-position/position-config:common.typeOptions.waningCrescentMoon')
}]
}

@@ -331,121 +374,131 @@ };

const autocompleteFormats = {
dateParseFormat : [
{label: 'yy Year (2 digits)', value: 'yy'},
{label: 'yyyy Year (4 digits)', value: 'yyyy'},
{label: 'M Month (1 digit)', value: 'M'},
{label: 'MM Month (2 digits)', value: 'MM'},
{label: 'MMM Month (name or abbr.)', value: 'MMM'},
{label: 'NNN Month (abbr.)', value: 'NNN'},
{label: 'd Day of Month (1 digit)', value: 'd'},
{label: 'dd Day of Month (2 digits)', value: 'dd'},
{label: 'E Day of Week (abbr.)', value: 'E'},
{label: 'EE Day of Week (name)', value: 'EE'},
{label: 'h Hour (1 digit 1-12)', value: 'h'},
{label: 'hh Hour (2 digits 1-12)', value: 'hh'},
{label: 'H Hour (1 digit 0-23)', value: 'H'},
{label: 'HH Hour (2 digits 0-23)', value: 'HH'},
{label: 'K Hour (1 digit 0-11)', value: 'K'},
{label: 'KK Hour (2 digits 0-11)', value: 'KK'},
{label: 'k Hour (1 digit 1-24)', value: 'k'},
{label: 'kk Hour (2 digits 1-24)', value: 'kk'},
{label: 'm Minute (1 digit)', value: 'm'},
{label: 'mm Minute (2 digits)', value: 'mm'},
{label: 's Second (1 digit)', value: 's'},
{label: 'ss Second (2 digits)', value: 'ss'},
{label: 'l Milliseconds (1-3 digits)', value: 'l'},
{label: 'll Milliseconds (2/3 digits)', value: 'll'},
{label: 'lll Milliseconds (3 digits)', value: 'lll'},
{label: 'L Milliseconds (1 digit rounded)', value: 'L'},
{label: 'LL Milliseconds (2 digits rounded)', value: 'LL'},
{label: 't AM/PM (1 digit)', value: 't'},
{label: 'tt AM/PM (2 digits)', value: 'tt'}
],
dateOutTSFormat: [
{label: 'd Days (1 digit)', value: 'd'},
{label: 'dd Days (2 digits)', value: 'dd'},
{label: 'td total Days (1 digit)', value: 'td'},
{label: 'tdd total Days (2 digits)', value: 'tdd'},
{label: 'h Hours (1-12)', value: 'h'},
{label: 'hh Hours (2 digits 01-12)', value: 'hh'},
{label: 'th total Hours (1-12)', value: 'h'},
{label: 'thh total Hours (2 digits 01-12)', value: 'hh'},
{label: 'H Hours (0-23)', value: 'H'},
{label: 'HH Hours (2 digits 00-23)', value: 'HH'},
{label: 'tH total Hours (0-23)', value: 'H'},
{label: 'tHH total Hours (2 digits 00-23)', value: 'HH'},
{label: 'K Hours (0-11)', value: 'K'},
{label: 'KK Hours (2 digits 00-11)', value: 'KK'},
{label: 'tK total Hours (0-11)', value: 'K'},
{label: 'tKK total Hours (2 digits 00-11)', value: 'KK'},
{label: 'k Hours (1-24)', value: 'k'},
{label: 'kk Hours (2 digits 01-24)', value: 'kk'},
{label: 'tk total Hours (1-24)', value: 'k'},
{label: 'tkk total Hours (2 digits 01-24)', value: 'kk'},
{label: 'm Minutes (0-59)', value: 'm'},
{label: 'mm Minutes (2 digits 00-59)', value: 'mm'},
{label: 'tm total Minutes (0-59)', value: 'm'},
{label: 'tmm total Minutes (2 digits 00-59)', value: 'mm'},
{label: 's Second (0-59)', value: 's'},
{label: 'ss Second (2 digits 00-59)', value: 'ss'},
{label: 'ts total Second (0-59)', value: 's'},
{label: 'tss total Second (2 digits 00-59)', value: 'ss'},
{label: 'l Milliseconds (0-999)', value: 'l'},
{label: 'll Milliseconds (2 digits 00-99)', value: 'll'},
{label: 'lll Milliseconds (3 digits 000-999)', value: 'lll'},
{label: 'L Milliseconds (1 digit rounded)', value: 'L'},
{label: 'LL Milliseconds (2 digits rounded)', value: 'LL'},
{label: 'tl total Milliseconds (0-999)', value: 'l'},
{label: 'tll total Milliseconds (2 digits 00-99)', value: 'll'},
{label: 'tlll total Milliseconds (3 digits 000-999)', value: 'lll'},
{label: 't AM/PM (1 digit - Lowercase)', value: 't'},
{label: 'tt AM/PM (2 digits - Lowercase)', value: 'tt'},
{label: 'T AM/PM (1 digit - Uppercase)', value: 'T'},
{label: 'TT AM/PM (2 digits - Uppercase)', value: 'TT'},
{label: 'S date\'s ordinal suffix (st, nd, rd, or th)', value: 'S'}
],
dateOutFormat: [{label: 'yyyy Year (4 digits)', value: 'yyyy'},
{label: 'yy Year (2 digits)', value: 'yy'},
{label: 'M Month (1 digit)', value: 'M'},
{label: 'MM Month (2 digits)', value: 'MM'},
{label: 'MMM Month (abbr.)', value: 'MMM'},
{label: 'NNN Month (name)', value: 'NNN'},
{label: 'd Day of Month (1 digit)', value: 'd'},
{label: 'dd Day of Month (2 digits)', value: 'dd'},
{label: 'E Day of Week (abbr.)', value: 'E'},
{label: 'EE Day of Week (name)', value: 'EE'},
{label: 'h Hour (1-12)', value: 'h'},
{label: 'hh Hour (2 digits 01-12)', value: 'hh'},
{label: 'H Hour (0-23)', value: 'H'},
{label: 'HH Hour (2 digits 00-23)', value: 'HH'},
{label: 'K Hour (0-11)', value: 'K'},
{label: 'KK Hour (2 digits 00-11)', value: 'KK'},
{label: 'k Hour (1-24)', value: 'k'},
{label: 'kk Hour (2 digits 01-24)', value: 'kk'},
{label: 'm Minute (0-59)', value: 'm'},
{label: 'mm Minute (2 digits 00-59)', value: 'mm'},
{label: 's Second (0-59)', value: 's'},
{label: 'ss Second (2 digits 00-59)', value: 'ss'},
{label: 'l Milliseconds (0-999)', value: 'l'},
{label: 'll Milliseconds (2/3 digits 00-999)', value: 'll'},
{label: 'lll Milliseconds (3 digits 000-999)', value: 'lll'},
{label: 'L Milliseconds (round to 1 digit 0-9)', value: 'L'},
{label: 'LL Milliseconds (round to 2 digits 00-99)', value: 'LL'},
{label: 't AM/PM (1 digit - Lowercase)', value: 't'},
{label: 'tt AM/PM (2 digits - Lowercase)', value: 'tt'},
{label: 'T AM/PM (1 digit - Uppercase)', value: 'T'},
{label: 'TT AM/PM (2 digits - Uppercase)', value: 'TT'},
{label: 'Z time zone (abbr.)', value: 'Z'},
{label: 'o time zone offset (abbr.)', value: 'o'},
{label: 'S date\'s ordinal suffix (st, nd, rd, or th)', value: 'S'},
{label: 'x Day difference', value: 'x'},
{label: 'xx Day difference (name)', value: 'xx'}
]
};
/**
* get auto complete formats
* @returns {Object} object of auto complete formats
*/
function getAutocompleteFormats() {
return {
dateParseFormat : [
{label: 'yy Year (2 digits)', value: 'yy'},
{label: 'yyyy Year (4 digits)', value: 'yyyy'},
{label: 'M Month (1 digit)', value: 'M'},
{label: 'MM Month (2 digits)', value: 'MM'},
{label: 'MMM Month (name or abbr.)', value: 'MMM'},
{label: 'NNN Month (abbr.)', value: 'NNN'},
{label: 'd Day of Month (1 digit)', value: 'd'},
{label: 'dd Day of Month (2 digits)', value: 'dd'},
{label: 'E Day of Week (abbr.)', value: 'E'},
{label: 'EE Day of Week (name)', value: 'EE'},
{label: 'h Hour (1 digit 1-12)', value: 'h'},
{label: 'hh Hour (2 digits 1-12)', value: 'hh'},
{label: 'H Hour (1 digit 0-23)', value: 'H'},
{label: 'HH Hour (2 digits 0-23)', value: 'HH'},
{label: 'K Hour (1 digit 0-11)', value: 'K'},
{label: 'KK Hour (2 digits 0-11)', value: 'KK'},
{label: 'k Hour (1 digit 1-24)', value: 'k'},
{label: 'kk Hour (2 digits 1-24)', value: 'kk'},
{label: 'm Minute (1 digit)', value: 'm'},
{label: 'mm Minute (2 digits)', value: 'mm'},
{label: 's Second (1 digit)', value: 's'},
{label: 'ss Second (2 digits)', value: 'ss'},
{label: 'l Milliseconds (1-3 digits)', value: 'l'},
{label: 'll Milliseconds (2/3 digits)', value: 'll'},
{label: 'lll Milliseconds (3 digits)', value: 'lll'},
{label: 'L Milliseconds (1 digit rounded)', value: 'L'},
{label: 'LL Milliseconds (2 digits rounded)', value: 'LL'},
{label: 't AM/PM (1 digit)', value: 't'},
{label: 'tt AM/PM (2 digits)', value: 'tt'}
],
dateOutTSFormat: [
{label: 'd Days (1 digit)', value: 'd'},
{label: 'dd Days (2 digits)', value: 'dd'},
{label: 'td total Days (1 digit)', value: 'td'},
{label: 'tdd total Days (2 digits)', value: 'tdd'},
{label: 'h Hours (1-12)', value: 'h'},
{label: 'hh Hours (2 digits 01-12)', value: 'hh'},
{label: 'th total Hours (1-12)', value: 'h'},
{label: 'thh total Hours (2 digits 01-12)', value: 'hh'},
{label: 'H Hours (0-23)', value: 'H'},
{label: 'HH Hours (2 digits 00-23)', value: 'HH'},
{label: 'tH total Hours (0-23)', value: 'H'},
{label: 'tHH total Hours (2 digits 00-23)', value: 'HH'},
{label: 'K Hours (0-11)', value: 'K'},
{label: 'KK Hours (2 digits 00-11)', value: 'KK'},
{label: 'tK total Hours (0-11)', value: 'K'},
{label: 'tKK total Hours (2 digits 00-11)', value: 'KK'},
{label: 'k Hours (1-24)', value: 'k'},
{label: 'kk Hours (2 digits 01-24)', value: 'kk'},
{label: 'tk total Hours (1-24)', value: 'k'},
{label: 'tkk total Hours (2 digits 01-24)', value: 'kk'},
{label: 'm Minutes (0-59)', value: 'm'},
{label: 'mm Minutes (2 digits 00-59)', value: 'mm'},
{label: 'tm total Minutes (0-59)', value: 'm'},
{label: 'tmm total Minutes (2 digits 00-59)', value: 'mm'},
{label: 's Second (0-59)', value: 's'},
{label: 'ss Second (2 digits 00-59)', value: 'ss'},
{label: 'ts total Second (0-59)', value: 's'},
{label: 'tss total Second (2 digits 00-59)', value: 'ss'},
{label: 'l Milliseconds (0-999)', value: 'l'},
{label: 'll Milliseconds (2 digits 00-99)', value: 'll'},
{label: 'lll Milliseconds (3 digits 000-999)', value: 'lll'},
{label: 'L Milliseconds (1 digit rounded)', value: 'L'},
{label: 'LL Milliseconds (2 digits rounded)', value: 'LL'},
{label: 'tl total Milliseconds (0-999)', value: 'l'},
{label: 'tll total Milliseconds (2 digits 00-99)', value: 'll'},
{label: 'tlll total Milliseconds (3 digits 000-999)', value: 'lll'},
{label: 't AM/PM (1 digit - Lowercase)', value: 't'},
{label: 'tt AM/PM (2 digits - Lowercase)', value: 'tt'},
{label: 'T AM/PM (1 digit - Uppercase)', value: 'T'},
{label: 'TT AM/PM (2 digits - Uppercase)', value: 'TT'},
{label: 'S date\'s ordinal suffix (st, nd, rd, or th)', value: 'S'}
],
dateOutFormat: [{label: 'yyyy Year (4 digits)', value: 'yyyy'},
{label: 'yy Year (2 digits)', value: 'yy'},
{label: 'M Month (1 digit)', value: 'M'},
{label: 'MM Month (2 digits)', value: 'MM'},
{label: 'MMM Month (abbr.)', value: 'MMM'},
{label: 'NNN Month (name)', value: 'NNN'},
{label: 'd Day of Month (1 digit)', value: 'd'},
{label: 'dd Day of Month (2 digits)', value: 'dd'},
{label: 'E Day of Week (abbr.)', value: 'E'},
{label: 'EE Day of Week (name)', value: 'EE'},
{label: 'h Hour (1-12)', value: 'h'},
{label: 'hh Hour (2 digits 01-12)', value: 'hh'},
{label: 'H Hour (0-23)', value: 'H'},
{label: 'HH Hour (2 digits 00-23)', value: 'HH'},
{label: 'K Hour (0-11)', value: 'K'},
{label: 'KK Hour (2 digits 00-11)', value: 'KK'},
{label: 'k Hour (1-24)', value: 'k'},
{label: 'kk Hour (2 digits 01-24)', value: 'kk'},
{label: 'm Minute (0-59)', value: 'm'},
{label: 'mm Minute (2 digits 00-59)', value: 'mm'},
{label: 's Second (0-59)', value: 's'},
{label: 'ss Second (2 digits 00-59)', value: 'ss'},
{label: 'l Milliseconds (0-999)', value: 'l'},
{label: 'll Milliseconds (2/3 digits 00-999)', value: 'll'},
{label: 'lll Milliseconds (3 digits 000-999)', value: 'lll'},
{label: 'L Milliseconds (round to 1 digit 0-9)', value: 'L'},
{label: 'LL Milliseconds (round to 2 digits 00-99)', value: 'LL'},
{label: 't AM/PM (1 digit - Lowercase)', value: 't'},
{label: 'tt AM/PM (2 digits - Lowercase)', value: 'tt'},
{label: 'T AM/PM (1 digit - Uppercase)', value: 'T'},
{label: 'TT AM/PM (2 digits - Uppercase)', value: 'TT'},
{label: 'Z time zone (abbr.)', value: 'Z'},
{label: 'o time zone offset (abbr.)', value: 'o'},
{label: 'S date\'s ordinal suffix (st, nd, rd, or th)', value: 'S'},
{label: 'x Day difference', value: 'x'},
{label: 'xx Day difference (name)', value: 'xx'}
]
};
}
// #region functions
/**
* getcurrent cursor position
* @returns {number|undefined} current cursor position
*/
$.fn.getCursorPosition = function () {
const input = this.get(0);
if (!input) {
return;
return undefined;
} // No (input) element found

@@ -465,4 +518,11 @@

}
return undefined;
};
/**
*initializes a value
* @param {*} data - object containing property
* @param {string} id - id of the property
* @param {*} newVal returns the new value
*/
function initializeValue(data, id, newVal) { // eslint-disable-line no-unused-vars

@@ -477,4 +537,9 @@ if (data[id] === null || typeof data[id] === 'undefined') {

/**
* initializes an inputbos with autocomplete
* @param {jQuery} inputBox - jsQuery selector of the input box
* @param {string} dataListID - id of the datalist from getAutocompleteFormats()
*/
function autocomplete(inputBox, dataListID) { // eslint-disable-line no-unused-vars
const dataList = autocompleteFormats[dataListID];
const dataList = getAutocompleteFormats()[dataListID];
// don't navigate away from the field on tab when selecting an item

@@ -515,11 +580,18 @@ inputBox.on('keydown', function (event) {

function appendOptions(node, parent, elementName, limit) { // eslint-disable-line no-unused-vars
/**
* append options to a select field
* @param {*} node - node representation for access to i18N function (node._())
* @param {jQuery} parent - jQuery selector of the parent element (<select> - field)
* @param {string} elementName - name of the element from getSelectFields()
* @param {Function} filter - function for filter the elements
*/
function appendOptions(node, parent, elementName, filter) { // eslint-disable-line no-unused-vars
// console.log('appendOptions elementName='+ elementName + ' limit='+limit);
const groups = globSelectFields[elementName + 'Groups'];
const groups = getSelectFields()[elementName + 'Groups'];
if (!groups) {
throw new Error('no group "' + elementName + 'Groups" in globSelectFields found!');
throw new Error('no group "' + elementName + 'Groups" in getSelectFields() found!');
}
const elements = globSelectFields[elementName];
const elements = getSelectFields()[elementName];
if (!groups) {
throw new Error('no elements "' + elementName + '" in globSelectFields found!');
throw new Error('no elements "' + elementName + '" in getSelectFields() found!');
}

@@ -532,4 +604,4 @@ const groupLength = groups.length;

if (groups[gIndex].id === elements[eIndex].group) {
if (limit) {
if (limit(elements[eIndex])) {
if (filter) {
if (filter(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));

@@ -545,2 +617,20 @@ }

/**
* @typedef {Object} tiData
* @property {string} valueProp - the name of the value property
* @property {string} typeProp - the name of the type property
* @property {string} [defaultValue] - value for the default value
* @property {string} [defaultType] - value for the default type
* @property {string} [tooltip] - a tootlip for the input field
* @property {string} [width] - width of the input field
* @property {string} [onChange] - on change function
* @property {string} [onFocus] - on focus / focus lost function
*/
/**
* setup a typedInput for node-red
* @param {*} node - node representation for access to i18N function (node._())
* @param {tiData} data - data of the typed input
* @returns {jQuery} jQuery selector of the typeInput field - ideal for chaining
*/
function setupTInput(node, data) { // eslint-disable-line no-unused-vars

@@ -562,3 +652,3 @@ const $inputField = $('#node-input-' + data.valueProp);

node[data.valueProp] = data.defaultValue;
$inputField.val(node[data.defaultValue]);
$inputField.val(data.defaultValue);
}

@@ -606,7 +696,17 @@ } else {

// ************************************************************************************************
function initCombobox(node, $inputSelect, $inputBox, dataList, optionElementName, value, baseWidth, timeFormat) { // eslint-disable-line no-unused-vars
/**
* initializes a combobox (combination of input and select box)
* @param {*} node - node representation for access to i18N function (node._())
* @param {jQuery} $inputSelect - jQuery selector of the select element
* @param {jQuery} $inputBox - jQuery selector of the input element
* @param {string} dataListID - id of the datalist from getAutocompleteFormats()
* @param {string} optionElementName - name of the element from getSelectFields()
* @param {string} value - value of the input/select field
* @param {number} baseWidth - base widtrh of the field combination
* @param {string} [timeFormat] - name of tzhe timeformat from position-config:common.timeFormat...
*/
function initCombobox(node, $inputSelect, $inputBox, dataListID, optionElementName, value, baseWidth, timeFormat) { // eslint-disable-line no-unused-vars
// console.log('initCombobox node=' + node + ' dataList=' + dataList + ' optionElementName=' + optionElementName + ' value=' + value + ' width=' + width); // eslint-disable-line
appendOptions(node, $inputSelect, optionElementName);
autocomplete($inputBox, dataList);
autocomplete($inputBox, dataListID);
const valueNum = Number(value);

@@ -642,4 +742,12 @@ timeFormat = timeFormat || 'default';

function addLabel(row, forEl, symb, text) { // eslint-disable-line no-unused-vars
const lbl = $('<label class="' + forEl + '-lbl" style="width:auto"/>').attr('for', forEl).appendTo(row);
/**
* add a label to a html element
* @param {jQuery} parent - element (row) to append the label
* @param {string} forEl - name of the element to what the label is
* @param {string} [symb] - class name of the symbol e.g. 'fa fa-clock'
* @param {string} [text] - text of the label
* @returns {jQuery} jQuery selector of the new label
*/
function addLabel(parent, forEl, symb, text) { // eslint-disable-line no-unused-vars
const lbl = $('<label class="' + forEl + '-lbl" style="width:auto"/>').attr('for', forEl).appendTo(parent);
if (symb) {

@@ -658,2 +766,15 @@ lbl.append('<i class= "' + symb + '" >');

/**
* @typedef {Object} multiselectTypes
* @property {string} label - the name of the type property
* @property {regex} selection - regular expression selector
*/
/**
* return the label who matches the regex selector for types
* @param {string} val - value to test
* @param {number} [length] - optional output if nothing is found
* @param {multiselectTypes[]} types - array of types
* @returns {string} the selected label or the given length or 'NA'
*/
function getMultiselectText(val, length, types) { // eslint-disable-line no-unused-vars

@@ -665,3 +786,3 @@ for (let index = 0; index < types.length; index++) {

}
if (length > 0) {
if (length && (length > 0)) {
return length;

@@ -672,2 +793,8 @@ }

/**
* set the checkboxes in a multiselect combo box to a value
* @param {string} value - value of the array
* @param {jQuery} field - parent jquery selector element
* @param {multiselectTypes[]} types - array of types
*/
function setMultiselect(value, field, types) { // eslint-disable-line no-unused-vars

@@ -689,10 +816,11 @@ if (value === '*' || typeof value === 'undefined') {

* adds a multiselect combo box to the form
* @param {*} node Node Red Source Node
* @param {*} parent Parent jQuery Element to add multiselect
* @param {*} elementName Name of the element in the node, e.g. 'operatorTypes'
* @param {*} i18N i18N element name, e.g. 'time-comp.operatorTypes'
* @param {*} id element id, e.g. 'node-input-rule-operatorType-1'
* @param {*} node - Node Red Source Node
* @param {jQuery} parent - parent jQuery selector to add multiselect
* @param {string} elementName - Name of the element in the node, e.g. 'operatorTypes'
* @param {string} i18N - i18N element name, e.g. 'time-comp.operatorTypes'
* @param {string} id - element id, e.g. 'node-input-rule-operatorType-1'
* @returns {jQuery} jQuery selector of the multiselect
*/
function multiselect(node, parent, elementName, i18N, id) { // eslint-disable-line no-unused-vars
const types = globSelectFields[elementName + 'Short'];
const types = getSelectFields()[elementName + 'Short'];
const getSelection = function getCBText(parent) {

@@ -721,5 +849,5 @@ const value = parent.find('#option-checkboxes input[type=checkbox]:checked');

list.attr('expanded', 'false');
const groups = globSelectFields[elementName + 'Groups'];
const groups = getSelectFields()[elementName + 'Groups'];
const groupLength = groups.length;
const elements = globSelectFields[elementName];
const elements = getSelectFields()[elementName];
const elementsLength = elements.length;

@@ -762,3 +890,26 @@ for (let gIndex = 0; gIndex < groupLength; gIndex++) {

function getTimeData(result, data) { // eslint-disable-line no-unused-vars
/**
* @typedef {Object} backendData
* @property {string} config - the config object
* @property {('getTimeData'|'getOutDataData')} kind - kind of request
* @property {string} type - type input type
* @property {string} value - type input value
* @property {string} [offsetType] - type input type for offset
* @property {string} [offset] - type input value for offset
* @property {number} [multiplier] - multiplier to value
* @property {boolean} [next] - identifier if the next should be output
* @property {string} [days] - allowed days identifier
* @property {string} [format] - output format
*/
/**
* get type Data from the backend
* @param {*} result
* @param {backendData} data
* @returns {*} object based on the request
*/
function getBackendData(result, data) { // eslint-disable-line no-unused-vars
// console.log('getBackendData'); // eslint-disable-line
// console.log(data); // eslint-disable-line
// data
if (!data || data.type === 'none' || data.type === '' || data.type === 'json' || data.type === 'jsonata' || data.type === 'bin') {

@@ -779,31 +930,5 @@ result({ value: data.type});

} else {
const url = '/sun-position/data?kind=getTimeData&' + jQuery.param( data );
const url = 'sun-position/data?' + jQuery.param( data );
$.getJSON(url, result);
}
}
function getDateData(result, data) { // eslint-disable-line no-unused-vars
const url = '/sun-position/data?kind=getDateData&' + jQuery.param( data );
$.getJSON(url, result);
}
function getOutDataData(result, data) { // eslint-disable-line no-unused-vars
if (data.type === 'none' || data.type === '' || data.type === 'json' || data.type === 'jsonata' || data.type === 'bin') {
result(data.type);
} else if (data.type === 'num' || data.type === 'str' || data.type === 'bool') {
result(data.value);
} else if (data.type === 'msg' || data.type === 'flow' || data.type === 'global' || data.type === 'env') {
result(data.type + '.' + data.value);
} else if (data.type === 'msgPayload') {
result('msg.payload');
} else if (data.type === 'msgTs') {
result('msg.ts');
} else if (data.type === 'msgLC') {
result('msg.lc');
} else if (data.type === 'msgValue') {
result('msg.value');
} else {
const url = '/sun-position/data?kind=getOutDataData&' + jQuery.param( data );
$.getJSON(url, result);
}
}

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

'use strict';
/**
* sunPositionNode
* @param {*} config - configuration
*/
function sunPositionNode(config) {

@@ -42,2 +45,6 @@ RED.nodes.createNode(this, config);

}
if (!hlp.isValidDate(now)) {
now = new Date();
node.error(RED._('node-red-contrib-sun-position/position-config:errors.invalidParameter', { param: 'msg.ts', type: 'Date', newValue: now }));
}
if (!this.positionConfig) {

@@ -60,3 +67,3 @@ node.error(RED._('node-red-contrib-sun-position/position-config:errors.pos-config'));

this.send(ports);
return;
return null;
}

@@ -67,8 +74,17 @@

if (node.startType !== 'none') {
const startTime = node.positionConfig.getTimeProp(node, msg, node.startType, node.start, node.startOffsetType, node.startOffset, node.startOffsetMultiplier);
node.debug('startTime: ' + util.inspect(startTime));
// const startTime = node.positionConfig.getTimeProp(node, msg, node.startType, node.start, node.startOffsetType, node.startOffset, node.startOffsetMultiplier);
const startTime = node.positionConfig.getTimeProp(node, msg, {
type: node.startType,
value : node.start,
offsetType : node.startOffsetType,
offset : node.startOffset,
multiplier : node.startOffsetMultiplier,
now
});
node.debug('startTime: ' + util.inspect(startTime, { colors: true, compact: 10, breakLength: Infinity }));
if (startTime.error) {
errorStatus = 'could not evaluate start time';
node.error(startTime.error);
// node.debug('startTime: ' + util.inspect(startTime));
// node.debug('startTime: ' + util.inspect(startTime, { colors: true, compact: 10, breakLength: Infinity }));
} else {

@@ -80,8 +96,17 @@ ports[0].payload.startTime = startTime.value.getTime();

if (node.endType !== 'none') {
const endTime = node.positionConfig.getTimeProp(node, msg, node.endType, node.end, node.endOffsetType, node.endOffset, node.endOffsetMultiplier);
node.debug('endTime: ' + util.inspect(endTime));
// const endTime = node.positionConfig.getTimeProp(node, msg, node.endType, node.end, node.endOffsetType, node.endOffset, node.endOffsetMultiplier);
const endTime = node.positionConfig.getTimeProp(node, msg, {
type: node.endType,
value : node.end,
offsetType : node.endOffsetType,
offset : node.endOffset,
multiplier : node.endOffsetMultiplier,
now
});
node.debug('endTime: ' + util.inspect(endTime, { colors: true, compact: 10, breakLength: Infinity }));
if (endTime.error) {
errorStatus = 'could not evaluate end time';
node.error(endTime.error);
// node.debug('endTime: ' + util.inspect(endTime));
// node.debug('endTime: ' + util.inspect(endTime, { colors: true, compact: 10, breakLength: Infinity }));
} else {

@@ -147,5 +172,5 @@ ports[0].payload.endTime = endTime.value.getTime();

this.status({
fill: fill,
fill,
shape: 'dot',
text: text
text
});

@@ -157,3 +182,3 @@ }

node.error(err.message);
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.log(util.inspect(err, Object.getOwnPropertyNames(err)));
node.status({

@@ -165,4 +190,13 @@ fill: 'red',

}
return null;
});
/**
* get the value ofd a numeric property
* @param {*} srcNode - source node
* @param {*} msg - message object
* @param {string} vType - type
* @param {string} value - value
* @returns {number} the result value for the type and value
*/
function getNumProp(srcNode, msg, vType, value) {

@@ -169,0 +203,0 @@ try {

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

'use strict';
/**
* timeCompNode
* @param {*} config - configuration
*/
function timeCompNode(config) {

@@ -17,6 +20,6 @@ RED.nodes.createNode(this, config);

this.positionConfig = RED.nodes.getNode(config.positionConfig);
// this.debug('initialize time Node ' + util.inspect(config));
// this.debug('initialize time Node ' + util.inspect(config, { colors: true, compact: 10, breakLength: Infinity }));
const node = this;
this.on('input', msg => {
this.on('input', msg => { // eslint-disable-line complexity
if (node.positionConfig === null ||

@@ -34,9 +37,30 @@ config.operator === null ||

try {
const inputData = node.positionConfig.getDateFromProp(node, msg, config.inputType, config.input, config.inputFormat, config.inputOffset, config.inputOffsetType, config.inputOffsetMultiplier);
// const inputData = node.positionConfig.getDateFromProp(node, msg, config.inputType, config.input, config.inputFormat, config.inputOffset, config.inputOffsetType, config.inputOffsetMultiplier);
const inputData = node.positionConfig.getTimeProp(node, msg, {
type: config.inputType,
value: config.input,
format: config.inputFormat,
offsetType: config.inputOffsetType,
offset: config.inputOffset,
multiplier: config.inputOffsetMultiplier
});
if (inputData.error) {
throw new Error(inputData.error);
}
if (config.result1Type !== 'none') {
let resultObj = null;
if (config.result1ValueType === 'input') {
resultObj = hlp.getFormattedDateOut(inputData, config.result1Format);
resultObj = hlp.getFormattedDateOut(inputData.value, config.result1Format);
} else {
resultObj = node.positionConfig.getOutDataProp(node, msg, config.result1ValueType, config.result1Value, config.result1Format, config.result1Offset, config.result1OffsetType, config.result1Multiplier, true);
// resultObj = node.positionConfig.getOutDataProp(node, msg, config.result1ValueType, config.result1Value, config.result1Format, config.result1Offset, config.result1OffsetType, config.result1Multiplier, true);
resultObj = node.positionConfig.getOutDataProp(node, msg, {
type: config.result1ValueType,
value: config.result1Value,
format: config.result1Format,
offsetType: config.result1OffsetType,
offset: config.result1Offset,
multiplier: config.result1Multiplier,
next: true
});
}

@@ -79,8 +103,25 @@

}
const ruleoperand = node.positionConfig.getDateFromProp(node, msg, rule.operandType, rule.operandValue, rule.format, rule.offsetValue, 'num', rule.multiplier);
/* const ruleoperand = node.positionConfig.getDateFromProp(node, msg, rule.operandType, rule.operandValue, rule.format, rule.offsetValue, 'num', rule.multiplier);
if (!ruleoperand) {
continue;
} */
let ruleoperand = null;
try {
ruleoperand = node.positionConfig.getTimeProp(node, msg, {
type: rule.operandType,
value: rule.operandValue,
format: rule.format,
offsetType: 'num',
offset: rule.offsetValue,
multiplier: rule.multiplier
});
} catch (ex) {
continue;
}
// node.debug('operand=' + util.inspect(ruleoperand));
// node.debug('operator=' + util.inspect(rule.operator));
if (!ruleoperand || ruleoperand.error) {
continue;
}
ruleoperand = ruleoperand.value;
// node.debug('operand=' + util.inspect(ruleoperand, { colors: true, compact: 10, breakLength: Infinity }));
// node.debug('operator=' + util.inspect(rule.operator, { colors: true, compact: 10, breakLength: Infinity }));

@@ -111,5 +152,5 @@ let compare = null;

if (compare) {
const inputOperant = new Date(inputData);
// node.debug('inputOperant=' + util.inspect(inputOperant));
// node.debug('operatorType=' + util.inspect(rule.operatorType));
const inputOperant = new Date(inputData.value);
// node.debug('inputOperant=' + util.inspect(inputOperant, { colors: true, compact: 10, breakLength: Infinity }));
// node.debug('operatorType=' + util.inspect(rule.operatorType, { colors: true, compact: 10, breakLength: Infinity }));
if (rule.operatorType !== '*' && typeof rule.operatorType !== 'undefined') {

@@ -182,7 +223,7 @@ switch (rule.operatorType) {

// node.debug('inputData=' + util.inspect(inputData));
// node.debug('operand=' + util.inspect(ruleoperand));
// node.debug('inputData=' + util.inspect(inputData, { colors: true, compact: 10, breakLength: Infinity }));
// node.debug('operand=' + util.inspect(ruleoperand, { colors: true, compact: 10, breakLength: Infinity }));
break;
}
// node.debug('result=' + util.inspect(result));
// node.debug('result=' + util.inspect(result, { colors: true, compact: 10, breakLength: Infinity }));
}

@@ -208,7 +249,7 @@ }

node.status({
text: inputData.toISOString()
text: inputData.value.toISOString()
});
node.send(resObj);
} catch (err) {
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.log(util.inspect(err, Object.getOwnPropertyNames(err)));
node.status({

@@ -215,0 +256,0 @@ fill: 'red',

@@ -14,2 +14,8 @@ /********************************************

/**
* get the schedule time
* @param {Date} time - time to schedule
* @param {number} [limit] - minimal time limit to schedule
* @returns {number} milliseconds until the defined Date
*/
function tsGetScheduleTime(time, limit) {

@@ -27,28 +33,45 @@ const now = new Date();

function tsSetAddProp(node, msg, type, name, valueType, value, format, offset, offsetType, multiplier, days, next) {
if (typeof next === 'undefined' || next === null || next === true || next === 'true') {
next = true;
} else if (next === 'false' || next === false) {
next = false;
// function tsSetAddProp(node, msg, type, name, valueType, value, format, offset, offsetType, multiplier, days, next) {
/**
*
* @param {*} node
* @param {*} msg
* @param {*} type
* @param {*} name
* @param {*} valueType
* @param {*} value
* @param {*} format
* @param {*} offset
* @param {*} offsetType
* @param {*} multiplier
* @param {*} days
* @param {*} next
*/
function tsSetAddProp(node, msg, data) {
if (typeof data.next === 'undefined' || data.next === null || data.next === true || data.next === 'true') {
data.next = true;
} else if (data.next === 'false' || data.next === false) {
data.next = false;
}
// node.debug(`tsSetAddProp ${msg}, ${type}, ${name}, ${valueType}, ${value}, ${format}, ${offset}, ${offsetType}, ${multiplier}, ${days}`);
if (type !== 'none') {
const res = node.positionConfig.getOutDataProp(node, msg, valueType, value, format, offset, offsetType, multiplier, next, days);
if (data.outType !== 'none') {
// const res = node.positionConfig.getOutDataProp(node, msg, valueType, value, format, offset, offsetType, multiplier, next, days);
const res = node.positionConfig.getOutDataProp(node, msg, data);
if (res === null || (typeof res === 'undefined')) {
throw new Error('could not evaluate ' + valueType + '.' + value);
throw new Error('could not evaluate ' + data.type + '.' + data.value);
} else if (res.error) {
this.error('error on getting additional payload 1: ' + res.error);
} else if (type === 'msgPayload') {
} else if (data.outType === 'msgPayload') {
msg.payload = res;
} else if (type === 'msgTs') {
} else if (data.outType === 'msgTs') {
msg.ts = res;
} else if (type === 'msgLc') {
} else if (data.outType === 'msgLc') {
msg.lc = res;
} else if (type === 'msgValue') {
} else if (data.outType === 'msgValue') {
msg.value = res;
} else if (type === 'msg') {
RED.util.setMessageProperty(msg, name, res);
} else if ((type === 'flow' || type === 'global')) {
const contextKey = RED.util.parseContextStore(name);
node.context()[type].set(contextKey.key, res, contextKey.store);
} else if (data.outType === 'msg') {
RED.util.setMessageProperty(msg, data.outValue, res);
} else if ((data.outType === 'flow' || data.outType === 'global')) {
const contextKey = RED.util.parseContextStore(data.outValue);
node.context()[data.outType].set(contextKey.key, res, contextKey.store);
}

@@ -58,2 +81,6 @@ }

/**
* timeInjectNode
* @param {*} config - configuration
*/
function timeInjectNode(config) {

@@ -63,3 +90,3 @@ RED.nodes.createNode(this, config);

this.positionConfig = RED.nodes.getNode(config.positionConfig);
// this.debug('initialize timeInjectNode ' + util.inspect(config));
// this.debug('initialize timeInjectNode ' + util.inspect(config, { colors: true, compact: 10, breakLength: Infinity }));

@@ -97,2 +124,8 @@ this.time = config.time;

/**
* creates the timeout
* @param {*} node - the node representation
* @param {boolean} [_onInit] - _true_ if is in initialisation
* @returns {object} state or error
*/
function doCreateTimeout(node, _onInit) {

@@ -111,3 +144,12 @@ let errorStatus = '';

if (node.timeType !== 'none' && node.positionConfig) {
node.nextTimeData = node.positionConfig.getTimeProp(node, undefined, node.timeType, node.time, node.offsetType, node.offset, node.offsetMultiplier, true, node.timeDays);
// node.nextTimeData = node.positionConfig.getTimeProp(node, undefined, node.timeType, node.time, node.offsetType, node.offset, node.offsetMultiplier, true, node.timeDays);
node.nextTimeData = node.positionConfig.getTimeProp(node, undefined, {
type: node.timeType,
value : node.time,
offsetType : node.offsetType,
offset : node.offset,
multiplier : node.offsetMultiplier,
next : true,
days : node.timeDays
});
if (node.nextTimeData.error) {

@@ -120,3 +162,3 @@ errorStatus = 'could not evaluate time';

}
node.debug('node.nextTimeData=' + util.inspect(node.nextTimeData));
node.debug('node.nextTimeData=' + util.inspect(node.nextTimeData, { colors: true, compact: 10, breakLength: Infinity }));
node.error(node.nextTimeData.error);

@@ -133,3 +175,13 @@ } else {

// (_srcNode, msg, vType, value, offset, offsetType, multiplier, next, days)
node.nextTimeAltData = node.positionConfig.getTimeProp(node, undefined, node.timeAltType, node.timeAlt, node.timeAltOffsetType, node.timeAltOffset, node.timeAltOffsetMultiplier, true, node.timeAltDays);
// node.nextTimeAltData = node.positionConfig.getTimeProp(node, undefined, node.timeAltType, node.timeAlt, node.timeAltOffsetType, node.timeAltOffset, node.timeAltOffsetMultiplier, true, node.timeAltDays);
node.nextTimeAltData = node.positionConfig.getTimeProp(node, undefined, {
type: node.timeAltType,
value : node.timeAlt,
offsetType : node.timeAltOffsetType,
offset : node.timeAltOffset,
multiplier : node.timeAltOffsetMultiplier,
next : true,
days : node.timeAltDays
});
if (node.nextTimeAltData.error) {

@@ -142,3 +194,3 @@ errorStatus = 'could not evaluate alternate time';

}
node.debug('node.nextTimeAltData=' + util.inspect(node.nextTimeAltData));
node.debug('node.nextTimeAltData=' + util.inspect(node.nextTimeAltData, { colors: true, compact: 10, breakLength: Infinity }));
node.error(node.nextTimeAltData.error);

@@ -152,3 +204,3 @@ } else {

if ((node.nextTime !== null) && (errorStatus === '')) {
if (!(node.nextTime instanceof Date) || node.nextTime === 'Invalid Date' || isNaN(node.nextTime)) {
if (!hlp.isValidDate(node.nextTime)) {
hlp.handleError(this, 'Invalid time format', undefined, 'internal error!');

@@ -196,3 +248,3 @@ return { state:'error', done: false, statusMsg: 'internal error!', errorMsg: 'Invalid time format'};

node.error(err.message);
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.log(util.inspect(err, Object.getOwnPropertyNames(err)));
node.status({

@@ -214,2 +266,3 @@ fill: 'red',

node.emit('input', msg);
return { state: 'emit', done: true };
}, millisec, isAlt, isAltFirst);

@@ -281,3 +334,13 @@ }

}
const value = node.positionConfig.getOutDataProp(node, msg, config.payloadType, config.payload, config.payloadTimeFormat, node.payloadOffset, config.payloadOffsetType, config.payloadOffsetMultiplier, true);
// const value = node.positionConfig.getOutDataProp(node, msg, config.payloadType, config.payload, config.payloadTimeFormat, node.payloadOffset, config.payloadOffsetType, config.payloadOffsetMultiplier, true);
const value = node.positionConfig.getOutDataProp(node, msg, {
type: config.payloadType,
value: config.payload,
format: config.payloadTimeFormat,
offsetType: config.payloadOffsetType,
offset: config.payloadOffset,
multiplier: config.payloadOffsetMultiplier,
next: true
});
if (value === null || (typeof value === 'undefined')) {

@@ -291,8 +354,38 @@ throw new Error('could not evaluate ' + config.payloadType + '.' + config.payload);

tsSetAddProp(this, msg, config.addPayload1Type, config.addPayload1, config.addPayload1ValueType, config.addPayload1Value,
config.addPayload1Format, config.addPayload1Offset, config.addPayload1OffsetType, config.addPayload1OffsetMultiplier, config.addPayload1Days, config.addPayload1Next);
tsSetAddProp(this, msg, config.addPayload2Type, config.addPayload2, config.addPayload2ValueType, config.addPayload2Value,
config.addPayload2Format, config.addPayload2Offset, config.addPayload2OffsetType, config.addPayload2OffsetMultiplier, config.addPayload2Days, config.addPayload2Next);
tsSetAddProp(this, msg, config.addPayload3Type, config.addPayload3, config.addPayload3ValueType, config.addPayload3Value,
config.addPayload3Format, config.addPayload3Offset, config.addPayload3OffsetType, config.addPayload3OffsetMultiplier, config.addPayload3Days, config.addPayload3Next);
tsSetAddProp(this, msg, {
outType: config.addPayload1Type,
outValue: config.addPayload1,
type: config.addPayload1ValueType,
value: config.addPayload1Value,
format: config.addPayload1Format,
offsetType: config.addPayload1OffsetType,
offset: config.addPayload1Offset,
multiplier: config.addPayload1OffsetMultiplier,
next: config.addPayload1Next,
days: config.addPayload1Days
});
tsSetAddProp(this, msg, {
outType: config.addPayload2Type,
outValue: config.addPayload2,
type: config.addPayload2ValueType,
value: config.addPayload2Value,
format: config.addPayload2Format,
offsetType: config.addPayload2OffsetType,
offset: config.addPayload2Offset,
multiplier: config.addPayload2OffsetMultiplier,
next: config.addPayload2Next,
days: config.addPayload2Days
});
tsSetAddProp(this, msg, {
outType: config.addPayload3Type,
outValue: config.addPayload3,
type: config.addPayload3ValueType,
value: config.addPayload3Value,
format: config.addPayload3Format,
offsetType: config.addPayload3OffsetType,
offset: config.addPayload3Offset,
multiplier: config.addPayload3OffsetMultiplier,
next: config.addPayload3Next,
days: config.addPayload3Days
});

@@ -302,3 +395,3 @@ node.send(msg);

node.error(err.message);
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.log(util.inspect(err, Object.getOwnPropertyNames(err)));
node.status({

@@ -340,3 +433,3 @@ fill: 'red',

node.error(err.message);
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.log(util.inspect(err, Object.getOwnPropertyNames(err)));
node.status({

@@ -357,3 +450,3 @@ fill: 'red',

node.error(err.message);
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.log(util.inspect(err, Object.getOwnPropertyNames(err)));
node.status({

@@ -360,0 +453,0 @@ fill: 'red',

@@ -18,2 +18,8 @@ /********************************************

/**
* get the differense between two month relative
* @param {Date} d1 - Date 1
* @param {Date} d1 - Date 2
* @returns {number} differences in month between the two dates
*/
function getMonthDiff(d1,d2) {

@@ -30,2 +36,8 @@ let months = (d2.getFullYear() - d1.getFullYear()) * 12;

/**
* get the differense between two month absolute
* @param {Date} d1 - Date 1
* @param {Date} d1 - Date 2
* @returns {number} differences in month between the two dates
*/
function getMonthDiffAbs(d1,d2) {

@@ -40,2 +52,8 @@ let months = (d2.getFullYear() - d1.getFullYear()) * 12;

/**
* get the differense between years absolute
* @param {Date} d1 - Date 1
* @param {Date} d1 - Date 2
* @returns {number} differences in years between the two dates
*/
function getYearDiffAbs(d1,d2) {

@@ -49,2 +67,9 @@ let years = (d2.getFullYear() - d1.getFullYear());

/**
* format timespan
* @param {Date} d1 - Date 1
* @param {Date} d1 - Date 2
* @param {string} format - the out format
* @returns {string} differences in years between the two dates
*/
function formatTS(d1, d2, format) {

@@ -142,2 +167,9 @@ const token = /[yMw]{1,2}|t?[dhHkKms]{1,2}|t?l{1,3}|[tT]{1,2}|S|L|"[^"]*"|'[^']*'/g;

/**
* get a formated timespan
* @param {Date} date1 - Date 1
* @param {Date} date2 - Date 2
* @param {string} format - the out format
* @returns {string} the formatet timespan
*/
function getFormattedTimeSpanOut(date1, date2, format) {

@@ -206,3 +238,3 @@ format = format || 0;

},
timeSpan: timeSpan,
timeSpan,
timeSpanAbs: {

@@ -232,3 +264,6 @@ ms: timeSpan % 1000,

'use strict';
/**
* timeSpanNode
* @param {*} config - configuration
*/
function timeSpanNode(config) {

@@ -253,10 +288,34 @@ RED.nodes.createNode(this, config);

try {
const operand1 = node.positionConfig.getDateFromProp(node, msg, config.operand1Type, config.operand1, config.operand1Format, config.operand1Offset, config.operand1OffsetType, config.operand1OffsetMultiplier);
/* const operand1 = node.positionConfig.getDateFromProp(node, msg, config.operand1Type, config.operand1, config.operand1Format, config.operand1Offset, config.operand1OffsetType, config.operand1OffsetMultiplier);
if (operand1 === null) {
return null;
} */
const operand1 = node.positionConfig.getTimeProp(node, msg, {
type: config.operand1Type,
value: config.operand1,
format: config.operand1Format,
offsetType: config.operand1OffsetType,
offset: config.operand1Offset,
multiplier: config.operand1OffsetMultiplier
});
if (operand1.error) {
throw new Error(operand1.error);
}
const operand2 = node.positionConfig.getDateFromProp(node, msg, config.operand2Type, config.operand2, config.operand2Format, config.operand2Offset, config.operand2OffsetType, config.operand2OffsetMultiplier);
/* const operand2 = node.positionConfig.getDateFromProp(node, msg, config.operand2Type, config.operand2, config.operand2Format, config.operand2Offset, config.operand2OffsetType, config.operand2OffsetMultiplier);
if (operand2 === null) {
return null;
} */
const operand2 = node.positionConfig.getTimeProp(node, msg, {
type: config.operand2Type,
value: config.operand2,
format: config.operand2Format,
offsetType: config.operand2OffsetType,
offset: config.operand2Offset,
multiplier: config.operand2OffsetMultiplier
});
if (operand2.error) {
throw new Error(operand2.error);
}
let timeSpan = operand1.getTime() - operand2.getTime();

@@ -270,3 +329,3 @@ if (config.operand === 0) {

if (config.result1ValueType === 'timespan') {
resultObj = getFormattedTimeSpanOut(operand1, operand2, config.result1TSFormat);
resultObj = getFormattedTimeSpanOut(operand1.value, operand2.value, config.result1TSFormat);
resultObj.start.timeLocaleTimeStr = node.positionConfig.toTimeString(resultObj.start.date);

@@ -277,7 +336,16 @@ resultObj.start.timeLocaleDateStr = node.positionConfig.toDateString(resultObj.start.date);

} else if (config.result1ValueType === 'operand1') {
resultObj = hlp.getFormattedDateOut(operand1, config.result1Format);
resultObj = hlp.getFormattedDateOut(operand1.value, config.result1Format);
} else if (config.result1ValueType === 'operand2') {
resultObj = hlp.getFormattedDateOut(operand2, config.result1Format);
resultObj = hlp.getFormattedDateOut(operand2.value, config.result1Format);
} else {
resultObj = node.positionConfig.getOutDataProp(node, msg, config.result1ValueType, config.result1Value, config.result1Format, config.result1Offset, config.result1OffsetType, config.result1Multiplier, true);
// resultObj = node.positionConfig.getOutDataProp(node, msg, config.result1ValueType, config.result1Value, config.result1Format, config.result1Offset, config.result1OffsetType, config.result1Multiplier, true);
resultObj = node.positionConfig.getOutDataProp(node, msg, {
type: config.result1ValueType,
value: config.result1Value,
format: config.result1Format,
offsetType: config.result1OffsetType,
offset: config.result1Offset,
multiplier: config.result1Multiplier,
next: true
});
}

@@ -317,5 +385,5 @@ // to

/*
node.debug('operand ' + util.inspect(ruleoperand));
node.debug('operator ' + util.inspect(rule.operator));
node.debug('operatorType ' + util.inspect(rule.operatorType)); */
node.debug('operand ' + util.inspect(ruleoperand, { colors: true, compact: 10, breakLength: Infinity }));
node.debug('operator ' + util.inspect(rule.operator, { colors: true, compact: 10, breakLength: Infinity }));
node.debug('operatorType ' + util.inspect(rule.operatorType, { colors: true, compact: 10, breakLength: Infinity })); */

@@ -353,3 +421,3 @@ let result = false;

node.error(err.message);
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.log(util.inspect(err, Object.getOwnPropertyNames(err)));
node.status({

@@ -372,3 +440,3 @@ fill: 'red',

node.status({
text: (operand1.getTime() - operand2.getTime()) / 1000 + 's'
text: (operand1.value.getTime() - operand2.value.getTime()) / 1000 + 's'
});

@@ -378,3 +446,3 @@

} catch (err) {
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.log(util.inspect(err, Object.getOwnPropertyNames(err)));
node.status({

@@ -387,2 +455,3 @@ fill: 'red',

}
return null;
});

@@ -389,0 +458,0 @@ }

@@ -13,2 +13,9 @@ /********************************************

'use strict';
/**
* get the Data for compare Date
* @param {number} comparetype - type of compare
* @param {*} msg - message object
* @param {*} node - node object
* @returns {*} Date value
*/
function getDate(comparetype, msg, node) {

@@ -39,3 +46,3 @@ let id = '';

const dto = new Date(msg.ts);
if (dto !== 'Invalid Date' && !isNaN(dto)) {
if (hlp.isValidDate(dto)) {
return dto;

@@ -47,2 +54,9 @@ }

/**
* set the node state
* @param {*} node - the niode Data
* @param {*} data - the state data
* @param {boolean} [_onInit] - indicates if the node in in initialisation
* @returns {boolean}
*/
function setstate(node, data, _onInit) {

@@ -89,2 +103,9 @@ if (data.error) {

/**
* calc the start and end times
* @param {*} node - thje noide data
* @param {*} msg - the messege object
* @param {*} config - the configuration
* @returns {object} containing start and end Dates
*/
function calcWithinTimes(node, msg, config) {

@@ -112,3 +133,3 @@ // node.debug('calcWithinTimes');

}), err);
node.debug(util.inspect(err));
node.log(util.inspect(err, Object.getOwnPropertyNames(err)));
}

@@ -128,3 +149,2 @@ }

}), err);
node.debug(util.inspect(err));
}

@@ -135,7 +155,22 @@ }

// node.debug('using alternate start time ' + result.altStartTime + ' - ' + config.startTimeAltType);
result.start = node.positionConfig.getTimeProp(node, msg, config.startTimeAltType, config.startTimeAlt, config.startOffsetAltType, config.startOffsetAlt, config.startOffsetAltMultiplier);
// result.start = node.positionConfig.getTimeProp(node, msg, config.startTimeAltType, config.startTimeAlt, config.startOffsetAltType, config.startOffsetAlt, config.startOffsetAltMultiplier);
result.start = node.positionConfig.getTimeProp(node, msg, {
type: config.startTimeAltType,
value : config.startTimeAlt,
offsetType : config.startOffsetAltType,
offset : config.startOffsetAlt,
multiplier : config.startOffsetAltMultiplier
});
result.startSuffix = '⎇ ';
} else {
// node.debug('using standard start time ' + result.altStartTime + ' - ' + config.startTimeAltType);
result.start = node.positionConfig.getTimeProp(node, msg, config.startTimeType, config.startTime, config.startOffsetType, config.startOffset, config.startOffsetMultiplier);
// result.start = node.positionConfig.getTimeProp(node, msg, config.startTimeType, config.startTime, config.startOffsetType, config.startOffset, config.startOffsetMultiplier);
result.start = node.positionConfig.getTimeProp(node, msg, {
type: config.startTimeType,
value : config.startTime,
offsetType : config.startOffsetType,
offset : config.startOffset,
multiplier : config.startOffsetMultiplier
});
}

@@ -145,13 +180,32 @@

// node.debug('using alternate end time ' + result.altEndTime + ' - ' + config.startTimeAltType);
result.end = node.positionConfig.getTimeProp(node, msg, config.endTimeAltType, config.endTimeAlt, config.endOffsetAltType, config.endOffsetAlt, config.endOffsetAltMultiplier);
// result.end = node.positionConfig.getTimeProp(node, msg, config.endTimeAltType, config.endTimeAlt, config.endOffsetAltType, config.endOffsetAlt, config.endOffsetAltMultiplier);
result.end = node.positionConfig.getTimeProp(node, msg, {
type: config.endTimeAltType,
value : config.endTimeAlt,
offsetType : config.endOffsetAltType,
offset : config.endOffsetAlt,
multiplier : config.endOffsetAltMultiplier
});
result.endSuffix = ' ⎇';
} else {
// node.debug('using standard end time ' + result.altEndTime + ' - ' + config.startTimeAltType);
result.end = node.positionConfig.getTimeProp(node, msg, config.endTimeType, config.endTime, config.endOffsetType, config.endOffset, config.endOffsetMultiplier);
// result.end = node.positionConfig.getTimeProp(node, msg, config.endTimeType, config.endTime, config.endOffsetType, config.endOffset, config.endOffsetMultiplier);
result.end = node.positionConfig.getTimeProp(node, msg, {
type: config.endTimeType,
value : config.endTime,
offsetType : config.endOffsetType,
offset : config.endOffset,
multiplier : config.endOffsetMultiplier
});
}
// node.debug(util.inspect(result, Object.getOwnPropertyNames(result)));
// node.debug(util.inspect(result, { colors: true, compact: 10, breakLength: Infinity }));
return result;
}
/**
* get the schedule time
* @param {Date} time - time to schedule
* @returns {number} milliseconds until the defined Date
*/
function getScheduleTime(time) {

@@ -167,2 +221,9 @@ const now = new Date();

/**
* check if message should be resend
* @param {boolean} isActive - define if resend is active
* @param {*} node - thew node Data
* @param {Date} time - the time to schedule
* @param {*} msg - the message object
*/
function checkReSendMsgDelayed(isActive, node, time, msg) {

@@ -189,3 +250,6 @@ if (node.timeOutObj) {

}
/**
* withinTimeSwitchNode
* @param {*} config - configuration
*/
function withinTimeSwitchNode(config) {

@@ -195,3 +259,3 @@ RED.nodes.createNode(this, config);

this.positionConfig = RED.nodes.getNode(config.positionConfig);
// this.debug('initialize withinTimeSwitchNode ' + util.inspect(config));
// this.debug('initialize withinTimeSwitchNode ' + util.inspect(config, { colors: true, compact: 10, breakLength: Infinity }));

@@ -220,5 +284,5 @@ this.propertyStart = config.propertyStart || '';

}
// this.debug('starting ' + util.inspect(msg, Object.getOwnPropertyNames(msg)));
// this.debug('self ' + util.inspect(this, Object.getOwnPropertyNames(this)));
// this.debug('config ' + util.inspect(config, Object.getOwnPropertyNames(config)));
// this.debug('starting ' + util.inspect(msg, { colors: true, compact: 10, breakLength: Infinity }));
// this.debug('self ' + util.inspect(this, { colors: true, compact: 10, breakLength: Infinity }));
// this.debug('config ' + util.inspect(config, { colors: true, compact: 10, breakLength: Infinity }));
const result = calcWithinTimes(this, msg, config);

@@ -254,5 +318,6 @@ const now = getDate(config.tsCompare, msg, node);

node.error(err.message);
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.log(util.inspect(err, Object.getOwnPropertyNames(err)));
setstate(node, { error: RED._('node-red-contrib-sun-position/position-config:errors.error-title') });
}
return null;
});

@@ -277,3 +342,3 @@

node.error(err.message);
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.log(util.inspect(err, Object.getOwnPropertyNames(err)));
setstate(node, { error: RED._('node-red-contrib-sun-position/position-config:errors.error-title') });

@@ -285,5 +350,6 @@ }

node.error(err.message);
node.debug(util.inspect(err, Object.getOwnPropertyNames(err)));
node.log(util.inspect(err, Object.getOwnPropertyNames(err)));
setstate(node, { error: RED._('node-red-contrib-sun-position/position-config:errors.error-title') });
}
return null;
}

@@ -290,0 +356,0 @@

{
"name": "node-red-contrib-sun-position",
"version": "0.4.0",
"version": "0.4.4-beta",
"description": "NodeRED nodes to get sun and moon position",

@@ -167,2 +167,12 @@ "keywords": [

"prefer-arrow-callback": "error",
"no-confusing-arrow": [
"error",
{
"allowParens": true
}
],
"arrow-parens": [
"error",
"as-needed"
],
"no-console": "warn",

@@ -187,3 +197,23 @@ "no-unused-vars": [

"no-var": "error",
"object-shorthand": [
"error",
"always"
],
"prefer-const": "error",
"prefer-rest-params": "error",
"no-useless-concat": "error",
"no-template-curly-in-string": "error",
"require-jsdoc": "warn",
"rest-spread-spacing": [
"error",
"never"
],
"symbol-description": "error",
"array-callback-return": "error",
"complexity": [
"warn",
35
],
"consistent-return": "error",
"no-lone-blocks": "error",
"linebreak-style": [

@@ -190,0 +220,0 @@ "warn",

@@ -22,44 +22,44 @@ # node-red-contrib-sun-position for NodeRED

* [node-red-contrib-sun-position for NodeRED](#node-red-contrib-sun-position-for-nodered)
* [Table of contents](#table-of-contents)
* [Preconditions](#preconditions)
* [Installation](#installation)
* [General](#general)
* [Saving resources](#saving-resources)
* [node-red-contrib-sun-position for NodeRED](#node-red-contrib-sun-position-for-NodeRED)
* [Table of contents](#Table-of-contents)
* [Preconditions](#Preconditions)
* [Installation](#Installation)
* [General](#General)
* [Saving resources](#Saving-resources)
* [second based accuracy](#second-based-accuracy)
* [Implemented Nodes](#implemented-nodes)
* [Implemented Nodes](#Implemented-Nodes)
* [sun-position](#sun-position)
* [sun-position - Node settings](#sun-position---node-settings)
* [Node Input](#node-input)
* [sun-position - Node Output](#sun-position---node-output)
* [sun-position - Node settings](#sun-position---Node-settings)
* [Node Input](#Node-Input)
* [sun-position - Node Output](#sun-position---Node-Output)
* [moon-position](#moon-position)
* [moon-position - Node settings](#moon-position---node-settings)
* [moon-position - Node Output](#moon-position---node-output)
* [moon-position - Node settings](#moon-position---Node-settings)
* [moon-position - Node Output](#moon-position---Node-Output)
* [time-inject](#time-inject)
* [time-inject - Node settings](#time-inject---node-settings)
* [time-inject - Node Input](#time-inject---node-input)
* [time-inject - Node Output](#time-inject---node-output)
* [time-inject - Node settings](#time-inject---Node-settings)
* [time-inject - Node Input](#time-inject---Node-Input)
* [time-inject - Node Output](#time-inject---Node-Output)
* [within-time](#within-time)
* [within-time - Node settings](#within-time---node-settings)
* [within-time - Node settings](#within-time---Node-settings)
* [time-comp](#time-comp)
* [time-comp - Node settings](#time-comp---node-settings)
* [time-comp - Node settings](#time-comp---Node-settings)
* [time-span](#time-span)
* [time-span - Node settings](#time-span---node-settings)
* [time-span - Node settings](#time-span---Node-settings)
* [blind-control](#blind-control)
* [Times definitions](#times-definitions)
* [Times definitions](#Times-definitions)
* [sun times](#sun-times)
* [remarks](#remarks)
* [blue hour](#blue-hour)
* [amateurDawn /amateurDusk](#amateurdawn-amateurdusk)
* [amateurDawn /amateurDusk](#amateurDawn-amateurDusk)
* [alternate properties](#alternate-properties)
* [moon times](#moon-times)
* [message, flow or global property or JSONATA expression](#message-flow-or-global-property-or-jsonata-expression)
* [message, flow or global property or JSONATA expression](#message-flow-or-global-property-or-JSONATA-expression)
* [input parse formats](#input-parse-formats)
* [output timestamp formats](#output-timestamp-formats)
* [output timespan formats](#output-timespan-formats)
* [Conditions](#conditions)
* [TODO](#todo)
* [Bugs and Feedback](#bugs-and-feedback)
* [LICENSE](#license)
* [Other](#other)
* [Conditions](#Conditions)
* [TODO](#TODO)
* [Bugs and Feedback](#Bugs-and-Feedback)
* [LICENSE](#LICENSE)
* [Other](#Other)

@@ -66,0 +66,0 @@ ## Preconditions

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

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