<?xml version="1.0" encoding="utf-8"?>
<Templates format="2" protocolVersion="83">
  <Template id="a3f4e1d2-7b8c-4e6f-9a1b-0c5d2e3f4a5b" revision="1.0">
    <Name>Paradox PAI MQTT</Name>
    <CcuModel>PacketParserCCU</CcuModel>
    <Producer>Paradox</Producer>
    <Model>PAI MQTT</Model>
    <Description>Paradox alarm panel integration via PAI (Paradox Alarm Interface) open-source MQTT bridge. Supports SP, MG and EVO panel families.</Description>
    <Category>4</Category>
    <ImportParameters>
      <Parameter>
        <Id>brokerHost</Id>
        <Name>MQTT broker address (the broker built into TapHome: taphome.mqtt.broker.local)</Name>
        <Value>taphome.mqtt.broker.local</Value>
      </Parameter>
      <Parameter>
        <Id>brokerPort</Id>
        <Name>MQTT broker port</Name>
        <Value>1883</Value>
      </Parameter>
      <Parameter>
        <Id>brokerUsername</Id>
        <Name>MQTT broker username</Name>
        <Value/>
      </Parameter>
      <Parameter>
        <Id>brokerPassword</Id>
        <Name>MQTT broker password</Name>
        <Value/>
      </Parameter>
      <Parameter>
        <Id>baseTopic</Id>
        <Name>PAI base topic (MQTT_BASE_TOPIC)</Name>
        <Value>paradox</Value>
      </Parameter>
      <Parameter>
        <Id>partitionKey</Id>
        <Name>Partition key as PAI publishes it (paradox/states/partitions/&lt;key&gt;, e.g. Area_1)</Name>
        <Value>Area_1</Value>
      </Parameter>
      <Parameter>
        <Id>zone1Key</Id>
        <Name>Zone 1 key as PAI publishes it (paradox/states/zones/&lt;key&gt;, e.g. Front_door)</Name>
        <Value>Zone_1</Value>
      </Parameter>
      <Parameter>
        <Id>zone2Key</Id>
        <Name>Zone 2 key</Name>
        <Value>Zone_2</Value>
      </Parameter>
      <Parameter>
        <Id>zone3Key</Id>
        <Name>Zone 3 key</Name>
        <Value>Zone_3</Value>
      </Parameter>
      <Parameter>
        <Id>zone4Key</Id>
        <Name>Zone 4 key</Name>
        <Value>Zone_4</Value>
      </Parameter>
      <Parameter>
        <Id>pgmKey</Id>
        <Name>PGM output key as PAI publishes it (paradox/states/outputs/&lt;key&gt;)</Name>
        <Value>PGM_1</Value>
      </Parameter>
    </ImportParameters>
    <Module>
      <Name>Paradox PAI MQTT Bridge</Name>
      <Id>-1</Id>
      <Model>PacketParserModuleMqtt</Model>
      <DeviceProperties>
        <IpAddress>$[brokerHost]</IpAddress>
        <Port>$[brokerPort]</Port>
        <User>$[brokerUsername]</User>
        <Password>$[brokerPassword]</Password>
        <RootTopic>$[baseTopic]/#</RootTopic>
        <CustomVariables>[{&quot;Name&quot;:&quot;baseTopic&quot;,&quot;ValueTypeId&quot;:2,&quot;NumericValue&quot;:0,&quot;StringValue&quot;:&quot;$[baseTopic]&quot;},{&quot;Name&quot;:&quot;paiStatus&quot;,&quot;ValueTypeId&quot;:2,&quot;NumericValue&quot;:0,&quot;StringValue&quot;:&quot;&quot;}]</CustomVariables>
        <ListenerScriptPacketParser>IF RECEIVEDMSG.TOPIC = baseTopic + &quot;/interface/availability&quot;
    paiStatus := TOSTRING(RECEIVEDMSG.PAYLOAD);
END
# PAI publishes availability as a retained message: offline = PAI lost the panel or stopped
IF paiStatus = &quot;offline&quot;
    ADDERROR(1, &quot;PAI reports offline - no updates from the alarm panel. Check PAI and its connection to the panel.&quot;);
