App and driver porting to Hubitat

That Virtual Air Conditoner Device Type Handler from SmartThings appears to have been designed for the ‘New’ ST App. Porting that DTH a to Hubitat may present some challenges compared to more traditional ST DTHs.

What not just use the Hubitat Virtual Thermostat, which is built-in to the Hubitat platform?

2 Likes

I'm trying, but here in Brazil we don't usually use a thermostat, we only use the air conditioning control manually, without this automatic temperature adjustment.
I just need a virtual air conditioning control.
I'm trying anyway to use the thermostat but it's not working properly, which is making me very frustrated.

Can you please explain more? I am curious what exactly you’re looking for in a Hubitat Device Driver.

As you mentioned, a Thermostat is designed to maintain temperature to a target by turning on/off either a cooling or heating device - in other words, a form of automatic control. Of course, a thermostat can simply be set to OFF to prevent the automatic control from occurring. However, this does not allow for manual control of the cooling or heating device.

So...I am trying to understand what exactly you’d like in a driver... Since you mentioned air conditioning, I assume you have some sort of air conditioning unit, correct? Does it have a built-in thermostat? How do you plan on having Hubitat interface with the air conditioner? You mentioned above that you don’t want thermostatic control from Hubitat...so do you simply want manual on/off control?

If you can describe in detail the requirements, the community may be able to come up with a simple solution.

2 Likes

Thank you very much for the response and consideration. The air conditioner already has a thermostat inside it, so I Send the HVAC IR remote control code like this
IrSend {"Protocol":"COOLIX","Bits":24,"Data":"0xB2BF00","DataLSB":"0x4DFD00","Repeat":0,"IRHVAC":{"Vendor":"COOLIX","Model":-1,"Power":"On","Mode":"Cool","Celsius":"On","Temp":17,"FanSpeed":"Auto","SwingV”:”Off,”SwingH":"Off","Quiet":"Off","Turbo":"Off","Econo":"Off","Light":"Off","Filter":"Off","Clean":"Off","Beep":"Off","Sleep":-1}}

I am thinking in some way to incorporate the other possibilities of the the native hubitat driver, I believe that some difficulties that I'm having is due to my knowledge being limited in programming.

These are the possibilities that we need in a virtual air conditioning control

"Power" :

  • On, Yes, True, 1
  • Off, No, False, 0

"Mode" :

  • Off, Stop
  • Auto, Automatic
  • Cool, Cooling
  • Heat, Heating
  • Dry, Drying, Dehumidify
  • Fan, Fanonly, Fan_Only

"FanSpeed" :

  • Auto, Automatic
  • Min, Minimum, Lowest, 1
  • Low, 2
  • Med, Medium, Mid, 3
  • High, Hi, 4
  • Max, Maximum, Highest, 5

"SwingV" : vertical swing of Fan

  • Auto, Automatic, On, Swing
  • Off, Stop
  • Min, Minimum, Lowest, Bottom, Down
  • Low
  • Mid, Middle, Med, Medium, Centre, Center
  • High, Hi
  • Highest, Max, Maximum, Top, Up

"SwingH" : horizontal swing of Fan

  • Auto, Automatic, On, Swing
  • Off, Stop
  • LeftMax, Left Max, MaxLeft, Max Left, FarLeft, Far Left
  • Left
  • Mid, Middle, Med, Medium, Centre, Center
  • Right
  • RightMax, Right Max, MaxRight, Max Right, FarRight, Far Right
  • Wide

"Celsius" : temperature is in Celsius ( "On" ) of Farenheit ( "Off" )
"Temp" : Temperature, can be float if supported by protocol
"Quiet" : Quiet mode ( "On" / "Off" )
"Turbo" : Turbo mode ( "On" / "Off" )
"Econo" : Econo mode ( "On" / "Off" )
"Light" : Light ( "On" / "Off" )
"Filter" : Filter active ( "On" / "Off" )
"Clean" : Clean mode ( "On" / "Off" )
"Beep" : Beep active ( "On" / "Off" )
"Sleep" : Timer in seconds


What I need is to make a virtual driver that has these possibilities.
If I can do this I can properly control my air conditioner

Wow! That’s not exactly a trivial driver. Now I see why you’re trying to port the code from ST. I am still not clear exactly how a virtual air conditioner driver would ever end up sending Infrared Commands to your air conditioner. What device is sending the IR commands?

