diff --git a/examples/example-neo.js b/examples/example-neo.js index dbcc458..3f9d5b2 100644 --- a/examples/example-neo.js +++ b/examples/example-neo.js @@ -1,61 +1,63 @@ -var heatmiser = require("../lib/heatmiser"); - -var neo = new heatmiser.Neo("192.168.1.112"); - -neo.on('success', function(data) { - console.log(data); -}); -neo.on('error', function(data) { - console.log(data); -}); - -neo.info(); -neo.statistics(); - -var devices = ['bathroom', 'livingroom']; - -neo.setAway(false, devices); -neo.setStandby(false, devices); - -var comfortLevels = { - "bathroom": { - "monday": { - "wake": ["07:00", 20], - "leave": ["09:00", 16], - "return": ["24:00", 21], - "sleep": ["24:00", 16] - }, - "sunday": { - "wake": ["09:00", 20], - "leave": ["11:00", 16], - "return": ["24:00", 21], - "sleep": ["24:00", 16] - } - }, - "livingroom": { - "monday": { - "wake": ["07:00", 19], - "leave": ["08:30", 16], - "return": ["16:30", 19], - "sleep": ["23:00", 16] +var heatmiser = require( "../lib/heatmiser" ); + +var neo = new heatmiser.Neo(); +neo.on( 'ready', function () { + + neo.on( 'success', function ( data ) { + console.log( data ); + } ); + neo.on( 'error', function ( data ) { + console.log( data ); + } ); + + neo.info(); + neo.statistics(); + + var devices = [ 'bathroom', 'livingroom' ]; + + neo.setAway( false, devices ); + neo.setStandby( false, devices ); + + var comfortLevels = { + "bathroom": { + "monday": { + "wake": [ "07:00", 20 ], + "leave": [ "09:00", 16 ], + "return": [ "24:00", 21 ], + "sleep": [ "24:00", 16 ] + }, + "sunday": { + "wake": [ "09:00", 20 ], + "leave": [ "11:00", 16 ], + "return": [ "24:00", 21 ], + "sleep": [ "24:00", 16 ] + } }, - "sunday": { - "wake": ["09:00", 19], - "leave": ["10:00", 16], - "return": ["20:00", 19], - "sleep": ["23:00", 16] + "livingroom": { + "monday": { + "wake": [ "07:00", 19 ], + "leave": [ "08:30", 16 ], + "return": [ "16:30", 19 ], + "sleep": [ "23:00", 16 ] + }, + "sunday": { + "wake": [ "09:00", 19 ], + "leave": [ "10:00", 16 ], + "return": [ "20:00", 19 ], + "sleep": [ "23:00", 16 ] + } } } -} -var keys = Object.keys(comfortLevels); -for (var i=0; i> 12) ^ nibble]; - } +var crc16 = function ( buf ) { + // Thanks to http://code.google.com/p/heatmiser-wifi/ for the algorithm + // Process 4 bits of data + var crc16_4bits = function ( crc, nibble ) { + var lookup = [ 0x0000, 0x1021, 0x2042, 0x3063, + 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, + 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF ]; + return ((crc << 4) & 0xffff) ^ lookup[ (crc >> 12) ^ nibble ]; + } - // Process the whole message - var crc = 0xffff; - for(var i=0; i> 4); - crc = crc16_4bits(crc, buf[i] & 0x0f); - } + // Process the whole message + var crc = 0xffff; + for ( var i = 0; i < buf.length; i++ ) { + crc = crc16_4bits( crc, buf[ i ] >> 4 ); + crc = crc16_4bits( crc, buf[ i ] & 0x0f ); + } - // Return the CRC - return crc; + // Return the CRC + return crc; } +var parse_dcb = function ( dcb_buf ) { + var model = [ 'DT', 'DT-E', 'PRT', 'PRT-E', 'PRTHW' ][ dcb_buf.readUInt8( 4 ) ]; + var version = dcb_buf.readUInt8( 3 ); + var length = dcb_buf.readUInt16LE( 0 ); + if ( length != dcb_buf.length ) throw "Incorrect DCB length"; + if ( model !== 'PRTHW' ) version &= 0x7F; + var program_mode = [ '5/2', '7' ][ dcb_buf.readUInt8( 16 ) ]; - -var parse_dcb = function(dcb_buf){ - var model = ['DT', 'DT-E', 'PRT', 'PRT-E', 'PRTHW'][dcb_buf.readUInt8(4)]; - var version = dcb_buf.readUInt8(3); - var length = dcb_buf.readUInt16LE(0); - if(length != dcb_buf.length) throw "Incorrect DCB length"; - if(model !== 'PRTHW') version &= 0x7F; - var program_mode = ['5/2', '7'][dcb_buf.readUInt8(16)]; - - return { - length: length, - vendor_id: ['HEATMISER', 'OEM'][dcb_buf.readUInt8(2)], - model: model, - version: version/10, - temp_format: ['C', 'F'][dcb_buf.readUInt8(5)], - switch_differential: dcb_buf.readUInt8(6)/2, - frost_protection: !!dcb_buf.readUInt8(7), - calibration_offset: dcb_buf.readUInt16LE(8), - output_delay: dcb_buf.readUInt8(10), - up_down_key_limit: dcb_buf.readUInt8(11), - sensor_selection: ['built_in_only', 'remote_only', 'floor_only', 'built_in+floor', 'remote+floor'][dcb_buf.readUInt8(13)], - optimum_start: dcb_buf.readUInt8(14), - rate_of_change: dcb_buf.readUInt8(15), - program_mode: program_mode, - frost_protect_temp: dcb_buf.readUInt8(17), - set_room_temp: dcb_buf.readUInt8(18), - floor_max_limit: dcb_buf.readUInt8(19), - floor_max_limit_enabled: !!dcb_buf.readUInt8(20), - device_on: !!dcb_buf.readUInt8(21), - key_lock: !!dcb_buf.readUInt8(22), - run_mode: ['heating', 'frost_protection'][dcb_buf.readUInt8(23)], - away_mode: !!dcb_buf.readUInt8(24), - holiday_enabled: !!dcb_buf.readUInt8(30), - holiday_return_date: {}, - temp_hold_minutes: dcb_buf.readUInt16LE(31), - remote_air_temp: dcb_buf.readUInt16LE(33) === 0xFFFF ? null : dcb_buf.readUInt16LE(33)/10, - floor_temp: dcb_buf.readUInt16LE(35) === 0xFFFF ? null : dcb_buf.readUInt16LE(35)/10, - built_in_air_temp: dcb_buf.readUInt16LE(37) === 0xFFFF ? null : dcb_buf.readUInt16LE(37)/10, - error_code: dcb_buf.readUInt8(39), - heating_on: !!dcb_buf.readUInt8(40), - boost_in_min: dcb_buf.readUInt16LE(41), - hot_water_on: !!((model == 'PRTHW') ? dcb_buf.readUInt8(43) : false), - current_time: extract_date(dcb_buf, model), - } + return { + length: length, + vendor_id: [ 'HEATMISER', 'OEM' ][ dcb_buf.readUInt8( 2 ) ], + model: model, + version: version / 10, + temp_format: [ 'C', 'F' ][ dcb_buf.readUInt8( 5 ) ], + switch_differential: dcb_buf.readUInt8( 6 ) / 2, + frost_protection: !!dcb_buf.readUInt8( 7 ), + calibration_offset: dcb_buf.readUInt16LE( 8 ), + output_delay: dcb_buf.readUInt8( 10 ), + up_down_key_limit: dcb_buf.readUInt8( 11 ), + sensor_selection: [ 'built_in_only', 'remote_only', 'floor_only', 'built_in+floor', 'remote+floor' ][ dcb_buf.readUInt8( 13 ) ], + optimum_start: dcb_buf.readUInt8( 14 ), + rate_of_change: dcb_buf.readUInt8( 15 ), + program_mode: program_mode, + frost_protect_temp: dcb_buf.readUInt8( 17 ), + set_room_temp: dcb_buf.readUInt8( 18 ), + floor_max_limit: dcb_buf.readUInt8( 19 ), + floor_max_limit_enabled: !!dcb_buf.readUInt8( 20 ), + device_on: !!dcb_buf.readUInt8( 21 ), + key_lock: !!dcb_buf.readUInt8( 22 ), + run_mode: [ 'heating', 'frost_protection' ][ dcb_buf.readUInt8( 23 ) ], + away_mode: !!dcb_buf.readUInt8( 24 ), + holiday_enabled: !!dcb_buf.readUInt8( 30 ), + holiday_return_date: {}, + temp_hold_minutes: dcb_buf.readUInt16LE( 31 ), + remote_air_temp: dcb_buf.readUInt16LE( 33 ) === 0xFFFF ? null : dcb_buf.readUInt16LE( 33 ) / 10, + floor_temp: dcb_buf.readUInt16LE( 35 ) === 0xFFFF ? null : dcb_buf.readUInt16LE( 35 ) / 10, + built_in_air_temp: dcb_buf.readUInt16LE( 37 ) === 0xFFFF ? null : dcb_buf.readUInt16LE( 37 ) / 10, + error_code: dcb_buf.readUInt8( 39 ), + heating_on: !!dcb_buf.readUInt8( 40 ), + boost_in_min: dcb_buf.readUInt16LE( 41 ), + hot_water_on: !!((model == 'PRTHW') ? dcb_buf.readUInt8( 43 ) : false), + current_time: extract_date( dcb_buf, model ), + } } -var extract_date = function(dcb_buf, model){ - var offset = (model == 'PRTHW') ? 3 : 0 - return new Date(2000 + dcb_buf.readUInt8(41 + offset), dcb_buf.readUInt8(42 + offset) -1 , dcb_buf.readUInt8(43 + offset), dcb_buf.readUInt8(45 + offset), dcb_buf.readUInt8(46 + offset), dcb_buf.readUInt8(47 + offset)) +var extract_date = function ( dcb_buf, model ) { + var offset = (model == 'PRTHW') ? 3 : 0 + return new Date( 2000 + dcb_buf.readUInt8( 41 + offset ), dcb_buf.readUInt8( 42 + offset ) - 1, dcb_buf.readUInt8( 43 + offset ), dcb_buf.readUInt8( 45 + offset ), dcb_buf.readUInt8( 46 + offset ), dcb_buf.readUInt8( 47 + offset ) ) } -var parse_response = function(response){ - var code = response.readUInt8(0); - if(code != 0x94) throw "Invalid return code"; - var frame_len = response.readUInt16LE(1); - if(frame_len != response.length) throw "Incorrect packet length"; - var crc = response.readUInt16LE(frame_len - 2); - var calc_crc = crc16(response.slice(0, frame_len - 2)); - if(crc != calc_crc) throw "Incorrect CRC"; +var parse_response = function ( response ) { + var code = response.readUInt8( 0 ); + if ( code != 0x94 ) throw "Invalid return code"; + var frame_len = response.readUInt16LE( 1 ); + if ( frame_len != response.length ) throw "Incorrect packet length"; + var crc = response.readUInt16LE( frame_len - 2 ); + var calc_crc = crc16( response.slice( 0, frame_len - 2 ) ); + if ( crc != calc_crc ) throw "Incorrect CRC"; - return { - code: code, - frame_len: frame_len, - crc: crc, - start_addr: response.readUInt16LE(3), - num_bytes: response.readUInt16LE(5), - dcb: parse_dcb(response.slice(7, frame_len - 2)) - } + return { + code: code, + frame_len: frame_len, + crc: crc, + start_addr: response.readUInt16LE( 3 ), + num_bytes: response.readUInt16LE( 5 ), + dcb: parse_dcb( response.slice( 7, frame_len - 2 ) ) + } } // Construct an arbitrary thermostat command -Wifi.prototype.command = function(operation, data, callback) { - var 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 +Wifi.prototype.command = function ( operation, data, callback ) { + var 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); - }); + var client = net.connect( { host: this.host, port: this.port }, function () { //'connect' listener + client.write( buf ); + } ); - client.setTimeout(3000); - client.on('data', function(data) { - var obj = parse_response(data); - this.model = obj.dcb.model; - // if callback is set don't emit an event - if (typeof callback === 'undefined') { - this.emit('success', obj); - } else { - callback(obj); - } - client.end(); - }.bind(this)); - client.on('timeout', function(e){ - client.end(); - this.emit('error', (typeof e === 'undefined') ? new Error("Timed out") : e); - }.bind(this)); - client.on('error', function(e){ - client.end(); - this.emit('error', e); - }.bind(this)); + client.setTimeout( 3000 ); + client.on( 'data', function ( data ) { + var obj = parse_response( data ); + this.model = obj.dcb.model; + // if callback is set don't emit an event + if ( typeof callback === 'undefined' ) { + this.emit( 'success', obj ); + } + else { + callback( obj ); + } + client.end(); + }.bind( this ) ); + client.on( 'timeout', function ( e ) { + client.end(); + this.emit( 'error', (typeof e === 'undefined') ? new Error( "Timed out" ) : e ); + }.bind( this ) ); + client.on( 'error', function ( e ) { + client.end(); + this.emit( 'error', e ); + }.bind( this ) ); } -Wifi.prototype.read_device = function(callback){ - this.info(callback); +Wifi.prototype.read_device = function ( callback ) { + this.info( callback ); } -Wifi.prototype.info = function(callback){ - this.command(0x93, new Buffer([0x00, 0x00, 0xFF, 0xFF]), callback); +Wifi.prototype.info = function ( callback ) { + this.command( 0x93, new Buffer( [ 0x00, 0x00, 0xFF, 0xFF ] ), callback ); } -Wifi.prototype.write_device = function(data) { - var self = this; +Wifi.prototype.write_device = function ( data ) { + var self = this; - var do_write = function(items) { - 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 do_write = function ( items ) { + 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 ); - self.command(0xa3, buffer); - } + self.command( 0xa3, buffer ); + } - if (this.model == null) { - this.read_device(function(){ - try { - status_to_dcb(this.model, data, do_write); - } catch (e) { - this.emit('error', e); - } - }); - } else { - try { - status_to_dcb(this.model, data, do_write); - } catch (e) { - this.emit('error', e); + if ( this.model == null ) { + this.read_device( function () { + try { + status_to_dcb( this.model, data, do_write ); + } + catch ( e ) { + this.emit( 'error', e ); + } + } ); + } + else { + try { + status_to_dcb( this.model, data, do_write ); + } + catch ( e ) { + this.emit( 'error', e ); + } } - } } -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') { - if (data % 1 == 0) { - // integer - buf.writeUInt8(data, 3); - } else { - // float, wrong type - throw "Float value not valid, must be integer: " + data; +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' ) { + if ( data % 1 == 0 ) { + // integer + buf.writeUInt8( data, 3 ); + } + else { + // float, wrong type + throw "Float value not valid, must be integer: " + data; + } + } + else { + data.copy( buf, 3 ); } - } - else { - data.copy(buf, 3); - } - return buf; + return buf; } -var timeToByteBuffer = function(hours, minutes) { - var buf = new Buffer(2); - buf[0] = hours; - buf[1] = minutes; - 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 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, callback) { - var items = []; +var status_to_dcb = function ( model, data, callback ) { + var items = []; - var keys = Object.keys(data); - for (var i=0; i{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 "Unsupported item for writing: " + key; } - 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 "Unsupported item for writing: " + key; } - } - callback(items); + callback( items ); } module.exports = Wifi; diff --git a/package.json b/package.json index bad88a3..9400c81 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "author": "Ben Pirt, Carlos Sanchez", "license": "MIT", "readmeFilename": "README.md", - "dependencies" : { + "dependencies": { "ip": "1.0.1" }, "devDependencies": { diff --git a/test/wifi.js b/test/wifi.js index 342ebcb..f55a742 100755 --- a/test/wifi.js +++ b/test/wifi.js @@ -1,150 +1,152 @@ #!/usr/bin/env node -var expect = require("expect.js"), - sinon = require("sinon"), - net = require('net'), - heatmiser = require("../lib/heatmiser"); +var expect = require( "expect.js" ), + sinon = require( "sinon" ), + net = require( 'net' ), + heatmiser = require( "../lib/heatmiser" ); -describe('heatmiser wifi', function(){ +describe( 'heatmiser wifi', function () { - // pin in hex little endian: 0xd204 - var hm = new heatmiser.Wifi('localhost', 1234, 8068, 'PRT-E'); + // pin in hex little endian: 0xd204 + var hm = new heatmiser.Wifi( 'localhost', 1234, 8068, 'PRT-E' ); - // stub sockets - var stub = null; - var socket = null; + // stub sockets + var stub = null; + var socket = null; - beforeEach(function(){ - socket = new net.Socket({}); - sinon.stub(socket, 'setTimeout'); + beforeEach( function () { + socket = new net.Socket( {} ); + sinon.stub( socket, 'setTimeout' ); - socket.on('data', function(data) { - // data.should.eql('foo'); - // test is done or it will timeout and fail - // done(); - }); + socket.on( 'data', function ( data ) { + // data.should.eql('foo'); + // test is done or it will timeout and fail + // done(); + } ); - // Stub the net.connect function - sinon.stub(net, 'connect'); - // Asynchronously call the second argument with a null error and some text when passed certain arguments - net.connect.callsArgWithAsync(1).returns(socket); + // Stub the net.connect function + sinon.stub( net, 'connect' ); + // Asynchronously call the second argument with a null error and some text when passed certain arguments + net.connect.callsArgWithAsync( 1 ).returns( socket ); - hm.on('success', function(data) {}); - hm.on('error', function(error) { throw error; }); - }) + hm.on( 'success', function ( data ) { + } ); + hm.on( 'error', function ( error ) { + throw error; + } ); + } ) - // When done make sure you restore stubs to the original functionality. - afterEach(function(){ - net.connect.restore(); - if (stub != null) stub.restore(); - }) + // When done make sure you restore stubs to the original functionality. + afterEach( function () { + net.connect.restore(); + if ( stub != null ) stub.restore(); + } ) + + describe( '#Heatmiser()', function () { + + it( 'should return host, port and pin', function () { + expect( hm.host ).to.be( 'localhost' ); + expect( hm.pin ).to.be( 1234 ); + expect( hm.port ).to.be( 8068 ); + } ) + + } ); + + describe( '#read_device()', function () { + + it( 'should read the thermostat', function ( done ) { + + stub = sinon.stub( socket, 'write', function ( data, encoding, cb ) { + + // operation: 0x93 + // data length: 0x0b00 + // pin: 0xd204 + // data: 0x0000ffff + // checksum: 0x28b4 + expect( data.toString( 'hex' ) ).to.be( "930b00d2040000ffff28b4" ); + done(); + } ); + + hm.read_device(); + } ) + + } ); + + describe( '#write_device()', function () { + + it( 'should set the thermostat time', function ( done ) { + + stub = sinon.stub( socket, 'write', function ( data, encoding, cb ) { + + // operation: 0xA3 + // data length: 0x1200 + // pin: 0xD204 + // items: 0x01 + // data => position: 0x2B00 size: 0x07 data: 0x0D0C1903132601 (13-12-25 wed 19:38:01) + // checksum: 0x3E1C + expect( data.toString( 'hex' ) ).to.be( "A31200D204012B00070D0C19031326013E1C".toLowerCase() ); + done(); + } ); + + var dcb = { + time: new Date( 2013, 11, 25, 19, 38, 01 ) + } + + hm.write_device( dcb ); + } ) + + it( 'should set the thermostat to frost mode (away)', function ( done ) { + + stub = sinon.stub( socket, 'write', function ( data, encoding, cb ) { + + // operation: 0xA3 + // data length: 0x0C00 + // pin: 0xD204 + // items: 0x01 + // data => position: 0x1700 size: 0x01 data: 0x01 (frost) + // checksum: 0x38DC + expect( data.toString( 'hex' ) ).to.be( "A30C00D204011700010138DC".toLowerCase() ); + + done(); + } ); + + var dcb = { + runmode: 'frost_protection' + } + + hm.write_device( dcb ); + } ) + + it( 'should set the thermostat to temperature hold and limit floor', function ( done ) { + + stub = sinon.stub( socket, 'write', function ( data, encoding, cb ) { + + // operation: 0xA3 + // data length: 0x1500 + // pin: 0xD204 + // items: 0x03 + // data => + // position: 0x1200 size: 0x01 data: 0x14 (20C) + // position: 0x2000 size: 0x02 data: 0x1E00 (30 minutes) + // position: 0x1300 size: 0x01 data: 0x17 (23 C) + // checksum: 0x6CC1 + expect( data.toString( 'hex' ) ).to.be( "A31500D20403120001142000021E00130001176CC1".toLowerCase() ); - describe('#Heatmiser()', function(){ + done(); + } ); - it('should return host, port and pin', function(){ - expect(hm.host).to.be('localhost'); - expect(hm.pin).to.be(1234); - expect(hm.port).to.be(8068); - }) + var dcb = { + heating: { + target: 20, // C + hold: 30 // minutes + }, + floorlimit: { + floormax: 23 + } + } - }); + hm.write_device( dcb ); + } ) - describe('#read_device()', function(){ - - it('should read the thermostat', function(done){ - - stub = sinon.stub(socket, 'write', function (data, encoding, cb) { - - // operation: 0x93 - // data length: 0x0b00 - // pin: 0xd204 - // data: 0x0000ffff - // checksum: 0x28b4 - expect(data.toString('hex')).to.be("930b00d2040000ffff28b4"); - done(); - }); - - hm.read_device(); - }) - - }); - - - describe('#write_device()', function(){ - - it('should set the thermostat time', function(done){ - - stub = sinon.stub(socket, 'write', function (data, encoding, cb) { - - // operation: 0xA3 - // data length: 0x1200 - // pin: 0xD204 - // items: 0x01 - // data => position: 0x2B00 size: 0x07 data: 0x0D0C1903132601 (13-12-25 wed 19:38:01) - // checksum: 0x3E1C - expect(data.toString('hex')).to.be("A31200D204012B00070D0C19031326013E1C".toLowerCase()); - done(); - }); - - var dcb = { - time: new Date(2013,11,25,19,38,01) - } - - hm.write_device(dcb); - }) - - it('should set the thermostat to frost mode (away)', function(done){ - - stub = sinon.stub(socket, 'write', function (data, encoding, cb) { - - // operation: 0xA3 - // data length: 0x0C00 - // pin: 0xD204 - // items: 0x01 - // data => position: 0x1700 size: 0x01 data: 0x01 (frost) - // checksum: 0x38DC - expect(data.toString('hex')).to.be("A30C00D204011700010138DC".toLowerCase()); - - done(); - }); - - var dcb = { - runmode: 'frost_protection' - } - - hm.write_device(dcb); - }) - - it('should set the thermostat to temperature hold and limit floor', function(done){ - - stub = sinon.stub(socket, 'write', function (data, encoding, cb) { - - // operation: 0xA3 - // data length: 0x1500 - // pin: 0xD204 - // items: 0x03 - // data => - // position: 0x1200 size: 0x01 data: 0x14 (20C) - // position: 0x2000 size: 0x02 data: 0x1E00 (30 minutes) - // position: 0x1300 size: 0x01 data: 0x17 (23 C) - // checksum: 0x6CC1 - expect(data.toString('hex')).to.be("A31500D20403120001142000021E00130001176CC1".toLowerCase()); - - done(); - }); - - var dcb = { - heating: { - target: 20, // C - hold: 30 // minutes - }, - floorlimit: { - floormax: 23 - } - } - - hm.write_device(dcb); - }) - - }); -}) + } ); +} )