Device Handler: Which value to show on main tile

The first post in the thread below gives instructions for creating your own device presentation (vid) beginning with the “Generate / Post Device Configuration” section. Alternatively, you can look through DTHs for similar devices on the ST GitHub and pull a vid off one of those that fits your needs.

1 Like

TO add on what @philh30 posted, It depends on how the DTH is currently written. you have to look at the order various capabilities were defined in the DTH. ALSO, there was a significant change to built-in capabilities around thermostats, where ST split various parts of the old thermostat capability out into smaller more specific features such as cooling setpoint, heating setpoint, etc. If your DTH doesn’t have the new thermostat capability set in it and still references the old builtin thermostat capability, the app really doesn’t understand i’s a thermostat at all… (Yeah I know how absurd that sounds)

1 Like

@nathancu This begs the question on why SmartThings wouldn’t update the DTH when things change. As my base device handler, I used the one “From Example” CT100 version. Why provide examples for devices that don’t work with the current system?

Thanks

Sorry not familiar with that…

Let me ask this. What dictates the value which is shown on the first image I posted?

1 Like

You have to create a custom presentation and device profile that specifies the display of your device, then ise that to create a VID, then stuff that back in your DTH. Its all detailed in Jody’s tutorial here:

In the past, you could just copy and paste the DTH from someone’s github.
I do not understand why things had to become so complicated. Yeah it might make sense if you show someone a complete diagram, flowchart and take them by the hand, but I am lost. Despite Jody doing a great job (I assume) it does not help me much. How can I create a custom presentation without using CLI but using the IDE? Why a custom device profile? Aren’t custom device profiles my device handlers?

Nothing has changed in that regard.

When it comes to changing the status and action of dashboard tiles, once you have the CLI installed (which takes five to ten minutes), it is a less than five minute job to make the required changes to a DTH, and only one person needs to do it.

The terminology is being made more appropriate and consistent at the moment, as stuff that has been around for a long time in the new environment is being made more accessible.

A device profile is largely the equivalent of the metadata in a device handler for new style integrations. Instead of the bizarre idea of defining the UI inside the handler, in the new environment the UI is specified in device presentation files - typically around 100kb of JSON - which describe how the device should be presented in the dashboard, on device detail cards, and in automations. This same presentation is being retrofitted into device handlers.

All capabilities already have their own capability presentations, so building a device presentation involves creating an intermediate file that describes the device in terms of capabilities. This is called a device config.

So to change what is displayed on a tile you generate a default device config from an existing DTH (one line in the CLI), edit that file (YAML or JSON) to change the order of a couple of arrays, generate a presentation from the config (one line in the CLI) and extract a UUID to use in the DTH.

Here is a real example illustrating the process:

@orangebucket Thanks Graham for your post.

Not true though, the DTH used to define the dashboard tiles and everything worked out of the box, so let’s agree that it has been changed.

I have the CLI installed (I just downloaded it and run it, no problem it immediately open up a new window in my browser and connected to my developer account).

Can we copy device profiles and copy them to the IDE? Where can I find my presentation files in the IDE? Also why isn’t the standard temperature measurement a capability with predefined presentation? Is that normal? How does STSC take the order of the predefined capabilities and picks arbitrarily which one to show :confused: ? Can we change that?

The IDE exists in the legacy environment which has no understanding of them.

The CLI is simply a front end to the REST API. The presentation files can be pulled from that if required.

I believe the pressure sensor not to be covered as a capability/component anywhere. So I need to define myself again in the developer section? And then define the presentation using the CLI without having visual tools?

By the way, what I did is this:

So to change what is displayed on a tile you generate a default device config from an existing DTH (one line in the CLI),

I am finding the temperature. It is the first one. But I get a malformed request. Below the DTH and the json file (I do not change since the temp is the first one in the states)

C:\Users\Nick\Documents\smartthings>cli-win.exe presentation:device-config:generate 3ce46925-e6ac-4b14-a6a7-00bb6020be99 --dth --output=config.json

C:\Users\Nick\Documents\smartthings>cli-win.exe presentation:device-config:create --input=config.json
[2020-09-11T22:07:15.670] [ERROR] cli - caught error Error: Request failed with status code 400: {“requestId”:“C01CC16C-55EC-4FDF-9965-111F85A8C22C”,“error”:{“code”:“BadRequestError”,“target”:null,“message”:“The request is malformed.”,“details”:}}