1 Like

I'm using Tasmota on Tuya IR bridge model UFO - R1
If I could get a virtual driver with these possibilities I can send the information via WebCoRE to UFO-R1

Isn’t there already community support for Tasmota? I’d recommend posting your questions in the Tasmota thread.

1 Like

Because in these commands I have everything that I need to guide the webcore piston

"Power" :

  • On
  • Off

"Mode" :

  • Off
  • Auto
  • Cool
  • Heat
  • Dry
  • Fan

"FanSpeed" :

  • Auto
  • Min
  • Low
  • Med
  • High
  • Max

"SwingV" : vertical swing of Fan

  • Auto
  • Off
  • Minn
  • Low
  • Mid
  • High
  • Highest

"SwingH" : horizontal swing of Fan

  • Auto
  • Off
  • LeftMax
  • Left
  • Mid
  • Right
  • RightMax
  • Wide

"Celsius" : temperature is in Celsius ( "On" ) of Farenheit ( "Off" )
"Temp" : Temperature, can be float if supported by protocol
"Quiet" : Quiet mode ( "On" / "Off" )
"Turbo" : Turbo mode ( "On" / "Off" )
"Econo" : Econo mode ( "On" / "Off" )
"Light" : Light ( "On" / "Off" )
"Filter" : Filter active ( "On" / "Off" )
"Clean" : Clean mode ( "On" / "Off" )
"Beep" : Beep active ( "On" / "Off" )
"Sleep" : Timer in seconds

I've already done that, but in reality what I want doesn't depend on tasmota.
I just want a virtual control that has these possibilities. The rest I do through webcore

OK, here is the original SmartThings DTH that you linked earlier in this thread which I have made some very basic changes to in order for it to work with Hubitat. I have no way of testing this, but it does compile. This appears to be a Child Driver, which will require a Parent in order to work properly.

