[RELEASE] Tasmota (Connect) for Sonoff, Tuya, SmartLife & other ESP8266 devices)

Tap & hold for 5 seconds on the “About SmartThings” box to enable “Developer mode”

FIrst - thank you for your replies and suggestions - it’s much appreciated!

I’m unable to enable developer mode following the directions given. No matter how long I press and hold “about SmartThings” nothing changes and no “developer mode” option appears.

I’ve rebooted the phone and tried multiple times. I do see the manually installed smartapp (named Tasmota (Connect)2) now appear in the “Add SmartApp” menu on my phone, but when I tap it the menu disappears and the smartapp does not show in my list. I can do this several times with not changes.

Super odd as I’ve added SmartApps before, again thanks for the patience and any advice or direction…

UPDATE: I was able to add the smartapp Tasmota (Connect) with an android tablet with no issues. No idea why I couldn’t do this with my IOS device.

I added the SmartApp then added the device. I have a Sonoff TH16 and in SmartThings when I added it I got two devices, one for the switch (open/close) and one for the temp sensor. The open/close does not activate the Sonoff device, and the temperature reading is not showing in the app. When I log directly into the flashed device via a webpage I can open/close and see the temperature.

Thoughts on why SmartThings doesn’t seem to connect to it?

Guys - I think this is my fault, I’m seeing there is more setup needed in the smartapp. Sorry for filling up this thread, I’ll do more reading and try to resolve this myself (RTFM).

Thanks again for your work on this!

UPDATE: Everything is working! I don’t know why the SmartThings app for IOS won’t work, but everything “just worked” in the android app (Version 1.7.60.23), including the option to enable Developer mode (though I didn’t need it). Also noticed that Android app shows Fahrenheit while IOS shows Celsius, but the number is Fahrenheit (so it’s not a conversion, it’s just the wrong letter!). I’m so glad I have Android devices I could use, I’ve included this here just in case someone else has these issues with IOS and this helps in some way.

This is probably a dumb question, sorry about that but what are the benefits of flashing Tasmota on an ESP8266 or ESP8285 Wifi device to be able to control it with Smartthings? It is my understanding that the main reason to “Tasmotize” a device is to have full local control vs cloud based solutions. However, isn’t the SmartApp for Tasmota (Connect) cloud based? Or am I missing something and the “tasmotized” devices can indeed work locally with smartthings by this solution?

Thank you very much for your replies, are very appreciated!

A question: in the Smarthtings App, when I pull up the Tasmota (Connect) smartapp, there is a setting called ‘Device Health Check’ that defaults to 5 minutes.

I can’t find any documentation for this setting in this thread or in the GitHub readme. The groovy file simply says “Check in on device health every so often” but doesn’t really say what this does.
Is it how often the smartapp polls devices? Any implication to setting it to 1 min rather than the default 5 min?

This allows all Tasmota devices to be under SmartThings app and the communication is between SmartThings Hub/Cloud and Tasmota local/LAN devices.

If real-time status is working fine for you, then is fine to keep it to the default 5 min.

It is the frequency of the hub pinging all the Tasmota devices for status.

If real-time status is working fine for you, then is fine to keep it to the default 5 min.

I’m actually trying to solve the following: I have a dual plug switch running Tasmota, and I have a smartthings automation such that when ST senses that switch 1 is in use (measured by power draw), it turns on switch 2. And the reverse: when switch 1’s power draw goes low, it turns off switch 2.

This was working fine for the past year, but sometime in the past few weeks, something changed such that there is an extremely noticeable delay (as in multiple minutes) in Smarthings noticing that switch 1’s power draw has increase and thus triggering the power on to switch 2. I doubled checked via the device’s tasmota web page that the power draw is indeed being reported correctly, but for some reason smartthings is not picking it up for many minutes.

I have no idea why something that was previously working fine has recently started lagging like this but I assume it’s due to the many smartthings backend changes that have been going on.

Thus, I’m trying to see if there’s anything I can tweak or tune to try to increase the response time of my automation and reduce the lag.

Check the Tasmota device console: You should find similar to these. If isn’t, then something is not right, and could be tricky to debug…

