Control4 integration, REST call not working properly

I am building an integration between Control4 and Smartthings as my house was primarily automated using C4 but I like several Smartthings features.

I updated the web2way C4 driver which allows me to query C4 controller and discover all switches and dimmers (for now… maybe I will expand it in the future). I also decided to use DHs to handle all the work instead of SmartApps for no other reason than I thought it was easier.

I did some previous tests by creating a dedicated DH for one of my dimmers and it worked like a charm. It’s basically a HubAction call and as long as you have the device network ID set to the hexadecimal value of the C4 Controller IP and the hub selected, the device will work like a charm.

Now the odd behaviour… I created a parent DH called C4 Controller which queries the actual controller and after parsing the results creates one child DH for each C4 switch and dimmer. This proved I was capable of sending messages and receiving them from the controller. I can see the messages returning from the controller in the live logging view.

However, every time I turn on or off a switch using a device created from a child DH, the message is sent but nothing happens. I can’t see any messages returning. I am not a Groovy expert so not sure if parent/child relationship has anything to do with this behaviour.

Please forgive the copy and paste but I don’t know how to make it look pretty.

Below a copy of the messages with a successfully returned message in bold followed by two request (on and off) without a response back.

12:22:57 AM: debug GET /?command=set&proxyID=208&variableID=1000&newValue=0 HTTP/1.1 
Accept: */* 
User-Agent: Linux UPnP/1.0 SmartThings 
HOST: 10.0.0.50:9000 

12:22:57 AM: debug childOff(208)
12:22:55 AM: debug GET /?command=set&proxyID=208&variableID=1000&newValue=1 HTTP/1.1 
Accept: */* 
User-Agent: Linux UPnP/1.0 SmartThings 
HOST: 10.0.0.50:9000 