/**
 *  Tasmota - Virtual Air Conditioner
 *
 *  Copyright 2020 AwfullySmart.com - HongTat Tan
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import groovy.json.JsonOutput
String driverVersion() { return "20200913" }
metadata {
    definition (name: "Tasmota Virtual Air Conditioner", namespace: "hongtat", author: "HongTat Tan") {
        capability "Actuator"
        capability "Temperature Measurement"
        capability "Thermostat Cooling Setpoint"
        capability "Thermostat Mode"
        //capability "voicehouse43588.airConditionerFanSpeed"
        //capability "voicehouse43588.airConditionerVerticalSwing"
        //capability "voicehouse43588.airConditionerHorizontalSwing"
        capability "Switch"

        //capability "Health Check"
        capability "Configuration"
        capability "Refresh"
        capability "Sensor"

        attribute "lastSeen", "string"
        attribute "airConditionerFanSpeed", "string"
        attribute "airConditionerVerticalSwing", "string"
        attribute "airConditionerHorizontalSwing", "string"
    }

    preferences {
        section {
            input(title: "Device Settings",
                    description: "To view/update this settings, go to the Tasmota (Connect) SmartApp and select this device.",
                    displayDuringSetup: false,
                    type: "paragraph",
                    element: "paragraph")
            input(title: "", description: "Tasmota Virtual Air Conditioner v${driverVersion()}", displayDuringSetup: false, type: "paragraph", element: "paragraph")
        }
    }

    /*
    tiles(scale: 2) {
        multiAttributeTile(name:"thermostat", type:"general", width:6, height:4, canChangeIcon: false)  {
            tileAttribute("device.switch", key: "PRIMARY_CONTROL") {
                attributeState("off", label: "Off", action: "switch.on", icon: "st.thermostat.heating-cooling-off", backgroundColor: "#ffffff")
                attributeState("on", label: "On", action: "switch.off", backgroundColor: "#00a0dc")
            }
            tileAttribute("device.temperature", key: "SECONDARY_CONTROL") {
                attributeState("temperature", label:'${currentValue}°', icon: "st.alarm.temperature.normal")
            }
            tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
                attributeState("default", label: '${currentValue}', unit: "°", defaultState: true)
            }
        }
        controlTile("coolingSetpoint", "device.coolingSetpoint", "slider",
                sliderType: "COOLING",
                debouncePeriod: 750,
                range: "device.coolingSetpointRange",
                width: 2, height: 2) {
            state "default", action:"setCoolingSetpoint", label:'${currentValue}', backgroundColor: "#55D4ED"
        }
        controlTile("thermostatMode", "device.thermostatMode", "enum", width: 2 , height: 2, supportedStates: "device.supportedThermostatModes") {
            state("off", action: "setThermostatMode", label: 'Off', icon: "st.thermostat.heating-cooling-off")
            state("cool", action: "setThermostatMode", label: 'Cool', icon: "st.thermostat.cool")
            state("heat", action: "setThermostatMode", label: 'Heat', icon: "st.thermostat.heat")
            state("auto", action: "setThermostatMode", label: 'Auto', icon: "st.tesla.tesla-hvac")
        }
        controlTile("fanSpeed", "device.fanSpeed", "enum", width: 2 , height: 2, supportedStates: "device.supportedFanSpeed") {
            state("auto", action: "setFanSpeed", label: 'Auto', icon: "st.thermostat.fan-on")
            state("1", action: "setFanSpeed", label: 'Min', icon: "st.thermostat.fan-on")
            state("2", action: "setFanSpeed", label: 'Low', icon: "st.thermostat.fan-on")
            state("3", action: "setFanSpeed", label: 'Medium', icon: "st.thermostat.fan-on")
            state("4", action: "setFanSpeed", label: 'High', icon: "st.thermostat.fan-on")
            state("5", action: "setFanSpeed", label: 'Max', icon: "st.thermostat.fan-on")
        }
        controlTile("swingV", "device.swingV", "enum", width: 3 , height: 1, supportedStates: "device.supportedAirConditionerSwingV") {
            state("auto", action: "setSwingV", label: 'Auto')
            state("off", action: "setSwingV", label: 'Off')
            state("min", action: "setSwingV", label: 'Min')
            state("low", action: "setSwingV", label: 'Low')
            state("mid", action: "setSwingV", label: 'Mid')
            state("high", action: "setSwingV", label: 'High')
            state("max", action: "setSwingV", label: 'Max')
        }
        valueTile("spacerV", "spacer", decoration: "flat", inactiveLabel: false, width: 3, height: 1) {
            state "default", label:'Swing-V'
        }
        controlTile("swingH", "device.swingH", "enum", width: 3 , height: 1, supportedStates: "device.supportedAirConditionerSwingH") {
            state("auto", action: "setSwingH", label: 'Auto')
            state("off", action: "setSwingH", label: 'Off')
            state("left max", action: "setSwingH", label: 'Left Max')
            state("left", action: "setSwingH", label: 'Left')
            state("mid", action: "setSwingH", label: 'Mid')
            state("right", action: "setSwingH", label: 'Right')
            state("right max", action: "setSwingH", label: 'Right Max')
            state("wide", action: "setSwingH", label: 'Wide')
        }
        valueTile("spacerH", "spacer", decoration: "flat", inactiveLabel: false, width: 3, height: 1) {
            state "default", label: 'Swing-H'
        }
        main "thermostat"
        details(["thermostat", "thermostatMode", "fanSpeed", "coolingSetpoint", "swingV", "swingH", "spacerV", "spacerH"])
    }
    */
}

def parse(description) {
}

def parseEvents(status, json) {
    def events = []
    if (status as Integer == 200) {
        // Bridge's Last seen
        if (json?.lastSeen) {
            events << sendEvent(name: "lastSeen", value: json?.lastSeen, displayed: false)
        }
    }
    return events
}

def ping() {
    // Intentionally left blank as parent should handle this
}

def installed() {
    state?.lastMode = "cool"
    sendEvent(name: "checkInterval", value: 30 * 60 + 2 * 60, displayed: false, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID])
    sendEvent(name: "supportedThermostatModes", value: JsonOutput.toJson(supportedThermostatModes), displayed: false)
    sendEvent(name: "supportedFanSpeed", value: JsonOutput.toJson(supportedFanSpeed), displayed: false)
    sendEvent(name: "supportedAirConditionerSwingV", value: JsonOutput.toJson(supportedAirConditionerSwingV), displayed: false)
    sendEvent(name: "supportedAirConditionerSwingH", value: JsonOutput.toJson(supportedAirConditionerSwingH), displayed: false)
    sendEvent(name: "coolingSetpointRange", value: thermostatSetpointRange, displayed: false)
    sendEvent(name: "switch", value: "off", displayed: false)
    sendEvent(name: "fanSpeed", value: supportedFanSpeed[0], displayed: false)
    sendEvent(name: "coolingSetpoint", value: thermostatSetpointRange[0], unit: getTemperatureScale(), displayed: false)
    sendEvent(name: "temperature", value: thermostatSetpointRange[0], unit: getTemperatureScale(), displayed: false)
    sendEvent(name: "thermostatMode", value: "off", data:[supportedThermostatModes: JsonOutput.toJson(supportedThermostatModes)], displayed: false)
    sendEvent(name: "swingV", value: "auto", displayed: false)
    sendEvent(name: "swingH", value: "auto", displayed: false)
}

