Resident Script - UserParam

Discussion in 'C-Bus Automation Controllers' started by Jagomeister, Feb 29, 2024.

  1. Jagomeister

    Jagomeister

    Joined:
    Dec 26, 2018
    Messages:
    4
    Likes Received:
    0
    Been battling all day with an issue with reading/writing UserParams from a Resident Script

    Tried writing a library and move all commands out to there tested worked fine
    Tried calling them from the resident script and they silent fail.

    its based on the MQTT script floating around but was extending to tie into HA closer

    The issue is in the hvac cmd
    The AC are connected by modbus and mapped to UserParms


    HW: 5500SHAC (i.MX28)
    SW: 1.14.0


    Code:
    mqtt_broker = '192.168.2.15'
    mqtt_username = 'cbus'
    mqtt_password = 'xxxxxxx'
    mqtt_clientid = 'mqtt2cbus'
    
    mqtt_write_topic = 'cbus/write/#';
    
    
    -- load mqtt module
    mqtt = require("mosquitto")
    
    -- create new mqtt client
    client = mqtt.new(mqtt_clientid)
    
    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)
     -- Topic: cbus/write/cat/net/app/grp/cmd
      log(topic, payload)
      parts = string.split(topic, "/")
    
      if not parts[5] then
        -- Invalid message format
        log('MQTT error', 'Invalid message format')
    
      elseif string.lower(parts[3]) == "light" then
         -- Topic: cbus/write/light/grp/switch|ramp
        net = 0;
        app = 56
        grp = tonumber(parts[4])
        cmd = string.lower(parts[5])
      
        log('LIGHT Command: ' .. net .."/".. app .."/".. grp .. "/" .. cmd .. " :: " .. payload)
      
        if cmd == 'switch' then     
              if string.upper(payload) == "ON" then
                  SetCBusLevel(net, app, grp, 255, 0)     
            elseif string.upper(payload) == "OFF" then
            SetCBusLevel(net, app, grp, 0, 0)
          end
        elseif cmd == "ramp" then
          if string.upper(payload) == "ON" then
                  SetCBusLevel(net, app, grp, 255, 0)
          elseif string.upper(payload) == "OFF" then
            SetCBusLevel(net, app, grp, 0, 0)
          else
            ramp = string.split(payload, ",")
            num = math.floor(ramp[1] + 0.5)
            if num and num < 256 then
              if ramp[2] ~= nil and tonumber(ramp[2]) > 1 then
                  SetCBusLevel(net, app, grp, num, ramp[2])
              else
                  SetCBusLevel(net, app, grp, num, 0)
              end
            end
          end
        end
     
      elseif string.lower(parts[3]) == "fan" then
        -- Topic: cbus/write/fan/grp/switch|speed
        net = 0;
        app = 56
        grp = tonumber(parts[4])
        cmd = string.lower(parts[5])
    
    log('FAN Command: ' .. net .."/".. app .."/".. grp .. "/" .. cmd .. " :: " .. payload) 
      
        if cmd == 'switch' then     
              if string.upper(payload) == "ON" then
                  SetCBusLevel(net, app, grp, 255, 0)     
            elseif string.upper(payload) == "OFF" then
            SetCBusLevel(net, app, grp, 0, 0)
          end
        elseif cmd == "speed" then 
          speed = tonumber(payload)
          -- See if speed or a numeric value
          if speed ~= nil then
            if speed == 0 then
              SetCBusLevel(net, app, grp, 0, 0)
            elseif speed == 1 then
              SetCBusLevel(net, app, grp, 84, 0)       
            elseif speed == 2 then
              SetCBusLevel(net, app, grp, 170, 0)
            elseif speed == 3 then
              SetCBusLevel(net, app, grp, 255, 0)         
                    end
          end
        end
      
      elseif string.lower(parts[3]) == "hvac" then
        -- Topic: cbus/write/hvac/grp/switch|fan|mode|vane|temp
        net = 0;
        app = 250
        grp = tonumber(parts[4])
        cmd = string.lower(parts[5])   
        log('HVAC Command: ' .. net .."/".. app .."/".. grp .. "/" .. cmd .. " :: " .. payload)
      
      
        if cmd == 'switch' then
         --aircon_set_power(grp, payload)
          --SetUserParam('Local, 'Test_AC_Switch', false)
          --SetUserParam('Local Network', 'Test_AC_Switch', false)
          SetUserParam(0, 'Test_AC_Switch', false)
        elseif cmd == "mode" then
        elseif cmd == "fan" then
        elseif cmd == "vane" then
        elseif cmd == 'temp' then 
        end
        
      elseif string.lower(parts[3]) == "blind" then
        -- Topic: cbus/write/blind/grp/action|position
        net = 0;
        app = 56
        grp = tonumber(parts[4])
        cmd = string.lower(parts[5])
    
        if cmd == 'position' then
          pos = tonumber(payload)
          -- See if position is numeric value
          if pos ~= nil then
            -- Map 0-100 to 0-255
            cbus_pos = map(pos, 0, 100, 0, 255)
            SetCBusLevel(net, app, grp, cbus_pos, 0)
          end
        elseif cmd == "action" then 
          action = string.lower(payload)
          if action == "open" then
            SetCBusLevel(net, app, grp, 255, 0) -- 255%
          elseif action == "close" then
            SetCBusLevel(net, app, grp, 0, 0) -- 0%       
          elseif action == "stop" then
            SetCBusLevel(net, app, grp, 5, 0) -- 2%
          elseif speed == "toggle" then
            SetCBusLevel(net, app, grp, 249, 0) -- 98%   
          elseif speed == "toggle_open" then
            SetCBusLevel(net, app, grp, 252, 0) -- 99%
          elseif speed == "toggle_close" then
            SetCBusLevel(net, app, grp, 2, 0) -- 1%             
                end
        end
      
      elseif string.lower(parts[3]) == "raw" then
         -- Topic: cbus/write/raw/net/app/grp/switch|ramp
        net = 0;
        app = tonumber(parts[5])
        grp = tonumber(parts[6])
        cmd = string.lower(parts[7])
      
        if cmd == 'switch' then     
              if string.upper(payload) == "ON" then
                  SetCBusLevel(net, app, grp, 255, 0)     
            elseif string.upper(payload) == "OFF" then
            SetCBusLevel(net, app, grp, 0, 0)
          end
        elseif cmd == "ramp" then
          if string.upper(payload) == "ON" then
                  SetCBusLevel(net, app, grp, 255, 0)
          elseif string.upper(payload) == "OFF" then
            SetCBusLevel(net, app, grp, 0, 0)
          else
            ramp = string.split(payload, ",")
            num = math.floor(ramp[1] + 0.5)
            if num and num < 256 then
              if ramp[2] ~= nil and tonumber(ramp[2]) > 1 then
                  SetCBusLevel(net, app, grp, num, ramp[2])
              else
                  SetCBusLevel(net, app, grp, num, 0)
              end
            end
          end
        end 
      end
    end
    
    client:login_set(mqtt_username, mqtt_password)
    client:connect(mqtt_broker)
    client:loop_forever()
     
    Last edited: Feb 29, 2024
    Jagomeister, Feb 29, 2024
    #1
  2. Jagomeister

    Jagomeister

    Joined:
    Dec 26, 2018
    Messages:
    4
    Likes Received:
    0
    It was the mosquitto lib causing the issue
    loop_forever is a blocking function

    tried loop_start had no luck either

    ended up setting up a loop and calling loop(0)
    then adding incomming UserParm data to a queue thats checked each cycle

    also renamed grp variables
     
    Jagomeister, Feb 29, 2024
    #2
  3. Jagomeister

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    240
    Likes Received:
    35
    Location:
    Melbourne
    Ages ago I managed to get loop_start() to work, but ended up with weird stability issues with the script crashing. So you're now using the same approach I use with https://github.com/autoSteve/acMqtt. Do an infinite loop that calls MQTT client:loop() as well as listening on a socket to enable bi-directional CBus/Mosquitto.
     
    ssaunders, Mar 2, 2024
    #3
  4. Jagomeister

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    240
    Likes Received:
    35
    Location:
    Melbourne
    A comment about your code, @Jagomeister. The variable 'grp' is a special global variable on an AC, so you are clobbering this by not declaring your grp variables as local. This may be okay, but certainly won't be if you want to use some of the 'behind the scenes' Logic Machine stuff, like grp.gettags(alias). I always use 'group' instead of 'grp'.

    The AC is based on Logic Machine, so you might find this link handy. https://kb.logicmachine.net/libraries/lua/

    An example utilising 'grp' in two ways:

    Code:
    if event.getvalue() == 255 then
      for _, tag in ipairs(grp.gettags(event.dst)) do -- Get the keywords for the object that raised the event
        parts = tag:split('=')
        if parts[2] ~= nil then
          if parts[1] == 'up' then up = parts[2] break end
        end
      end
      grp.write(event.dst, 0) -- Set the level of the group that raised the event to zero
      PulseCBusLevel('Local Network', 'Lighting', up, 255, 0, 3, 0)
    end
     
    Last edited: Mar 3, 2024
    ssaunders, Mar 3, 2024
    #4
  5. Jagomeister

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    240
    Likes Received:
    35
    Location:
    Melbourne
    And another handy one-liner.

    This will convert an 'event.dst' alias like '0/56/123' in an event script into a table of numbers (three for lighting/user param, and four for measurement app):
    Code:
    local addr = load("return {"..event.dst:gsub('/',',').."}")()
    Then you could use statements like objectName = GetCBusGroupTag(addr[1], addr[2], addr[3]), and avoid using variables like net, app, group at all.
     
    ssaunders, Mar 3, 2024
    #5
  6. Jagomeister

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    240
    Likes Received:
    35
    Location:
    Melbourne
    Going back to post zero, what's your AC set up, BTW, @Jagomeister? Sounds like Airtopia. If so, my acMqtt script supports Airtopia to a HomeAssistant climate device...
     
    ssaunders, Mar 3, 2024
    #6
  7. Jagomeister

    Jagomeister

    Joined:
    Dec 26, 2018
    Messages:
    4
    Likes Received:
    0
    did a full rewrite. Running pretty good now

    setting timeoout 250 blocks long enough not to hammer cpu but is still pretty responsive
    so far its running stable.

    I have Mitsubishi Electric splits
    each has a Inteses ME-AC-MBS-1 connected that bridges it to the modbus.
    Was that or the wifi adapters and I decided that way. This was close on 5 years ago only just had time to actually config them.

    for the last 4.5 years I had been running a custom version of cgate web that I had rewritten to support more then I network. As I have cbus in more then 1 building, But it was not great and had issue if server ever went down.

    then there is a nodered that talks to mqtt (cbus and native devices) and s7 (siemen logos) this them gives the interface to control all lighting thats not my house.




    Code:
    while true
    do
      -- Run mosquitto and collect messages
      client:loop(250)
     
      -- Process any message that are in the queue
      while MQTT2CBUS.queue.last >= MQTT2CBUS.queue.first
      do
        -- items in the queue
        if MQTT2CBUS.logging then
          log('MQTT2CBUS Queue Size:', ((MQTT2CBUS.queue.last + 1) - MQTT2CBUS.queue.first), MQTT2CBUS.queue.first, MQTT2CBUS.queue.last) 
        end 
          
        local data = MQTT2CBUS.queue.data[MQTT2CBUS.queue.first]
        MQTT2CBUS.queue.data[MQTT2CBUS.queue.first] = nil
        MQTT2CBUS.queue.first = MQTT2CBUS.queue.first + 1
    
        if MQTT2CBUS.logging then
          log('MQTT2CBUS Queue Data:', data) 
        end     
        
        -- Process the data
        if data.class == 'light' then
          MQTT2CBUS.light(data.addr.grp, data.command, data.payload)
        elseif data.class == 'fan' then
          MQTT2CBUS.fan(data.addr.grp, data.command, data.payload)
        elseif data.class == 'blind' then
          MQTT2CBUS.blind(data.addr.grp, data.command, data.payload)
        elseif data.class == 'hvac' then
          MQTT2CBUS.hvac(data.addr.grp, data.command, data.payload)   
        elseif data.class == 'switch' then
          MQTT2CBUS.switch(data.addr.grp, data.command, data.payload)
        elseif data.class == 'raw' then
          MQTT2CBUS.raw(data.addr, data.command, data.payload)
        else
          error('MQTT2CBUS: Unknown class', data.class)
        end 
      end  
     
    Jagomeister, Mar 3, 2024
    #7
  8. Jagomeister

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    240
    Likes Received:
    35
    Location:
    Melbourne
    Nicely done.

    Here's a trick I use to get max performance and cut down the number of lines. Untested, but should work instead of the if / elseif / elseif / elseif... Functions can be assigned to variables in LUA, and table lookup is blazingly fast. The data.class ~= 'raw' and/or bit changes from using data.addr.grp to data.addr for raw calls.

    Code:
    local opts = { light = MQTT2CBUS.light, fan = MQTT2CBUS.fan, blind = MQTT2CBUS.blind, hvac = MQTT2CBUS.hvac, switch = MQTT2CBUS.switch, raw = MQTT2CBUS.raw, }
    
        -- Process the data
        if opts[data.class] ~= nil then opts[data.class](data.class ~= 'raw' and data.addr.grp or data.addr, data.command, data.payload) else error('MQTT2CBUS: Unknown class', data.class) end
    And depending on the structure of MQTT2CBUS, this might even be able to be cut further to just a one-liner:

    Code:
        -- Process the data
        if MQTT2CBUS[data.class] ~= nil then MQTT2CBUS[data.class](data.class ~= 'raw' and data.addr.grp or data.addr, data.command, data.payload) else error('MQTT2CBUS: Unknown class', data.class) end
    
     
    Last edited: Mar 5, 2024
    ssaunders, Mar 5, 2024
    #8
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.