Getting shelly / shellies announce data via MQTT 2.4

no, this is not what I’m saying. my problem is that the transformation just does not work. I had set:

REGEX:bla

But the value that was assigned to the item is != bla
This must not be the case, right?

I don’t know if this was already suggested, but you could simplify the whole “command: announce” loop by sending the MQTT message to “shellies/command” with the payload “announce”. This will force all shellies on the MQTT server to send their announce message.

Only if the full message is literally “bla” would the regex match. But because you didn’t provide any parens the REGEX doesn’t actually return anything.

If you want “bla” assigned to the Item, like that your message needs to be just “bla” and the REGEX needs to be

REGEX:(bla)

If you want the Item to be assigned “bla” when any message that contains bla in it the REGEX would be

REGEX:.*(bla).*

If you want the Item to be assigned the full message for any message that contains bla the REGEX needs to be

REGEX:(.*bla.*)

The expression must match the full message. And you must provide parens to indicate the part of the message you want to pass on.

1 Like

You cannot imagine how thankful I am, but at the same time I’m really sorry because I don’t understand.

What I did:

  1. REGEX:(bla)
  2. MQTT message ‘foobar’ on topic ‘shellies/announce’

Second test with:

  1. REGEX:.*(bla).*
  2. MQTT message ‘foobar’ on topic ‘shellies/announce’

What I see in both cases:
MQTTTestItem changed from […] to foobar

It shouldn’t behave like this, right? I’d expect the item value either to be empty or unchanged.

But if you use “REGEX:something” then it doesn’t match “foobar” topic values anymore? Otherwise the regex transformation is not used at all.

That is the behavior I would have expected too.

I would have expected that if the REGEX doesn’t return anything that nothing happens, not that the full message get’s passed on bypassing REGEX. In my mind, a “nothing returned” result from the REGEX transform is a valid result of the transformation.

I could see ambiguity in how that empty value should be treated. Should that be an error and set the Item to UNDEF? Should that be itself passed on and set the Item to “”? Or should the message itself just become ignored?

Obviously, I would prefer the latter since it let’s us achieve the REGEX filter capability from MQTT1 which, unfortunately, is quite useful given how a number of providers decided it was a good idea to publish data from multiple devices to the same topic, like discussed above.

Of course this behavior also begs the question, how is it that when I put the REGEX in front of a JSONPATH it appears to work as I intend. If I use REGEX:(.*<shelly name>.*)∩JSONPATH:$.ip, only those messages that include <shelly name> appear to get passed to the JSONPATH and the rest of the messages that don’t include it get ignored.

no matter what I write after REGEX: the item will always change to foobar. even if I combine the regex with jsonpath

hey guys, some time has passed and I was forced to reinstall my whole OH instance. unfortunately I’m still facing the same issue. using the REGEX expression followed by the JSONPATH filter I’d like to assign the IP of a specific shelly device to a specific OH item. This does not work. Not sure if we were on the same page. Do you understand the problem? In any case, thanks in advance for your help.

Using OH 2.5 M1? Chaining doesn’t work in 2.4.

yep, 2.5M1

What I expect is:
If a specific shelly device name has been found in the announce message, then assign the IP address to a specific item. Unfortunately the first part (the device name) is ignored.

I know this works.

Does the announce message include the IP address?

need to sleep over it. maybe I’m really doing something stupid or this is a weird bug. will report back tomorrow.

If you keep in place the REGEXP with JSONPATH in the Channel definition you can’t get all the info you want because you’re trimming out those information.

You can try to clean up the channel definition and keep just the state topic shellies/announce.
Then you can make a rule to reach your goal.
For example…create a text item linked to the annouce channel …let’s call it Shellies_Annouce…you can write a rule like this:

rule "test rule"
when
Item Shellies_Annouce changed
then
var id = transform("JSONPATH", "$.id", Shellies_Annouce)
var ip = transform("JSONPATH", "$.ip", Shellies_Annouce)

if(id == 'yourshellyID'){
YourIpItem.postUpdate(ip)
}

Of course change the name of the items to match yours.

I can confirm that the REGEX filtering does not work as we would like. At least not with two or more shellies. I defined the following channel for both my Shelly Plug S’s to extract the IP address directly (without the JSONPATH chaining):

Type string : ip "IP S1"         [ stateTopic="shellies/announce", transformationPattern="REGEX:.*shellyplug-s-7AE33C.*\"ip\":\"([0-9\\.]*)\".*" ]
...
Type string : ip "IP S2"         [ stateTopic="shellies/announce", transformationPattern="REGEX:.*shellyplug-s-7AEFF1.*\"ip\":\"([0-9\\.]*)\".*" ]

To force an update, I use: mosquitto_pub -t 'shellies/command' -m 'announce'. My log then shows what is happening:

11:49:27.453 [INFO ] [smarthome.event.ItemStateChangedEvent] - ShellyS1_IP changed from 192.168.249.106 to {"id":"shellyplug-s-7AEFF1","mac":"B4E62D7AEFF1","ip":"192.168.249.107","new_fw":false, "fw_ver":"20190531-080150/v1.5.0-hotfix2@022ec015"}
11:49:27.455 [INFO ] [smarthome.event.ItemStateChangedEvent] - ShellyS2_IP changed from {"id":"shellyplug-s-7AE33C","mac":"B4E62D7AE33C","ip":"192.168.249.106","new_fw":false, "fw_ver":"20190531-080150/v1.5.0-hotfix2@022ec015"} to 192.168.249.107
11:49:27.505 [INFO ] [smarthome.event.ItemStateChangedEvent] - ShellyS1_IP changed from {"id":"shellyplug-s-7AEFF1","mac":"B4E62D7AEFF1","ip":"192.168.249.107","new_fw":false, "fw_ver":"20190531-080150/v1.5.0-hotfix2@022ec015"} to 192.168.249.106
11:49:27.506 [INFO ] [smarthome.event.ItemStateChangedEvent] - ShellyS2_IP changed from 192.168.249.107 to {"id":"shellyplug-s-7AE33C","mac":"B4E62D7AE33C","ip":"192.168.249.106","new_fw":false, "fw_ver":"20190531-080150/v1.5.0-hotfix2@022ec015"}

So if the regex matches, then the output is the matched string, otherwise, the output is the full message.

My conclusion is that this approach does not work for this use-case, even if the output of the regex transformation would be an empty string, when the regex does not match. I think in that case all IP would get set to empty, except for the last device that is announced. So I guess we have to solve this in a rule, just as @alexxio suggested.

