Setup WS2812 Strip with esphome

  • Platform information:
    • Hardware: RPi 3B+
    • OS: Raspbian Stretch Lite (openHAB 2.5.0 Build #1628 in docker)
  • Issue of the topic:
    I want to integrate with openhab my nodemcu running esphome which is connected to ws2812 led strip.
    leds are working fine right now and I can control it by publishing to various mqtt topics.
    As soon as I power on the nodemcu, the following gets published to the broker by esphome:

homeassistant/light/ledstrip/fastled_ws2811_light/config {“schema”:“json”,“brightness”:true,“rgb”:true,“name”:“FastLED WS2811 Light”,“state_topic”:“ledstrip/light/fastled_ws2811_light/state”,“command_topic”:“ledstrip/light/fastled_ws2811_light/command”,“availability_topic”:“ledstrip/status”,“unique_id”:“ESPlightfastled_ws2811_light”,“device”:{“identifiers”:“84f3eb7405c3”,“name”:“ledstrip”,“sw_version”:“esphome v1.12.2 Jul 2 2019, 14:56:52”,“model”:“PLATFORMIO_NODEMCU”,“manufacturer”:“espressif”}}

ledstrip/light/fastled_ws2811_light/state {“state”:“ON”,“brightness”:148,“color”:{“r”:209,“g”:255,“b”:72}}

Earlier I thought this would be autodiscovered by openhab just like it discovers tasmota running nodemcus but to my suprise it doesn’t.
I dont know how to go ahead from here to add this to the items and sitemaps .

With homeassistant where it was auto discovered, when i set the color of strip go red from HA interface, this is what gets published

ledstrip/light/fastled_ws2811_light/command {“state”:“ON”,“brightness”:255,“color”:{“r”:255,“g”:0,“b”:0}}

Please tell me how to replicate this json message in openhab.
Found some cues about color picker and transform path and map but I need some guidance from experts of OH I guess.

I believe this message get’s retained. So if you initiate a scan from the Inbox it should show up. I doubt that the binding subscribes to this topic to receive the message and therefore discover the device without being told to.

You will probably need to use a Rule to convert the HSBType (Hue Saturation Brightness) to what is effectively a BRGB (Brightness, Red, Green, Blue). You will probably want to use the mqttPublish from the Rule.

For example, if you create the following Item:

Group:Color LEDs
Color fastled_ws2811_light (LEDs)

NOTE there is not link to a Channel here.

With Rule

rule "Publish changes to the Color for LEDs"
when
    Member of LEDs changed
then
    val topic = "ledstrip/light/"+triggeringItem.name+"/command"

    val HSBType newState = triggeringItem.state
    val st = triggeringItem.getStateAs(OnOffType).toString // get the on off state
    val brightness = triggeringItem.getStateAs(PercentType).toString // get the brightness
    val red = newState.getRed
    val green = newState.getGreen
    val blue = newState.getBlue

    val message = "{\"state\":\""+st+",\"brightness\":"+brightness+",\"color\":{\"r\":"+red+",\"g\":"+green+",\"b\":"+blue+"}}"
end

    val mqttActions = getActions("mqtt","mqtt:systemBroker:embedded-mqtt-broker") // user the Thing ID for your configured Broker
    mqttActions.publishMQTT(topic,message)
end
1 Like

Thanks a lot. That works great.
Two further questions.
1). How would this Color item get the actual state from the state topic so that it syncs with the actual state all the time.
2). Google Home is what I use for voice control. How to make it controllable from there.
Edit:
I needed to add a name to the item and now google can control it. Now it looks like:
Color fastled_ws2811_light “LED Strip” (LEDs) [“Lighting”]
Only question 1 needs to be addressed now

I tried adding [ Lighting ] tag to the Color LEDs item. But on syncing devices in google, it says An error occured while syncing openhab.
Edit:
I needed to add a name to the item and now google can control it. Now it looks like:
Color fastled_ws2811_light “LED Strip” (LEDs) [“Lighting”]

You will probably need a role to convert the json to HSB type.

I tried to make this rule but isn’t working

rule "Get LED State"
when
    Item Ledstate changed
