Thermostat parameters for Alexa

Good morning
Foreword, I am new to developing Scripts for Hubitat .
I modified a thermostat script of Eliot S. to suit my needs.
The Device works in Hubitat correctly.
My problem is that Alexa does not read the temperature of the thermostat,
It can change the setpoint, if I ask for the status it can tell me if it is in heating or cooling or off, but it says the temperature is not accessible from the thermostat object.

Can this be a problem with the drive?
How do I decide which parameters are readable by Alexa or external devices?

It follows the code and my settings.
Thank you

App Device:

definition(
        name: "Virtual Thermostat With Device",
        namespace: "piratemedia",
        author: "Eliot S.",
        description: "Control a heater or cooler in conjunction with any temperature sensor, like a SmartSense Multi.",
        category: "Green Living",
        iconUrl: "https://raw.githubusercontent.com/eliotstocker/SmartThings-VirtualThermostat-WithDTH/master/logo-small.png",
        iconX2Url: "https://raw.githubusercontent.com/eliotstocker/SmartThings-VirtualThermostat-WithDTH/master/logo.png",
        parent: "piratemedia:Virtual Thermostat Manager",
)

preferences {
    section("Choose a temperature sensor(s)... (If multiple sensors are selected, the average value will be used)"){
        input "sensors", "capability.temperatureMeasurement", title: "Sensor", multiple: true
    }
    section("Select the heater outlet(s)... "){
        input "heaterOutlets", "capability.switch", title: "Heater Outlets", multiple: true
    }
    section("Select the cooler outlet(s)... "){
        input "coolerOutlets", "capability.switch", title: "Cooler Outlets", multiple: true
    }
    section("Only heat or cool when contact(s) aren't open (optional, leave blank to not require contact sensor)..."){
        input "motion", "capability.contactSensor", title: "Contact", required: false, multiple: true
    }
    section("Never go below this temperature: (optional)"){
        input "emergencySetpoint", "decimal", title: "Emergency Temp", required: false
    }
    section("Temperature Threshold (Don't allow heating or cooling to go above or below this amount from set temperature)") {
        input "threshold", "decimal", title: "Temperature Threshold", required: false, defaultValue: 1.0
    }
}

def installed()
{
    state.deviceID = Math.abs(new Random().nextInt() % 9999) + 1
}

def createDevice() {
    def thermostat
    def label = app.getLabel()

    log.debug "create device with id: pmvt$state.deviceID, named: $label" //, hub: $sensor.hub.id"
    try {
        thermostat = addChildDevice("piratemedia", "Virtual Thermostat Device", "pmvt" + state.deviceID, null, [label: label, name: label, completedSetup: true])
    } catch(e) {
        log.error("caught exception", e)
    }
    return thermostat
}

def shouldHeatingBeOn(thermostat) {
    if (emergencySetpoint && emergencySetpoint > getAverageTemperature()) {
        return true;
    }

    if (thermostat.currentValue('thermostatMode') != "heat") {
        return false;
    }

    if (motion) {
        for (m in motion) {
            if (m.currentValue('contact') == "open") {
                return false;
            }
        }
    }

    if (thermostat.currentValue("heatingSetpoint") - getAverageTemperature() <= threshold) {
        return false;
    }

    return true;
}

def shouldCoolingBeOn(thermostat) {
    if (thermostat.currentValue('thermostatMode') != "cool") {
        return false;
    }

    if (motion) {
        for (m in motion) {
            if (m.currentValue('contact') == "open") {
                return false;
            }
        }
    }

    if (getAverageTemperature() - thermostat.currentValue("coolingSetpoint") <= threshold) {
        return false;
    }

    return true;
}

def getHeatingStatus(thermostat) {
    if (emergencySetpoint && emergencySetpoint > getAverageTemperature()) {
        return 'heating';
    }

    if (thermostat.currentValue('thermostatMode') != "heat") {
        return 'idle';
    }

    if (motion) {
        for (m in motion) {
            if (m.currentValue('contact') == "open") {
                return 'pending heat';
            }
        }
    }

    if (thermostat.currentValue("heatingSetpoint") - getAverageTemperature() <= threshold) {
        return 'idle';
    }

    return 'heating';
}

def getCoolingStatus(thermostat) {
    if (thermostat.currentValue('thermostatMode') != "cool") {
        return 'idle';
    }

    if (motion) {
        for (m in motion) {
            if (m.currentValue('contact') == "open") {
                return 'pending cool';
            }
        }
    }

    if (getAverageTemperature() - thermostat.currentValue("coolingSetpoint") <= threshold) {
        return 'idle';
    }

    return 'cooling';
}

