Changeset 66
- Timestamp:
- 2012-06-18 17:53:25 (13 years ago)
- Location:
- trunk
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
TabularUnified trunk/L_VistaAlarmPanel1.lua ¶
r63 r66 25 25 PARTITION = "urn:micasaverde-com:serviceId:AlarmPartition2", 26 26 SECURITY_SENSOR = "urn:micasaverde-com:serviceId:SecuritySensor1" 27 } 28 29 local ZONE_TYPE = { 30 UNKNOWN = 0, 31 WIRED = 1, 32 RF = 2 27 33 } 28 34 … … 70 76 -- channel 71 77 -- name 78 -- type 79 } 80 81 -- maps Vera security sensors to "virtual" panel zones 82 local g_securitySensors = { 83 -- zone 72 84 } 73 85 … … 447 459 debug("(VistaAlarmPanel::storePinCode) partNo = "..partNo) 448 460 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 453 466 local file = io.open(FILE, "w") 454 467 file:write(partNo..pinCode) … … 482 495 luup.variable_set(SID.SECURITY_SENSOR, "LastTrip", tostring(os.time()), g_zones[zone].device) 483 496 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 485 498 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 487 503 luup.variable_set(SID.SECURITY_SENSOR, "Tripped", "1", g_zones[zone].device) 488 504 debug("(VistaAlarmPanel::addFaultedZone) Set zone #"..zone.." to tripped.") … … 498 514 499 515 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 502 522 end 503 523 … … 516 536 517 537 function 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 518 543 partNo = tonumber(partNo, 10) 519 544 … … 708 733 709 734 710 function processIncoming (data)711 712 if (not NUM_PARTITIONS) then713 log("(VistaAlarmPanel::processIncoming) ERROR: NumPartitions not set. Terminate.")714 return false715 elseif (NO_ADDRESSES) then716 log("(VistaAlarmPanel::processIncoming) ERROR: No keypads for one or more partitions. Terminate.")717 return false718 end719 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) == '!') then726 return processExMessage(data)727 end728 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]) then734 log("(VistaAlarmPanel::processIncoming) ERROR: Invalid message.")735 return false736 end737 738 local flags = getStatusFlags(sections[1])739 if (flags.PROGRAMMING_MODE) then740 debug("(VistaAlarmPanel::processIncoming) Programming mode, drop message.")741 return true742 end743 744 -- Get the partitions this message is for.745 local targetPartitions746 if (NUM_PARTITIONS == 1) then747 targetPartitions = {1}748 else749 targetPartitions = getTargetPartitions(sections[3])750 end751 752 --753 -- Process message.754 --755 if (sections[4]:find("^FAULT %d%d")) then -- Zone faulted756 for _, partNo in pairs(targetPartitions) do757 updateArmMode(flags, partNo, false)758 end759 addFaultedZone(tonumber(sections[2], 10))760 elseif (sections[4]:find("^BYPAS %d%d")) then -- Zone bypassed761 for _, partNo in pairs(targetPartitions) do762 updateArmMode(flags, partNo, false)763 end764 removeFaultedZone(tonumber(sections[2], 10))765 elseif (sections[4]:find("^CHECK %d%d")) then -- Zone supervision notice766 log("(VistaAlarmPanel::processIncoming) Zone supervision notice: " .. sections[4])767 else768 for _, partNo in pairs(targetPartitions) do769 -- Check if we have any alarms.770 if (checkForAlarms(flags, sections[2], partNo)) then771 return false772 end773 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] = nil781 else -- Chances are this message is an alarm status report.782 updateArmMode(flags, partNo, true)783 end784 end785 end786 787 return true788 end789 790 735 -- find a zone by address and channel, and set it's faulted status 791 736 local function updateZoneByAddress(address, channel, faulted) … … 808 753 end 809 754 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 -- 761 local 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 811 end 812 813 -- Process "!REL" or "!EXP" messages 814 -- 815 -- Example messages 816 -- !REL:12,01,01 817 -- !EXP:07,01,01 818 -- 819 local 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 841 end 842 843 function 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) 849 end 850 851 function 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 817 895 -- 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 931 end 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. 937 function 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)] 821 945 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 897 949 end 898 950 … … 990 1042 luup.chdev.append(lug_device, rootPtr, "vista_zone_"..zoneNo, "Zone #"..zoneNo.." - "..zoneName, "urn:schemas-micasaverde-com:device:MotionSensor:1", "D_MotionSensor1.xml", "", parameters, false) 991 1043 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 992 1050 g_zones[zoneNo] = { 993 1051 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 995 1054 } 996 1055 end … … 1041 1100 end 1042 1101 1102 -- setup listeners for the vera devices that are mapped to virtual zones 1103 local 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 1120 end 1121 1043 1122 1044 1123 ------------------------------------------------------------------------------- … … 1088 1167 1089 1168 -- 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. 1090 1171 for zoneNo, zone in pairs(g_zones) do 1091 1172 local lastTrip = luup.variable_get(SID.SECURITY_SENSOR, "LastTrip", zone.device) or 0 1092 if (os.difftime(os.time(), lastTrip) >= TTL ) then1173 if (os.difftime(os.time(), lastTrip) >= TTL and zone.type ~= ZONE_TYPE.RF) then 1093 1174 removeFaultedZone(zoneNo) 1094 1175 end 1095 1176 end 1096 1177 1178 -- Initialize virtual zones 1179 initVirtualZones() 1180 1097 1181 -- Start the TTL timer. 1098 1182 ttlCountdownTimer() -
TabularUnified trunk/S_VistaAlarmPanel1.xml ¶
r40 r66 49 49 <name>CheckConnectionTimer</name> 50 50 <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> 51 59 </stateVariable> 52 60 </serviceStateTable>
Note: See TracChangeset
for help on using the changeset viewer.