Combining two DHT for spa cover needed

So today I combined two DHTs to give me a custom DHT to tell me the position of my spa cover. The DHT works, but I wasn’t expecting the new app to make the tiles so difficult…
I am using a 2016ish SmartThings multi sensor as the DHT template in IDE. It’s my sensor too. I added “windowShade” capability since this offered open, partially open, as well as closed. I used the fact that a sensor can capture My spa cover closed at 12 o’clock, partially open (half open) at 6 o’clock and open (fully) at 9 o’clock to write the DHT to properly report the windowShade value properly based on Y and Z accels. It shows properly in history, but I can’t figure how to add it to the tiles in this new app! Ideally, I would show windowShade as big/primary and add temperature & battery as secondary.
Any helpful suggestions? I took a few stabs at the new CLI lingo, but I’m quickly getting overwhelmed!

New app doesn’t use tiles. Capabilities have presentations that display automatically.

Do you have a link to the DTH?




  • 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:
  • 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.
    //definition(name: “SmartSense Multi Sensor Spa”, namespace: “insaneoctane”, author: “insaneoctane”, runLocally: false, minHubCoreVersion: ‘000.017.0012’, executeCommandsLocally: false)
    import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
    import physicalgraph.zigbee.zcl.DataType

metadata {
definition(name: “SmartSense Multi Sensor Spa”, namespace: “insaneoctane”, author: “insaneoctane”, runLocally: true, minHubCoreVersion: ‘000.017.0012’, executeCommandsLocally: true, mnmn: “SmartThings”, vid: “SmartThings-smartthings-SmartSense_Multi_Sensor”) {

	capability "Three Axis"
	capability "Battery"
	capability "Configuration"
	capability "Sensor"
	capability "Contact Sensor"
	capability "Acceleration Sensor"
	capability "Refresh"
	capability "Temperature Measurement"
	capability "Health Check"
	capability "Window Shade"

	fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320", deviceJoinName: "Multipurpose Sensor Spa"
	fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321", deviceJoinName: "Multipurpose Sensor Spa"
	fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321-S", deviceJoinName: "Multipurpose Sensor Spa"
	fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500,FC02", outClusters: "0019", manufacturer: "SmartThings", model: "multiv4", deviceJoinName: "Multipurpose Sensor Spa"
	fingerprint inClusters: "0000,0001,0003,0020,0402,0500,FC02", outClusters: "0019", manufacturer: "Samjin", model: "multi", deviceJoinName: "Multipurpose Sensor Spa"


simulator {
	status "open": "zone report :: type: 19 value: 0031"
	status "closed": "zone report :: type: 19 value: 0030"

        state("closed",  label:'${name}', icon:"st.doors.garage.garage-closed", backgroundColor:"#bbbbdd", nextState: "opening")
        state("open",    label:'up', icon:"st.doors.garage.garage-open", backgroundColor:"#ffcc33", nextState: "closing")
        state("partially open", label:'preset', icon:"st.Transportation.transportation13", backgroundColor:"#ffcc33")
        state("closing", label:'${name}', icon:"st.doors.garage.garage-closing", backgroundColor:"#bbbbdd")
        state("opening", label:'${name}', icon:"st.doors.garage.garage-opening", backgroundColor:"#ffcc33")
	standardTile("acceleration", "device.acceleration", width: 2, height: 2) {
		state("active", label: 'Active', icon: "", backgroundColor: "#00a0dc")
		state("inactive", label: 'Inactive', icon: "st.motion.acceleration.inactive", backgroundColor: "#cccccc")
	valueTile("temperature", "device.temperature", width: 2, height: 2) {
		state("temperature", label: '${currentValue}°',
				backgroundColors: [
						[value: 31, color: "#153591"],
						[value: 44, color: "#1e9cbb"],
						[value: 59, color: "#90d2a7"],
						[value: 74, color: "#44b621"],
						[value: 84, color: "#f1d801"],
						[value: 95, color: "#d04e00"],
						[value: 96, color: "#bc2323"]
	valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
		state "battery", label: '${currentValue}% battery', unit: ""
	standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
		state "default", action: "refresh.refresh", icon: "st.secondary.refresh"

	main(["contact", "SpaCover", "acceleration", "temperature"])
	details(["contact", "SpaCover", "acceleration", "temperature", "battery", "refresh"])

xyzResults.y = x
xyzResults.z = y

log.debug "parseAxis -- ${xyzResults}"

if (garageSensor == "Yes")
	results += garageEvent(xyzResults.z)

if (SpaCoverSensor == "Yes")
	results += SpaEvent(xyzResults)

def value = "${xyzResults.x},${xyzResults.y},${xyzResults.z}"
results << [
		name           : "threeAxis",
		value          : value,
		linkText       : getLinkText(device),
		descriptionText: "${getLinkText(device)} was ${value}",
		handlerName    : name,
		isStateChange  : isStateChange(device, "threeAxis", value),
		displayed      : false

SpaCoverValue = ‘closed’
SpaWas = ‘closed’
} else if ((xyzValues.y >= -742) && (xyzValues.z >= -742)) {
contactValue = ‘open’
SpaCoverValue = ‘partially open’
SpaWas = ‘partially opened’
} else if ((xyzValues.y > -742) && (xyzValues.y <= 742) && (xyzValues.z < -742)) {
contactValue = ‘open’
SpaCoverValue = ‘open’
SpaWas = ‘Fully opened’
} else {
contactValue = ‘closed’
SpaCoverValue = ‘unknown’
SpaWas = ‘unknown’
if (contactValue != null) {
def descriptionText = contactValue == ‘open’ ? ‘{{ device.displayName }} was opened’ : ‘{{ device.displayName }} was closed’
results << [name: ‘contact’, value: contactValue, descriptionText: descriptionText, translatable: true]
//log.debug “spaCoverValue= {SpaCoverValue}" if (SpaCoverValue != null) { def SpaCoverdescriptionText = '{{ device.displayName }} was {{ SpaWas }}' //log.debug "spaCoverValue not null, is {{ SpaCoverValue }}" results << [name: 'windowShade', value: SpaCoverValue, descriptionText: SpaCoverdescriptionText, isStateChange: isStateChange(device, "windowShade", SpaCoverValue), translatable: true] } //log.debug "windowShade: {device.displayName} value = ${SpaCoverValue}”
//log.debug results

private getManufacturerCode() {
if (device.getDataValue(“manufacturer”) == “SmartThings”) {
return “0x110A”
} else if (device.getDataValue(“manufacturer”) == “Samjin”) {
return “0x1241”
} else {
return “0x104E”

private shouldUseOldBatteryReporting() {
def isFwVersionLess = true // By default use the old battery reporting
def deviceFwVer = “${device.getFirmwareVersion()}”
def deviceVersion = deviceFwVer.tokenize(’.’) // We expect the format ###.###.### where ### is some integer

if (deviceVersion.size() == 3) {
	def targetVersion = [1, 15, 7] // Centralite Firmware 1.15.7 contains battery smoothing fixes, so versions before that should NOT be smoothed
	def devMajor = deviceVersion[0] as int
	def devMinor = deviceVersion[1] as int
	def devBuild = deviceVersion[2] as int

	isFwVersionLess = ((devMajor < targetVersion[0]) ||
		(devMajor == targetVersion[0] && devMinor < targetVersion[1]) ||
		(devMajor == targetVersion[0] && devMinor == targetVersion[1] && devBuild < targetVersion[2]))

return isFwVersionLess // If f/w version is less than 1.15.7 then do NOT smooth battery reports and use the old reporting


private hexToInt(value) {
new BigInteger(value, 16)

Ah if you have the CLI you are rolling. If the DTH works then great. Having runLocally: true, minHubCoreVersion: ‘000.017.0012’, executeCommandsLocally: true, present in the definition() is always a bit of a risk as it means your DTH may run locally but isn’t actually using your code if it does. If everything works right for you then great, but if you wonder why bits of your code don’t seem to be running, lose those bits.

Forget all you know about Tiles, those are ancient history.

When editing the DTH in the IDE you’ll see a UUID at the end of the URL. That’s your DTH ID. You’ll need that.

Assuming the CLI is installed as smartthings.exe, what you want to do is run the command

smartthings presentation:device-config:generate DTHID --dth --output=spacover.json

That analyses your DTH and produces a usable device config from it. You need to take a look at that in an editor to see if it does what you need. What you are interested in at the moment is right at the top of the file. If you speak JSON, the first entry in the states array at the top defines what attribute appears in your dashboard tile in the app.

It’ll look something like this:

"detailView": [
        "component": "main",
        "capability": "temperatureMeasurement",
        "version": 1,
        "values": [],
        "patch": []
        "component": "main",
        "capability": "battery",
        "version": 1,
        "values": [],
        "patch": []
        "component": "main",
        "capability": "contactSensor",
        "version": 1,
        "values": [],
        "patch": []

You can see the obvious groupings. The app is a bit of a law unto itself but if you rearrange the order of them you will hopefully see that reflected in the app.

Anyway once you’ve done that you need to go back to the CLI and do

smartthings presentation:device-config:create --input=spacover.json

That should run without error and ideally would produce a nice summary. In practice it spews out a full config file. In there you will find a UUID labelled as the vid and the presentationId (old and new terms). You want that. What you have actually done is created a device presentation file and that is what the app uses to know what to display.

In your DTH you change the mnmn to "SmartThingsCommunity" and the vid to the UUID mentioned above, and save and publish.

You then need the app to notice you’ve been playing. If you are lucky you can do this by editing the device in the IDE, changing something significant like the device name or device label, and updating.

You will be probably be disappointed at the look of the UI in the app, but that’s pretty all the messing around you get for the moment.

1 Like


  1. Create JSON and from existing DTH
  2. Move parts of JSON around
  3. Create vid ID from JSON
  4. Add vid reference to DTH in IDE

I will try this but it’s not obvious how the vid ID produced from my DTH by CLI ever makes anything useful to my DTH for the GUI? Also, how is the JSON output going to know to contain my new windowShade capability unless it’s reading my DTH capabilities?

Wow, you’re on fire, @orangebucket! :ok_hand:

  • The device presentation (identified by the VID) is the collection of all the presentations of the capabilities included in your device.
  • A capability presentation defines how the value is shown (slider, simple text, list, etc.).
  • The stock capabilities have this already configured, so, you just need to define in which view (of the device) you want to show the capabilities.

When you use the DTH ID to generate the device-config, it takes the capabilities’ list in your DTH’s definition, that’s why the last changes you made (add WindowShade) must be saved and published.
The device-config file is used to generate the device presentation. For you to have a better reference on the views, here’s a short description:

  1. Dashboard - It is the main view, the one you see when you open the mobile app.
    a. States - this is where you show a capability value, eg. Temperature.
    b. Actions - here is where you include a capability that provides an action, eg. a Switch button.
    Note: Only one capability can be included in each section, a and b.

  2. Detail - This is the view shown when you enter the device. As graham said, you can order differently the capabilities using the device-config. Some are fixed to the top, though, like the Switch capability.
    Here, all the capabilities that belong to your DTH should be included.

  3. Automations - This is for the Automations tool in the app. Not all capabilities have this section configured, so if you add all here as well, only those that use this section will appear.

Great replies, thank you.
I added windowShade tiles, but then I removed them because they weren’t working…am I understanding it correctly that they are unnecessary for the CLI to generate my JSON and vid id because it’s only looking at capabilities and not the tiles?

The starting point is the ID of your DTH which includes the windowShade capability. ST is already well aware of what capabilities are in that as it processes it when you save it. Just look at the device handlers page in the IDE and you can see it knows which capabilities are used by each handler.

The app doesn’t give a monkey’s about the DTH. All the information it requires about how to build the GUI is in the presentation and the ID for that is part of the device object (having been read from the DTH).

Update: @nayelyz described all that more eloquently while I was typing.

I followed your instructions and things.just.worked. :grin:

Not used to that. here’s the result:

Perfectly functional now. In a perfect world, I’d like to change the tile title from “Window shade” to “Spa cover”, but that’s certainly a nit!

Thanks a million!

Just to contribute a little more interesting data to this thread, this is how I was able to generate the mG required for the 3 states (open, partially open, closed). The sensor essentially spins 270 degrees thru the cover postions:

The graph shows Y & Z mG values across 360 degree rotation. I used color codes (green= closed = 0 deg, yellow = half open = 180 deg, orange = fully open = 270 deg) to decide what state I would call the position/rotation and just wrote the < or > functions in the DTH to define the “WindowShade” for my spa cover. The X-axis isn’t used at all. Fun stuff!

That’s awesome! Remember to mark @orangebucket’s post above as the solution so others can benefit from this thread. Thanks!

Had it been a custom capability then it could be done using a modified device config, but if it were a custom capability you could have had it say what you wanted in the first place.

As it is a stock capability I am less confident as they are something of a law unto themselves.

I am testing something at the moment but I have hit a cache that could take hours to clear.