END</ListenerScriptPacketParser>
      </DeviceProperties>
      <Devices>
        <Device>
          <Name>Partition</Name>
          <Id>-2</Id>
          <Model>PacketParserMultiValueSwitch</Model>
          <DeviceProperties>
            <DeviceType>0</DeviceType>
            <StartupValueSource>ReadFromDevice</StartupValueSource>
            <CustomVariables>[{&quot;Name&quot;:&quot;partitionKey&quot;,&quot;ValueTypeId&quot;:2,&quot;NumericValue&quot;:0,&quot;StringValue&quot;:&quot;$[partitionKey]&quot;}]</CustomVariables>
            <ListenerScriptPacketParser>IF RECEIVEDMSG.TOPIC = baseTopic + &quot;/states/partitions/&quot; + partitionKey + &quot;/current_state&quot;
    VAR state := TOSTRING(RECEIVEDMSG.PAYLOAD);
    # PAI current_state: disarmed, arming (exit delay), armed_away, armed_home (stay), armed_night (sleep), triggered
    IF state = &quot;disarmed&quot;
        Mu := 0;
    ELSEIF state = &quot;armed_away&quot;
        Mu := 1;
    ELSEIF state = &quot;armed_home&quot;
        Mu := 2;
    ELSEIF state = &quot;armed_night&quot;
        Mu := 3;
    ELSEIF state = &quot;arming&quot;
        Mu := 4;
    ELSEIF state = &quot;triggered&quot;
        Mu := 5;
    END
END</ListenerScriptPacketParser>
            <ReadSwitchStateScriptPacketParser>RETURN(Mu);</ReadSwitchStateScriptPacketParser>
            <WriteSwitchStateScriptPacketParser># Only the four commands are sent. Arming and Alarm are states reported by the panel, never commands.
VAR cmd := SWITCH(Mu, 0, &quot;disarm&quot;, 1, &quot;arm&quot;, 2, &quot;arm_stay&quot;, 3, &quot;arm_sleep&quot;, &quot;&quot;);
IF cmd != &quot;&quot;
    MQTTPUBLISH(baseTopic + &quot;/control/partitions/&quot; + partitionKey, cmd);
END</WriteSwitchStateScriptPacketParser>
            <Value0>0</Value0>
            <Value0Index>0</Value0Index>
            <Value0Name>Disarmed</Value0Name>
            <Value0IconId>85</Value0IconId>
            <Value1>1</Value1>
            <Value1Index>1</Value1Index>
            <Value1Name>Armed</Value1Name>
            <Value1IconId>84</Value1IconId>
            <Value2>2</Value2>
            <Value2Index>2</Value2Index>
            <Value2Name>Armed Stay</Value2Name>
            <Value2IconId>37</Value2IconId>
            <Value3>3</Value3>
            <Value3Index>3</Value3Index>
            <Value3Name>Armed Sleep</Value3Name>
            <Value3IconId>47</Value3IconId>
            <Value4>4</Value4>
            <Value4Index>4</Value4Index>
            <Value4Name>Arming</Value4Name>
            <Value4IconId>16</Value4IconId>
            <Value5>5</Value5>
            <Value5Index>5</Value5Index>
            <Value5Name>Alarm</Value5Name>
            <Value5IconId>19</Value5IconId>
            <Value6Index>70</Value6Index>
            <Value7Index>71</Value7Index>
            <Value8Index>72</Value8Index>
            <Value9Index>73</Value9Index>
          </DeviceProperties>
        </Device>
        <Device>
          <Name>Zone 1</Name>
          <Id>-3</Id>
          <Model>PacketParserReedContact</Model>
          <DeviceProperties>
            <DeviceType>0</DeviceType>
            <OnStateIconId>87</OnStateIconId>
            <OffStateIconId>88</OffStateIconId>
            <OnStateName>Open</OnStateName>
            <OffStateName>Closed</OffStateName>
            <CustomVariables>[{&quot;Name&quot;:&quot;zoneKey&quot;,&quot;ValueTypeId&quot;:2,&quot;NumericValue&quot;:0,&quot;StringValue&quot;:&quot;$[zone1Key]&quot;},{&quot;Name&quot;:&quot;zoneTamper&quot;,&quot;ValueTypeId&quot;:1,&quot;NumericValue&quot;:0},{&quot;Name&quot;:&quot;zoneLowBattery&quot;,&quot;ValueTypeId&quot;:1,&quot;NumericValue&quot;:0},{&quot;Name&quot;:&quot;zoneBypassed&quot;,&quot;ValueTypeId&quot;:1,&quot;NumericValue&quot;:0},{&quot;Name&quot;:&quot;zoneAlarm&quot;,&quot;ValueTypeId&quot;:1,&quot;NumericValue&quot;:0}]</CustomVariables>
            <ListenerScriptPacketParser>VAR prefix := baseTopic + &quot;/states/zones/&quot; + zoneKey + &quot;/&quot;;