05:25:46.239 RUL: TELE-ENERGY#TOTAL performs "WebSend [192.168.1.207:39500] /?json={"StatusSNS":{"ENERGY":{"Total":"264.039"}}}"
05:25:46.800 RSL: stat/tasmota/RESULT = {"WebSend":"Done"}
05:25:46.816 RUL: TELE-ENERGY#VOLTAGE performs "WebSend [192.168.1.207:39500] /?json={"StatusSNS":{"ENERGY":{"Voltage":"246"}}}"
05:25:47.380 RSL: stat/tasmota/RESULT = {"WebSend":"Done"}
05:25:47.397 RUL: TELE-ENERGY#CURRENT performs "WebSend [192.168.1.207:39500] /?json={"StatusSNS":{"ENERGY":{"Current":"0.105"}}}"
05:25:47.963 RSL: stat/tasmota/RESULT = {"WebSend":"Done"}

Thanks for the reply @hongtat . So if my internet connection fails then I wouldn’t be able to control the Tasmota devices via Smartthings, right?

Thanks!

You wouldn’t be able to control Tasmota devices via ST, if internet connection fails.

this is only for wifi device?

i have Moes wall switch but is zigbee so not luck here, i am correct?

Yes. This is for wifi ESP8266 devices.

Can you help us with zigbee version of switch

We will donate the developer with solutuin 30€ until now

Please if you have time check this thread

I am trying to monitor battery power via monitoring the VCC of ESP8266 (basically Vcc drops below 3V when the battery drops below 3.6V )
Using Tasmota 9.5.0 firmware ( with USE_ADC_VCC enabled) and HongTat DHT.
I can Vcc value posted via a console on the actual Tasmota device (STATE = {“Vcc”:3.438}.
Modified and published the DHT code in my IDE to show it up in the ST app.
Here is my issue - it would show up in a simulator but would not in the ST app.
I have tried to remove the device and re-add but still no luck.
I am no programmer by any means. :blush:

Any suggestions?

Here is the modified code:

/**
 *  Tasmota - Generic Switch
 *
 *  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/>.
 */

String driverVersion() { return "20210810" }
import groovy.json.JsonSlurper
metadata {
    definition(name: "Tasmota Generic Switch", namespace: "hongtat", author: "HongTat Tan", ocfDeviceType: "oic.d.switch", vid: "af06f1a5-45b6-39ee-86bb-7cbe15062491", mnmn: "SmartThingsCommunity") {
        capability "Actuator"
        capability "Health Check"
        capability "Switch"
        capability "Polling"
        capability "Refresh"
        capability "Sensor"
        capability "Signal Strength"
		
        attribute "lastSeen", "string"
        attribute "version", "string"
    }

    simulator {
    }

    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 Generic Switch v${driverVersion()}", displayDuringSetup: false, type: "paragraph", element: "paragraph")
        }
    }

    tiles(scale: 2) {
        multiAttributeTile(name: "switch", type: "lighting", width: 6, height: 4, canChangeIcon: true) {
            tileAttribute("device.switch", key: "PRIMARY_CONTROL") {
                attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#00A0DC"
                attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff"
            }
        }

        standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
            state "default", label: '', action: "refresh.refresh", icon: "st.secondary.refresh"
        }
        standardTile("lqi", "device.lqi", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
            state "default", label: 'LQI: ${currentValue}'
        }
        standardTile("rssi", "device.rssi", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
            state "default", label: 'RSSI: ${currentValue}dBm'
        }
		standardTile("Vcc", "device.Vcc", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
            state "default", label: 'Vcc: ${currentValue}V'
         }   
        main "switch"
        details(["switch", "refresh", "lqi", "rssi", "Vcc"])
    }
}

def installed() {
    sendEvent(name: "checkInterval", value: 30 * 60 + 2 * 60, displayed: false, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID])
    sendEvent(name: "switch", value: "off")
    log.debug "Installed"
    response(refresh())
}

def uninstalled() {
    sendEvent(name: "epEvent", value: "delete all", isStateChange: true, displayed: false, descriptionText: "Delete endpoint devices")
}

def updated() {
    initialize()
}