then
    val json = transform("JSONPATH", "$.", Ledstate.state.toString)
    val st = transform("JSONPATH", "$.state", json)
    val brightness = transform("JSONPATH", "$.brightness", json)
    val red = transform("JSONPATH", "$.color.r", json)
    val green = transform("JSONPATH", "$.color.g", json)
    val blue = transform("JSONPATH", "$.color.b", json)
    val HSBType hsb = HSBType::fromRGB(red, green, blue)
    sendCommand(fastled_ws2811_light, hsb)
end

items file has the following:

Group:Color LEDs
Color fastled_ws2811_light "LED Strip" (LEDs) ["Lighting"]
String Ledstate "Led State" (gMyRoom) { channel="mqtt:topic:thingid:tchid2" }

Also what should be done about the brightness value.

I know a lot of things would be wrong with the above code. Sorry for that because I am not a programmer by any standards. So need a lot of help. Thanks a lot by the way, great to be a part of such a lovely community.

This is the error thrown

Could not invoke method: org.eclipse.smarthome.core.library.types.HSBType.fromRGB(int,int,int) on instance: null

The Rule looks reasonable. What doesn’t work? Since you are creating the HSBType from just the RGB, you don’t need to parse out the brightness or the state.

I recommend adding some logInfo statements to see in the logs exactly what values are parsed out of the JSON to make sure they make sense.

The error is coming from the fact that the values you parse out are Strings, not Numbers or ints and fromRGB requires ints.

So do the following:

val int red = new BigDecimal( transform("JSONPATH", "$.color.r", json) ).intValue

Okay I tried to add this line along with loginfo as follows:

rule "Get LED State"
when
    Item Ledstate changed
then
    val json = transform("JSONPATH", "$.", Ledstate.state.toString)
    logInfo("admin", "json" + json)
    val int red = new BigDecimal( transform("JSONPATH", "$.color.r", json) ).intValue
    logInfo("admin", "red" + red)
end

This is in the logs:

2019-07-05 10:36:06.067 [INFO ] [eclipse.smarthome.model.script.admin] - json{"state":"ON","brightness":96,"color":{"r":255,"g":48,"b":210}}
2019-07-05 10:36:06.072 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Get LED State': An error occurred during the script execution: null

Also tried with

logInfo("admin", "red" + red.toString)

Also without loginfo lines.
But the error is the same.

Okay I tried somewhat like this now :slight_smile:

rule "Get LED State"
when
    Item Ledstate changed
then
    val json = transform("JSONPATH", "$.", Ledstate.state.toString)
    val number1 = transform("JSONPATH", "$.color.r", json)
    val number2 = transform("JSONPATH", "$.color.g", json)
    val number3 = transform("JSONPATH", "$.color.b", json)
    val int red1 = Integer.parseInt(number1)
    val int green1 = Integer.parseInt(number2)
    val int blue1 = Integer.parseInt(number3)
    logInfo("admin", "red" + red1)
    var HSBType hsb = HSBType::fromRGB(red1, green1, blue1)
    sendCommand(fastled_ws2811_light, hsb)

    logInfo("admin", "hsb" + hsb)
end

Seems to update the color state now but the brightness does not follow along. Isn’t there a way to insert brightness value in the hsB.

This has turned out to be funny:
As soon as I change the color in the color picker, looks like both these rules get triggered in the following way. First the rule 2 gets triggered and thus sends out the command to the strip. Then the strip sends back state mqtt msg and the rule 1 gets triggered which does something weird because after that the brightness value is lost and becomes fully bright. Also the turn off is not working anymore.

rule "Get LED State"
when
    Item Ledstate changed
then
    val json = transform("JSONPATH", "$.", Ledstate.state.toString)
    val number1 = transform("JSONPATH", "$.color.r", json)
    val number2 = transform("JSONPATH", "$.color.g", json)
    val number3 = transform("JSONPATH", "$.color.b", json)
    val int red1 = Integer.parseInt(number1)
    val int green1 = Integer.parseInt(number2)
    val int blue1 = Integer.parseInt(number3)
    logInfo("rule1", "red" + red1)
    var HSBType hsb = HSBType::fromRGB(red1, green1, blue1)
    sendCommand(fastled_ws2811_light, hsb)

    logInfo("rule1", "hsb" + hsb)
end


rule "Publish changes to the Color for LEDs"
when
    Member of LEDs changed
