Changeset 66


Ignore:
Timestamp:
2012-06-18 17:53:25 (13 years ago)
Author:
hugheaves
Message:

Changed TTL handling so that it only applies to wired zones.
Fixed bug with PIN not being stored in memory when SecureStore? is false. (and most functions using PIN expected it to be in memory)
Added "DisableSendStar?" setting that disables sending "*" to the panel. (Sending "*" from Vera can interfere with manual alarm operation from the keypad)
Added core support for Virtual Zones - mapping vera sensors (motion detectors, switches, etc.) into alarm panel zones

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • TabularUnified trunk/L_VistaAlarmPanel1.lua

    r63 r66  
    2525    PARTITION = "urn:micasaverde-com:serviceId:AlarmPartition2", 
    2626    SECURITY_SENSOR = "urn:micasaverde-com:serviceId:SecuritySensor1" 
     27} 
     28 
     29local ZONE_TYPE = { 
     30    UNKNOWN = 0, 
     31    WIRED = 1, 
     32    RF = 2 
    2733} 
    2834 
     
    7076    -- channel 
    7177    -- name 
     78    -- type 
     79} 
     80 
     81-- maps Vera security sensors to "virtual" panel zones 
     82local g_securitySensors = { 
     83    -- zone 
    7284} 
    7385 
     
    447459    debug("(VistaAlarmPanel::storePinCode) partNo = "..partNo) 
    448460 
    449     if (SECURE_STORE) then 
    450         -- Store the PIN code to be used for sending '*'. 
    451         g_pinCodes[partNo] = pinCode 
    452     else 
     461    -- Store the new PIN in memory 
     462    g_pinCodes[partNo] = pinCode 
     463 
     464    -- Then also store the PIN in the file if SECURE_STORE is not enabled 
     465    if (not SECURE_STORE) then 
    453466        local file = io.open(FILE, "w") 
    454467        file:write(partNo..pinCode) 
     
    482495    luup.variable_set(SID.SECURITY_SENSOR, "LastTrip", tostring(os.time()), g_zones[zone].device) 
    483496 
    484     local tripped = luup.variable_get(SID.SECURITY_SENSOR, "Tripped", g_zones[zone].device) 
     497    local tripped = luup.variable_get(SID.SECURITY_SENSOR, "Tripped", g_zones[zone].device) or 0 
    485498    if (tripped ~= "1") then 
    486         table.insertSet(g_faultedZones, zone) 
     499        -- Add zone to the TTL list, unless it's an RF zone 
     500        if (g_zones[zone].type ~= ZONE_TYPE.RF) then 
     501            table.insertSet(g_faultedZones, zone) 
     502        end 
    487503        luup.variable_set(SID.SECURITY_SENSOR, "Tripped", "1", g_zones[zone].device) 
    488504        debug("(VistaAlarmPanel::addFaultedZone) Set zone #"..zone.." to tripped.") 
     
    498514 
    499515    table.removeItem(g_faultedZones, zone) 
    500     luup.variable_set(SID.SECURITY_SENSOR, "Tripped", "0", g_zones[zone].device) 
    501     debug("(VistaAlarmPanel::removeFaultedZone) Set zone #"..zone.." to not tripped.") 
     516     
     517    local tripped = luup.variable_get(SID.SECURITY_SENSOR, "Tripped", g_zones[zone].device) or 0 
     518    if (tripped == "1") then 
     519        luup.variable_set(SID.SECURITY_SENSOR, "Tripped", "0", g_zones[zone].device) 
     520        debug("(VistaAlarmPanel::removeFaultedZone) Set zone #"..zone.." to not tripped.") 
     521    end 
    502522end 
    503523 
     
    516536 
    517537function sendStar (partNo) 
     538    local disableSendStar = luup.variable_get(SID.PANEL, "DisableSendStar", lug_device) or "0" 
     539    if (disableSendStar == "1") then 
     540        debug ("Send star disabled, not sending star") 
     541    end 
     542     
    518543    partNo = tonumber(partNo, 10) 
    519544 
     
    708733 
    709734 
    710 function processIncoming (data) 
    711  
    712     if (not NUM_PARTITIONS) then 
    713         log("(VistaAlarmPanel::processIncoming) ERROR: NumPartitions not set. Terminate.") 
    714         return false 
    715     elseif (NO_ADDRESSES) then 
    716         log("(VistaAlarmPanel::processIncoming) ERROR: No keypads for one or more partitions. Terminate.") 
    717         return false 
    718     end 
    719  
    720     debug("(VistaAlarmPanel::processIncoming) Incoming data = '"..(data or "").."'.") 
    721  
    722     -- Get the timestamp of the last good message from the AD2USB. 
    723     g_timeLastMessage = os.time() 
    724  
    725     if (data:sub(1, 1) == '!') then 
    726         return processExMessage(data) 
    727     end 
    728  
    729     -- Split the message into sections. 
    730     local sections = {data:match('^%[([%d%-]+)%],(%x+),%[(%x+)%],"(.+)"$')} 
    731  
    732     -- Check if the message is valid. 
    733     if (not sections[1] or not sections[2] or not sections[3] or not sections[4]) then 
    734         log("(VistaAlarmPanel::processIncoming) ERROR: Invalid message.") 
    735         return false 
    736     end 
    737  
    738     local flags = getStatusFlags(sections[1]) 
    739     if (flags.PROGRAMMING_MODE) then 
    740         debug("(VistaAlarmPanel::processIncoming) Programming mode, drop message.") 
    741         return true 
    742     end 
    743  
    744     -- Get the partitions this message is for. 
    745     local targetPartitions 
    746     if (NUM_PARTITIONS == 1) then 
    747         targetPartitions = {1} 
    748     else 
    749         targetPartitions = getTargetPartitions(sections[3]) 
    750     end 
    751  
    752     -- 
    753     -- Process message. 
    754     -- 
    755     if (sections[4]:find("^FAULT %d%d")) then -- Zone faulted 
    756         for _, partNo in pairs(targetPartitions) do 
    757             updateArmMode(flags, partNo, false) 
    758         end 
    759         addFaultedZone(tonumber(sections[2], 10)) 
    760     elseif (sections[4]:find("^BYPAS %d%d")) then -- Zone bypassed 
    761         for _, partNo in pairs(targetPartitions) do 
    762             updateArmMode(flags, partNo, false) 
    763         end 
    764         removeFaultedZone(tonumber(sections[2], 10)) 
    765     elseif (sections[4]:find("^CHECK %d%d")) then -- Zone supervision notice 
    766         log("(VistaAlarmPanel::processIncoming) Zone supervision notice: " .. sections[4]) 
    767     else 
    768         for _, partNo in pairs(targetPartitions) do 
    769             -- Check if we have any alarms. 
    770             if (checkForAlarms(flags, sections[2], partNo)) then 
    771                 return false 
    772             end 
    773  
    774             -- Update the chime mode. 
    775             updateChimeMode(flags, partNo) 
    776  
    777             if (sections[4]:lower():find("enter user code", 1, true)) then -- A lettered key was pressed. 
    778                 debug("(VistaAlarmPanel::processIncoming) A lettered key was pressed, send user code...") 
    779                 luup.io.write(g_userCodes[partNo] or "") 
    780                 g_userCodes[partNo] = nil 
    781             else -- Chances are this message is an alarm status report. 
    782                 updateArmMode(flags, partNo, true) 
    783             end 
    784         end 
    785     end 
    786  
    787     return true 
    788 end 
    789  
    790735-- find a zone by address and channel, and set it's faulted status 
    791736local function updateZoneByAddress(address, channel, faulted) 
     
    808753end 
    809754 
    810 -- Process AD2USB messages starting with "!". 
    811 --   This function currently processes only "!RFX", "!REL", 
    812 --   and "!EXP" messages, and ignores all others 
    813 function processExMessage (data) 
    814  
    815     -- Messages that we want to process look like the 
    816     -- following: 
     755 
     756-- Process "!RFX" messages 
     757-- 
     758-- Example messages:  
     759-- !RFX:0180036,80 
     760-- 
     761local function processRFXMessage(data) 
     762    -- break the incoming message into sections (I wish Lua had real regex handling :( 
     763    local sections = {data:match('^!RFX:(%d%d%d%d%d%d%d),(%x%x)$')} 
     764     
     765    if (not sections or #sections == 0) then 
     766        log ("(VistaAlarmPanel::processRFXMessage) Invalid RFX message received: " .. data) 
     767        return false 
     768    end 
     769     
     770    local serial = sections[1] 
     771    local rawDeviceData = tonumber(sections[2], 16) 
     772         
     773    -- decode raw device data 
     774    local flags = {} 
     775    flags.unknown1 = bit.band(rawDeviceData, 0x01) > 0 
     776    flags.battery = bit.band(rawDeviceData, 0x02) > 0 
     777    flags.supervision = bit.band(rawDeviceData, 0x04) > 0 
     778    flags.unknown2 = bit.band(rawDeviceData, 0x08) > 0 
     779    -- note that the loop status bits are not in the order you would expect 
     780    local loop = {} 
     781    loop[3] = bit.band(rawDeviceData, 0x10) > 0 
     782    loop[2] = bit.band(rawDeviceData, 0x20) > 0 
     783    loop[4] = bit.band(rawDeviceData, 0x40) > 0 
     784    loop[1] = bit.band(rawDeviceData, 0x80) > 0 
     785         
     786    if (DEBUG_MODE) then 
     787        local message = "(VistaAlarmPanel::processExMessage) Decoded RFX message: serial = " .. serial 
     788        for i = 1, 4 do 
     789            message = message .. ", loop[" .. i .. "] = " .. tostring(loop[i]) 
     790        end 
     791        for k, v in pairs(flags) do 
     792            message = message .. ", flags." .. k .. " = " .. tostring(v)             
     793        end 
     794        debug (message) 
     795    end 
     796 
     797    -- Ignore any messages that have the "unknown" flags set. 
     798    -- We don't know the unknown flags do, so we don't want to 
     799    -- process any messages with those flags 
     800    if (flags.unknown1 or flags.unknown2) then 
     801        log("(VistaAlarmPanel::processExMessage) Unknown flags set in RFX message, serial = " .. deviceSerialNo) 
     802        return true 
     803    end 
     804         
     805    -- update Zone tripped statuses 
     806    for i = 1, 4 do 
     807        updateZoneByAddress(serial, i, loop[i]) 
     808    end 
     809     
     810    return true 
     811end 
     812 
     813-- Process "!REL" or "!EXP" messages 
     814-- 
     815-- Example messages 
     816-- !REL:12,01,01 
     817-- !EXP:07,01,01 
     818-- 
     819local function processRELEXPMessages(data) 
     820    local sections = {data:match('^!(%u%u%u):(%d%d),(%d%d),(%d%d)$')} 
     821 
     822    if (not sections or #sections == 0) then 
     823        log ("(VistaAlarmPanel::processRELEXPMessages) Invalid message received: " .. data) 
     824        return false 
     825    end 
     826 
     827    local address = sections[2] 
     828    local channel = tonumber(sections[3], 10) 
     829    local faulted = bit.band(tonumber(sections[4], 10), 0x01) > 0 
     830    local supervision = bit.band(tonumber(sections[4], 10), 0x02) > 0 
     831 
     832    debug("(VistaAlarmPanel::processExMessage) Decoded " .. sections[1] .. 
     833        " message: address = " .. address  .. ", channel = " .. channel ..  
     834        ", faulted = " .. tostring(faulted) .. ", supervision = " .. tostring(supervision)) 
     835             
     836    if (not supervision) then 
     837        updateZoneByAddress(address, channel, faulted) 
     838    end 
     839     
     840    return true 
     841end 
     842 
     843function sendVirtualZoneStatus(zone, status) 
     844    debug ("(VistaAlarmPanel::sendVirtualZoneStatus) zone: " .. (zone or "") .. ", status: " .. (status or "")) 
     845    local command = "L" .. string.format("%02d", zone) .. status 
     846    debug ("(VistaAlarmPanel::sendVirtualZoneStatus) Sending virtual extender message to panel: " .. command) 
     847    luup.io.write(command) 
     848    luup.sleep(SLEEP_MS) 
     849end 
     850         
     851function processIncoming (data) 
     852 
     853    if (not NUM_PARTITIONS) then 
     854        log("(VistaAlarmPanel::processIncoming) ERROR: NumPartitions not set. Terminate.") 
     855        return false 
     856    elseif (NO_ADDRESSES) then 
     857        log("(VistaAlarmPanel::processIncoming) ERROR: No keypads for one or more partitions. Terminate.") 
     858        return false 
     859    end 
     860 
     861    debug("(VistaAlarmPanel::processIncoming) Incoming data = '"..(data or "").."'.") 
     862 
     863    -- Get the timestamp of the last good message from the AD2USB. 
     864    g_timeLastMessage = os.time() 
     865 
     866    if (data:sub(1,4) == "!RFX") then 
     867        return processRFXMessage(data) 
     868    elseif (data:sub(1,4) == "!REL" or data:sub(1,4) == "!EXP") then 
     869        return processRELEXPMessages(data) 
     870    end 
     871 
     872    -- Split the message into sections. 
     873    local sections = {data:match('^%[([%d%-]+)%],(%x+),%[(%x+)%],"(.+)"$')} 
     874 
     875    -- Check if the message is valid. 
     876    if (not sections[1] or not sections[2] or not sections[3] or not sections[4]) then 
     877        log("(VistaAlarmPanel::processIncoming) ERROR: Invalid message.") 
     878        return false 
     879    end 
     880 
     881    local flags = getStatusFlags(sections[1]) 
     882    if (flags.PROGRAMMING_MODE) then 
     883        debug("(VistaAlarmPanel::processIncoming) Programming mode, drop message.") 
     884        return true 
     885    end 
     886 
     887    -- Get the partitions this message is for. 
     888    local targetPartitions 
     889    if (NUM_PARTITIONS == 1) then 
     890        targetPartitions = {1} 
     891    else 
     892        targetPartitions = getTargetPartitions(sections[3]) 
     893    end 
     894 
    817895    -- 
    818     -- !RFX:0180036,80 
    819     -- !REL:12,01,01 
    820     -- !EXP:07,01,01 
     896    -- Process message. 
     897    -- 
     898    if (sections[4]:find("^FAULT %d%d")) then -- Zone faulted 
     899        for _, partNo in pairs(targetPartitions) do 
     900            updateArmMode(flags, partNo, false) 
     901        end 
     902        addFaultedZone(tonumber(sections[2], 10)) 
     903    elseif (sections[4]:find("^BYPAS %d%d")) then -- Zone bypassed 
     904        for _, partNo in pairs(targetPartitions) do 
     905            updateArmMode(flags, partNo, false) 
     906        end 
     907        removeFaultedZone(tonumber(sections[2], 10)) 
     908    elseif (sections[4]:find("^CHECK %d%d")) then -- Zone supervision notice 
     909        log("(VistaAlarmPanel::processIncoming) Zone supervision notice: " .. sections[4]) 
     910    else 
     911        for _, partNo in pairs(targetPartitions) do 
     912            -- Check if we have any alarms. 
     913            if (checkForAlarms(flags, sections[2], partNo)) then 
     914                return false 
     915            end 
     916 
     917            -- Update the chime mode. 
     918            updateChimeMode(flags, partNo) 
     919 
     920            if (sections[4]:lower():find("enter user code", 1, true)) then -- A lettered key was pressed. 
     921                debug("(VistaAlarmPanel::processIncoming) A lettered key was pressed, send user code...") 
     922                luup.io.write(g_userCodes[partNo] or "") 
     923                g_userCodes[partNo] = nil 
     924            else -- Chances are this message is an alarm status report. 
     925                updateArmMode(flags, partNo, true) 
     926            end 
     927        end 
     928    end 
     929 
     930    return true 
     931end 
     932 
     933-- This function is registered with luup.variable_watch for Vera devices that are 
     934-- mapped to virtual zones in the security panel. 
     935-- When the Vera sensor state changes, it sends a "Virtual Zone Expander" message 
     936-- to the panel using the virtual zone. 
     937function securitySensorCallback(lul_device, lul_service, lul_variable, lul_value_old, lul_value_new) 
     938    debug("(VistaAlarmPanel::securitySensorCallback) Received callback: lul_device = " .. (lul_device or "nil") .. 
     939        ", lul_service = " .. (lul_service or "nil") .. 
     940        ", lul_variable = " .. (lul_variable or "nil") .. 
     941        ", lul_value_old = " .. (lul_value_old or "nil") .. 
     942        ", lul_value_new = " .. (lul_value_new or "nil")) 
     943         
     944    local securitySensor = g_securitySensors[tonumber(lul_device)] 
    821945     
    822     -- break the incoming message into sections (I wish Lua had real regex handling :( 
    823     local sections = {data:match('^!(%u%u%u):(%d+),(%x%x),?(%x?%x?)$')} 
    824  
    825     -- see if we got a match 
    826     if (#sections == 0) then 
    827         log("(VistaAlarmPanel::processExMessage) Unrecognized message format.") 
    828         return true 
    829     end 
    830  
    831     if (sections[1] == "RFX" and sections[2] ~= "" and sections[3] ~= "") then 
    832      
    833         local serial = sections[2] 
    834          
    835         -- decode raw device data 
    836         local rawDeviceData = tonumber(sections[3], 16) 
    837         local flags = {} 
    838         flags.unknown1 = bit.band(rawDeviceData, 0x01) > 0 
    839         flags.battery = bit.band(rawDeviceData, 0x02) > 0 
    840         flags.supervision = bit.band(rawDeviceData, 0x04) > 0 
    841         flags.unknown2 = bit.band(rawDeviceData, 0x08) > 0 
    842         -- note that the loop status bits are not in the order you would expect 
    843         local loop = {} 
    844         loop[3] = bit.band(rawDeviceData, 0x10) > 0 
    845         loop[2] = bit.band(rawDeviceData, 0x20) > 0 
    846         loop[4] = bit.band(rawDeviceData, 0x40) > 0 
    847         loop[1] = bit.band(rawDeviceData, 0x80) > 0 
    848          
    849         if (DEBUG_MODE) then 
    850             local message = "(VistaAlarmPanel::processExMessage) Decoded RFX message: serial = " .. serial 
    851             for i = 1, 4 do 
    852                 message = message .. ", loop[" .. i .. "] = " .. tostring(loop[i]) 
    853             end 
    854             for k, v in pairs(flags) do 
    855                 message = message .. ", flags." .. k .. " = " .. tostring(v)             
    856             end 
    857             debug (message) 
    858         end 
    859  
    860         -- Ignore any messages that have the "unknown" flags set. 
    861         -- We don't know the unknown flags do, so we don't want to 
    862         -- process any messages with those flags 
    863         if (flags.unknown1 or flags.unknown2) then 
    864             log("(VistaAlarmPanel::processExMessage) Unknown flags set in RFX message, serial = " .. deviceSerialNo) 
    865             return true 
    866         end 
    867          
    868         -- If this isn't a supervision message, then 
    869         -- update Zone tripped statuses 
    870         if (not flags.supervision) then 
    871             for i = 1, 4 do 
    872                 updateZoneByAddress(serial, i, loop[i]) 
    873             end 
    874         end 
    875      
    876     elseif ((sections[1] == "REL" or sections[1] == "EXP") and sections[2] ~= "" 
    877         and sections[3] ~= "" and sections[4] ~= "") then 
    878      
    879             local address = sections[2] 
    880             local channel = tonumber(sections[3], 10) 
    881             local faulted = bit.band(tonumber(sections[4], 10), 0x01) > 0 
    882             local supervision = bit.band(tonumber(sections[4], 10), 0x02) > 0 
    883  
    884             debug("(VistaAlarmPanel::processExMessage) Decoded " .. sections[1] .. 
    885                 " message: address = " .. address  .. ", channel = " .. channel ..  
    886                 ", faulted = " .. tostring(faulted) .. ", supervision = " .. tostring(supervision)) 
    887              
    888             if (not supervision) then 
    889                 updateZoneByAddress(address, channel, faulted) 
    890             end 
    891     else 
    892         log("(VistaAlarmPanel::processExMessage) Unrecognized message format.") 
    893         return true      
    894     end 
    895  
    896     return true 
     946    if (securitySensor and lul_value_new == "0" or lul_value_new == "1") then 
     947        sendVirtualZoneStatus (securitySensor.zone, lul_value_new) 
     948    end 
    897949end 
    898950 
     
    9901042        luup.chdev.append(lug_device, rootPtr, "vista_zone_"..zoneNo, "Zone #"..zoneNo.." - "..zoneName, "urn:schemas-micasaverde-com:device:MotionSensor:1", "D_MotionSensor1.xml", "", parameters, false) 
    9911043        zoneNo = tonumber(zoneNo, 10) 
     1044        local zoneType = ZONE_TYPE.UNKNOWN 
     1045        if (zoneAddress:len() == 2 and zoneChannel:len() == 1) then 
     1046            zoneType = ZONE_TYPE.WIRED 
     1047        elseif (zoneAddress:len() == 7 and zoneChannel:len() == 1) then 
     1048            zoneType = ZONE_TYPE.RF 
     1049        end 
    9921050        g_zones[zoneNo] = { 
    9931051            address = zoneAddress, -- keep address as string because RF serial # "0000012" != address "12"  
    994             channel = tonumber(zoneChannel, 10) 
     1052            channel = tonumber(zoneChannel, 10), 
     1053            type = zoneType 
    9951054        } 
    9961055    end 
     
    10411100end 
    10421101 
     1102-- setup listeners for the vera devices that are mapped to virtual zones 
     1103local function initVirtualZones() 
     1104    local virtualZones = luup.variable_get(SID.PANEL, "VirtualZones", lul_device) or "" 
     1105    log("(VistaAlarmPanel::initVirtualZones) VirtualZones setting: " .. virtualZones) 
     1106    for deviceNo, zoneNo in virtualZones:gmatch("(%d+)-(%d+);?") do 
     1107        local zone = tonumber(zoneNo) 
     1108        local device = tonumber(deviceNo) 
     1109        if (zone < 1 or zone > 99) then 
     1110            log("(VistaAlarmPanel::initVirtualZones) Invalid zone number " .. zoneNo) 
     1111        else 
     1112            log("(VistaAlarmPanel::initVirtualZones) Mapping Vera device " .. device .. " to virtual zone " .. zone) 
     1113            g_securitySensors[tonumber(deviceNo)] = { ["zone"] = zone } 
     1114            -- send tripped status to panel to make sure panel is "in sync" 
     1115            local status = luup.variable_get(SID.SECURITY_SENSOR, "Tripped", device) or "0" 
     1116            sendVirtualZoneStatus(zone, status) 
     1117            luup.variable_watch("securitySensorCallback", SID.SECURITY_SENSOR, "Tripped", device) 
     1118        end 
     1119    end 
     1120end 
     1121 
    10431122 
    10441123------------------------------------------------------------------------------- 
     
    10881167 
    10891168    -- Check if there are any faulted zones that need to be updated. 
     1169    -- Ignore RF zones as they send out regular supervision messages that 
     1170    -- update the status anyway. 
    10901171    for zoneNo, zone in pairs(g_zones) do 
    10911172        local lastTrip = luup.variable_get(SID.SECURITY_SENSOR, "LastTrip", zone.device) or 0 
    1092         if (os.difftime(os.time(), lastTrip) >= TTL) then 
     1173        if (os.difftime(os.time(), lastTrip) >= TTL and zone.type ~= ZONE_TYPE.RF) then 
    10931174            removeFaultedZone(zoneNo) 
    10941175        end 
    10951176    end 
    10961177 
     1178    -- Initialize virtual zones 
     1179    initVirtualZones() 
     1180     
    10971181    -- Start the TTL timer. 
    10981182    ttlCountdownTimer() 
  • TabularUnified trunk/S_VistaAlarmPanel1.xml

    r40 r66  
    4949            <name>CheckConnectionTimer</name> 
    5050            <datatype>ui2</datatype> 
     51        </stateVariable> 
     52        <stateVariable> 
     53            <name>VirtualZones</name> 
     54            <datatype>string</datatype> 
     55        </stateVariable> 
     56        <stateVariable> 
     57            <name>DisableSendStar</name> 
     58            <datatype>boolean</datatype> 
    5159        </stateVariable> 
    5260    </serviceStateTable> 
Note: See TracChangeset for help on using the changeset viewer.