def updated() {
    initialize()
}

def initialize() {
}

def setFanSpeed(String speed) {
    def fanSpeed = [name: "fanSpeed"]
    if (supportedFanSpeed.contains(speed as String)) {
        fanSpeed.value = speed as String
        sendEvent(fanSpeed)
        done()
    } else {
        log.debug "Unsupported fan speed: '${speed}'"
    }
}

def on() {
    if (state?.lastMode) {
        setThermostatMode(state?.lastMode)
    } else {
        setThermostatMode("cool")
    }
}

def off() {
    setThermostatMode("off")
}

def cool() {
    setThermostatMode("cool")
}

def heat() {
    setThermostatMode("heat")
}

def auto() {
    setThermostatMode("auto")
}

def setThermostatMode(String mode) {
    def modeMap = [name: "thermostatMode", data:[supportedThermostatModes: supportedThermostatModes.encodeAsJson()]]
    def switchMap = [name: "switch"]

    if (supportedThermostatModes.contains(mode)) {
        switch (mode) {
            case "off":
                modeMap.value = "off"
                modeMap.displayed = true
                switchMap.value = "off"
                break
            default:
                modeMap.value = mode
                modeMap.displayed = true
                switchMap.value = "on"
                state?.lastMode = mode
                break
        }
        sendEvent(modeMap)
        sendEvent(switchMap)
        done()
    } else {
        log.debug "Unsupported AC mode: '${mode}'"
    }
}

def setCoolingSetpoint(setpoint) {
    if (setpoint < thermostatSetpointRange[0]) {
        setpoint = thermostatSetpointRange[0]
    } else if (setpoint > thermostatSetpointRange[1]) {
        setpoint = thermostatSetpointRange[1]
    }
    sendEvent(name: "coolingSetpoint", value: setpoint, unit: getTemperatureScale(), displayed: false)
    sendEvent(name: "temperature", value: setpoint, unit: getTemperatureScale())
    done()
}

def setSwingV(verticalSwing) {
    if (supportedAirConditionerSwingV.contains(verticalSwing)) {
        sendEvent(name: "swingV", value: verticalSwing)
        done()
    } else {
        log.debug "Unsupported vertical swing mode: '${verticalSwing}'"
    }
}

def setSwingH(horizontalSwing) {
    if (supportedAirConditionerSwingH.contains(horizontalSwing)) {
        sendEvent(name: "swingH", value: horizontalSwing)
        done()
    } else {
        log.debug "Unsupported horizontal swing mode: '${horizontalSwing}'"
    }
}

def done() {
    def bridge = parent.childSetting(device.id, "bridge") ?: null
    def vendor = parent.childSetting(device.id, "hvac") ?: null
    if (bridge && vendor) {
        def command = [:]
        def power = device.currentState("switch").value
        command.Vendor = vendor
        command.Power = power
        command.Mode = (power == "off") ? state.lastMode : device.currentState("thermostatMode").value
        command.FanSpeed = device.currentState("fanSpeed").value
        command.Temp = device.currentState("coolingSetpoint").value
        command.SwingV = device.currentState("swingV").value
        command.SwingH = device.currentState("swingH").value
        if (getTemperatureScale() == "F") {
            command.Celsius = 'Off'
        }
        if (power == "off") {
            log.debug "Command 1: " + JsonOutput.toJson(command)
            parent.callTasmota(bridge, 'IRhvac ' + JsonOutput.toJson(command))

            command.Mode = "off"
            log.debug "Command 2: " + JsonOutput.toJson(command)
            parent.callTasmota(bridge, 'IRhvac ' + JsonOutput.toJson(command))
        } else {
            log.debug "Command: " + JsonOutput.toJson(command)
            parent.callTasmota(bridge, 'IRhvac ' + JsonOutput.toJson(command))
        }
    } else {
        log.debug "Error: Please specify an IR bridge and air conditioner brand"
    }
}

