Shelly Bulb via MQTT 2.4

Hi there,

I wanted to share a tutorial for integrating the Shelly Bulb into openHAB 2.4 via Mosquitto.
Unfortunately I’m struggeling quite at the end - would be great if someone could help …

First at all you need to set up your MQTT broker (mosquitto) and the binding (including adding the broker as a thing in OH).

I recommend to use MQTT.fx in order to see what’s happening on the shelly.

  • Log in to the web interface of your shelly bulb via browser:
    http://192.168.178.XX

  • Then go to “Internet & Security” / Advanded and “enable action execution via MQTT”
    There you have to provide the login data of your MQTT broker and its IP:Port

  • After that you need to add a new thing for the Shelly Bulb in the Paper UI.
    Go to Configuration/Things, select +, choose “MQTT Thing Binding”, click on “add manually” and select “Generic MQTT Thing”,
    Provide a name and Thing ID and select “MQTT Broker” in bridge selection.

  • After the new thing has been added you can create new channels for it - e.g. a Switch
    MQTT state topic: shellies/shellybulb-XYZ/color/0
    MQTT command topic: shellies/shellybulb-XYZ/color/0/status
    Show More:
    On/Open value: on
    Off/Closed value: off

At this point I’m lost :frowning:

I’ve created an item

Switch Shellybulb1Switch "shellybulb1_switch" (Gtest) {channel="mqtt:topic:9d69f90f:shellybulb1_switch"}

But it only reacts when I switch the bulb via the shelly web interface - not vice versa …

The messages according to MQTT.fx are:

shellies/shellybulb-XYZ/color/0 --> on 
shellies/shellybulb-XYZ/color/0/status --> {"ison":true,"mode":"color","red":255,"green":87,"blue":59,"white":0,"gain":100,"temp":4015,"brightness":100,"effect":0}
shellies/shellybulb-XYZ/color/0 --> off
shellies/shellybulb-XYZ/color/0/status --> {"ison":false,"mode":"color","red":255,"green":87,"blue":59,"white":0,"gain":100,"temp":4015,"brightness":100,"effect":0}

with the correct log in OH:

Shellybulb1Switch changed from OFF to ON
Shellybulb1Switch changed from ON to OFF

BUT when if switch the item on the sitemap it only triggers

shellies/shellybulb-XYZ/color/0/status --> on

but not the other topic.

OH log:

Item 'Shellybulb1Switch' received command ON
Shellybulb1Switch predicted to become ON
Shellybulb1Switch changed from OFF to ON

Are you sure the command topic ends on status? That seems rather odd.

Actually I’m not sure …

I’m a newbie to MQTT and have tried to adapt the syntax from Shelly1 as described here

The MQTT.fx topics collector delivers 2 topics:

shellies/shellybulb-3CC557/color/0
shellies/shellybulb-3CC557/color/0/status

that’s all.

I think the problem is that one need to transform the commands from openhab to MQTT.
There are some options in the channel configuration - but I don’t know how to do it …

That other topic cleary says:

  • MQTT state topic: shellies/shelly1-123ABC/relay/0
  • MQTT command topic: shellies/shelly1-123ABC/relay/0/command
1 Like

Thank you so much - this is the solution :slight_smile:

:+1:

For future reference, here the full description of the Shelly Bulb MQTT API
http://shelly-api-docs.shelly.cloud/#bulb-mqtt

excerpt from link above:
shellies/shellybulb-<deviceid>/color/0/command accepts on and off payloads
shellies/shellybulb-<deviceid>/color/0 is used by the device to report its current on-off state

For controlling other parameters of the LED channels publish to
shellies/shellybulb-<deviceid>/color/0/set
Subscribers can use this to obtain the latest device state.
shellies/shellybulb-<deviceid>/color/0/status
for format of the json and all options see link above

1 Like

Now I would like to use the color picker:

Color Shellybulb1Color "shellybulb1_color" (Gtest) {channel="mqtt:topic:xyz:shellybulb1_color"}

The channel for " shellybulb1_color" is:

MQTT state topic: shellies/shellybulb-xyz/color/0
MQTT command topic: shellies/shellybulb-xyz/color/0/set

