Skip to content

Commit

Permalink
Incorporation of new feature supporting configurable setting of the e…
Browse files Browse the repository at this point in the history
…xpiry period (Issue alisdairjsmyth#8)
  • Loading branch information
alisdairjsmyth committed Dec 15, 2016
1 parent 3f3f4ce commit 5731705
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 37 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ It is configured with the following properties:
* <b>temperature threshold</b>: (optional) temperature at which the blind will be fully closed while the sun is in the window. This setting overrides <b>altitudethreshold</b> and <b>depth</b> in the calculation
* <b>clouds threshold</b>: (optional) maximum percentage of sky occluded by clouds for the calculation to be performed
* <b>night position</b>: (optional) the position of the blind outside of daylight hours. Defaults to 100.
* <b>expiry period</b>: (optional) the duration in hours a manual setting will remain is place. Default to 2.

The calculation requires the output of the <a href="https://www.npmjs.com/package/node-red-contrib-sunpos" target="_new">Sun Position</a> Node. This can be supplemented with current weather conditions, such as that from forecastio or weather underground. <b>msg.topic</b> should be set to weather, and <b>msg.payload</b> either or both of the following properties:
* <b>maxtemp</b>: the forecasted maximum temperature for the day;
Expand Down Expand Up @@ -81,8 +82,8 @@ This node calculates the appropriate blind position to restrict direct sunlight
* channel
* mode
* orientation
* noffset
* poffset
* noffset (optional)
* poffset (optional)
* top
* bottom
* depth
Expand All @@ -93,11 +94,12 @@ This node calculates the appropriate blind position to restrict direct sunlight
* temperaturethreshold (optional)
* cloudsthreshold (optional)
* night position (optional)
* expiry period (optional)
* the output of the <a href="https://www.npmjs.com/package/node-red-contrib-sunpos" target="_new">Sun Position</a> Node;
* current weather conditions, such as that from forecastio or weather underground. <b>msg.topic</b> should be set to weather, and <b>msg.payload</b> either or both of the following properties:
* maxtemp
* clouds
* a specified blind position, which will remain in effect for 2 hours
* a specified blind position, which will remain in effect for the expiry period
* channel
* blindPosition

Expand Down
11 changes: 11 additions & 0 deletions blindcontroller/blindcontroller.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@
<label for="node-input-nightposition"><i class="fa fa-angle-down"></i> <span data-i18n="blindcontroller.label.nightposition"></span></label>
<input type="text" id="node-input-nightposition" data-i18n="[placeholder]blindcontroller.placeholder.nightposition">
</div>
<div class="form-row">
<label for="node-input-expiryperiod"><i class="fa fa-clock-o"></i> <span data-i18n="blindcontroller.label.expiryperiod"></span></label>
<input type="text" id="node-input-expiryperiod" data-i18n="[placeholder]blindcontroller.placeholder.expiryperiod">
</div>
</script>

<script type="text/x-red" data-help-name="blindcontroller">
Expand All @@ -100,6 +104,7 @@
<li><b>Temperature Threshold</b>: temperature at which the blind will be fully closed while the sun is in the window. This setting overrides <b>altitudethreshold</b> and <b>depth</b> in the calculation</li>
<li><b>Clouds Threshold</b>: A numerical value between 0 and 1 (inclusive) representing the percentage of sky occluded by clouds. A value of 0 corresponds to clear sky, 0.4 to scattered clouds, 0.75 to broken cloud cover, and 1 to completely overcast skies.</li>
<li><b>Night Position</b>: The position of the blind outside of daylight hours.</li>
<li><b>Expiry Period</b>: The time in hours a manual setting will remain in place.</li>
</ul>
</script>

Expand Down Expand Up @@ -178,6 +183,12 @@
return ((n >= 0) && (n <= 100) && (n%i == 0));
}
},
expiryperiod: {value:"", required:false,
validate: function(v) {
var n = Number(v);
return (n >= 0);
}
}
},
inputs: 1,
outputs: 1,
Expand Down
74 changes: 43 additions & 31 deletions blindcontroller/blindcontroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ module.exports = function(RED) {
var blindProperty = [
"channel",
"orientation",
"noffset",
"poffset",
"top",
"bottom",
"depth",
Expand Down Expand Up @@ -135,15 +133,17 @@ module.exports = function(RED) {
node.error(RED._("blindcontroller.error.blind.invalid-mode") + msg.payload.mode, msg);
validMsg = false;
}
if ((typeof msg.payload.noffset != "number") ||
if ((msg.payload.noffset) &&
((typeof msg.payload.noffset != "number") ||
(msg.payload.noffset < 0) ||
(msg.payload.noffset > 90)) {
(msg.payload.noffset > 90))) {
node.error(RED._("blindcontroller.error.blind.invalid-noffset") + msg.payload.noffset, msg);
validMsg = false;
}
if ((typeof msg.payload.poffset != "number") ||
if ((msg.payload.poffset) &&
((typeof msg.payload.poffset != "number") ||
(msg.payload.poffset < 0) ||
(msg.payload.poffset > 90)) {
(msg.payload.poffset > 90))) {
node.error(RED._("blindcontroller.error.blind.invalid-poffset") + msg.payload.poffset, msg);
validMsg = false;
}
Expand Down Expand Up @@ -217,6 +217,12 @@ module.exports = function(RED) {
node.error(RED._("blindcontroller.error.blind.invalid-nightposition") + msg.payload.nightposition, msg);
validMsg = false;
}
if ((msg.payload.expiryperiod) &&
((typeof msg.payload.expiryperiod != "number") ||
(msg.payload.expiryposition < 0))) {
node.error(RED._("blindcontroller.error.blind.invalid-expiryperiod") + msg.payload.expiryperiod, msg);
validMsg = false;
}
}
return validMsg;
}
Expand Down Expand Up @@ -266,28 +272,25 @@ module.exports = function(RED) {
* based on the orientation of the window and the azimuth of the sun
*/
function isSunInWindow(blind, azimuth) {
var noffset = blind.noffset ? blind.noffset : 90;
var poffset = blind.poffset ? blind.poffset : 90;

var sunInWindow = false;

/*
* Checks the sun azimuth is between window orientation +/- offset.
* Where the range includes ranges each side of north, separate checks
* need to be performed either side of north
*/
if (blind.orientation - noffset < 0) {
if ((((360 + blind.orientation - noffset) <= azimuth) & (azimuth <= 360)) ||
((0 <= azimuth) && (azimuth <= blind.orientation + poffset))) {
if (blind.orientation - blind.noffset < 0) {
if ((((360 + blind.orientation - blind.noffset) <= azimuth) & (azimuth <= 360)) ||
((0 <= azimuth) && (azimuth <= blind.orientation + blind.poffset))) {
sunInWindow = true;
}
} else if (blind.orientation + poffset > 360) {
if (((0 <= azimuth) & (azimuth <= (blind.orientation + poffset - 360))) ||
(((blind.orientation - noffset) <= azimuth) && (azimuth <= 360))) {
} else if (blind.orientation + blind.poffset > 360) {
if (((0 <= azimuth) & (azimuth <= (blind.orientation + blind.poffset - 360))) ||
(((blind.orientation - blind.noffset) <= azimuth) && (azimuth <= 360))) {
sunInWindow = true;
}
} else {
if (((blind.orientation - noffset) <= azimuth) && (azimuth <= (blind.orientation + poffset))) {
if (((blind.orientation - blind.noffset) <= azimuth) && (azimuth <= (blind.orientation + blind.poffset))) {
sunInWindow = true;
}
}
Expand Down Expand Up @@ -451,7 +454,7 @@ module.exports = function(RED) {
*/
function setPosition (node, msg, blind) {
blind.blindPosition = msg.payload.blindPosition;
blind.blindPositionExpiry = calcBlindPositionExpiry ();
blind.blindPositionExpiry = calcBlindPositionExpiry (blind);
blind.blindPositionReasonCode = "01";
blind.blindPositionReasonDesc = RED._("blindcontroller.positionReason.01");
msg.payload = blind;
Expand All @@ -460,12 +463,11 @@ module.exports = function(RED) {
}

/*
* Calculates the expiry timestamp to be 2 hours after the current time
* Calculates the expiry timestamp
*/
function calcBlindPositionExpiry () {
function calcBlindPositionExpiry (blind) {
var expiryTimestamp = new Date();
expiryTimestamp.setHours(expiryTimestamp.getHours()+ 2)

expiryTimestamp.setHours(expiryTimestamp.getHours()+ blind.expiryperiod);
return expiryTimestamp;
}

Expand Down Expand Up @@ -520,10 +522,20 @@ module.exports = function(RED) {
/*
* Default settings if not specified in input msg
*/
blinds[channel].mode = (typeof msg.payload.mode != "undefined") ? msg.payload.mode : "Summer";
blinds[channel].maxopen = (typeof msg.payload.maxopen != "undefined") ? msg.payload.maxopen : 0;
blinds[channel].maxclosed = (typeof msg.payload.maxclosed != "undefined") ? msg.payload.maxclosed : 100;
blinds[channel].nightposition = (typeof msg.payload.nightposition != "undefined") ? msg.payload.nightposition : 100;
blinds[channel].mode = (typeof msg.payload.mode != "undefined") ?
msg.payload.mode : RED._("blindcontroller.placeholder.mode");
blinds[channel].noffset = (typeof msg.payload.noffset != "undefined") ?
msg.payload.noffset : Number(RED._("blindcontroller.placeholder.noffset"));
blinds[channel].poffset = (typeof msg.payload.poffset != "undefined") ?
msg.payload.poffset : Number(RED._("blindcontroller.placeholder.poffset"));
blinds[channel].maxopen = (typeof msg.payload.maxopen != "undefined") ?
msg.payload.maxopen : Number(RED._("blindcontroller.placeholder.maxopen"));
blinds[channel].maxclosed = (typeof msg.payload.maxclosed != "undefined") ?
msg.payload.maxclosed : Number(RED._("blindcontroller.placeholder.maxclosed"));
blinds[channel].nightposition = (typeof msg.payload.nightposition != "undefined") ?
msg.payload.nightposition : Number(RED._("blindcontroller.placeholder.nightposition"));
blinds[channel].expiryperiod = (typeof msg.payload.expiryperiod != "undefined") ?
msg.payload.expiryperiod : Number(RED._("blindcontroller.placeholder.expiryperiod"));
break;
case "weather":
weather = msg.payload;
Expand Down Expand Up @@ -557,20 +569,20 @@ module.exports = function(RED) {
channel: channel,
mode: config.mode,
orientation: Number(config.orientation),
noffset: Number(config.noffset),
poffset: Number(config.poffset),
noffset: (config.noffset != "") ? Number(config.noffset) : Number(RED._("blindcontroller.placeholder.noffset")),
poffset: (config.poffset != "") ? Number(config.poffset) : Number(RED._("blindcontroller.placeholder.poffset")),
top: Number(config.top),
bottom: Number(config.bottom),
depth: Number(config.depth),
altitudethreshold: Number(config.altitudethreshold),
increment: Number(config.increment),
maxopen: (config.maxopen != "") ? Number(config.maxopen) : 0,
maxclosed: (config.maxclosed != "") ? Number(config.maxclosed) : 100,
maxopen: (config.maxopen != "") ? Number(config.maxopen) : Number(RED._("blindcontroller.placeholder.maxopen")),
maxclosed: (config.maxclosed != "") ? Number(config.maxclosed) : Number(RED._("blindcontroller.placeholder.maxclosed")),
temperaturethreshold: config.temperaturethreshold,
cloudsthreshold: config.cloudsthreshold,
nightposition: (config.nightposition != "") ? Number(config.nightposition) : 100
nightposition: (config.nightposition != "") ? Number(config.nightposition) : Number(RED._("blindcontroller.placeholder.nightposition")),
expiryperiod: (config.expiryperiod != "") ? Number(config.expiryperiod) : Number(RED._("blindcontroller.placeholder.expiryperiod"))
};

this.blind = blinds[channel];
var node = this;
var sunPosition = {};
Expand Down
10 changes: 7 additions & 3 deletions blindcontroller/locales/en-US/blindcontroller.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
"temperaturethreshold" : "Temperature Threshold",
"cloudsthreshold" : "Cloud Threshold",
"name" : "Name",
"nightposition" : "Night Position"
"nightposition" : "Night Position",
"expiryperiod" : "Expiry Period"
},
"placeholder" : {
"mode" : "Summer",
"channel" : "1",
"orientation" : "0",
"noffset" : "90",
Expand All @@ -35,7 +37,8 @@
"temperaturethreshold" : "0",
"cloudsthreshold" : "0",
"name" : "Name",
"nightposition" : "100"
"nightposition" : "100",
"expiryperiod" : "2"
},
"error" : {
"invalid-msg-payload" : "Invalid msg.payload",
Expand All @@ -62,7 +65,8 @@
"invalid-max-settings" : "Maximum open position must be less than maximum closed position: ",
"invalid-altitudethreshold" : "Altitude threshold must be of type number and between 0 and 90: ",
"invalid-cloudsthreshold" : "Cloud threshold must be of type number and between 0 and 1: ",
"invalid-nightposition" : "Night position must be between 0 and 100 and a multiple of increment: "
"invalid-nightposition" : "Night position must be between 0 and 100 and a multiple of increment: ",
"invalid-expiryperiod" : "Expiry period must be greater than 0: "
},
"blindPosition" : {
"missing-property" : "Missing property: ",
Expand Down

0 comments on commit 5731705

Please sign in to comment.