Thermostat modes not available in rule machine

It looks like maybe your driver came from here?

That driver is missing the thermostatOperatingState attribute. It is trying to send an enum event for data.on.

image

On their local API doc, that looks like a 0 or 1 binary value, for operating or not.
image

I changed it to this:


and added the attribute:
image

If you want to try it out.

metadata {
    definition(name: "Airzone Aidoo Pro (Local API)", namespace: "my hubitat", author: "AP'") {
        capability "TemperatureMeasurement"
        capability "RelativeHumidityMeasurement"
        capability "Thermostat"
        capability "Refresh"
        capability "Polling"

        attribute "connectivity", "string"
        attribute "ecoMode", "string"
        attribute "ecoCoolPoint", "number"
        attribute "ecoHeatPoint", "number"
        attribute "roomTemp", "number"
        attribute "zoneID", "number"
        attribute "systemID", "number"
        attribute "modeText", "string"
        attribute "fanModeText", "string"
        attribute "supportedThermostatModes", "JSON_OBJECT"
        attribute "supportedThermostatFanModes", "JSON_OBJECT"
        attribute "thermostatOperatingState", "ENUM"

        command "setThermostatMode", [[name:"Thermostat mode*", type:"ENUM", constraints:["off","heat","cool","auto"]]]
        command "setThermostatFanMode", [[name:"Fan mode*", type:"ENUM", constraints:["auto","on"]]]
        command "setHeatingSetpoint", [[name:"Heating setpoint*", type:"NUMBER", description:"Set Heating Point (°C)"]]
        command "setCoolingSetpoint", [[name:"Cooling setpoint*", type:"NUMBER", description:"Set Cooling Point (°C)"]]
        command "refresh"
    }
    preferences {
        input("ip", "string", title:"Aidoo IP Address", description:"Device local IP, e.g. 192.168.1.73", required: true)
        input("systemID", "number", title:"System ID", description:"Usually 1", defaultValue:1, required: true)
        input("zoneID", "number", title:"Zone ID", description:"Usually 1", defaultValue:1, required: true)
        input("pollInterval", "number", title:"Polling interval (mins)", defaultValue:2, required: false)
    }
}

def installed() { initialize() }
def updated() { unschedule(); initialize() }
def initialize() {
    state.lastPoll = now()
    if (settings.pollInterval) runEveryNMinutes("refresh", settings.pollInterval as int)
    refresh()
}

def setThermostatAttributes() {
	sendEvent(name: "thermostatOperatingState", value: "off")
	sendEvent(name: "supportedThermostatFanModes", value: '["low","medium","high","auto","disable"]')
	sendEvent(name: "supportedThermostatModes", value: '["heat","cool","idle"]')
	sendEvent(name: "thermostatFanMode", value: "auto")
	sendEvent(name: "thermostatMode", value: "idle")
}

// Main polling function - pulls latest zone/system state
def refresh() {
    def apiUrl = "http://${settings.ip}:3000/api/v1/hvac"
    def body = [systemID: settings.systemID, zoneID: settings.zoneID]
    def params = [
        uri: apiUrl,
        contentType: "application/json",
        body: groovy.json.JsonOutput.toJson(body),
        timeout: 5
    ]
    try {
        httpPost(params) { resp ->
            if(resp.status == 200 && resp.data?.data) {
                parseApiResponse(resp.data.data[0])
                sendEvent(name: "connectivity", value: "ONLINE")
            } else {
                sendEvent(name: "connectivity", value: "OFFLINE")
            }
        }
    } catch (e) {
        log.warn "Airzone API error: $e"
        sendEvent(name: "connectivity", value: "OFFLINE")
    }
}

def parseApiResponse(data) {
    // Standard states
    sendEvent(name: "temperature", value: data.roomTemp ?: data.setpoint)
    sendEvent(name: "humidity", value: data.humidity ?: 0)
    sendEvent(name: "heatingSetpoint", value: data.heatsetpoint ?: 0)
    sendEvent(name: "coolingSetpoint", value: data.coolsetpoint ?: 0)
    sendEvent(name: "thermostatMode", value: modeMap(data.mode))
    if (data.on == 1) sendEvent(name: "thermostatOperatingState", value: "heating")
    if (data.on == 0) sendEvent(name: "thermostatOperatingState", value: "idle")
    //sendEvent(name: "thermostatOperatingState", value: data.on ? "heating" : "idle")
    sendEvent(name: "thermostatFanMode", value: fanModeMap(data.speed))

    // Update available modes for drop-downs (always in sync)
    sendEvent(name: "supportedThermostatModes", value: groovy.json.JsonOutput.toJson(["off","heat","cool","auto"]))
    sendEvent(name: "supportedThermostatFanModes", value: groovy.json.JsonOutput.toJson(["auto","on"]))

    // Custom states
    sendEvent(name: "ecoMode", value: data.eco_mode ? "ON" : "OFF")
    sendEvent(name: "ecoCoolPoint", value: data.ecoCoolPoint)
    sendEvent(name: "ecoHeatPoint", value: data.ecoHeatPoint)
    sendEvent(name: "roomTemp", value: data.roomTemp)
    sendEvent(name: "zoneID", value: data.zoneID)
    sendEvent(name: "systemID", value: data.systemID)
    sendEvent(name: "modeText", value: modeMap(data.mode))
    sendEvent(name: "fanModeText", value: fanModeMap(data.speed))
}

def modeMap(n) {
    switch(n as int) {
        case 1: return "heat"
        case 2: return "cool"
        case 3: return "auto"
        case 4: return "off"
        default: return "off"
    }
}
def fanModeMap(n) {
    switch(n as int) {
        case 0: return "auto"
        case 1: return "on"
        default: return "auto"
    }
}

// Set Thermostat Mode
def setThermostatMode(String mode) {
    def modeVal = modeStrToApi(mode)
    sendEvent(name: "thermostatMode", value: mode)   // <-- UI stays in sync immediately
    sendPut([systemID: settings.systemID, zoneID: settings.zoneID, mode: modeVal])
}
def setThermostatFanMode(String fanMode) {
    def speedVal = (fanMode == "on") ? 1 : 0
    sendEvent(name: "thermostatFanMode", value: fanMode)
    sendPut([systemID: settings.systemID, zoneID: settings.zoneID, speed: speedVal])
}
def setHeatingSetpoint(setpoint) {
    sendPut([systemID: settings.systemID, zoneID: settings.zoneID, heatsetpoint: setpoint as int])
}
def setCoolingSetpoint(setpoint) {
    sendPut([systemID: settings.systemID, zoneID: settings.zoneID, coolsetpoint: setpoint as int])
}
def sendPut(bodyMap) {
    def apiUrl = "http://${settings.ip}:3000/api/v1/hvac"
    def params = [
        uri: apiUrl,
        contentType: "application/json",
        body: groovy.json.JsonOutput.toJson(bodyMap),
        timeout: 5
    ]
    try {
        httpPut(params) { resp ->
            if(resp.status == 200) {
                log.info "Set succeeded: ${resp.data}"
                refresh()
            } else {
                log.warn "Set failed: ${resp.data}"
            }
        }
    } catch (e) {
        log.warn "Put error: $e"
    }
}
def modeStrToApi(mode) {
    switch(mode) {
        case "heat": return 1
        case "cool": return 2
        case "auto": return 3
        case "off": return 0
        default: return 0
    }
}
1 Like