Shelly RGBW2 doesn't respond to Hue commands

I sent you a private message so that we can try to figure it out collaboratively.

Here's what @darthy_sanchez and I found so far. @darthy_sanchez, please post anything I missed or any remaining issues that you discover.

The attribute declaration for 'huelevel' is missing. Need to add this in the metadata section:

attribute "huelevel", "number"

There's a typo in CustomRGBwColor. Corrected line around line 632:

h = hsvColors[0]

There's a bug in setColor with regard to the level/huelevel attributes. Corrected line around 650:

sendEvent(name: "huelevel", value: parameters.level)

PollShellyStatus needs to set the hue, saturation, and huelevel parameters to keep them up to date. After this line, around line 366, add a similar implementation as in setColor:

// existing code above...
blue = obsStatus.lights.blue[0]
// new code below
r = red.toInteger()
g = green.toInteger()
b = blue.toInteger()
hsvColors = ColorUtils.rgbToHSV([r,g,b])
sendEvent(name: "HSV", value: hsvColors)
h = hsvColors[0]
s = hsvColors[1]
huelevel = hsvColors[2]
sendEvent(name: "hue", value: h)
sendEvent(name: "saturation", value: s)
sendEvent(name: "huelevel", value: huelevel)
// existing code below...

With those corrected, here are implementations for setHue and setSaturation

def setHue(value)
{
    PollShellyStatus()
    setColor([hue: value, saturation: device.currentValue("saturation").toInteger(), level: device.currentValue("huelevel").toInteger()])
}
def setSaturation(value)
{
    PollShellyStatus()
    setColor([hue: device.currentValue("hue").toInteger(), saturation: value, level: device.currentValue("huelevel").toInteger()])
}
1 Like

Nothing to add from my part, except that everything works fine and a few remarks:
-no need for RGBW2 Group bindings anymore; MirrorMe app is now able to discover the RGBW2's
-hue values from RGBGenie remote are correctly translated at the RGBW2
-level and saturation values changed from other inputs (driver, shelly app) are interpreted correctly by the RGBW2 driver when calling PollShellyStatus inside setHue function and will remain constant when changing color from RGB Genie.
-there is a 1 sec delay in reaction and it seems to be only slightly smaller if I don't call the PollShellyStatus function, so this workaround doesn't have a noticeable impact in the overall delay. However, I would like to see RGBGenie (@Gnant) throw in a nicer HSL dial on the next one :wink:
Thank you @tomw for fixing this one! @Evilborg, maybe you can take a look at this and even upload it to the rep (if you approve it, of course)!

1 Like

Can you do a pull request so i can look at the changes please ? If not the fix will stay here.

I don't have a working instance of the modified code or a device to test with, so I think it would be more productive for @darthy_sanchez to submit the code that they have tested and confirmed to be working. What do you think, @darthy_sanchez?

1 Like

I made the changes -- I believe they are correct if you want to look at it before I make the commit I would appreciate it.

1 Like

Ah, looks like we were both hacking at it in parallel. Where can I view your changes?

1 Like

It's local -- I only changed the rgbw2 code for now..

In your fix for def setColor() I kept the level so its reflected correctly and just added your line..

sendEvent(name: "level", value: parameters.level)
sendEvent(name: "huelevel", value: parameters.level)

It wasn't a typo :smiley:

/**
 *
 *  Shelly RGBW[2]/RGBW+ Device Handler
 *  Raw code is located at https://gitlab.borgnet.us:8443/sgrayban/shelly-drivers/raw/master/Drivers/Shelly/Shelly-RGBW2.groovy
 *
 *  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
 *
 *-------------------------------------------------------------------------------------------------------------------
 *
 * See all the Shelly Products at https://shelly.cloud/
 *
 *  TODO - Finish up code for White mode to control all 4 channels 0-3
 *
 *  Changes:
 *  1.5.6 - Changed Copyright to new company
 *        - Removed all white settings as Shelly uses 2 different FW for colour and white now
 *        - Added NTP server preference
 *        - You can now set the device name
 *        - Added manual or polling only refresh
 *        - Re-added capability "Switch"
 *  1.5.5 - Fixed the secure login username password (!!Note: Passwords must not contain these special characters &?)
 *  1.5.4 - Added code that will allow you to upgrade the device firmware and will auto-refresh in 30 seconds.
 *  1.5.2 - Removing old RGB <> HSV methods
 *        - Added donation link
 *        - Removed importURL because it can't be used anymore
 *        - New location for code
 *  1.5.1 - New attributes rgbCode, MAC, RGB, etc.....
 *        - ColourMap now works in dashboard and in device menu
 *        - Set colour now works in Alexa
 *        - New json parsing.. much cleaner since I can troll the entire tree in one shot
 *        - Refresh change
 *        - ColorMode moved to preferences
 *        - Setting colour via Alexa works.. in theory it should work in GH also
 *        - Device now works in RM
 *        - Support for username/password
 *  1.5.0 - Code for the future RGBW2+
 *        - Use of selecting lightEffects using static Map
 *          and setNextEffect setPreviousEffect commands
 *        - setLevel works in Dashboard
 *        - Predefined colours
 *        - 1 codebase for RGBW2 and RGBW2+ selectable by deviceType()
 *        - New refresh rate code (djgutheinz)
 *        - Change Level added. This feature will step up/down in light percentage with a command to stop the level change.
 *  1.0.0 - Initial release
 *
 */

import hubitat.helper.ColorUtils
import groovy.transform.Field
import groovy.json.*

def deviceType() { return "rgbw2" }
@Field static Map lightEffects = [1:"Meteor Shower",2:"Gradual Change",3:"Flash"]

//def deviceType() { return "rgbw2Plus" } // Waiting for device 4(?) months
//@Field static Map lightEffects = [1:"Meteor Shower",2:"Gradual Change",3:"Breath",4:"Flash",5:"Gradual On/Off",6:"Red/Green Change"]

//	==========================================================

def setVersion(){
	state.Version = "1.5.6"
	state.InternalName = "ShellyRGBW"
}

metadata {
	definition (
        name: "Shelly ${deviceType()}",
		namespace: "sgrayban",
		author: "Scott Grayban"
		)
	{
        capability "Actuator"
        capability "Sensor"
        capability "Refresh" // refresh command
        capability "Switch"
        capability "SwitchLevel"
        capability "Polling"
        capability "PowerMeter"
        capability "ChangeLevel"
        capability "Light"
        capability "LightEffects"
        capability "ColorMode"
        capability "ColorControl"
        capability "SignalStrength"
        
        // Color shortcut commands
        command "Red"
        command "Blue"
        command "Green"
        command "White"
        command "Cyan"
        command "Magenta"
        command "Orange"
        command "Purple"
        command "Yellow"
        command "Pink"
        command "WarmWhite"
        command "SoftWhite"
        command "Daylight"
        command "ColdWhite"
        command "ClearBlueSky"
        command "RebootDevice"
        command "CustomRGBwColor", ["r","g","b","w"]
        command "UpdateDeviceFW" // ota?update=1

        if (deviceType == "rgbw2Plus") {
        command "setCustomEffect", [[name:"Effect to use (0=OFF)", type: "ENUM", description: "Effect to use (0=OFF)", constraints: ["0","1","2","3","4","5","6",]]]
        }
        else {
        command "setCustomEffect", [[name:"Effect to use (0=OFF)", type: "ENUM", description: "Effect to use (0=OFF)", constraints: ["0","1","2","3"]]]
        }

        attribute "colorMode", "string"
        attribute "hexCode", "string"
        attribute "effectName", "string"
        attribute "lightEffects", "string"
        attribute "overpower", "string"
        attribute "dcpower", "string"

        attribute "WiFiSignal", "string"
        attribute "MAC", "string"
        attribute "IP", "string"
        attribute "SSID", "string"
        attribute "FW_Update_Needed", "string"

        attribute "cloud", "string"
        attribute "power", "string"
        attribute "overpower", "string"

        attribute "RGBw", "string"
        attribute "RGB", "string"
        attribute "HEX", "string"
        attribute "HSV", "string"
        attribute "hue", "number"
        attribute "saturation", "number"
        attribute "level", "number"
        attribute "huelevel", "number"
        attribute "cloud_connected", "string"
        attribute "DeviceType", "string"
        attribute "LastRefresh", "string"
        attribute "DriverStatus", "string"
        attribute "DeviceName", "string"
        attribute "NTPServer", "string"
    }

	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"]

    def power_Type = [:]
        power_Type << ["24" : "24v"]
        power_Type << ["12" : "12v"]

	input("ip", "string", title:"Shelly IP Address:", description:"EG; 192.168.0.100", 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 (ip != null) {
        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
        input name: "devicename", type: "text", title: "Give your device a name:", description: "EG; Location/Room<br>NO SPACES in name", required: false
        input ("powerType", "enum", title: "DC Power voltage:", options: power_Type, defaultValue: true, required: true)
        input ("refresh_Rate", "enum", title: "Device Refresh Rate", 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"] //RK
    }
	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 located</center>", description: "<center><a href='http://shelly-api-docs.shelly.cloud/'>[here]</a></center>"
	input name: "Donate", type: "text", title: "<center><font color=blue>Donate</font><br>If you like my drivers please donate</center>", description: "<center><a href='https://paypal.me/sgrayban'>[here]</a></center>"
	}
}

/*
    if (getDataValue("deviceType") == "SHRGBW2") {
        def lightEffects = [1:"Meteor Shower",2:"Gradual Change",3:"Flash"]
    } else {
        def lightEffects = [1:"Meteor Shower",2:"Gradual Change",3:"Breath",4:"Flash",5:"Gradual On/Off",6:"Red/Green Change"]
    }
*/

def installed() {
    def le = new groovy.json.JsonBuilder(lightEffects)
    sendEvent(name:"lightEffects",value:le)
    log.debug "Installed ${device.id}"
    state.DeviceName = "NotSet"
}

def uninstalled() {
    log.debug "Uninstalled"
    unschedule()
}

def initialize() {
	log.info "initialize"
}

def poll() {
	if (locale == "UK") {
	if (txtEnable) log.info "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") {
	if (txtEnable) log.info "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'"
    refresh()
}

def updated() {
    log.info "Preferences updated..."
    log.warn "Debug logging is: ${debugOutput == true}"
    log.warn "JSON parsing logging is: ${debugParse == true}"
    log.warn "Description Text logging is: ${txtEnable == true}"
    unschedule()

    switch(powerType) {
      case "24" :
        sendSwitchCommand "/settings?dcpower=true"
        if (txtEnable) log.info "Executing dcpower=true"
        break
      case "12" :
        sendSwitchCommand "/settings?dcpower=false"
        if (txtEnable) log.info "Executing dcpower=false"
        break
    }

    switch(refresh_Rate) {
		case "1 min" :
			runEvery1Minute(autorefresh)
			break
		case "5 min" :
			runEvery5Minutes(autorefresh)
			break
		case "15 min" :
			runEvery15Minutes(autorefresh)
			break
		case "manual" :
			unschedule(autorefresh)
            log.info "Autorefresh disabled"
            break
	}

    if (txtEnable) log.info ("Refresh set for every ${refresh_Rate} minute(s).")
    if (debugOutput) runIn(1800,logsOff)
	if (txtEnable) runIn(600,txtOff)
    if (debugParse) runIn(600,parseOff)
    
    state.colorMode = "${ColorMode}"
    state.LastRefresh = new Date().format("YYYY/MM/dd \n HH:mm:ss", location.timeZone)
    
    if (devType == "rgbw2Plus") {
    state.lightEffects = [1:"Meteor Shower",2:"Gradual Change",3:"Breath",4:"Flash",5:"Gradual On/Off",6:"Red/Green Change"]
    }
    else {
    state.lightEffects = [1:"Meteor Shower",2:"Gradual Change",3:"Flash"]
    }
    sendEvent(name: "lightEffects", value: "${state.lightEffects}" )
    if (txtEnable) log.info ("state.lightEffects: ${state.lightEffects}" )

    sendSwitchCommand "/settings?sntp_server=${ntp_server}"
    sendSwitchCommand "/settings?name=${devicename}"

    dbCleanUp()
    version()
    refresh()
}

def dbCleanUp() {
	state.remove("rssi")
	state.remove("MAC")
	state.remove("IP")
  	state.remove("SSID")
    state.remove("FW_Update")
    state.remove("cloud")
    state.remove("power")
    state.remove("overpower")
    state.remove("switch")
    state.remove("mode")
    state.remove("RGBw")
    state.remove("RGB")
    state.remove("level")
    state.remove("lightEffect")
    state.remove("red")
    state.remove("green")
    state.remove("blue")
    state.remove("white")
    state.remove("has_update")
    state.remove("Status")
    state.remove("UpdateInfo")
    state.remove("colorMode")
}
def ping() {
	if (txtEnable) log.info "ping"
	refresh()
}

def refresh() {
    PollShellySettings()
    PollShellyStatus()
    PollShellySettings()
}

def logsOff(){
	log.warn "debug logging auto disabled..."
	device.updateSetting("debugOutput",[value:"false",type:"bool"])
}

def parseOff(){
	log.warn "Json logging auto disabled..."
	device.updateSetting("debugParse",[value:"false",type:"bool"])
}

def txtOff(){
	log.warn "Description text logging auto disabled..."
	device.updateSetting("debugParse",[value:"false",type:"bool"])
}

def parse(description) {
    runIn(2, refresh)
}

def PollShellyStatus(){
 logDebug "Shelly Status called"
    def paramsStatus = [uri: "http://${username}:${password}@${ip}/status"]
try {
    httpGet(paramsStatus) {
        respStatus -> respStatus.headers.each {
        logJSON "ResponseStatus: ${it.name} : ${it.value}"
    }
        obsStatus = respStatus.data

        logJSON "paramsStatus: ${paramsStatus}"
        logJSON "responseStatus contentType: ${respStatus.contentType}"
	    logJSON  "responseStatus data: ${respStatus.data}"

       // def each state 
        state.rssi = obsStatus.wifi_sta.rssi
        state.ssid = obsStatus.wifi_sta.ssid
        state.mac = obsStatus.mac
        state.has_update = obsStatus.has_update
        state.effect = obsStatus.lights.effect[0]

        ison = obsStatus.lights.ison[0]
        power = obsStatus.meters.power[0]
        mode = obsStatus.mode
        red = obsStatus.lights.red[0]
        green = obsStatus.lights.green[0]
        blue = obsStatus.lights.blue[0]
        rgbwCode = "${red},${green},${blue},${white}"
        rgbCode = "${red},${green},${blue}"

        r = red.toInteger()
        g = green.toInteger()
        b = blue.toInteger()
        hsvColors = ColorUtils.rgbToHSV([r,g,b])
        sendEvent(name: "HSV", value: hsvColors)
        h = hsvColors[0]
        s = hsvColors[1]
        huelevel = hsvColors[2]
        sendEvent(name: "hue", value: h)
        sendEvent(name: "saturation", value: s)
        sendEvent(name: "huelevel", value: huelevel)
        
//        def idstr = state.mac
//        deviceid = idstr.substring(6,6)
//        sendEvent(name: "DeviceID", value: deviceid)
        
/*
-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);
        }

        sendEvent(name: "rssi", value: state.rssi)
        sendEvent(name: "MAC", value: state.mac)
        sendEvent(name: "SSID", value: state.ssid)
        sendEvent(name: "power", value: power)
        sendEvent(name: "level", value: obsStatus.lights.gain[0])
        sendEvent(name: "overpower", value: obsStatus.lights.overpower[0])
        sendEvent(name: "colorMode", value: mode)
        sendEvent(name: "RGBw", value: rgbwCode)
        if (txtEnable) log.info "rgbCode = $red,$green,$blue,$white"
        sendEvent(name: "RGB", value: rgbCode)
        Hex = ColorUtils.rgbToHEX( [red, blue, green] )
        sendEvent(name: "HEX", value: Hex)

        if (ison == true) {
            sendEvent(name: "switch", value: "on")
        } else {
            sendEvent(name: "switch", value: "off")
        }

        if (obsStatus.cloud.enabled == true) {
            sendEvent(name: "cloud", value: "<font color='green'>Enabled</font>")
        } else {
            sendEvent(name: "cloud", value: "<font color='red'>Disabled</font>")
        }
            
        if (obsStatus.cloud.connected == true) {
            sendEvent(name: "cloud_connected", value: "<font color='green'>Connected</font>")
        } else {
            sendEvent(name: "cloud_connected", value: "<font color='red'>Not Connected</font>")
        }
// FW Updates
   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>")
    }

// send Effects
    if (state.effect == 0) {
        if (txtEnable) log.info "sendEvent effect None"
        sendEvent(name: "effectName", value: "Disabled")
    }
    if (state.effect == 1) {
        if (txtEnable) log.info "sendEvent effect Meteor shower"
        sendEvent(name: "effectName", value: "Meteor Shower")
    }
    if (state.effect == 2) {
        if (txtEnable) log.info "sendEvent effect Gradual change"
        sendEvent(name: "effectName", value: "Gradual Change")
    }
// RGBW2 only has 1-3 effects plus 0 which is off
// RGBW2+ has 6 effects plus 0 which is off
    if (state.effect == 3) {
    if (devType == "rgbw2Plus") {
            if (txtEnable) log.info "sendEvent effect Breath"
            sendEvent(name: "effectName", value: "Breath")
    } else {
            if (txtEnable) log.info "sendEvent effect Flash"
            sendEvent(name: "effectName", value: "Flash")
        }
    }

    if (devType == "rgbw2Plus") {
    if (state.effect == 4) {
        if (txtEnable) log.info "sendEvent effect Flash"
        sendEvent(name: "effectName", value: "Flash")
    }
    if (state.effect == 5) {
        if (txtEnable) log.info "sendEvent effect Gradual On/Off"
        sendEvent(name: "effectName", value: "Gradual On/Off")
    }
    if (state.effect == 6) {
        if (txtEnable) log.info  "sendEvent effect Red/Green Change"
        sendEvent(name: "effectName", value: "Red/Green Change")
    }
}

 
} // End try
        
    } catch (e) {
        log.error "something went wrong: $e"
    }
    
} // End PollShellyStatus

def PollShellySettings(){
 logDebug "Shelly Settings called"
    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 "paramsSettings: ${paramsSettings}"
        logJSON "responseSettings contentType: ${respSettings.contentType}"
	    logJSON "responseSettings data: ${respSettings.data}"

        state.DeviceType = obsSettings.device.type
        state.ShellyHostname = obsSettings.device.hostname
        state.sntp_server = obsSettings.sntp.server
        sendEvent(name: "NTPServer", value: state.sntp_server)
        
        sendEvent(name: "DeviceType", value: state.DeviceType)
        
        updateDataValue("model", state.DeviceType)
        updateDataValue("ShellyHostname", state.ShellyHostname)
        updateDataValue("ShellyIP", obsSettings.wifi_sta.ip)
        updateDataValue("ShellySSID", obsSettings.wifi_sta.ssid)
        updateDataValue("manufacturer", "Allterco Robotics")
        updateDataValue("MAC", state.mac)


// dcpower
    if (obsSettings.dcpower == 0) {
        if (txtEnable) log.info "sendEvent dcpower=12v"
        sendEvent(name: "dcpower", value: "12v")
    }
    if (obsSettings.dcpower == 1) {
        if (txtEnable) log.info "sendEvent dcpower=24v"
        sendEvent(name: "dcpower", value: "24v")
    }
    
//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}"
       }
        updateDataValue("DeviceName", state.DeviceName)

    } // End try
        
    } catch (e) {
        log.error "something went wrong: $e"
    }
    
}// End PollShellySettings


//	Device Commands
//switch.on
def on() {
    if (txtEnable) log.info "Executing switch.on"
    sendSwitchCommand "/color/0?turn=on"
}

//switch.off
def off() {
    if (txtEnable) log.info "Executing switch.off"
    sendSwitchCommand "/color/0?turn=off"
}

// Colours
def Red() {
    if (txtEnable) log.info "Executing colour red"
    sendSwitchCommand "/color/0?effect=0&red=255&green=0&blue=0&white=0"
}
def Green() {
    if (txtEnable) log.info "Executing colour green"
    sendSwitchCommand "/color/0?effect=0&red=0&green=128&blue=0&white=0"
}
def Blue() {
    if (txtEnable) log.info "Executing colour blue"
    sendSwitchCommand "/color/0?effect=0&red=0&green=0&blue=255&white=0"
}
def White() {
    if (txtEnable) log.info "Executing colour white"
    sendSwitchCommand "/color/0?effect=0&red=255&green=255&blue=255&white=255"
}
def Cyan() {
    if (txtEnable) log.info "Executing colour cyan"
    sendSwitchCommand "/color/0?effect=0&red=0&green=255&blue=255&white=0"
}
def Magenta() {
    if (txtEnable) log.info "Executing colour magenta"
    sendSwitchCommand "/color/0?effect=0&red=255&green=0&blue=33&white=0"
}
def Orange() {
    if (txtEnable) log.info "Executing colour orange"
    sendSwitchCommand "/color/0?effect=0&red=255&green=102&blue=0&white=0"
}
def Purple() {
    if (txtEnable) log.info "Executing colour purple"
    sendSwitchCommand "/color/0?effect=0&red=170&green=0&blue=255&white=0"
}
def Yellow() {
    if (txtEnable) log.info "Executing colour yellow"
    sendSwitchCommand "/color/0?effect=0&red=255&green=255&blue=0&white=0"
}
def Pink() {
    if (txtEnable) log.info "Executing colour pink"
    sendSwitchCommand "/color/0?effect=0&red=255&green=20&blue=147&white=0"
}

// White mode colours
// http://planetpixelemporium.com/tutorialpages/color.html
def WarmWhite() {
   if (txtEnable) log.info "Executing colour warmWhite"
   sendSwitchCommand "/color/0?effect=0&red=255&green=154&blue=30&white=50"
}
def SoftWhite() {
    if (txtEnable) log.info "Executing colour softWhite"
    sendSwitchCommand "/color/0?effect=0&red=255&green=147&blue=41&white=25"
}
def ColdWhite() {
    if (txtEnable) log.info "Executing colour ColdWhite"
    sendSwitchCommand "/color/0?effect=0&red=255&green=255&blue=255&white=0"
}
def Daylight() {
    if (txtEnable) log.info "Executing colour daylight"
    sendSwitchCommand "/color/0?effect=0&red=255&green=255&blue=251&white=250"
}
def ClearBlueSky() {
    if (txtEnable) log.info "Executing colour daylight"
    sendSwitchCommand "/color/0?effect=0&red=64&green=156&blue=255&white=100"
}

// switch.CustomRGBwColor
def CustomRGBwColor(r,g,b,w=null) {
    if (txtEnable) log.info "Executing Custom Color Red:${r} Green:${g} Blue:${b} White=${w}"
    if (w == null) {
        sendSwitchCommand "/color/0?red=${r}&green=${g}&blue=${b}&white=0"
    } else {
        sendSwitchCommand "/color/0?red=${r}&green=${g}&blue=${b}&white=${w}"
    }
    r = r.toInteger()
    g = g.toInteger()
    b = b.toInteger()
	hsvColors = ColorUtils.rgbToHSV([r,g,b])
    sendEvent(name: "HSV", value: hsvColors)
    h = hsvColors[0]
    s = hsvColors[1]
    huelevel = hsvColors[2]
    state.hue = h
    state.saturation = s
    state.huelevel = huelevel
	sendEvent(name: "hue", value: h)
	sendEvent(name: "saturation", value: s)
	sendEvent(name: "huelevel", value: huelevel)
}

def setHue(hue) {
//    return null
    rgbColors = ColorUtils.hsvToRGB([hue,device.currentValue("level"),device.currentValue("saturation")])
    settings.enableHueInDegrees ? hue /= 3.6 : null
    limit(hue)
    h = rgbColors[0]
    log.info "hue is ${hue}"
    
    PollShellyStatus()
    setColor([hue: value, saturation: device.currentValue("saturation").toInteger(), level: device.currentValue("huelevel").toInteger()])
}

def setSaturation(value)
{
    PollShellyStatus()
    setColor([hue: device.currentValue("hue").toInteger(), saturation: value, level: device.currentValue("huelevel").toInteger()])
}

def setColor(parameters){
    logDebug "Color set to ${parameters}"
    
	sendEvent(name: "hue", value: parameters.hue)
	sendEvent(name: "saturation", value: parameters.saturation)
	sendEvent(name: "level", value: parameters.level)
	sendEvent(name: "huelevel", value: parameters.level)
    state.hue = parameters.hue
    state.saturation = parameters.saturation
    state.huelevel = parameters.level
	rgbColors = ColorUtils.hsvToRGB( [parameters.hue, parameters.saturation, parameters.level] )
    r = rgbColors[0].toInteger()
    g = rgbColors[1].toInteger()
    b = rgbColors[2].toInteger()
    w = 0
    if (txtEnable) log.info "Red: ${r},Green:${g},Blue:${b}"
    CustomRGBwColor(r,g,b,w)
}

//switch.effect
def setCustomEffect(effectnumber) {
    if (txtEnable) log.info "Executing setEffect ${effectnumber}"
    sendSwitchCommand "/color/0?effect=${effectnumber}"
}

def setEffect(String effect){
    def id = lightEffects.find{ it.value == effect }
    if (id) setEffect(id.key)
    refresh()
}

def setEffect(id){
    def descriptionText
    def efSelect = lightEffects."${id}"
    descriptionText = "${device.displayName}, effect was was set to ${efSelect}"
    if (txtEnable) log.info "${descriptionText}"
    sendEvent(name:"effectName", value:efSelect, descriptionText:descriptionText)
    descriptionText = "${device.displayName}//, colorMode is EFFECTS"
    state.crntEffectId = id
    //device specific code here
    sendSwitchCommand "/color/0?effect=${id}"
    refresh()
}

def setNextEffect(){
    def currentEffect = state.crntEffectId ?: 0
    currentEffect++
    if (devType == "rgbw2Plus") {
    if (currentEffect >= 6) currentEffect = 1
    }
    else {
    if (currentEffect >= 4) currentEffect = 1
    }
    setEffect(currentEffect)
    refresh()
}

def setPreviousEffect(){
    def currentEffect = state.crntEffectId ?: 2
    currentEffect--
    if (devType == "rgbw2Plus") {
    if (currentEffect < 1) currentEffect = 6 - 1
    }
    else {
    if (currentEffect < 1) currentEffect = 4 - 1
    }
    setEffect(currentEffect)
    refresh()
}

//switch.level
def setLevel(percent) {
    if (colorMode == "white") {
        sendSwitchCommand "/white/0?turn=on&brightness=${percent}"
        if (txtEnable) log.info ("White Mode setLevel ${percent}")
    } else
        sendSwitchCommand "/color/0?turn=on&gain=${percent}&white=${percent}"
        if (txtEnable) log.info ("Color Mode setLevel ${percent}")
}

def setLevel(percentage, rate) {
	if (percentage < 0 || percentage > 100) {
		log.warn ("$device.name $device.label: Whoa there buddy.... entered level is not from 0...100")
		return
	}
	percentage = percentage.toInteger()
    if (colorMode == "white") {
        sendSwitchCommand "/white/0?turn=on&brightness=${percentage}"
        if (txtEnable) log.info ("White Mode setLevel(x,x): rate = ${rate} // percentage = ${percentage}")
    } else
        sendSwitchCommand "/color/0?turn=on&gain=${percentage}&white=${percent}"
        if (txtEnable) log.info ("Color Mode setLevel(x,x): rate = ${rate} // percentage = ${percentage}")
}
// End Direct Device Commands

def startLevelChange(direction) {
	if (txtEnable) log.info ("startLevelChange: direction = ${direction}")
	if (direction == "up") {
		levelUp()
	} else {
		levelDown()
	}
}

def stopLevelChange() {
	if (txtEnable) log.info ("stopLevelChange")
	unschedule(levelUp)
	unschedule(levelDown)
}

def levelUp() {
	def newLevel = device.currentValue("level").toInteger() + 2
    runIn(1, refresh)
	if (newLevel > 101) { return }
	if (newLevel > 100) { newLevel = 100 }
	setLevel(newLevel, 0)
	runInMillis(500, levelUp)
}

def levelDown() {
	def newLevel = device.currentValue("level").toInteger() - 2
    runIn(1, refresh)
	if (newLevel < -1) { return }
	else if (newLevel <= 0) { off() }
	else {
		setLevel(newLevel, 0)
		runInMillis(500, levelDown)
	}
}

def autorefresh() {
	if (locale == "UK") {
	if (txtEnable) log.info "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") {
	if (txtEnable) log.info "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 ${refresh_Rate} minute(s) AUTO REFRESH" //RK
    refresh()
}

// handle commands
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(10,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)
}

private logDebug(msg) {
	if (settings?.debugOutput || settings?.debugOutput == null) {
	log.debug "$msg"
	}
}

private logJSON(msg) {
	if (settings?.debugParse || settings?.debugParse == null) {
	log.info "$msg"
	}
}

// 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: "http://sgrayban.borgnet.online:8081/scotts-projects/version.json"]
	  try {
			httpGet(paramsUD) { respUD ->
				  logJSON " 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.DriverUpdateInfo = (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(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.DriverUpdateInfo **"
				 } 
				 else if(currentVer > newVer){
					 state.DriverStatus = "<b>You are using a Test version of this Driver $state.Version (Stable Version: $newVerRaw)</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.Status == "Current"){
		   state.DriverUpdateInfo = "Up to date"
	  }
	  else {
		   sendEvent(name: "DriverUpdate", value: state.DriverUpdateInfo)
		   sendEvent(name: "DriverStatus", value: state.DriverStatus)
	  }
}

I don't follow the extra calculations in setHue. Did you intend to store any of those values away? Seems like maybe they aren't used, otherwise.

On attribute 'level', in my drivers I have always associated that with the SwitchLevel 'level' (basically, dimmer value, or gain/brightness in Shelly terms). There isn't a level attribute on ColorControl, which I assumed was because of this potential conflict. If these had been named correctly (by ST?) as HSV instead of HSL, it might have helped avoid this confusion.

Do you interpret 'level' differently? If you set it with color changes, I think it will have the unintended side effect of affecting the dimmer level. And also the other way around with setLevel.

I though level meant brightness level and wouldnt that also reflect the HUE level ? Or am I wrong in that assumption ?

Level does mean brightness in the SwitchLevel context.

In HSL (what the labels inherited from ST imply but is not what Hubitat actually uses) and HSV (what Hubitat actually uses), the L or V terms actually are part of describing the color itself, regardless of the brightness setting. I learned this the hard way when I developed my own HSL and then HSV conversions before I knew that Hubitat had a built in conversion library. :wink:

While I'm complaining, hue is on a 0-100 scale for no apparent reason, instead of 360 like it should be. The Hubitat conversion libraries already take this into account, so it only matters if you write your own.

Here are the definitions, from Wikipedia:

  • In the HSV "hexcone" model, value is defined as the largest component of a color, our M above (fig. 12b). This places all three primaries, and also all of the "secondary colors"—cyan, yellow, and magenta—into a plane with white, forming a hexagonal pyramid out of the RGB cube.[10]

{isplaystyle V=ax(R,G,B)=M}|0x0

  • In the HSL "bi-hexcone" model, lightness is defined as the average of the largest and smallest color components (fig. 12c), i.e. the mid-range of the RGB components. This definition also puts the primary and secondary colors into a plane, but a plane passing halfway between white and black. The resulting color solid is a double-cone similar to Ostwald's, shown above.[11]

{isplaystyle L=peratorname {mid} (R,G,B)={frac {1}{2}}(M+m)}|0x0

ahh ok that makes more sense then - I always assumed that huelevel was associated with the actual brightness of the light. It's actually the level of colour instead. Correct?

Something like that. I finally understand the math, but I don't claim to understand the HSL/HSV concepts fully. Hue is definitely the color (think like color family). I haven't been able to wrap my head around level and saturation, because they both seem to describe the 'amount' of color in different ways. This is a decent explanation.

1 Like

Ugg I was going to merge the PR but you deleted the repo so I can't do it anymore.

Sorry, did not want to step on toes since you were also working on it. Also, I was a couple of noodling commits in and did not want to lose context from prior to the merge.

Here is a version of what I proposed. FYI, I didn't test because I only have a Shelly Bulb, no RGBW controller.

/**
 *
 *  Shelly RGBW[2]/RGBW+ Device Handler
 *
 *  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
 *
 *-------------------------------------------------------------------------------------------------------------------
 *
 * See all the Shelly Products at https://shelly.cloud/
 *
 *  TODO - Finish up code for White mode to control all 4 channels 0-3
 *
 *  Changes:
 *  1.5.6 - Changed Copyright to new company
 *        - Removed all white settings as Shelly uses 2 different FW for colour and white now
 *        - Added NTP server preference
 *        - You can now set the device name
 *        - Added manual or polling only refresh
 *        - Re-added capability "Switch"
 *  1.5.5 - Fixed the secure login username password (!!Note: Passwords must not contain these special characters &?)
 *  1.5.4 - Added code that will allow you to upgrade the device firmware and will auto-refresh in 30 seconds.
 *  1.5.2 - Removing old RGB <> HSV methods
 *        - Added donation link
 *        - Removed importURL because it can't be used anymore
 *        - New location for code
 *  1.5.1 - New attributes rgbCode, MAC, RGB, etc.....
 *        - ColourMap now works in dashboard and in device menu
 *        - Set colour now works in Alexa
 *        - New json parsing.. much cleaner since I can troll the entire tree in one shot
 *        - Refresh change
 *        - ColorMode moved to preferences
 *        - Setting colour via Alexa works.. in theory it should work in GH also
 *        - Device now works in RM
 *        - Support for username/password
 *  1.5.0 - Code for the future RGBW2+
 *        - Use of selecting lightEffects using static Map
 *          and setNextEffect setPreviousEffect commands
 *        - setLevel works in Dashboard
 *        - Predefined colours
 *        - 1 codebase for RGBW2 and RGBW2+ selectable by deviceType()
 *        - New refresh rate code (djgutheinz)
 *        - Change Level added. This feature will step up/down in light percentage with a command to stop the level change.
 *  1.0.0 - Initial release
 *
 */

import hubitat.helper.ColorUtils
import groovy.transform.Field
import groovy.json.*

def deviceType() { return "rgbw2" }
@Field static Map lightEffects = [1:"Meteor Shower",2:"Gradual Change",3:"Flash"]

//def deviceType() { return "rgbw2Plus" } // Waiting for device 4(?) months
//@Field static Map lightEffects = [1:"Meteor Shower",2:"Gradual Change",3:"Breath",4:"Flash",5:"Gradual On/Off",6:"Red/Green Change"]

//	==========================================================

def setVersion(){
	state.Version = "1.5.6"
	state.InternalName = "ShellyRGBW"
}

metadata {
	definition (
        name: "Shelly ${deviceType()}",
		namespace: "sgrayban",
		author: "Scott Grayban",
                importUrl: "https://raw.githubusercontent.com/ShellyUSA/Hubitat-Drivers/master/Shelly-RGBW2.groovy"
		)
	{
        capability "Actuator"
        capability "Sensor"
        capability "Refresh" // refresh command
        capability "Switch"
        capability "SwitchLevel"
        capability "Polling"
        capability "PowerMeter"
        capability "ChangeLevel"
        capability "Light"
        capability "LightEffects"
        //capability "Color Mode"
        capability "ColorControl"
        capability "SignalStrength"
        
        // Color shortcut commands
        command "Red"
        command "Blue"
        command "Green"
        command "White"
        command "Cyan"
        command "Magenta"
        command "Orange"
        command "Purple"
        command "Yellow"
        command "Pink"
        command "WarmWhite"
        command "SoftWhite"
        command "Daylight"
        command "ColdWhite"
        command "ClearBlueSky"
        command "RebootDevice"
        command "CustomRGBwColor", ["r","g","b","w"]
        command "UpdateDeviceFW" // ota?update=1

        if (deviceType == "rgbw2Plus") {
        command "setCustomEffect", [[name:"Effect to use (0=OFF)", type: "ENUM", description: "Effect to use (0=OFF)", constraints: ["0","1","2","3","4","5","6",]]]
        }
        else {
        command "setCustomEffect", [[name:"Effect to use (0=OFF)", type: "ENUM", description: "Effect to use (0=OFF)", constraints: ["0","1","2","3"]]]
        }

        attribute "colorMode", "string"
        attribute "hexCode", "string"
        attribute "effectName", "string"
        attribute "lightEffects", "string"
        attribute "overpower", "string"
        attribute "dcpower", "string"

        attribute "WiFiSignal", "string"
        attribute "MAC", "string"
        attribute "IP", "string"
        attribute "SSID", "string"
        attribute "FW_Update_Needed", "string"

        attribute "cloud", "string"
        attribute "power", "string"
        attribute "overpower", "string"

        attribute "RGBw", "string"
        attribute "RGB", "string"
        attribute "HEX", "string"
        attribute "hue", "number"
        attribute "saturation", "number"
        attribute "huelevel", "number"
        attribute "level", "number"
        attribute "cloud_connected", "string"
        attribute "DeviceType", "string"
        attribute "LastRefresh", "string"
        attribute "DriverStatus", "string"
        attribute "DeviceName", "string"
        attribute "NTPServer", "string"
    }

	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"]

    def power_Type = [:]
        power_Type << ["24" : "24v"]
        power_Type << ["12" : "12v"]

	input("ip", "string", title:"Shelly IP Address:", description:"EG; 192.168.0.100", 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 (ip != null) {
        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
        input name: "devicename", type: "text", title: "Give your device a name:", description: "EG; Location/Room<br>NO SPACES in name", required: false
        input ("powerType", "enum", title: "DC Power voltage:", options: power_Type, defaultValue: true, required: true)
        input ("refresh_Rate", "enum", title: "Device Refresh Rate", 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"] //RK
    }
	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 located</center>", description: "<center><a href='http://shelly-api-docs.shelly.cloud/' target='_blank'>[here]</a></center>"
	}
}

/*
    if (getDataValue("deviceType") == "SHRGBW2") {
        def lightEffects = [1:"Meteor Shower",2:"Gradual Change",3:"Flash"]
    } else {
        def lightEffects = [1:"Meteor Shower",2:"Gradual Change",3:"Breath",4:"Flash",5:"Gradual On/Off",6:"Red/Green Change"]
    }
*/

def installed() {
    def le = new groovy.json.JsonBuilder(lightEffects)
    sendEvent(name:"lightEffects",value:le)
    log.debug "Installed ${device.id}"
    state.DeviceName = "NotSet"
}

def uninstalled() {
    log.debug "Uninstalled"
    unschedule()
}

def initialize() {
	log.info "initialize"
}

def poll() {
	if (locale == "UK") {
	if (txtEnable) log.info "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") {
	if (txtEnable) log.info "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'"
    refresh()
}

def updated() {
    log.info "Preferences updated..."
    log.warn "Debug logging is: ${debugOutput == true}"
    log.warn "JSON parsing logging is: ${debugParse == true}"
    log.warn "Description Text logging is: ${txtEnable == true}"
    unschedule()

    switch(powerType) {
      case "24" :
        sendSwitchCommand "/settings?dcpower=true"
        if (txtEnable) log.info "Executing dcpower=true"
        break
      case "12" :
        sendSwitchCommand "/settings?dcpower=false"
        if (txtEnable) log.info "Executing dcpower=false"
        break
    }

    switch(refresh_Rate) {
		case "1 min" :
			runEvery1Minute(autorefresh)
			break
		case "5 min" :
			runEvery5Minutes(autorefresh)
			break
		case "15 min" :
			runEvery15Minutes(autorefresh)
			break
		case "manual" :
			unschedule(autorefresh)
            log.info "Autorefresh disabled"
            break
	}

    if (txtEnable) log.info ("Refresh set for every ${refresh_Rate} minute(s).")
    if (debugOutput) runIn(1800,logsOff)
	if (txtEnable) runIn(600,txtOff)
    if (debugParse) runIn(600,parseOff)
    
    state.colorMode = "${ColorMode}"
    state.LastRefresh = new Date().format("YYYY/MM/dd \n HH:mm:ss", location.timeZone)
    
    if (devType == "rgbw2Plus") {
    state.lightEffects = [1:"Meteor Shower",2:"Gradual Change",3:"Breath",4:"Flash",5:"Gradual On/Off",6:"Red/Green Change"]
    }
    else {
    state.lightEffects = [1:"Meteor Shower",2:"Gradual Change",3:"Flash"]
    }
    sendEvent(name: "lightEffects", value: "${state.lightEffects}" )
    if (txtEnable) log.info ("state.lightEffects: ${state.lightEffects}" )

    sendSwitchCommand "/settings?sntp_server=${ntp_server}"
    sendSwitchCommand "/settings?name=${devicename}"

    dbCleanUp()
    version()
    refresh()
}

def dbCleanUp() {
	state.remove("rssi")
	state.remove("MAC")
	state.remove("IP")
  	state.remove("SSID")
    state.remove("FW_Update")
    state.remove("cloud")
    state.remove("power")
    state.remove("overpower")
    state.remove("switch")
    state.remove("mode")
    state.remove("RGBw")
    state.remove("RGB")
    state.remove("level")
    state.remove("lightEffect")
    state.remove("red")
    state.remove("green")
    state.remove("blue")
    state.remove("white")
    state.remove("has_update")
    state.remove("Status")
    state.remove("UpdateInfo")
    state.remove("colorMode")
}
def ping() {
	if (txtEnable) log.info "ping"
	refresh()
}

def refresh() {
    PollShellySettings()
    PollShellyStatus()
    PollShellySettings()
}

def logsOff(){
	log.warn "debug logging auto disabled..."
	device.updateSetting("debugOutput",[value:"false",type:"bool"])
}

def parseOff(){
	log.warn "Json logging auto disabled..."
	device.updateSetting("debugParse",[value:"false",type:"bool"])
}

def txtOff(){
	log.warn "Description text logging auto disabled..."
	device.updateSetting("debugParse",[value:"false",type:"bool"])
}

def parse(description) {
    runIn(2, refresh)
}

def PollShellyStatus(){
 logDebug "Shelly Status called"
    def paramsStatus = [uri: "http://${username}:${password}@${ip}/status"]
try {
    httpGet(paramsStatus) {
        respStatus -> respStatus.headers.each {
        logJSON "ResponseStatus: ${it.name} : ${it.value}"
    }
        obsStatus = respStatus.data

        logJSON "paramsStatus: ${paramsStatus}"
        logJSON "responseStatus contentType: ${respStatus.contentType}"
	    logJSON  "responseStatus data: ${respStatus.data}"

       // def each state 
        state.rssi = obsStatus.wifi_sta.rssi
        state.ssid = obsStatus.wifi_sta.ssid
        state.mac = obsStatus.mac
        state.has_update = obsStatus.has_update
        state.effect = obsStatus.lights.effect[0]

        ison = obsStatus.lights.ison[0]
        power = obsStatus.meters.power[0]
        mode = obsStatus.mode
        red = obsStatus.lights.red[0]
        green = obsStatus.lights.green[0]
        blue = obsStatus.lights.blue[0]
        rgbwCode = "${red},${green},${blue},${white}"
        rgbCode = "${red},${green},${blue}"
        
//        def idstr = state.mac
//        deviceid = idstr.substring(6,6)
//        sendEvent(name: "DeviceID", value: deviceid)
        
/*
-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);
        }

        sendEvent(name: "rssi", value: state.rssi)
        sendEvent(name: "MAC", value: state.mac)
        sendEvent(name: "SSID", value: state.ssid)
        sendEvent(name: "power", value: power)
        sendEvent(name: "level", value: obsStatus.lights.gain[0])
        sendEvent(name: "overpower", value: obsStatus.lights.overpower[0])
        sendEvent(name: "colorMode", value: mode)
        sendEvent(name: "RGBw", value: rgbwCode)
        if (txtEnable) log.info "rgbCode = $red,$green,$blue,$white"
        sendEvent(name: "RGB", value: rgbCode)
        Hex = ColorUtils.rgbToHEX( [red, blue, green] )
        sendEvent(name: "HEX", value: Hex)
        
        hsvColors = ColorUtils.rgbToHSV([red.toInteger(), green.toInteger(), blue.toInteger()])
        sendEvent(name: "hue", value: hsvColors[0])
        sendEvent(name: "saturation", value: hsvColors[1])
        sendEvent(name: "huelevel", value: hsvColors[2])

        if (ison == true) {
            sendEvent(name: "switch", value: "on")
        } else {
            sendEvent(name: "switch", value: "off")
        }

        if (obsStatus.cloud.enabled == true) {
            sendEvent(name: "cloud", value: "<font color='green'>Enabled</font>")
        } else {
            sendEvent(name: "cloud", value: "<font color='red'>Disabled</font>")
        }
            
        if (obsStatus.cloud.connected == true) {
            sendEvent(name: "cloud_connected", value: "<font color='green'>Connected</font>")
        } else {
            sendEvent(name: "cloud_connected", value: "<font color='red'>Not Connected</font>")
        }
// FW Updates
   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>")
    }

// send Effects
    if (state.effect == 0) {
        if (txtEnable) log.info "sendEvent effect None"
        sendEvent(name: "effectName", value: "Disabled")
    }
    if (state.effect == 1) {
        if (txtEnable) log.info "sendEvent effect Meteor shower"
        sendEvent(name: "effectName", value: "Meteor Shower")
    }
    if (state.effect == 2) {
        if (txtEnable) log.info "sendEvent effect Gradual change"
        sendEvent(name: "effectName", value: "Gradual Change")
    }
// RGBW2 only has 1-3 effects plus 0 which is off
// RGBW2+ has 6 effects plus 0 which is off
    if (state.effect == 3) {
    if (devType == "rgbw2Plus") {
            if (txtEnable) log.info "sendEvent effect Breath"
            sendEvent(name: "effectName", value: "Breath")
    } else {
            if (txtEnable) log.info "sendEvent effect Flash"
            sendEvent(name: "effectName", value: "Flash")
        }
    }

    if (devType == "rgbw2Plus") {
    if (state.effect == 4) {
        if (txtEnable) log.info "sendEvent effect Flash"
        sendEvent(name: "effectName", value: "Flash")
    }
    if (state.effect == 5) {
        if (txtEnable) log.info "sendEvent effect Gradual On/Off"
        sendEvent(name: "effectName", value: "Gradual On/Off")
    }
    if (state.effect == 6) {
        if (txtEnable) log.info  "sendEvent effect Red/Green Change"
        sendEvent(name: "effectName", value: "Red/Green Change")
    }
}

 
} // End try
        
    } catch (e) {
        log.error "something went wrong: $e"
    }
    
} // End PollShellyStatus

def PollShellySettings(){
 logDebug "Shelly Settings called"
    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 "paramsSettings: ${paramsSettings}"
        logJSON "responseSettings contentType: ${respSettings.contentType}"
	    logJSON "responseSettings data: ${respSettings.data}"

        state.DeviceType = obsSettings.device.type
        state.ShellyHostname = obsSettings.device.hostname
        state.sntp_server = obsSettings.sntp.server
        sendEvent(name: "NTPServer", value: state.sntp_server)
        
        sendEvent(name: "DeviceType", value: state.DeviceType)
        
        updateDataValue("model", state.DeviceType)
        updateDataValue("ShellyHostname", state.ShellyHostname)
        updateDataValue("ShellyIP", obsSettings.wifi_sta.ip)
        updateDataValue("ShellySSID", obsSettings.wifi_sta.ssid)
        updateDataValue("manufacturer", "Allterco Robotics")
        updateDataValue("MAC", state.mac)


// dcpower
    if (obsSettings.dcpower == 0) {
        if (txtEnable) log.info "sendEvent dcpower=12v"
        sendEvent(name: "dcpower", value: "12v")
    }
    if (obsSettings.dcpower == 1) {
        if (txtEnable) log.info "sendEvent dcpower=24v"
        sendEvent(name: "dcpower", value: "24v")
    }
    
//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}"
       }
        updateDataValue("DeviceName", state.DeviceName)

    } // End try
        
    } catch (e) {
        log.error "something went wrong: $e"
    }
    
}// End PollShellySettings


//	Device Commands
//switch.on
def on() {
    if (txtEnable) log.info "Executing switch.on"
    sendSwitchCommand "/color/0?turn=on"
}

//switch.off
def off() {
    if (txtEnable) log.info "Executing switch.off"
    sendSwitchCommand "/color/0?turn=off"
}

// Colours
def Red() {
    if (txtEnable) log.info "Executing colour red"
    sendSwitchCommand "/color/0?effect=0&red=255&green=0&blue=0&white=0"
}
def Green() {
    if (txtEnable) log.info "Executing colour green"
    sendSwitchCommand "/color/0?effect=0&red=0&green=128&blue=0&white=0"
}
def Blue() {
    if (txtEnable) log.info "Executing colour blue"
    sendSwitchCommand "/color/0?effect=0&red=0&green=0&blue=255&white=0"
}
def White() {
    if (txtEnable) log.info "Executing colour white"
    sendSwitchCommand "/color/0?effect=0&red=255&green=255&blue=255&white=255"
}
def Cyan() {
    if (txtEnable) log.info "Executing colour cyan"
    sendSwitchCommand "/color/0?effect=0&red=0&green=255&blue=255&white=0"
}
def Magenta() {
    if (txtEnable) log.info "Executing colour magenta"
    sendSwitchCommand "/color/0?effect=0&red=255&green=0&blue=33&white=0"
}
def Orange() {
    if (txtEnable) log.info "Executing colour orange"
    sendSwitchCommand "/color/0?effect=0&red=255&green=102&blue=0&white=0"
}
def Purple() {
    if (txtEnable) log.info "Executing colour purple"
    sendSwitchCommand "/color/0?effect=0&red=170&green=0&blue=255&white=0"
}
def Yellow() {
    if (txtEnable) log.info "Executing colour yellow"
    sendSwitchCommand "/color/0?effect=0&red=255&green=255&blue=0&white=0"
}
def Pink() {
    if (txtEnable) log.info "Executing colour pink"
    sendSwitchCommand "/color/0?effect=0&red=255&green=20&blue=147&white=0"
}

// White mode colours
// http://planetpixelemporium.com/tutorialpages/color.html
def WarmWhite() {
   if (txtEnable) log.info "Executing colour warmWhite"
   sendSwitchCommand "/color/0?effect=0&red=255&green=154&blue=30&white=50"
}
def SoftWhite() {
    if (txtEnable) log.info "Executing colour softWhite"
    sendSwitchCommand "/color/0?effect=0&red=255&green=147&blue=41&white=25"
}
def ColdWhite() {
    if (txtEnable) log.info "Executing colour ColdWhite"
    sendSwitchCommand "/color/0?effect=0&red=255&green=255&blue=255&white=0"
}
def Daylight() {
    if (txtEnable) log.info "Executing colour daylight"
    sendSwitchCommand "/color/0?effect=0&red=255&green=255&blue=251&white=250"
}
def ClearBlueSky() {
    if (txtEnable) log.info "Executing colour daylight"
    sendSwitchCommand "/color/0?effect=0&red=64&green=156&blue=255&white=100"
}

// switch.CustomRGBwColor
def CustomRGBwColor(r,g,b,w=null) {
    if (txtEnable) log.info "Executing Custom Color Red:${r} Green:${g} Blue:${b} White=${w}"
    if (w == null) {
        sendSwitchCommand "/color/0?red=${r}&green=${g}&blue=${b}&white=0"
    } else {
        sendSwitchCommand "/color/0?red=${r}&green=${g}&blue=${b}&white=${w}"
    }
}

def setHue(value)
{
    PollShellyStatus()
    setColor([hue: value, saturation: device.currentValue("saturation").toInteger(), level: device.currentValue("huelevel").toInteger()])
}

def setSaturation(value)
{
    PollShellyStatus()
    setColor([hue: device.currentValue("hue").toInteger(), saturation: value, level: device.currentValue("huelevel").toInteger()])
}

def setColor(parameters){
    logDebug "Color set to ${parameters}"
    
	sendEvent(name: "hue", value: parameters.hue)
	sendEvent(name: "saturation", value: parameters.saturation)
	sendEvent(name: "huelevel", value: parameters.level)
	rgbColors = ColorUtils.hsvToRGB( [parameters.hue, parameters.saturation, parameters.level] )
    r = rgbColors[0].toInteger()
    g = rgbColors[1].toInteger()
    b = rgbColors[2].toInteger()
    w = 0
    if (txtEnable) log.info "Red: ${r},Green:${g},Blue:${b}"
    CustomRGBwColor(r,g,b,w)
}

//switch.effect
def setCustomEffect(effectnumber) {
    if (txtEnable) log.info "Executing setEffect ${effectnumber}"
    sendSwitchCommand "/color/0?effect=${effectnumber}"
}

def setEffect(String effect){
    def id = lightEffects.find{ it.value == effect }
    if (id) setEffect(id.key)
    refresh()
}

def setEffect(id){
    def descriptionText
    def efSelect = lightEffects."${id}"
    descriptionText = "${device.displayName}, effect was was set to ${efSelect}"
    if (txtEnable) log.info "${descriptionText}"
    sendEvent(name:"effectName", value:efSelect, descriptionText:descriptionText)
    descriptionText = "${device.displayName}//, colorMode is EFFECTS"
    state.crntEffectId = id
    //device specific code here
    sendSwitchCommand "/color/0?effect=${id}"
    refresh()
}

def setNextEffect(){
    def currentEffect = state.crntEffectId ?: 0
    currentEffect++
    if (devType == "rgbw2Plus") {
    if (currentEffect >= 6) currentEffect = 1
    }
    else {
    if (currentEffect >= 4) currentEffect = 1
    }
    setEffect(currentEffect)
    refresh()
}

def setPreviousEffect(){
    def currentEffect = state.crntEffectId ?: 2
    currentEffect--
    if (devType == "rgbw2Plus") {
    if (currentEffect < 1) currentEffect = 6 - 1
    }
    else {
    if (currentEffect < 1) currentEffect = 4 - 1
    }
    setEffect(currentEffect)
    refresh()
}

//switch.level
def setLevel(percent) {
    if (colorMode == "white") {
        sendSwitchCommand "/white/0?turn=on&brightness=${percent}"
        if (txtEnable) log.info ("White Mode setLevel ${percent}")
    } else
        sendSwitchCommand "/color/0?turn=on&gain=${percent}&white=${percent}"
        if (txtEnable) log.info ("Color Mode setLevel ${percent}")
}

def setLevel(percentage, rate) {
	if (percentage < 0 || percentage > 100) {
		log.warn ("$device.name $device.label: Whoa there buddy.... entered level is not from 0...100")
		return
	}
	percentage = percentage.toInteger()
    if (colorMode == "white") {
        sendSwitchCommand "/white/0?turn=on&brightness=${percentage}"
        if (txtEnable) log.info ("White Mode setLevel(x,x): rate = ${rate} // percentage = ${percentage}")
    } else
        sendSwitchCommand "/color/0?turn=on&gain=${percentage}&white=${percent}"
        if (txtEnable) log.info ("Color Mode setLevel(x,x): rate = ${rate} // percentage = ${percentage}")
}
// End Direct Device Commands

def startLevelChange(direction) {
	if (txtEnable) log.info ("startLevelChange: direction = ${direction}")
	if (direction == "up") {
		levelUp()
	} else {
		levelDown()
	}
}

def stopLevelChange() {
	if (txtEnable) log.info ("stopLevelChange")
	unschedule(levelUp)
	unschedule(levelDown)
}

def levelUp() {
	def newLevel = device.currentValue("level").toInteger() + 2
    runIn(1, refresh)
	if (newLevel > 101) { return }
	if (newLevel > 100) { newLevel = 100 }
	setLevel(newLevel, 0)
	runInMillis(500, levelUp)
}

def levelDown() {
	def newLevel = device.currentValue("level").toInteger() - 2
    runIn(1, refresh)
	if (newLevel < -1) { return }
	else if (newLevel <= 0) { off() }
	else {
		setLevel(newLevel, 0)
		runInMillis(500, levelDown)
	}
}

def autorefresh() {
	if (locale == "UK") {
	if (txtEnable) log.info "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") {
	if (txtEnable) log.info "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 ${refresh_Rate} minute(s) AUTO REFRESH" //RK
    refresh()
}

// handle commands
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(10,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)
}

private logDebug(msg) {
	if (settings?.debugOutput || settings?.debugOutput == null) {
	log.debug "$msg"
	}
}

private logJSON(msg) {
	if (settings?.debugParse || settings?.debugParse == null) {
	log.info "$msg"
	}
}

// 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"]
	  try {
			httpGet(paramsUD) { respUD ->
				  logJSON " 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.DriverUpdateInfo = (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(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.DriverUpdateInfo **"
				 } 
				 else if(currentVer > newVer){
					 state.DriverStatus = "<b>You are using a Test version of this Driver $state.Version (Stable Version: $newVerRaw)</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.Status == "Current"){
		   state.DriverUpdateInfo = "Up to date"
	  }
	  else {
		   sendEvent(name: "DriverUpdate", value: state.DriverUpdateInfo)
		   sendEvent(name: "DriverStatus", value: state.DriverStatus)
	  }
}
1 Like

Works perfectly! Thank you very much.

Did you make these changes in the RGB bulb as well?

I took at stab at it. Same disclaimer: I didn't actually test it. :upside_down_face:

/**
 *  Shelly Bulb Device Handler
 *
 *  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
 *
 *-------------------------------------------------------------------------------------------------------------------
 *
 *  Changes:
 *  2.1.3 - Added code that will allow you to upgrade the device firmware and will auto-refresh in 30 seconds.
 *  2.1.2 - RSSI value is definded as exelent, good or poor. The actual rssi reading is under state variables.
 *  2.1.1 - New code location - https://gitlab.borgnet.us:8443/sgrayban/shelly-drivers/raw/master/Drivers/Shelly/Shelly-Bulb.groovy
 *        - Removing old RGB <> HSV methods
 *        - Added username and password
 *        - New getshellyaddress()
 *        - Changed to new hubitat.helper.ColorUtils class for colour map
 *        - ColorMap now works in dashboard
 *        - White mode is fully supported now (Bulb MUST BE SET to WHITE to use)
 *  2.0.1 - Code rewrite to support all API calls
 *  2.0.0 - New License -- Personal Use Only
 *  1.0.2 - Changed operand for refresh rate in update()
 *  1.0.1 - Changed how the update check worked if refresh rate was set to No Selection
 *  1.0.0 - Initial release
 *
 */

import groovy.transform.Field
import groovy.json.*
import hubitat.helper.ColorUtils

@Field static Map lightEffects = [1:"Meteor Shower",2:"Gradual Change",3:"Breath",4:"Flash",5:"Gradual On/Off",6:"Red/Green Change"]

metadata {
	definition (
		name: "Shelly Bulb",
		namespace: "sgrayban",
		author: "Scott Grayban",
                importUrl: "https://raw.githubusercontent.com/ShellyUSA/Hubitat-Drivers/master/Shelly-Bulb.groovy"
		)
	{
        capability "Actuator"
        capability "Sensor"
        capability "Refresh" // refresh command
        capability "Bulb"
        capability "Polling"
        capability "Change Level"
        capability "Switch Level"
        capability "Color Control"

        command "Red"
        command "Blue"
        command "Green"
        command "White"
        command "Cyan"
        command "Magenta"
        command "Orange"
        command "Purple"
        command "Yellow"
        command "Pink"
        command "WarmWhite"
        command "CoolWhite"
        command "Daylight"
        command "CustomRGBColor", ["r","g","b"]
        command "setCustomEffect", [[name:"Effect to use (0=OFF)", type: "ENUM", description: "Effect to use (0=OFF)", constraints: ["0","1","2","3","4","5","6",]]]
        command "Effect", [[name:"Effect to use", type: "ENUM", description: "Effect to use", constraints: ["0","1","2","3","4","5","6"] ] ]
        command "UpdateDeviceFW" // ota?update=1

        attribute "switch", "string"
        attribute "colorMode", "string"
        attribute "HEX", "string"
        attribute "RGB", "string"
        attribute "hue", "number"
        attribute "saturation", "number"
        attribute "huelevel", "number"
        attribute "level", "number"
        attribute "effectName", "string"
        attribute "lightEffects", "string"
        attribute "WhiteModeOnly", "tring"
        attribute "MAC", "string"
        attribute "FW_Update_Needed", "string"
        attribute "Cloud", "string"
        attribute "IP", "string"
        attribute "SSID", "string"
        attribute "Cloud_Connected", "string"
        attribute "WiFiSignal", "string"
        attribute "LastRefresh", "string"
    }

	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"]

    def Color_Mode = [:]
        Color_Mode << ["color" : "Color"]
        Color_Mode << ["white" : "White"]
        
	input("ip", "string", title:"Shelly IP Address:", description:"EG; 192.168.0.100", defaultValue:"" , required: true, displayDuringSetup: 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
	input ("ColorMode", "enum", title: "Colour Mode", options: Color_Mode, defaultValue: "color")
	input ("refresh_Rate", "enum", title: "Device Refresh Rate", options: refreshRate, defaultValue: "30 min")
	input "locale", "enum", title: "Choose refresh date format", required: true, defaultValue: true, //RK
			options: [US:"US MM/DD/YYYY",UK:"UK DD/MM/YYYY"] //RK
	input name: "debugOutput", type: "bool", title: "Enable debug logging?", defaultValue: true
	input name: "txtEnable", type: "bool", title: "Enable descriptionText logging", defaultValue: true //RK
	input name: "debugParse", type: "bool", title: "Enable JSON parse logging?", defaultValue: false
	input name: "Shellyinfo", type: "text", title: "<center><font color=blue>Info Box</font><br>Shelly API docs located</center>", description: "<center><a href='http://shelly-api-docs.shelly.cloud/' target='_blank'>[here]</a></center>"
	}
}

def setVersion(){
        state.Version = "2.1.3"
        state.InternalName = "ShellyBulb"
}

def ping() {
	if (txtEnable) log.info "ping"
	poll()
}

def initialize() {
	log.info "initialize"
}

def installed() {
    def le = new groovy.json.JsonBuilder(lightEffects)
    sendEvent(name:"lightEffects",value:le)
//    sendEvent(name: "WhiteModeOnly", value:le)
    log.debug "Installed"
}

def uninstalled() {
    log.debug "Uninstalled"
    unschedule()
}

def updated() {
    log.debug "Updated"
    log.info "Preferences updated..."
    log.warn "Debug logging is: ${debugOutput == true}"
    unschedule()

    switch(ColorMode) {
      case "color" :
        sendSwitchCommand "/settings?mode=${ColorMode}"
        if (txtEnable) log.info "Executing mode=${ColorMode}"
        break
      case "white" :
        sendSwitchCommand "/settings?mode=${ColorMode}"
        if (txtEnable) log.info "Executing mode=${ColorMode}"
        break
      default:
		sendSwitchCommand "/settings?mode=${ColorMode}"
    }

    switch(refresh_Rate) {
		case "1 min" :
			runEvery1Minute(autorefresh)
			break
		case "5 min" :
			runEvery5Minutes(autorefresh)
			break
		case "15 min" :
			runEvery15Minutes(autorefresh)
			break
		default:
			runEvery30Minutes(autorefresh)
	}
	if (txtEnable) log.info ("Refresh set for every ${refresh_Rate} minute(s).")

    if (debugOutput) runIn(1800,logsOff)
    if (debugParse) runIn(1800,logsOff)
    
    state.lightEffects = [1:"Meteor Shower",2:"Gradual Change",3:"Breath",4:"Flash",5:"Gradual On/Off",6:"Red/Green Change"]
    sendEvent(name: "lightEffects", value: "${state.lightEffects}")
    state.WhiteModeOnly = ["WarmWhite, CoolWhite & Daylight"]
    sendEvent(name: "WhiteModeOnly", value:"${state.WhiteModeOnly}")
    
    state.LastRefresh = new Date().format("YYYY/MM/dd \n HH:mm:ss", location.timeZone)

    dbCleanUp()
    version()
    refresh()
}

def refresh(){
 logDebug "Shelly Status called"
    def params1 = [uri: "http://${username}:${password}@${ip}/status"]

try {
    httpGet(params1) {
        resp1 -> resp1.headers.each {
        logJSON "Response1: ${it.name} : ${it.value}"
    }
        obs1 = resp1.data
       
        logJSON "params1: ${params1}"
        logJSON "response1 contentType: ${resp1.contentType}"
	    logJSON  "response1 data: ${resp1.data}"

       // def each state 
        state.rssi = obs1.wifi_sta.rssi
        state.ssid = obs1.wifi_sta.ssid
        state.mac = obs1.mac
        state.has_update = obs1.has_update
        state.cloud = obs1.cloud.enabled
        state.effect = obs1.lights.effect[0]
        state.cloud_connected = obs1.cloud.connected

        ison = obs1.lights.ison[0]
        colorMode = obs1.lights.mode[0]
        red = obs1.lights.red[0]
        green = obs1.lights.green[0]
        blue = obs1.lights.blue[0]
        rgbCode = "${red},${green},${blue}"
        
        sendEvent(name: "MAC", value: state.mac)
        sendEvent(name: "SSID", value: state.ssid)
        sendEvent(name: "level", value: obs1.lights.gain[0])
        sendEvent(name: "colorMode", value: colorMode)
        sendEvent(name: "IP", value: obs1.wifi_sta.ip)

/*
-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);
        }

        updateDataValue("model", state.DeviceType)
        updateDataValue("ShellyHostname", state.ShellyHostname)
        updateDataValue("ShellyIP", obs1.wifi_sta.ip)
        updateDataValue("ShellySSID", state.ssid)
        updateDataValue("manufacturer", "Allterco Robotics")
        updateDataValue("MAC", state.mac)

        if (colorMode == "color") if (txtEnable) log.info "rgbCode = $red,$green,$blue,$white"
        if (colorMode == "color") sendEvent(name: "RGB", value: rgbCode)
        if (colorMode == "color") Hex = ColorUtils.rgbToHEX( [red, blue, green] )
        if (colorMode == "color") sendEvent(name: "HEX", value: Hex)
            
        hsvColors = ColorUtils.rgbToHSV([red.toInteger(), green.toInteger(), blue.toInteger()])
        sendEvent(name: "hue", value: hsvColors[0])
        sendEvent(name: "saturation", value: hsvColors[1])
        sendEvent(name: "huelevel", value: hsvColors[2])

        if (ison == true) {
            sendEvent(name: "switch", value: "on")
        } else {
            sendEvent(name: "switch", value: "off")
        }

        if (cloud == enabled) {
            sendEvent(name: "Cloud", value: "<font color='green'>Enabled</font>")
        } else {
            sendEvent(name: "Cloud", value: "<font color='red'>Disabled</font>")
        }

        state.cloudConnected = obs1.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>")
        }
        
// FW Updates
        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>")
        }

// send Effects
        if (state.effect == 0) {
            if (txtEnable) log.info "sendEvent effect None"
            sendEvent(name: "effectName", value: "Disabled")
        }
        if (state.effect == 1) {
            if (txtEnable) log.info "sendEvent effect Meteor shower"
            sendEvent(name: "effectName", value: "Meteor Shower")
        }
        if (state.effect == 2) {
            if (txtEnable) log.info "sendEvent effect Gradual change"
            sendEvent(name: "effectName", value: "Gradual Change")
        }
        if (state.effect == 3) {
            if (txtEnable) log.info "sendEvent effect Breath"
            sendEvent(name: "effectName", value: "Breath")
        }
        if (state.effect == 4) {
            if (txtEnable) log.info "sendEvent effect Flash"
            sendEvent(name: "effectName", value: "Flash")
        }
        if (state.effect == 5) {
            if (txtEnable) log.info "sendEvent effect Gradual On/Off"
            sendEvent(name: "effectName", value: "Gradual On/Off")
        }
        if (state.effect == 6) {
            if (txtEnable) log.info  "sendEvent effect Red/Green Change"
            sendEvent(name: "effectName", value: "Red/Green Change")
        }
} // End try
        
    } catch (e) {
        log.error "something went wrong: $e"
    }
    
} // End refresh

def getSettings(){
 logDebug "Shelly Settings called"
    def params = [uri: "http://${username}:${password}@${ip}/settings"]

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}"

        state.DeviceType = obs.device.type
        state.ShellyHostname = obs.device.hostname

} // End try
        
    } catch (e) {
        log.error "something went wrong: $e"
    }
    
} // End refresh

//switch.on
def on() {
    if (txtEnable) log.info "Executing switch.on"
    sendSwitchCommand "/light/0?turn=on"
}

//switch.off
def off() {
    if (txtEnable) log.info "Executing switch.off"
    sendSwitchCommand "/light/0?turn=off"
}

def Red() {
    if (txtEnable) log.info "Executing colour red"
    sendSwitchCommand "/light/0?turn=on&gain=100&effect=0&red=255&green=0&blue=0&turn=on"
}
def Green() {
    if (txtEnable) log.info "Executing colour green"
    sendSwitchCommand "/light/0?turn=on&gain=100&effect=0&red=0&green=128&blue=0&turn=on"
}
def Blue() {
    if (txtEnable) log.info "Executing colour blue"
    sendSwitchCommand "/light/0?turn=on&gain=100&effect=0&red=0&green=0&blue=255&turn=on"
}
def White() {
    if (txtEnable) log.info "Executing colour white"
    sendSwitchCommand "/light/0?turn=on&gain=100&effect=0&red=255&green=255&blue=255&turn=on"
}
def Cyan() {
    if (txtEnable) log.info "Executing colour cyan"
    sendSwitchCommand "/light/0?turn=on&gain=100&effect=0&red=0&green=255&blue=255&turn=on"
}
def Magenta() {
    if (txtEnable) log.info "Executing colour magenta"
    sendSwitchCommand "/light/0?turn=on&gain=100&effect=0&red=255&green=0&blue=33&turn=on"
}
def Orange() {
    if (txtEnable) log.info "Executing colour orange"
    sendSwitchCommand "/light/0?turn=on&gain=100&effect=0&red=255&green=102&blue=0&turn=on"
}
def Purple() {
    if (txtEnable) log.info "Executing colour purple"
    sendSwitchCommand "/light/0?turn=on&gain=100&effect=0&red=170&green=0&blue=255&turn=on"
}
def Yellow() {
    if (txtEnable) log.info "Executing colour yellow"
    sendSwitchCommand "/light/0?turn=on&gain=100&effect=0&red=255&green=255&blue=0&turn=on"
}
def Pink() {
    if (txtEnable) log.info "Executing colour pink"
    sendSwitchCommand "/light/0?turn=on&gain=100&effect=0&red=255&green=20&blue=147&turn=on"
}

// White mode colours
// http://www.westinghouselighting.com/color-temperature.aspx
// http://planetpixelemporium.com/tutorialpages/light.html
// temp is in Kevin
def WarmWhite() {
   if (txtEnable) log.info "Executing colour warmWhite"
   if (colorMode == "white") sendSwitchCommand "/light/0?turn=on&white=0&temp=3000"
}
def CoolWhite() {
    if (txtEnable) log.info "Executing colour coolWhite"
    if (colorMode == "white") sendSwitchCommand "/light/0?turn=on&white=50&temp=4500"
}
def Daylight() {
    if (txtEnable) log.info "Executing colour daylight"
    if (colorMode == "white") sendSwitchCommand "/light/0?turn=on&white=100&temp=6500"
}

// switch.CustomRGBwColor
def CustomRGBColor(r,g,b) {
    if (txtEnable) log.info "Executing Custom Color Red:${r} Green:${g} Blue:${b}"
    if (w == null) {
        sendSwitchCommand "/light/0?turn=on&gain=100&red=${r}&green=${g}&blue=${b}"
    } else {
        sendSwitchCommand "/light/0?turn=on&gain=100&red=${r}&green=${g}&blue=${b}"
    }
}

def setHue(value)
{
    refresh()
    setColor([hue: value, saturation: device.currentValue("saturation").toInteger(), level: device.currentValue("huelevel").toInteger()])
}

def setSaturation(value)
{
    refresh()
    setColor([hue: device.currentValue("hue").toInteger(), saturation: value, level: device.currentValue("huelevel").toInteger()])
}

def setColor( parameters ){
    logDebug "Color set to ${parameters}"
    
	sendEvent(name: "hue", value: parameters.hue)
	sendEvent(name: "saturation", value: parameters.saturation)
	sendEvent(name: "huelevel", value: parameters.level)    
	rgbColors = ColorUtils.hsvToRGB( [parameters.hue, parameters.saturation, parameters.level] )
    r = rgbColors[0].toInteger()
    g = rgbColors[1].toInteger()
    b = rgbColors[2].toInteger()
    def Hex = ColorUtils.rgbToHEX([r, g, b])
    if (txtEnable) log.info "Red: ${r},Green:${g},Blue:${b}"
    if (txtEnable) log.info "HEX: ${HEX}"
    CustomRGBColor(r,g,b)
}

//switch.effect
def Effect(number) {
    if (txtEnable) log.info "Executing colour white"
    sendSwitchCommand "/light/0?effect=${number}"
}

//switch.mode
def Mode(string) {
    if (txtEnable) log.info "Executing colour mode"
    sendSwitchCommand "/settings?mode=${string}"
}

//switch.gain
def setLevel() {
    if (txtEnable) log.info "Executing Brightness Level"
    sendLevelSwitchCommand "turn=on&effect=0&gain=${number}"
}

//switch.level
//  TODO: Need to add rate and transition to Shelly FW
def setLevel(percent, transTime) {
    if (ColorMode == "white") {
        sendLevelCommand "turn=on&brightness=${percent}"
        if (txtEnable) log.info ("White Mode setLevel(x): transition time = ${transTime}")
    } else
        sendLevelCommand "turn=on&gain=${percent}"
        if (txtEnable) log.info ("Color Mode setLevel(x): transition time = ${transTime}")
}

def setRateLevel(percentage, rate) {
	if (percentage < 0 || percentage > 100) {
		log.warn ("$device.name $device.label: Entered brightness is not from 0...100")
		return
	}
//	percentage = percentage.toInteger()
    if (ColorMode == "white") {
        sendLevelCommand "turn=on&brightness=${percentage}"
        if (txtEnable) log.info ("White Mode setLevel(x,x): rate = ${rate} // percentage = ${percentage}")
    } else
        sendLevelCommand "turn=on&gain=${percentage}"
        if (txtEnable) log.info ("Color Mode setLevel(x,x): rate = ${rate} // percentage = ${percentage}")
}
// End Direct Device Commands

def startLevelChange(direction) {
	if (txtEnable) log.info ("startLevelChange: direction = ${direction}")
	if (direction == "up") {
		levelUp()
	} else {
		levelDown()
	}
}

def stopLevelChange() {
	if (txtEnable) log.info ("stopLevelChange")
	unschedule(levelUp)
	unschedule(levelDown)
}

def levelUp() {
	def newLevel = device.currentValue("level").toInteger() + 2
	if (newLevel > 101) { return }
	if (newLevel > 100) { newLevel = 100 }
	setRateLevel(newLevel, 0)
	runInMillis(500, levelUp)
}

def levelDown() {
	def newLevel = device.currentValue("level").toInteger() - 2
	if (newLevel < -1) { return }
	else if (newLevel <= 0) { off() }
	else {
		setRateLevel(newLevel, 0)
		runInMillis(500, levelDown)
	}
}

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") {
	if (debugOutput) log.info "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") {
	if (debugOutput) log.info "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
    getSettings()
    refresh()
}

private logDebug(msg) {
	if (settings?.debugOutput || settings?.debugOutput == null) {
	log.debug "$msg"
	}
}

private logJSON(msg) {
	if (settings?.debugParse || settings?.debugParse == null) {
	log.info "$msg"
	}
}

// handle commands
//RK Updated to include last refreshed
def poll() {
	if (locale == "UK") {
	if (debugOutput) log.info "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") {
	if (debugOutput) log.info "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
    dbCleanUp()
    refresh()
}

def sendSwitchCommand(action) {
    if (txtEnable) log.info "Calling ${action}"
	def path = path
	def body = body 
	def headers = [:]
    if (username != null) {
        headers.put("HOST", "${username}:${password}@${ip}")
    } else {
        headers.put("HOST", "${ip}")
    }
	headers.put("Content-Type", "application/x-www-form-urlencoded")
    runIn(2, refresh)

	try {
		def hubAction = new hubitat.device.HubAction(
			method: method,
			path: action,
			body: body,
			headers: headers
			)
		logDebug hubAction
		return hubAction
	}
	catch (Exception e) {
        logDebug "sendSwitchCommand hit exception ${e} on ${hubAction}"
	}
}

def sendLevelCommand(action) {
    if (txtEnable) log.info "Setting Level ${action}"
    if (username != null) {
        host = "${username}:${password}@${ip}"
    } else {
        host = "${ip}"
    }
    sendHubCommand(new hubitat.device.HubAction(
      method: "POST",
      path: "/light/0",
      body: action,
      headers: [
        HOST: host,
        "Content-Type": "application/x-www-form-urlencoded"
      ]
    ))
    refresh()
}

def UpdateDeviceFW() {
    if (txtEnable) log.info "Updating Device FW"
    if (username != null) {
        host = "${username}:${password}@${ip}"
    } else {
        host = "${ip}"
    }
	sendEvent(name: "FW_Update_Needed", value: "Please Wait..Updating FW.")
    sendHubCommand(new hubitat.device.HubAction(
      method: "POST",
      path: "/ota?update=1",
      body: action,
      headers: [
        HOST: host,
        "Content-Type": "application/x-www-form-urlencoded"
      ]
    ))
    runIn(30,refresh)
}

def parse(description) {
    return
}

private dbCleanUp() {
//	unschedule()
    state.remove("UpdateInfo")
    state.remove("Current")
	state.remove("InternalName")
	state.remove("version")
	state.remove("Version")
	state.remove("Status")
}

// 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"]
	  try {
			httpGet(paramsUD) { respUD ->
				  if (txtEnable) log.warn " Version Checking - Response Data: ${respUD.data}"   // Troubleshooting Debug Code - Uncommenting this line should show the JSON response from your webserver
				  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(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: $newVerRaw)</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)
}

any reason why you removed the power meter capability ?

It wasn't there for the bulb on the ShellyUSA master branch. Did I grab the wrong one?

whoops I forgot to commit those changes months ago..... LOL let me push that