Possible Route to Home Kit?

[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;
}
1 Like