def getThermostatSetpointRange() {
    (getTemperatureScale() == "C") ? [16, 31] : [60, 87]
}

def getSupportedThermostatModes() {
    ["off", "cool", "heat", "auto"]
}

def getSupportedFanSpeed() {
    ["auto", "1", "2", "3", "4", "5"]
}

def getSupportedAirConditionerSwingV() {
    ["auto", "off", "min", "low", "mid", "high", "max"]
}

def getSupportedAirConditionerSwingH() {
    ["auto", "off", "left max", "left", "mid", "right", "right max", "wide"]
}

The more I look at this ST DTH, the more confused I get. I think the original author created custom capabilities using the New ST API. These are not supported on Hubitat. So, I am not sure if this DTH will ever work properly without a significant rewrite on Hubitat.

Do you have this working on SmartThings today?

1 Like

I agree with you, I don't think it can work properly. I would like to thank you very much for your efforts in helping me.
Yes this device handler works great on Smartthings.
I wanted to ask you a favor, but only if it's not going to be too much trouble for you.
Do you know how to create a virtual driver?
You would solve my problem if you could create a virtual driver for me with these characteristics that I commented:
"Power" :

  • On
  • Off
    "Mode" :
  • Off
  • Auto
  • Cool
  • Heat
  • Dry
  • Fan
    "FanSpeed" :
  • Auto
  • Low
  • Med
  • Max
    "SwingV" : vertical swing of Fan
  • Auto
  • Off
    "SwingH" : horizontal swing of Fan
  • Auto
  • Off
    "Celsius" :
  • On
  • Off
    "Temp" :
    Range 16-31
    "Quiet" :
  • On
  • Off
    Quiet mode ( "On" / "Off" )
    "Turbo" :
  • On
  • Off
    "Beep" :
  • On
  • Off

Your request is not detailed enough to create a virtual driver from. Drivers typically implement standard capabilities. A capability includes a list of commands and attributes. It is possible to create a driver with custom commands and custom attributes. Most of what you're requesting above would fall into the custom attribute category, however it is unclear what commands would be required to change those attributes. Also, unknown are what 'user preferences' would be required in the driver. Also unknown is how you plan to interact with this virtual device. You mention webCoRE will do the rest, however I then question what is the value of the virtual driver to begin with? Are you wanting to display and control the virtual thermostat via the Hubitat Dashboard? If so, that brings with it a whole new set of challenges as the Dashboard is designed to work with devices that implement Standard Capabilities, not massively custom attributes and commands.

Thus, one cannot create a virtual driver based on the information provided thus far.

Perhaps you could contact the ST developer and ask for assistance in porting their work to Hubitat?

2 Likes

I understand, thank you very much for the clarifications.
I will try to add only then the switch capability in a thermostat driver.

Do you know if it is possible to modify the Hubitat thermostat driver to work in celsius? Here in Brazil we don't use farenheit scale

It should work in Celsius if you set the temperature scale to be Celsius under the hub's Location

2 Likes

A friend of mine made this SmartApp for Smartthings in an attempt to save the state of the device in case of power failure.I I wanted to bring it to Hubitat but I am having a problem when selecting the state of the device

https://raw.githubusercontent.com/w35l3y/SmartThingsPublic/master/smartapps/br-com-wesley/switch-state-restorer.src/switch-state-restorer.groovy

preferences {
section("Primary switch") {
input "primary", "capability.switch", required: true
input "primaryState", "enum", required: true, defaultValue:"on", metadata: [values: ["on", "off"]], title: "State when switch have power source restored"
}
section("Secondary switches") {
input "secondary", "capability.switch", required: true, multiple: true
}

I was wondering if you guys could help me to work in the code so that the state of the switch would appear
This is a very interesting smartapp with the potential to help many people

Try changing the following line as follows:

        input "primaryState", "enum", required: true, defaultValue:"on", options: [["on"], ["off"]], title: "State when switch have power source restored"
1 Like

It worked perfectly. Thank you very much.
The idea of this smartapp is that you select an outlet or a switch that is always turned on and that when power is missing it would be turned off, then the app would notice this and restore it and other selected devices to the last saved state

1 Like

You know...I never tested this...is that really true? One would think it wouldn't have time to report back that it's off....