Got LAN DeviceHandler; What Services SmartApp needed?

I’m trying to put together a simple device handler and smartapp for a garage controller. I was able to put together a very simple DeviceHandler that working well with my hardware. According to online doc, the reason a device needs smartapp is “SmartApps are the connecting glue between the unique protocols of such LAN or cloud devices and a Device Handler you would create for such devices. These Service Manager SmartApps discover LAN or cloud devices and then continue to maintain their connection.” So sound like I need to create this Service Manager SmartApp. Is there a pair example of SmartApp and its counter part DeviceHandler so I can see and relate? Here’s my simple Device Handler, How do I develop a simple SmartApp that would work with this DeviceHandler? TIA!

/**

  • Generic HTTP Device v1.0.20160402
  • Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except
  • in compliance with the License. You may obtain a copy of the License at:
  •  http://www.apache.org/licenses/LICENSE-2.0
    
  • Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
  • on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
  • for the specific language governing permissions and limitations under the License.

*/

import groovy.json.JsonSlurper

metadata {
definition (name: “fiController”, author: “bhoang”, namespace:“bhoang”) {
capability “GarageDoorControl”

    //Custom command to select which door
    command "door"
    
    attribute "doorSelect", "number"
    attribute "d0Vld", "number"
    attribute "d1Vld", "number"
    attribute "d2Vld", "number"
    
}

preferences {
	input("DeviceIP", "string", title:"Device IP Address", description: "Please enter your device's IP Address", required: true, displayDuringSetup: true)
	input("linkCode", "string", title:"LinkCode", description: "Please enter Senclo's Link Code", required: true, displayDuringSetup: true)
	input("userName", "string", title:"User Name:", description: "Enter your Senclo's user name", required: true, displayDuringSetup: true)
	input("userPassword", "string", title:"User Password", description: "Enter your Senclo's user password", required: true, displayDuringSetup: true)
	input("doorID", "string", title:"Door Number", description: "Enter door number to operate", required: true, displayDuringSetup: true)
}

simulator {
}

tiles(scale: 2) {
	// Multi Tile:
	//multiAttributeTile(name:"DeviceTrigger", type: "generic", width: 6, height: 6, canChangeIcon: true) {
	multiAttributeTile(name:"fiSelect", type: "generic", width: 3, height: 3, canChangeIcon: false) {
    	tileAttribute("doorSelect", key: "PRIMARY_CONTROL", decoration: "flat", width: 3, height: 3) {
          attributeState "1", label: '${currentValue}', action: "door", backgroundColor:"79B821"
	    }
        
        tileAttribute("temperature", key: "SECONDARY_CONTROL") {
            //attributeState "temperature", label:'Temperature: ${tempValue}°C'
            attributeState "temperature", label:'Temperature: 70°C'
        }
	}
    
	standardTile("closeButton", "device.closeButton", decoration: "flat", width: 3, height:3) {
		state "closeDoor", label:'CLOSE' , action: "close", icon: "st.Transportation.transportation14", backgroundColor:"#FF2255", nextState: "closingDoor" 
		state "closingDoor", label:'CLOSING' , icon: "st.Transportation.transportation14", backgroundColor:"#22FF55" 
	}
    
	standardTile("openButton", "device.openButton", decoration: "flat", width:3, height:3) {
		state "openDoor", label:'OPEN' , action: "open", icon: "st.Transportation.transportation12", backgroundColor:"#33CCFF", nextState: "openingDoor"
		state "openingDoor", label:'OPENING' , icon: "st.Transportation.transportation14", backgroundColor:"#CC33FF"
	}

    valueTile("d0Vld", "device.d0Vld", decoration: "flat") {
        state "defaul", value: '${currentValue}'
    }
    valueTile("d1Vld", "device.d1Vld", decoration: "flat") {
        state "default", value: '${currentValue}'
    }
    valueTile("d2Vld", "device.d2Vld", decoration: "flat") {
        state "default", value: '${currentValue}'
    }
	main (["fiSelect", "temperature"])
	details(["fiSelect", "openButton", "closeButton"])
}

} //end of metadata