12:22:55 AM: debug childOn(208)
**12:22:46 AM: debug {"1000":"1"}**
12:22:45 AM: debug GET /?command=get&proxyID=201&variableID=1000 HTTP/1.1 
Accept: */* 
User-Agent: Linux UPnP/1.0 SmartThings 
HOST: 10.0.0.50:9000 

12:22:45 AM: debug Executing 'refresh()'

The code I am using follows.

C4 Controller DH code:

import groovy.json.JsonSlurper

metadata {
	definition (name: "C4 Controller", namespace: "lsilva171", author: "Luis Carlos Silva") {
		capability "Configuration"
		capability "Refresh"
		capability "Polling"
	}

	preferences {
		input("ControllerIP", "string", title:"C4 Controller IP Address", description: "Enter C4 controller's IP Address", required: true, displayDuringSetup: true)
		input("DriverPort", "string", title:"C4 Driver Port", description: "Enter web2Way C4 driver's port", required: true, displayDuringSetup: true)
	}
}

simulator {
}

tiles (scale:2) {
	standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
		state "default", label:'Refresh', action: "refresh.refresh", icon: "st.secondary.refresh-icon"
	}
	standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
		state "configure", label:'Configure', action:"configuration.configure", icon:"st.secondary.tools"
	}
	childDeviceTiles("all")
}

def installed() {
}

def updated() {
}

def initialize() {
}

def configure() {
	log.debug "Executing 'configure()'"
    def hosthex = convertIPtoHex(ControllerIP).toUpperCase()
    def porthex = convertPortToHex(DriverPort).toUpperCase()
    device.deviceNetworkId = "$hosthex:$porthex" 
	log.debug "The C4 Controller DNI configured is $device.deviceNetworkId"
	sendMsg("GET", "?command=getitems")
}

def refresh() {
	log.debug "Executing 'refresh()'"
    sendMsg("GET", "?command=get&proxyID=201&variableID=1000")
}

def parse(String description) {
	def msg = parseLanMessage(description)
	def body = msg.body
	log.debug msg.body
    def json = msg.json
	if (msg.body.contains("item")) {
		def wswitch = json.item.findAll { it.type == "6" && it.name.contains("Switch") }
		wswitch.each { 
			def proxyid = it.proxyid.toInteger()
			def index = json.item.findIndexOf { it.proxyid == "${proxyid}" } + 1
			try {
				log.trace "Switch: ${json.item[index].name} : ${json.item[index].proxyid}"
				addChildDevice("C4 Child Switch", "C4_child_switch:${json.item[index].proxyid}", null,
				[completedSetup: true, label: "C4 ${json.item[index].name}", isComponent: false,
				componentName: "C4_child_switch:${json.item[index].proxyid}", componentLabel: "${json.item[index].name}"])
			} catch (e) {
				log.error "Child device creation failed with error = ${e}"
				state.alertMessage = "Child device creation failed. Please make sure that the C4 Child Switch DH is installed and published."
				runIn(2, "sendAlert")
			}
		}
		def wdimmer = json.item.findAll { it.type == "6" && it.name.contains("Dimmer") }
		wdimmer.each {
			def proxyid = it.proxyid.toInteger()
			def index = json.item.findIndexOf { it.proxyid == "${proxyid}" } + 1
			try {
				log.trace "Dimmer: ${json.item[index].name} : ${json.item[index].proxyid}"
				addChildDevice("C4 Child Dimmer", "C4_child_dimmer:${json.item[index].proxyid}", null,
				[completedSetup: true, label: "C4 ${json.item[index].name}", isComponent: false,
				componentName: "C4_child_dimmer:${json.item[index].proxyid}", componentLabel: "${json.item[index].name}"])
			} catch (e) {
				log.error "Child device creation failed with error = ${e}"
				state.alertMessage = "Child device creation failed. Please make sure that the C4 Child Dimmer DH is installed and published."
				runIn(2, "sendAlert")
			}
		}
	} else if (msg.body.contains("variable")) {
		// Placeholder
	} else {
		// Placeholder
	}
}

private String convertIPtoHex(ipAddress) { 
    String hex = ipAddress.tokenize( '.' ).collect {  String.format( '%02x', it.toInteger() ) }.join()
    return hex
}

private String convertPortToHex(port) {
	String hexport = port.toString().format( '%04x', port.toInteger() )
    return hexport
}

private sendMsg(String method, String path) {
	try {
		def result = new physicalgraph.device.HubAction(
			method: "${method}",
			path: "/${path}",
			headers: [ HOST: "$ControllerIP:$DriverPort" ]
		)    
		log.debug result
		return result
	}
	catch (Exception e) {
		log.debug "Hit Exception $e on $result"
    }
}

private sendAlert() {
	sendEvent(
		descriptionText: state.alertMessage,
		eventType: "ALERT",
		name: "childDeviceCreation",
		value: "failed",
		displayed: true,
   )
}

def childOn(String dni) {
	def proxyid = dni.split(":")[-1]
	log.debug "childOn($proxyid)"
	sendMsg("GET", "?command=set&proxyID=${proxyid}&variableID=1000&newValue=1")
}

def childOff(String dni) {
	def proxyid = dni.split(":")[-1]
	log.debug "childOff($proxyid)"
	sendMsg("GET", "?command=set&proxyID=${proxyid}&variableID=1000&newValue=0")
}

def childSetLevel(String dni, value) {
    def proxyid = dni.split(":")[-1]
	log.debug "childSetLevel($proxyid), level = ${value}"
	sendMsg("GET", "?command=set&proxyID=${proxyid}&variableID=1001&newValue=${value}")
}

and the C4 Dimmer Child DH code:

metadata {
	definition (name: "C4 Child Dimmer", namespace: "lsilva171", author: "Luis Carlos Silva") {
		capability "Switch"
		capability "Relay Switch"
		capability "Switch Level"
		capability "Actuator"
	}
}

simulator {
}

tiles (scale:2) {
	multiAttributeTile(name:"switch", type: "lighting", width: 3, height: 4, canChangeIcon: true) {
		tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
		attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
		attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00a0dc", nextState:"turningOff"
		attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
		attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00a0dc", nextState:"turningOff"
		}
		tileAttribute ("device.level", key: "SLIDER_CONTROL") {
		attributeState "level", action:"switch level.setLevel"
		}
	}
	main "switch"
	details(["switch"])
}

void on() {
	parent.childOn(device.deviceNetworkId)
	sendEvent(name: "switch", value: "on", isStateChange: "true")
}

void off() {
	parent.childOff(device.deviceNetworkId)
	sendEvent(name: "switch", value: "off", isStateChange: "true")
}

def setLevel(value) {
	log.debug "setLevel >> value: $value"
	def level = Math.max(Math.min(value as Integer, 99), 0)
	if (level > 0) {
		sendEvent(name: "switch", value: "on")
	} else {
		sendEvent(name: "switch", value: "off")
	}
	def SwitchPathLevel = "/?command=set&proxyID=${ProxyID}&variableID=1001&newValue=${level}"
	sendEvent(name: "level", value: level, unit: "%")
    parent.childSetLevel(device.deviceNetworkId, level)
}

def setLevel(value, duration) {
	log.debug "setLevel >> value: $value, duration: $duration"
	def level = Math.max(Math.min(value as Integer, 99), 0)
	setLevel(level)
}

def generateEvent(String name, String value) {
	//log.debug("Passed values to routine generateEvent in device named $device: Name - $name  -  Value - $value")
	sendEvent(name: "switch", value: value)
}

Any help is appreciated.

Tagging @pstuart

Thx for tagging @pstuart but I believe he is not actively working with Smartthings anymore.

In fact, I think the issue I am facing is related to either Smartthings or Groovy.

By the way, does anyone knows how I can make the source code look nicer in the post?

I know it can be done, just can’t figure out how to do it.

Or the </> icon in the formatting toolbar

Makes the selected text in your post look like this

Thx for the tips. Looks much better now.

2 Likes

Found the answer to my question.

According to @bflorian you need to send the result of the child’s call to the hub using sendHubCommand(). When a child device calls a method of it’s parent nothing special happens to the return value because the API is not involved. The result of the method is simply returned to the child therefore you need to send the return value of the method to the hub when that method is called in response to tapping a tile.

The child methods will look like this:

def childOn(String dni) {
	def proxyid = dni.split(":")[-1]
	log.debug "childOn($proxyid)"
	sendHubCommand(sendMsg("GET", "?command=set&proxyID=${proxyid}&variableID=1000&newValue=1"))
}

def childOff(String dni) {
	def proxyid = dni.split(":")[-1]
	log.debug "childOff($proxyid)"
	sendHubCommand(sendMsg("GET", "?command=set&proxyID=${proxyid}&variableID=1000&newValue=0"))
}

def childSetLevel(String dni, value) {
    def proxyid = dni.split(":")[-1]
	log.debug "childSetLevel($proxyid), level = ${value}"
	sendHubCommand(sendMsg("GET", "?command=set&proxyID=${proxyid}&variableID=1001&newValue=${value}"))
}

Now let me focus on polling/refreshing the devices state.

Hi, any progress on the C4 integration pls?

Currently, I have a DT (called C4 Controller) capable of finding all switches and dimmers and then creating the necessary devices automatically. I am also working on finding TVs, AVs and Apple TVs but my paid job is consuming too much of time so it’s still work in progress.

Regarding refreshing, the C4 Controller device has a manual button but also refresh automatically based on the ST pooling timer.

I still want to have the device refresh to be triggered by C4 but unfortunately I don’t have access to C4 API documentation so I depend of Google to find scraps of info.

Another consideration is how ST could subscribe to those events. I am considering using a Node JS proxy for it but didn’t try it yet.

If you want I can post my DHs so you have at least a starting point.

1 Like

Thanks @NOS4A2 that would be great.
Actually all I want is to control switches and dimmers…
Would appreciate if you can post the DH pls.
Regards
Jihad

Hi, can u pls also share the dedicated Dh done for the specific dimmer/switch?
How do you get the device id?
where do you load the web2way?
I am ok doin creating a dedicated Dh per switch… if thats what it takes.

So, you will need 3 DHs. One for the C4 controller, one for the switches and another for the dimmers.

Please remember it is still a work in progress and I never tried outside my environment, but I am confident it will work :slight_smile:

Here, DH for the C4 controller. After creating the device. you will need to enter your C4 controller IP address and web2Way C4 driver port number (I used the default 9000).

/*
 *  Control4 Virtual Controller
 *
 *  Copyright 2017 Luis Carlos Silva
 *
 *  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.
 *
 *  Requires the web2Way Control4 driver
 *
 */