/**
 *  Xiaomi Aqara Temperature Humidity Sensor
 *  Version 1.3
 *
 *
 *  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.
 *
 *  Original device handler code by a4refillpad, adapted for use with Aqara model by bspranger
 *  Additional contributions to code by alecm, alixjg, bspranger, cscheiene, gn0st1c, foz333, jmagnuson, rinkek, ronvandegraaf, snalee, tmleafs, twonk, & veeceeoh 
 * 
 *  Known issues:
 *  Xiaomi sensors do not seem to respond to refresh requests
 *  Inconsistent rendering of user interface text/graphics between iOS and Android devices - This is due to SmartThings, not this device handler
 *  Pairing Xiaomi sensors can be difficult as they were not designed to use with a SmartThings hub. See 
 *
 */

metadata {
	definition (name: "Xiaomi Aqara Temperature Humidity Sensor", namespace: "bspranger", author: "bspranger") {
	capability "Temperature Measurement"
	capability "Relative Humidity Measurement"
	capability "Sensor"
	capability "Battery"
	capability "Health Check"

	attribute "lastCheckin", "String"
	attribute "lastCheckinDate", "String"
	attribute "maxTemp", "number"
	attribute "minTemp", "number"
	attribute "maxHumidity", "number"
	attribute "minHumidity", "number"
	attribute "multiAttributesReport", "String"
	attribute "currentDay", "String"
	attribute "batteryRuntime", "String"

	fingerprint profileId: "0104", deviceId: "5F01", inClusters: "0000, 0003, FFFF, 0402, 0403, 0405", outClusters: "0000, 0004, FFFF", manufacturer: "LUMI", model: "lumi.weather", deviceJoinName: "Xiaomi Aqara Temp Sensor"

	command "resetBatteryRuntime"
	}

    // simulator metadata
    simulator {
        for (int i = 0; i <= 100; i += 10) {
            status "${i}F": "temperature: $i F"
        }

        for (int i = 0; i <= 100; i += 10) {
            status "${i}%": "humidity: ${i}%"
        }
    }

    tiles(scale: 2) {
        multiAttributeTile(name:"temperature", type:"generic", width:6, height:4) {
            tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
                attributeState("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"]
 				]
                )
            }
            tileAttribute("device.multiAttributesReport", key: "SECONDARY_CONTROL") {
                attributeState("multiAttributesReport", label:'${currentValue}' //icon:"st.Weather.weather12",
                )
            }
        }
        valueTile("temperature2", "device.temperature", inactiveLabel: false) {
            state "temperature", label:'${currentValue}°', icon:"st.Weather.weather2",
                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("humidity", "device.humidity", inactiveLabel: false, width: 2, height: 2) {
            state "humidity", label:'${currentValue}%', unit:"%", icon:"https://raw.githubusercontent.com/bspranger/Xiaomi/master/images/XiaomiHumidity.png",
            backgroundColors:[
                [value: 0, color: "#FFFCDF"],
                [value: 4, color: "#FDF789"],
                [value: 20, color: "#A5CF63"],
                [value: 23, color: "#6FBD7F"],
                [value: 56, color: "#4CA98C"],
                [value: 59, color: "#0072BB"],
                [value: 76, color: "#085396"]
            ]
        }
        standardTile("pressure", "device.pressure", inactiveLabel: false, decoration:"flat", width: 2, height: 2) {
            state "pressure", label:'${currentValue}', icon:"https://raw.githubusercontent.com/bspranger/Xiaomi/master/images/XiaomiPressure.png"
        }
        valueTile("battery", "device.battery", inactiveLabel: false, width: 2, height: 2) {
            state "battery", label:'${currentValue}%', unit:"%", icon:"https://raw.githubusercontent.com/bspranger/Xiaomi/master/images/XiaomiBattery.png",
            backgroundColors:[
                [value: 10, color: "#bc2323"],
                [value: 26, color: "#f1d801"],
                [value: 51, color: "#44b621"]
            ]
        }
        valueTile("spacer", "spacer", decoration: "flat", inactiveLabel: false, width: 1, height: 1) {
	    state "default", label:''
        }
        valueTile("lastcheckin", "device.lastCheckin", inactiveLabel: false, decoration:"flat", width: 4, height: 1) {
            state "lastcheckin", label:'Last Event:\n ${currentValue}'
        }
        valueTile("batteryRuntime", "device.batteryRuntime", inactiveLabel: false, decoration:"flat", width: 4, height: 1) {
            state "batteryRuntime", label:'Battery Changed: ${currentValue}'
        }

        main("temperature2")
        details(["temperature", "battery", "pressure", "humidity", "spacer", "lastcheckin", "spacer", "spacer", "batteryRuntime", "spacer"])
    }
	preferences {
		//Button Config
		input description: "The settings below customize additional infomation displayed in the main tile.", type: "paragraph", element: "paragraph", title: "MAIN TILE DISPLAY"
		input name: "displayTempInteger", type: "bool", title: "Display temperature as integer?", description:"NOTE: Takes effect on the next temperature report. High/Low temperatures are always displayed as integers."
		input name: "displayTempHighLow", type: "bool", title: "Display high/low temperature?"
		input name: "displayHumidHighLow", type: "bool", title: "Display high/low humidity?"
		//Temp, Humidity, and Pressure Offsets and Pressure Units
		input description: "The settings below allow correction of variations in temperature, humidity, and pressure by setting an offset. Examples: If the sensor consistently reports temperature 5 degrees too warm, enter '-5' for the Temperature Offset. If it reports humidity 3% too low, enter ‘3' for the Humidity Offset. NOTE: Changes will take effect on the NEXT temperature / humidity / pressure report.", type: "paragraph", element: "paragraph", title: "OFFSETS & UNITS"
		input "tempOffset", "decimal", title:"Temperature Offset", description:"Adjust temperature by this many degrees", range:"*..*"
		input "humidOffset", "number", title:"Humidity Offset", description:"Adjust humidity by this many percent", range: "*..*"
		input "pressOffset", "number", title:"Pressure Offset", description:"Adjust pressure by this many units", range: "*..*"
		input name:"PressureUnits", type:"enum", title:"Pressure Units", options:["mbar", "kPa", "inHg", "mmHg"], description:"Sets the unit in which pressure will be reported"
		input description: "NOTE: The temperature unit (C / F) can be changed in the location settings for your hub.", type: "paragraph", element: "paragraph", title: ""
		//Date & Time Config
		input description: "", type: "paragraph", element: "paragraph", title: "DATE & CLOCK"    
		input name: "dateformat", type: "enum", title: "Set Date Format\nUS (MDY) - UK (DMY) - Other (YMD)", description: "Date Format", options:["US","UK","Other"]
		input name: "clockformat", type: "bool", title: "Use 24 hour clock?"
		//Battery Reset Config
		input description: "If you have installed a new battery, the toggle below will reset the Changed Battery date to help remember when it was changed.", type: "paragraph", element: "paragraph", title: "CHANGED BATTERY DATE RESET"
		input name: "battReset", type: "bool", title: "Battery Changed?", description: ""
		//Battery Voltage Offset
		input description: "Only change the settings below if you know what you're doing.", type: "paragraph", element: "paragraph", title: "ADVANCED SETTINGS"
		input name: "voltsmax", title: "Max Volts\nA battery is at 100% at __ volts.\nRange 2.8 to 3.4", type: "decimal", range: "2.8..3.4", defaultValue: 3
		input name: "voltsmin", title: "Min Volts\nA battery is at 0% (needs replacing)\nat __ volts.  Range 2.0 to 2.7", type: "decimal", range: "2..2.7", defaultValue: 2.5
	}
}