def initialize() {
    if (device.hub == null) {
        log.error "Hub is null, must set the hub in the device settings so we can get local hub IP and port"
        return
    }
    // Child creation
    String childMeta = getDataValue("child")
    if (childMeta != null && ((childMeta.startsWith("{") && childMeta.endsWith("}")) || (childMeta.startsWith("[") && childMeta.endsWith("]")))) {
        def json = new JsonSlurper().parseText(childMeta)
        if (json != null) {
            boolean hasError = false
            json.each { i,tasmota ->
                try {
                    String dni = "${device.deviceNetworkId}-ep${i}"
                    addChildDevice(tasmota, dni, device.getHub().getId(),
                            [completedSetup: true, label: "${device.displayName} ${i}", isComponent: false])
                    log.debug "Created '${device.displayName}' - ${i}ch (${tasmota})"
                } catch (all) {
                    hasError = true
                    log.error "Error: ${(all as String).split(":")[1]}."
                }
            }
            if (hasError == false) {
                updateDataValue("child","")
            }
        }
    }

    def syncFrequency = (parent.generalSetting("frequency") ?: 'Every 1 minute').replace('Every ', 'Every').replace(' minute', 'Minute').replace(' hour', 'Hour')
    try {
        "run$syncFrequency"(refresh)
    } catch (all) { }
    sendEvent(name: "checkInterval", value: parent.checkInterval(), displayed: false, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID])

    parent.callTasmota(this, "Status 5")
    parent.callTasmota(this, "Backlog Rule1 ON Power#state DO WebSend ["+device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")+"] /?json={\"StatusSTS\":{\"POWER\":\"%value%\"}} ENDON ON Power1#state DO WebSend ["+device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")+"] /?json={\"StatusSTS\":{\"POWER1\":\"%value%\"}} ENDON ON Power2#state DO WebSend ["+device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")+"] /?json={\"StatusSTS\":{\"POWER2\":\"%value%\"}} ENDON ON Power3#state DO WebSend ["+device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")+"] /?json={\"StatusSTS\":{\"POWER3\":\"%value%\"}} ENDON ON Power4#state DO WebSend ["+device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")+"] /?json={\"StatusSTS\":{\"POWER4\":\"%value%\"}} ENDON;Rule1 1")
    parent.callTasmota(this, "Backlog Rule2 ON Power5#state DO WebSend ["+device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")+"] /?json={\"StatusSTS\":{\"POWER5\":\"%value%\"}} ENDON ON Power6#state DO WebSend ["+device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")+"] /?json={\"StatusSTS\":{\"POWER6\":\"%value%\"}} ENDON ON Power7#state DO WebSend ["+device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")+"] /?json={\"StatusSTS\":{\"POWER7\":\"%value%\"}} ENDON ON Power8#state DO WebSend ["+device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")+"] /?json={\"StatusSTS\":{\"POWER8\":\"%value%\"}} ENDON;Rule2 1")
    parent.callTasmota(this, "Status 8")
    refresh()
}

def parse(String description) {
    def events = null
    def message = parseLanMessage(description)
    def json = parent.getJson(message.header)
    if (json != null) {
        events = parseEvents(200, json)
    }
    return events
}

def calledBackHandler(physicalgraph.device.HubResponse hubResponse) {
    def events = null
    def status = hubResponse.status
    def json = hubResponse.json
    events = parseEvents(status, json)
    return events
}