def door() {
if(device.currentValue(‘doorSelect’)==1) {
if(device.currentValue(‘d1Vld’)==1) {
sendEvent(name: “doorSelect”, value: “2”, isStateChange: true)
log.debug “doorSelect=” + device.currentValue(‘doorSelect’)
} else if(device.currentValue(‘d2Vld’)==1) {
sendEvent(name: “doorSelect”, value: “2”, isStateChange: true)
log.debug “doorSelect=” + device.currentValue(‘doorSelect’)
} else {
sendEvent(name: “doorSelect”, value: “1”, isStateChange: true)
log.debug “doorSelect=” + device.currentValue(‘doorSelect’)
}
}else if(device.currentValue(‘doorSelect’)==2) {
if(device.currentValue(‘d2Vld’)==1) {
sendEvent(name: “doorSelect”, value: “3”, isStateChange: true)
log.debug “doorSelect=” + device.currentValue(‘doorSelect’)
} else if(device.currentValue(‘d0Vld’)==1) {
sendEvent(name: “doorSelect”, value: “1”, isStateChange: true)
log.debug “doorSelect=” + device.currentValue(‘doorSelect’)
} else {
sendEvent(name: “doorSelect”, value: “2”, isStateChange: true)
log.debug “doorSelect=” + device.currentValue(‘doorSelect’)
}
} else if(device.currentValue(‘doorSelect’)==3) {
if(device.currentValue(‘d0Vld’)==1) {
sendEvent(name: “doorSelect”, value: “1”, isStateChange: true)
log.debug “doorSelect=” + device.currentValue(‘doorSelect’)
} else if(device.currentValue(‘d1Vld’)==1) {
sendEvent(name: “doorSelect”, value: “2”, isStateChange: true)
log.debug “doorSelect=” + device.currentValue(‘doorSelect’)
} else {
sendEvent(name: “doorSelect”, value: “3”, isStateChange: true)
log.debug “doorSelect=” + device.currentValue(‘doorSelect’)
}
}

}

def open() {
runCmd(“Open”)
}
def close() {
runCmd(“Close”)
}

def runCmd(String varCommand) {
def host = DeviceIP
def hosthex = convertIPtoHex(host).toUpperCase()
def porthex = convertPortToHex(80).toUpperCase()
device.deviceNetworkId = “$hosthex:$porthex”

log.debug "The device id configured is: $device.deviceNetworkId"

log.debug "state.DSel=" + state.DSel

def dsn = 0
if (device.currentValue('doorSelect') >= 1) 
  dsn = device.currentValue('doorSelect') - 1
else 
  dsn = 0

def path = "/ST?" + "LC=" + linkCode + "&UN=" + userName + "&UP=" + userPassword + "&ID=" + dsn + "&CMD=" + varCommand
//log.debug "path is: $path"
//log.debug "Uses which method: GET"
def body = DeviceBodyText
//log.debug "body is: $body"

def headers = [:]
//ip:port
headers.put("HOST", "$host:80")
//headers.put("Content-Type", "application/x-www-form-urlencoded")
headers.put("Content-Type", "application/json")
//log.debug "The Header is $headers"
def method = "GET"

try {
	def hubAction = new physicalgraph.device.HubAction(
		method: method,
		path: path,
		body: body,
		headers: headers
		)
	hubAction.options = [outputMsgToS3:false]
	log.debug hubAction
	hubAction
}
catch (Exception e) {
	log.debug "Hit Exception $e on $hubAction"
}

}

