<?xml version="1.0" encoding="utf-8"?>
<Templates format="2" protocolVersion="83">
  <Template id="3bbb4c9a-8c1a-441c-a1e8-5d1ea53a3616" revision="1.0">
    <SuggestedCCUParameters/>
    <RevisionHistory>
      <Revision id="1.0">Initial version</Revision>
    </RevisionHistory>
    <Name>SONOFF SNZB-02LD Waterproof Temperature Sensor with Probe (Zigbee2Tasmota)</Name>
    <CcuModel>PacketParserCCU</CcuModel>
    <Producer>SONOFF</Producer>
    <Model>SNZB-02LD</Model>
    <Description>Battery-powered Zigbee 3.0 temperature sensor with a waterproof probe (-40 to 115 °C), read through a Tasmota Zigbee gateway (Zigbee2Tasmota) over MQTT.</Description>
    <Category>5</Category>
    <ImportParameters>
      <Parameter>
        <Id>IpAddress</Id>
        <Name>Mqtt Broker ${ipAddress}</Name>
        <Value>192.168.0.1</Value>
      </Parameter>
      <Parameter>
        <Id>Port</Id>
        <Name>Mqtt Broker port</Name>
        <Value>1883</Value>
      </Parameter>
      <Parameter>
        <Id>GWname</Id>
        <Name>Gateway topic (Tasmota -&gt; Configuration -&gt; MQTT -&gt; Topic)</Name>
        <Value>tasmota_XXXXX</Value>
      </Parameter>
      <Parameter>
        <Id>deviceId</Id>
        <Name>Device short address (e.g. 0x2916)</Name>
        <Value>0xFFFF</Value>
      </Parameter>
    </ImportParameters>
    <Module>
      <Name>SONOFF SNZB-02LD Waterproof Temperature Sensor with Probe (Zigbee2Tasmota)</Name>
      <Id>-1</Id>
      <Model>PacketParserModuleMqtt</Model>
      <DeviceProperties>
        <ReadScriptPacketParser>MQTTPUBLISH(&quot;cmnd/&quot; + GWname + &quot;/ZbInfo&quot;, ID)</ReadScriptPacketParser>
        <InternalPollInterval>3600000</InternalPollInterval>
        <CustomVariables>[{&quot;Name&quot;:&quot;GWname&quot;,&quot;ValueTypeId&quot;:2,&quot;NumericValue&quot;:0,&quot;StringValue&quot;:&quot;$[GWname]&quot;},{&quot;Name&quot;:&quot;ID&quot;,&quot;ValueTypeId&quot;:2,&quot;NumericValue&quot;:0,&quot;StringValue&quot;:&quot;$[deviceId]&quot;},{&quot;Name&quot;:&quot;BatteryVoltage&quot;,&quot;ValueTypeId&quot;:0,&quot;NumericValue&quot;:0},{&quot;Name&quot;:&quot;BatteryPercentage&quot;,&quot;ValueTypeId&quot;:0,&quot;NumericValue&quot;:0},{&quot;Name&quot;:&quot;LastSeenEpoch&quot;,&quot;ValueTypeId&quot;:0,&quot;NumericValue&quot;:0}]</CustomVariables>
        <ServiceAttributesScriptsPacketParser>[{&quot;AttributeDefinition&quot;:{&quot;Name&quot;:&quot;IEEE Address&quot;},&quot;ReadFormula&quot;:&quot;MQTTPUBLISH(\&quot;cmnd/\&quot; + GWname + \&quot;/ZbInfo\&quot;, ID)&quot;},{&quot;AttributeDefinition&quot;:{&quot;Name&quot;:&quot;Model ID&quot;}},{&quot;AttributeDefinition&quot;:{&quot;Name&quot;:&quot;Manufacturer&quot;}},{&quot;AttributeDefinition&quot;:{&quot;Name&quot;:&quot;Endpoints&quot;}},{&quot;AttributeDefinition&quot;:{&quot;Name&quot;:&quot;Config&quot;}},{&quot;AttributeDefinition&quot;:{&quot;Name&quot;:&quot;Link Quality&quot;}},{&quot;AttributeDefinition&quot;:{&quot;Name&quot;:&quot;Battery Voltage&quot;},&quot;ReadFormula&quot;:&quot;BatteryVoltage + \&quot; V\&quot;&quot;},{&quot;AttributeDefinition&quot;:{&quot;Name&quot;:&quot;Battery Percentage&quot;},&quot;ReadFormula&quot;:&quot;BatteryPercentage + \&quot;%\&quot;&quot;}]</ServiceAttributesScriptsPacketParser>
        <ListenerScriptPacketParser>if INDEXOF(RECEIVEDMSG.Topic, &quot;SENSOR&quot;) &gt; 0
    # Battery values arrive in their own ZbReceived message, cluster 0x0001
    var ZbReceived := PARSEJSON(RECEIVEDMSG.Payload, &quot;ZbReceived.&quot; + ID, true);
    if !ISNULL(ZbReceived)
        var bat := PARSEJSON(ZbReceived, &quot;BatteryPercentage&quot;, true);
        if(!ISNULL(bat), BatteryPercentage := bat);
        var volt := PARSEJSON(ZbReceived, &quot;BatteryVoltage&quot;, true);
        if(!ISNULL(volt), BatteryVoltage := volt);
        # Every message of the sensor proves it is alive, ZbInfo comes only once per hour
        DATETIME received := NOW();
        LastSeenEpoch := received.UNIXTIME;
    end

    # ZbInfo answer to the hourly request of the module read script
    var deviceInfo := PARSEJSON(RECEIVEDMSG.Payload, &quot;ZbInfo.&quot; + ID, true);
    if !ISNULL(deviceInfo)
        var seen := PARSEJSON(deviceInfo, &quot;LastSeenEpoch&quot;, true);
        if(!ISNULL(seen), LastSeenEpoch := seen);
        var infoBat := PARSEJSON(deviceInfo, &quot;BatteryPercentage&quot;, true);
        if(!ISNULL(infoBat), BatteryPercentage := infoBat);
        var infoVolt := PARSEJSON(deviceInfo, &quot;BatteryVoltage&quot;, true);
        if(!ISNULL(infoVolt), BatteryVoltage := infoVolt);
        COMPLETESERVICEATTRIBUTE(&quot;IEEE Address&quot;, PARSEJSON(deviceInfo, &quot;IEEEAddr&quot;));
        COMPLETESERVICEATTRIBUTE(&quot;Model ID&quot;, PARSEJSON(deviceInfo, &quot;ModelId&quot;));
        COMPLETESERVICEATTRIBUTE(&quot;Manufacturer&quot;, PARSEJSON(deviceInfo, &quot;Manufacturer&quot;));
        COMPLETESERVICEATTRIBUTE(&quot;Endpoints&quot;, PARSEJSON(deviceInfo, &quot;Endpoints&quot;));
        COMPLETESERVICEATTRIBUTE(&quot;Config&quot;, PARSEJSON(deviceInfo, &quot;Config&quot;));
        var lqi := PARSEJSON(deviceInfo, &quot;LinkQuality&quot;, true);
        if !ISNULL(lqi)
            COMPLETESERVICEATTRIBUTE(&quot;Link Quality&quot;, ROUND(LINEAR(lqi, 0, 0, 254, 100)) + &quot;%&quot;);
        end
    end
