LUA code to convert Hex to String based on Table

Discussion in 'C-Bus Automation Controllers' started by Ks04, Sep 2, 2022.

  1. Ks04

    Ks04

    Joined:
    Dec 28, 2019
    Messages:
    107
    Likes Received:
    9
    Hi There,

    I've made some good progress creating a module to integrate my Inception Alarm with my SHAC.

    The API for the inception returns states as a set of bit flags in the form of a JSON-serialized base-10 integer. The presence or absence of certain flags indicates whether certain state conditions are active for the given item. (Table at bottom of post).

    When the Alarm is ready to arm it returns a value of "1800"
    1000 = "ArmReady"
    800 = "Disarmed"

    So the alarm is disarmed and ready to arm.

    My question is how do I do a mapping using LUA to take the hex value and map to the bit-wise table to return a string... hope that makes sense. Any pointers greatly appreciated as this one has me stumped!

    Integer Value Hex Value String Value Description
    1 0x00000001 Armed Area is armed.
    2 0x00000002 Alarm Area is in alarm.
    4 0x00000004 EntryDelay Area is in entry delay.
    8 0x00000008 ExitDelay Area is in exit delay.
    16 0x00000010 ArmWarning Area is in arm warning.
    32 0x00000020 DeferDisarmed Area has been defer disarmed (temporarily disarmed).
    64 0x00000040 DetectingActiveInputs One or more inputs in this area are currently unsealed.
    128 0x00000080 WalkTestActive A walk test is currently active for this area.
    256 0x00000100 AwayArm Area is armed in Full mode.
    512 0x00000200 StayArm Area is armed in Perimeter mode).
    1024 0x00000400 SleepArm Area is armed in Night mode.
    2048 0x00000800 Disarmed Area is disarmed.
    4096 0x00001000 ArmReady Area is ready to arm (i.e. no active inputs).
     
    Ks04, Sep 2, 2022
    #1
  2. Ks04

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    What is the api format?
    How is data received to shac, tcp socket or http?
    Is it a request/response Do you have an example json message,

    mapping table would be possible but might not be required, what are your plans with the received data? to send the strings to user parameter, if so how many points are required.

    if your json response was inside variable res then you could qualify each response like

    res = json.pdecode(json message)

    if res == ‘1000’ then
    SetUserParam(0, ‘armed state’, ‘Arm Ready’)
    End

    If res == ‘800’ then
    SetUserParam(0, ‘armed state’, ‘Disarmed’)
    End

    Lua table would need a key used to match the data required , key can be numeric, probably what you want.
    Need to loop through the table to see if the data matches a known, but be pending on what you want to match It maybe easier to parse the json message into an individual string and qualify them as above.
     
    Last edited: Sep 2, 2022
    Pie Boy, Sep 2, 2022
    #2
  3. Ks04

    Ks04

    Joined:
    Dec 28, 2019
    Messages:
    107
    Likes Received:
    9
    Thanks Pie Boy,

    Unfortunately I think it's a little more complex (but happy to be wrong). Here's an example of the JSON response which is coming from a HTTP API:
    Code:
    {
        "ID": "AreaStateRequest",
        "Result": {
            "updateTime": "2910517842",
            "stateData": [
                {
                    "ID": "5c5e4622-153e-46cc-8b03-aaa63cec2ab9",
                    "ReportingID": 1,
                    "stateValue": 7,
                    "PublicState": 6144,
                    "Info1": null,
                    "Info2": "3"
                },
                {
                    "ID": "1c857d52-71ae-4240-886e-302c6430e801",
                    "ReportingID": 2,
                    "stateValue": 8,
                    "PublicState": 6144,
                    "Info1": null,
                    "Info2": "0"
                }
            ]
        }
    }
    PublicState is the response in DEC. I've worked out how to convert this to HEX, and can push that to a user parameter. However in this example 6144 = 1800 Hex so I would like to push the string "Arm Ready - Disarmed" to the user parameter so it's readable.

    There must be an easier way then coming up with all the different permutations
     
    Ks04, Sep 3, 2022
    #3
  4. Ks04

    SgrAystar

    Joined:
    Oct 4, 2018
    Messages:
    56
    Likes Received:
    5
    Location:
    Melbourne, Australia
    Does the SHAC support LUA Bitwise operators?
     
    SgrAystar, Sep 3, 2022
    #4
  5. Ks04

    Ks04

    Joined:
    Dec 28, 2019
    Messages:
    107
    Likes Received:
    9
    It does appear so - the SHAC UI has a reference link to this: http://bitop.luajit.org/api.html
     
    Ks04, Sep 3, 2022
    #5
  6. Ks04

    SgrAystar

    Joined:
    Oct 4, 2018
    Messages:
    56
    Likes Received:
    5
    Location:
    Melbourne, Australia
    Your result is a 13 bit map, so could test each bit in turn?

    if (PublicState & 4096) then
    String = "Area is ready to arm"
    end
    if (PublicState & 2048) then
    String = String+", Area is disarmed"
    end
    if (PublicState & 1024) then
    etc....
     
    SgrAystar, Sep 3, 2022
    #6
  7. Ks04

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    oh yes i understand now, assuming you have worked out the bit flags and have split them out and returned the enum etc then the vals table can be looped through as below and string returned for the associated emum as indexed in table
    the variable res in the below would be the enum derived from the returned bit flag/s

    Code:
    
    vals = {
                [1] = 'Armed Area is armed',
                [2] = 'Alarm Area is in alarm',
                [4] = 'EntryDelay Area is in entry delay',
                [8] = 'ExitDelay Area is in exit delay',
                [16] = 'ArmWarning Area is in arm warning',
                [32] = 'DeferDisarmed Area has been defer disarmed (temporarily disarmed)',
                [128] = 'WalkTestActive A walk test is currently active for this area',
                [256] = ' AwayArm Area is armed in Full mode',
                [512] = 'StayArm Area is armed in Perimeter mode',
                [1024] = 'SleepArm Area is armed in Night mode',
                [2048] = ' Disarmed Area is disarmed',
                [4096] = 'ArmReady Area is ready to arm',
               }
    
    res = 4
    
    for i = 1, #vals do
     
      if res == i then
        log(vals[i])
        break
      end
     
      end
    
    Or if you dont need to loop through the table then,

    Code:
    if vals[res] then
    log(vals[res])
    end
    
     
    Last edited: Sep 5, 2022
    Pie Boy, Sep 5, 2022
    #7
  8. Ks04

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    231
    Likes Received:
    31
    Location:
    Melbourne
    How about something like this?

    Sets a user param for each bitwise test that has a changed value. 'val' and 'offVal' can be anything that'll fit in a user param. Boolean, string, number... The table and code could be simplified if all that is required is a true/false, but you asked for a string option. :)

    Using storage.set/get instead of user params is an alternative form of storage if the flags are just being used in script and not for display.

    Code:
    val = 0x1800
    
    tests = {
      [0x0001] = {userP = 'Armed Area', val = 'armed', offVal = 'disarmed'},
      [0x0002] = {userP = 'Alarm Area', val = true, offVal = false},
      [0x0004] = {userP = 'EntryDelay Area', val = true, offVal = false},
      [0x0008] = {userP = 'ExitDelay Area', val = true, offVal = false},
      [0x0010] = {userP = 'ArmWarning Area', val = true, offVal = false},
      [0x0020] = {userP = 'DeferDisarmed Area', val = true, offVal = false},
      [0x0040] = {userP = 'DetectingActiveInputs', val = true, offVal = false},
      [0x0080] = {userP = 'WalkTestActive', val = true, offVal = false},
      [0x0100] = {userP = 'AwayArm Area', val = true, offVal = false},
      [0x0200] = {userP = 'StayArm Area', val = true, offVal = false},
      [0x0400] = {userP = 'SleepArm Area', val = true, offVal = false},
      [0x0800] = {userP = 'Disarmed Area', val = true, offVal = false},
      [0x1000] = {userP = 'ArmReady Area', val = true, offVal = false},
    }
    
    for testVal, t in pairs(tests) do
      local state
      if bit.band(val, testVal) == testVal then state = t.val else state = t.offVal end
      if GetUserParam(t.userP) ~= state then
        log('Setting '..t.userP..' to '..tostring(state))
        SetUserParam(t.userP, state)
      end
    end
     
    ssaunders, Sep 5, 2022
    #8
  9. Ks04

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    231
    Likes Received:
    31
    Location:
    Melbourne
    And for bonus points, if you just want the states in variables, then something like this...

    Code:
    val = 0x1800
    
    states = {
      stayArmArea = false,
      sleepArmArea = false,
      disarmedArea = false,
      armReadyArea = false,
    }
    
    tests = {
      [0x0200] = {userP = function(s) states.stayArmArea = s end, val = true, offVal = false},
      [0x0400] = {userP = function(s) states.sleepArmArea = s end, val = true, offVal = false},
      [0x0800] = {userP = function(s) states.disarmedArea = s end, val = true, offVal = false},
      [0x1000] = {userP = function(s) states.armReadyArea = s end, val = true, offVal = false},
    }
    
    for testVal, t in pairs(tests) do
      local state
      if bit.band(val, testVal) == testVal then state = t.val else state = t.offVal end
      t.userP(state)
    end
    
    log(states)
     
    ssaunders, Sep 5, 2022
    #9
  10. Ks04

    SgrAystar

    Joined:
    Oct 4, 2018
    Messages:
    56
    Likes Received:
    5
    Location:
    Melbourne, Australia
    The example of 1800 above shows that more than one bit can be set in PublicState.
    If the real world combinations are limited you could hard code a table of expected responses, otherwise couldn't you loop through each bit in turn and construct a string as you go?
     
    SgrAystar, Sep 5, 2022
    #10
  11. Ks04

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    231
    Likes Received:
    31
    Location:
    Melbourne
    To continue post 9...

    Code:
    if states.disarmedArea and states.armReadyArea then state = 'Alarm is disarmed and ready to arm' end
    The values of any other bits are unmportant, so both 0x1800 and 0x1A00 would return 'Alarm is disarmed and ready to arm'...
     
    ssaunders, Sep 5, 2022
    #11
  12. Ks04

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    231
    Likes Received:
    31
    Location:
    Melbourne
    Then again, you might want something brutally simple...

    Code:
    val = 0x1A00
    if bit.band(val, 0x1000) > 0 and bit.band(val, 0x0800) > 0 then log('Arm Ready - Disarmed') end
    That would return 'Arm Ready - Disarmed'. And if val were 0x0800 or any other value without those two bits set it would not.

    (Edit: Even simpler)
     
    ssaunders, Sep 5, 2022
    #12
  13. Ks04

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    This is my updated just poke the area state into it and it will search through the codes and return the values,
    It takes into account multiple bits in public state,
    It will log all messages associated with all bits from public state,
    hopefully it helps

    Code:
    Area_Code = 6144
    
    vals = {[1] = 'Armed Area is armed',
    [2] = 'Alarm Area is in alarm',
    [4] = 'EntryDelay Area is in entry delay',
    [8] = 'ExitDelay Area is in exit delay',
    [16] = 'ArmWarning Area is in arm warning',
    [32] = 'DeferDisarmed Area has been defer disarmed (temporarily disarmed)',
    [128] = 'WalkTestActive A walk test is currently active for this area',
    [256] = ' AwayArm Area is armed in Full mode',
    [512] = 'StayArm Area is armed in Perimeter mode',
    [1024] = 'SleepArm Area is armed in Night mode',
    [2048] = ' Disarmed Area is disarmed',
    [4096] = 'ArmReady Area is ready to arm'
    }
    
    for k, v in pairs(vals) do
    
    if (bit.band(k , Area_Code)== k) then
        log(v)
      end
    
    end
     
    Last edited: Sep 5, 2022
    Pie Boy, Sep 5, 2022
    #13
  14. Ks04

    Ks04

    Joined:
    Dec 28, 2019
    Messages:
    107
    Likes Received:
    9
    Thanks all - this has been really really helpful. Pie Boy i've ended up working with your solution:

    Code:
    Area_Code = 6208
    string = ""
    
    vals = {
    [1] = 'Armed',
    [2] = 'Alarm',
    [4] = 'Entry Delay',
    [8] = 'Exit Delay',
    [16] = 'Arm Warning',
    [32] = 'Defer Disarmed',
    [64] = 'Detecting Active Inputs',
    [128] = 'Walk Test Active',
    [256] = ' Away Arm',
    [512] = 'Stay Arm',
    [1024] = 'Sleep Arm',
    [2048] = ' Disarmed',
    [4096] = 'Arm Ready'
    }
    
    for k, v in pairs(vals) do
    
    if (bit.band(k , Area_Code)== k) then
        string = string .. " - " .. v
      end
    
    end
    
    log(string)
    I'll post the full code in my inception thread as I continue to work on it!
     
    Ks04, Sep 5, 2022
    #14
  15. Ks04

    Ks04

    Joined:
    Dec 28, 2019
    Messages:
    107
    Likes Received:
    9
    Any thoughts why the bits aren't being evaluated in sequence?

    E.g. "Closed - Locked" was calculated from a Public State ID of 768
    but "Unlocked - Closed" which was calculated from a Public State ID of 513

    I would have expected "Locked - Closed" to be calculated from 768. Any thoughts on how i can get this to be consistent?

    Code:
    local function dooreval(res)
    string = ""
    
    vals = {
    [1] = 'Unlocked',
    [2] = 'Open',
    [4] = 'Locked Out',
    [8] = 'Forced',
    [16] = 'Held Open Warning',
    [32] = 'Held Open Too Long',
    [64] = 'Breakglass',
    [128] = 'Reader Tamper',
    [256] = 'Locked',
    [512] = 'Closed',
    [1024] = 'Held Response Muted'
    }
    
    for k, v in pairs(vals) do
    
    if (bit.band(k , res)== k) then
        if string == "" then string = string .. v
        else string = string .. " - " .. v
        end
    end
    
    end
    return(string)
    end    
     
    Ks04, Sep 8, 2022
    #15
  16. Ks04

    SgrAystar

    Joined:
    Oct 4, 2018
    Messages:
    56
    Likes Received:
    5
    Location:
    Melbourne, Australia
    You could try changing 'pairs' to 'ipairs'?
     
    SgrAystar, Sep 8, 2022
    #16
  17. Ks04

    Ks04

    Joined:
    Dec 28, 2019
    Messages:
    107
    Likes Received:
    9
    Nope, that kills it all together.
     
    Ks04, Sep 8, 2022
    #17
  18. Ks04

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    231
    Likes Received:
    31
    Location:
    Melbourne
    You have a dictionary, and in LUA the order of a dictionary is not a thing.

    How about something like this? This example returns "Two - Eight", always in the same order. Harder to work out what is going on, but basically the table (not collection) has an order, and each represents 2 to the power of the position in the table.

    Code:
    valToTest = 0x0A
    
    vals = {'One','Two','Four','Eight','Sixteen'}
    
    s = ''
    for i=1,#vals do
      if bit.band(valToTest, 2^(i-1)) > 0 then
        if s == '' then s = vals[i] else s = s..' - '..vals[i] end
      end
    end
    
    log(s)
     
    ssaunders, Sep 8, 2022
    #18
  19. Ks04

    ssaunders

    Joined:
    Dec 17, 2008
    Messages:
    231
    Likes Received:
    31
    Location:
    Melbourne
    You could also get rid of the if/then/else by using a weird LUAism and make it even harder to read... ;) And basing the loop at zero makes this slightly faster to evaluate and 'easier' to read.
    Code:
    valToTest = 0x1A
    
    vals = {'One','Two','Four','Eight','Sixteen'}
    
    s = ''
    for i=0,#vals-1 do
      if bit.band(valToTest, 2^i) > 0 then
        s = (#s>0 and s..' - '..vals[i+1]) or vals[i+1]
      end
    end
    
    log(s)
     
    Last edited: Sep 8, 2022
    ssaunders, Sep 8, 2022
    #19
  20. Ks04

    Ks04

    Joined:
    Dec 28, 2019
    Messages:
    107
    Likes Received:
    9
    Lua is so weird! But that works perfectly, thanks!
     
    Ks04, Sep 8, 2022
    #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.