def parse(String description) {
log.debug “Parsing ‘${description}’”

def msg = parseLanMessage(description)
def headersAsString = msg.header // => headers as a string
def headerMap = msg.headers      // => headers as a Map
def body = msg.body              // => request body as a string
def status = msg.status          // => http status code of the response
def json = msg.json              // => any JSON included in response body, as a data structure of lists and maps
def xml = msg.xml                // => any XML included in response body, as a document tree structure
def data = msg.data              // => either JSON or XML in response body (whichever is specified by content-type header in response)


sendEvent(name: "d0Vld", value: json.Door0Vld, isStateChange: true)
sendEvent(name: "d1Vld", value: json.Door1Vld, isStateChange: true)
sendEvent(name: "d2Vld", value: json.Door2Vld, isStateChange: true)

log.debug "d0Vld: " + device.currentValue('d0Vld')
log.debug "d1Vld: " + device.currentValue('d1Vld')
log.debug "d2Vld: " + device.currentValue('d2Vld')

if(status == 200)
{
  if (device.currentValue('closeButton') == "closingDoor") {
     sendEvent(name: "door", value: "closeDoor", isStateChange: true)
  } else  if (device.currentValue('openButton') == "openingDoor") {
     sendEvent(name: "door", value: "openDoor", isStateChange: true)
  }
} else {
  //There's something wrong still return to the same state
  if (device.currentValue('closeButton') == "closingDoor") {
     sendEvent(name: "door", value: "closeDoor", isStateChange: true)
  } else  if (device.currentValue('openButton') == "openingDoor") {
     sendEvent(name: "door", value: "openDoor", isStateChange: true)
  }
}

}

def installed() {
log.trace "installed()"
log.info "==================== Device Handler Installed ==================="
state.installedAt = now()
sendEvent(name: “doorSelect”, value: “1”, displayed: false)
sendEvent(name: “d0Vld”, value: “1”, displayed: false)
sendEvent(name: “d1Vld”, value: “0”, displayed: false)
sendEvent(name: “d2Vld”, value: “0”, displayed: false)
sendEvent(name: “d2Vld”, value: “0”, displayed: false)
sendEvent(name: “closeButton”, value: “closeDoor”, displayed: false)
sendEvent(name: “openButton”, value: “openDoor”, displayed: false)
}

private String convertIPtoHex(ipAddress) {
String hex = ipAddress.tokenize( ‘.’ ).collect { String.format( ‘%02x’, it.toInteger() ) }.join()
//log.debug “IP address entered is $ipAddress and the converted hex code is $hex"
return hex
}
private String convertPortToHex(port) {
String hexport = port.toString().format( ‘%04x’, port.toInteger() )
//log.debug hexport
return hexport
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
//log.debug(“Convert hex to ip: $hex”)
[convertHexToInt(hex[0…1]),convertHexToInt(hex[2…3]),convertHexToInt(hex[4…5]),convertHexToInt(hex[6…7])].join(”.")
}
private getHostAddress() {
def parts = device.deviceNetworkId.split(":")
//log.debug device.deviceNetworkId
def ip = convertHexToIP(parts[0])
def port = convertHexToInt(parts[1])
return ip + “:” + port
}

I presume you ref: http://docs.smartthings.com/en/latest/cloud-and-lan-connected-device-types-developers-guide/index.html and specifically: http://docs.smartthings.com/en/latest/cloud-and-lan-connected-device-types-developers-guide/building-lan-connected-device-types/division-of-labor.html#service-manager-responsibilities

The DTH and service SmartApp for Philips Hue Bridge is a good (though complex) example that comes to mind. Search the SmartThings Community GitHub.

I’ll try to think about another.


As a side note:

You should avoid Custom Commands in a DTH. In your case it’s relatively easy. Your LAN Service Manager SmartApp should spawn 3 child devices (1 standard Garage Door Capability for each door), just like Hue spawns a device instance for each discovered lightbulb.

Dang… Hue Bridge is really much too complicated an example and now SmartThings has changed these to use the new “SUPER LAN CONNECT” feature; which I don’t think we have access to use!