Could you please help me with JSON payload.

According to the Shelly doc

Device expects a JSON payload on this topic, with the following sample contents:

{
    "ison": false,   /* bool, can be set with on and off commands */
    "mode": "color", /* "color" or "white" */
    "red": 0,        /* red brightness, 0..255, applies in mode="color" */
    "green": 0,      /* green brightness, 0..255, applies in mode="color" */
    "blue": 255,     /* blue brightness, 0..255, applies in mode="color" */
    "white": 0,      /* white brightness, 0..255, applies in mode="color" */
    "gain": 100,     /* gain for all channels, 0..100, applies in mode="color" */
    "temp": 4750,       /* color temperature in K, 3000..6500, applies in mode="white" */
    "brightness": 100,  /* brightness, 0..100, applies in mode="white" */
    "effect": 0 /* applies an effect when set */
}

When setting the color to red in the web interface MQTT.fx tells me:

shellies/shellybulb-XYZ/color/0/status --> {"ison":true,"mode":"color","red":255,"green":0,"blue":0,"white":0,"gain":100,"temp":4015,"brightness":100,"effect":0}

Where do I have to put that code above in? Is it for “outgoing value format”?

Your question sounds as you may not be familiar with rules…is so please read the documentation before you proceed.
I have everything defined in a text file, so you will need to adjust if you configure things and items in PaperUI (and I cannot help a lot there as I am not using it). Finally, do NOT mix textual configuration and configuration in text files.
So here my thing definition:

Bridge mqtt:broker:mosquitto "Mosquitto" @ "Systems" [ host="127.0.0.1", secure=false ]	
	{
	Thing topic Shelly_Down_front_left "Bulb Front left" @ "Downstairs" {
	Channels: 
		Type string : Power "Switch" [ stateTopic="shellies/shellybulb-3CC48E/color/0", commandTopic="shellies/shellybulb-3CC48E/color/0/command" ]
		Type string : Shelly_status "Status" [ stateTopic="shellies/shellybulb-3CC48E/color/0/status", commandTopic="shellies/shellybulb-3CC48E/color/0/set" ]
	}
}

My corresponding items:

String Down_front_left_proxy "Proxy for Wall Downstairs Front left" {channel="mqtt:topic:mosquitto:Shelly_Down_front_left:Power"}
Switch Down_front_left "Wall Downstairs Front left" (gShelly_power, gShelly_lamps, gTwilightLightsON, gTwilightLightsOFF, gWeatherTwoLightsON, gWeatherTwoLightsOFF, gDownstairs,
        gMorningLightsON, gMorningLightsOFF, gLights,gLightPanel,gEveningLightsON, gNightLightsOFF) // {channel="mqtt:topic:mosquitto:Shelly_Down_front_left:Power"}
String Down_front_left_status "Status bulb down front left" (gShelly_status) {channel="mqtt:topic:mosquitto:Shelly_Down_front_left:Shelly_status"}
Color Down_front_left_color "Wall Downstairs Front left Color" (gShelly_color, gShelly_lamps)
Switch Down_front_left_mode "bulb mode" (gShelly_mode, gShelly_lamps) //white or color
Dimmer Down_front_left_dim "Brightness" (gShelly_dim, gShelly_lamps)
Dimmer Down_front_left_coldim "Color Temperature" (gShelly_coldim, gShelly_lamps)

My Rules:
Maybe a quick remark: the code is not optimized and I am sure can be improved. I have four bulbs and I wrote one rule for each function that works for all four; however, it is relying on groups and naming schemes.
I left a lot of commented out login statements in here that I use to debug, you can delete as you wish.
A lot of the rules deals with parsing the JSON, and more importantly putting it back together.
You will need the JSON transform loaded in your system.

import org.eclipse.smarthome.model.script.ScriptServiceUtil

rule "shelly Sitemap to MQTT"
when 
	Member of gShelly_power received command