metadata {
	definition (name: "C4 Controller", namespace: "lsilva171", author: "Luis Carlos Silva") {
		capability "Configuration"
		capability "Refresh"
		capability "Polling"
	}

	preferences {
		input("ControllerIP", "string", title:"C4 Controller IP Address", description: "Enter C4 controller's IP Address", required: true, displayDuringSetup: true)
		input("DriverPort", "string", title:"C4 Driver Port", description: "Enter web2Way C4 driver's port", required: true, displayDuringSetup: true)
	}
}

simulator {
}

tiles (scale:2) {
	standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
		state "default", label:'Refresh', action: "refresh.refresh", icon: "st.secondary.refresh-icon"
	}
	standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
		state "configure", label:'Configure', action:"configuration.configure", icon:"st.secondary.tools"
	}
	childDeviceTiles("all")
}

def installed() {
}

def updated() {
}

def initialize() {
}

def configure() {
	log.debug "Executing 'configure()'"
    def hosthex = convertIPtoHex(ControllerIP).toUpperCase()
    def porthex = convertPortToHex(DriverPort).toUpperCase()
    device.deviceNetworkId = "$hosthex:$porthex" 
	log.debug "The C4 Controller DNI configured is $device.deviceNetworkId"
	sendMsg("GET", "?command=getitems")
}

