WLED control without the binding

Foreword

If you want easy setup, don’t copy me - use the binding from here WLed: A binding for controlling LED strips and strings from an opensource esp8266 project and forget you ever saw this post.

This post (#2) shows my first attempt in all its glory, for posterity. The first post (#1) simplifies the things file somewhat, and adds features thanks to @sujitrp.

Introduction

I have a NodeMCU clone flashed with WLED connected to a WS2812B LED strip. A WS2812B LED strip enables each LED to be individually controlled.

Setup

Device

Once connected to the local wifi, setup MQTT on the device:

  • Enable MQTT
  • Point to the local Mosquitto broker.
  • Name the device (Client ID)
  • Set the Device Topic

image

OpenHAB

Things

Notes
  • There is a separate things file which defines the bridge to the Mosquitto broker.
  • WLED MQTT information is on the WLED wiki. In short, you can command three topics:
    • A brightness or power on/off/toggle to the Device Topic (wled/rgb2)
    • A colour in hex to wled/rgb2/col
    • Almost everything using the HTTP API syntax to wled/rgb2/api
  • Return topics are almost identical:
    • Brightness from wled/rgb2/g
    • Colour from wled/rgb2/c
    • Almost everything in xml format from wled/rgb2/v. A typical xml string looks as follows, where all parameters are described at HTTP API syntax:
<?xml version="1.0" ?><vs><ac>28</ac><cl>255</cl><cl>0</cl><cl>212</cl><cs>0</cs><cs>0</cs><cs>0</cs><ns>0</ns><nr>1</nr><nl>1</nl><nf>1</nf><nd>60</nd><nt>0</nt><fx>9</fx><sx>127</sx><ix>63</ix><fp>0</fp><wv>0</wv><ws>0</ws><ps>0</ps><cy>0</cy><ds>WLED</ds><ss>0</ss></vs>
  • The switch channel
    • Sends an ON or OFF command on wled/rgb2
    • Receives the full XML string on wled/rgb2/v and:
      • Extracts the resultant brightness (<ac>) via XPATH (there is no on/off parameter in the xml), then
      • Transforms the brightness value into a switch ‘boolean’ through a javascript transform. If brightness is zero, switch is off; if brightness is not zero, switch is on.
        • You could probably do this with a single transform by capturing wled/rgb2/g instead (see the brightness channel below)
  • The colour channel
    • Sends a hex colour on wled/rgb2/col by
      • Transforming the RGB colour to hex with an outbound javascript transform.
    • Receives the resultant hex colour on wled/rgb2/c and
      • Transforms the hex to RGB with a javascript transform.
  • The brightness channel
    • Sends a brightness value (0-255) on wled/rgb2 by
      • Transforming the dimmer percent (0-100%) via an outbound javascript transform.
    • Receives the resultant brightness on wled/rgb2/g and
      • Transforms the brightness (0-255) into a dimmer percentage (0-100%) with a javascript transform.
  • The speed channel
    • Sends speed value (0-255) on wled/rgb2/api by
      • Transforming the dimmer percent (0-100%) and adding HTTP API specific parameter identifications via an outbound javascript transform.
    • Receives the resultant speed on wled/rgb2/v and
      • Extracts the resultant speed (<sx>) via XPATH, then
      • Transforms the speed value (0-255) into a dimmer value through a javascript transform.
  • The intensity channel
    • Sends intensity value (0-255) on wled/rgb2/api by
      • Transforming the dimmer percent (0-100%) and adding HTTP API specific parameter identifications via an outbound javascript transform.
    • Receives the resultant intensity on wled/rgb2/v and
      • Extracts the resultant intensity (<ix>) via XPATH, then
      • Transforms the intensity value (0-255) into a dimmer value through a javascript transform.
  • The api channel is used to send commands directly from rules, rather than sitemap widgets.
Thing mqtt:topic:swRGB2 "RGB2 lights" (mqtt:broker:MosquittoMqttBroker) {
		Channels:
			Type switch : switch "Power Switch" [ 
				commandTopic="wled/rgb2",
				on="ON",
				off="OFF",
				stateTopic="wled/rgb2/v",
				transformationPattern="XPATH:/vs/ac/text()∩JS:dimmer2switch.js"
			]
            Type colorRGB : colour "Colour" [ 
				commandTopic="wled/rgb2/col",
				transformationPatternOut="JS:rgb2hex.js",
                stateTopic="wled/rgb2/c",
                transformationPattern="JS:hex2rgb.js"
			]

            Type dimmer : brightness "Brightness" [ 
				commandTopic="wled/rgb2",
                transformationPatternOut="JS:openhab2wled.js",                
                stateTopic="wled/rgb2/g",
                transformationPattern="JS:wled2openhab.js"
			]

			Type dimmer : speed "Speed" [
				commandTopic="wled/rgb2/api",
				transformationPatternOut="JS:openhab2wled-speed.js",
				stateTopic="wled/rgb2/v",
				transformationPattern="XPATH:/vs/sx/text()∩JS:wled2openhab.js"
			]

			Type dimmer : intensity "Intensity" [
				commandTopic="wled/rgb2/api",
				transformationPatternOut="JS:openhab2wled-intensity.js",
				stateTopic="wled/rgb2/v",
				transformationPattern="XPATH:/vs/ix/text()∩JS:wled2openhab.js"
			]

			Type string : api "api" [
				commandTopic="wled/rgb2/api"
			]
	}

Transforms

Quite frankly, heavily indebted to the generous souls on StackExchange for quite a few of these…!

dimmer2switch.js
(function(x) {
    if(x == 0){
        return "OFF";
    }
    else{
        return "ON";
    }
})(input)
rgb2hex.js
(function(x) {
    var splitstring = x.split(',');
    var r = splitstring[0];
    var g = splitstring[1];
    var b = splitstring[2];
    return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
})(input)

function componentToHex(rgb) { 
    var hex = Number(rgb).toString(16);
    if (hex.length < 2) {
         hex = "0" + hex;
    }
    return hex;
}
hex2rgb.js
(function(colour) {
    var r,g,b;
    if ( colour.charAt(0) == '#' ) {
        colour = colour.substr(1);
    }
    if ( colour.length == 3 ) {
        colour = colour.substr(0,1) + colour.substr(0,1) + colour.substr(1,2) + colour.substr(1,2) + colour.substr(2,3) + colour.substr(2,3);
    }
    r = colour.charAt(0) + '' + colour.charAt(1);
    g = colour.charAt(2) + '' + colour.charAt(3);
    b = colour.charAt(4) + '' + colour.charAt(5);
    r = parseInt( r,16 );
    g = parseInt( g,16 );
    b = parseInt( b ,16);
  
    return r + ","  +g + ","  +b ;
})(input)
openhab2wled.js
(function(x) {
    return Math.round(((x / 100) * 255));
})(input)
wled2openhab.js
(function(x) {
    return Math.round(((x / 255) * 100));
})(input)
openhab2wled-speed.js
(function(x) {
    return "SX=" +Math.round(((x / 100) * 255));
})(input)
openhab2wled-intensity.js
(function(x) {
    return "IX=" +Math.round(((x / 100) * 255));
})(input)

Items

Switch sRGB2 "RGB2 lights switch" {channel="mqtt:topic:swRGB2:switch"}
Dimmer dRGB2Brightness "RGB2 Brightness" {channel="mqtt:topic:swRGB2:brightness"}
Color cRGB2Colour "RGB2 colour" {channel="mqtt:topic:swRGB2:colour"}
Dimmer dRGB2Speed "RGB2 speed" {channel="mqtt:topic:swRGB2:speed"}
Dimmer dRGB2Intensity "RGB2 intensity" {channel="mqtt:topic:swRGB2:intensity"}
String strRGB2API "RGB2 API" {channel="mqtt:topic:swRGB2:api"}

Sitemap

Switch item=sRGB2 label="RGB2" icon="light" 
Slider item=dRGB2Brightness label="RGB2 brightness [%d %%]" icon="slider" sendFrequency=500
Colorpicker item=cRGB2Colour label="RGB2 colour"
Slider item=dRGB2Speed label="RGB2 speed"
Slider item=dRGB2Intensity label="RGB2 intensity"

Rules

I’m using jython to create rules.

In $OPENHAB_CONF/automation/lib/python/personal I have a file called personal_functions.py which contains, amongst other things:

personal_functions.py
def set_wled(switch, colour="op", brightness="op", effect="op", speed="op", intensity="op", nightlight="op"):
    
    a = "&A=" + str(get_8bit_number(brightness)) if brightness != "op" else ""
    t = "T=1" if switch else "T=0"
    c = "&R=" + str(colour[0]) + "&G=" + str(colour[1]) + "&B=" + str(colour[2]) if colour != "op" else "" 
    fx = "&FX=" +str(effect) if effect != "op" else ""
    sx = "&SX=" + str(get_8bit_number(speed)) if speed != "op" else ""
    ix = "&IX=" + str(get_8bit_number(intensity)) if intensity != "op" else ""
    nl = "&NL=" + str(nightlight) if nightlight != "op" else ""
    
    return a + t + c + fx + sx + ix + nl  
  
def get_8bit_number(number):
    return int(float(number)/100*255)

This function will take in

  • a switch state (1 or 0 for on and off)
  • a colour in RGB formatted as (R,G,B)
  • a brightness (0-100)
  • an effect index
  • a speed (0-100)
  • an intensity (0-100)

and will return a string formatted for sending to WLED via the api channel. Only the switch argument is mandatory - all others are optional. This function is used in a number of jython rules, remembering to import the personal_functions.py into each rule file.

Sample rules
import personal.personal_functions
reload(personal.personal_functions)
from personal.personal_functions import set_wled

----

#Turns the LED strip on, sets the colour to pink, sets brightness to 100%, sets the effect to Dual Scan, sets speed to 95% and intensity to 50%
events.sendCommand("strRGB2API",set_wled(switch=1,colour=(255,192,203),brightness=100,effect=11,speed=95,intensity=50))

To Do

  • Try and reduce the number of different javascript transforms required! (Not sure how!) See first post for how!
2 Likes