then
    //logInfo("Sitemap to MQTT", "rule triggered, triggering item: " + triggeringItem.name.toString)
    //logInfo("Sitemap to MQTT", "Item to be used: " + ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name.toString +"_proxy").name.toString)
	Thread::sleep(10)
    //val proxy = triggeringItem.name.toString +"_proxy"
    //val testItem = ScriptServiceUtil.getItemRegistry.getItem(proxy)
	if (triggeringItem.state == ON ) {//sendCommand(triggeringItem.name.toString +"_proxy", "on")
        ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name.toString +"_proxy").sendCommand("on")
    }
    if (triggeringItem.state == OFF ) {
        ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name.toString +"_proxy").sendCommand("off")
    }
    Thread::sleep(10)                    //wait for persistence to catch up
end


rule "HSB value to RGB color value"
when
	Member of gShelly_color received command 
then
    if ((triggeringItem == NULL) || (triggeringItem == UNDEF))
        return;
    //logInfo("Colorpicker", "colorpicker" + Down_front_left_color.state.toString)
	val hsbValue = triggeringItem.state as HSBType
    val brightness = hsbValue.brightness.intValue 
	val redValue = ((((hsbValue.red.intValue * 255) / 100) *brightness) /100).toString 
	val greenValue = ((((hsbValue.green.intValue * 255) / 100) *brightness) /100).toString
	val blueValue = ((((hsbValue.blue.intValue * 255) / 100) *brightness) /100).toString
    val String new_color = "\"red\":" + redValue + ",\"green\":" + greenValue + ",\"blue\":" + blueValue 
    val trig_name_base = triggeringItem.name.toString.split("_color").get(0)
    val trig_base = ScriptServiceUtil.getItemRegistry.getItem(trig_name_base)
    val trig_status = ScriptServiceUtil.getItemRegistry.getItem(trig_name_base + "_status")
    //logInfo("Colorpicker", "original status: "+trig_status.state.toString)
    //logInfo("colorpicker", "This is the new color: "+ new_color)
    var String new_status = trig_status.state.toString.split("\"red\"").get(0) + new_color +  ",\"white\"" + trig_status.state.toString.split("\"white\"").get(1)
    new_status = new_status.split("\"mode\"").get(0) + "\"mode\":" + "\"color\"" + ",\"red\"" +  new_status.split("\"red\"").get(1)
    //logInfo("colorpicker", "New Status: "+new_status)
    trig_status.sendCommand(new_status)
    trig_base.sendCommand(ON)
    Thread::sleep(10)                    //wait for persistence to catch up
end



rule "set colortemp and brightness"
when 
    Member of gShelly_coldim received command or 
    Member of gShelly_dim received command 
then 
    if ((triggeringItem == NULL) || (triggeringItem == UNDEF))
        return;
    Thread::sleep(10)
    //logInfo("brightness", "Dimmer value is: " + Down_front_left_dim.state.toString)
    //logInfo("brightness", "Colortemp is: " + Down_front_left_coldim.state.toString)
    var String trig_name_base
    if (triggeringItem.name.toString.contains("_coldim")) {
        trig_name_base = triggeringItem.name.toString.split("_coldim").get(0)
    }
        else trig_name_base = triggeringItem.name.toString.split("_dim").get(0)
    val trig_base = ScriptServiceUtil.getItemRegistry.getItem(trig_name_base)
    val trig_status = ScriptServiceUtil.getItemRegistry.getItem(trig_name_base + "_status")
    val trig_coldim = ScriptServiceUtil.getItemRegistry.getItem(trig_name_base + "_coldim")
    val trig_dim = ScriptServiceUtil.getItemRegistry.getItem(trig_name_base + "_dim")
    val color_temp = new DecimalType(trig_coldim.state.toString) * 35 +3000 
    //logInfo("brightness", "New colortemp is: " + color_temp.toString)
    var String new_status = trig_status.state.toString.split("\"temp\"").get(0) + "\"temp\":" + color_temp.toString + ",\"brightness\":" + trig_dim.state.toString + ",\"effect\"" + trig_status.state.toString.split("\"effect\"").get(1)
    new_status = new_status.split("\"mode\"").get(0) + "\"mode\":" + "\"white\"" + ",\"red\"" +  new_status.split("\"red\"").get(1)
    //logInfo("brightness", "old status is: " + Down_front_left_status.state.toString)
    //logInfo("brightness", "new status is: " + new_status)
    trig_status.sendCommand(new_status)
    trig_base.sendCommand(ON)
    Thread::sleep(10)                    //wait for persistence to catch up
  end 