def parseEvents(status, json) {
    def events = []
    if (status as Integer == 200) {
        def channel = getDataValue("endpoints")?.toInteger()
        def eventdateformat = parent.generalSetting("dateformat")
        def now = location.timeZone ? new Date().format("${eventdateformat}a", location.timeZone) : new Date().format("yyyy MMM dd EEE h:mm:ss")

        // Power
        if (channel != null) {
            for (i in 0..channel) {
                def number = (i > 0) ? i : ""
                def power = (json?.StatusSTS?."POWER${number}" != null) ? (json?.StatusSTS?."POWER${number}") : ((json?."POWER${number}" != null) ? json?."POWER${number}" : null)

                def powerStatus = null
                if (power in ["ON", "1"]) {
                    powerStatus = "on"
                } else if (power in ["OFF", "0"]) {
                    powerStatus = "off"
                }
                if (powerStatus != null) {
                    if ((channel == 1) || (channel > 1 && i == 1)) {
                        events << sendEvent(name: "switch", value: powerStatus)
                    } else {
                        String childDni = "${device.deviceNetworkId}-ep$i"
                        def child = childDevices.find { it.deviceNetworkId == childDni }
                        child?.sendEvent(name: "switch", value: powerStatus)
                    }
                    log.debug "Switch $number: '$powerStatus'"
                }
            }
        }

        // Temperature, Humidity (Status 8)
        def resultTH = null
        if (json?.StatusSNS != null) {
            for (record in json.StatusSNS) {
                if (record.value instanceof Map && (record.value.containsKey("Humidity") || record.value.containsKey("Temperature"))) {
                    resultTH = record.value
                }
            }
            if (resultTH != null) {
                def childMessage = [:]
                if (resultTH.containsKey("Humidity")) {
                    childMessage.humidity = Math.round((resultTH.Humidity as Double) * 100) / 100
                }
                if (resultTH.containsKey("Temperature")) {
                    childMessage.temperature = resultTH.Temperature.toFloat()
                }
                if (json?.StatusSNS?.TempUnit != null) {
                    childMessage.tempUnit = json?.StatusSNS?.TempUnit
                }
                def child = childDevices.find { it.typeName == "Tasmota Child Temp/Humidity Sensor" }
                child?.parseEvents(200, childMessage)
            }
        }

        // MAC
        if (json?.StatusNET?.Mac != null) {
            def dni = parent.setNetworkAddress(json.StatusNET.Mac)
            def actualDeviceNetworkId = device.deviceNetworkId
            if (actualDeviceNetworkId != state.dni) {
                runIn(10, refresh)
            }
            log.debug "MAC: '${json.StatusNET.Mac}', DNI: '${state.dni}'"
            if (state.dni == null || state.dni == "" || dni != state.dni) {
                if (channel > 1 && childDevices) {
                    childDevices.each {
                        it.deviceNetworkId = "${dni}-ep" + parent.channelNumber(it.deviceNetworkId)
                        log.debug "Child: " + "${dni}-ep" + parent.channelNumber(it.deviceNetworkId)
                    }
                }
            }
            state.dni = dni
        }

        // Signal Strength
        if (json?.StatusSTS?.Wifi != null) {
            events << sendEvent(name: "lqi", value: json?.StatusSTS?.Wifi.RSSI, displayed: false)
            events << sendEvent(name: "rssi", value: json?.StatusSTS?.Wifi.Signal, displayed: false)
            
         }
          // Vcc
        if (json?.StatusSTS?.Vcc != null) {
            events << sendEvent(name: "Vcc", value: json?.StatusSTS?.Vcc, displayed: false)
        }


        // Version
        if (json?.StatusFWR?.Version != null) {
            state.lastCheckedVersion = new Date().getTime()
            events << sendEvent(name: "version", value: json.StatusFWR.Version, displayed: false)
        }

        // Call back
        if (json?.cb != null) {
            parent.callTasmota(this, json.cb)
        }

        // Last seen
        events << sendEvent(name: "lastSeen", value: now, displayed: false)
    }
    return events
}

def on() {
    def channel = getDataValue("endpoints")?.toInteger()
    parent.callTasmota(this, "POWER" + ((channel == 1) ? "" : 1) + " 1")
}

def off() {
    def channel = getDataValue("endpoints")?.toInteger()
    parent.callTasmota(this, "POWER" + ((channel == 1) ? "" : 1) + " 0")
}

def refresh(dni=null) {
    def lastRefreshed = state.lastRefreshed
    if (lastRefreshed && (now() - lastRefreshed < 5000)) return
    state.lastRefreshed = now()

    // Check version every 30m
    def lastCheckedVersion = state.lastCheckedVersion
    if (!lastCheckedVersion || (lastCheckedVersion && (now() - lastCheckedVersion > (30 * 60 * 1000)))) {
        parent.callTasmota(this, "Status 2")
    }

    def actualDeviceNetworkId = device.deviceNetworkId
    if (state.dni == null || state.dni == "" || actualDeviceNetworkId != state.dni) {
        parent.callTasmota(this, "Status 5")
    }
    parent.callTasmota(this, "Status 8")
    parent.callTasmota(this, "Status 11")
}

def ping() {
    refresh()
}

def childOn(dni) {
    parent.callTasmota(this, "POWER" + parent.channelNumber(dni) + " 1")
}

def childOff(dni) {
    parent.callTasmota(this, "POWER" + parent.channelNumber(dni) + " 0")
}

Does the Generic Metering Switch dth work for you? It has voltage.

I want to measure internal core VCC voltage not the external one, which usually is using A0 GPIO.
The solution was found and one great samaritan (Nayely Zarazua) help me to accomplish this.
Details are here

@hongtat will tasmota connect go away when smartthings ends support for groovy?

Yes. It will.

Whether there would be a replacement for it, I’m unable to provide any specifics yet…