then
    val topic = "ledstrip/light/"+triggeringItem.name+"/command"

    val HSBType newState = triggeringItem.state
    val st = triggeringItem.getStateAs(OnOffType).toString // get the on off state
    val brightnessint = triggeringItem.getStateAs(PercentType)  // get the brightness
    val brightnesschanged = ((brightnessint + 40) * 255) /140
    val brightness = brightnesschanged.toString
    val red = newState.getRed
    val green = newState.getGreen
    val blue = newState.getBlue

    val message = "{\"state\":"+st+",\"brightness\":"+brightness+",\"color\":{\"r\":"+red+",\"g\":"+green+",\"b\":"+blue+"}}"

    val mqttActions = getActions("mqtt","mqtt:broker:btid")// user the Thing ID for your configured Broker
    mqttActions.publishMQTT(topic,message)
    logInfo("rule2", "message" + message)
end

These are the logs

2019-07-05 13:15:36.386 [INFO ] [clipse.smarthome.model.script.rule2] - message{"state":ON,"brightness":92.89285714,"color":{"r":11,"g":2.86,"b":10.457333333062}}
2019-07-05 13:15:36.479 [INFO ] [clipse.smarthome.model.script.rule1] - red255
2019-07-05 13:15:36.487 [INFO ] [clipse.smarthome.model.script.rule1] - hsb304,74,100
2019-07-05 13:15:36.572 [INFO ] [clipse.smarthome.model.script.rule2] - message{"state":ON,"brightness":255.00000000,"color":{"r":100,"g":26.00,"b":95.066666664200}}
2019-07-05 13:15:36.657 [INFO ] [clipse.smarthome.model.script.rule1] - red255
2019-07-05 13:15:36.661 [INFO ] [clipse.smarthome.model.script.rule1] - hsb304,74,100

Right now its a great mess. :rofl:

After Playing around a bit and changing values from percentage to 0-255 range as this is what is required by esphome.

rule "Get LED State"
when
    Item Ledstate changed
then
    val json = transform("JSONPATH", "$.", Ledstate.state.toString)
    val number1 = transform("JSONPATH", "$.color.r", json)
    val number2 = transform("JSONPATH", "$.color.g", json)
    val number3 = transform("JSONPATH", "$.color.b", json)
    val int red1 = Integer.parseInt(number1)
    val int green1 = Integer.parseInt(number2)
    val int blue1 = Integer.parseInt(number3)
    logInfo("rule1", "state msg coming back r=" + red1+ " g=" +green1+ " b=" +blue1)
    var HSBType hsb = HSBType::fromRGB(red1, green1, blue1)
    sendCommand(fastled_ws2811_light, hsb)

    logInfo("rule1", "corresponding hsb value " + hsb)
end

rule "Publish changes to the Color for LEDs"
when
    Member of LEDs changed
then
    val topic = "ledstrip/light/"+triggeringItem.name+"/command"

    val HSBType newState = triggeringItem.state
    val st = triggeringItem.getStateAs(OnOffType).toString // get the on off state
    val brightnessint = ((triggeringItem.getStateAs(PercentType)  * 215) /100) + 40 // change max value 100 to 255 
    val brightness = brightnessint.toString
    val redint = (newState.getRed * 255) /100
    val red = redint.toString
    val greenint = (newState.getGreen * 255) /100
    val green = greenint.toString
    val blueint = (newState.getBlue * 255) /100
    val blue = blueint.toString
    val message = "{\"state\":"+st+",\"brightness\":"+brightness+",\"color\":{\"r\":"+red+",\"g\":"+green+",\"b\":"+blue+"}}"
    logInfo("rule2", "Command sent r=" + red+ " g=" +green+ " b=" +blue)
    val mqttActions = getActions("mqtt","mqtt:broker:btid")// user the Thing ID for your configured Broker
    mqttActions.publishMQTT(topic,message)
    logInfo("rule2", "Full Command in json " + message)
end

Log