def getAverageTemperature() {
    def total = 0;
    def count = 0;

    for(sensor in sensors) {
        total += sensor.currentValue("temperature")
        count++
    }

    return total / count;
}

def handleChange() {
    def thermostat = getThermostat()

    thermostat.setHeatingStatus(getHeatingStatus(thermostat))
    thermostat.setCoolingStatus(getCoolingStatus(thermostat))
    thermostat.setVirtualTemperature(getAverageTemperature())

    if (shouldHeatingBeOn(thermostat)) {
        heaterOutlets.on()
    } else {
        heaterOutlets.off()
    }

    if (shouldCoolingBeOn(thermostat)) {
        coolerOutlets.on()
    } else {
        coolerOutlets.off()
    }
}

def getThermostat() {
    def child = getChildDevices().find {
        d -> d.deviceNetworkId.startsWith("pmvt" + state.deviceID)
    }
    return child;
}

def uninstalled() {
    deleteChildDevice("pmvt" + state.deviceID);
}

def updated() {
    log.debug "running updated: $app.label"
    unsubscribe()
    unschedule()

    def thermostat = getThermostat()
    if(thermostat == null) {
        thermostat = createDevice()
    }

    subscribe(sensors, "temperature", temperatureHandler)

    if (motion) {
        subscribe(motion, "contact", motionHandler)
    }

    subscribe(thermostat, "thermostatSetpoint", thermostatTemperatureHandler)
    subscribe(thermostat, "thermostatMode", thermostatModeHandler)

    thermostat.setVirtualTemperature(getAverageTemperature())
}

def temperatureHandler(evt) {
    handleChange();
}

def motionHandler(evt) {
    handleChange();
}

def thermostatTemperatureHandler(evt) {
    handleChange();
}

def thermostatModeHandler(evt) {
    handleChange();
}

App manager:

definition(
        name: "Virtual Thermostat Manager",
        namespace: "piratemedia",
        author: "Eliot S.",
        description: "Control a heater in conjunction with any temperature sensor like a SmartSense Multi, to create a thermostat device in SmartThings",
        category: "Green Living",
        iconUrl: "https://raw.githubusercontent.com/eliotstocker/SmartThings-VirtualThermostat-WithDTH/master/logo-small.png",
        iconX2Url: "https://raw.githubusercontent.com/eliotstocker/SmartThings-VirtualThermostat-WithDTH/master/logo.png",
        singleInstance: true
)

preferences {
    page(name: "Install", title: "Thermostat Manager", install: true, uninstall: true) {
        section("Devices") {
        }
        section {
            app(name: "thermostats", appName: "Virtual Thermostat With Device", namespace: "piratemedia", title: "New Thermostat", multiple: true)
        }
    }
}

def installed() {
    initialize()
}

def updated() {
    unsubscribe()
    initialize()
}

def initialize() {
}

Driver:

metadata {
    definition (name: "Virtual Thermostat Device",
            namespace: "piratemedia",
            author: "Eliot S.") {
        capability "Thermostat"
        capability "Thermostat Heating Setpoint"
        capability "Thermostat Cooling Setpoint"
        capability "Thermostat Setpoint"
        capability "Sensor"
        capability "Actuator"
        
        command "refresh"

        command "setVirtualTemperature", ["number"]
        command "setHeatingStatus", ["string"]
        command "setCoolingStatus", ["string"]

        attribute "temperatureUnit", "string"
    }
}

def shouldReportInCentigrade() {
    try {
        def ts = getTemperatureScale();
        return ts == "C"
    } catch (e) {
        log.error e
    }
    return true;
}

def installed() {
    initialize()
}

def configure() {
    initialize()
}

private initialize() {
    setHeatingSetpoint(defaultTemp())
    setCoolingSetpoint(defaultTemp())
    setVirtualTemperature(defaultTemp())
    setHeatingStatus("off")
    setCoolingStatus("off")
    setThermostatMode("off")
    sendEvent(name:"supportedThermostatModes", value: ['heat', 'cool', 'off'], displayed: false)
    sendEvent(name:"supportedThermostatFanModes", values: [], displayed: false)

    state.tempScale = "C"
}