VAR topic := RECEIVEDMSG.TOPIC;
IF INDEXOF(topic, prefix) = 0
    VAR payload := TOSTRING(RECEIVEDMSG.PAYLOAD);
    VAR isOn := IF(payload = &quot;True&quot; OR payload = &quot;1&quot;, 1, 0);
    IF topic = prefix + &quot;open&quot;
        Rc := isOn;
    ELSEIF topic = prefix + &quot;tamper&quot;
        zoneTamper := isOn;
    ELSEIF topic = prefix + &quot;rf_low_battery_trouble&quot;
        zoneLowBattery := isOn;
    ELSEIF topic = prefix + &quot;bypassed&quot;
        zoneBypassed := isOn;
    ELSEIF topic = prefix + &quot;alarm&quot;
        zoneAlarm := isOn;
    END
END
# Warnings are rebuilt from the stored zone states on every message, so they stay until the panel clears them
IF zoneAlarm = 1
    ADDWARNING(1, &quot;Zone in alarm&quot;);
END
IF zoneTamper = 1
    ADDWARNING(2, &quot;Zone tampered&quot;);
END
IF zoneLowBattery = 1
    ADDWARNING(3, &quot;Wireless zone: low battery&quot;);
END
IF zoneBypassed = 1
    ADDWARNING(4, &quot;Zone bypassed&quot;);
END</ListenerScriptPacketParser>
            <ReadStateScriptPacketParser>RETURN(Rc);</ReadStateScriptPacketParser>
          </DeviceProperties>
        </Device>
        <Device>
          <Name>Zone 2</Name>
          <Id>-4</Id>
          <Model>PacketParserReedContact</Model>
          <DeviceProperties>
            <DeviceType>0</DeviceType>
            <OnStateIconId>87</OnStateIconId>
            <OffStateIconId>88</OffStateIconId>
            <OnStateName>Open</OnStateName>
            <OffStateName>Closed</OffStateName>
            <CustomVariables>[{&quot;Name&quot;:&quot;zoneKey&quot;,&quot;ValueTypeId&quot;:2,&quot;NumericValue&quot;:0,&quot;StringValue&quot;:&quot;$[zone2Key]&quot;},{&quot;Name&quot;:&quot;zoneTamper&quot;,&quot;ValueTypeId&quot;:1,&quot;NumericValue&quot;:0},{&quot;Name&quot;:&quot;zoneLowBattery&quot;,&quot;ValueTypeId&quot;:1,&quot;NumericValue&quot;:0},{&quot;Name&quot;:&quot;zoneBypassed&quot;,&quot;ValueTypeId&quot;:1,&quot;NumericValue&quot;:0},{&quot;Name&quot;:&quot;zoneAlarm&quot;,&quot;ValueTypeId&quot;:1,&quot;NumericValue&quot;:0}]</CustomVariables>
            <ListenerScriptPacketParser>VAR prefix := baseTopic + &quot;/states/zones/&quot; + zoneKey + &quot;/&quot;;
VAR topic := RECEIVEDMSG.TOPIC;
IF INDEXOF(topic, prefix) = 0
    VAR payload := TOSTRING(RECEIVEDMSG.PAYLOAD);
    VAR isOn := IF(payload = &quot;True&quot; OR payload = &quot;1&quot;, 1, 0);
    IF topic = prefix + &quot;open&quot;
        Rc := isOn;
    ELSEIF topic = prefix + &quot;tamper&quot;
        zoneTamper := isOn;
    ELSEIF topic = prefix + &quot;rf_low_battery_trouble&quot;
        zoneLowBattery := isOn;
    ELSEIF topic = prefix + &quot;bypassed&quot;
        zoneBypassed := isOn;
    ELSEIF topic = prefix + &quot;alarm&quot;
        zoneAlarm := isOn;
    END