// Parse incoming device messages to generate events
def parse(String description) {
    log.debug "${device.displayName}: Parsing description: ${description}"

    // Determine current time and date in the user-selected date format and clock style
    def now = formatDate()    
    def nowDate = new Date(now).getTime()

	// Any report - temp, humidity, pressure, & battery - results in a lastCheckin event and update to Last Checkin tile
	// However, only a non-parseable report results in lastCheckin being displayed in events log
    sendEvent(name: "lastCheckin", value: now, displayed: false)
    sendEvent(name: "lastCheckinDate", value: nowDate, displayed: false)

	// Check if the min/max temp and min/max humidity should be reset
    checkNewDay(now)

	// getEvent automatically retrieves temp and humidity in correct unit as integer
	Map map = zigbee.getEvent(description)

	// Send message data to appropriate parsing function based on the type of report
	if (map.name == "temperature") {
        def temp = parseTemperature(description)
		map.value = displayTempInteger ? (int) temp : temp
		map.descriptionText = "${device.displayName} temperature is ${map.value}°${temperatureScale}"
		map.translatable = true
		updateMinMaxTemps(map.value)
	} else if (map.name == "humidity") {
		map.value = humidOffset ? (int) map.value + (int) humidOffset : (int) map.value
		updateMinMaxHumidity(map.value)
	} else if (description?.startsWith('catchall:')) {
		map = parseCatchAllMessage(description)
	} else if (description?.startsWith('read attr - raw:')) {
		map = parseReadAttr(description)
	} else {
		log.debug "${device.displayName}: was unable to parse ${description}"
        sendEvent(name: "lastCheckin", value: now) 
	}

	if (map) {
		log.debug "${device.displayName}: Parse returned ${map}"
		return createEvent(map)
	} else
		return [:]
}

