JSON Integration Question

I just got my Hubitat a week or so ago and I'm loving this thing! I moved my z-wave and zigbee devices from wink and got my google homes working as well. There was a bit of a learning curve but everything "works"..

Now I'm looking to tie in some sensors from my home security system. They are proprietary to the system and I doubt I can join them directly.

I can get their status in JSON by making a web request. The Json looks like this:

 "sensors": [
        {
            "name": "Front Door",
            "text": "OK",
            "code": "C",
            "id": 1,
            "date": "2019-12-01T09:28:28"
        },
        {
            "name": "Back Door",
            "text": "OK",
            "code": "C",
            "id": 2,
            "date": "2019-12-01T11:07:30"
        },
.... etc...
]

The code is either C for Closed or O for Open. The date is the last time it was activated.

I would like to be able to display them on a dashboard and trigger events, but I'm not sure where to start...

Thoughts/ideas/direction would be greatly appreciated!!

I have two drivers that rely on JSON requests that you might be able to get some basics from. The simpler of the two is my Neptune Systems Apex driver:
https://www.drdsnell.com/projects/hubitat/drivers/NeptuneSystemsApex.groovy

Here is the project page in the forum. It originally was for XML but was recently switched to JSON.
https://community.hubitat.com/t/project-driver-for-neptune-systems-apex/

It polls the Neptune on a regular basis (refresh) in a couple steps then passes the data through the ParseApex.

To make your life easier I would make your driver only handle one of them at a time and just make two devices, one for each door. Make it configurable as to what device name it will actually be polling. That way you can easily send events for that particular feature and treat them as if they were contact sensors (or other sensors depending on what is in that list).

Let me know if you want some more specific help. This type of stuff is just what I have been doing with my drivers although none of the JSON stuff I have done is as time-critical as a security system could be.

Thanks for the reply! I didn't have time this week to dig in to much, but i'm looking now.

I was trying to avoid making a ton of calls to their api and was hoping to just call out once then read the result for my doors. But I like your point about making each one its own device, I think that will simplify a lot of what I'm struggling with..

Do I have any options for saving this state in hubitat anywhere? Ie if one device makes the call and can save the data then the other doors just look at the already saved data if it's only been a few seconds? Maybe I'm making it too complex..

Thanks for helping!

Hi @xiopod,

I have also been writing a few integrations myself for temperature and humidity sensors, including SensorPush which has a cloud hosted data storage exposed through an API. The API returns JSON in a very similar structure. If I am reading @snell 's suggestion right, I think it is something similar to what I have implemented, but would be interested to know if it is not.

I wrote a single device driver that makes one API call to get all the sensor data in one go, executed as part of the device refresh method. Using the JSON returned, the driver then dynamically creates (if required) and updates child devices based on the sensors returned, Virtual temperature and humidity devices in my case. You can update attributes on a device, including another device than the one the driver is for, by calling the sendEvent method.

Does that make sense? If you want some examples I can provide some code snippets.

Simon

1 Like

I have to agree that @sburke781's method sounds like a good one, with child devices. I just have not gotten into those yet myself.

1 Like

I'd be very interested in examples! Child devices sound like the path I want. Thanks!

1 Like

Take a look at Hubduio. That is all based on the Parent/Child device relationship.

1 Like

I did base my code on a community post, so there is a good chance @Ryan780 's link is the one, I'll try and confirm this if I can.

Some snippets from what I ended up doing are below:

This first one shows the HTTP Post being called and iterating through the results, temperature and humidity samples in my case. I call methods to check if a child device exists and if one does not, I create one (I've included those code snippets further down in this post). I then call the sendEvent method to update the attribute on the child device.

httpPost(samplesPostParams)
            { samples ->
                samples?.data?.sensors?.each { it2 ->
                    
                    def tempStr = (String)(it2.value.temperature)
                    tempStr = tempStr.replace("[","").replace("]","")
                    def temperature = (Double.parseDouble(tempStr) - 32) * 5 / 9
                    
                    def childTempDevice = findChildDevice(it.value.id, "Temperature")

                    if (childTempDevice == null) {
                        createSensor(it.value.id, it.value.name, "Temperature")
                        childTempDevice = findChildDevice(it.value.id, "Temperature")
                    }
                    
                    if (childTempDevice == null) {
                        log.debug("Lookup of newly created sensor failed... ${it.value.id}, ${it.value.name}, Temperature")                         
                    }
                    else {
                        childTempDevice.sendEvent(name: "temperature", value: String.format("%.2f",temperature))
                    }

....

The methods below are used in the previous code snippet to find and create the child devices. Notice the creation of the Netowrk DNI for the child devices incorporating the parent network id and adding extra detail for the child.

def deriveSensorDNI(sensorId, sensorType) {

return "${device.deviceNetworkId}-id${sensorId}-type${sensorType}"
}

def findChildDevice(sensorId, sensorType) {
	getChildDevices()?.find { it.deviceNetworkId == deriveSensorDNI(sensorId, sensorType)}
}

def createSensor(sensorId, sensorName, sensorType) {
    log.debug("createSensor: Creating SensorPush Sensor: ${sensorId}, ${sensorName}, ${sensorType}")

def childDevice = findChildDevice(sensorId, sensorType)

if (childDevice == null) {
    childDevice = addChildDevice("hubitat", "Virtual ${sensorType} Sensor", deriveSensorDNI(sensorId, sensorType), [label: "${device.displayName} (${sensorType} Sensor - ${sensorName})", isComponent: false])
}
else {
  log.debug("createSensor: child device ${childDevice.deviceNetworkId} already exists")
}
	
}

I think the post I referred to when I was doing my development was composite-devices-parent-child-devices, which appears to be when @ogiewon was also starting to use Parent-Child devices for Hubduio. I have benefited from @ogiewon 's code as I've been learning, so can highly recommend getting in touch with him.

Simon

Sorry for the delay between posts, but I wanted to follow up incase someone is trying to do the same thing. Here is the device I ended up creating:


metadata {
    definition(name: "Door Sensors", namespace: "Home Sensors", author: "Jason") {
        capability "Switch"
        capability "Sensor"
	    capability "Refresh"
        attribute "panelStatus", "string"
        command "stopSchedule"
        command "runSchedule"
    }
}

preferences {
    section("Settings") {
        input "endpointUrl", "text", title: "Endpoint URL", required: true
        input "pullingSeconds", "number", title: "Number of seconds", required: true
        input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
    }
}

def initialize() {
    log.debug 'Initializing'
    stopSchedule()        
}

def stopSchedule(){
    unschedule()    
}
def runSchedule(){
    log.info "Scheduling Refresh evey ${pullingSeconds} seconds"
    schedule("0/${pullingSeconds} * * * * ? *", refresh)
}

def logsOff() {
    log.warn "debug logging disabled..."
    device.updateSetting("logEnable", [value: "false", type: "bool"])
}

def updated() {
    log.info "updated..."
    log.warn "debug logging is: ${logEnable == true}"
    if (logEnable) runIn(1800, logsOff)    
}

def getParams(){
	def params = [uri: "${endpointUrl}",requestContentType: "application/json",timeout: 20]
}

def refresh() {
    if(state.Refreshing) {
        log.warn "Already Refreshing"	
        return
    }
    state.Refreshing = true
    if (logEnable) log.info "Refreshing..."	
    asynchttpGet('getPanelStatusCallback', getParams());
	
}

def getPanelStatusCallback(resp, data) {  
    
    try {	        
        def json = new groovy.json.JsonSlurper().parseText(resp.data)        
        sendEvent(name: 'panelStatus', value: json.panelDescription, data: json)
        handleSensorValues(json.sensors) 
        sendEvent(name: 'ERROR', value: null)
    } 
	catch(Exception e) {
		log.warn "error occured: ${e}"
        sendEvent(name: 'ERROR', value: "error occured calling security panel api", data: e)
	}
    if (logEnable) log.info "Refresh complete"
    state.Refreshing = false
}

def handleSensorValues(sensors) {

    if(!state.Sensors) { state.Sensors = [:]}
    
    SensorCodes = [O: "Open", C: "Closed", OW: "Open", CW: "Closed"]    
    sensors.each { sensor ->         
        prevCode = state.Sensors[sensor.name]        
        newCode = SensorCodes.get(sensor.code,sensor.code)
        if(prevCode && prevCode.code != sensor.code) {     
            oldCode = SensorCodes.get(prevCode.code,prevCode.code)            
            log.info "${sensor.name} is now ${newCode}. It was: ${oldCode}. ${sensor.text.trim()}"
            //sendEvent(name: "sensor.${sensor.name}", value: newCode, data: sensor)
            setChildDevice(sensor.name, newCode)    
        } 
        
        state.Sensors << [(sensor.name): sensor]
    }
}


def setChildDevice(name, contactCode){
    def childDevice = getChildDevices()?.find { it.deviceNetworkId.endsWith(name)}
    
    if (!childDevice) {
        try {
            addChildDevice("hubitat", "Virtual Contact Sensor", "${device.deviceNetworkId}-Sensor${name}", [name: "${device.displayName} ${name} Sensor", isComponent: true])
            childDevice = getChildDevices()?.find { it.deviceNetworkId.endsWith(name)}
        } catch (e) {
            log.error "Failed to add child device: ${name}"            
        }
    }
    if (childDevice) {
        try {                
            childDevice.sendEvent(name: "contact", value: contactCode)
            //log.debug "Senser ${name} is ${contactCode}"
        } catch (e) {
            log.error "Couldn't find child device, probably doesn't exist...? Error: ${e}"
        }
    }
}
    
private removeChildDevices() {
    if (logEnable) log.debug "Removing Child Devices"
    try {
        getChildDevices()?.each {
        	try {
            	deleteChildDevice(it.deviceNetworkId)
            } catch (e) {
                log.debug "Error deleting ${it.deviceNetworkId}, probably locked into a SmartApp: ${e}"
            }
        }
    } catch (err) {
        log.debug "Either no children exist or error finding child devices for some reason: ${err}"
    }
}

If you see anything that could be better let me know. Thanks for the help and advise!

1 Like