It would be better when the Shelly firmware would reply to the general announce request in a separate topic for each device: shellies/shelly-<deviceid>/announce`. Let’s try to convince them to do that.

hallelujah :wink: thanks for the confirmation. then I know I’m nothing doing wrong

Hello,

thanks for hints with JSONPATH. With Shelly firmware 1.6.X every device has got its own announce topic :star_struck:

So for me works

mqtt.things

    Thing topic ShellyBulbDuo_66483E "ShellyBulbDuo-66483E" @ "Wohnzimmer" {
        Channels:
            Type switch : chan_ShellyBulbDuo_66483E_light       "Light"                 [ stateTopic="shellies/ShellyBulbDuo-66483E/light/0", commandTopic="shellies/ShellyBulbDuo-66483E/light/0/command", on="on", off="off" ]
            Type string : chan_ShellyBulbDuo_66483E_firmware    "Firmware"              [ stateTopic="shellies/ShellyBulbDuo-66483E/announce", commandTopic="shellies/ShellyBulbDuo-66483E/command", transformationPattern="JSONPATH:$.fw_ver"]
            Type switch : chan_ShellyBulbDuo_66483E_fwupdate    "FW-Update"             [ stateTopic="shellies/ShellyBulbDuo-66483E/announce", transformationPattern="JSONPATH:$.new_fw", on="true", off="false" ]
            Type string : chan_ShellyBulbDuo_66483E_ip          "IP-Adresse"            [ stateTopic="shellies/ShellyBulbDuo-66483E/announce", transformationPattern="JSONPATH:$.ip"]
            Type string : chan_ShellyBulbDuo_66483E_id          "Shelly-ID"             [ stateTopic="shellies/ShellyBulbDuo-66483E/announce", transformationPattern="JSONPATH:$.id"]
            Type string : chan_ShellyBulbDuo_66483E_mac         "MAC-Adresse"           [ stateTopic="shellies/ShellyBulbDuo-66483E/announce", transformationPattern="JSONPATH:$.mac"]
    }

XXX.items

String ChanShellyBulbDuo66483EFirmware 
    "WZ Kugel 3 FW" 
    <text>
    (gEg_Wohnzimmer,gShellyDetails,gNoPersistence,gShellyAnnounce)
    {channel="mqtt:topic:mqttHAN:ShellyBulbDuo_66483E:chan_ShellyBulbDuo_66483E_firmware"}
String ChanShellyBulbDuo66483EIp 
    "WZ Kugel 3 IP" 
    <text>
    (gEg_Wohnzimmer,gShellyDetails,gNoPersistence)
    {channel="mqtt:topic:mqttHAN:ShellyBulbDuo_66483E:chan_ShellyBulbDuo_66483E_ip"}
Switch ChanShellyBulbDuo66483EFwupdate 
    "WZ Kugel 3 FW Upd" 
    <siren>
    (gEg_Wohnzimmer,gShellyDetails,gNoPersistence)
    {channel="mqtt:topic:mqttHAN:ShellyBulbDuo_66483E:chan_ShellyBulbDuo_66483E_fwupdate"}
String ChanShellyBulbDuo66483EId 
    "WZ Kugel 3 ID" 
    <text>
    (gEg_Wohnzimmer,gShellyDetails,gNoPersistence)
    {channel="mqtt:topic:mqttHAN:ShellyBulbDuo_66483E:chan_ShellyBulbDuo_66483E_id"}
String ChanShellyBulbDuo66483EMac 
    "WZ Kugel 3 MAC" 
    <text>
    (gEg_Wohnzimmer,gShellyDetails,gNoPersistence)
    {channel="mqtt:topic:mqttHAN:ShellyBulbDuo_66483E:chan_ShellyBulbDuo_66483E_mac"}

shelly.rules

// Regel löst von allen Mitgliedern der Gruppe gShellyAnnounce
// einen Status per announce auf das command Topic aus
rule "Shelly announce"
when
        Channel 'astro:sun:local:noon#event' triggered START
        or Item ReReadShellyDetails received command ON
then
    // Loop through the Shelly Announce Items and send the "announce" command to each, 5 seconds apart
    gShellyAnnounce.members.forEach[ shelly, index | 
        createTimer(now.plusSeconds(2*index), [ | shelly.sendCommand("announce") ] )
    ]
    ReReadShellyDetails.postUpdate(OFF)
end

Thanks to Allterco

Hello to all,

I am translating my rules to jython with the helpers library and it´s time to translate this one.

rule "QueryShellyStatus"

when

    System started or
    Time cron "0 0/5 * 1/1 * ? *" //every 5 minutes

then

    // Loop through the Shelly Announce Items and send the "announce" command to each, 5 seconds apart

    gShellyGeneralCommands.members.forEach[ shelly, index | 
        createTimer(now.plusSeconds(5*index), [ | shelly.sendCommand("announce") ] )
    ]

end

I am trying the following in jython:

@rule("QueryShellyStatus", description="Query Shelly Status", tags=["system_status"])
@when("System started")
@when("Time cron 0 0/5 * 1/1 * ? *") #every 5 minutes
def query_shelly_status(event):
    index = 0
    for shelly in ir.getItem("gShellyGeneralCommands").members:
        index = index + 1 
        ScriptExecution.createTimer(DateTime.now().plusSeconds(5*index), lambda: events.sendCommand(str(shelly.name), "announce"))

I do not know what I am doing wrong, or not so correct because i get a timer each 5 seconds just for the last iterated item.

Could anyone give me a hand please?

Thanks in advanced for the support.

I don’t think I can help much with this code beyond mentioning that I once ran into something like this and I can’t remember how I solved it. It’s a quirk of Python though in that it references the variable “shelly” by reference so when the timer goes off, it accesses “shelly” which is currently what ever the last value in the iteration happened to be. If I recall correctly, you need to pass the “shelly” to the lamda and set it to a lambda local variable.

...index), lambda s=shelly: events.sendCommand(str(s.name)...

I’m not 100% positive on this though.

Also, there should be no need to call str on the name. It’s already a String.

Having said that, since you are reworking this stuff anyway it might be worth looking into the Shelly binding. I believe this Rule would not even be required when using the that binding. The binding itself polls the Shellies for the information that is published on an announce.

Hello @rlkoshak

Thanks a lot for your prompt reply and help. You nailed it :slight_smile:. Understood perfectly what you explained. Tested and works flawlessly.

Also, there should be no need to call str on the name. It’s already a String.

Corrected this too. Makes perfect sense.

Having said that, since you are reworking this stuff anyway it might be worth looking into the Shelly binding. I believe this Rule would not even be required when using the that binding. The binding itself polls the Shellies for the information that is published on an announce.

I will have a look at the shelly binding also to test it.

Thanks again for your precious input.

I had used lambdas in most of the examples in the helper library documentation, since it would be more familiar for users of the rules DSL. However, lambdas are limited to one line and can be tricky to use, so I had updated the docs a while ago to include options that do not use them. You’ll see this pushed soon. In order for you to not use a lambda, you will need to use createTimerWithArgument, which is the same as createTimer, but it allows for you to pass one argument. That may sound limiting, but the argument can be any object, so it can be a collection of objects. Here are some examples, which I have also now added to the docs…

from core.actions import ScriptExecution
from org.joda.time import DateTime


@rule("QueryShellyStatus", description="Query Shelly Status", tags=["system_status"])
@when("System started")
@when("Time cron 0 0/5 * * * ?") #every 5 minutes
def query_shelly_status(event):
    index = 0
    for shelly in ir.getItem("gShellyGeneralCommands").members:
        ScriptExecution.createTimerWithArgument(DateTime.now().plusSeconds(5 * index), StringType("announce"), shelly.send)
        index += 1
from core.actions import ScriptExecution
from org.joda.time import DateTime


def timer_function(item_name):
    events.sendCommand(item_name, "announce")


@rule("QueryShellyStatus", description="Query Shelly Status", tags=["system_status"])
@when("System started")
@when("Time cron 0 0/5 * * * ?") #every 5 minutes
def query_shelly_status(event):
    index = 0
    for shelly in ir.getItem("gShellyGeneralCommands").members:
        ScriptExecution.createTimerWithArgument(DateTime.now().plusSeconds(5 * index), shelly.name, timer_function)
        index += 1