// Calculate temperature with 0.1 precision in C or F unit as set by hub location settings
private parseTemperature(String description) {
	def temp = ((description - "temperature: ").trim()) as Float
	def offset = tempOffset ? tempOffset : 0
	temp = (temp > 100) ? (100 - temp) : temp
    temp = (temperatureScale == "F") ? ((temp * 1.8) + 32) + offset : temp + offset
	return temp.round(1)
}

// Check catchall for battery voltage data to pass to getBatteryResult for conversion to percentage report
private Map parseCatchAllMessage(String description) {
	Map resultMap = [:]
	def catchall = zigbee.parse(description)
	log.debug catchall

	if (catchall.clusterId == 0x0000) {
		def MsgLength = catchall.data.size()
		// Original Xiaomi CatchAll does not have identifiers, first UINT16 is Battery
		if ((catchall.data.get(0) == 0x01 || catchall.data.get(0) == 0x02) && (catchall.data.get(1) == 0xFF)) {
			for (int i = 4; i < (MsgLength-3); i++) {
				if (catchall.data.get(i) == 0x21) { // check the data ID and data type
					// next two bytes are the battery voltage
					resultMap = getBatteryResult((catchall.data.get(i+2)<<8) + catchall.data.get(i+1))
					break
				}
			}
		}
	}
	return resultMap
}

// Parse pressure report or battery report on reset button press
private Map parseReadAttr(String description) {
	Map resultMap = [:]

	def cluster = description.split(",").find {it.split(":")[0].trim() == "cluster"}?.split(":")[1].trim()
	def attrId = description.split(",").find {it.split(":")[0].trim() == "attrId"}?.split(":")[1].trim()
	def value = description.split(",").find {it.split(":")[0].trim() == "value"}?.split(":")[1].trim()

	// log.debug "${device.displayName}: Parsing read attr: cluster: ${cluster}, attrId: ${attrId}, value: ${value}"

	if ((cluster == "0403") && (attrId == "0000")) {
		def result = value[0..3]
		float pressureval = Integer.parseInt(result, 16)

		if (!(settings.PressureUnits)){
			settings.PressureUnits = "mbar"
		}
		// log.debug "${device.displayName}: Converting ${pressureval} to ${PressureUnits}"
	
		switch (PressureUnits) {
			case "mbar":
				pressureval = (pressureval/10) as Float
				pressureval = pressureval.round(1);
				break;

			case "kPa":
				pressureval = (pressureval/100) as Float
				pressureval = pressureval.round(2);
				break;

			case "inHg":
				pressureval = (((pressureval/10) as Float) * 0.0295300)
				pressureval = pressureval.round(2);
				break;

			case "mmHg":
				pressureval = (((pressureval/10) as Float) * 0.750062)
				pressureval = pressureval.round(2);
				break;
		}
		// log.debug "${device.displayName}: Pressure is ${pressureval} ${PressureUnits} before applying the pressure offset."

		if (settings.pressOffset) {
			pressureval = (pressureval + settings.pressOffset)
		}

		pressureval = pressureval.round(2);

		resultMap = [
			name: 'pressure',
			value: pressureval,
			unit: "${PressureUnits}",
			isStateChange: true,
			descriptionText : "${device.displayName} Pressure is ${pressureval} ${PressureUnits}"
		]
	} else if (cluster == "0000" && attrId == "0005")  {
		def modelName = ""
	
		// Parsing the model name
		for (int i = 0; i < value.length(); i+=2) {
			def str = value.substring(i, i+2);
			def NextChar = (char)Integer.parseInt(str, 16);
			modelName = modelName + NextChar
		}
		log.debug "${device.displayName}: Reported model: ${modelName}"
	}
	return resultMap
}