rule "color or white mode"
when 
    Member of gShelly_mode received command
then 
    Thread::sleep(10)
    //logInfo("CWmode", "triggeringItem: " + triggeringItem.name.toString)
    val trig_name_base = triggeringItem.name.toString.split("_mode").get(0)
    //logInfo("CWmode", "trig_name_base = " + trig_name_base)
    val trig_base = ScriptServiceUtil.getItemRegistry.getItem(trig_name_base)
    //logInfo("CWmode", "first one down")
    val trig_status = ScriptServiceUtil.getItemRegistry.getItem(trig_name_base + "_status")
    if (triggeringItem.state == OFF) {
        trig_status.sendCommand(trig_status.state.toString.split("\"mode\"").get(0) + "\"mode\":" + "\"white\"" + ",\"red\"" + trig_status.state.toString.split("\"red\"").get(1))
        trig_base.sendCommand(ON)
    }
    if (triggeringItem.state == ON) {
        trig_status.sendCommand(trig_status.state.toString.split("\"mode\"").get(0) + "\"mode\":" + "\"color\"" + ",\"red\"" + trig_status.state.toString.split("\"red\"").get(1))
        trig_base.sendCommand(ON)
    }
    Thread::sleep(10)                    //wait for persistence to catch up
end



rule "Update OH from Shellies"
when 
    Member of gShelly_status changed
then 
    Thread::sleep(10)
    //logInfo("update", "triggering Item: " + triggeringItem.name.toString)
    val trig_name_base = triggeringItem.name.toString.split("_status").get(0)
    //logInfo("update", "trig_name_base = " + trig_name_base)
    val trig_base = ScriptServiceUtil.getItemRegistry.getItem(trig_name_base)
    val trig_coldim = ScriptServiceUtil.getItemRegistry.getItem(trig_name_base + "_coldim")
    val trig_dim = ScriptServiceUtil.getItemRegistry.getItem(trig_name_base + "_dim")
    val trig_mode = ScriptServiceUtil.getItemRegistry.getItem(trig_name_base + "_mode")
    val trig_color = ScriptServiceUtil.getItemRegistry.getItem(trig_name_base + "_color")
    val json = triggeringItem.state.toString
    //logInfo("update", "json is: " + json)
    //logInfo("update", "temp: " + transform("JSONPATH", "$.temp", json))
    //logInfo("update", "Updating temp with: " + Math.round((Float::parseFloat(transform("JSONPATH", "$.temp", json))-3000)/35))
    trig_coldim.postUpdate(Math.round((Float::parseFloat(transform("JSONPATH", "$.temp", json))-3000)/35)) 
    trig_dim.postUpdate(Integer::parseInt(transform("JSONPATH", "$.brightness", json)))
    if ("white" == transform("JSONPATH", "$.mode", json)) trig_mode.postUpdate(OFF) 
    if ("color" == transform("JSONPATH", "$.mode", json)) trig_mode.postUpdate(ON) 
    trig_color.postUpdate(HSBType.fromRGB(Integer::parseInt(transform("JSONPATH", "$.red", json)) , Integer::parseInt(transform("JSONPATH", "$.green", json)) , Integer::parseInt(transform("JSONPATH", "$.blue", json))))
    if ("true" == transform("JSONPATH", "$.ison", json)) trig_base.postUpdate(ON) else trig_base.postUpdate(OFF)
end

As I said earlier, not pretty but it works for me.
and just for completeness, here my sitemap entries:

Switch item=Down_front_left
Switch item=Down_front_left_mode mappings=[ON="Color",OFF="White"] 
Colorpicker item=Down_front_left_color  visibility=[Down_front_left_mode==ON]
Slider item=Down_front_left_dim  visibility=[Down_front_left_mode==OFF]
Slider item=Down_front_left_coldim  visibility=[Down_front_left_mode==OFF]

