5500SHAC - Lua to enumerate all group addresses

Discussion in 'C-Bus Automation Controllers' started by Narkov, Jan 1, 2020.

  1. Narkov

    Narkov

    Joined:
    Nov 12, 2019
    Messages:
    20
    Likes Received:
    4
    Hi,

    I want to have a resident Lua script that will periodically grab all lighting group addresses and broadcast them via MQTT.

    The MQTT side of the code is working fine but I can't figure out what SHAC Lua function will allow me to dump all active group addresses. I can all them individually ("GetCBusLevel") but can't find anything that will just dump all active.

    Any ideas? Thanks in advance.
     
    Narkov, Jan 1, 2020
    #1
  2. Narkov

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    You can use grp.all() which returns a lua table with all objects and their data, you would then need to extract the data you need then send it to your broker etc

    I would have an event script (works like an if haschanged) for each GA you need to send to MQTT this way the SHAC is pushing the data when and GA state changes. Although this would be tedious to set up if you have a lot of them, I think there is a way to do a mass generate with a script, I’ll take a look later on. The only time you would need to grab all the values would be on start up or if c-bus goes offline.
     
    Pie Boy, Jan 1, 2020
    #2
  3. Narkov

    Narkov

    Joined:
    Nov 12, 2019
    Messages:
    20
    Likes Received:
    4
    Thanks for that! That looks like it has the data I need. Can I ask how you found this? I wasn't able to find anything in the script helpers or function reference.

    An easy way to capture all "haschanged" events in one script would be awesome. That way it would only be one call to process all events.

    So this is basically how I'm doing it. This grp.all() portion is to re-sync the MQTT broker in the event of a restart or inconsistency.

    Thanks again for your help.
     
    Narkov, Jan 1, 2020
    #3
  4. Narkov

    Narkov

    Joined:
    Nov 12, 2019
    Messages:
    20
    Likes Received:
    4
    I think I'm cooked here. I've been doing some research and I think the only way to do this is to add a tag to every group and then have an event based script execute on that tag. That's not scalable for 100+ groups.

    Not to mention, it looks as though each event will have to individually re-connect to MQTT to push a message. There's no persistent connection. Yuck :(

    It's a shame I can't use a resident script and then some kind of event loop.
     
    Narkov, Jan 2, 2020
    #4
  5. Narkov

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    if you create a user library, then run a function from a resident script to create a loop that would work

    create library called MQTT, inside it put function eg (if you need to change some code in the loop make sure to disable the resident script calling the function first)

    function MQTT_Loop()

    --Your MQtt connection/ processing code here

    --you can create another function that will be global and call that from any event script (you just need to require the library first) to send messages eg

    function MQTT_Publish(Ga, level)
    Client:publish('message'..GA..'/'..level)
    end

    end


    then from a resident script call that function every x seconds etc like this that takes care of the looping part

    require ("user.MQTT")
    MQTT_Loop()

    i'm working on a way to generate/create the event scripts for all required GA from one script so we don't have to manually tediously create them all
     
    Pie Boy, Jan 2, 2020
    #5
  6. Narkov

    Narkov

    Joined:
    Nov 12, 2019
    Messages:
    20
    Likes Received:
    4
    So that works fine for pushing MQTT events back to C-Bus but the problem remains with C-Bus events being pushed back to MQTT.

    I need to somehow get all group events to push through a single MQTT connection without having to reconnect to MQTT with each event.

    I'm now trying to use a local UDP socket in a resident program and have group events shoot off a packet to this program for each event. It's messy and I stlll need to have a event script attached to every group address but I think it's efficient from a processing point of view.
     
    Narkov, Jan 2, 2020
    #6
  7. Narkov

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    ok so you plan to have each even script with some thing like

    client.ON_CONNECT = function(stat, code, err)
    if stat then
    client:publish('devices/' .. device_id .. '/messages/events/', data)
    else
    log('mqtt connect error', err, code)
    end
    end

    client.ON_PUBLISH = function()
    client:disconnect()
    running = false
    end

    you are looking for a way to get all objects updated to mqtt at once?
     
    Pie Boy, Jan 2, 2020
    #7
  8. Narkov

    Narkov

    Joined:
    Nov 12, 2019
    Messages:
    20
    Likes Received:
    4
    The plan is to tag each group with "All" then have a event based script that basically calls:

    Code:
    require('socket').udp():sendto(event.dst .. "/" .. event.getvalue(), '127.0.0.1', 5432)
    This will be picked up by a resident script that handles a persistent connection to MQTT and reads any events off the socket and pushes them to MQTT.

    The only crappy part about this is having to manually tag >100 group addresses and keep them updated. The code works fine now but it's sloppy.

    The ideal situation would be to somehow have a way to capture all events in a single script.
     
    Narkov, Jan 2, 2020
    #8
  9. Narkov

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    you can write keywords from lua code, this code loops through from 0 to 255 and would apply "tags " to all objects with 0/56/xxx format

    for i = 0,255 do
    grp.addtags('0/56/'..i, 'tags')
    end
     
    Pie Boy, Jan 2, 2020
    #9
  10. Narkov

    Narkov

    Joined:
    Nov 12, 2019
    Messages:
    20
    Likes Received:
    4
    Wow...that gets me to about 99% of what I need. The only frustrating part would be remembering to add the tag if I add a new group. I guess I could just schedule a script to do this for me every 24 hours :)

    Thanks for your help Pie Boy. Once I get the script polished up, I'll post it here.
     
    Narkov, Jan 2, 2020
    #10
  11. Narkov

    Narkov

    Joined:
    Nov 12, 2019
    Messages:
    20
    Likes Received:
    4
    Ok. So here's what I have so far.

    I'm trying to replicate the below nodejs app without having to install node+cgate.

    https://github.com/the1laz/cgateweb

    There are a few components to make it all work:
    • A resident script to push commands received from MQTT to C-Bus
    Code:
    mqtt_broker = 'YOUR_MQTT_BROKER_HOST'
    mqtt_username = 'MQTT_USERNAME'
    mqtt_password = 'MQTT_PASSWORD'
    
    mqtt_read_topic = 'cbus/read/'
    mqtt_write_topic = 'cbus/write/#';
    
    -- load mqtt module
    mqtt = require("mosquitto")
    
    -- create new mqtt client
    client = mqtt.new()
    
    log("created MQTT client", client)
    
    -- C-Bus events to MQTT local listener
    server = require('socket').udp()
    server:settimeout(1)
    server:setsockname('127.0.0.1', 5432)
    
    client.ON_CONNECT = function()
      log("MQTT connected - send")
    end
    
    client:login_set(mqtt_username, mqtt_password)
    client:connect(mqtt_broker)
    client:loop_start()
    
    while true do
        cmd = server:receive()
        if cmd then
        parts = string.split(cmd, "/")
        network = 254
        app = tonumber(parts[2])
        group = tonumber(parts[3])
        level = tonumber(parts[4])
          state = (level ~= 0) and "ON" or "OFF"
        client:publish(mqtt_read_topic .. network .. "/" .. app .. "/" .. group .. "/state", state, 1, true)
        client:publish(mqtt_read_topic .. network .. "/" .. app .. "/" .. group .. "/level", level, 1, true)
        end
    end
    • A resident script to push events received from C-Bus to MQTT
    Code:
    mqtt_broker = 'YOUR_MQTT_BROKER_HOST'
    mqtt_username = 'MQTT_USERNAME'
    mqtt_password = 'MQTT_PASSWORD'
    
    mqtt_read_topic = 'cbus/read/'
    mqtt_write_topic = 'cbus/write/#';
    
    -- load mqtt module
    mqtt = require("mosquitto")
    
    -- create new mqtt client
    client = mqtt.new()
    
    log("created MQTT client", client)
    
    client.ON_CONNECT = function()
      log("MQTT connected - receive")
      local mid = client:subscribe(mqtt_write_topic, 2)
    end
    
    client.ON_MESSAGE = function(mid, topic, payload)
    
      log(topic, payload)
     
      parts = string.split(topic, "/")
    
      if not parts[6] then
       
        log('MQTT error', 'Invalid message format')
       
      elseif parts[6] == "getall" then
       
        datatable = grp.all()
        for key,value in pairs(datatable) do
          dataparts = string.split(value.address, "/")
              network = tonumber(dataparts[1])
              app = tonumber(dataparts[2])
          group = tonumber(dataparts[3])
          if app == tonumber(parts[4]) and group ~= 0 then
                level = tonumber(value.data)
                state = (level ~= 0) and "ON" or "OFF"
            log(parts[3], app, group, state, level)
            client:publish(mqtt_read_topic .. parts[3] .. "/" .. app .. "/" .. group .. "/state", state, 1, true)
                client:publish(mqtt_read_topic .. parts[3] .. "/" .. app .. "/" .. group .. "/level", level, 1, true)
              end  
          end
        log('Done')
      elseif parts[6] == "switch" then
       
        if payload == "ON" then
                SetCBusLevel(0, parts[4], parts[5], 255, 0)
        elseif payload == "OFF" then
          SetCBusLevel(0, parts[4], parts[5], 0, 0)
        end
       
      elseif parts[6] == "measurement" then
    
        SetCBusMeasurement(0, parts[3], parts[5], payload, -1)
       
      elseif parts[6] == "ramp" then
    
        if payload == "ON" then
                SetCBusLevel(0, parts[4], parts[5], 255)
        elseif payload == "OFF" then
          SetCBusLevel(0, parts[4], parts[5], 0)
        else
          ramp = string.split(payload, ",")
          num = round(ramp[1])
          if num and num < 256 then
            if ramp[2] ~= nil and ramp[2] > 1 then
                SetCBusLevel(0, parts[4], parts[5], num, ramp[2])
            else
                SetCBusLevel(0, parts[4], parts[5], num, 0)
            end
          end
        end
       
      end
     
    end
    
    client:login_set(mqtt_username, mqtt_password)
    client:connect(mqtt_broker)
    client:loop_forever()
    • An event based script to push C-Bus events to the MQTT scripts
    This script should be triggered against a keyword that is applied against every group address that you want to monitor. For me, I just used the keyword "All".

    Code:
    require('socket').udp():sendto(event.dst .. "/" .. event.getvalue(), '127.0.0.1', 5432)
     
    Narkov, Jan 2, 2020
    #11
    zei20t likes this.
  12. Narkov

    zei20t

    Joined:
    Aug 18, 2010
    Messages:
    130
    Likes Received:
    1
    Location:
    Sydney, Australia
    this is great, thank you!

    exactly what i was looking for, will hopefully not have to run cgate server and cgate web
     
    zei20t, Jan 21, 2021
    #12
  13. Narkov

    Narkov

    Joined:
    Nov 12, 2019
    Messages:
    20
    Likes Received:
    4
    I have been running this (without cgate server or web) now for over a year without any issues. It's rock solid and doesn't need any hand holding.

    Enjoy!
     
    Narkov, Jan 21, 2021
    #13
    zei20t likes this.
  14. Narkov

    Narkov

    Joined:
    Nov 12, 2019
    Messages:
    20
    Likes Received:
    4
    Narkov, Jan 21, 2021
    #14
    zei20t likes this.
  15. Narkov

    zei20t

    Joined:
    Aug 18, 2010
    Messages:
    130
    Likes Received:
    1
    Location:
    Sydney, Australia
    zei20t, Jan 21, 2021
    #15
  16. Narkov

    zei20t

    Joined:
    Aug 18, 2010
    Messages:
    130
    Likes Received:
    1
    Location:
    Sydney, Australia
    ok, thanks very much for this. i mostly got it working over the weekend.

    a few things, i think its my light config in HA though.

    im getting an error on the SHAC, i think this is due to 'ramp' topics:

    Resident script:74: attempt to call global 'round' (a nil value)
    stack traceback:
    Resident script:74: in function <Resident script:27>
    [C]: in function 'loop_forever'

    and weirdly, dimming from HA to cbus doesnt work, but cbus to HA shows the current level. I think its my light config. this is what im currently using:

    - platform: mqtt
    name: Office Main
    state_topic: "cbus/read/254/56/12/state"
    command_topic: "cbus/write/254/56/12/switch"
    brightness_state_topic: "cbus/read/254/56/12/level"
    brightness_command_topic: "cbus/write/254/56/12/ramp"
    brightness_scale: 100
    qos: 2
    payload_on: "ON"
    payload_off: "OFF"
    optimistic: false

    any ideas what im doing wrong please?
     
    zei20t, Jan 31, 2021
    #16
  17. Narkov

    Narkov

    Joined:
    Nov 12, 2019
    Messages:
    20
    Likes Received:
    4
    So my HA light entities look like this. The top one is a switch and the bottom one is a dimmer.

    - platform: mqtt
    name: Guest Bath
    state_topic: 'cbus/read/254/56/1/state'
    command_topic: 'cbus/write/254/56/1/switch'
    payload_on: 'ON'
    payload_off: 'OFF'
    unique_id: mqtt_1

    - platform: mqtt
    name: Pantry benches
    state_topic: 'cbus/read/254/56/2/state'
    command_topic: 'cbus/write/254/56/2/switch'
    brightness_state_topic: 'cbus/read/254/56/2/level'
    brightness_command_topic: 'cbus/write/254/56/2/ramp'
    payload_on: 'ON'
    payload_off: 'OFF'
    on_command_type: 'brightness'
    unique_id: mqtt_2

    I think the key difference with the dimmer is the "on_command_type" and this dictates which command is sent and when. I think you'll find that without it, the brightness value is sent to the switch command which it doesn't support.

    For your own benefit, add a "unique_id" as well so that you can edit the entities from the UI instead of just the YAML.

    Let me know how you go.
     
    Narkov, Jan 31, 2021
    #17
  18. Narkov

    zei20t

    Joined:
    Aug 18, 2010
    Messages:
    130
    Likes Received:
    1
    Location:
    Sydney, Australia
    Thanks for the reply, narkov.

    from cbus --> HA, i can see the dimming level and on/off status

    however, commands from HA --> cbus dont make it. weird.

    ive seen several configs for HA and cbus lights, ill just have to play and see what i get. or a combination of them lol
     
    zei20t, Feb 1, 2021
    #18
  19. Narkov

    zei20t

    Joined:
    Aug 18, 2010
    Messages:
    130
    Likes Received:
    1
    Location:
    Sydney, Australia
    ahh also thanks for the hint about adding the unique ID. didnt know that was possible!
     
    zei20t, Feb 1, 2021
    #19
  20. Narkov

    Narkov

    Joined:
    Nov 12, 2019
    Messages:
    20
    Likes Received:
    4
    It still doesn't work with the on_command_type or you haven't tried that yet?
     
    Last edited: Feb 1, 2021
    Narkov, Feb 1, 2021
    #20
Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.