// Convert raw 4 digit integer voltage value into percentage based on minVolts/maxVolts range
private Map getBatteryResult(rawValue) {
    // raw voltage is normally supplied as a 4 digit integer that needs to be divided by 1000
    // but in the case the final zero is dropped then divide by 100 to get actual voltage value 
    def rawVolts = rawValue / 1000
    def minVolts
    def maxVolts

    if(voltsmin == null || voltsmin == "")
    	minVolts = 2.5
    else
   	minVolts = voltsmin
    
    if(voltsmax == null || voltsmax == "")
    	maxVolts = 3.0
    else
	maxVolts = voltsmax
    
    def pct = (rawVolts - minVolts) / (maxVolts - minVolts)
    def roundedPct = Math.min(100, Math.round(pct * 100))

    def result = [
        name: 'battery',
        value: roundedPct,
        unit: "%",
        isStateChange: true,
        descriptionText : "${device.displayName} Battery at ${roundedPct}% (${rawVolts} Volts)"
    ]

    return result
}

// If the day of month has changed from that of previous event, reset the daily min/max temp values
def checkNewDay(now) {
	def oldDay = ((device.currentValue("currentDay")) == null) ? "32" : (device.currentValue("currentDay"))
	def newDay = new Date(now).format("dd")
	if (newDay != oldDay) {
		resetMinMax()
		sendEvent(name: "currentDay", value: newDay, displayed: false)
	}
}

// Reset daily min/max temp and humidity values to the current temp/humidity values
def resetMinMax() {
	def currentTemp = device.currentValue('temperature')
	def currentHumidity = device.currentValue('humidity')
    currentTemp = currentTemp ? (int) currentTemp : currentTemp
	log.debug "${device.displayName}: Resetting daily min/max values to current temperature of ${currentTemp}° and humidity of ${currentHumidity}%"
    sendEvent(name: "maxTemp", value: currentTemp, displayed: false)
    sendEvent(name: "minTemp", value: currentTemp, displayed: false)
    sendEvent(name: "maxHumidity", value: currentHumidity, displayed: false)
    sendEvent(name: "minHumidity", value: currentHumidity, displayed: false)
    refreshMultiAttributes()
}

// Check new min or max temp for the day
def updateMinMaxTemps(temp) {
	temp = temp ? (int) temp : temp
	if ((temp > device.currentValue('maxTemp')) || (device.currentValue('maxTemp') == null))
		sendEvent(name: "maxTemp", value: temp, displayed: false)	
	if ((temp < device.currentValue('minTemp')) || (device.currentValue('minTemp') == null))
		sendEvent(name: "minTemp", value: temp, displayed: false)
	refreshMultiAttributes()
}

// Check new min or max humidity for the day
def updateMinMaxHumidity(humidity) {
	if ((humidity > device.currentValue('maxHumidity')) || (device.currentValue('maxHumidity') == null))
		sendEvent(name: "maxHumidity", value: humidity, displayed: false)
	if ((humidity < device.currentValue('minHumidity')) || (device.currentValue('minHumidity') == null))
		sendEvent(name: "minHumidity", value: humidity, displayed: false)
	refreshMultiAttributes()
}

// Update display of multiattributes in main tile
def refreshMultiAttributes() {
	def temphiloAttributes = displayTempHighLow ? (displayHumidHighLow ? "Today's High/Low:  ${device.currentState('maxTemp')?.value}° / ${device.currentState('minTemp')?.value}°" : "Today's High: ${device.currentState('maxTemp')?.value}°  /  Low: ${device.currentState('minTemp')?.value}°") : ""
	def humidhiloAttributes = displayHumidHighLow ? (displayTempHighLow ? "    ${device.currentState('maxHumidity')?.value}% / ${device.currentState('minHumidity')?.value}%" : "Today's High: ${device.currentState('maxHumidity')?.value}%  /  Low: ${device.currentState('minHumidity')?.value}%") : ""
	sendEvent(name: "multiAttributesReport", value: "${temphiloAttributes}${humidhiloAttributes}", displayed: false)
}

