Scripting setup help with ASCii TCP/ip socket

Discussion in 'C-Bus Automation Controllers' started by kojobomb, Oct 18, 2023.

  1. kojobomb

    kojobomb

    Joined:
    May 27, 2019
    Messages:
    76
    Likes Received:
    6
    Location:
    Possum Brush
    I reloaded your new script and it seems to be working fine, I wont know 100% untill im back onsite next weekend and can test out the responsiveness, speed and useability as I'm now testing via a VPN but for now all of the GA's with the keyword "Symetrix" fire off commands and im getting back a response ACK and the new levels.
    I am extremely greatful for how far you have brought me through this so far.
    I will also be needing to read responses from the socket when levels change in the symetrix and be able to convert those responses back into CBus GA levels but for now I am super excited with the progress.
    OK next goal will be to follow your lead and keep at it until its perfected, Event script to send UDP commands to resident script for processing.
    Thank you @ssaunders and @Pie Boy
     
    kojobomb, Oct 29, 2023
    #21
  2. kojobomb

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    242
    Likes Received:
    35
    Location:
    Melbourne
    Two-way. You neeed a resident script with a reliable Symetrix connection, fed by an event script for incoming, receive, keep-alive, restart, etc., when unexpected network things happen. Do check out my scripts at https://github.com/autoSteve/acMqtt. I both send and receive messages from a mosqitto broker via a TCP socket, UDP local for CBus from events. And check for keyword changes in CBus and do automated event script re-starts. And cope with nasty network things. So far, so good???.. still improving... I did a switch upgrade just two days ago and fine tuned things even further when unexpected things happened... Finished? Hopefully.

    (Not...)

    Good luck with this into the future @kojobomb.
     
    ssaunders, Oct 29, 2023
    #22
  3. kojobomb

    kojobomb

    Joined:
    May 27, 2019
    Messages:
    76
    Likes Received:
    6
    Location:
    Possum Brush
    My event script is working well sending controls to the Symetrix DSP only i have a question in relation to this line received see logs attached

    * string: [tcp-sock] warning...partial line received: ACK#00018=00000

    everything is working fine, just not sure if something should be done to rectify this "warning"


    Code:
     local logging = true
    
    local value = event.getvalue()
    local dest = event.dst
    
    local grps = GetCBusByKW('Symetrix', 'or') -- The only keyword used is case sensitive
    local k, v, alias, net, app, group
    local addr = ''
    local function trim(s) if s ~= nil then return s:match "^%s*(.-)%s*$" else return nil end end -- Remove leading and trailing spaces
    
    for k, v in pairs(grps) do
      alias = table.concat(v.address, '/')
      if alias == dest then
        local tags = v.keywords
        local t
        for _, t in ipairs(tags) do
          local tp = string.split(t, '=')
          tp[1] = trim(tp[1]):lower()
          if tp[2] then
            tp[2] = trim(tp[2])
            if tp[1] == 'sym' then
              addr = tp[2] -- Get device address
              break
            end
          end
        end
        break
      end
    end
    
    if addr ~= '' then
      -- create tcp socket
      local sock = require('socket').tcp()
      sock:settimeout(1)
      local connected, err = sock:connect('192.168.1.246', 48631)
    
      if connected then
        -- connect ok
        if logging then log('[tcp-sock] connection ok') end
      else
        -- error while connecting
        log('[tcp-sock] connection failed: '..err)
        sock:close()
        do return end
      end
    
      local command = 'CS '..addr..' '..(value * 257).. '\r'
    
      -- send command
      sock:send(command)
    
      local sent = socket.gettime()
      local timeout = 5
    
      while true do
        local data, err, partial = sock:receive('*l')
    
        if err or partial ~= nil then
          -- error while receiving, or an incomplete line received
          if err then log('[tcp-sock] connection closed: '..err) end
          if partial ~= nil then log('[tcp-sock] warning... partial line received: '..partial) end
          break
        else
          -- entire response received okay
          log(data)
          break
        end
    
        if socket.gettime() - sent > 5 then log('Timeout receiving data') break end
      end
      sock:close()
    else
      log('Error: sym=address tag missing')
    end
    
    
     

    Attached Files:

    kojobomb, Apr 26, 2024
    #23
  4. kojobomb

    kojobomb

    Joined:
    May 27, 2019
    Messages:
    76
    Likes Received:
    6
    Location:
    Possum Brush
    I have a resident script that is reliable and returning "group" and "levels" back via the socket when things change in the Symetrix DSP.

    response from socket = * string: #00018=65535

    I now need to work out how to convert this response back into the correct CBus GA Level via the keyword tags "sym=?,Symetrix"

    @ssaunders you have been so helpful so far, here is to hoping you can help with the last little bit to complete this, it has been a few months since i had time to put into this.



    Code:
    -- socket connected
    if connected then
     
    
      -- read until one line received or error occured
      while true do
        char, err = sock:receive(1)
    
        -- error while receiving, timeout or closed socket
        if err then
          -- remote server closed connection, reconnect
          if err == 'closed' then
            connected = false
            alert('[tcp-sock] connection closed')
            os.sleep(1)
          end
    
          break
        -- end of line, parse buffer
        elseif char == '\r' then
          data = table.concat(buffer)
          log(data)
          buffer = {}
    
          -- wait some time before next request
          os.sleep(1)
    
          break
        -- other char, add to buffer
        else
          buffer[ #buffer + 1 ] = char
        end
      end
    -- first call or previously disconnected
    else
      -- close previous connection when disconnected
      if sock then
        sock:close()
        sock = nil
      end
    
      -- create tcp socket
      sock = require('socket').tcp()
      sock:settimeout(1)
      connected, err = sock:connect('192.168.1.246', 48631)
    
      -- connect ok, reset buffer
      if connected then
        alert('[tcp-sock] connection ok')
        buffer = {}
      -- error while connecting
      else
        alert('[tcp-sock] connection failed: %s', err)
        os.sleep(5)
      end
    end
     
    kojobomb, Apr 26, 2024
    #24
  5. kojobomb

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    242
    Likes Received:
    35
    Location:
    Melbourne
    Here are some nuggets of code.

    These two functions are complete overkill for your keywords, but the approach is quite extensible. I use this code a lot. The first function gets all groups having a common keyword, e.g. "Symetrix". The second function gets key/value pair keywords, e.g. key is "sym", value is your question mark.

    Code:
    --[[
    Get applicable groups and keywords
    --]]
    local function getGroups(kw)
      local t
      local grps = {}
      for _, t in ipairs(db:getall("SELECT o.address, o.tagcache, o.name FROM objects AS o JOIN objecttags AS ot ON o.id=ot.object WHERE ot.tag='"..kw.."'")) do
        if t.tagcache == nil then goto skip end
        local alias = knxlib.decodega(t.address)
        local parts = load("return {"..alias:gsub('/',',').."}")()
        grps[alias] = { tags = {}, name = t.name, keywords = t.tagcache:gsub(', ',','), net = parts[1], app = parts[2], group = parts[3], channel = parts[4], }
        if parts[2] == 228 then -- Get the measurement app channel name from the CBus tag map
          local resp = db:getall('SELECT tag FROM cbus_tag_map WHERE tagtype="S" AND net='..parts[1]..' AND app='..parts[2]..' AND grp='..parts[3]..' AND tagid='..parts[4])
          if resp ~= nil and resp[1] ~= nil and resp[1]['tag'] ~= nil then grps[alias].name = resp[1]['tag'] else grps[alias].name = alias end
        end
        local tags = {}
        local tg
        for _, tg in ipairs(grps[alias].keywords:split(',')) do
          parts = tg:split('=')
          if parts[2] then tags[parts[1]:trim()] = parts[2]:trim() else tags[parts[1]:trim()] = -1 end
        end
        grps[alias].tags = tags
        ::skip::
      end
      return grps
    end
    
    --[[
    Get key/value pairs. Returns a keyword if found in 'allow'. (allow, synonym and special parameters are optional).
    --]]
    local function getKeyValue(alias, tags, _L, synonym, special, allow)
      if synonym == nil then synonym = {} end
      if special == nil then special = {} end
      if allow == nil then allow = {} end
      local dType = nil
      for k, t in pairs(tags) do
        k = k:trim()
        if t ~= -1 then
          if synonym[k] then k = synonym[k] end
          if special[k] ~= nil then special[k] = true end
          local v = t:trim()
          if _L[k] then
            if type(_L[k]) == 'number' then _L[k] = tonumber(v) if _L[k] == nil then error('Error: Bad numeric value for '..alias..', keyword "'..k..'="') end
            elseif type(_L[k]) == 'table' then
              _L[k] = string.split(v, '/')
              local i, tv for i, tv in ipairs(_L[k]) do _L[k][i] = tv:trim() end
            else _L[k] = v end
          end
        else
          if synonym[k] then k = synonym[k] end
          if special[k] ~= nil then special[k] = true end
          if allow[k] then
            if dType == nil then dType = k else error('Error: More than one "type" keyword used for '..alias) end
          end
        end
      end
      return dType
    end
    Below these two functions, and before your resident loop starts, do something like the following.

    Code:
    local grps = getGroups('Symetrix')
    local syms = {}
    local alias, v
    for alias, v in pairs(grps) do
      local _L = {
        sym = '',
      }
      getKeyValue(alias, v.tags, _L)
      syms[_L.sym] = alias
    end
    Untested, but the table 'syms' can then be used to look up the C-Bus alias (net/app/group).

    That could be used to do a "grp.write(syms[inboundsymaddress], valuetowrite)".

    Also pulled apart by that code is net/app/group, which could be used to do SetCBusLevel(net, app, group). In that case you'd assign syms[_L.sym] = { net=v.net, app=v.app, group=v.group, }, and do "SetCBusLevel(syms[inbound].net, syms[inbound].app, syms[inbound].group, valuetowrite)"

    Let me know how you go...
     
    Last edited: Apr 27, 2024
    ssaunders, Apr 27, 2024
    #25
    Timbo likes this.
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.