diff --git a/README.md b/README.md
index c695027d..d59bdb13 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,10 @@ Copyright 2013 IBM Corp. under [the Apache 2.0 license](LICENSE).
**79-sensorTag** - Reads data from the Ti BLE SensorTag device.
+**100-heatmiser-in** - Writes settings for temperature and frost protection to Heatmiser thermostats.
+
+**101-heatmiser-out** - Reads settings from Heatmiser thermostats at a polling interval.
+
**101-scanBLE** - Scans for a particular Bluetooth Low Energy (BLE) device.
**145-BBB-hardware** - A collection of analogue & digital input & output nodes for the Beaglebone Black
diff --git a/hardware/heatmiser-in/100-heatmiser-in.html b/hardware/heatmiser-in/100-heatmiser-in.html
new file mode 100644
index 00000000..76b2f73b
--- /dev/null
+++ b/hardware/heatmiser-in/100-heatmiser-in.html
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
diff --git a/hardware/heatmiser-in/100-heatmiser-in.js b/hardware/heatmiser-in/100-heatmiser-in.js
new file mode 100644
index 00000000..44eb1396
--- /dev/null
+++ b/hardware/heatmiser-in/100-heatmiser-in.js
@@ -0,0 +1,191 @@
+/**
+ * Copyright 2014 Sean Bedford
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var RED = require(process.env.NODE_RED_HOME+"/red/red");
+
+var Heatmiser = require("heatmiser");
+var util = require('util');
+
+function HeatmiserInputNode(n) {
+ // TODO - holiday and hot water cases when confirmed working
+ var DEBUG = false;
+ RED.nodes.createNode(this,n);
+ this.ip = n.ip || "192.168.0.1";
+ this.pin = n.pin || "1234";
+ this.multiWriteFunc = undefined;
+ hminnode = this;
+ this.pollIntervalRef = undefined;
+
+ this.hm = new Heatmiser(this.ip, this.pin);
+
+ this.hm.on('success', function(data) {
+ if (DEBUG) {
+ util.log("[100-heatmiser-in.js] - Successfully wrote data. Response : " + JSON.stringify(data));
+ }
+ hminnode.currentStatus = data.dcb;
+ if (hminnode.multiWriteFunc) {
+ hminnode.multiWriteFunc();
+ hminnode.multiWriteFunc = undefined;
+ return;
+ }
+ hminnode.send({topic: "", payload:JSON.stringify(data.dcb)});
+ });
+ this.hm.on('error', function(data) {
+ if (DEBUG) {
+ util.log("[100-heatmiser-in.js] - Error during data setting : " + JSON.stringify(data));
+ }
+ hminnode.send(data);
+ });
+
+ this.on("close", function() {
+ if (this.pollIntervalRef) {
+ clearInterval(this.pollIntervalRef);
+ this.pollIntervalRef = undefined;
+ }
+ });
+
+ this.read = function() {
+ if (hminnode.hm) {
+ hminnode.hm.read_device();
+ }
+ };
+
+ if (!this.currentStatus) {
+ this.read();
+ this.pollIntervalRef = setInterval(this.read, 30*60*1000);
+ }
+
+ this.write = function(dcb) {
+ if (hminnode.hm) {
+ hminnode.hm.write_device(dcb);
+ }
+ };
+
+ this.validateAndWrite = function(message) {
+ for (var key in message.payload) {
+ // Ensure our valid keys contain valid values
+ switch(key) {
+ case "runmode" :
+ if (DEBUG) {
+ util.log("[100-heatmiser-in.js] Hit the runmode case");
+ }
+ if (message.payload[key] !== "frost" && message.payload[key] !== "heating") {
+ util.log("[100-heatmiser-in.js] Warning: Unsupported 'runmode' value passed!");
+ return;
+ }
+ break;
+
+ // case "holiday" :
+ // if (DEBUG) {
+ // util.log("[100-heatmiser-in.js] Hit the holiday case");
+ // }
+ // if (!('enabled' in message.payload[key]) && !('time' in message.payload[key])) {
+ // util.log("[100-heatmiser-in.js] Warning: Unsupported 'holiday' value passed!");
+ // return;
+ // }
+ // var time = message.payload[key].time;
+ // // Ensure hminnode time is a date
+ // if (typeof(time) == "string") {
+ // util.log("Typeof time was " +typeof(message.payload[key].time));
+ // // message.payload[key].time = new Date(message.payload[key].time);
+ // message.payload[key].time = new Date(2014, 02, 15, 12, 0, 0);
+ // util.log("Typeof time is now " +typeof(message.payload[key].time));
+ // }
+ // // Also add in away mode (for hot water) if we're on hols
+ // if (message.payload[key].time) {
+ // message.payload.away_mode = 1;
+ // }
+ // else {
+ // message.payload.away_mode = 0;
+ // }
+ // break;
+
+ // case "hotwater" :
+ // if (DEBUG) {
+ // util.log("[100-heatmiser-in.js] Hit the hotwater case");
+ // }
+ // if (message.payload[key] !== "on" && message.payload[key] !== "boost" && message.payload[key] !== "off") {
+ // util.log("[100-heatmiser-in.js] Warning: Unsupported 'hotwater' value passed!");
+ // return;
+ // }
+ // break;
+
+ case "heating" :
+ // Ensure heating stays last! It's got a multi write scenario
+ if (DEBUG) {
+ util.log("[100-heatmiser-in.js] Hit the heating case");
+ }
+ if (!('target' in message.payload[key]) && !('hold' in message.payload[key])) {
+ util.log("[100-heatmiser-in.js] Warning: Unsupported 'heating' value passed!");
+ return;
+ }
+ // Set sane temp and time ranges and sanitise to float/int
+ var target = parseFloat(message.payload[key].target);
+ var hold = parseInt(message.payload[key].hold);
+ (target > 30.0) ? message.payload[key].target = 30.0 : message.payload[key].target = target;
+ (hold > 1440) ? message.payload[key].hold = 1440 : message.payload[key].hold = hold;
+ (target <= 10.0) ? message.payload[key].target = 10.0 : message.payload[key].target = target;
+ (hold <= 0) ? message.payload[key].hold = 0 : message.payload[key].hold = hold;
+
+ // Ensure hminnode runmode == heating first
+ if (hminnode.currentStatus.run_mode === "frost_protection") {
+ // Use the multiWriteFunc as a callback in our success case
+ hminnode.multiWriteFunc = function() {
+ hminnode.write(message.payload);
+ }
+ hminnode.write({"runmode" : "heating"});
+ // End the flow here to ensure no double-writing
+ return;
+ }
+ break;
+
+ default :
+ break;
+ }
+ }
+ // Valid set of key messages, construct DCB and write
+ var dcb = message.payload;
+ if (DEBUG) {
+ util.log("[100-heatmiser-in.js] Injecting " + JSON.stringify(dcb));
+ }
+ hminnode.write(dcb);
+ };
+
+ this.on("input", function(message) {
+ // Valid inputs are heating:{target:, hold:}, read:, runmode:frost/heating, holiday:{enabled:, time:}, hotwater:{'on':1/0 / 'boost':1/0}
+ if (message.payload) {
+ if (typeof(message.payload) === "string") {
+ message.payload = JSON.parse(message.payload);
+ }
+ // Compare message.payload data to confirm valid and send to thermostat
+ var validInputs = ["heating", "runmode"];
+ for (var key in message.payload) {
+ if (message.payload.hasOwnProperty(key)) {
+ if (validInputs.indexOf(key) < 0) {
+ util.log("[100-heatmiser-in.js] Warning: Unsupported key ("+key+") passed!");
+ return;
+ }
+ }
+ }
+ hminnode.validateAndWrite(message);
+ }
+ else {
+ util.log("[100-heatmiser-in.js] Warning: Invalid input passed!");
+ return;
+ }
+ });
+}
+RED.nodes.registerType("heatmiser-in",HeatmiserInputNode);
diff --git a/hardware/heatmiser-out/101-heatmiser-out.html b/hardware/heatmiser-out/101-heatmiser-out.html
new file mode 100644
index 00000000..9f00948b
--- /dev/null
+++ b/hardware/heatmiser-out/101-heatmiser-out.html
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
diff --git a/hardware/heatmiser-out/101-heatmiser-out.js b/hardware/heatmiser-out/101-heatmiser-out.js
new file mode 100644
index 00000000..a7ec6b07
--- /dev/null
+++ b/hardware/heatmiser-out/101-heatmiser-out.js
@@ -0,0 +1,91 @@
+/**
+ * Copyright 2014 Sean Bedford
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+var RED = require(process.env.NODE_RED_HOME+"/red/red");
+
+var Heatmiser = require("heatmiser");
+var util = require('util');
+
+function HeatmiserOutputNode(n) {
+ // TODO - holiday and hot water cases when confirmed working
+ var DEBUG = false;
+ RED.nodes.createNode(this,n);
+ this.ip = n.ip || "192.168.0.1";
+ this.pin = n.pin || "1234";
+ this.pollTime = n.pollTime*60*1000 || 30*60*1000;
+ this.pollIntervalRef = undefined;
+ hmoutnode = this;
+
+ this.hm = new Heatmiser(this.ip, this.pin);
+
+ this.hm.on('success', function(data) {
+ if (DEBUG) {
+ util.log("[100-heatmiser-in.js] - Successfully wrote data. Response : " + JSON.stringify(data));
+ }
+ hmoutnode.send({topic: "", payload:JSON.stringify(data.dcb)});
+ });
+ this.hm.on('error', function(data) {
+ if (DEBUG) {
+ util.log("[100-heatmiser-in.js] - Error during data setting : " + JSON.stringify(data));
+ }
+ hmoutnode.send(data);
+ });
+
+ this.read = function() {
+ if (hmoutnode.hm) {
+ hmoutnode.hm.read_device();
+ }
+ };
+
+ if (!this.currentStatus) {
+ this.read();
+ this.pollIntervalRef = setInterval(this.read, this.pollTime);
+ }
+
+ this.on("close", function() {
+ if (this.pollIntervalRef) {
+ clearInterval(this.pollIntervalRef);
+ this.pollIntervalRef = undefined;
+ }
+ });
+
+ this.on("input", function(message) {
+ // Valid inputs are heating:{target:, hold:}, read:, runmode:frost/heating, holiday:{enabled:, time:}, hotwater:{'on':1/0 / 'boost':1/0}
+ if (message.payload == "undefined" || !message.payload) {
+ message.payload = {read : true};
+ }
+ if (typeof(message.payload) == "string") {
+ message.payload = JSON.parse(message.payload);
+ }
+ if (message.payload.read) {
+ hmoutnode.read();
+ }
+ else if (message.payload) {
+ // Compare message.payload data to confirm valid and send to thermostat
+ var validInputs = ["heating", "runmode"];
+ for (var key in message.payload) {
+ if (message.payload.hasOwnProperty(key)) {
+ if (validInputs.indexOf(key) < 0) {
+ util.log("[100-heatmiser.js] Warning: Unsupported key ("+key+") passed!");
+ return;
+ }
+ }
+ }
+ hmoutnode.validateAndWrite(message);
+ }
+ });
+}
+RED.nodes.registerType("heatmiser-out",HeatmiserOutputNode);
diff --git a/hardware/hue/104-hue_manage.html b/hardware/hue/104-hue_manage.html
index 87cc7705..d23f3973 100644
--- a/hardware/hue/104-hue_manage.html
+++ b/hardware/hue/104-hue_manage.html
@@ -13,6 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
+
-->