def refresh() {
	log.debug "Executing 'refresh()'"
	def children = getChildDevices()
	children.each { child ->
		def proxyid = child.deviceNetworkId.split(":")[-1]
		//sendHubCommand(sendMsg("GET", "?command=getvariables&proxyID=${proxyid}"))
		sendHubCommand(sendMsg("GET", "?command=get&proxyID=${proxyid}&variableID=1000"))
		if (child.hasCapability("Switch Level")) { 
			sendHubCommand(sendMsg("GET", "?command=get&proxyID=${proxyid}&variableID=1001"))
		}
	}
}

def poll() {
	refresh()
}

def parse(String description) {
	def msg = parseLanMessage(description)
	//log.debug msg.body
	def json = msg.json
	if (json.item) {
		def hub = location.hubs[0]
		def wswitch = json.item.findAll { it.type == "6" && it.name.contains("Switch") }
		log.debug "Found ${wswitch.size()} switches"
		wswitch.each { 
			def proxyid = it.proxyid.toInteger()
			def index = json.item.findIndexOf { it.proxyid == "${proxyid}" } + 1
			try {
				//log.trace "Switch: ${json.item[index].name} : ${json.item[index].proxyid}"
				addChildDevice("C4 Child Switch", "C4_virtual_device:${json.item[index].proxyid}", hub.id,
				[completedSetup: true, label: "${json.item[index].name}", isComponent: false,
				componentName: "C4_child_switch:${json.item[index].proxyid}", componentLabel: "${json.item[index].name}"])
			} catch (e) {
				//log.error "Switch child device creation failed with error = ${e}"
				state.alertMessage = "Switch child device creation failed. Please use Live Logging to find why."
				runIn(2, "sendAlert")
			}
		}
		def wdimmer = json.item.findAll { it.type == "6" && it.name.contains("Dimmer") }
		log.debug "Found ${wdimmer.size()} dimmers"
		wdimmer.each {
			def proxyid = it.proxyid.toInteger()
			def index = json.item.findIndexOf { it.proxyid == "${proxyid}" } + 1
			try {
				//log.trace "Dimmer: ${json.item[index].name} : ${json.item[index].proxyid}"
				addChildDevice("C4 Child Dimmer", "C4_virtual_device:${json.item[index].proxyid}", hub.id,
				[completedSetup: true, label: "${json.item[index].name}", isComponent: false,
				componentName: "C4_child_dimmer:${json.item[index].proxyid}", componentLabel: "${json.item[index].name}"])
			} catch (e) {
				//log.error "Dimmer child device creation failed with error = ${e}"
				state.alertMessage = "Dimmer child device creation failed. Please use Live Logging to find why."
				runIn(2, "sendAlert")
			}
		}
		def wshade = json.item.findAll { it.type == "6" && it.name.contains("Shade") }
		log.debug "Found ${wshade.size()} shades"
		wshade.each {
			def proxyid = it.proxyid.toInteger()
			def index = json.item.findIndexOf { it.proxyid == "${proxyid}" } + 1
			try {
				//log.trace "Shade: ${json.item[index].name} : ${json.item[index].proxyid}"
				addChildDevice("C4 Child Shade", "C4_virtual_device:${json.item[index].proxyid}", hub.id,
				[completedSetup: true, label: "${json.item[index].name}", isComponent: false,
				componentName: "C4_child_shade:${json.item[index].proxyid}", componentLabel: "${json.item[index].name}"])
			} catch (e) {
				//log.error "Shade child device creation failed with error = ${e}"
				state.alertMessage = "Shade child device creation failed. Please use Live Logging to find why."
				runIn(2, "sendAlert")
			}
		}
	} else if (json.variableid) {
		try {
			def childDevice = getChildDevices()?.find { it.deviceNetworkId == "C4_virtual_device:${json.variableid[0].proxyid}"}
			//def childDevice = getChildDevices()?.find { it.deviceNetworkId.contains(":${json.variableid[0].proxyid}")}
			if (json.variableid[0]."1000") {
				if (json.variableid[0]."1000".toInteger()) {
					log.debug "ProxyID: ${json.variableid[0].proxyid} is on"
					childDevice.sendEvent(name: "switch", value: "on", isStateChange: "true")
                    childDevice.sendEvent(name: "level", value: 100, unit: "%")
				} else {
					log.debug "ProxyID: ${json.variableid[0].proxyid} is off"
					childDevice.sendEvent(name: "switch", value: "off", isStateChange: "true")
                    childDevice.sendEvent(name: "level", value: 0, unit: "%")
				}
			}
			if (json.variableid[0]."1001") {
				def level = json.variableid[0]."1001".toInteger()
				log.debug "ProxyID: ${json.variableid[0].proxyid} level is ${level}"
				childDevice.sendEvent(name: "level", value: level, unit: "%")
			}
		} catch (e) {
            log.error "Couldn't find C4_virtual_device:${json.variableid[0].proxyid} child device. Error: ${e}"
        }
	} else if (json.variable) {
    	// Placeholder
	} else {
    	log.debug "Couldn't identify the parsed JSON"
	}
}