Visibility is used to display colorpicker only in color mode and color temperature and intensity only in white mode.

Hope that helps

1 Like

@lipp_markus: Thanks a lot!

I’ll look into this. I wasn’t aware that one need so many code lines just to set the RGB values …
Do I have to install this addon first, right?
“JSONPath Transformation”

That’s because you need outgoing transformations, and they are not yet in (probably in a few days though).
And to ship around that shortcoming, you are forced to use full blown rules for the moment. If you can wait for another week, a shorter solution will be doable as well.

Cheers, David

1 Like

Yes, you will need to install the JSONPATH transformation.

Sounds promising. Will there be a dedicated shelly binding?

Nope, I was talking to the shelly devs, to use a MQTT convention like Homie instead. They have not responded / forgotten about it. But that is the right way to go. Is their MQTT support still “experimental”? Then it’s still possible to convince them probably.

1 Like

So let’s keep fingers crossed :wink:

Allterco guy here. Homie is one among many attempts to “standartize” the IoT – very OpenHAB-specific, so not really a widely adopted standard per se. MQTT in shellies tries to be the simplest possible functional interface to allow for easy integration with any system, not just openHAB. HomeAssistant have their own thing with the discovery mechanism, probably many other “standards” exist. Truth it, you can’t really do without a “plugin” of sorts on your home automation orchestrator. So, it is very unlikely that we ever implement Homie for Shellies.

I tried reading through the topic to understand what the problem is, but I’d appreciate it if someone summarizes the issue. Can we maybe make tiny changes to the MQTT interface to facilitate integration?

Basically, use shellies/shellybulb-<deviceid>/color/0/set to control the device and shellies/shellybulb-<deviceid>/color/0/status to subscribe for status updates. Both topics work with json payloads, input and output format should be identical.

1 Like

You are very close to the homie topic layout, have you noticed that? But you are missing self-describing information, like a type (string, integer, boolean) for topic values etc. And names for the device itself and its functions.
Without those a (generic) discover mechanism of any sorts is not really feasible.

I am looking at

https://homieiot.github.io/specification/spec-core-develop/

and it doesn’t seem trivial to add this without a) a fair amount of effort and b) remaining backward-compatible.

Can we maybe provide a static registry of sorts not hosted on the devices themselves, to be used for discovery and configuration? I haven’t played with openHAB so excuse the ignorance…

Homie tries to stay dependency free, that’s why there is no json used. You guys have chosen to compact some states into json objects. But that doesn’t prevent you from becoming Homie compliant nevertheless, with a trick.
(That is: extensions)

But for easy integration I really need some sorts of:

  • shelly/device-id/$name
  • shelly/device-id/$ready (published as last-will, to identify when a shelly went offline)
  • shelly/device-id/$nodes (a comma separated list of features. We call those properties in Homie. I guess that can be made available in a static registry)

For what we call nodes in Homie:

  • shelly/device-id/color/$name (a name for this feature → We need a presentable string for the user)
  • shelly/device-id/color/$properties (a list of sub-topics, comma separated, like “0” in your color case. Again a candidate for a central registry)

And you even have something like properties:

  • shelly/device-id/color/0 (In Homie we have the status directly published to this topic, you are using “status” instead. It would be super helpful if you publish to this topic as well, I guess)
  • shelly/device-id/color/0/set (that’s perfect, we are using this topic as well in Homie for setting a value)

For being discoverable you need a name though like:

  • shelly/device-id/color/0/$name (=“First bulb”)
  • shelly/device-id/color/0/$type (and that is were we apply the magic. Use “json-shelly” as type and just keep your json format for /set and the value topic and done, you are homie compliant. I mean, almost.)

If you decide to add those little meta topics, if you add two more, you are actually completely Homie compliant. That is:

  • shelly/device-id/$homie (=4.0)
  • shelly/device-id/$extensions (=“shelly”)

That’s my proposal. I would add the “shelly” extension to the Homie webpage and OH2 MQTT binding.

Cheers, David

2 Likes

Hi there,

Any news on this?