2019-07-05 17:37:03.016 [INFO ] [eclipse.smarthome.model.script.rule2] - Command sent r=7.89140000 g=28.05000000 b=3.36600000
2019-07-05 17:37:03.043 [INFO ] [eclipse.smarthome.model.script.rule2] - Full Command in json {"state":ON,"brightness":63.65000000,"color":{"r":7.89140000,"g":28.05000000,"b":3.36600000}}
2019-07-05 17:37:03.129 [INFO ] [eclipse.smarthome.model.script.rule1] - state msg coming back r=71 g=255 b=30
2019-07-05 17:37:03.136 [INFO ] [eclipse.smarthome.model.script.rule1] - corresponding hsb value 109,88,100
2019-07-05 17:37:03.175 [INFO ] [eclipse.smarthome.model.script.rule2] - Command sent r=71.73999999 g=255.00000000 b=30.60000000
2019-07-05 17:37:03.367 [INFO ] [eclipse.smarthome.model.script.rule2] - Full Command in json {"state":ON,"brightness":255.00000000,"color"":{"r":71.73999999,"g":255.00000000,"b":30.60000000}}

2019-07-05 17:37:03.465 [INFO ] [eclipse.smarthome.model.script.rule1] - state msg coming back r=71 g=255 b=30
2019-07-05 17:37:03.474 [INFO ] [eclipse.smarthome.model.script.rule1] - corresponding hsb value 109,88,100

So, as can be seen, as I change the color in UI, first rule 2 gets values, after converting a bit to 0-255 range, sends command to esphome… then esphome sends state mqtt msg back, rule 1 immediately gets the values from that msg and unfortunately, the values are changed drastically by the esphome because of them being rgb type and these values change the color picker brightness to maximum i.e. 100%. Because the values are changed so rule 2 again kicks into play sending another command with brightness value 255 this time and thus also changes actual brightness of LED to max.

I think I should leave the state mqtt msg for someone smarter, I’ll have to live without that for now.

If anyone needs the code, this is what works (half) till now:

rule "Publish changes to the Color for LEDs"
when
    Member of LEDs changed
then
    val topic = "ledstrip/light/"+triggeringItem.name+"/command"
    val HSBType newState = triggeringItem.state
    val st = triggeringItem.getStateAs(OnOffType).toString // get the on off state
    val brightnessint = ((triggeringItem.getStateAs(PercentType)  * 255) /100)  // change max value 100 to 255 
    val brightness = brightnessint.toString
    val redint = (newState.getRed * 255) /100
    val red = redint.toString
    val greenint = (newState.getGreen * 255) /100
    val green = greenint.toString
    val blueint = (newState.getBlue * 255) /100
    val blue = blueint.toString
    val message = "{\"state\":"+st+",\"brightness\":"+brightness+",\"color\":{\"r\":"+red+",\"g\":"+green+",\"b\":"+blue+"}}"
    val mqttActions = getActions("mqtt","mqtt:broker:btid")// user the Thing ID for your configured Broker
    mqttActions.publishMQTT(topic,message)
end

Not when you are creating it using RGB. But once you create it, you can postUpdate(new PercentType(brightness). There may be a way to set the brightness directly on the HSBType, but I don’t use Color lights so don’t know. Search the forum for examples is all I can recommend. In your first Rule, use postUpdate instead of sendCommand.

Use received command to trigger your second Rule. In OH, the primary distinction between a command and an update is that an update only changes OH’s internal state. It does not send the change out to the devices linked to the Item. A command is intended to go out to the device and cause it to do something.

Also, see https://www.openhab.org/docs/configuration/rules-dsl.html#myitem-sendcommand-new-state-versus-sendcommand-myitem-new-state

1 Like

Wow that could make my life easier. Thanks a ton. I’ll try the post update command in rule 1.
Edit:
postUpdate turned out to also trigger rule 2 again and thus a ping pong game is started between the two rules.
To solve this I later added lines to send command or update state from state mqtt msg only if nothing was being updated in the last 1 second. This ends the ping pong game right away.
Now Rule 1 updates the color picker even if the command was sent by the google home.

Okay got this to work with a bit of workarounds added.
This is the final code I use.

var tvalue = now                                                   // variable for storing time

rule "Get LED State"
when
    Item Ledstate changed
then
    if(tvalue.isBefore(now.minusSeconds(1))) {                      // for not playing mqtt ping pong b/w rule1 and rule2
    val json = transform("JSONPATH", "$.", Ledstate.state.toString)
    val brt = transform("JSONPATH", "$.brightness", json)
    val int brt1 = Integer.parseInt(brt)                           // get actual brightness value from esphome state msg
    val brt2 = (brt1 *100) /255                                    //calculate actual brightness value percentage
    val number1 = transform("JSONPATH", "$.color.r", json)
    val number2 = transform("JSONPATH", "$.color.g", json)
    val number3 = transform("JSONPATH", "$.color.b", json)
    val int red1 = Integer.parseInt(number1)
    val int green1 = Integer.parseInt(number2)
    val int blue1 = Integer.parseInt(number3)
    var HSBType hsb = HSBType::fromRGB(red1, green1, blue1)        // this hsb value does not have actual brightness value 
    val hsbhue = hsb.getHue()
    val hsbsat = hsb.getSaturation()
    var HSBType newState = new HSBType( hsbhue,hsbsat,new PercentType(brt2)) // reinsert hsb values with actual brightness
    fastled_ws2811_light.sendCommand(newState)                             // update color picker in the UI
    tvalue = now
    }
end



rule "Publish changes to the Color for LEDs"
when
    Member of LEDs changed
then
    if(tvalue.isBefore(now.minusSeconds(1))) {                       // same as in rule 1
    val topic = "ledstrip/light/"+triggeringItem.name+"/command"
    val HSBType newState = triggeringItem.state
    val st = triggeringItem.getStateAs(OnOffType).toString           // get the on off state
    val brightnessint = ((triggeringItem.getStateAs(PercentType)  * 215) /100) + 40 // change max value to 255 and also added base brightness40
    val brightness = brightnessint.toString
    val redint = (newState.getRed * 255) /100
    val red = redint.toString
    val greenint = (newState.getGreen * 255) /100
    val green = greenint.toString
    val blueint = (newState.getBlue * 255) /100
    val blue = blueint.toString
    val message = "{\"state\":"+st+",\"brightness\":"+brightness+",\"color\":{\"r\":"+red+",\"g\":"+green+",\"b\":"+blue+"}}"
    val mqttActions = getActions("mqtt","mqtt:broker:btid")           // use the Thing ID for your configured Broker
    mqttActions.publishMQTT(topic,message)
    tvalue = now                                                         // update time 
    }
end

Thanks a lot Mr. Rich.
I have no idea about the best practices of coding. So maybe someone can make this better for the sake of other users. I can live with this for now.
Also I want to know is Can there be a binding or some predefined settings for adding esphome devices right away??

The only suggestions I’d have is to avoid using int until the last moment. Using primitives like int is known to significantly slow down Rules parsing (i.e. OH startup time) on RPis. It also helps code readability to indent when you have a new context (i.e. the { }).

It doesn’t hurt to split things up across multiple lines but I prefer to combine things into one line where I can.

While the timestamp appears to work, I think proper use of update and command ad corresponding triggers would be more appropriate.

rule "LED State changed, update Item with new state"
when
    Item Ledstate changed
then
    val json = transform("JSONPATH", "$.", Ledstate.state.toString)
    val b = new BigInteger(transform("JSONPATH", "$.brightness", json)) // get actual brightness value from esphome state msg
    val brightness = (b *100) /255                                    //calculate actual brightness value percentage

    val red = new BigDecimal(transform("JSONPATH", "$.color.r", json))
    val green = new BigDecimal(transform("JSONPATH", "$.color.g", json))
    val blue = new BigDecimal(transform("JSONPATH", "$.color.b", json))

    // HSB stands for Hue, Saturation, and Brightness
    // Typically an RGB doesn't include a brightness and the brightness is calculated based on how large the
    // RGB values are. For example (200,200,200) would be brighter than (50,50,50)
    // If esphome is sending RGB values that do not correspond with the brightness they delivered then
    // esphome is not using RGB in a standard way. Are you certain the range of RGB values reported by
    // esphome is 0-255? If the range is larger than they need to be normalized like you did for brightness above
    var hsbRGB = HSBType::fromRGB(red, green, blue)
    var hsdCorrected = new HSBType(hsbRGB.hue, hsbRGB.saturation, new PercentType(brightness))

    fastled_ws2811_light.postUpdate(hsbCorrected)
end

I’ll leave it to you to apply some of these ideas to your other Rule. Just be sure to change the trigger to Member of LEDs received command.