Skip to content

Commit

Permalink
Prepare for version 1.0.0 (#1)
Browse files Browse the repository at this point in the history
* Added keywords to package

* Version bumb

* More details in  README

* Added comments and new formatting

* Added a template
  • Loading branch information
zinen authored Aug 22, 2020
1 parent 87e5387 commit 747cd2c
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 53 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# node-red-contrib-hue-tunable-white
Node-RED node automatic change white color temperature of lights as the day progress.

A tiny script just to give you a feeling of varying daylight. It's used to take advantage of a function that can be automated for the connected lights.
A tiny script just to give you a feeling of varying daylight. It's used to increase mental health for people, in this modern world, spending too much time inside.

## What is tunable white
*Aliases: 'tunable white', 'dynamic white', 'tunable dynamic white', 'kelvin changing'*
Expand All @@ -11,12 +11,12 @@ Its the possibility to change the 'color' of the white light according to the co
## Usage
Give the node an input every so often to make it do its thing. Eg. every 15 minutes seams to work good. Also i recommend sending a ``msg.reset`` once a day, eg. at night, to overrule any manually changed lights back to having its color temperature automated.

*I'm using this script to fight of winter depression, and it has been running successfully in my home for some time now, since winter 2016*

### To get easy started
In Node-RED editor, click menu at top right corner -> Import -> Examples -> node-red-contrib-hue-tunable-white -> basic.

# How it works
At first run all lights are controlled to the right color temperature for the current time. Once this first run is completed the node will do a more careful update of the color temperature.
It will only change the color if the lights currently has the default startup color temperature or has the value has this node specified last. This means you can still change you lights and this node will respect that you manually took control of the light.
It will only change the color if the lights currently has the default startup color temperature or has the value has this node specified last. This means you can still change you lights and this node will respect that you manually took control of the light. If you turn your lights on and off from the mains this is also handled just fine if you lights startup with its standard value.

## Calculating the color
The node will use system time of the server to do a calculation of the color. A caveat to only relying on time is the color does not follow the real state of the sun at your location, as I found it more usable to control the light according to a fix schema.
Expand All @@ -26,7 +26,7 @@ At 6:00 and again at 22:00 the color will be the most orange. At 14:00 the color

The graph below shows how the color will change according to the current hour.

![alt](./img/graph.png)
![Graph](./img/graph.png)

# Requirements
Requires a Hue Bridge and for you to grab a API key from it:
Expand Down
1 change: 1 addition & 0 deletions examples/basic.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"id":"2f1a4c99.85d814","type":"inject","z":"ad7102ae.6dd24","name":"every 15min","topic":"","payload":"","payloadType":"date","repeat":"900","crontab":"","once":false,"onceDelay":0.1,"x":120,"y":40,"wires":[["99ce1c37.b0585"]]},{"id":"99ce1c37.b0585","type":"tunable-white","z":"ad7102ae.6dd24","url":"","key":"","x":530,"y":40,"wires":[[]]},{"id":"f179da8f.6a28f8","type":"inject","z":"ad7102ae.6dd24","name":"reset at night","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"00 04 * * *","once":false,"onceDelay":0.1,"x":120,"y":100,"wires":[["70f17c31.1c2c54"]]},{"id":"70f17c31.1c2c54","type":"change","z":"ad7102ae.6dd24","name":"","rules":[{"t":"set","p":"reset","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":320,"y":100,"wires":[["99ce1c37.b0585"]]}]
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "node-red-contrib-hue-tunable-white",
"version": "0.0.2",
"version": "1.0.0",
"description": "Node-RED node automatic change white color temperature of lights as the day progress.",
"main": "tunable-white.js",
"scripts": {
Expand All @@ -13,7 +13,9 @@
"tunable white",
"daylight",
"philips hue",
"dynamic white"
"dynamic white",
"daylight",
"daylight simulator"
],
"node-red": {
"nodes": {
Expand Down
36 changes: 18 additions & 18 deletions tunable-white.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
<script type="text/javascript">
RED.nodes.registerType('tunable-white',{
category: 'network',
RED.nodes.registerType('tunable-white', {
category: 'homesmart',
color: '#94c1d0',
defaults: {
url: {value:"", required:true},
key: {value:"", required:true},
url: { value: "", required: true },
key: { value: "", required: true },
},
inputs:1,
outputs:1,
inputs: 1,
outputs: 1,
icon: "light.svg",
label: function() {
return this.name||"tunable white";
label: function () {
return this.name || "tunable white"
}
});
})
</script>

<script type="text/html" data-template-name="tunable-white">
Expand All @@ -21,10 +21,10 @@
<input type="text" id="node-input-url" placeholder="Eg. 192.168.0.45">
</div>
<div class="form-row">
<label for="node-input-key"><i class="fa fa-key"></i> API Key(user)</label>
<label for="node-input-key"><i class="fa fa-key"></i> API key (username)</label>
<input type="text" id="node-input-key" placeholder="Eg. Xyewrasd324235tgh34213xSGR">
</div>
<div class="form-tips"><b>Tip:</b> If you don't got an API key, you should google how to. Start by opening the Bridge console here: https://192.168.0.45/debug/clip.html </div>
<div class="form-tips"><b>Tip:</b> To get an API key follow Philips' <a href="https://developers.meethue.com/develop/get-started-2/">official guide to getting the API key</a> (aka. *username*)</div>
</script>

<script type="text/html" data-help-name="tunable-white">
Expand All @@ -33,10 +33,10 @@
<h3>Inputs</h3>
<dl class="message-properties">
<dt>msg.reset
<span class="property-type">boolean (200-400)</span>
<span class="property-type">boolean</span>
</dt>
<dd>
<code>= True</code> will force all light to update now no matter their current color. Its a good practice to run this once a day.
<code>= true</code> will force all lights to update now no matter their current color. Its a good practice to run this once a day.
</dd>
</dl>
<h3>Outputs</h3>
Expand All @@ -45,25 +45,25 @@ <h3>Outputs</h3>
<span class="property-type">number (200-400)</span>
</dt>
<dd>
The new color temperature sent to the lights. Based on the what Philips Hue API <code>ct</code> color.
The new color temperature sent to the lights. For more info see Philips Hue API calls about <code>ct</code> value.
</dd>
<dt>payload.bulbChanged
<dt>payload.lightChanged
<span class="property-type">array</span>
</dt>
<dd>
Array of reachable lights that was changed in color. (All reachable light is changed at first run).
</dd>
<dt>payload.bulbNotChanged
<dt>payload.lightNotChanged
<span class="property-type">array</span>
</dt>
<dd>
Array of reachable lights that was not changed in color, possible because they had been manually changed to another color then the default one.
Array of reachable lights that was not changed in color, possible because they had been manually changed to another color then the default one. <i>Unreachable/offline lights are not listed here.</i>
</dd>
<dt>time
<span class="property-type">string</span>
</dt>
<dd>
Server time, as seen by node-red. If wrong update timezone of installation.
Server time in 24h format, as seen by node-red. If wrong update timezone of installation.
</dd>
</dl>
</script>
61 changes: 34 additions & 27 deletions tunable-white.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
module.exports = function (RED) {
'use strict'
const fetch = require('node-fetch')
function TunableWhiteNode (config) {
RED.nodes.createNode(this, config)

this.APIKey = config.key
this.ipAddress = config.url
this.url = 'http://' + this.ipAddress + '/api/' + this.APIKey + '/lights/'
this.url = 'http://' + config.url + '/api/' + config.key + '/lights/'
this.lastColor = 0
this.forceRun = true
var node = this
Expand All @@ -18,65 +15,75 @@ module.exports = function (RED) {
}

node.on('input', async function (msg, send, done) {
const bulbChanged = []
const bulbNotChanged = []
// Used as output to tell which reachable lights was changed
const lightChanged = []
// Used as output to tell which reachable lights that was not changed
const lightNotChanged = []
try {
// Calculate the current hour as a decimal number
const currentHour = (new Date()).getHours()
const currentMinute = (new Date()).getMinutes()
// Convert minute to decimal of hour, eg. 15 minutes = 0,25 in decimal
const convertedTime = currentHour + currentMinute * 0.01667
// Used as output to to tell the time used by calculations. (mostly relevant for debugging)
msg.time = String(currentHour).padStart(2, '0') + ':' + String(currentMinute).padStart(2, '0')
const convertedMinute = currentMinute * 0.01667 // Convert minute to decimal, eg. 15 minutes = 0,25 in decimal
const convertedTime = currentHour + convertedMinute
if (msg.reset === true) {
this.forceRun = true
}
// Insert excel equation below, y = 3,125x2 - 87,5x + 812,5:
// Insert equation from calculated graph below, y = 3,125x2 - 87,5x + 812,5:
let calculatedTemperature = Math.floor(3.125 * convertedTime ** 2 - 87.5 * convertedTime + 812)
if (calculatedTemperature < 200) {
// Don't use lower color temperature value then 200
calculatedTemperature = 200
} else if (calculatedTemperature > 400) {
// Don't use higher color temperature value then 400
calculatedTemperature = 400
}
// Lamp data settings, bri = 1 -254, ct= 153-454
// ct value lower then 190 does not seem to effect Osram LIGHTIFY
// transition time of 5 seconds seams to look nice and is still quick
const dataCycle = { ct: calculatedTemperature, transitiontime: 50 }
// const r = await fetch(url)
// Make a promise race to make sure longest timeout for fetching data is defined.
// as the Hue Bridge in most cases is installed on the local LAN a short timeout is used.
const r = await Promise.race([
fetch(this.url),
new Promise((resolve, reject) =>
setTimeout(() => reject(new Error('Url request timeout')), 5000)
)
])
const requestData = await r.json()
// Hue Bridge normally reply with and JSON object. But in case of errors it returns an array
if (Array.isArray(requestData) && 'error' in requestData[0]) {
// In case of error from Hue Bridge, throw its description
throw new Error(requestData[0].error.description)
}
let i = 0 // Used to keep track of amount of request to the Hue bridge during this cycle

// Used to keep track of the amount of request to the Hue bridge during this cycle
let i = 0
for (const k in requestData) {
// Looks if bulb is reachable and in white mode(eg. not colored).
// Looks if light is reachable and in white mode(eg. not colored).
if (requestData[k].state.reachable === true && requestData[k].state.colormode === 'ct') {
const ctState = requestData[k].state.ct
// Look to see if color should change:
// If 366 = Philips Hue bulb default color, then change
// If 370 = Osram LIGHTIFY bulb default color, then change
// If 366 = Philips Hue light default color, then change
// If 370 = Osram LIGHTIFY light default color, then change
// If color(ctState) same as last ordered color, then change
// If bulb already is the desired, don't change
if (this.forceRun || (((ctState === 366) || (ctState === 370) || (ctState === this.lastColor)) && (ctState !== calculatedTemperature))) {
// If light already is the desired, don't change
if (this.forceRun === true || (((ctState === 366) || (ctState === 370) || (ctState === this.lastColor)) && (ctState !== calculatedTemperature))) {
i = i + 1
fetch(this.url + toString(k) + '/state/', {
// Fetch, but don't await since the reply is not relevant
fetch(this.url + String(k) + '/state/', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(dataCycle)
})
bulbChanged.push(requestData[k].name)
lightChanged.push(requestData[k].name)
if (i > 9) {
i = 0
await delay(1000) // 1 second timeout as defined in the Phillips Hue API (Core Limitations)
// 1 second timeout after 10 requests as defined in the Philips Hue API (Core Limitations)
await delay(1000)
}
} else {
bulbNotChanged.push(requestData[k].name)
lightNotChanged.push(requestData[k].name)
}
}
}
Expand All @@ -86,13 +93,13 @@ module.exports = function (RED) {
return
}

// Disables force_run command
// Clears force run flag
if (this.forceRun) {
this.forceRun = false
}
msg.payload = { newColor: this.lastColor, bulbChanged: bulbChanged, bulbNotChanged: bulbNotChanged }
msg.payload = { newColor: this.lastColor, lightChanged: lightChanged, lightNotChanged: lightNotChanged }
send(msg)
// New listener for Node-RED 1.0 >
// Listener used in Node-RED 1.0.0 >
done()
})
}
Expand Down

0 comments on commit 747cd2c

Please sign in to comment.