[UPDATE] Please use @tonesto7 Tony's much more polished version of the homebridge-hubitat plugin from the thread linked immediately below. Thank you Tony for your work on making this easy to install for users, and easier to use with Hubitat.
[Begin original message]
Yea, that is me doing that editing. I have the "JSON Complete API" app running on my Hubitat. One thing that I have noticed is that Hubitat is removing spaces in the JSON response, specifically in the names of the device capabilties. For example, "Temperature Measurement" is coming over as "TemperatureMeasurement", which then confuses the homebridge-smartthings plugin. I have even edited portions of the plugin to account for this, which has helped. But right now the direct updates from Hubitat to Homebridge are not working.
I hope to have more time to debug this and then try to get it released to the Hubitat community. I am totally open to any assistance from community members who are a little more skilled in Groovy and NodeJS though!
Using the code below, I have a working Hubitat Homebridge conficguration, but the DIRECT UPDATES are not working for some reason. Which means, you can use Homekit on your iOS device to control devices, but their status in Homekit is not being updated automatically. (UPDATE: Using the old polling method for updates does work, however it is not ideal as it places an unnecessary workload on the Hubitat Hub.)
I could really use some help from the community, if anyone is interested.
Here is my "JSON Complete API" app, updated for Hubitat.
/**
* JSON Complete API
*
* Copyright 2017 Paul Lovelace
*
* Modifications for Hubitat (in progress) - Dan Ogorchock 2/14/2018
*/
definition(
name: "JSON Complete API",
namespace: "pdlove",
author: "Paul Lovelace",
description: "API for JSON with complete set of devices",
category: "SmartThings Labs",
iconUrl: "https://raw.githubusercontent.com/pdlove/homebridge-smartthings/master/smartapps/JSON%401.png",
iconX2Url: "https://raw.githubusercontent.com/pdlove/homebridge-smartthings/master/smartapps/JSON%402.png",
iconX3Url: "https://raw.githubusercontent.com/pdlove/homebridge-smartthings/master/smartapps/JSON%403.png",
oauth: true)
preferences {
page(name: "copyConfig")
}
//When adding device groups, need to add here
def copyConfig() {
if (!state.accessToken) {
createAccessToken()
}
dynamicPage(name: "copyConfig", title: "Configure Devices", install:true, uninstall:true) {
section("Select devices to include in the /devices API call") {
paragraph "Version 0.5.5"
input "deviceList", "capability.refresh", title: "Most Devices", multiple: true, required: false
input "sensorList", "capability.sensor", title: "Sensor Devices", multiple: true, required: false
input "switchList", "capability.switch", title: "All Switches", multiple: true, required: false
//paragraph "Devices Selected: ${deviceList ? deviceList?.size() : 0}\nSensors Selected: ${sensorList ? sensorList?.size() : 0}\nSwitches Selected: ${switchList ? switchList?.size() : 0}"
}
section("Configure Pubnub") {
input "pubnubSubscribeKey", "text", title: "PubNub Subscription Key", multiple: false, required: false
input "pubnubPublishKey", "text", title: "PubNub Publish Key", multiple: false, required: false
input "subChannel", "text", title: "Channel (Can be anything)", multiple: false, required: false
}
section() {
paragraph "View this SmartApp's configuration to use it in other places."
//Original SmartThings cloud endpoint
//href url:"${apiServerUrl("/api/smartapps/installations/${app.id}/config?access_token=${state.accessToken}")}", style:"embedded", required:false, title:"Config", description:"Tap, select, copy, then click \"Done\""
//Hubitat cloud endpoint
//href url:"${getApiServerUrl()}/${hubUID}/apps/${app.id}/config?access_token=${state.accessToken}", style:"embedded", required:false, title:"Config", description:"Tap, select, copy, then click \"Done\""
//Hubitat local endpoint
href url:fullLocalApiServerUrl("config") + "?access_token=${state.accessToken}", style:"embedded", required:false, title:"Config", description:"Tap, select, copy, then click \"Done\""
}
section() {
paragraph "View the JSON generated from the installed devices."
//Original SmartThings cloud endpoint
//href url:"${apiServerUrl("/api/smartapps/installations/${app.id}/devices?access_token=${state.accessToken}")}", style:"embedded", required:false, title:"Device Results", description:"View accessories JSON"
//Hubitat cloud endpoint
//href url:"${getApiServerUrl()}/${hubUID}/apps/${app.id}/devices?access_token=${state.accessToken}", style:"embedded", required:false, title:"Device Results", description:"View accessories JSON"
//Hubitat local endpoint
href url:fullLocalApiServerUrl("devices") + "?access_token=${state.accessToken}", style:"embedded", required:false, title:"Device Results", description:"View accessories JSON"
}
section() {
paragraph "Enter the name you would like shown in the smart app list"
label title:"SmartApp Label (optional)", required: false
}
}
}
def renderDevices() {
def deviceData = []
deviceList.each {
try {
deviceData << [name: it.displayName,
basename: it.name,
deviceid: it.id,
status: it.status,
manufacturerName: it.getManufacturerName(),
modelName: it.getModelName(),
lastTime: it.getLastActivity(),
capabilities: deviceCapabilityList(it),
commands: deviceCommandList(it),
attributes: deviceAttributeList(it)
]
} catch (e) {
log.error("Error Occurred Parsing Device "+it.displayName+", Error " + e)
}
}
sensorList.each {
try {
deviceData << [name: it.displayName,
basename: it.name,
deviceid: it.id,
status: it.status,
manufacturerName: it.getManufacturerName(),
modelName: it.getModelName(),
lastTime: it.getLastActivity(),
capabilities: deviceCapabilityList(it),
commands: deviceCommandList(it),
attributes: deviceAttributeList(it)
]
} catch (e) {
log.error("Error Occurred Parsing Device "+it.displayName+", Error " + e)
}
}
switchList.each {
try {
deviceData << [name: it.displayName,
basename: it.name,
deviceid: it.id,
status: it.status,
manufacturerName: it.getManufacturerName(),
modelName: it.getModelName(),
lastTime: it.getLastActivity(),
capabilities: deviceCapabilityList(it),
commands: deviceCommandList(it),
attributes: deviceAttributeList(it)
]
} catch (e) {
log.error("Error Occurred Parsing Device "+it.displayName+", Error " + e)
}
}
return deviceData
}
def findDevice(paramid) {
def device = deviceList.find { it.id == paramid }
if (device) return device
device = sensorList.find { it.id == paramid }
if (device) return device
device = switchList.find { it.id == paramid }
return device
}
//No more individual device group definitions after here.
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
if(!state.accessToken) {
createAccessToken()
}
registerAll()
state.subscriptionRenewed = 0
subscribe(location, null, HubResponseEvent, [filterEvents:false])
log.debug "0.5.5"
}
def authError() {
[error: "Permission denied"]
}
def renderConfig() {
def configJson = new groovy.json.JsonOutput().toJson([
description: "JSON API",
platforms: [
[
platform: "SmartThings",
name: "SmartThings",
//
//Original SmartThings cloud endpoint
//app_url: apiServerUrl("/api/smartapps/installations/"),
//
//Hubitat cloud endpoint
//app_url: "${getApiServerUrl()}/${hubUID}/apps/",
//
//Hubitat local endpoint
app_url: getLocalApiServerUrl() + "/",
app_id: app.id,
access_token: state.accessToken
]
],
])
def configString = new groovy.json.JsonOutput().prettyPrint(configJson)
render contentType: "text/plain", data: configString
}
def renderLocation() {
[
latitude: location.latitude,
longitude: location.longitude,
mode: location.mode,
name: location.name,
temperature_scale: location.temperatureScale,
zip_code: location.zipCode,
hubIP: location.hubs[0].getDataValue("localIP"),
//hubIP: location.hubs[0].localIP,
smartapp_version: '0.5.5'
]
}
def CommandReply(statusOut, messageOut) {
def replyData =
[
status: statusOut,
message: messageOut
]
def replyJson = new groovy.json.JsonOutput().toJson(replyData)
render contentType: "application/json", data: replyJson
}
def deviceCommand() {
log.info("Command Request")
def device = findDevice(params.id)
def command = params.command
if (!device) {
log.error("Device Not Found")
CommandReply("Failure", "Device Not Found")
} else if (!device.hasCommand(command)) {
log.error("Device "+device.displayName+" does not have the command "+command)
CommandReply("Failure", "Device "+device.displayName+" does not have the command "+command)
} else {
def value1 = request.JSON?.value1
def value2 = request.JSON?.value2
try {
if (value2) {
device."$command"(value1,value2)
} else if (value1) {
device."$command"(value1)
} else {
device."$command"()
}
log.info("Command Successful for Device "+device.displayName+", Command "+command)
CommandReply("Success", "Device "+device.displayName+", Command "+command)
} catch (e) {
log.error("Error Occurred For Device "+device.displayName+", Command "+command)
CommandReply("Failure", "Error Occurred For Device "+device.displayName+", Command "+command)
}
}
}
def deviceAttribute() {
def device = findDevice(params.id)
def attribute = params.attribute
if (!device) {
httpError(404, "Device not found")
} else {
def currentValue = device.currentValue(attribute)
[currentValue: currentValue]
}
}
def deviceQuery() {
def device = findDevice(params.id)
if (!device) {
device = null
httpError(404, "Device not found")
}
if (result) {
def jsonData =
[
name: device.displayName,
deviceid: device.id,
capabilities: deviceCapabilityList(device),
commands: deviceCommandList(device),
attributes: deviceAttributeList(device)
]
def resultJson = new groovy.json.JsonOutput().toJson(jsonData)
render contentType: "application/json", data: resultJson
}
}
def deviceCapabilityList(device) {
def i=0
device.capabilities.collectEntries { capability->
[
(capability.name):1
]
}
}
def deviceCommandList(device) {
def i=0
device.supportedCommands.collectEntries { command->
[
(command.name): (command.arguments)
]
}
}
def deviceAttributeList(device) {
device.supportedAttributes.collectEntries { attribute->
try {
[
(attribute.name): device.currentValue(attribute.name)
]
} catch(e) {
[
(attribute.name): null
]
}
}
}
def getAllData() {
//Since we're about to send all of the data, we'll count this as a subscription renewal and clear out pending changes.
state.subscriptionRenewed = now()
state.devchanges = []
def deviceData =
[ location: renderLocation(),
deviceList: renderDevices() ]
def deviceJson = new groovy.json.JsonOutput().toJson(deviceData)
render contentType: "application/json", data: deviceJson
}
def startSubscription() {
//This simply registers the subscription.
state.subscriptionRenewed = now()
def deviceJson = new groovy.json.JsonOutput().toJson([status: "Success"])
render contentType: "application/json", data: deviceJson
}
def endSubscription() {
//Because it takes too long to register for an api command, we don't actually unregister.
//We simply blank the devchanges and change the subscription renewal to two hours ago.
state.devchanges = []
state.subscriptionRenewed = 0
def deviceJson = new groovy.json.JsonOutput().toJson([status: "Success"])
render contentType: "application/json", data: deviceJson
}
def registerAll() {
//This has to be done at startup because it takes too long for a normal command.
log.debug "Registering All Events"
state.devchanges = []
registerChangeHandler(deviceList)
registerChangeHandler(sensorList)
registerChangeHandler(switchList)
}
def registerChangeHandler(myList) {
myList.each { myDevice ->
def theAtts = myDevice.supportedAttributes
theAtts.each {att ->
subscribe(myDevice, att.name, changeHandler)
log.debug "Registering ${myDevice.displayName}.${att.name}"
}
}
}
def changeHandler(evt) {
//Send to Pubnub if we need to.
if (pubnubPublishKey!=null) {
def deviceData = [device: evt.deviceId, attribute: evt.name, value: evt.value, date: evt.date]
def changeJson = new groovy.json.JsonOutput().toJson(deviceData)
def changeData = URLEncoder.encode(changeJson)
def uri = "http://pubsub.pubnub.com/publish/${pubnubPublishKey}/${pubnubSubscribeKey}/0/${subChannel}/0/${changeData}"
log.debug "${uri}"
httpGet(uri)
}
if (state.directIP!="") {
//Send Using the Direct Mechanism
def deviceData = [device: evt.deviceId, attribute: evt.name, value: evt.value, date: evt.date]
//How do I control the port?!?
log.debug "Sending Update to ${state.directIP}:${state.directPort}"
def result = new hubitat.device.HubAction(
method: "GET",
path: "/update",
headers: [
HOST: "${state.directIP}:${state.directPort}",
change_device: evt.deviceId,
change_attribute: evt.name,
change_value: evt.value,
change_date: evt.date
]
)
//log.debug "changeHandler: sending result = ${result}"
sendHubCommand(result)
}
//Only add to the state's devchanges if the endpoint has renewed in the last 10 minutes.
if (state.subscriptionRenewed>(now()-(1000*60*10))) {
// if (evt.isStateChange()) {
log.debug "evt.getIsStateChange() = ${evt.getIsStateChange()}"
if (evt.getIsStateChange()) {
state.devchanges << [device: evt.deviceId, attribute: evt.name, value: evt.value, date: evt.date]
} else if (state.subscriptionRenewed>0) { //Otherwise, clear it
log.debug "Endpoint Subscription Expired. No longer storing changes for devices."
state.devchanges=[]
state.subscriptionRenewed=0
}
}
}
def getChangeEvents() {
//Store the changes so we can swap it out very quickly and eliminate the possibility of losing any.
//This is mainly to make this thread safe because I'm willing to bet that a change event can fire
//while generating/sending the JSON.
def oldchanges = state.devchanges
state.devchanges=[]
state.subscriptionRenewed = now()
if (oldchanges.size()==0) {
def deviceJson = new groovy.json.JsonOutput().toJson([status: "None"])
render contentType: "application/json", data: deviceJson
} else {
def changeJson = new groovy.json.JsonOutput().toJson([status: "Success", attributes:oldchanges])
render contentType: "application/json", data: changeJson
}
}
def enableDirectUpdates() {
log.debug("Command Request")
state.directIP = params.ip
state.directPort = params.port
log.debug("Trying ${state.directIP}:${state.directPort}")
def result = new hubitat.device.HubAction(
method: "GET",
path: "/initial",
headers: [
HOST: "${state.directIP}:${state.directPort}"
],
query: deviceData
)
sendHubCommand(result)
}
def HubResponseEvent(evt) {
log.debug(evt.description)
}
def locationHandler(evt) {
def description = evt.description
def hub = evt?.hubId
log.debug "cp desc: " + description
if (description.count(",") > 4)
{
def bodyString = new String(description.split(',')[5].split(":")[1].decodeBase64())
log.debug(bodyString)
}
}
def getSubscriptionService() {
def replyData =
[
pubnub_publishkey: pubnubPublishKey,
pubnub_subscribekey: pubnubSubscribeKey,
pubnub_channel: subChannel
]
def replyJson = new groovy.json.JsonOutput().toJson(replyData)
render contentType: "application/json", data: replyJson
}
mappings {
/* if (!params.access_token || (params.access_token && params.access_token != state.accessToken)) {
path("/devices") { action: [GET: "authError"] }
path("/config") { action: [GET: "authError"] }
path("/location") { action: [GET: "authError"] }
path("/:id/command/:command") { action: [POST: "authError"] }
path("/:id/query") { action: [GET: "authError"] }
path("/:id/attribute/:attribute") { action: [GET: "authError"] }
path("/subscribe") { action: [GET: "authError"] }
path("/getUpdates") { action: [GET: "authError"] }
path("/unsubscribe") { action: [GET: "authError"] }
path("/startDirect/:ip/:port") { action: [GET: "authError"] }
path("/getSubcriptionService") { action: [GET: "authError"] }
} else {
*/
path("/devices") { action: [GET: "getAllData"] }
path("/config") { action: [GET: "renderConfig"] }
path("/location") { action: [GET: "renderLocation"] }
path("/:id/command/:command") { action: [POST: "deviceCommand"] }
path("/:id/query") { action: [GET: "deviceQuery"] }
path("/:id/attribute/:attribute") { action: [GET: "deviceAttribute"] }
path("/subscribe") { action: [GET: "startSubscription"] }
path("/getUpdates") { action: [GET: "getChangeEvents"] }
path("/unsubscribe") { action: [GET: "endSubscription"] }
path("/startDirect/:ip/:port") { action: [GET: "enableDirectUpdates"] }
path("/getSubcriptionService") { action: [GET: "getSubscriptionService"] }
// }
}
And here is my edited version of smartthingsapi.js which runs on the homebridge app server (in my case, a raspberry pi).
var http = require('http');
var url = require('url');
var app_host;
var app_port;
var app_path;
var access_token;
function _http(data, callback) {
//console.log("Calling Smartthings");
var options = {
hostname: app_host,
port: app_port,
path: app_path + data.path + "?access_token=" + access_token,
method: data.method,
headers: {}
};
var that = this;
if (data.data) {
data.data = JSON.stringify(data.data);
options.headers['Content-Length'] = Buffer.byteLength(data.data);
options.headers['Content-Type'] = "application/json";
}
var str = '';
var req = http.request(options, function (response) {
response.on('data', function (chunk) {
str += chunk;
});
response.on('end', function () {
if (data.debug) console.log("response in http:", str);
try {
str = JSON.parse(str);
} catch (e) {
if (data.debug) {
console.log(e.stack);
console.log("raw message", str);
}
str = undefined;
}
if (callback) { callback(str); callback=undefined; };
});
});
if (data.data) {
req.write(data.data);
}
req.end();
req.on('error', function (e) {
console.log("error at req: ", e.message);
if (callback) { callback(); callback=undefined; };
});
}
function POST(data, callback) {
data.method = "POST";
_http(data, callback);
}
function PUT(data, callback) {
data.method = "PUT";
_http(data, callback);
}
function GET(data, callback) {
data.method = "GET";
_http(data, callback);
}
function DELETE(data, callback) {
data.method = "DELETE";
_http(data, callback);
}
var smartthings = {
init: function (inURL, inAppID, inAccess_Token) {
var appURL = url.parse(inURL);
app_host = appURL.hostname || "graph.api.smartthings.com";
app_port = appURL.port || "80";
app_path = (appURL.path || "/api/smartapps/installations/") + inAppID + "/";
access_token = inAccess_Token;
},
getDevices: function (callback) {
GET({ debug: false, path: 'devices' }, function (data) {
if (callback) { callback(data); callback=undefined; };
})
},
getDevice: function (deviceid, callback) {
GET({ debug: false, path: deviceid + '/query' }, function (data) {
if (data) {
if (callback) { callback(data); callback=undefined; };
} else {
if (callback) { callback(); callback=undefined; };;
}
})
},
getUpdates: function (callback) {
GET({ debug: false, path: 'getUpdates' }, function (data) {
if (callback) { callback(data); callback=undefined; };;
})
},
runCommand: function (callback, deviceid, command, values) {
POST({ debug: false, path: deviceid + '/command/' + command, data: values }, function (data) {
if (callback) { callback(); callback=undefined; };;
})
},
startDirect: function (callback, myIP, myPort) {
GET({ debug: false, path: 'startDirect/' + myIP + '/' + myPort }, function (data) {
if (callback) { callback(); callback=undefined; };;
})
},
getSubscriptionService: function (callback) {
GET({ debug: false, path: 'getSubcriptionService' }, function (data) {
if (callback) { callback(data); callback=undefined; };;
})
}
}
module.exports = smartthings;
And finally, here is my copy of smartthings.js which is modified to account for the lack of spaces in the device Capabilities names returned by Hubitat.
var inherits = require('util').inherits;
var Accessory, Service, Characteristic, uuid, EnergyCharacteristics;
/*
* SmartThings Accessory
*/
module.exports = function(oAccessory, oService, oCharacteristic, ouuid) {
if (oAccessory) {
Accessory = oAccessory;
Service = oService;
Characteristic = oCharacteristic;
EnergyCharacteristics = require('../lib/customCharacteristics').EnergyCharacteristics(Characteristic)
uuid = ouuid;
inherits(SmartThingsAccessory, Accessory);
SmartThingsAccessory.prototype.loadData = loadData;
SmartThingsAccessory.prototype.getServices = getServices;
}
return SmartThingsAccessory;
};
module.exports.SmartThingsAccessory = SmartThingsAccessory;
function SmartThingsAccessory(platform, device) {
this.deviceid = device.deviceid;
this.name = device.name;
this.platform = platform;
this.state = {};
this.device = device;
var idKey = 'hbdev:smartthings:' + this.deviceid;
var id = uuid.generate(idKey);
Accessory.call(this, this.name, id);
var that = this;
//Get the Capabilities List
for (var index in device.capabilities) {
if ((platform.knownCapabilities.indexOf(index) == -1) && (platform.unknownCapabilities.indexOf(index) == -1))
platform.unknownCapabilities.push(index);
}
this.getaddService = function(Service) {
var myService = this.getService(Service);
if (!myService) myService = this.addService(Service);
return myService
};
this.deviceGroup = "unknown"; //This way we can easily tell if we set a device group
var thisCharacteristic;
if (device.capabilities["SwitchLevel"] !== undefined) {
if (device.commands.levelOpenClose) {
//This is a Window Shade
this.deviceGroup = "shades"
thisCharacteristic = this.getaddService(Service.WindowCovering).getCharacteristic(Characteristic.TargetPosition)
thisCharacteristic.on('get', function(callback) { callback(null, parseInt(that.device.attributes.level)); });
thisCharacteristic.on('set', function(value, callback) { that.platform.api.runCommand(callback, that.deviceid, "setLevel", { value1: value }); });
that.platform.addAttributeUsage("level", this.deviceid, thisCharacteristic);
thisCharacteristic = this.getaddService(Service.WindowCovering).getCharacteristic(Characteristic.CurrentPosition)
thisCharacteristic.on('get', function(callback) { callback(null, parseInt(that.device.attributes.level)); });
that.platform.addAttributeUsage("level", this.deviceid, thisCharacteristic);
} else if (device.commands.lowSpeed) {
//This is a Ceiling Fan
this.deviceGroup = "fans"
thisCharacteristic = this.getaddService(Service.Fan).getCharacteristic(Characteristic.On)
thisCharacteristic.on('get', function(callback) { callback(null, that.device.attributes.switch == "on"); })
thisCharacteristic.on('set', function(value, callback) {
if (value)
that.platform.api.runCommand(callback, that.deviceid, "on");
else
that.platform.api.runCommand(callback, that.deviceid, "off"); });
that.platform.addAttributeUsage("switch", this.deviceid, thisCharacteristic);
thisCharacteristic = this.getaddService(Service.Fan).getCharacteristic(Characteristic.RotationSpeed)
thisCharacteristic.on('get', function(callback) { callback(null, parseInt(that.device.attributes.level)); });
thisCharacteristic.on('set', function(value, callback) {
if (value > 0)
that.platform.api.runCommand(callback, that.deviceid, "setLevel", {value1: value }); });
that.platform.addAttributeUsage("level", this.deviceid, thisCharacteristic);
} else if (device.commands.setLevel) {
this.deviceGroup = "lights";
thisCharacteristic = this.getaddService(Service.Lightbulb).getCharacteristic(Characteristic.On)
thisCharacteristic.on('get', function(callback) { callback(null, that.device.attributes.switch == "on"); });
thisCharacteristic.on('set', function(value, callback) {
if (value)
that.platform.api.runCommand(callback, that.deviceid, "on");
else
that.platform.api.runCommand(callback, that.deviceid, "off"); });
that.platform.addAttributeUsage("switch", this.deviceid, thisCharacteristic);
thisCharacteristic = this.getaddService(Service.Lightbulb).getCharacteristic(Characteristic.Brightness)
thisCharacteristic.on('get', function(callback) { callback(null, parseInt(that.device.attributes.level)); });
thisCharacteristic.on('set', function(value, callback) { that.platform.api.runCommand(callback, that.deviceid, "setLevel", { value1: value }); });
that.platform.addAttributeUsage("level", this.deviceid, thisCharacteristic);
if (device.capabilities["ColorControl"] !== undefined) {
thisCharacteristic = this.getaddService(Service.Lightbulb).getCharacteristic(Characteristic.Hue)
thisCharacteristic.on('get', function(callback) { callback(null, Math.round(that.device.attributes.hue*3.6)); });
thisCharacteristic.on('set', function(value, callback) { that.platform.api.runCommand(callback, that.deviceid, "setHue", { value1: Math.round(value/3.6) }); });
that.platform.addAttributeUsage("hue", this.deviceid, thisCharacteristic);
thisCharacteristic = this.getaddService(Service.Lightbulb).getCharacteristic(Characteristic.Saturation)
thisCharacteristic.on('get', function(callback) { callback(null, parseInt(that.device.attributes.saturation)); });
thisCharacteristic.on('set', function(value, callback) { that.platform.api.runCommand(callback, that.deviceid, "setSaturation", { value1: value }); });
that.platform.addAttributeUsage("saturation", this.deviceid, thisCharacteristic);
}
}
}
if (device.capabilities["GarageDoorControl"] !== undefined) {
this.deviceGroup = "garage_doors";
thisCharacteristic = this.getaddService(Service.GarageDoorOpener).getCharacteristic(Characteristic.TargetDoorState)
thisCharacteristic.on('get', function(callback) {
if (that.device.attributes.door == 'closed' || that.device.attributes.door == 'closing')
callback(null, Characteristic.TargetDoorState.CLOSED);
else if (that.device.attributes.door == 'open' || that.device.attributes.door == 'opening')
callback(null, Characteristic.TargetDoorState.OPEN); });
thisCharacteristic.on('set', function(value, callback) {
if (value == Characteristic.TargetDoorState.OPEN) {
that.platform.api.runCommand(callback, that.deviceid, "open");
that.device.attributes.door = "opening";
} else if (value == Characteristic.TargetDoorState.CLOSED) {
that.platform.api.runCommand(callback, that.deviceid, "close");
that.device.attributes.door = "closing";
} });
that.platform.addAttributeUsage("door", this.deviceid, thisCharacteristic);
thisCharacteristic = this.getaddService(Service.GarageDoorOpener).getCharacteristic(Characteristic.CurrentDoorState)
thisCharacteristic.on('get', function(callback) {
switch (that.device.attributes.door) {
case 'open':
callback(null, Characteristic.TargetDoorState.OPEN);
break;
case 'opening':
callback(null, Characteristic.TargetDoorState.OPENING);
break;
case 'closed':
callback(null, Characteristic.TargetDoorState.CLOSED);
break;
case 'closing':
callback(null, Characteristic.TargetDoorState.CLOSING);
break;
default:
callback(null, Characteristic.TargetDoorState.STOPPED);
break;
}
});
that.platform.addAttributeUsage("door", this.deviceid, thisCharacteristic);
this.getaddService(Service.GarageDoorOpener).setCharacteristic(Characteristic.ObstructionDetected, false);
}
if (device.capabilities["Lock"] !== undefined) {
this.deviceGroup = "locks";
thisCharacteristic = this.getaddService(Service.LockMechanism).getCharacteristic(Characteristic.LockCurrentState)
thisCharacteristic.on('get', function(callback) {
switch (that.device.attributes.lock) {
case 'locked':
callback(null, Characteristic.LockCurrentState.SECURED);
break;
case 'unlocked':
callback(null, Characteristic.LockCurrentState.UNSECURED);
break;
default:
callback(null, Characteristic.LockCurrentState.UNKNOWN);
break;
} });
that.platform.addAttributeUsage("lock", this.deviceid, thisCharacteristic);
thisCharacteristic = this.getaddService(Service.LockMechanism).getCharacteristic(Characteristic.LockTargetState)
thisCharacteristic.on('get', function(callback) {
switch (that.device.attributes.lock) {
case 'locked':
callback(null, Characteristic.LockCurrentState.SECURED);
break;
case 'unlocked':
callback(null, Characteristic.LockCurrentState.UNSECURED);
break;
default:
callback(null, Characteristic.LockCurrentState.UNKNOWN);
break;
} });
thisCharacteristic.on('set', function(value, callback) {
if (value === false) {
value = Characteristic.LockTargetState.UNSECURED;
} else if (value === true) {
value = Characteristic.LockTargetState.SECURED;
}
switch (value) {
case Characteristic.LockTargetState.SECURED:
that.platform.api.runCommand(callback, that.deviceid, "lock");
that.device.attributes.lock = "locked";
break;
case Characteristic.LockTargetState.UNSECURED:
that.platform.api.runCommand(callback, that.deviceid, "unlock");
that.device.attributes.lock = "unlocked";
break;
} });
that.platform.addAttributeUsage("lock", this.deviceid, thisCharacteristic);
}
// if (devices.capabilities["Valve"] !== undefined) {
// this.deviceGroup = "valve";
// Thinking of implementing this as a Door service.
// }
if (device.capabilities["Button"] !== undefined) {
this.deviceGroup = " button";
}
if (device.capabilities["Switch"] !== undefined && this.deviceGroup == "unknown") {
this.deviceGroup = "switch";
thisCharacteristic = this.getaddService(Service.Switch).getCharacteristic(Characteristic.On)
thisCharacteristic.on('get', function(callback) { callback(null, that.device.attributes.switch == "on"); })
thisCharacteristic.on('set', function(value, callback) {
if (value)
that.platform.api.runCommand(callback, that.deviceid, "on");
else
that.platform.api.runCommand(callback, that.deviceid, "off");
});
that.platform.addAttributeUsage("switch", this.deviceid, thisCharacteristic);
if (device.capabilities["SwitchLevel"] !== undefined) {
thisCharacteristic = this.getaddService(Service.Lightbulb).getCharacteristic(Characteristic.Brightness)
thisCharacteristic.on('get', function(callback) { callback(null, parseInt(that.device.attributes.level)); });
thisCharacteristic.on('set', function(value, callback) { that.platform.api.runCommand(callback, that.deviceid, "setLevel", { value1: value }); });
that.platform.addAttributeUsage("level", this.deviceid, thisCharacteristic);
}
}
if ((device.capabilities["SmokeDetector"] !== undefined) && (that.device.attributes.smoke)) {
this.deviceGroup = "detectors";
thisCharacteristic = this.getaddService(Service.SmokeSensor).getCharacteristic(Characteristic.SmokeDetected)
thisCharacteristic.on('get', function(callback) {
if (that.device.attributes.smoke == 'clear')
callback(null, Characteristic.SmokeDetected.SMOKE_NOT_DETECTED);
else
callback(null, Characteristic.SmokeDetected.SMOKE_DETECTED);
});
that.platform.addAttributeUsage("smoke", this.deviceid, thisCharacteristic);
}
if ((device.capabilities["CarbonMonoxideDetector"] !== undefined) && (that.device.attributes.carbonMonoxide)) {
this.deviceGroup = "detectors";
thisCharacteristic = this.getaddService(Service.CarbonMonoxideSensor).getCharacteristic(Characteristic.CarbonMonoxideDetected)
thisCharacteristic.on('get', function(callback) {
if (that.device.attributes.carbonMonoxide == 'clear')
callback(null, Characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL);
else
callback(null, Characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL);
});
that.platform.addAttributeUsage("carbonMonoxide", this.deviceid, thisCharacteristic);
}
if (device.capabilities["MotionSensor"] !== undefined) {
if (this.deviceGroup == 'unknown') this.deviceGroup = "sensor";
thisCharacteristic = this.getaddService(Service.MotionSensor).getCharacteristic(Characteristic.MotionDetected)
thisCharacteristic.on('get', function(callback) { callback(null, (that.device.attributes.motion == "active")); });
that.platform.addAttributeUsage("motion", this.deviceid, thisCharacteristic);
}
if (device.capabilities["WaterSensor"] !== undefined) {
if (this.deviceGroup == 'unknown') this.deviceGroup = "sensor";
thisCharacteristic = this.getaddService(Service.LeakSensor).getCharacteristic(Characteristic.LeakDetected)
thisCharacteristic.on('get', function(callback) {
var reply = Characteristic.LeakDetected.LEAK_DETECTED;
if (that.device.attributes.water == "dry") reply = Characteristic.LeakDetected.LEAK_NOT_DETECTED;
callback(null, reply); });
that.platform.addAttributeUsage("water", this.deviceid, thisCharacteristic);
}
if (device.capabilities["PresenceSensor"] !== undefined) {
if (this.deviceGroup == 'unknown') this.deviceGroup = "sensor";
thisCharacteristic = this.getaddService(Service.OccupancySensor).getCharacteristic(Characteristic.OccupancyDetected)
thisCharacteristic.on('get', function(callback) { callback(null, (that.device.attributes.presence == "present")); });
that.platform.addAttributeUsage("presence", this.deviceid, thisCharacteristic);
}
if (device.capabilities["RelativeHumidityMeasurement"] !== undefined) {
if (this.deviceGroup == 'unknown') this.deviceGroup = "sensor";
thisCharacteristic = this.getaddService(Service.HumiditySensor).getCharacteristic(Characteristic.CurrentRelativeHumidity)
thisCharacteristic.on('get', function(callback) { callback(null, Math.round(that.device.attributes.humidity)); });
that.platform.addAttributeUsage("humidity", this.deviceid, thisCharacteristic);
}
if (device.capabilities["TemperatureMeasurement"] !== undefined) {
if (this.deviceGroup == 'unknown') this.deviceGroup = "sensor";
thisCharacteristic = this.getaddService(Service.TemperatureSensor).getCharacteristic(Characteristic.CurrentTemperature)
thisCharacteristic.on('get', function(callback) {
if (that.platform.temperature_unit == 'C')
callback(null, Math.round(that.device.attributes.temperature*10)/10);
else
callback(null, Math.round(((that.device.attributes.temperature - 32) / 1.8)*10)/10);
});
that.platform.addAttributeUsage("temperature", this.deviceid, thisCharacteristic);
}
if (device.capabilities["ContactSensor"] !== undefined && device.capabilities["GarageDoorControl"] === undefined) {
if (this.deviceGroup == 'unknown') this.deviceGroup = "sensor";
thisCharacteristic = this.getaddService(Service.ContactSensor).getCharacteristic(Characteristic.ContactSensorState)
thisCharacteristic.on('get', function(callback) {
if (that.device.attributes.contact == "closed")
callback(null, Characteristic.ContactSensorState.CONTACT_DETECTED);
else
callback(null, Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);
});
that.platform.addAttributeUsage("contact", this.deviceid, thisCharacteristic);
}
if (device.capabilities["Battery"] !== undefined) {
thisCharacteristic = this.getaddService(Service.BatteryService).getCharacteristic(Characteristic.BatteryLevel)
thisCharacteristic.on('get', function(callback) { callback(null, Math.round(that.device.attributes.battery)); });
that.platform.addAttributeUsage("battery", this.deviceid, thisCharacteristic);
thisCharacteristic = this.getaddService(Service.BatteryService).getCharacteristic(Characteristic.StatusLowBattery)
thisCharacteristic.on('get', function(callback) {
if (that.device.attributes.battery < 0.20)
callback(null, Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW);
else
callback(null, Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL);
});
this.getaddService(Service.BatteryService).setCharacteristic(Characteristic.ChargingState, Characteristic.ChargingState.NOT_CHARGING);
that.platform.addAttributeUsage("battery", this.deviceid, thisCharacteristic);
}
if (device.capabilities["EnergyMeter"] !== undefined) {
this.deviceGroup = 'EnergyMeter';
thisCharacteristic = this.getaddService(Service.Outlet).addCharacteristic(EnergyCharacteristics.TotalConsumption1)
thisCharacteristic.on('get', function(callback) { callback(null, Math.round(that.device.attributes.energy)); });
that.platform.addAttributeUsage("energy", this.deviceid, thisCharacteristic);
}
if (device.capabilities["PowerMeter"] !== undefined) {
thisCharacteristic = this.getaddService(Service.Outlet).addCharacteristic(EnergyCharacteristics.CurrentConsumption1)
thisCharacteristic.on('get', function(callback) { callback(null, Math.round(that.device.attributes.power)); });
that.platform.addAttributeUsage("power", this.deviceid, thisCharacteristic);
}
if (device.capabilities["AccelerationSensor"] !== undefined) {
if (this.deviceGroup == 'unknown') this.deviceGroup = "sensor";
}
if (device.capabilities["ThreeAxis"] !== undefined) {
if (this.deviceGroup == 'unknown') this.deviceGroup = "sensor";
}
if (device.capabilities["Thermostat"] !== undefined) {
this.deviceGroup = "thermostats";
thisCharacteristic = this.getaddService(Service.Thermostat).getCharacteristic(Characteristic.CurrentHeatingCoolingState)
thisCharacteristic.on('get', function(callback) {
switch (that.device.attributes.thermostatOperatingState) {
case "pending cool":
case "cooling":
callback(null, Characteristic.CurrentHeatingCoolingState.COOL);
break;
case "pending heat":
case "heating":
callback(null, Characteristic.CurrentHeatingCoolingState.HEAT);
break;
default: //The above list should be inclusive, but we need to return something if they change stuff.
//TODO: Double check if Smartthings can send "auto" as operatingstate. I don't think it can.
callback(null, Characteristic.CurrentHeatingCoolingState.OFF);
break;
}
});
that.platform.addAttributeUsage("thermostatOperatingState", this.deviceid, thisCharacteristic);
//Handle the Target State
thisCharacteristic = this.getaddService(Service.Thermostat).getCharacteristic(Characteristic.TargetHeatingCoolingState)
thisCharacteristic.on('get', function(callback) {
switch (that.device.attributes.thermostatMode) {
case "cool":
callback(null, Characteristic.TargetHeatingCoolingState.COOL);
break;
case "emergency heat":
case "heat":
callback(null, Characteristic.TargetHeatingCoolingState.HEAT);
break;
case "auto":
callback(null, Characteristic.TargetHeatingCoolingState.AUTO);
break;
default: //The above list should be inclusive, but we need to return something if they change stuff.
callback(null, Characteristic.TargetHeatingCoolingState.OFF);
break;
}
})
thisCharacteristic.on('set', function(value, callback) {
switch (value) {
case Characteristic.TargetHeatingCoolingState.COOL:
that.platform.api.runCommand(callback, that.deviceid, "cool");
that.device.attributes.thermostatMode = 'cool';
break;
case Characteristic.TargetHeatingCoolingState.HEAT:
that.platform.api.runCommand(callback, that.deviceid, "heat");
that.device.attributes.thermostatMode = 'heat';
break;
case Characteristic.TargetHeatingCoolingState.AUTO:
that.platform.api.runCommand(callback, that.deviceid, "auto");
that.device.attributes.thermostatMode = 'auto';
break;
case Characteristic.TargetHeatingCoolingState.OFF:
that.platform.api.runCommand(callback, that.deviceid, "off");
that.device.attributes.thermostatMode = 'off';
break;
}
});
that.platform.addAttributeUsage("thermostatMode", this.deviceid, thisCharacteristic);
if (device.capabilities["RelativeHumidityMeasurement"] !== undefined) {
thisCharacteristic = this.getaddService(Service.Thermostat).getCharacteristic(Characteristic.CurrentRelativeHumidity)
thisCharacteristic.on('get', function(callback) {
callback(null, parseInt(that.device.attributes.humidity));
});
that.platform.addAttributeUsage("humidity", this.deviceid, thisCharacteristic);
}
thisCharacteristic = this.getaddService(Service.Thermostat).getCharacteristic(Characteristic.CurrentTemperature)
thisCharacteristic.on('get', function(callback) {
if (that.platform.temperature_unit == 'C')
callback(null, Math.round(that.device.attributes.temperature*10)/10);
else
callback(null, Math.round(((that.device.attributes.temperature - 32) / 1.8)*10)/10);
});
that.platform.addAttributeUsage("temperature", this.deviceid, thisCharacteristic);
thisCharacteristic = this.getaddService(Service.Thermostat).getCharacteristic(Characteristic.TargetTemperature)
thisCharacteristic.on('get', function(callback) {
var temp = undefined;
switch (that.device.attributes.thermostatMode) {
case "cool":
temp = that.device.attributes.coolingSetpoint;
break;
case "emergency heat":
case "heat":
temp = that.device.attributes.heatingSetpoint;
break;
default: //This should only refer to auto
// Choose closest target as single target
var high = that.device.attributes.coolingSetpoint;
var low = that.device.attributes.heatingSetpoint;
var cur = that.device.attributes.temperature;
temp = Math.abs(high - cur) < Math.abs(cur - low) ? high : low;
break;
}
if (!temp)
callback('Unknown');
else if (that.platform.temperature_unit == 'C')
callback(null, Math.round(temp*10)/10);
else
callback(null, Math.round(((temp - 32) / 1.8)*10)/10);
})
thisCharacteristic.on('set', function(value, callback) {
//Convert the Celsius value to the appropriate unit for Smartthings
var temp = value;
if (that.platform.temperature_unit == 'C')
temp = value;
else
temp = ((value * 1.8) + 32);
//Set the appropriate temperature unit based on the mode
switch (that.device.attributes.thermostatMode) {
case "cool":
that.platform.api.runCommand(callback, that.deviceid, "setCoolingSetpoint", {
value1: temp
});
that.device.attributes.coolingSetpoint = temp;
break;
case "emergency heat":
case "heat":
that.platform.api.runCommand(callback, that.deviceid, "setHeatingSetpoint", {
value1: temp
});
that.device.attributes.heatingSetpoint = temp;
break;
default: //This should only refer to auto
// Choose closest target as single target
var high = that.device.attributes.coolingSetpoint;
var low = that.device.attributes.heatingSetpoint;
var cur = that.device.attributes.temperature;
var isHighTemp = Math.abs(high - cur) < Math.abs(cur - low);
if (isHighTemp) {
that.platform.api.runCommand(callback, that.deviceid, "setCoolingSetpoint", { value1: temp });
} else {
that.platform.api.runCommand(null, that.deviceid, "setHeatingSetpoint", { value1: temp });
}
break;
}
});
that.platform.addAttributeUsage("thermostatMode", this.deviceid, thisCharacteristic);
that.platform.addAttributeUsage("coolingSetpoint", this.deviceid, thisCharacteristic);
that.platform.addAttributeUsage("heatingSetpoint", this.deviceid, thisCharacteristic);
that.platform.addAttributeUsage("temperature", this.deviceid, thisCharacteristic);
thisCharacteristic = this.getaddService(Service.Thermostat).getCharacteristic(Characteristic.TemperatureDisplayUnits)
thisCharacteristic.on('get', function(callback) {
if (platform.temperature_unit == "C")
callback(null, Characteristic.TemperatureDisplayUnits.CELSIUS);
else
callback(null, Characteristic.TemperatureDisplayUnits.FAHRENHEIT);
});
//that.platform.addAttributeUsage("temperature_unit", "platform", thisCharacteristic);
thisCharacteristic = this.getaddService(Service.Thermostat).getCharacteristic(Characteristic.HeatingThresholdTemperature)
thisCharacteristic.on('get', function(callback) {
if (that.platform.temperature_unit == 'C')
callback(null, Math.round(that.device.attributes.heatingSetpoint*10)/10);
else
callback(null, Math.round(((that.device.attributes.heatingSetpoint - 32) / 1.8)*10)/10);
})
thisCharacteristic.on('set', function(value, callback) {
//Convert the Celsius value to the appropriate unit for Smartthings
var temp = value;
if (that.platform.temperature_unit == 'C')
temp = value;
else
temp = ((value * 1.8) + 32);
that.platform.api.runCommand(callback, that.deviceid, "setHeatingSetpoint", {
value1: temp
});
that.device.attributes.heatingSetpoint = temp;
});
that.platform.addAttributeUsage("heatingSetpoint", this.deviceid, thisCharacteristic);
thisCharacteristic = this.getaddService(Service.Thermostat).getCharacteristic(Characteristic.CoolingThresholdTemperature)
thisCharacteristic.on('get', function(callback) {
if (that.platform.temperature_unit == 'C')
callback(null, Math.round((that.device.attributes.coolingSetpoint*10))/10);
else
callback(null, Math.round(((that.device.attributes.coolingSetpoint - 32) / 1.8)*10)/10);
});
thisCharacteristic.on('set', function(value, callback) {
//Convert the Celsius value to the appropriate unit for Smartthings
var temp = value;
if (that.platform.temperature_unit == 'C')
temp = value;
else
temp = ((value * 1.8) + 32);
that.platform.api.runCommand(callback, that.deviceid, "setCoolingSetpoint", {
value1: temp
});
that.device.attributes.coolingSetpoint = temp;
});
that.platform.addAttributeUsage("coolingSetpoint", this.deviceid, thisCharacteristic);
}
this.loadData(device, this);
}
function loadData(data, myObject) {
var that = this;
if (myObject !== undefined) that = myObject;
if (data !== undefined) {
this.device = data;
for (var i = 0; i < that.services.length; i++) {
for (var j = 0; j < that.services[i].characteristics.length; j++) {
that.services[i].characteristics[j].getValue();
}
}
} else {
this.log.debug("Fetching Device Data")
this.platform.api.getDevice(this.deviceid, function(data) {
if (data === undefined) return;
this.device = data;
for (var i = 0; i < that.services.length; i++) {
for (var j = 0; j < that.services[i].characteristics.length; j++) {
that.services[i].characteristics[j].getValue();
}
}
});
}
}
function getServices() {
return this.services;
}