end

# 0 = no battery report received yet
if BatteryPercentage &gt; 0 AND BatteryPercentage &lt; 30
    ADDWARNING(1, &quot;Low battery (&lt;30%)&quot;);
end

# The sensor reports at least once per hour; LastSeenEpoch comes from its messages and from ZbInfo
if LastSeenEpoch &gt; 0
    DATETIME now := NOW();
    var secondsSeen := now.UNIXTIME - LastSeenEpoch;
    if secondsSeen &gt; 12 * 60 * 60
        if secondsSeen &gt; 24 * 60 * 60
            ADDERROR(3, &quot;Device is offline &gt; 24H&quot;);
        else
            ADDWARNING(2, &quot;Device is offline &gt; 12H&quot;);
        end
    end
end</ListenerScriptPacketParser>
        <IpAddress>$[IpAddress]</IpAddress>
        <Port>$[Port]</Port>
        <RootTopic>tele/$[GWname]/#</RootTopic>
      </DeviceProperties>
      <Devices>
        <Device>
          <Name>Probe Temperature</Name>
          <Id>-2</Id>
          <Model>PacketParserTemperatureSensor</Model>
          <DeviceProperties>
            <DeviceType>3001</DeviceType>
            <InternalPollInterval>15000</InternalPollInterval>
            <ListenerScriptPacketParser>if INDEXOF(RECEIVEDMSG.Topic, &quot;SENSOR&quot;) &gt; 0
    var temp := PARSEJSON(RECEIVEDMSG.Payload, &quot;ZbReceived.&quot; + ID + &quot;.Temperature&quot;, true);
    if ISNULL(temp)
        # Last known value from the ZbInfo answer, e.g. right after import
        temp := PARSEJSON(RECEIVEDMSG.Payload, &quot;ZbInfo.&quot; + ID + &quot;.Temperature&quot;, true);
    end
    if(!ISNULL(temp), Te := temp);
end</ListenerScriptPacketParser>
          </DeviceProperties>
        </Device>
      </Devices>
    </Module>
  </Template>
</Templates>