//Reset the date displayed in Battery Changed tile to current date
def resetBatteryRuntime(paired) {
	def now = formatDate(true)
	def newlyPaired = paired ? " for newly paired sensor" : ""
	sendEvent(name: "batteryRuntime", value: now)
	log.debug "${device.displayName}: Setting Battery Changed to current date${newlyPaired}"
}

// installed() runs just after a sensor is paired using the "Add a Thing" method in the SmartThings mobile app
def installed() {
	state.battery = 0
	if (!batteryRuntime) resetBatteryRuntime(true)
	checkIntervalEvent("installed")
}

// configure() runs after installed() when a sensor is paired
def configure() {
	log.debug "${device.displayName}: configuring"
		state.battery = 0
	if (!batteryRuntime) resetBatteryRuntime(true)
	checkIntervalEvent("configured")
	return
}

// updated() will run twice every time user presses save in preference settings page
def updated() {
		checkIntervalEvent("updated")
		if(battReset) {
			resetBatteryRuntime()
			device.updateSetting("battReset", false)
		}
}

private checkIntervalEvent(text) {
    // Device wakes up every 1 hours, this interval allows us to miss one wakeup notification before marking offline
    log.debug "${device.displayName}: Configured health checkInterval when ${text}()"
    sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}

def formatDate(batteryReset) {
    def correctedTimezone = ""
    def timeString = clockformat ? "HH:mm:ss" : "h:mm:ss aa"

	// If user's hub timezone is not set, display error messages in log and events log, and set timezone to GMT to avoid errors
    if (!(location.timeZone)) {
        correctedTimezone = TimeZone.getTimeZone("GMT")
        log.error "${device.displayName}: Time Zone not set, so GMT was used. Please set up your location in the SmartThings mobile app."
        sendEvent(name: "error", value: "", descriptionText: "ERROR: Time Zone not set, so GMT was used. Please set up your location in the SmartThings mobile app.")
    } 
    else {
        correctedTimezone = location.timeZone
    }

    if (dateformat == "US" || dateformat == "" || dateformat == null) {
        if (batteryReset)
            return new Date().format("MMM dd yyyy", correctedTimezone)
        else
            return new Date().format("EEE MMM dd yyyy ${timeString}", correctedTimezone)
    }
    else if (dateformat == "UK") {
        if (batteryReset)
            return new Date().format("dd MMM yyyy", correctedTimezone)
        else
            return new Date().format("EEE dd MMM yyyy ${timeString}", correctedTimezone)
        }
    else {
        if (batteryReset)
            return new Date().format("yyyy MMM dd", correctedTimezone)
        else
            return new Date().format("EEE yyyy MMM dd ${timeString}", correctedTimezone)
    }
}

I get a malformed once I try to run this:

C:\Users\Nick\Documents\smartthings>cli-win.exe presentation:device-config:create --input=config.json