END
# Warnings are rebuilt from the stored zone states on every message, so they stay until the panel clears them
IF zoneAlarm = 1
    ADDWARNING(1, &quot;Zone in alarm&quot;);
END
IF zoneTamper = 1
    ADDWARNING(2, &quot;Zone tampered&quot;);
END
IF zoneLowBattery = 1
    ADDWARNING(3, &quot;Wireless zone: low battery&quot;);
END
IF zoneBypassed = 1
    ADDWARNING(4, &quot;Zone bypassed&quot;);
END</ListenerScriptPacketParser>
            <ReadStateScriptPacketParser>RETURN(Rc);</ReadStateScriptPacketParser>
          </DeviceProperties>
        </Device>
        <Device>
          <Name>Zone 3</Name>
          <Id>-5</Id>
          <Model>PacketParserReedContact</Model>
          <DeviceProperties>
            <DeviceType>0</DeviceType>
            <OnStateIconId>87</OnStateIconId>
            <OffStateIconId>88</OffStateIconId>
            <OnStateName>Open</OnStateName>
            <OffStateName>Closed</OffStateName>
            <CustomVariables>[{&quot;Name&quot;:&quot;zoneKey&quot;,&quot;ValueTypeId&quot;:2,&quot;NumericValue&quot;:0,&quot;StringValue&quot;:&quot;$[zone3Key]&quot;},{&quot;Name&quot;:&quot;zoneTamper&quot;,&quot;ValueTypeId&quot;:1,&quot;NumericValue&quot;:0},{&quot;Name&quot;:&quot;zoneLowBattery&quot;,&quot;ValueTypeId&quot;:1,&quot;NumericValue&quot;:0},{&quot;Name&quot;:&quot;zoneBypassed&quot;,&quot;ValueTypeId&quot;:1,&quot;NumericValue&quot;:0},{&quot;Name&quot;:&quot;zoneAlarm&quot;,&quot;ValueTypeId&quot;:1,&quot;NumericValue&quot;:0}]</CustomVariables>
            <ListenerScriptPacketParser>VAR prefix := baseTopic + &quot;/states/zones/&quot; + zoneKey + &quot;/&quot;;
VAR topic := RECEIVEDMSG.TOPIC;
IF INDEXOF(topic, prefix) = 0
    VAR payload := TOSTRING(RECEIVEDMSG.PAYLOAD);
    VAR isOn := IF(payload = &quot;True&quot; OR payload = &quot;1&quot;, 1, 0);
    IF topic = prefix + &quot;open&quot;
        Rc := isOn;
    ELSEIF topic = prefix + &quot;tamper&quot;
        zoneTamper := isOn;
    ELSEIF topic = prefix + &quot;rf_low_battery_trouble&quot;
        zoneLowBattery := isOn;
    ELSEIF topic = prefix + &quot;bypassed&quot;
        zoneBypassed := isOn;
    ELSEIF topic = prefix + &quot;alarm&quot;
        zoneAlarm := isOn;
    END
END
# Warnings are rebuilt from the stored zone states on every message, so they stay until the panel clears them
IF zoneAlarm = 1
    ADDWARNING(1, &quot;Zone in alarm&quot;);
END
IF zoneTamper = 1
    ADDWARNING(2, &quot;Zone tampered&quot;);
END
IF zoneLowBattery = 1
    ADDWARNING(3, &quot;Wireless zone: low battery&quot;);
END
IF zoneBypassed = 1
    ADDWARNING(4, &quot;Zone bypassed&quot;);