def getTempColors() {
    def colorMap
    if(shouldReportInCentigrade()) {
        colorMap = [
                [value: 0, color: "#153591"],
                [value: 7, color: "#1e9cbb"],
                [value: 15, color: "#90d2a7"],
                [value: 23, color: "#44b621"],
                [value: 29, color: "#f1d801"],
                [value: 33, color: "#d04e00"],
                [value: 36, color: "#bc2323"]
        ]
    } else {
        colorMap = [
                [value: 40, color: "#153591"],
                [value: 44, color: "#1e9cbb"],
                [value: 59, color: "#90d2a7"],
                [value: 74, color: "#44b621"],
                [value: 84, color: "#f1d801"],
                [value: 92, color: "#d04e00"],
                [value: 96, color: "#bc2323"]
        ]
    }
}

def unitString() { return shouldReportInCentigrade() ? "C" : "F" }
def defaultTemp() { return shouldReportInCentigrade() ? 20 : 70 }
def lowRange() { return shouldReportInCentigrade() ? 9 : 45 }
def highRange() { return shouldReportInCentigrade() ? 45 : 113 }
def getRange() { return "${lowRange()}..${highRange()}" }

def getTemperature() {
    return device.currentValue("temperature")
}

def setHeatingSetpoint(temp) {
    def ctsp = device.currentValue("thermostatSetpoint")
    def chsp = device.currentValue("heatingSetpoint")

    if(ctsp != temp || chsp != temp) {
        sendEvent(name:"thermostatSetpoint", value: temp, unit: unitString(), displayed: false)
        sendEvent(name:"heatingSetpoint", value: temp, unit: unitString())
    }
}

def setCoolingSetpoint(temp) {
    def ctsp = device.currentValue("thermostatSetpoint")
    def ccsp = device.currentValue("coolingSetpoint")

    if(ctsp != temp || ccsp != temp) {
        sendEvent(name:"thermostatSetpoint", value: temp, unit: unitString(), displayed: false)
        sendEvent(name:"coolingSetpoint", value: temp, unit: unitString())
    }
}

def parse(data) {
    log.debug "parse data: $data"
}

def refresh() {
    log.trace "Executing refresh"
    sendEvent(name: "supportedThermostatModes", value: ['heat', 'cool', 'off'], displayed: false)
    sendEvent(name: "supportedThermostatFanModes", values: [], displayed: false)
}

def getThermostatMode() {
    return device.currentValue("thermostatMode")
}

def getOperatingState() {
    return device.currentValue("thermostatOperatingState")
}

def getThermostatSetpoint() {
    return device.currentValue("thermostatSetpoint")
}

def getHeatingSetpoint() {
    return device.currentValue("heatingSetpoint")
}

def getCoolingSetpoint() {
    return device.currentValue("coolingSetpoint")
}

def setThermostatMode(mode) {
    if(device.currentValue("thermostatMode") != mode) {
        sendEvent(name: "thermostatMode", value: mode)
    }
}

def changeMode() {
    def currentMode = device.currentValue("thermostatMode")
    def newMode = (currentMode == "off") ? "heat" : (currentMode == "heat") ? "cool" : "off"
    setThermostatMode(newMode)
    return newMode
}

def setVirtualTemperature(temp) {
    sendEvent(name:"temperature", value: temp, unit: unitString(), displayed: true)
}

def setHeatingStatus(string) {
    if(device.currentValue("thermostatOperatingState") != string) {
        sendEvent(name:"thermostatOperatingState", value: string)
    }
}

def setCoolingStatus(string) {
    if(device.currentValue("thermostatOperatingState") != string) {
        sendEvent(name:"thermostatOperatingState", value: string)
    }
}

Can you edit each message and use the Preformatted Text button, please?

Select all the code, then click the </> button. That will format the code to be readable. It's likely that you'll want to delete the code in the message and go back to the original to re-paste in order to get indents restored.

2 Likes

Thanks for the suggestion

I think you are missing lrM... Last Running Mode. I think that's the attribute Alexa is looking for...

// Update lastRunningMode based on mode and operatingstate
def lrM(mode) {
	String lrm = getDataValue("lastRunningMode")
	if (mode.contains("auto") || mode.contains("off") && lrm != "heat") { updateDataValue("lastRunningMode", "heat") }
	 else { updateDataValue("lastRunningMode", mode) }
}

but it's been years since I encountered that issue and I may be confusing this with a that. :slight_smile:

1 Like

works correctly.
Thanks for the tip

I will test it for a bit and maybe i will publish it somewere.
maybe on github but im not a big expert on it.