Inception Alarm

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

  1. Ks04

    Ks04

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

    I'm about to embark on an integration between my SHAC and Inner Range Inception alarm using their relevant APIs.

    Before I invest too much time in this, it appears from a search some have already gone down this road - has anyone got any code I can borrow?

    Thanks,

    Kyle
     
    Ks04, Sep 2, 2022
    #1
  2. Ks04

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    your other post gave me the push i needed to finally do this...this might get you started,
    change variables username, password, ip address and add in/make any area id variable/s required, as per the documentation if no request/s are made after 10 min api requires to login again, if we are polling the inception panel for changes probably be ok, if not w scheduled script to call the login function could every x min is probably a simple way to do it.
    ------below user Library
    Code:
    require ('ltn12')
    require ('socket.http')
    require('encdec')
    
    json = require("json")
    https = require ('ssl.https')
     
    Username = 'Installer'
    Password = 'Password1'
    Inception_ip = '192.168.250.15'
    
    
    vals = {[1] = 'Armed', [2] = 'Alarm', [4] = 'Entry Delay', [8] = 'Exit Delay', [16] = 'Arm Warning',
             [32] = 'Defer Disarme', [128] = 'Walk Test Active', [256] = ' Away Arm',
             [512] = 'Stay Arm', [1024] = 'Sleep Arm', [2048] = ' Disarmed', [4096] = 'Arm Ready' }
    
      --Area Id's
      House = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx'                                           
      Office = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
    
    
    function Inception_Post(url, body, headers) --headers = {}
       res_body ={}
     
      local body, code, hdrs, stat = https.request
     
      {
        url = 'http://'..Inception_ip..'/api/v1'..url;
        method = "POST",
        headers = headers,
        source = ltn12.source.string(body);
        sink = ltn12.sink.table(res_body);
      }
    
      if (code ~= 200) then
        log("Inception post error : "..tostring(code)..","..tostring(body))
        return
      end
     
       return json.pdecode(res_body[1])
      end
    
    
    function Inception_Get(url, headers)
      Get_response ={}
    
      res, err = https.request({
         url = 'http://'..Inception_ip..'/api/v1'..url,
         method = "GET",
         headers = headers,
       sink = ltn12.sink.table(Get_response),
      })
    local data = json.pdecode(Get_response[1])
      return data
    end
    
    
    -- manage hashed login
    function Inception_Login()
    
    encoded_credentials = encdec.base64enc(encdec.sha256(string.lower(Username)..':'..Password, true))
    
    credentials = json.encode({
      ["Username"] = Username,
        ["PasswordHash"] = encoded_credentials
    })
    
      local res = Inception_Post('/authentication/hashed-login', credentials, {["Content-Type"] = "text/plain;application/json",  ["Content-Length"] = #credentials})
    --  log(res.UserID)
      storage.set('Inception_sesh', res.UserID)
      return res.UserID
    end
    
    
    
    function Get_Areas_Id() -- Get Areas
    local areas = Inception_Get('/control/area',  { ["Cookie"] = 'LoginSessId='..storage.get('Inception_sesh') ,["Accept"] = 'application/json'})
    
    for i = 1, #areas do
      log(areas[i].Name..' = ' .. areas[i].ID)
    end
     
    end
    
    function Get_Areas_State(id)
      str = ''
      local states = Inception_Get('/control/area/'..id..'/state', { ["Cookie"] = 'LoginSessId='..storage.get('Inception_sesh') ,["Accept"] = 'application/json'})
     
     for k, v in pairs(vals) do
     
     if (bit.band(k , states.State)== k) then
          str = str ..' - '.. v
      end
     
    end
      return str
    end
    
    function Get_Users()
      local users = Inception_Get('/control/user', { ["Cookie"] = 'LoginSessId='..storage.get('Inception_sesh') ,["Accept"] = 'application/json'})
      return users
    end
    
    function Control_Area(id, C_type)
     
      bodys = json.encode({
          ["Type"] = "ControlArea",
            ["AreaControlType"] = C_type,
          ["Entity"] = id,
          ["ExitDelay"] = true,
    })
    
      local res = Inception_Post('/control/area/'..id..'/activity', bodys, { ["Cookie"] = 'LoginSessId='..storage.get('Inception_sesh') ,["Content-Type"] = 'application/json', ["Content-Length"] = #bodys})
     
      return res
    end
    -- call functions from whatever script
    -- ********************Call Functions******************
    -- Login
    Inception_Login()

    -- return area id and name/s then update variables in library
    Get_Areas_Id() -- this logs all areas and id's


    --Make area state request
    status = Get_Areas_State(House)
    log(status)

    users = Get_Users()
    log(users)

    -- Arm/Disarm area
    arm = Control_Area(House, "Arm")
    log(arm)
    disarm = Control_Area(House, "Disarm")
    log(disarm)
     
    Pie Boy, Sep 6, 2022
    #2
    Timbo likes this.
  3. Ks04

    Ks04

    Joined:
    Dec 28, 2019
    Messages:
    108
    Likes Received:
    9
    Legend! I took a slightly different approach and used the API key and included it as part of the request to save having to login. I then used the monitor APIs which use 60 second long-polling to get the latest status. This is still very much a WIP, and I'll continue to work on it (borrowing some ideas from yours above) but sharing some early drafts which currently polls the areas and updates a User Parameter:

    Happy for any feedback

    Code:
    -- Inner Range Inception Script for Long Polling Status --
    
    ------------------------------
         -- User Settings --
    ------------------------------
    
    -- Inception URL and Credentials
    local api_token = "xxxxxxxx"
    local API_ROOT = "http://192.168.1.112/api/v1"
    
    -- Inception IDs for querying APIs
    local area_id = "xxxxx"
    local front_door = "xxxxx"
    local rear_door = "xxxxx"
    
    -- NAC User Parameters
    local CBUS_USERPARAM_NAME_ALARMSTATE = "alarmstate"
    local CBUS_USERPARAM_NAME_FRONTDOOR = "frontdoor"
    local CBUS_USERPARAM_NAME_REARDOOR = "reardoor"
    
    ------------------------------
           -- Utilities --
    ------------------------------
    
    -- Utility Function to check if variable is empty
    local function isempty(s)
      return s == nil or s == ''
    end
    
    -- Initialise the Time Since Update variable (TSU)
    if isempty(tsu) then
      tsu = "0"
    end
    
    ------------------------------
    -- Inception Array Decoding --
    ------------------------------
    
    -- Area Decode Function
    local function areaeval(res)
    --log("Number coming in is... " .. res)
    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 , res)== k) then
        if string == "" then string = string .. v
        else string = string .. " - " .. v
        end
    end
    
    end
    --log("string going out is... ".. string)
    return(string)
    end 
      
    -- Door Decode Function
    local function dooreval(res)
    --log("Number coming in is... " .. 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
    --log("string going out is... ".. string)
    return(string)
    end 
    
    ------------------------------
      -- Query INCEPTION API --
    ------------------------------
    local http = require("socket.http")
    local json = require("json")
    local endpoint = "monitor-updates"
      
        local payload = json.encode({{
            ID = "CBUS-Areas-Monitor",
            RequestType = "MonitorEntityStates",
            InputData = {
                stateType = "AreaState",
                timeSinceUpdate = tsu }
        }}) 
      
        local body, code, headers, status = http.request {
        method = "POST",
        url = API_ROOT .. "/" .. endpoint,
        headers  =
                {
                ["Accept"] = "application/json";
                ["Authorization"] = "APIToken " .. api_token;
                },
        body = payload,
        timeout = 60,
        }
    
    log("Getting Data from Endpoint URL " .. API_ROOT .. '/' .. endpoint .. '\n' .. "TSU is " ..tsu .. '\n' .. 'body:' .. tostring(body) .. '\n' .. 'code:' .. tostring(code) .. '\n' .. 'status:' .. tostring(status))
    
    -- Validate that the response is not blank and was successful
    if isempty(body) == false then
        local httptable = json.decode(body)
        -- log(httptable)
        -- log("The last time the HTTP table was updated was... " .. tostring(httptable["Result"]["updateTime"]))
        tsu = tostring(httptable["Result"]["updateTime"])
    
        -- Find the matching area ID and translate it to a string
        local numUpdates = table.getn(httptable["Result"]["stateData"])
        for httptableindex = 1,numUpdates,1
        do
            if httptable["Result"]["stateData"][httptableindex]["ID"] == area_id then
                local PublicState = httptable["Result"]["stateData"][httptableindex]["PublicState"]
                local AlarmAreaStatus = areaeval(PublicState)
                log("Alarm Area Status is: " .. AlarmAreaStatus .. " which was calculated from a Public State ID of " .. PublicState)                     
             -- Set the C-Bus User Parameter
          SetUserParam(0, CBUS_USERPARAM_NAME_ALARMSTATE, AlarmAreaStatus)
              --    log("We have a match! " .. tostring(httptable["Result"]["stateData"][httptableindex]["ID"]) .. " The Status is now set to " .. tostring(httptable["Result"]["stateData"][httptableindex]["PublicState"]))
            end
        end
    elseif isempty(body) then
      log("Houston we have a problem, code returned was" .. code .. '\n' .. tostring(httptable))
    end
     
    Ks04, Sep 6, 2022
    #3
  4. Ks04

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    Nice one, yeah cool I'm going to add the Api key aswell now, didn't see that before.... Cheers
     
    Pie Boy, Sep 6, 2022
    #4
  5. Ks04

    Ks04

    Joined:
    Dec 28, 2019
    Messages:
    108
    Likes Received:
    9
    Tonights update now brings in the doors status from the API as well. I need to find a better way to manage the various IDs which the API spits out and mapping them to C-Bus attributes... job for another night.

    Code:
    -- Inner Range Inception Script for Long Polling Status --
    
    -- ToDo:
    -- Implement a nicer way to manage the area ids and mapping to user parameters (and subsequently updating the user parameters)
    -- Separate out the functions for querying the API
    -- Function to Arm the alarm
    -- Maybe API Call to unlock door (or leave as a relay as currently might be more reliable?)
    
    ------------------------------
         -- User Settings --
    ------------------------------
    
    -- Inception URL and Credentials
    local api_token = "xxx"
    local API_ROOT = "http://192.168.1.112/api/v1"
    
    -- Inception IDs for querying APIs
    local area_id = "xxx"
    local front_door_id = "yyy"
    local rear_door_id = "zzz"
    
    -- NAC User Parameters
    local CBUS_USERPARAM_NAME_ALARMSTATE = "alarmstate"
    local CBUS_USERPARAM_NAME_FRONTDOOR = "frontdoor"
    local CBUS_USERPARAM_NAME_REARDOOR = "reardoor"
    
    ------------------------------
           -- Utilities --
    ------------------------------
    
    -- Utility Function to check if variable is empty
    local function isempty(s)
      return s == nil or s == ''
    end
    
    -- Initialise the Time Since Update variable (tsu)
    if isempty(tsuarea) then
      tsuarea = "0"
    end
    
    if isempty(tsudoors) then
      tsudoors = "0"
      log("setting tsudoors to 0")
    end
    
    -- function to return the correct door/area to update
    local function whichid(id)
    if id == area_id then result = CBUS_USERPARAM_NAME_ALARMSTATE
    elseif id == front_door_id then result = CBUS_USERPARAM_NAME_FRONTDOOR
    elseif id == rear_door_id then result = CBUS_USERPARAM_NAME_REARDOOR
    else result = "no"
    end
    return result
    end
    
    ------------------------------
    -- Inception Array Decoding --
    ------------------------------
    
    -- Area Decode Function
    local function areaeval(res)
    --log("Number coming in is... " .. res)
    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 , res)== k) then
        if string == "" then string = string .. v
        else string = string .. " - " .. v
        end
    end
    
    end
    --log("string going out is... ".. string)
    return(string)
    end   
        
    -- Door Decode Function
    local function dooreval(res)
    --log("Number coming in is... " .. 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
    --log("string going out is... ".. string)
    return(string)
    end   
    
    ------------------------------
        -- C-Bus Functions --
    ------------------------------
    
    
    ------------------------------
      -- Query INCEPTION API --
    ------------------------------
    local http = require("socket.http")
    local json = require("json")
    local endpoint = "monitor-updates"
        
        local payload = json.encode(
        {
            {
            ID = "CBUS-Areas-Monitor",
            RequestType = "MonitorEntityStates",
            InputData = {
                stateType = "AreaState",
                timeSinceUpdate = tsuarea
            }
        },
        {
            ID = "CBUS-Doors-Monitor",
            RequestType = "MonitorEntityStates",
            InputData = {
                stateType = "DoorState",
                timeSinceUpdate = tsudoors
            }
        }
        })   
        
        local body, code, headers, status = http.request {
        method = "POST",
        url = API_ROOT .. "/" .. endpoint,
        headers  =
                {
                ["Accept"] = "application/json";
                ["Authorization"] = "APIToken " .. api_token;
                },
        body = payload,
        timeout = 60,
        }
    
    log("Getting Data from Endpoint URL " .. API_ROOT .. '/' .. endpoint .. '\n' .. "tsuarea is " ..tsuarea .. '\n' .."TSUdoors is " .. tsudoors .. '\n'.. 'body:' .. tostring(body) .. '\n' .. 'code:' .. tostring(code) .. '\n' .. 'status:' .. tostring(status))
    
      if (code ~= 200) then
        log("Inception post error : "..tostring(code)..","..tostring(body))
        return
      end
    
    -- Validate that the response is not blank and was successful
    
    if isempty(body) == false then
        local httptable = json.decode(body)
        log(httptable)
        -- log("The last time the HTTP table was updated was... " .. tostring(httptable["Result"]["updateTime"]))
        
        -- check if the response was related to areas?
        if tostring(httptable["ID"]) == "CBUS-Areas-Monitor" then
            tsuarea = tostring(httptable["Result"]["updateTime"])
            -- Find the matching area ID and translate it to a string
            local numUpdates = table.getn(httptable["Result"]["stateData"])
            for httptableindex = 1,numUpdates,1
            do
                if httptable["Result"]["stateData"][httptableindex]["ID"] == area_id then
                    local PublicState = httptable["Result"]["stateData"][httptableindex]["PublicState"]
                    local AlarmAreaStatus = areaeval(PublicState)
                    log("Alarm Area Status is: " .. AlarmAreaStatus .. " which was calculated from a Public State ID of " .. PublicState)                       
                     -- Set the C-Bus User Parameter
              SetUserParam(0, CBUS_USERPARAM_NAME_ALARMSTATE, AlarmAreaStatus)
                      --    log("We have a match! " .. tostring(httptable["Result"]["stateData"][httptableindex]["ID"]) .. " The Status is now set to " .. tostring(httptable["Result"]["stateData"][httptableindex]["PublicState"]))
                end
        end
      end
        -- check if the response was related to doors?
    
        if tostring(httptable["ID"]) == "CBUS-Doors-Monitor" then
            tsudoors = tostring(httptable["Result"]["updateTime"])   
            -- Find the matching ID and translate it to a string
            local numUpdates = table.getn(httptable["Result"]["stateData"])
            for httptableindex = 1,numUpdates,1
            do
                if httptable["Result"]["stateData"][httptableindex]["ID"] == front_door_id then
                    local PublicState = httptable["Result"]["stateData"][httptableindex]["PublicState"]
                    local FrontDoorStatus = dooreval(PublicState)
                    log("Front Door Status is: " .. FrontDoorStatus .. " which was calculated from a Public State ID of " .. PublicState)                       
                -- Set the C-Bus User Parameter
              SetUserParam(0, CBUS_USERPARAM_NAME_FRONTDOOR, FrontDoorStatus)
            --    log("We have a match! " .. tostring(httptable["Result"]["stateData"][httptableindex]["ID"]) .. " The Status is now set to " .. tostring(httptable["Result"]["stateData"][httptableindex]["PublicState"]))
                end
                if httptable["Result"]["stateData"][httptableindex]["ID"] == rear_door_id then
                    local PublicState = httptable["Result"]["stateData"][httptableindex]["PublicState"]
                    local RearDoorStatus = dooreval(PublicState)
                    log("Rear Door Status is: " .. RearDoorStatus .. " which was calculated from a Public State ID of " .. PublicState)                       
                   -- Set the C-Bus User Parameter
                    SetUserParam(0, CBUS_USERPARAM_NAME_REARDOOR, RearDoorStatus)
                    
                  --    log("We have a match! " .. tostring(httptable["Result"]["stateData"][httptableindex]["ID"]) .. " The Status is now set to " .. tostring(httptable["Result"]["stateData"][httptableindex]["PublicState"]))
                end
          end
      end
    end
     
    Ks04, Sep 7, 2022
    #5
    Timbo likes this.
  6. Ks04

    Timbo

    Joined:
    Aug 3, 2004
    Messages:
    22
    Likes Received:
    4
    Location:
    Perth, Australia
    This is awesome guys, going to swap over to using the API - appreciate both of your efforts!
    I've been using the hacky http method which you've probably seen in other threads - has NEVER skipped a beat, but clearly messy and only exposes what you manually setup as an automation on the Inception side.

    FYI, this has also been working flawlessly for some time - handy for anybody going down the MQTT path:
    https://github.com/matthew-larner/inception-mqtt
     
    Timbo, Sep 7, 2022
    #6
    Pie Boy likes this.
  7. Ks04

    Pie Boy

    Joined:
    Nov 21, 2012
    Messages:
    248
    Likes Received:
    31
    Location:
    New Zealand
    @Ks04 a couple on observations that might help
    I believe you can use

    if body then
    ….
    end

    To check if a var has a value

    if body would be true if it has a value and false or nil otherwise
    Also if you use json.pdecode() it uses protected mode,
    Meaning if for any reason invalid json string was attempted to be decoded it would just discard it,
    without doing pdecode() will stop the script if the above condition arose.
     
    Pie Boy, Sep 7, 2022
    #7
  8. Ks04

    Ks04

    Joined:
    Dec 28, 2019
    Messages:
    108
    Likes Received:
    9
    Thanks, I was doing this originally, however it was then throwing an error when you get a 200 status, but no body (as there are no updates) which returns 'nil'

    pdecode() gets me part of the way there, however then other statements get confused by the nil table: "
    Resident script:167: attempt to index local 'httptable' (a nil value)
     
    Ks04, Sep 8, 2022
    #8
  9. Ks04

    Ks04

    Joined:
    Dec 28, 2019
    Messages:
    108
    Likes Received:
    9
    You've all be so helpful, any suggestions on how I might clean up the matching of ID's

    The Inception API send a unique string for each input - e.g.:
    Front Door: "a"
    Rear Door: "b"

    I have defined each of these as a variable:
    Code:
    s.front_door_id = "a"
    s.rear_door_id = "b"
    I've then separately mapped the Front Door and Rear door to a C-Bus User Param:
    Code:
    A.CBUS_USERPARAM_NAME_FRONTDOOR = "frontdoor"
    A.CBUS_USERPARAM_NAME_REARDOOR = "reardoor"
    Then each time I get a result from the API, I'm looping through the response to check if the response equals the Front Door or Rear Door to work out which user parameter to update. At the moment this uses if statements (see below) but is there a cleaner way?

    Could I for example build a table which has the API String in one column and the User Parameters in the second, then search the table for that result and return the user parameter to update?

    Code:
    local numUpdates = table.getn(httptable["Result"]["stateData"])
        for httptableindex = 1,numUpdates,1
        do
            if httptable["Result"]["stateData"][httptableindex]["ID"] == secrets.front_door_id then
                local PublicState = httptable["Result"]["stateData"][httptableindex]["PublicState"]
                local FrontDoorStatus = inception.dooreval(PublicState)
                log("Front Door Status is: " .. FrontDoorStatus .. " which was calculated from a Public State ID of " .. PublicState)                      
            -- Set the C-Bus User Parameter
        SetUserParam(0, inception.CBUS_USERPARAM_NAME_FRONTDOOR, FrontDoorStatus)
        local strn = string.split(FrontDoorStatus, "-")
        local DoorScreenLabel = tostring(strn[1])
        SetCBusLabel(0,56,122,1,'Variant 1',string.sub(DoorScreenLabel, 1, 13))
        log("Setting C-Bus Label to: " .. tostring(DoorScreenLabel))
        --    log("We have a match! " .. tostring(httptable["Result"]["stateData"][httptableindex]["ID"]) .. " The Status is now set to " .. tostring(httptable["Result"]["stateData"][httptableindex]["PublicState"]))
            end
            if httptable["Result"]["stateData"][httptableindex]["ID"] == secrets.rear_door_id then
                local PublicState = httptable["Result"]["stateData"][httptableindex]["PublicState"]
                local RearDoorStatus = inception.dooreval(PublicState)
                log("Rear Door Status is: " .. RearDoorStatus .. " which was calculated from a Public State ID of " .. PublicState)                      
            -- Set the C-Bus User Parameter
                SetUserParam(0, inception.CBUS_USERPARAM_NAME_REARDOOR, RearDoorStatus)
            --    log("We have a match! " .. tostring(httptable["Result"]["stateData"][httptableindex]["ID"]) .. " The Status is now set to " .. tostring(httptable["Result"]["stateData"][httptableindex]["PublicState"]))
            end
     
    Ks04, Nov 17, 2022
    #9
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.