private String convertIPtoHex(ipAddress) { 
    String hex = ipAddress.tokenize( '.' ).collect {  String.format( '%02x', it.toInteger() ) }.join()
    return hex
}

private String convertPortToHex(port) {
	String hexport = port.toString().format( '%04x', port.toInteger() )
    return hexport
}

private sendMsg(String method, String path) {
	try {
		def result = new physicalgraph.device.HubAction(
			method: "${method}",
			path: "/${path}",
			headers: [ HOST: "$ControllerIP:$DriverPort" ]
		)    
		//log.debug result
		return result
	}
	catch (Exception e) {
		log.debug "Hit Exception $e on $result"
    }
}

private sendAlert() {
	sendEvent(
		descriptionText: state.alertMessage,
		eventType: "ALERT",
		name: "childDeviceCreation",
		value: "failed",
		displayed: true,
   )
}

def childOn(String dni) {
	def proxyid = dni.split(":")[-1]
	log.debug "childOn($proxyid)"
	sendHubCommand(sendMsg("GET", "?command=set&proxyID=${proxyid}&variableID=1000&newValue=1"))
}

def childOff(String dni) {
	def proxyid = dni.split(":")[-1]
	log.debug "childOff($proxyid)"
	sendHubCommand(sendMsg("GET", "?command=set&proxyID=${proxyid}&variableID=1000&newValue=0"))
}

def childSetLevel(String dni, value) {
    def proxyid = dni.split(":")[-1]
	log.debug "childSetLevel($proxyid), level = ${value}"
	sendHubCommand(sendMsg("GET", "?command=set&proxyID=${proxyid}&variableID=1001&newValue=${value}"))
}

def childOpen(String dni) {
	def proxyid = dni.split(":")[-1]
	log.debug "childOpen($proxyid)"
	sendHubCommand(sendMsg("GET", "?command=set&proxyID=${proxyid}&variableID=1000&newValue=1"))
}

def childClose(String dni) {
	def proxyid = dni.split(":")[-1]
	log.debug "childClose($proxyid)"
	sendHubCommand(sendMsg("GET", "?command=set&proxyID=${proxyid}&variableID=1000&newValue=0"))
}

Now, the C4 Switch DH.

It’s a DH child of C4 Controller DH.

/*
 *  Control4 Virtual Child Switch
 *
 *  Copyright 2017 Luis Carlos Silva
 *
 *  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.
 *
 */

metadata {
	definition (name: "C4 Child Switch", namespace: "lsilva171", author: "Luis Carlos Silva") {
		capability "Switch"
	}

	simulator {
	}

	tiles(scale: 2) {
		multiAttributeTile(name:"switch", type: "lighting", width: 3, height: 4, canChangeIcon: true) {
			tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
			attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState:"turningOn"
			attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#00a0dc", nextState:"turningOff"
			attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
			attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00a0dc", nextState:"turningOff"	
			}
		}
		main "switch"
		details(["switch"])
	}
}

void on() {
	parent.childOn(device.deviceNetworkId)
	sendEvent(name: "switch", value: "on", isStateChange: "true")
}

void off() {
	parent.childOff(device.deviceNetworkId)
	sendEvent(name: "switch", value: "off", isStateChange: "true")
}

Finally, the C4 Dimmer DH. This one also a child DH of C4 Controller DH.

/*
 *  Control4 Virtual Child Dimmer
 *
 *  Copyright 2017 Luis Carlos Silva
 *
 *  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.
 *
*/

metadata {
	definition (name: "C4 Child Dimmer", namespace: "lsilva171", author: "Luis Carlos Silva") {
		capability "Switch"
		capability "Switch Level"
	}

	simulator {
	}

	tiles (scale:2) {
		multiAttributeTile(name:"switch", type: "lighting", width: 3, height: 4, canChangeIcon: true) {
			tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
			attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
			attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00a0dc", nextState:"turningOff"
			attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
			attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00a0dc", nextState:"turningOff"
			}
			tileAttribute ("device.level", key: "SLIDER_CONTROL") {
			attributeState "level", action:"switch level.setLevel"
			}
		}
		main "switch"
		details(["switch"])
	}
}

void on() {
	parent.childOn(device.deviceNetworkId)
	sendEvent(name: "switch", value: "on", isStateChange: "true")
}

void off() {
	parent.childOff(device.deviceNetworkId)
	sendEvent(name: "switch", value: "off", isStateChange: "true")
}

def setLevel(value) {
	log.debug "setLevel >> value: $value"
	def level = Math.max(Math.min(value as Integer, 99), 0)
	if (level > 0) {
		sendEvent(name: "switch", value: "on")
	} else {
		sendEvent(name: "switch", value: "off")
	}
    parent.childSetLevel(device.deviceNetworkId, level)
	sendEvent(name: "level", value: level, unit: "%")
}

def setLevel(value, duration) {
	log.debug "setLevel >> value: $value, duration: $duration"
	def level = Math.max(Math.min(value as Integer, 99), 0)
	setLevel(level)
}

def generateEvent(String name, String value) {
	//log.debug("Passed values to routine generateEvent in device named $device: Name - $name  -  Value - $value")
	sendEvent(name: "switch", value: value)
}

Last but not least, my version of the C4 driver. You will need to add this driver to your C4 project somehow.

Just create a file named web2Way.c4i and paste the following:

<devicedata>
    <copyright>Copyright 2016 itsfrosty.  All rights reserved.</copyright>
    <creator>Controller</creator>
    <manufacturer>itsfrosty</manufacturer>
    <name>Web 2-Way</name>
    <model>Web 2-Way</model>
    <created>12/10/2016 7:01 AM</created>
    <modified>08/12/2017 17:00 PM</modified>
    <version>73</version>
    <small>devices_sm\C4.gif</small>
    <large>devices_lg\C4.gif</large>
    <control>lua_gen</control>
    <controlmethod>ip</controlmethod>
    <driver>DriverWorks</driver>
    <combo>True</combo>
    <OnlineCategory>others</OnlineCategory>
    <proxies qty="1">
		<proxy>webevents</proxy>
    </proxies>
    <config>
        <power_management_method>AlwaysOn</power_management_method>
        <power_command_delay>0</power_command_delay>
        <power_delay>0</power_delay>
        <power_command_needed>False</power_command_needed>
        <documentation>Copyright 2016 itsfrosty
This driver allows you to get and set the state of any control4 device by making
an http request. The response is in JSON format.

Use Case:
---------
I am using this (along with my homebridge-c4-plugin) to connect homekit to my
control4. So I can say "Siri turn on Kitchen Lights" or "Siri is my garage door
closed". You can use this with any other hub which supports making http requests.

Examples:
----------
- Get the current level of dimmer light:
http://CONTROLLER_IP:9000/?command=get&amp;proxyID=85&amp;variableID=1001

Result: {"1001":"75"}
which means the dimmer light with proxyID 25 is currently at 75%

- Update the current level of dimmer light:
http://CONTROLLER_IP:9000/?command=set&amp;proxyID=25&amp;variableID=1001&amp;newValue=100

Result: {"success":"true"}

- Get the current temperature set for thermostat:
http://CONTROLLER_IP:9000/?command=get&amp;proxyID=34&amp;variableID=1132,1130

Result: {"1132":"68","1130":"67","success":"true"}
which means the thermostat is set to 68F and current temperature is 67F

- Update the temperature of thermostat:
http://CONTROLLER_IP:9000/?command=set&amp;proxyID=34&amp;variableID=1132&amp;newValue=66

Result: {"success":"true"}

How to use:
------------
Sorry this is not currently easy to use :(. You will have to find the variableID
and proxyID for each device you want to connect. I hope in future we can
make it use device names and variable names instead of IDs.

- You need to either have access to ComposerPro or ask your dealer to install it
for you.
- To find proxyID, mouse over the device or check info for the device.
- To find variableID, execute:
for k,v in pairs(C4:GetDeviceVariables(85)) do print(k, v.name, v.value) end
which will print all variables, their IDs and current values.

Warning:
---------
This is completely unencrypted and unsecure. Anybody with access to your wifi
will be able to control any of your control4 connected devices. So don't open
it to internet and also make sure you have a secured wifi.
</documentation>
        <script><![CDATA[local HTTP_PORT = 9000
g_isInitialized = false
g_debugMode = false

function OnDriverDestroyed()
  C4:DestroyServer()
end

function MyDriverInit()
  if (g_isInitialized == true) then
    return
  end
  g_isInitialized = true

  C4:CreateServer(HTTP_PORT)
end

function OnTimerExpired(idTimer)
  if (idTimer == gInitTimer) then
    MyDriverInit()
    gInitTimer = C4:KillTimer(gInitTimer)
  end
end

function OnPropertyChanged(strProperty)
  local prop = Properties[strProperty]

  if (strProperty == "Debug") then
    if (prop == "On") then
      g_debugMode = true;
    else
      g_debugMode = false;
    end
  end
end

function debugPrint(str)
  if (g_debugMode) then
    print(str)
  end
end

function urldecode(str)
  str = str:gsub('+', ' ')
  str = str:gsub('%%(%x%x)', function(h)
    return string.char(tonumber(h, 16))
    end)
  return str
end

function parseRequest(recRequest)
  local _, _, url = string.find(recRequest, "GET /(.*) HTTP")
  url = url or ""
  debugPrint("URL: " .. url)
  if (string.len(url) == 0) then
    return nil
  end

  local params = {}
  url = url:gsub('?', '  ', 1)
  url = url:match('%s+(.+)')

  if (url == '' or url == nil) then
    return params
  end

  for k,v in url:gmatch('([^&=?]-)=([^&=?]+)' ) do
    params[k] = urldecode(v)
  end
  return params
end

function setVariable(proxyID, variableID, newValue)
  if (newValue == nil or newValue == '0' or newValue == '') then
    newValue = 0
  end
  C4:SetDeviceVariable(proxyID, variableID, newValue)
end

function getVariable(proxyID, variableID)
  return C4:GetDeviceVariable(proxyID, variableID)
end

function processRequest(params)
  if (params.command ~= 'getitems' and params.proxyID == nil) then
    msg = '{"success":"false"}';
  elseif (params.command == 'set') then
    setVariable(params.proxyID, params.variableID, params.newValue)
    msg = '{"success":"true"}';
  elseif (params.command == 'get') then
    msg = '{"variableid":[';
    for variableID in string.gmatch(params.variableID, "%d+") do
	  msg = msg .. '{"proxyid":"' .. params.proxyID .. '",'
      msg = msg .. '"' .. variableID .. '":"' .. getVariable(params.proxyID, variableID) .. '"},'
    end
	msg = msg:sub(1,-2)
	msg = msg .. ']}'
  elseif (params.command == 'getvariables') then
	msg = '{"variable":[{"proxyid":"' .. params.proxyID .. '"},';
    for key,value in pairs(C4:GetDeviceVariables(params.proxyID)) do 
	  msg = msg .. '{"variableid":"' .. key .. '",'
	  msg = msg .. '"name":"' .. value.name .. '",'
	  msg = msg .. '"value":"' .. value.value .. '"},'
	end
	msg = msg:sub(1,-2)
	msg = msg .. ']}'
  elseif (params.command == 'getitems') then
    strItems = C4:GetProjectItems("LIMIT_DEVICE_DATA")
	local item, id, name, devtype, rest, c4i
    local find = string.find
    msg = '{"item":[';
    for item in strItems:gfind("<id>(.-)</itemdata>") do
      _, _, id, name, devtype, rest = find(item, "(.-)<.-<name>(.-)</name>.-<type>(.-)</type>(.*)")
      _, _, c4i = find(rest, "<config_data_file>(.-)</config")
      name = string.gsub(name, "&apos;", "'", 5)
      name = string.gsub(name, "&lt;", "<", 5)
      name = string.gsub(name, "&gt;", ">", 5)
      name = string.gsub(name, "&quot;", "'", 5)
      name = string.gsub(name, "&amp;", "&", 5)
	  msg = msg .. '{"proxyid":"' .. id .. '",'
	  msg = msg .. '"type":"' .. devtype .. '",'
	  msg = msg .. '"name":"' .. name .. '"},'
	  -- debugPrint("ProxyID: " .. id .. " Type: " .. devtype .. " Name: " .. name)
	end
	msg = msg:sub(1,-2)
	msg = msg .. ']}'
  else
    msg = '{"success":"false"}';
  end
  return msg
end
	
function OnServerDataIn(nHandle, strData)
  local msg = '';

  params = parseRequest(strData)
  result = processRequest(params)

  local headers = "HTTP/1.1 200 OK\r\nContent-Length: " .. result:len() .. "\r\nContent-Type: application/json\r\n\r\n"
  C4:ServerSend(nHandle,  headers .. result)
  C4:ServerCloseClient(nHandle)
end

gInitTimer = C4:AddTimer(5, "SECONDS")]]></script>
		<actions>
            <action>
                <name>Default Action</name>
                <command>DEFAULT_ACTION</command>
            </action>
        </actions>
        <properties>
            <property>
                <name>Debug</name>
                <type>LIST</type>
                <readonly>false</readonly>
                <default>Off</default>
                <items>
                    <item>Off</item>
                    <item>On</item>
                </items>
            </property>
        </properties>
    </config>
</devicedata>

Once you have all 3 DHs in place and the C4 Controller created and configured, just go to your ST app and under Things find the C4 Controller device. Use the Configure button to automatically scan for all C4 switches and dimmers. Once found, a switch or dimmer device will be automatically created. The refresh button, basically updates the state of each device.

There are some issues with the C4 Dimmer. Because I still don’t have a satisfactory answer for the dimmer automatic state update, sometimes it doesn’t currently reflect the right dimmer %.

This fix is in the top of my to-do list but my real job will need to give me a break (hopefully in a couple of weeks from now).

thank you very much @NOS4A2
I created all 3 DHs but how do I create the C4 controller and configure it, to be able to use it from the ST app under Things? I will need to add a device, right? To do that I need a device network id…

for the C4 driver, I will need the help of the distributor, right?

yep, you need the driver associated with your C4 project and running in your controller.

The most common way of adding a driver to a C4 project is with the help of a distributor.

However, if you carefully use Google, you may find alternative ways of doing it :wink:

thanks @NOS4A2, but I still can’t find a way to use the C4 DH from the ST app.
I created the 3 DHs… the only way to use them is via adding a device… or shall I create the C4 controller as a smart app?

The only way to use them is via adding a C4 device based on the web2way driver.

This C4 driver is the responsible to receive calls from Smartthings hub requesting for a list of switches and dimmers, and their status (ON, OFF and dimmer level) among other pieces of information.

Without the driver this solution won’t work.

You don’t need a smart app for the C4 controller. A C4 controller device based on the C4 controller DH is the one responsible to request the list of switches and dimmers, and for create the necessary devices. Once the devices are created, the C4 controller device will request for the switches and dimmers status periodically.