END</ListenerScriptPacketParser>
            <ReadStateScriptPacketParser>RETURN(Rc);</ReadStateScriptPacketParser>
          </DeviceProperties>
        </Device>
        <Device>
          <Name>Zone 4</Name>
          <Id>-6</Id>
          <Model>PacketParserReedContact</Model>
          <DeviceProperties>
            <DeviceType>0</DeviceType>
            <OnStateIconId>87</OnStateIconId>
            <OffStateIconId>88</OffStateIconId>
            <OnStateName>Open</OnStateName>
            <OffStateName>Closed</OffStateName>
            <CustomVariables>[{&quot;Name&quot;:&quot;zoneKey&quot;,&quot;ValueTypeId&quot;:2,&quot;NumericValue&quot;:0,&quot;StringValue&quot;:&quot;$[zone4Key]&quot;},{&quot;Name&quot;:&quot;zoneTamper&quot;,&quot;ValueTypeId&quot;:1,&quot;NumericValue&quot;:0},{&quot;Name&quot;:&quot;zoneLowBattery&quot;,&quot;ValueTypeId&quot;:1,&quot;NumericValue&quot;:0},{&quot;Name&quot;:&quot;zoneBypassed&quot;,&quot;ValueTypeId&quot;:1,&quot;NumericValue&quot;:0},{&quot;Name&quot;:&quot;zoneAlarm&quot;,&quot;ValueTypeId&quot;:1,&quot;NumericValue&quot;:0}]</CustomVariables>
            <ListenerScriptPacketParser>VAR prefix := baseTopic + &quot;/states/zones/&quot; + zoneKey + &quot;/&quot;;
VAR topic := RECEIVEDMSG.TOPIC;
IF INDEXOF(topic, prefix) = 0
    VAR payload := TOSTRING(RECEIVEDMSG.PAYLOAD);
    VAR isOn := IF(payload = &quot;True&quot; OR payload = &quot;1&quot;, 1, 0);
    IF topic = prefix + &quot;open&quot;
        Rc := isOn;
    ELSEIF topic = prefix + &quot;tamper&quot;
        zoneTamper := isOn;
    ELSEIF topic = prefix + &quot;rf_low_battery_trouble&quot;
        zoneLowBattery := isOn;
    ELSEIF topic = prefix + &quot;bypassed&quot;
        zoneBypassed := isOn;
    ELSEIF topic = prefix + &quot;alarm&quot;
        zoneAlarm := isOn;
    END
END
# Warnings are rebuilt from the stored zone states on every message, so they stay until the panel clears them
IF zoneAlarm = 1
    ADDWARNING(1, &quot;Zone in alarm&quot;);
END
IF zoneTamper = 1
    ADDWARNING(2, &quot;Zone tampered&quot;);
END
IF zoneLowBattery = 1
    ADDWARNING(3, &quot;Wireless zone: low battery&quot;);
END
IF zoneBypassed = 1
    ADDWARNING(4, &quot;Zone bypassed&quot;);
END</ListenerScriptPacketParser>
            <ReadStateScriptPacketParser>RETURN(Rc);</ReadStateScriptPacketParser>
          </DeviceProperties>
        </Device>
        <Device>
          <Name>PGM output</Name>
          <Id>-7</Id>
          <Model>PacketParserSwitch</Model>
          <DeviceProperties>
            <DeviceType>0</DeviceType>
            <StartupValueSource>ReadFromDevice</StartupValueSource>
            <OnStateIconId>60</OnStateIconId>
            <OffStateIconId>61</OffStateIconId>
            <OnStateName>${general_on}</OnStateName>
            <OffStateName>${general_off}</OffStateName>
            <CustomVariables>[{&quot;Name&quot;:&quot;outputKey&quot;,&quot;ValueTypeId&quot;:2,&quot;NumericValue&quot;:0,&quot;StringValue&quot;:&quot;$[pgmKey]&quot;}]</CustomVariables>
            <ListenerScriptPacketParser>IF RECEIVEDMSG.TOPIC = baseTopic + &quot;/states/outputs/&quot; + outputKey + &quot;/on&quot;
    VAR payload := TOSTRING(RECEIVEDMSG.PAYLOAD);
    St := IF(payload = &quot;True&quot; OR payload = &quot;1&quot;, 1, 0);
END</ListenerScriptPacketParser>
            <ReadSwitchStateScriptPacketParser>RETURN(St);</ReadSwitchStateScriptPacketParser>
            <WriteSwitchStateScriptPacketParser>MQTTPUBLISH(baseTopic + &quot;/control/outputs/&quot; + outputKey, IF(St = 1, &quot;on&quot;, &quot;off&quot;));</WriteSwitchStateScriptPacketParser>
          </DeviceProperties>
        </Device>
      </Devices>
    </Module>
  </Template>
</Templates>
