Blind control with P-Box DD-7006B

Discussion in 'C-Bus Automation Controllers' started by CBriggs, Mar 17, 2024.

  1. CBriggs

    CBriggs

    Joined:
    Aug 2, 2015
    Messages:
    13
    Likes Received:
    0
    Location:
    South Australia
    Hi All,
    I'm a noob to doing any scripting that communicates outside of the C-Bus environment, so I'm looking for some basic how to guides or examples to get me started.
    The plan is to create some event based scripts to control my Dooya roller blinds that are connected to a Connector P-Box DD-7706B bridge. I have the bridge working and can ping it from the SHAC, but I don't know how to construct and send the messages in lua.
    I have the attached file that gives some instructions regarding the messages that need to be sent / received, but honestly I don't know where to start with these.
    I was thinking of setting up individual event scripts for each blind (I only have 7), and just sending the open / close / stop commands using eDLT switches for the inputs.
    Has anyone done this already, or can offer some suggestions as to where I should start my learning journey?
    Cheers
     

    Attached Files:

    CBriggs, Mar 17, 2024
    #1
  2. CBriggs

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    242
    Likes Received:
    35
    Location:
    Melbourne
    It looks quite straightforward, but non-trivial. I'd approach it in the following way.

    A resident script that listens on two sockets, and sends on another, achieving bidirectional comms.

    To send a message, use something like this, with the msgID being unique and incrementing:
    Code:
    message = {
    msgType='GetDeviceList',
    msgID=os.date('%Y%m%d')..math.floor(socket.gettime())
    }
    require('socket').udp():sendto(json.encode(message), '238.0.0.18', 32100)
    Set up to listen for multicast blind messages in the resident:
    Code:
    bServer = require('socket').udp()
    bServer:settimeout(0.1) -- Tenth of a second timeout, could be less
    result, err = bServer:setsockname('238.0.0.18', 32101)
    Also set up a local socket to listen for event script messages:
    Code:
    eServer = require('socket').udp()
    eServer:settimeout(0.1) -- Tenth of a second timeout, could be less
    result, err = eServer:setsockname('0.0.0.0', 5432)
    Create a simple event script that fires on the keyword BLIND or something. In it, send UDP messages to the resident script when CBus values change. The payload will be a string like 0/56/123/255, being network, application, group, level. Tag each of the blind control groups with the keyword 'BLIND'.

    Code:
    require('socket').udp():sendto(event.dst.."/"..event.getvalue(), '127.0.0.1', 5432)
    Set up an infinite loop in the resident with something like this:

    Code:
    while true do
      bCmd = bServer:receive()
      eCmd = eServer:receive()
      if bCmd then
        -- Process blind message inbound
        msg = json.decode(bCmd)
        -- Table will then contain what happened
      end
      if eCmd then
        --Process event script message inbound
        parts = eCmd:split('/')
        net = tonumber(parts[1]) app = tonumber(parts[2]) group = tonumber(parts[3]) level = tonumber(parts[4])
        -- Build a message table
        message = { content, including msgType, mac, deviceType, AccessToken, msgID and data }
        require('socket').udp():sendto(json.encode(message), '238.0.0.18', 32100) -- send it to the blind server
        -- You could store that a message has been sent (by msgID, i.e. awaiting[msgID] = true) to look for responses to each message
      end
    end
    If you are setting the levels of the BLIND groups based on inbound blind messages, then you'll need some kind of 'ignore' flag (like ignore[net..'/'..app..'/'..group] = true) to enable you to ignore a socket message coming from the event script for a given alias when you know your resident made the change. When processing eCmd clear the flag and do nothing if it is set.
     
    Last edited: Mar 20, 2024
    ssaunders, Mar 20, 2024
    #2
    CBriggs likes this.
  3. CBriggs

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    242
    Likes Received:
    35
    Location:
    Melbourne
    As for implementing open/close/stop only, I'd take an approach similar to a CBus shutter relay. Levels 0=open, 255=close, 5=stop, which would be simple to set up on the eDLTs and to interpret for inbound event script message levels in the resident.

    I also had a think about how you could identify the blinds as being associated with a given group address. Sending a GetDeviceList will give you all the blind MACs. So rig up a send 'GetDeviceList', then log the contents of an inbound GetDeviceListAck. These could be recorded in a table in script, e.g.

    Code:
    mac = {
      ['0/56/123'] = '500291b691fd005f',
      ['0/56/124'] = '500291b691fd0060',
    }
    Working out which mac corresponds to which blind will be fun. But you only have seven, so trial and error. Get one working in script then try other addresses one by one to see which blind operates.

    If you wanted to get fancy, then instead of a table have keywords for each group address like BLIND, mac=500291b691fd0060, and then lookup all groups having the keywords 'BLIND' at the start of the resident and get the MACs that way. Something like this would construct an identical table...

    Code:
    mac = {}
    grps = grp.tag('BLIND')
    for _, g in ipairs(grps) do
      tags = g.tagcache:split(', ')
      for _, t in ipairs(tags) do pos = t:find('mac=', 1, true) if pos and pos >= 1 then mac[g.address] = t:split('=')[2] break end end
    end
    And thinking a bit more, probably use
    msgID=os.date('%Y%m%d')..math.floor(socket.gettime()*10), with the *10 to get a new unique message ID every tenth of a second. The socket timeout of 0.1 means you'll never duplicate msgIDs, assuming one of the listening sockets has a timeout of zero (in my first reply both have a timeout of 0.1, so iterations would be five per second max, but with only one timeout 10 iterations per second max).
     
    Last edited: Mar 20, 2024
    ssaunders, Mar 20, 2024
    #3
    CBriggs likes this.
  4. CBriggs

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    242
    Likes Received:
    35
    Location:
    Melbourne
    I messed up by using setsockname for receiving the blind server messages, @CBriggs. Oops. Use setpeername() instead... I also believe that a non-zero timeout is required, so use something small for one socket, and smaller for the other.

    Code:
    bServer = require('socket').udp()
    bServer:settimeout(0.01)
    bServer:setpeername('238.0.0.18', 32101)
     
    ssaunders, Mar 21, 2024
    #4
    CBriggs likes this.
  5. CBriggs

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    242
    Likes Received:
    35
    Location:
    Melbourne
    Nope. I was right-ish the first time regarding setsockname().

    Playing with this some more tonight, I came up with the following that may help you, @CBriggs. On event, this sends a level in payload data (which is just for testing) to a test resident script, which in turn fires a message at a loopback blind interface, which in turn fires back an acknowlegement message to the test resident.

    How you do all the token stuff to authenticate is beyond my simple simulation, but you get the idea, yeah?

    A test resident:

    Code:
    blindInterface = '127.0.0.1' --'238.0.0.18'
    listenPort = 5439
    
    socket = require('socket')
    eServer = socket.udp()
    eServer:settimeout(0.05)
    result, err = eServer:setsockname('0.0.0.0', listenPort)
    
    bServer = socket.udp()
    bServer:settimeout(0.05)
    bServer:setsockname('*', 32101)
    
    awaiting = {}
    
    while true do
      bCmd = bServer:receive()
      if bCmd then
        -- Process blind message inbound
        msg = json.decode(bCmd)
        -- Table will then contain what happened
        if awaiting[msg.msgID] and msg.msgType:match"Ack$" then
          awaiting[msg.msgID] = nil
          log('Received ack from message ID '..msg.msgID)
          -- Process acknowledgement
          log('bCmd', msg)
        end
      end
      eCmd = eServer:receive()
      if eCmd then
        log('eCmd: '..eCmd)
        --Process event script message inbound
        parts = eCmd:split('/')
        net = tonumber(parts[1]) app = tonumber(parts[2]) group = tonumber(parts[3]) level = tonumber(parts[4])
        id = os.date('%Y%m%d')..math.floor(socket.gettime()*10)
        -- Build a message table
        message = {
          msgType='GetDeviceList',
          mac='',
          deviceType='',
          AccessToken='',
          msgID=id,
          data= { level=level }
        }
        require('socket').udp():sendto(json.encode(message), blindInterface, 32100) -- send it to the blind interface
        awaiting[id] = true
      end
    end
    A test loopback resident:

    Code:
    local socket = require("socket")
    
    local addr = '127.0.0.1'
    local receivePort = 32100
    local sendPort = 32101
    
    udp = socket.udp()
    udp:setsockname(addr, receivePort)
    udp:settimeout(0.1)
    
    send = socket.udp()
    -- send:setpeername(addr, sendPort)
    
    while true do
      data, ip, port = udp:receivefrom()
      if data then
        d = json.decode(data)
        d.msgType = d.msgType..'Ack'
        data = json.encode(d)
        log(data, ip, sendPort)
        send:sendto(data, ip, sendPort)
      end
    end
    A test event script on 'BLIND' keyword:

    Code:
    log(event.getvalue())
    require('socket').udp():sendto(event.dst.."/"..event.getvalue(), '127.0.0.1', 5439)
     
    ssaunders, Mar 21, 2024
    #5
    CBriggs likes this.
  6. CBriggs

    CBriggs

    Joined:
    Aug 2, 2015
    Messages:
    13
    Likes Received:
    0
    Location:
    South Australia
    Thank you so much @ssaunders, this information has been perfect to get me underway. I did manage to work out a way to send a message to the blinds, but your way of building the message is so much neater!

    I used the packetsender program to send the GetDeviceList message and retrieved the mac addresses.

    I'm not sure that I really need the blinds to stop part way, so currently have just set it up to toggle open and closed. I didn't have much luck trying to get the correct outputs from both eDLT and DLT switches (I only want to assign one switch on the DLT) that I use to make the blinds operate, so I just changed them to toggle rather than shutter relay.
    I'm not sure yet if I will go through to the point of receiving the report data back, as I don't really need the feedback if I only run the to the open/closed positions. Only advantage would be in updating the CBus status if they were operated from a different remote.
    I set the switch toggle to start an event script for the set of blinds in the room (the seven blinds are in sets of two, two, and three), which then sends the messages for each blind to run open or closed depending on event.getvalue(). So far I have only done a set of two, and the duplicate msgIDs hasn't caused a problem. I confirmed the messages using packetsender and the msgIDs were the same, but the blinds still responded.

    Once again, a huge thankyou for the info you provided, I used it to pick out the easy bits to do the bare minimum I needed, and I will certainly refer back to continue adding more functionality as I progress.
     
    CBriggs, Mar 21, 2024
    #6
  7. CBriggs

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    242
    Likes Received:
    35
    Location:
    Melbourne
    @CBriggs, receiving multicast might add an interesting twist. I do so wish I had one of these to play with, so I'm running blind, pardon the pun.

    Refer to ip-add-membership at https://tst2005.github.io/lua-socket/udp.html when the time comes, as it seems important for multicast.

    So glad that you're on your way to embracing socket comms! You won't look back.
     
    ssaunders, Mar 21, 2024
    #7
  8. CBriggs

    CBriggs

    Joined:
    Aug 2, 2015
    Messages:
    13
    Likes Received:
    0
    Location:
    South Australia
    One of the things that started to get tricky for me was the number of messages that I was receiving back. The system generates a heartbeat message at regular intervals, an acknowledge message when I send a message, and then a report message when the blind stops.
    With the added info about me running multiple blinds at once (sorry I didn't mention that earlier) the message handling would probably need to keep updating a table with current operation and position.
    The acknowledge message could be useful to confirm that the blind is currently moving or stopped, and the report message is useful to know the position it is in when it stops.
    This data could then be used to decide what message needs to be sent for an alternative position.
    With this concept I could use a slider switch to set the desired position 0-100%.

    This is going to be a fun experiment :)
     
    CBriggs, Mar 21, 2024
    #8
  9. CBriggs

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    242
    Likes Received:
    35
    Location:
    Melbourne
    There you go. Go code. The weekend. (Sings a bad rendition of Tomorrow) ...it's only a day away....
     
    ssaunders, Mar 21, 2024
    #9
  10. CBriggs

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    242
    Likes Received:
    35
    Location:
    Melbourne
    Another thought on groups of blinds, @CBriggs. You could utilise keywords like BLIND, mac=500291b691fd0060,500291b691fd005f for each group address, using mac:split(',') to store a table in the mac table value instead of just a string.

    Then when sending a command, send messages to all macs for the GA using 'for _, m in ipairs(mac[ga]) do'. Monitor for status returns for just the first mac because all the blinds should be doing the same thing.
     
    ssaunders, Mar 23, 2024
    #10
    CBriggs 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.