{
    "type": "dth",
    "iconUrl": null,
    "dashboard": {
        "states": [
            {
                "component": "main",
                "capability": "temperatureMeasurement",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            },
            {
                "component": "main",
                "capability": "relativeHumidityMeasurement",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            },
            {
                "component": "main",
                "capability": "battery",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            },
            {
                "component": "main",
                "capability": "sensor",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            },
            {
                "component": "main",
                "capability": "healthCheck",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            }
        ],
        "actions": [
            {
                "component": "main",
                "capability": "temperatureMeasurement",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            },
            {
                "component": "main",
                "capability": "relativeHumidityMeasurement",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            },
            {
                "component": "main",
                "capability": "battery",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            },
            {
                "component": "main",
                "capability": "sensor",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            },
            {
                "component": "main",
                "capability": "healthCheck",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            }
        ]
    },
    "detailView": [
        {
            "component": "main",
            "capability": "temperatureMeasurement",
            "version": 1,
            "values": [],
            "patch": [],
            "visibleCondition": null
        },
        {
            "component": "main",
            "capability": "relativeHumidityMeasurement",
            "version": 1,
            "values": [],
            "patch": [],
            "visibleCondition": null
        },
        {
            "component": "main",
            "capability": "battery",
            "version": 1,
            "values": [],
            "patch": [],
            "visibleCondition": null
        },
        {
            "component": "main",
            "capability": "sensor",
            "version": 1,
            "values": [],
            "patch": [],
            "visibleCondition": null
        },
        {
            "component": "main",
            "capability": "healthCheck",
            "version": 1,
            "values": [],
            "patch": [],
            "visibleCondition": null
        }
    ],
    "automation": {
        "conditions": [
            {
                "component": "main",
                "capability": "temperatureMeasurement",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            },
            {
                "component": "main",
                "capability": "relativeHumidityMeasurement",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            },
            {
                "component": "main",
                "capability": "battery",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            },
            {
                "component": "main",
                "capability": "sensor",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            },
            {
                "component": "main",
                "capability": "healthCheck",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            }
        ],
        "actions": [
            {
                "component": "main",
                "capability": "temperatureMeasurement",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            },
            {
                "component": "main",
                "capability": "relativeHumidityMeasurement",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            },
            {
                "component": "main",
                "capability": "battery",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            },
            {
                "component": "main",
                "capability": "sensor",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            },
            {
                "component": "main",
                "capability": "healthCheck",
                "version": 1,
                "values": [],
                "patch": [],
                "visibleCondition": null
            }
        ]
    },
    "presentationId": "8da27758-2f10-3ddd-b882-a5e630b3bc86",
    "manufacturerName": "SmartThingsCommunity",
    "vid": "8da27758-2f10-3ddd-b882-a5e630b3bc86",
    "mnmn": "SmartThingsCommunity"
}

Something tells me that the cli is no good either…

I have seen it suggested that the two lines …

"presentationId": "8da27758-2f10-3ddd-b882-a5e630b3bc86",
"manufacturerName": "SmartThingsCommunity",

… are the problem. They would appear to be new terms being introduced for ‘vid’ and ‘mnmn’ but although the API generated them, it doesn’t yet seem able to consume them. Someone should really be on the naughty step for that one.

So for the time being we cannot do much, but wait, right?

You should be able to edit out the two offending lines.

It doesn’t seem to do the trick:

C:\Users\Nick\Documents\smartthings>cli-win.exe presentation:device-config:create --input=config2.json
[2020-09-18T11:59:04.397] [ERROR] cli - caught error Error: Request failed with status code 400: {“requestId”:“62985FF5-EF5C-4C64-91FA-E82C4BCE08D0”,“error”:{“code”:“BadRequestError”,“target”:null,“message”:“The request is malformed.”,“details”:}}

For reference the json:

{
"type": "dth",
"iconUrl": null,
"dashboard": {
    "states": [
        {
            "component": "main",
            "capability": "temperatureMeasurement",
            "version": 1,
            "values": [],
            "patch": [],
            "visibleCondition": null
        }
    ]
},
"detailView": [
    {
        "component": "main",
        "capability": "temperatureMeasurement",
        "version": 1,
        "values": [],
        "patch": [],
        "visibleCondition": null
    },
    {
        "component": "main",
        "capability": "relativeHumidityMeasurement",
        "version": 1,
        "values": [],
        "patch": [],
        "visibleCondition": null
    },
    {
        "component": "main",
        "capability": "battery",
        "version": 1,
        "values": [],
        "patch": [],
        "visibleCondition": null
    },
    {
        "component": "main",
        "capability": "sensor",
        "version": 1,
        "values": [],
        "patch": [],
        "visibleCondition": null
    },
    {
        "component": "main",
        "capability": "healthCheck",
        "version": 1,
        "values": [],
        "patch": [],
        "visibleCondition": null
    }
],
"automation": {
    "conditions": [
        {
            "component": "main",
            "capability": "temperatureMeasurement",
            "version": 1,
            "values": [],
            "patch": [],
            "visibleCondition": null
        },
        {
            "component": "main",
            "capability": "relativeHumidityMeasurement",
            "version": 1,
            "values": [],
            "patch": [],
            "visibleCondition": null
        },
        {
            "component": "main",
            "capability": "battery",
            "version": 1,
            "values": [],
            "patch": [],
            "visibleCondition": null
        }
    ],
    "actions": [
        {
            "component": "main",
            "capability": "temperatureMeasurement",
            "version": 1,
            "values": [],
            "patch": [],
            "visibleCondition": null
        },
        {
            "component": "main",
            "capability": "relativeHumidityMeasurement",
            "version": 1,
            "values": [],
            "patch": [],
            "visibleCondition": null
        },
        {
            "component": "main",
            "capability": "battery",
            "version": 1,
            "values": [],
            "patch": [],
            "visibleCondition": null
        }
    ]
},
"manufacturerName": "SmartThingsCommunity",
"vid": "8da27758-2f10-3ddd-b882-a5e630b3bc86"
}

