Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

heatmiser

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

heatmiser - npm Package Compare versions

Comparing version 0.0.1 to 0.0.2

.travis.yml

238

lib/heatmiser.js
var net = require('net');
function Heatmiser(host, pin, port) {
this.host = host;
this.pin = pin;
this.port = (typeof port === "undefined") ? 8068 : port;
this.model = null;
}
var crc16 = function(buf){

@@ -28,3 +35,3 @@ // Thanks to http://code.google.com/p/heatmiser-wifi/ for the algorithm

var parse_dcb = function(dcb_buf){
var model = ['DT', 'DT-E', 'PRT', 'PRT-E', 'PRTHW'][dcb_buf.readUInt8(4)];
this.model = ['DT', 'DT-E', 'PRT', 'PRT-E', 'PRTHW'][dcb_buf.readUInt8(4)];
var version = dcb_buf.readUInt8(3);

@@ -97,7 +104,14 @@ var length = dcb_buf.readUInt16LE(0);

var read_device = function(host, port, pin, success, error){
var client = net.connect({host: host, port: port}, function() { //'connect' listener
var buf = new Buffer([0x93, 0x0B, 0x00, (pin & 0xFF), ((pin & 0xFF00) >> 8), 0x00, 0x00, 0xFF, 0xFF]);
var crc = crc16(buf)
buf = Buffer.concat([buf, (new Buffer([(crc & 0xFF), ((crc & 0xFF00) >> 8)]))]);
// Construct an arbitrary thermostat command
Heatmiser.prototype.command = function(operation, success, error, data) {
len = 7 + data.length;
var buf = new Buffer(5+data.length+2);
buf.writeUInt8(operation, 0); // 0
buf.writeUInt16LE(len, 1); // 1-2
buf.writeUInt16LE(this.pin, 3); // 3-4
data.copy(buf, 5);
var crc = crc16(buf.slice(0,buf.length-2));
buf.writeUInt16LE(crc, buf.length-2); // last 2 bytes
var client = net.connect({host: this.host, port: this.port}, function() { //'connect' listener
client.write(buf);

@@ -108,3 +122,3 @@ });

client.on('data', function(data) {
success(parse_response(data));
success(data);
client.end();

@@ -114,3 +128,3 @@ });

client.end();
error(e);
error((typeof e === 'undefined') ? new Error("Timed out") : e);
});

@@ -123,3 +137,209 @@ client.on('error', function(e){

exports.read_device = read_device;
Heatmiser.prototype.read_device = function(success, error){
var check_response = function(data) {
success(parse_response(data));
}
this.command(0x93, check_response, error, new Buffer([0x00, 0x00, 0xFF, 0xFF]));
}
Heatmiser.prototype.write_device = function(data, success, error) {
var write = function() {
var items;
try {
items = status_to_dcb(this.model, data);
} catch(e) {
error(e);
return;
}
var buf = Buffer.concat(items);
// First byte to send is the number of items
var buffer = new Buffer(buf.length+1);
buffer[0] = items.length;
buf.copy(buffer, 1);
var check_response = function(data) {
success(parse_response(data));
}
this.command(0xa3, check_response, error, buffer);
}.bind(this);
// ensure the model is set in the first call to write
if (this.model == null) {
this.read_device(write, error);
} else {
write();
}
}
var dcb_entry = function(position, data) {
var l = (typeof data === 'number') ? 1 : data.length
var buf = new Buffer(2+1+l); // position, length , data
buf.writeUInt16LE(position, 0);
buf.writeUInt8(l, 2);
if(typeof data === 'number') {
buf.writeUInt8(data, 3);
}
else {
data.copy(buf, 3);
}
return buf;
}
var timeToByteBuffer = function(hours, minutes) {
var buf = new Buffer(2);
buf[0] = hours;
buf[1] = minutes;
return buf;
}
var dateTimeToByteBuffer = function(datetime) {
var buf = new Buffer(7);
var i = 0;
buf[i++] = datetime.getFullYear()-2000;
buf[i++] = datetime.getMonth()+1;
buf[i++] = datetime.getDate();
var day = datetime.getDay();
buf[i++] = day == 0 ? 7 : day; // 0 Sunday -> 7
buf[i++] = datetime.getHours();
buf[i++] = datetime.getMinutes();
buf[i++] = datetime.getSeconds();
return buf;
}
var status_to_dcb = function(model, data) {
var items = [];
for(var key in data) {
switch(key) {
case 'time':
// Current date and time, from a javascript Date object
items.push(dcb_entry(43, dateTimeToByteBuffer(data[key])));
break;
case 'enabled':
// General operating status (on/off)
items.push(dcb_entry(21, data[key] ? 1 : 0));
break;
case 'keylock':
// General operating status (on/off)
items.push(dcb_entry(22, data[key] ? 1 : 0));
break;
case 'holiday':
// holiday mode
// { enabled: false }
// { enabled: true, time: new Date() }
if (('enabled' in data[key]) && !data[key]['enabled']) {
// Cancel holiday mode
items.push(dcb_entry(24, 0));
} else if ('time' in data[key]) {
// Set return date and time, date without seconds
data[24] = dateTimeToByteBuffer(data[key]['time']).slice(0,5);
}
break;
case 'runmode':
// Run mode (controls heating)
if (model != 'TM1')
items.push(dcb_entry(23, data[key] == 'frost' ? 1 : 0));
break;
case 'awaymode':
// Away mode (controls hot water)
if (model.match(/(HW|TM1)$/))
items.push(dcb_entry(31, data[key] == 'away' ? 1 : 0));
break;
case 'frostprotect':
// Frost protection (temperature only, cannot disable)
if ('target' in data[key])
items.push(dcb_entry(17, data[key]['target']));
break;
case 'floorlimit':
// Floor limit (temperature only, cannot disable)
if (model.match(/-E$/) && ('floormax' in data[key]))
items.push(dcb_entry(19, data[key]['floormax']));
break;
case 'heating':
// Status of heating (target and hold - in minutes - only, cannot turn on/off)
if (model != 'TM1') {
if ('target' in data[key])
items.push(dcb_entry(18, data[key]['target']));
if ('hold' in data[key])
var buf = new Buffer(2);
buf.writeUInt16LE(data[key]['hold'], 0);
items.push(dcb_entry(32, buf));
}
break;
case 'hotwater':
// Status of hot water (values are different from those read)
if (model.match(/(HW|TM1)$/)) {
if ('boost' in data[key])
var buf = new Buffer(2);
buf.writeUInt16LE(data[key]['boost'], 0);
items.push(dcb_entry(25, buf));
if ('on' in data[key])
items.push(dcb_entry(42, data[key]['on'] ? 1 : 2));
else
items.push(dcb_entry(42, 0));
}
break;
case 'comfort':
// Heating comfort levels program
if (model.match(/^PRT/)) {
var days = data[key].length
var days_expected = status.config.progmode == '5/2' ? 2 : 7
if (days != days_expected) {
throw new Error("Incorrect number of days specified for comfort levels program " + days + ". Expected " + days_expected);
}
for (var day=0;day<days;day++) {
var comfort;
for (var entry=0;entry<4;entry++) {
var row = data[key][day][entry];
comfort.concat(typeof row !== "undefined" ? timeToByteBuffer(row['time']).push(row['target']) : new Buffer([24,0,16]) );
var new_schedule = new Buffer([24,0,16]); // default disabled schedule
if (typeof row !== "undefined") {
timeToByteBuffer(row['time']).copy(new_schedule);
new_schedule[2] = row['target'];
}
comfort = Buffer.concat(comfort, new_schedule);
}
items.push(dcb_entry((days == 2 ? 47 : 103) + day*12, comfort));
}
}
break;
case 'timer':
// Hot water control program
if (model.match(/^(PRTHW|TM1)$/)) {
var days = data[key].length
var days_expected = status.config.progmode == '5/2' ? 2 : 7
if (days != days_expected) {
throw new Error("Incorrect number of days specified for hot water control program " + days + ". Expected " + days_expected);
}
for (var day=0;day<days;day++) {
var timer;
for (var entry=0;entry<4;entry++) {
var row = data[key][day][entry];
timer.concat(typeof row !== "undefined" ? timeToByteBuffer(row['on']).concat(timeToByteBuffer(row['off'])) : [24,0,24,0] );
}
items.push(dcb_entry((days == 2 ? 71 : 187) + day*16, timer));
}
}
break;
default:
// HERE - Need to add support for item 26 (TM1 countdown)
// Other settings are not writable (including basic configuration)
// Feature 01: $status->{config}->{units}
// Feature 02: $status->{config}->{switchdiff}
// Feature 05: $status->{config}->{outputdelay}
// Feature 06 (06-10): Communications settings
// Feature 07 (11): $status->{config}->{locklimit}
// Feature 08 (12): $status->{config}->{sensor}
// Feature 10 (14): $status->{config}->{optimumstart}
// Feature 12 (16): $status->{config}->{progmode}
throw new Error("Unsupported item for writing: " + key);
}
}
return items;
}
module.exports = Heatmiser;

8

package.json
{
"name": "heatmiser",
"version": "0.0.1",
"version": "0.0.2",
"description": "A node.js app that talks to heatmiser wifi thermostats",
"main": "lib/heatmiser.js",
"scripts": {
"test": ""
"test": "./node_modules/.bin/mocha"
},

@@ -22,3 +22,5 @@ "repository": {

"devDependencies": {
"optimist": "*"
"mocha": "*",
"expect.js": "*",
"sinon": "*"
},

@@ -25,0 +27,0 @@ "engines": {

@@ -8,1 +8,34 @@ heatmiser-node

https://github.com/bjpirt/heatmiser-js
# Reading the thermostat status
var hm = new Heatmiser('localhost', 1234);
hm.read_device(function(success) {
console.log(success);
}, function(error) {
console.log(error);
});
# Writing to the thermostat
var log = function(msg) {
console.log(msg);
}
// set the time
hm.write_device({
time: new Date()
}, log, log);
// set the thermostat to frost mode
hm.write_device({
runmode: 'frost'
}, log, log);
// set the thermostat hold
hm.write_device({
heating: {
target: 20, // C
hold: 30 // minutes
}
}, log, log);
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc