WLED Integration / Drivers

thanks.

Here are the options when using webcore.

The setEffect option allows these choices:

So I can't figure how to apply this:

solved, I think.

Posted over at the webcore forum (WLED webcore query (can't duplicate function from RM) - Hubitat - webCoRE Community Forum).

This reply came along:

I can't code, at all. But I tried just replacing all references to "setEffect" with "setEffectCustom".

ie.

/**
*
*  WLED Device Type
*
*  Author: bryan@joyful.house
*
*  Date: 2019-11-27
*/
import java.net.URLEncoder

metadata {
    definition (name: "WLED-mod", namespace: "joyfulhouse", author: "Bryan Li") {
        capability "Color Control"
        capability "Color Temperature"
        capability "Refresh"
        capability "Switch"
        capability "Switch Level"
        capability "Light"
        capability "ColorMode"

        capability "Alarm"

        attribute "colorName", "string"
        attribute "effectName", "string"
        attribute "paletteName", "string"

        //command "getEffects"
        //command "getPalettes"
        command "setEffectCustom", 
            [
                [name:"FX ID", type: "NUMBER", description: "Effect ID", constraints: []],
                [name:"FX Speed", type: "NUMBER", description: "Relative Effect Speed (0-255)", constraints: []],
                [name:"FX Intensity", type: "NUMBER", description: "Effect Intensity(0-255)", constraints: []],
                [name:"Color Palette", type: "NUMBER", description: "Color Palette", constraints: []]
            ]
    }

    // simulator metadata
    simulator {
    
    }
    
    // Preferences
    preferences {
        input "uri", "text", title: "URI", description: "(eg. http://[wled_ip_address])", required: true, displayDuringSetup: true
        input name: "ledSegment", type: "number", title: "LED Segment", defaultValue: 0
        input name: "transitionTime", type: "enum", description: "", title: "Transition time", options: [[500:"500ms"],[1000:"1s"],[1500:"1.5s"],[2000:"2s"],[5000:"5s"]], defaultValue: 1000
        input name: "refreshInterval", type: "enum", description: "", title: "Refresh interval", options: [
            [30: "30 Seconds"],[60:"1 Minute"],[300:"5 Minutes"],[600:"10 Minutes"],[1800:"30 Minutes"],[3600:"1 Hour"],[0:"Disabled"]],
            defaultValue: 3600
        input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
        input name: "txtEnable", type: "bool", title: "Enable descriptionText logging", defaultValue: true
    }
}

def initialize() {
     installed()
}

def installed() {
    setSchedule()
    refresh()
}

def updated() {
    setSchedule()
    refresh()
}

def setSchedule() {
  logDebug "Setting refresh interval to ${settings.refreshInterval}s"
  unschedule()
  switch(settings.refreshInterval){
    case "0":
      unschedule()
      break
    case "30":
      schedule("0/30 * * ? * * *", refresh)
      break
    case "60":
      schedule("0 * * ? * * *", refresh)
      break
    case "300":
      schedule("0 0/5 * ? * * *", refresh)
      break
    case "600":
      schedule("0 0/10 * ? * * *", refresh)
      break
    case "1800":
      schedule("0 0/30 * ? * * *", refresh)
      break
    case "3600":
      schedule("0 * 0/1 ? * * *", refresh)
      break
    default:
      unschedule()
  }
}

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

def parseResp(resp) {
    // Handle Effects and Palettes
    if(!state.effects)
        getEffects()
    
    if(!state.palettes)
        getPalettes()
    
    def effects = state.effects
    def palettes = state.palettes
    
    // Update State
    state = resp.data
    state.effects = effects
    state.palettes = palettes
    
    synchronize(resp.data)
}

def parsePostResp(resp){

}

def synchronize(data){
    logDebug "Synchronizing status: ${data.seg[settings.ledSegment?.toInteger() ?: 0]}}"
    seg = data.seg[settings.ledSegment?.toInteger() ?: 0]
    
    // Power
    if(seg.on){
        if(device.currentValue("switch") != "on")
            sendEvent(name: "switch", value: "on")
    }
    else {
        if(device.currentValue("switch") == "on")
            sendEvent(name: "switch", value: "off")
    }
    
    //TODO: Synchronize everything else
}

// Switch Capabilities
def on() {
    sendEthernetPost("/json/state","{\"on\":true, \"seg\": [{\"id\": ${ledSegment}, \"on\":true}]}")  
    sendEvent(name: "switch", value: "on")
}

def off() {
    sendEthernetPost("/json/state","{\"on\":true, \"seg\": [{\"id\": ${ledSegment}, \"on\":false}]}")    
    sendEvent(name: "switch", value: "off")
}

// Color Names
def setGenericTempName(temp){
    if (!temp) return
    def genericName
    def value = temp.toInteger()
    if (value <= 2000) genericName = "Sodium"
    else if (value <= 2100) genericName = "Starlight"
    else if (value < 2400) genericName = "Sunrise"
    else if (value < 2800) genericName = "Incandescent"
    else if (value < 3300) genericName = "Soft White"
    else if (value < 3500) genericName = "Warm White"
    else if (value < 4150) genericName = "Moonlight"
    else if (value <= 5000) genericName = "Horizon"
    else if (value < 5500) genericName = "Daylight"
    else if (value < 6000) genericName = "Electronic"
    else if (value <= 6500) genericName = "Skylight"
    else if (value < 20000) genericName = "Polar"
    def descriptionText = "${device.getDisplayName()} color is ${genericName}"
    if (txtEnable) log.info "${descriptionText}"
    sendEvent(name: "colorName", value: genericName ,descriptionText: descriptionText)
}

def setGenericName(hue){
    def colorName
    hue = hue.toInteger()
    if (!hiRezHue) hue = (hue * 3.6)
    switch (hue.toInteger()){
        case 0..15: colorName = "Red"
            break
        case 16..45: colorName = "Orange"
            break
        case 46..75: colorName = "Yellow"
            break
        case 76..105: colorName = "Chartreuse"
            break
        case 106..135: colorName = "Green"
            break
        case 136..165: colorName = "Spring"
            break
        case 166..195: colorName = "Cyan"
            break
        case 196..225: colorName = "Azure"
            break
        case 226..255: colorName = "Blue"
            break
        case 256..285: colorName = "Violet"
            break
        case 286..315: colorName = "Magenta"
            break
        case 316..345: colorName = "Rose"
            break
        case 346..360: colorName = "Red"
            break
    }
    def descriptionText = "${device.getDisplayName()} color is ${colorName}"
    if (txtEnable) log.info "${descriptionText}"
    sendEvent(name: "colorName", value: colorName ,descriptionText: descriptionText)
}

// Dimmer function
def setLevel(value) {
    setLevel(value,(transitionTime?.toBigDecimal() ?: 1000) / 1000)
}

def setLevel(value,rate) {
    rate = rate.toBigDecimal()
    def scaledRate = (rate * 10).toInteger()
    
    if(value > 0){
        def isOn = device.currentValue("switch") == "on"
        if(!isOn)
            on()
        
        if(value >= 100) {
            setValue = 255
            value = 100
        }
        else {
            setValue = (value.toInteger() * 2.55).toInteger()
        }
        
        msg = "{\"on\":true, \"seg\": [{\"id\": ${ledSegment}, \"on\":true, \"bri\": ${setValue}}]}"
        sendEthernetPost("/json/state", msg)
        sendEvent(name: "level", value: value, descriptionText: "${device.displayName} is ${value}%", unit: "%")
    } else {
        off()
    }
    
    refresh()
}

// Color Functions
def setColor(value){
    if (value.hue == null || value.saturation == null) return
    def rate = transitionTime?.toInteger() ?: 1000

    // Turn off if level is set to 0/black
    if (value.level == 0) {
        off()
        return
    } else if(value.level >= 100) {
        level = 255
    }    else {
        level = value.level * 256
    }
    
    // Convert to RGB from HSV
    rgbValue = hsvToRgb(value.hue, value.saturation, value.level)
    
    // Send to WLED
    logDebug("Setting RGB Color to ${rgbValue}")
    setRgbColor(rgbValue)
    setGenericName(value.hue)
}

def setColorTemperature(temp){
    on()
    rgbValue = colorTempToRgb(temp)
    setRgbColor(rgbValue)
    setGenericTempName(temp)
}

def setHue(value){
    //TODO: Fix color conversion
    def color = [:]
    color.hue = value
    color.level = 255
    color.saturation = 100
    
    setColor(color)
}

def setRgbColor(rgbValue){
    // Turn off any active effects
    setEffectCustom(0,0)
    
    // Send Color
    body = "{\"on\":true, \"seg\": [{\"id\": ${ledSegment}, \"on\":true, \"col\": [${rgbValue}]}]}"
    logDebug("Setting color: ${body}")
    sendEthernetPost("/json/state", body)
    refresh()
}

// Device Functions
def refresh() {
    sendEthernet("/json/state")
}

def sendEthernet(path) {
    if(settings.uri != null){
        def params = [
            uri: "${settings.uri}",
            path: "${path}",
            headers: [:]
        ]

        try {
            httpGet(params) { resp ->
                parseResp(resp)
            }
        } catch (e) {
            log.error "something went wrong: $e"
        }
    }
}

def sendEthernetPost(path, body) {
    if(settings.uri != null){
        
        def params = [
            uri: "${settings.uri}",
            path: "${path}",
            headers: [
                    "Content-Type": "application/json",
                    "Accept": "*/*"
                ],
            body: "${body}"
        ]

        try {
            httpPost(params) { resp ->
                parsePostResp(resp)
            }
        } catch (e) {
            log.error "something went wrong: $e"
        }
    }
}

// Helper Functions
def logDebug(message){
    if(logEnable) log.debug(message)
}

def hsvToRgb(float hue, float saturation, float value) {
    if(hue==100) hue = 99
    hue = hue/100
    saturation = saturation/100
    value = value/100
    
    int h = (int)(hue * 6)
    float f = hue * 6 - h
    float p = value * (1 - saturation)
    float q = value * (1 - f * saturation)
    float t = value * (1 - (1 - f) * saturation)

    switch (h) {
      case 0: return rgbToString(value, t, p)
      case 1: return rgbToString(q, value, p)
      case 2: return rgbToString(p, value, t)
      case 3: return rgbToString(p, q, value)
      case 4: return rgbToString(t, p, value)
      case 5: return rgbToString(value, p, q)
      default: log.error "Something went wrong when converting from HSV to RGB. Input was " + hue + ", " + saturation + ", " + value
    }
}

def colorTempToRgb(kelvin){
    temp = kelvin/100
    
    if( temp <= 66 ){ 
        red = 255
        green = temp
        green = 99.4708025861 * Math.log(green) - 161.1195681661
        if( temp <= 19){
            blue = 0
        } else {
            blue = temp-10
            blue = 138.5177312231 * Math.log(blue) - 305.0447927307
        }
    } else {
        red = temp - 60
        red = 329.698727446 * Math.pow(red, -0.1332047592)
        green = temp - 60
        green = 288.1221695283 * Math.pow(green, -0.0755148492 )
        blue = 255
    }
    
    rs = clamp(red,0, 255)
    gs = clamp(green,0,255)
    bs = clamp(blue,0, 255)
    
    return "[" + rs + "," + gs + "," + bs + "]";
}

def rgbToString(float r, float g, float b) {
    String rs = (int)(r * 255)
    String gs = (int)(g * 255)
    String bs = (int)(b * 255)
    return "[" + rs + "," + gs + "," + bs + "]";
}

def clamp( x, min, max ) {
    if(x<min){ return min; }
    if(x>max){ return max; }
    return x;
}

// FastLED FX and Palletes

def getEffects(){
    logDebug "Getting Effects List"
    def params = [
        uri: "${settings.uri}",
        path: "/json/effects",
        headers: [
            "Content-Type": "application/json",
            "Accept": "*/*"
        ],
        body: "${body}"
    ]

    try {
        httpGet(params) {
            resp ->
                state.effects = resp.data
        }
    } catch (e) {
        log.error "something went wrong: $e"
    }
}

def getPalettes(){
    logDebug "Getting Effects List"
    def params = [
        uri: "${settings.uri}",
        path: "/json/palettes",
        headers: [
            "Content-Type": "application/json",
            "Accept": "*/*"
        ],
        body: "${body}"
    ]

    try {
        httpGet(params) {
            resp ->
                state.palettes = resp.data
        }
    } catch (e) {
        log.error "something went wrong: $e"
    }
}

def setEffectCustom(fx){
    def i = ledSegment?.toInteger() ?: 0
    setEffectCustom(fx, state.seg[i].sx, state.seg[i].ix, state.seg.pal)
}

def setEffectCustom(fx, pal){
    def i = ledSegment?.toInteger() ?: 0
    setEffectCustom(fx, state.seg[i].sx, state.seg[i].ix, pal)
}

def setEffectCustom(fx,sx,ix){
    def i = ledSegment?.toInteger() ?: 0
    setEffectCustom(fx, sx, ix, state.seg[i].pal)
}

def setEffectCustom(fx, sx, ix, pal){
    logDebug("Setting Effect: [{\"id\": ${ledSegment},\"fx\": ${fx},\"sx\": ${sx},\"ix\": ${ix},\"pal\": ${pal}}]")
    body = "{\"on\":true, \"seg\": [{\"id\": ${ledSegment},\"fx\": ${fx},\"sx\": ${sx},\"ix\": ${ix},\"pal\": ${pal}}]}"
    
    sendEthernetPost("/json/state", body)
    
    // Effect Name
    def effectName = state.effects.getAt(fx.intValue())
    def descriptionText = "${device.getDisplayName()} effect is ${effectName}"
    if (txtEnable) log.info "${descriptionText}"
        sendEvent(name: "effectName", value: effectName, descriptionText: descriptionText)
        
    // Palette Name
    def paletteName = state.palettes.getAt(pal.intValue())
    descriptionText = "${device.getDisplayName()} color palette is ${paletteName}"
    if (txtEnable) log.info "${descriptionText}"
        sendEvent(name: "paletteName", value: paletteName, descriptionText: descriptionText)

    if(fx > 0){
        // Color Name
        descriptionText = "${device.getDisplayName()} color is defined by palette"
        sendEvent(name: "colorName", value: "Palette", descriptionText: descriptionText)
    }
    
    // Refresh
    refresh()
}

// Alarm Functions
def siren(){
    // Play "Siren" effect
    logDebug("Alarm \"siren\" activated")
    setEffectCustom(38,255,255,0)
}

def strobe(){
    // Set Effect to Strobe
    logDebug("Alarm strobe activated")
    setEffectCustom(23,255,255,0)
}

def both(){
    //Cannot do both, default to strobe
    strobe()
}

Which (after a quick add/remove device for webcore) allowed this option:

...instead of the single integer option previously, i.e ...

Thus:

image

Solved....?

Hi @djh_wolf !

Updated my driver using the WLED-mod you posted (with the SetEffectCustom changes) and added the changes made by @TroyP that allows me to turn it On/Off.

Still no luck in changing colors / effects / Temp... even thru the Devices Tab on HE's interface.

This is how my device looks.

Yours?

Hey @djh_wolf !

So yours has 0 segments?

Mine was imported by the driver with the 39 segments on my strip.

I’ll test that as soon as I get home in a couple of hours!

There you go. That's your problem

Ok!
Solved!!

Can control effects, colors and so ob thru HE’s devices tab.

Now… time for some webcore tinkering…

1 Like

@djh_wolf
With the help of @joaomf the driver now returns Effect and Palette numbers.

Rationale: I'm trying to create a Webcore piston in which each pressing of a button would change de effect to the next one. The same idea would be used for double presses and the palette. (Assuming speed and intensity would always remain the same).

Will do some tinkering and maybe webcore will allow one parameter to be changed, without altering the others...

Interested in how this progresses...

Any one know if the aircookie drive can work with segments?
Im using a esp32 with 3 segments but the driver will only turn off one segment?

Should have waited 1 min as just worked out i can have a preset of OFF which works

Yes it does.


1 Like

I tried that and it would only connect to the first one i set up...

Just tried again and looks like its working

1 Like

So next question...
I have quite a few zigbbe colour bulbs and i would like to colour match wled to these bulbs or the other way not really fussed.
But i cant seem to find a colour name in wled or settings that are accessed in Hubitat.

Revisiting this thread after some time, needed to make a minor fixes anyway, so I added the setEffectCustom function for use with WebCORE. Thanks for looking into that. Technically it works with setEffect and passing only a single parameter but you won't be able to control the other settings (speed, intensity, palette).

Updated code in the github repo.

1 Like

Fantastic! Cheers!

I was looking at the Hypercubes after seeing them on Paul Hibbert's tech channel. Pretty nice looking, so I reached out. It turns out that these support WLED so it should be possible to integrate them readily with Hubitat.

1 Like

Since their app is practically a dead-ringer for WLED, that is not surprising:

1 Like

I am actually not that familiar with WLED. When I made my own WS2812-based lights a couple years ago I wrote my own code for it and created my own Hubitat drivers. They work well for the Christmas lights I made them as.

Anyways, my HyperCube arrived so I wrote a driver for it. I tried one of the WLED drivers I found on the forum but it was a bit old and most things did not quite work. So I just pulled up the WLED docs, found out about the JSON API, and then just reworked one of my other drivers for it instead last night. Considering I have a bunch of API drivers and it is so simple... it was pretty easy to do.

1 Like

Hi,

Any idea why I'm seeing this error? It seems to appear every minute.
I'm using an ESP 8266 running WLED version 0.13.3

Thanks in advance.
Paul

dev:332023-05-17 13:15:00.242errorjava.lang.NullPointerException: Cannot get property 'on' on null object on line 156 (method parseResp)
dev:332023-05-17 13:15:00.232debugSynchronizing status: null}
dev:332023-05-17 13:15:00.228debug[bri:255, lor:0, mainseg:0, nl:[dur:60, mode:1, on:false, rem:-1, tbri:0], on: false, pl:-1, ps:-1, seg:[[bri:255, cct:127, col:[[255, 221, 172], [0, 0, 0], [0, 0, 0]], frz:false, fx:0, grp:1, id:0, ix:128, len:68, mi:false, of:0, on:true, pal:0, rev:false, sel:true, spc:0, start:0, stop:68, sx:128]], transition:7, udpn:[recv:true, send: false]]

I'm having some problems with Hubitat's color picker popup.
Installed and configured the WLED driver no problem, and the commands all work perfectly from the devices page, but the trouble is on the dashboard. When I adjust the color (on the dashboard) it works great while I'm making the adjustment, however, the moment I close Hubitat's color selector popup, Hubitat sends 255,255,255 (white) to the WLED driver.

Here is the Log where I set the color:
dev:122024-01-07 11:14:33.809 PMdebugSetting RGB Color to [53,25,229]

And here is what happens when I close the color selector popup:
dev:122024-01-07 11:14:36.960 PMdebugSetting RGB Color to [255,255,255]

I'm running WLED 0.14.0 and Hubitat 2.3.7.145

Thank you for any advice,
LLL