It appears the cli works if I keep the:
“vid”: “8da27758-2f10-3ddd-b882-a5e630b3bc86”,
“mnmn”: “SmartThingsCommunity”

cli-win.exe presentation:device-config:create --input=config3.json

Yep, I think I could have phrased it better. Those are the current working terms (vid standing for ‘Vendor Id’ originally, and my best guess for mnmn being ‘Mah Nà Mah Nà’ ) with the other two being the new ones that suddenly appeared.

1 Like

I am ok with Mah Nà Mah Nà! lol

Done! It now shows correctly! Awesome!

I took from my IDE the device handler ID (you can change it, let’s call it for now handler vid, zzz vid) and change it below.

{
“type”: “dth”,
“iconUrl”: null,
“dashboard”: {
“states”: [
{
“component”: “main”,
“capability”: “temperatureMeasurement”,
“version”: 1,
“values”: ,
“patch”: ,
“visibleCondition”: null
}
]
},
“detailView”: [
{
“component”: “main”,
“capability”: “temperatureMeasurement”,
“version”: 1,
“values”: ,
“patch”: ,
“visibleCondition”: null
},
{
“component”: “main”,
“capability”: “relativeHumidityMeasurement”,
“version”: 1,
“values”: ,
“patch”: ,
“visibleCondition”: null
},
{
“component”: “main”,
“capability”: “battery”,
“version”: 1,
“values”: ,
“patch”: ,
“visibleCondition”: null
},
{
“component”: “main”,
“capability”: “sensor”,
“version”: 1,
“values”: ,
“patch”: ,
“visibleCondition”: null
},
{
“component”: “main”,
“capability”: “healthCheck”,
“version”: 1,
“values”: ,
“patch”: ,
“visibleCondition”: null
}
],
“automation”: {
“conditions”: [
{
“component”: “main”,
“capability”: “temperatureMeasurement”,
“version”: 1,
“values”: ,
“patch”: ,
“visibleCondition”: null
},
{
“component”: “main”,
“capability”: “relativeHumidityMeasurement”,
“version”: 1,
“values”: ,
“patch”: ,
“visibleCondition”: null
},
{
“component”: “main”,
“capability”: “battery”,
“version”: 1,
“values”: ,
“patch”: ,
“visibleCondition”: null
}
],
“actions”: [
{
“component”: “main”,
“capability”: “temperatureMeasurement”,
“version”: 1,
“values”: ,
“patch”: ,
“visibleCondition”: null
},
{
“component”: “main”,
“capability”: “relativeHumidityMeasurement”,
“version”: 1,
“values”: ,
“patch”: ,
“visibleCondition”: null
},
{
“component”: “main”,
“capability”: “battery”,
“version”: 1,
“values”: ,
“patch”: ,
“visibleCondition”: null
}
]
},
“vid”: “zzz”,
“mnmn”: “SmartThingsCommunity”
}

Change the zzz vid above in the json file with your own handler ID. (you can pick this up from the IDE → device handler → open ui and take it from the url, it should something like “3ce46925-p9iy-o087-a6a7-00bb6020be99”
Then you need to save it as config.json and download the cli from Releases · SmartThingsCommunity/smartthings-cli · GitHub.

Then, once you unzip it, just run the command

cli-win.exe presentation:device-config:create --input=config.json

It will create the json file and at the end of the command you will see the new vid. Copy that one (it should be different to the zzz vid above in the json, but still a vid, let’s call it a presentation vid, i.e. yyy vid).

Then go back again on the IDE and change your handler as such:
definition (name: “Xiaomi Aqara Temperature Humidity Sensor”, author: “bspranger”, namespace: “bspranger”, mnmn:“SmartThingsCommunity”, vid:“yyy”)
Put your presentation vid instead of yyy.

Alrighty then! Next figure out how to add this to device settings in App so user can select which value to show in main tile. Then update the App (or all device handlers or whatever) to include this. After that we have stepped a tiny bit towards more user friendly and usable system :wink: