Foreword
I was interested in controlling WLED without using the binding. In the end I learned a bit more about:
- Javascript transforms
- XML (XPATH) transforms
- Chaining multiple transforms
- Jython personal functions
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 (#1) shows my final setup. The next post (#2) shows my first attempt in all its glory, for posterity.
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
OpenHAB
Things
bridge.things
I have a separate file which just contains the bridge Thing to my Mosquitto MQTT broker:
Bridge mqtt:broker:MosquittoMqttBroker "Mosquitto MQTT Broker" [
host="192.168.1.92",
secure=false,
port=1883,
clientID="OpenHAB2"
]
wled.things
Differences from first attempt
- Culled 4 javascript transforms
-
switch
stateTopic
changed to the brightness topic, as that’s all we were extracting with XPATH anyway. Now we only need the JS transform. -
brightness channel has lost both of its javascript transforms, in favour of the
min
,max
andstep
options. -
speed and intensity channels have lost both of their javascript transforms, in favour of the
min
,max
andstep
options.- OUT: also used
formatBeforePublish
to add the API prefix of “SX=” and “IX=” in front of the speed or intensity number
- OUT: also used
-
effect and palette channels have been added, using
formatBeforePublish
to publish directly to the WLED api topic, and an XPATH transform to receive messages on the v topic.
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/g",
transformationPattern="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",
stateTopic="wled/RGB2/g",
min=0,
max=255,
step=1
]
Type dimmer : speed "Speed" [
commandTopic="wled/RGB2/api",
formatBeforePublish="SX=%s",
stateTopic="wled/RGB2/v",
transformationPattern="XPATH:/vs/sx/text()",
min=0,
max=255,
step=1
]
Type dimmer : intensity "Intensity" [
commandTopic="wled/RGB2/api",
formatBeforePublish="IX=%s",
stateTopic="wled/RGB2/v",
transformationPattern="XPATH:/vs/ix/text()",
min=0,
max=255,
step=1
]
Type number : effect "Effect" [
commandTopic="wled/RGB2/api",
formatBeforePublish="FX=%s",
stateTopic="wled/RGB2/v",
transformationPattern="XPATH:/vs/fx/text()"
]
Type number : palette "Palette" [
commandTopic="wled/RGB2/api",
formatBeforePublish="FP=%s",
stateTopic="wled/RGB2/v",
transformationPattern="XPATH:/vs/fp/text()"
]
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)
Items
Differences from first attempt
Here I added autoUpdate="false"
to the three dimmer items, to prevent the slider from jumping around. Also added the effect and palette things
Switch sRGB2 "RGB2 lights" {channel="mqtt:topic:swRGB2:switch"}
Dimmer dRGB2Brightness "RGB2 Brightness" {channel="mqtt:topic:swRGB2:brightness", autoupdate="false"}
Color cRGB2Colour "RGB2 RGB lights colour" {channel="mqtt:topic:swRGB2:colour"}
Dimmer dRGB2Speed "RGB2 Speed" {channel="mqtt:topic:swRGB2:speed", autoupdate="false"}
Dimmer dRGB2Intensity "RGB2 Intensity" {channel="mqtt:topic:swRGB2:intensity", autoupdate="false"}
Number nRGB2Effect "RGB2 Effect" {channel="mqtt:topic:swRGB2:effect"}
Number nRGB2Palette "RGB2 Palette" {channel="mqtt:topic:swRGB2:palette"}
String strRGB2API "RGB2 API" {channel="mqtt:topic:swRGB2:api"}
Sitemap
Differences from first attempt
Here I added formatting to the slider labels so that it didn’t display a value with 200 significant figures. Also added effect and palette selection widgets.
Switch item=sRGB2 label="RGB2" icon="light"
Slider item=dRGB2Brightness label="RGB2 brightness [%d %%]" icon="slider"
Colorpicker item=cRGB2Colour label="RGB2 colour"
Slider item=dRGB2Speed label="RGB2 speed [%d %%]"
Slider item=dRGB2Intensity label="RGB2 intensity [%d %%]"
Selection item=nRGB2Effect label="RGB2 effect [%s]" mappings=[27='Android', 1='Blink', 26='Blink Rainbow', 91='Bouncing Balls', 68='BPM', 2='Breathe', 88='Candle', 102='Candle Multi', 28='Chase', 31='Chase Flash', 32='Chase Flash Rnd', 30='Chase Rainbow', 29='Chase Random', 111='Chunchun', 52='Circus', 34='Colorful', 8='Colorloop', 74='Colortwinkle', 67='Colorwaves', 112='Dancing Shadows', 21='Dark Sparkle', 18='Dissolve', 19='Dissolve Rnd', 96='Drip', 11='Dual Scan', 60='Dual Scanner', 7='Dynamic', 12='Fade', 69='Fill Noise', 66='Fire 2012', 45='Fire Flicker', 42='Fireworks', 90='Fireworks 1D', 89='Fireworks Starburst', 110='Flow', 87='Glitter', 46='Gradient', 53='Halloween', 82='Halloween Eyes', 100='Heartbeat', 58='ICU', 64='Juggle', 75='Lake', 41='Lighthouse', 57='Lightning', 47='Loading', 25='Mega Strobe', 44='Merry Christmas', 76='Meteor', 59='Multi Comet', 70='Noise 1', 71='Noise 2', 72='Noise 3', 73='Noise 4', 107='Noise Pal', 62='Oscillate', 101='Pacifica', 65='Palette', 98='Percent', 105='Phased', 109='Phased Noise', 97='Plasma', 48='Police', 49='Police All', 95='Popcorn', 63='Pride 2015', 78='Railway', 43='Rain', 9='Rainbow', 33='Rainbow Runner', 5='Random Colors', 38='Red & Blue', 79='Ripple', 99='Ripple Rainbow', 15='Running', 37='Running 2', 16='Saw', 10='Scan', 40='Scanner', 92='Sinelon', 93='Sinelon Dual', 94='Sinelon Rainbow', 108='Sinewave', 77='Smooth Meteor', 0='Solid', 103='Solid Glitter', 83='Solid Pattern', 84='Solid Pattern Tri', 20='Sparkle', 22='Sparkle+', 85='Spots', 86='Spots Fade', 39='Stream', 61='Stream 2', 23='Strobe', 24='Strobe Rainbow', 104='Sunrise', 6='Sweep', 36='Sweep Random', 13='Theater', 14='Theater Rainbow', 35='Traffic Light', 54='Tri Chase', 56='Tri Fade', 55='Tri Wipe', 17='Twinkle', 106='Twinkle Up', 81='Twinklecat', 80='Twinklefox', 51='Two Areas', 50='Two Dots', 113='Washing machine', 3='Wipe', 4='Wipe Random']
Selection item=nRGB2Palette label="RGB2 palette [%s]" mappings=[18='Analoguous', 46='April Night', 50='Aurora', 39='Autumn', 3='Based on primary', 5='Based on set', 26='Beach', 22='Beech', 15='Breeze', 48='C9', 7='Cloud', 37='Cyane', 0='Default', 24='Departure', 30='Drywet', 35='Fire', 10='Forest', 32='Grintage', 28='Hult', 29='Hult 64', 36='Icefire', 31='Jul', 25='Landscape', 8='Lava', 38='Light Pink', 40='Magenta', 41='Magred', 9='Ocean', 44='Orange & Teal', 47='Orangery', 6='Party', 20='Pastel', 2='Primary color', 11='Rainbow', 12='Rainbow bands', 1='Random Cycle', 16='Red & Blue', 33='Rewhi', 14='Rivendell', 49='Sakura', 4='Set colors', 27='Sherbet', 19='Splash', 13='Sunset', 21='Sunset 2', 34='Tertiary', 45='Tiamat', 23='Vintage', 43='Yelblu', 17='Yellowout', 42='Yelmag']
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:
def set_wled(switch=None, colour=None, brightness=None, effect=None, speed=None, intensity=None, nightlight=None):
a = "&A=" + str(get_8bit_number(brightness)) if brightness != None else ""
t = "&T=" + str(switch) if switch != None else ""
c = "&R=" + str(colour[0]) + "&G=" + str(colour[1]) + "&B=" + str(colour[2]) if colour != None else ""
fx = "&FX=" +str(effect) if effect != None else ""
sx = "&SX=" + str(get_8bit_number(speed)) if speed != None else ""
ix = "&IX=" + str(get_8bit_number(intensity)) if intensity != None else ""
nl = "&NL=" + str(nightlight) if nightlight != None else ""
returnString = a + t + c + fx + sx + ix + nl
return returnString[1:]
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)
- a nighlight countdown time (in minutes)
and will return a string formatted for sending to WLED via the api channel. All arguments are optional. This function is used in a number of jython rules, remembering to import the personal_functions.py into each rule file. If the nightlight
parameter is provided, this will set the nightlight time and activate the nightlight function.
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))
EDIT: 08/11/2020 - Swap first two posts around, so that the first post has the final, working solution. Updated function to include nightlight feature.
EDIT: 01/11/2020 - Update and alphabetise effects and palettes.