whoops didnt upload it yet... its very very beta right now which is why I probably didnt upload it yet and wanted feedback on it.
/**
*
* Shelly TRV Driver
*
* Copyright © 2018-2019 Scott Grayban
* Copyright © 2020 Allterco Robotics US
*
* 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.
*
* Hubitat is the Trademark and intellectual Property of Hubitat Inc.
* Shelly is the Trademark and Intellectual Property of Allterco Robotics Ltd
*
*-------------------------------------------------------------------------------------------------------------------
*
* 1.0.0 - Initial code
*
*/
import groovy.json.*
import groovy.transform.Field
def setVersion(){
state.Version = "1.0.0"
state.InternalName = "ShellyTRV"
}
metadata {
definition (
name: "Shelly TRV",
namespace: "ShellyUSA",
author: "Scott Grayban",
importUrl: "https://raw.githubusercontent.com/ShellyUSA/Hubitat-Drivers/master/ShellyTRV.groovy"
)
{
capability "Actuator"
capability "Sensor"
capability "Refresh"
capability "Polling"
capability "SignalStrength"
capability "TemperatureMeasurement"
capability "Valve"
capability "Battery"
attribute "FW_Update_Needed", "string"
attribute "LastRefresh", "string"
attribute "internal_tempC", "number"
attribute "internal_tempF", "number"
attribute "DeviceOverTemp", "string"
attribute "MAC", "string"
attribute "RelayChannel", "number"
attribute "Primary_IP", "string"
attribute "Primary_SSID", "string"
attribute "Secondary_IP", "string"
attribute "Secondary_SSID", "string"
attribute "WiFiSignal", "string"
attribute "Cloud", "string"
attribute "Cloud_Connected", "string"
attribute "energy", "number"
attribute "DeviceType", "string"
attribute "DeviceName", "string"
attribute "NTPServer", "string"
attribute "Position", "number"
attribute "voltage", "number"
command "RebootDevice"
command "UpdateDeviceFW" // ota?update=1
//command "updatecheck" // Only used for development
command "getSettings"
command "TargetTemp" , ["temp"]
}
preferences {
def refreshRate = [:]
refreshRate << ["1 min" : "Refresh every minute"]
refreshRate << ["5 min" : "Refresh every 5 minutes"]
refreshRate << ["15 min" : "Refresh every 15 minutes"]
refreshRate << ["30 min" : "Refresh every 30 minutes"]
refreshRate << ["manual" : "Manually or Polling Only"]
input("ip", "string", title:"IP", description:"Shelly IP Address", defaultValue:"" , required: true)
input name: "username", type: "text", title: "Username:", description: "(blank if none)", required: false
input name: "password", type: "password", title: "Password:", description: "(blank if none)", required: false
if (channel < 1) input name: "ntp_server", type: "text", title: "NTP time server:", description: "E.G. time.google.com or 192.168.0.59", defaultValue: "time.google.com", required: true
// Only show for channel 0 since the device name is for the entire device
if (channel < 1) input name: "devicename", type: "text", title: "Give your device a name:", description: "EG; Location/Room<br>NO SPACES in name", required: false
input("refresh_Rate", "enum", title: "Device Refresh Rate", description:"<font color=red>!!WARNING!!</font><br>DO NOT USE if you have over 50 Shelly devices.", options: refreshRate, defaultValue: "manual")
input "locale", "enum", title: "Choose refresh date format", required: true, defaultValue: true, options: [US:"US MM/DD/YYYY",UK:"UK DD/MM/YYYY"]
input name: "debugOutput", type: "bool", title: "Enable debug logging?", defaultValue: true
input name: "debugParse", type: "bool", title: "Enable JSON parse logging?", defaultValue: true
input name: "txtEnable", type: "bool", title: "Enable descriptionText logging", defaultValue: true
input name: "Shellyinfo", type: "text", title: "<center><font color=blue>Info Box</font><br>Shelly API docs are located</center>",
description: "<center><br><a href='http://shelly-api-docs.shelly.cloud/' title='shelly-api-docs.shelly.cloud' target='_blank'>[here]</a></center>"
}
}
def initialize() {
log.info "initialize"
if (txtEnable) log.info "initialize"
}
def installed() {
log.debug "Installed"
state.DeviceName = "NotSet"
state.RelayName = "NotSet"
}
def uninstalled() {
unschedule()
log.debug "Uninstalled"
}
def updated() {
if (txtEnable) log.info "Preferences updated..."
log.warn "Debug logging is: ${debugOutput == true}"
log.warn "Switch protection is: ${settings?.protect}"
unschedule()
dbCleanUp()
if (ip != null) { // Don't set until IP is saved
if (!(getDataValue("model") in ["SHSW-1","SHEM"])) {
sendSwitchCommand "/settings/relay/${channel}?max_power=${maxpower}"
}
if (getDataValue("model") in ["SHPLG-1","SHPLG-S","SHPLG-U1"]) {
logDebug "LED setting ${led_status} and ${led_power}"
sendSwitchCommand "/settings?led_status_disable=${led_status}"
sendSwitchCommand "/settings?led_power_disable=${led_power}"
}
if (channel < 1) sendSwitchCommand "/settings?sntp_server=${ntp_server}"
// Set device and relay name
if (channel < 1) sendSwitchCommand "/settings?name=${devicename}"
if (!(getDataValue("model") in ["SHPLG-S","SHPLG-1","SHPLG-U1"])) sendSwitchCommand "/settings/relay/${channel}?name=${relayname}"
if (getDataValue("model") == "SHEM") sendSwitchCommand "/settings/relay/${channel}?ctraf_type=${ctraf_type}"
}
switch(refresh_Rate) {
case "1 min" :
runEvery1Minute(autorefresh)
break
case "5 min" :
runEvery5Minutes(autorefresh)
break
case "15 min" :
runEvery15Minutes(autorefresh)
break
case "30 min" :
runEvery30Minutes(autorefresh)
break
case "manual" :
unschedule(autorefresh)
log.info "Autorefresh disabled"
break
}
if (txtEnable) log.info ("Auto Refresh set for every ${refresh_Rate} minute(s).")
if (debugOutput) runIn(1800,logsOff) //Off in 30 minutes
if (debugParse) runIn(300,logsOff) //Off in 5 minutes
state.LastRefresh = new Date().format("YYYY/MM/dd \n HH:mm:ss", location.timeZone)
version()
refresh()
getSettings()
}
private dbCleanUp() {
state.remove("version")
state.remove("Version")
state.remove("ShellyfwUpdate")
state.remove("power")
state.remove("overpower")
state.remove("dcpower")
state.remove("max_power")
state.remove("internal_tempC")
state.remove("Status")
state.remove("max_power")
state.remove("RelayName")
state.remove("RelayChannel")
state.remove("powerSource")
}
def refresh(){
if (ip != null) { // Don't set until IP is saved
logDebug "Shelly Status called"
getSettings()
def params = [uri: "http://${username}:${password}@${ip}/status"]
try {
httpGet(params) {
resp -> resp.headers.each {
logJSON "Response: ${it.name} : ${it.value}"
}
obs = resp.data
logJSON "params: ${params}"
logJSON "response contentType: ${resp.contentType}"
logJSON "response data: ${resp.data}"
if (obs.temperature != null) sendEvent(name: "internal_tempC", value: obs.temperature)
if (obs.tmp != null) {
sendEvent(name: "internal_tempC", unit: "C", value: obs.tmp.tC)
sendEvent(name: "internal_tempF", unit: "F", value: obs.tmp.tF)
}
if (obs.overtemperature != null) sendEvent(name: "DeviceOverTemp", value: obs.overtemperature)
if (obs.wifi_sta != null) {
state.rssi = obs.wifi_sta.rssi
state.ssid = obs.wifi_sta.ssid
state.ip = obs.wifi_sta.ip
sendEvent(name: "Primary_SSID", value: state.ssid)
sendEvent(name: "Primary_IP", value: state.ip)
}
sendEvent(name: "Charging", value: obs.charger)
sendEvent(name: "calibrated", value: obs.calibrated)
sendEvent(name: "voltage", value: obs.bat.voltage)
sendEvent(name: "battery", unit: "%", value: obs.bat.value)
/*
-30 dBm Excellent | -67 dBm Good | -70 dBm Poor | -80 dBm Weak | -90 dBm Dead
*/
signal = state.rssi
if (signal <= 0 && signal >= -70) {
sendEvent(name: "WiFiSignal", value: "<font color='green'>Excellent</font>", isStateChange: true);
} else
if (signal < -70 && signal >= -80) {
sendEvent(name: "WiFiSignal", value: "<font color='green'>Good</font>", isStateChange: true);
} else
if (signal < -80 && signal >= -90) {
sendEvent(name: "WiFiSignal", value: "<font color='yellow'>Poor</font>", isStateChange: true);
} else
if (signal < -90 && signal >= -100) {
sendEvent(name: "WiFiSignal", value: "<font color='red'>Weak</font>", isStateChange: true);
}
state.mac = obs.mac
sendEvent(name: "MAC", value: state.mac)
sendEvent(name: "rssi", value: state.rssi)
// Device FW Updates
state.has_update = obs.has_update
if (state.has_update == true) {
if (txtEnable) log.info "sendEvent NEW SHELLY FIRMWARE"
sendEvent(name: "FW_Update_Needed", value: "<font color='red'>FIRMWARE Update Required</font>")
}
if (state.has_update == false) {
if (txtEnable) log.info "sendEvent Device FW is current"
sendEvent(name: "FW_Update_Needed", value: "<font color='green'>Device FW is current</font>")
}
// Cloud
state.cloud = obs.cloud.enabled
if (state.cloud == true) {
sendEvent(name: "Cloud", value: "<font color='green'>Enabled</font>")
} else {
sendEvent(name: "Cloud", value: "<font color='red'>Disabled</font>")
}
state.cloudConnected = obs.cloud.connected
if (state.cloudConnected == true) {
sendEvent(name: "Cloud_Connected", value: "<font color='green'>Connected</font>")
} else {
sendEvent(name: "Cloud_Connected", value: "<font color='red'>Not Connected</font>")
}
// Relays
if (obs.relays != null) {
if (channel ==0) ison = obs.relays.ison[0]
if (channel ==1) ison = obs.relays.ison[1]
if (channel ==2) ison = obs.relays.ison[2]
if (channel ==3) ison = obs.relays.ison[3]
if (ison == true) {
sendEvent(name: "switch", value: "on")
} else {
sendEvent(name: "switch", value: "off")
}
}
} // End try
} catch (e) {
log.error "something went wrong: $e"
}
} // End if !==ip
} // End Refresh Status
// Get shelly device type
def getSettings(){
if (ip != null) { // Don't set until IP is saved
getThermostatsSettings()
logDebug "Get Shelly Settings"
def paramsSettings = [uri: "http://${username}:${password}@${ip}/settings"]
try {
httpGet(paramsSettings) {
respSettings -> respSettings.headers.each {
logJSON "ResponseSettings: ${it.name} : ${it.value}"
}
obsSettings = respSettings.data
logJSON "params: ${paramsSettings}"
logJSON "response contentType: ${respSettings.contentType}"
logJSON "response data: ${respSettings.data}"
state.DeviceType = obsSettings.device.type
if (state.DeviceType == "SHSW-1") sendEvent(name: "DeviceType", value: "Shelly 1")
if (state.DeviceType == "SHSW-PM") sendEvent(name: "DeviceType", value: "Shelly 1PM")
if (state.DeviceType == "SHSW-21") sendEvent(name: "DeviceType", value: "Shelly 2")
if (state.DeviceType == "SHSW-25") sendEvent(name: "DeviceType", value: "Shelly 2.5")
if (state.DeviceType == "SHSW-44") sendEvent(name: "DeviceType", value: "Shelly 4Pro")
if (state.DeviceType == "SHEM") sendEvent(name: "DeviceType", value: "Shelly EM")
if (state.DeviceType == "SHEM-3") sendEvent(name: "DeviceType", value: "Shelly EM3")
if (state.DeviceType == "SHPLG-1") sendEvent(name: "DeviceType", value: "Shelly Plug")
if (state.DeviceType == "SHPLG-S") sendEvent(name: "DeviceType", value: "Shelly PlugS")
if (state.DeviceType == "SHPLG-U1") sendEvent(name: "DeviceType", value: "Shelly Plug US")
if (state.DeviceType == "SHTRV-01") sendEvent(name: "DeviceType", value: "Shelly TRV")
state.ShellyHostname = obsSettings.device.hostname
state.sntp_server = obsSettings.sntp.server
sendEvent(name: "NTPServer", value: state.sntp_server)
if (obsSettings.led_status_disable != null) {
if (obsSettings.led_status_disable == false) {
sendEvent(name: "LED_NetworkStatus", value: "<font color='green'>Enabled</font>")
} else {
sendEvent(name: "LED_NetworkStatus", value: "<font color='red'>Disabled</font>")
}
if (obsSettings.led_power_disable == false) {
sendEvent(name: "LED_Output", value: "<font color='green'>Enabled</font>")
} else {
sendEvent(name: "LED_Output", value: "<font color='red'>Disabled</font>")
}
}
//Get Device name
if (obsSettings.name != "NotSet") {
state.DeviceName = obsSettings.name
sendEvent(name: "DeviceName", value: state.DeviceName)
updateDataValue("DeviceName", state.DeviceName)
if (txtEnable) log.info "DeviceName is ${obsSettings.name}"
} else if (obsSettings.name != null) {
state.DeviceName = "NotSet"
sendEvent(name: "DeviceName", value: state.DeviceName)
if (txtEnable) log.info "DeviceName is ${obsSettings.name}"
}
//Get Relay name
if (getDataValue("model") != "SHPLG-S" && getDataValue("model") != "SHPLG-1" && getDataValue("model") != "SHPLG-U1" && getDataValue("model") != "SHEM") {
if (obsSettings.relays != null) {
if (channel == 0) relay_name = obsSettings.relays.name[0]
if (channel == 1) relay_name = obsSettings.relays.name[1]
if (channel == 2) relay_name = obsSettings.relays.name[2]
if (channel == 3) relay_name = obsSettings.relays.name[3]
if (relay_name != null) {
state.RelayName = relay_name
sendEvent(name: "RelayName", value: state.RelayName)
if (txtEnable) log.info "RelayName is ${relay_name}"
} else {
state.RelayName = "NotSet"
sendEvent(name: "RelayName", value: state.RelayName)
if (txtEnable) log.info "RelayName is ${relay_name}"
}
updateDataValue("RelayName", state.RelayName)
}
} // The Plug devices do not offer a relay name
if (getDataValue("model") == "SHEM") {
state.RelayName = obsSettings.relays.name[0]
sendEvent(name: "RelayName", value: state.RelayName)
updateDataValue("RelayName", state.RelayName)
}
if (obsSettings.wifi_sta1 != null) {
state.rssi = obsSettings.wifi_sta1.rssi
state.Secondary_ssid = obsSettings.wifi_sta1.ssid
state.Secondary_IP = obsSettings.wifi_sta1.ip
if (obsSettings.wifi_sta1.enabled == true) sendEvent(name: "Secondary_SSID", value: state.Secondary_ssid)
if (state.Secondary_IP != null) sendEvent(name: "Secondary_IP", value: state.Secondary_IP)
}
logDebug "updating data values"
updateDataValue("model", state.DeviceType)
updateDataValue("ShellyHostname", state.ShellyHostname)
updateDataValue("ShellyIP", state.ip)
updateDataValue("ShellySSID", obsSettings.wifi_sta.ssid)
updateDataValue("manufacturer", "Allterco Robotics")
updateDataValue("MAC", state.mac)
updateDataValue("DeviceName", state.DeviceName)
} // End try
} catch (e) {
log.error "something went wrong: $e"
}
} // End if !==ip
} // End getSettings Status
def getThermostatsSettings(){
if (ip != null) { // Don't set until IP is saved
logDebug "Shelly Thermostats Settings called"
def params = [uri: "http://${username}:${password}@${ip}/thermostats/0"]
try {
httpGet(params) {
resp -> resp.headers.each {
logJSON "Response: ${it.name} : ${it.value}"
}
obs = resp.data
logJSON "params: ${params}"
logJSON "response contentType: ${resp.contentType}"
logJSON "response data: ${resp.data}"
sendEvent(name: "Position", value: obs.pos)
if (obs.pos >= 1) {
sendEvent(name: "valve", value: "open", isStateChange: true)
} else
sendEvent(name: "valve", value: "closed", isStateChange: true)
sendEvent(name: "temperature", value: obs.tmp.value)
} // End try
} catch (e) {
log.error "something went wrong: $e"
}
} // End if !==ip
} // End thermostat Status
def open() {
logDebug "Executing Open"
sendSwitchCommand "/thermostats/0?pos=100"
sendEvent(name: "valve", value: "open")
}
def close() {
logDebug "Executing Close"
sendSwitchCommand "/thermostats/0?pos=0"
sendEvent(name: "valve", value: "closed")
}
def TargetTemp(temp) {
logDebug "Executing Target Temp"
sendSwitchCommand "/settings/thermostats/0?target_t=${temp}"
}
def ping() {
logDebug "ping"
poll()
}
def logsOff(){
log.warn "debug logging auto disabled..."
device.updateSetting("debugOutput",[value:"false",type:"bool"])
device.updateSetting("debugParse",[value:"false",type:"bool"])
}
def autorefresh() {
if (locale == "UK") {
logDebug "Get last UK Date DD/MM/YYYY"
state.LastRefresh = new Date().format("d/MM/YYYY \n HH:mm:ss", location.timeZone)
sendEvent(name: "LastRefresh", value: state.LastRefresh, descriptionText: "Last refresh performed")
}
if (locale == "US") {
logDebug "Get last US Date MM/DD/YYYY"
state.LastRefresh = new Date().format("MM/d/YYYY \n HH:mm:ss", location.timeZone)
sendEvent(name: "LastRefresh", value: state.LastRefresh, descriptionText: "Last refresh performed")
}
if (txtEnable) log.info "Executing 'auto refresh'" //RK
refresh()
}
private logJSON(msg) {
if (settings?.debugParse || settings?.debugParse == null) {
log.info "$msg"
}
}
private logDebug(msg) {
if (settings?.debugOutput || settings?.debugOutput == null) {
log.debug "$msg"
}
}
// handle commands
//RK Updated to include last refreshed
def poll() {
if (locale == "UK") {
logDebug "Get last UK Date DD/MM/YYYY"
state.LastRefresh = new Date().format("d/MM/YYYY \n HH:mm:ss", location.timeZone)
sendEvent(name: "LastRefresh", value: state.LastRefresh, descriptionText: "Last refresh performed")
}
if (locale == "US") {
logDebug "Get last US Date MM/DD/YYYY"
state.LastRefresh = new Date().format("MM/d/YYYY \n HH:mm:ss", location.timeZone)
sendEvent(name: "LastRefresh", value: state.LastRefresh, descriptionText: "Last refresh performed")
}
if (txtEnable) log.info "Executing 'poll'" //RK
refresh()
}
def sendSwitchCommand(action) {
if (txtEnable) log.info "Calling ${action}"
def params = [uri: "http://${username}:${password}@${ip}/${action}"]
try {
httpPost(params) {
resp -> resp.headers.each {
logDebug "Response: ${it.name} : ${it.value}"
}
} // End try
} catch (e) {
log.error "something went wrong: $e"
}
runIn(2, refresh)
}
def RebootDevice() {
if (txtEnable) log.info "Rebooting Device"
def params = [uri: "http://${username}:${password}@${ip}/reboot"]
try {
httpPost(params) {
resp -> resp.headers.each {
logDebug "Response: ${it.name} : ${it.value}"
}
} // End try
} catch (e) {
log.error "something went wrong: $e"
}
runIn(15,refresh)
}
def UpdateDeviceFW() {
if (txtEnable) log.info "Updating Device FW"
def params = [uri: "http://${username}:${password}@${ip}/ota?update=1"]
try {
httpPost(params) {
resp -> resp.headers.each {
logDebug "Response: ${it.name} : ${it.value}"
}
} // End try
} catch (e) {
log.error "something went wrong: $e"
}
runIn(30,refresh)
}
// Check Version ***** with great thanks and acknowlegment to Cobra (github CobraVmax) for his original code **************
def version(){
updatecheck()
schedule("0 0 18 1/1 * ? *", updatecheck) // Cron schedule
// schedule("0 0/1 * 1/1 * ? *", updatecheck) // Test Cron schedule
}
def updatecheck(){
setVersion()
def paramsUD = [uri: "https://raw.githubusercontent.com/ShellyUSA/Hubitat-Drivers/master/resources/version.json", contentType: "application/json; charset=utf-8"]
try {
httpGet(paramsUD) { respUD ->
if (debugParse) log.debug " Version Checking - Response Data: ${respUD.data}"
def copyrightRead = (respUD.data.copyright)
state.Copyright = copyrightRead
def newVerRaw = (respUD.data.versions.Driver.(state.InternalName))
def newVer = (respUD.data.versions.Driver.(state.InternalName).replace(".", ""))
def currentVer = state.Version.replace(".", "")
state.UpdateInfo = (respUD.data.versions.UpdateInfo.Driver.(state.InternalName))
state.author = (respUD.data.author)
state.icon = (respUD.data.icon)
if(newVer == "NLS"){
state.DriverStatus = "<b>** This driver is no longer supported by $state.author **</b>"
log.warn "** This driver is no longer supported by $state.author **"
} else
if(newVer == "BETA"){
state.Status = "<b>** THIS IS BETA CODE **</b>"
log.warn "** BETA CODE **"
} else
if(currentVer < newVer){
state.DriverStatus = "<b>New Version Available (Version: $newVerRaw)</b>"
log.warn "** There is a newer version of this driver available (Version: $newVerRaw) **"
log.warn "** $state.UpdateInfo **"
} else
if(currentVer > newVer){
state.DriverStatus = "<b>You are using a Test version of this Driver (Version: $state.Version)</b>"
} else {
state.DriverStatus = "Current"
log.info "You are using the current version of this driver"
}
} // httpGet
} // try
catch (e) {
log.error "Something went wrong: CHECK THE JSON FILE AND IT'S URI - $e"
}
if(state.DriverStatus == "Current"){
state.UpdateInfo = "Up to date"
sendEvent(name: "DriverUpdate", value: state.UpdateInfo)
sendEvent(name: "DriverStatus", value: state.DriverStatus)
} else {
sendEvent(name: "DriverUpdate", value: state.UpdateInfo)
sendEvent(name: "DriverStatus", value: state.DriverStatus)
}
sendEvent(name: "DriverAuthor", value: "sgrayban")
sendEvent(name: "DriverVersion", value: state.Version)
}