diff --git a/README.md b/README.md index 3f97367..1ef6ed4 100755 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Note: This plugin communicates with the KlikAanKlikUit ICS-1000 or Lightwave Lin Set the correct manager_host in the configuration: - web.trustsmartcloud.com - lightwaverfhost.co.uk +- control-api.lightwaverf.com # Installation diff --git a/lib/lightwaverf.js b/lib/lightwaverf.js index d38595c..fb28e5d 100644 --- a/lib/lightwaverf.js +++ b/lib/lightwaverf.js @@ -416,6 +416,16 @@ LightwaveRF.prototype.getDevices = function(roomsString,devicesString,typesStrin * Connect to the server and obtain the configuration */ LightwaveRF.prototype.getConfiguration = function(email,pin,manager_host, manager_host_path,callback){ + + if(manager_host === "control-api.lightwaverf.com") { + this.getConfigurationV2(email, pin, manager_host, callback); + } else { + this.getConfigurationV1(email, pin, manager_host, manager_host_path, callback) + } +} + + +LightwaveRF.prototype.getConfigurationV1 = function(email,pin,manager_host, manager_host_path,callback) { // An object of options to indicate where to post to var post_options = { //host: 'lightwaverfhost.co.uk', @@ -519,4 +529,330 @@ LightwaveRF.prototype.getConfiguration = function(email,pin,manager_host, manage post_req.end(); } +/** + * New lightwaveRF server methods + */ +LightwaveRF.prototype.getConfigurationV2 = function(user, pin, host, callback) +{ + var host_url = "https://" + host;//control-api.lightwaverf.com'; + this.log("getConfigurationV2 " + host_url); + this.getApplicationKey(user, pin, host_url, callback); +} + +/** + * Connect to the server and obtain the application key + */ +LightwaveRF.prototype.getApplicationKey = function(user, pin, host, callback) +{ + this.log.debug('Getting Application Key from LightWave'); + + https.get(host + '/v1/user?password=' + pin + '&username=' + user, function(res) { + + const { statusCode } = res; + const contentType = res.headers['content-type']; + + let error; + // Any 2xx status code signals a successful response but + // here we're only checking for 200. + if (statusCode !== 200) { + error = "Request Failed.\nStatus Code: " + statusCode; + } else if (!/^application\/json/.test(contentType)) { + error = "Invalid content-type.\nExpected application/json but received " + contentType; + } + if (error) { + this.log.error(error); + // Consume response data to free up memory + res.resume(); + if(callback) callback(error); + return; + } + + res.setEncoding('utf8'); + let rawData = ''; + res.on('data', (chunk) => { rawData += chunk; }); + res.on('end', () => { + try { + const parsedData = JSON.parse(rawData); + this.log.debug("getApplicationKey parsedData = "); + this.log.debug(parsedData); + + this.application_key = parsedData.application_key; + + this.getToken(this.application_key, host, callback); + + } catch (e) { + this.log.error(e); + if(callback) callback(error); + } + }); + }.bind(this)).on('error', (e) => { + this.log.error("Got error: " + e); + if(callback) callback(error); + }); + +} + +/** + * Connect to the server and obtain the token + */ +LightwaveRF.prototype.getToken = function(application_key, host, callback) +{ + this.log.debug('Getting token from LightWave'); + + https.get(host + '/v1/auth?application_key=' + application_key, function (res) { + + const { statusCode } = res; + const contentType = res.headers['content-type']; + + let error; + // Any 2xx status code signals a successful response but + // here we're only checking for 200. + if (statusCode !== 200) { + error = "Request Failed.\nStatus Code: " + statusCode; + } else if (!/^application\/json/.test(contentType)) { + error = "Invalid content-type.\nExpected application/json but received " + contentType; + } + if (error) { + this.log.error(error); + // Consume response data to free up memory + res.resume(); + if(callback) callback(error); + return; + } + + res.setEncoding('utf8'); + let rawData = ''; + res.on('data', (chunk) => { rawData += chunk; }); + res.on('end', () => { + try { + const parsedData = JSON.parse(rawData); + this.log.debug("getToken parsedData = "); + this.log.debug(parsedData); + + this.token = parsedData.token; + + this.getDeviceTypes(this.token, host, callback); + + } catch (e) { + this.log.error(e); + if(callback) callback(error); + } + }); + }.bind(this)).on('error', (e) => { + this.log.error("Got error: " + e); + if(callback) callback(error); + }); +} + +/** + * Connect to the server and obtain the device types + */ +LightwaveRF.prototype.getDeviceTypes = function(token, host, callback) +{ + this.log.debug('Getting device types from LightWave'); + + var options = { + headers: { + 'X-LWRF-token': token, + 'X-LWRF-platform': 'ios', + 'X-LWRF-skin': 'lightwaverf' + } + }; + + https.get(host + '/v1/device_type?nested=1', options, function (res) { + + const { statusCode } = res; + const contentType = res.headers['content-type']; + + let error; + // Any 2xx status code signals a successful response but + // here we're only checking for 200. + if (statusCode !== 200) { + error = "Request Failed.\nStatus Code: " + statusCode; + } else if (!/^application\/json/.test(contentType)) { + error = "Invalid content-type.\nExpected application/json but received " + contentType; + } + if (error) { + this.log.error(error); + // Consume response data to free up memory + res.resume(); + if(callback) callback(error); + return; + } + + res.setEncoding('utf8'); + let rawData = ''; + res.on('data', (chunk) => { rawData += chunk; }); + res.on('end', () => { + try { + const parsedData = JSON.parse(rawData); + this.log.debug("getDeviceTypes parsedData = "); + this.log.debug(parsedData); + + this.getUserProfile(this.token, host, callback); + + } catch (e) { + this.log.error(e); + if(callback) callback(error); + } + }); + }.bind(this)).on('error', (e) => { + this.log.error("Got error: " + e); + if(callback) callback(error); + }); +} + +/** + * Connect to the server and obtain the device types + */ +LightwaveRF.prototype.getUserProfile = function(token, host, callback) +{ + this.log.debug('Getting user profile from LightWave'); + + var options = { + headers: { + 'X-LWRF-token': token, + 'X-LWRF-platform': 'ios', + 'X-LWRF-skin': 'lightwaverf' + } + }; + + https.get(host + '/v1/user_profile?nested=1', options, function (res) { + + const { statusCode } = res; + const contentType = res.headers['content-type']; + + let error; + // Any 2xx status code signals a successful response but + // here we're only checking for 200. + if (statusCode !== 200) { + error = "Request Failed.\nStatus Code: " + statusCode; + } else if (!/^application\/json/.test(contentType)) { + error = "Invalid content-type.\nExpected application/json but received " + contentType; + } + if (error) { + this.log.error(error); + // Consume response data to free up memory + res.resume(); + if(callback) callback(error); + return; + } + + res.setEncoding('utf8'); + let rawData = ''; + res.on('data', (chunk) => { rawData += chunk; }); + res.on('end', () => { + try { + const parsedData = JSON.parse(rawData); + this.log.debug("getUserProfile parsedData = "); + this.log.debug(parsedData); + + this.parseRooms(parsedData, callback); + + } catch (e) { + this.log.error(e); + if(callback) callback(error); + } + }); + }.bind(this)).on('error', (e) => { + this.log.error("Got error: " + e); + if(callback) callback(error); + }); +} + +/** + * Parse the response + */ +LightwaveRF.prototype.parseRooms = function(lightwaveResponse, callback) +{ + if(!lightwaveResponse.content.estates) { + this.log.warn("No estates found. Please create an estate for lightwaverf: " + lightwaveResponse.content); + if(callback) callback(1); + } else if(!lightwaveResponse.content.estates[0]) { + this.log.warn("No estates[0] empty. Please create an estate for lightwaverf: " + lightwaveResponse.content); + if(callback) callback(1); + } else if(!lightwaveResponse.content.estates[0].locations) { + this.log.warn("No locations found. Please create a location for lightwaverf: " + lightwaveResponse.content.estates[0]); + if(callback) callback(1); + } else if(!lightwaveResponse.content.estates[0].locations[0]) { + this.log.warn("No estates[0].locations[0] empty. Please create a location for lightwaverf and assign rooms: " + lightwaveResponse.content.estates[0]); + if(callback) callback(1); + } else if(!lightwaveResponse.content.estates[0].locations[0].zones) { + this.log.warn("No zones found. Please create a zone for lightwaverf: " + lightwaveResponse.content.estates[0].locations[0]); + if(callback) callback(1); + } else if(!lightwaveResponse.content.estates[0].locations[0].zones[0]) { + this.log.warn("No estates[0].zones[0] empty. Please create a zone for lightwaverf and assign rooms: " + lightwaveResponse.content.estates[0].locations[0].zones[0]); + if(callback) callback(1); + } else if(!lightwaveResponse.content.estates[0].locations[0].zones[0].rooms) { + this.log.warn("No rooms found. Please create a room for lightwaverf. And assign it to: " + lightwaveResponse.content.estates[0].locations[0].zones[0]); + if(callback) callback(1); + } else if(!lightwaveResponse.content.estates[0].locations[0].zones[0].rooms[0]) { + this.log.warn("No estates[0].zones[0].rooms[0] empty. Please create a room for lightwaverf and assign it to: " + lightwaveResponse.content.estates[0].locations[0].zones[0]); + if(callback) callback(1); + } + else { + + this.log.debug("Parsing lightwaveResponse: " + lightwaveResponse.content.estates[0].locations[0].zones[0].rooms[0].devices); + + var home = lightwaveResponse.content.estates[0].locations[0].zones[0]; + + var rooms = []; + for(var i=0; i < home.rooms.length; i++) { + var r = home.rooms[i]; + var room = { + name: r.name, + number: r.room_number, + status: r.name, + active: r.active === 1, + devices: [] + }; + + rooms.push(room); + + this.log.debug("Room " + room.name + " with " + (r.devices ? r.devices.length : "0 ") + " devices"); + + for (var j = 0; j < (r.devices ? r.devices.length: 0); j++) { + var d = r.devices[j]; + + var device = { + name: d.name, + status: d.name, + number: d.device_number, + active: d.active === 1, + mood: false + }; + + this.log.debug("Adding device " + device) + room.devices.push(device); + + // Get device types + // O: On/Off Switch + // D: Dimmer + // R: Radiator(s) + // P: Open/Close + // I: Inactive (i.e. not configured) + // m: Mood (inactive) + // M: Mood (active) + // o: All Off + var dType = ""; + if(d.active === 0) dType = "I"; + else if(d.device_type_id === 1) dType = "O"; + else if(d.device_type_id === 2) dType = "D"; + else if(d.device_type_id === 3) dType = "P"; + else if(d.device_type_id === 11) dType = "O"; + else if(d.device_type_id === 12) dType = "O"; + + this.devices.push({roomId:r.room_number,roomName:r.name, + deviceId:d.device_number,deviceName:d.name, + deviceType:dType}); + + } + } + + this.log.debug('Rooms:' + rooms) + + if(callback) callback(this.devices, this); + } +}; + module.exports = LightwaveRF; diff --git a/package.json b/package.json index 1503324..adebad0 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-lightwaverf", - "version": "0.3.9", + "version": "0.4.0", "description": "LightwaveRF plugin for homebridge: https://github.com/nfarina/homebridge", "license": "ISC", "keywords": [