diff --git a/fhem/CHANGED b/fhem/CHANGED index 5d0e97aa6..32dc1dfcc 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,13 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: 14_SD_WS.pm: new sensor Bresser 7-in-1 Comfort Wetter Center + new set command to allow battery replacement + new sensor SM60020 multi purpose environment sensor + new sensor TFA 30.3251.10 + new senosr TFA 35.1077.54.S2 with 30.3151 + temp/hum, 30.3152 (rain) and 30.3153 (wind) + new sensor TM40, wireless Grill-, Meat-, + Roasting-Thermometer with 4 temp sensors - feature: 00_SIGNALduino.pm: Updated to vversion 3.5.4 update commandref make SIGNALduino_IdList more robust diff --git a/fhem/FHEM/14_SD_WS.pm b/fhem/FHEM/14_SD_WS.pm index 873571a3d..bf6975744 100644 --- a/fhem/FHEM/14_SD_WS.pm +++ b/fhem/FHEM/14_SD_WS.pm @@ -40,14 +40,21 @@ # 05.01.2022 Protokoll 108: neuer Sensor Fody E42 # 08.01.2022 neues Protokoll 107: Soil Moisture Sensor Fine Offset WH51, ECOWITT WH51, MISOL/1, Froggit DP100 # 14.01.2022 neues Protokoll 116: Thunder and lightning sensor Fine Offset WH57, aka Froggit DP60, aka Ambient Weather WH31L +# 29.01.2022 neues Protokoll 117: Bresser 7-in-1 Comfort Wetter Center +# 04.02.2022 neu: set replaceBatteryForSec (Ident ersetzen bei Batteriewechsel Sensor) +# 12.03.2022 Protokoll 115: neue Sensoren SM60020 Boden-/Erd-Sensor für Feuchte- und Temperatur, Innensensor für Feuchte- und Temperatur +# 11.04.2022 Protokoll 85: neuer Sensor Windmesser TFA 30.3251.10 mit Windrichtung, Pruefung CRC8 eingearbeitet +# 23.05.2022 neues Protokoll 120: Wetterstation TFA 35.1077.54.S2 mit 30.3151 (Thermo/Hygro-Sender), 30.3152 (Regenmesser), 30.3153 (Windmesser) +# 11.06.2022 neues Protokoll 122: TM40, Wireless Grill-, Meat-, Roasting-Thermometer with 4 Temperature Sensors package main; -#use version 0.77; our $VERSION = version->declare('v3.4.3'); +#use version 0.77; our $VERSION = version->declare('v3.5.4'); use strict; use warnings; use Carp qw(carp); +use FHEM::Meta; # Forward declarations sub SD_WS_LFSR_digest8_reflect; @@ -59,11 +66,11 @@ sub SD_WS_WH2SHIFT; sub SD_WS_Initialize { my $hash = shift // return; $hash->{Match} = '^W\d+x{0,1}#.*'; - $hash->{DefFn} = "SD_WS_Define"; - $hash->{UndefFn} = "SD_WS_Undef"; - $hash->{ParseFn} = "SD_WS_Parse"; + $hash->{DefFn} = \&SD_WS_Define; + $hash->{UndefFn} = \&SD_WS_Undef;$hash->{SetFn} = \&SD_WS_Set; + $hash->{ParseFn} = \&SD_WS_Parse; $hash->{AttrList} = "do_not_notify:1,0 ignore:0,1 showtime:1,0 " . - "model:E0001PA,S522,TX-EZ6,other " . + "model:E0001PA,S522,TFA_30.3251.10,TX-EZ6,other " . "max-deviation-temp:1,2,3,4,5,6,7,8,9,10,15,20,25,30,35,40,45,50 ". "max-deviation-hum:1,2,3,4,5,6,7,8,9,10,15,20,25,30,35,40,45,50 ". "$readingFnAttributes "; @@ -91,12 +98,15 @@ sub SD_WS_Initialize { 'SD_WS_107_H.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => q{}, autocreateThreshold => '2:180'}, 'SD_WS_108.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => 'temp4:Temp,', autocreateThreshold => '5:120'}, 'SD_WS_110_TR.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => 'temp4:Temp,', autocreateThreshold => '3:180'}, - 'SD_WS_111_TL.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => 'temp4:Temp,', autocreateThreshold => '3:600'}, + 'SD_WS_111_TL.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => 'temp4:Temp,', autocreateThreshold => '3:600'}, 'SD_WS_113_T.*' => { ATTR => 'event-min-interval:.*:60 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => 'temp4:Temp,', autocreateThreshold => '10:180'}, 'SD_WS_115.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => 'temp4hum4:Temp/Hum,', autocreateThreshold => '5:120'}, 'SD_WS_116.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => q{}, autocreateThreshold => '2:180'}, + 'SD_WS_117.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => 'temp4hum4:Temp/Hum,', autocreateThreshold => '5:120'}, + 'SD_WS_120.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => 'temp4hum4:Temp/Hum,', autocreateThreshold => '2:180'}, + 'SD_WS_122_T.*' => { ATTR => 'event-min-interval:.*:60 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => 'temp4:Temp,', autocreateThreshold => '10:180'}, }; - return; + return FHEM::Meta::InitMod( __FILE__, $hash ); } ############################# @@ -123,6 +133,52 @@ sub SD_WS_Undef { my ($hash, $name) = @_; carp "SD_WS_Undef, too few arguments ($hash, $name)" if @_ < 2; delete($modules{SD_WS}{defptr}{$hash->{CODE}}) if(defined($hash->{CODE}) && defined($modules{SD_WS}{defptr}{$hash->{CODE}})); + RemoveInternalTimer($hash); + return; +} + +############################# +sub SD_WS_Set { + my $hash = shift // carp 'device hash needs to be first parameter'; + my $name = shift // carp 'name of the device must be second parameter'; + my $cmd = shift // carp 'Specify the command as third prarameter'; + my @args = @_; + + my $ret = undef; + my $LastInputDev = $hash->{LASTInputDev}; + return $ret if (!defined $LastInputDev); + + my $rhash = $defs{$LastInputDev}; + my $rlongids = AttrVal($rhash->{NAME},'longids',0); + my $def = $hash->{DEF}; + my @array = split(/_/,$def); + my $model = $array[0] . '_' . $array[1] . '_' . $array[2]; + if (($rlongids ne "0") && ($rlongids eq "1" || $rlongids eq "ALL" || ($rlongids =~ m/$model/))) { + if ($cmd eq '?') { + $ret = 'replaceBatteryForSec'; # return setList + } elsif ($cmd eq 'replaceBatteryForSec') { + return "$name: No or too small value specified for the replaceBatteryForSec command" if (scalar(@args) < 1 || $args[0] < 1); + my @found = devspec2array("TYPE=SD_WS:FILTER=i:replaceBattery>0"); + return "$name: Only one sensor at a time, replaceBatteryForSec is already activated at @found" if (scalar(@found) > 0); + my $repBatForSec = $args[0]; + $hash->{replaceBattery} = $repBatForSec; + InternalTimer(gettimeofday() + $repBatForSec, \&SD_WS_TimerRemoveReplaceBattery, $hash, 0); + Log3 $name, 3, "$name: SD_WS_Set, wait $repBatForSec seconds for a new identity of the sensor"; + } else { + $ret = "$name: Unknown argument $cmd, choose replaceBatteryForSec"; + } + + } + return $ret; +} + +############################# +sub SD_WS_TimerRemoveReplaceBattery { + my $hash = shift; + my $name = $hash->{NAME}; + delete($hash->{replaceBattery}); + RemoveInternalTimer($hash); + Log3 $name, 3, "$name: SD_WS_TimerRemoveReplaceBattery, no sensor with unknown identity received"; return; } @@ -151,11 +207,13 @@ sub SD_WS_Parse { my $rawTemp; my $temp; my $temp2; + my $temp3; + my $temp4; my $hum; my $windspeed; my $winddir; my $winddirtxt; - my @winddirtxtar=('N','NNE','NE','ENE','E','ESE','SE','SSE','S','SSW','SW','WSW','W','WNW','NW','NNW'); + my @winddirtxtar=('N','NNE','NE','ENE','E','ESE','SE','SSE','S','SSW','SW','WSW','W','WNW','NW','NNW','N'); my $windgust; my $trend; my $trendTemp; @@ -168,10 +226,14 @@ sub SD_WS_Parse { my $distance; my $uv; my $adc; + my $brightness; + my @moisture_map=(0, 7, 13, 20, 27, 33, 40, 47, 53, 60, 67, 73, 80, 87, 93, 99); # ID 115 my $count; my $identified; + my $transmitter; + my $dcf; - my %decodingSubs = ( + my %decodingSubs = ( 50 => # Protocol 50 # FF550545FF9E # FF550541FF9A @@ -584,12 +646,13 @@ sub SD_WS_Parse { } , 85 => { - # Protokollbeschreibung: Kombisensor TFA 30.3222.02 fuer Wetterstation TFA 35.1140.01 - # ----------------------------------------------------------------------------------- + # Protokollbeschreibung: Kombisensor TFA 30.3222.02 fuer Wetterstation TFA 35.1140.01, Windmesser TFA 30.3251.10 + # -------------------------------------------------------------------------------------------------------------- # 0 4 | 8 12 | 16 20 | 24 28 | 32 36 | 40 44 | 48 52 | 56 60 | 64 # 0000 1001 | 0001 0110 | 0001 0000 | 0000 0111 | 0100 1001 | 0100 0000 | 0100 1001 | 0100 1001 | 1 - # ???? iiii | iiii iiii | iiii iiii | b??? ??yy | tttt tttt | tttt ???? | hhhh hhhh | ???? ???? | ? message 1 - # ???? iiii | iiii iiii | iiii iiii | b?cc ??yy | wwww wwww | wwww ???? | 0000 0000 | ???? ???? | ? message 2 + # 0000 iiii | iiii iiii | iiii iiii | b??? ??yy | tttt tttt | tttt 0000 | hhhh hhhh | CCCC CCCC | ? message 1 TFA 30.3222.02 + # 0000 iiii | iiii iiii | iiii iiii | b?cc ??yy | wwww wwww | wwww 0000 | 0000 0000 | CCCC CCCC | ? message 2 TFA 30.3222.02 + # 0000 iiii | iiii iiii | iiii iiii | b?cc ??yy | wwww wwww | wwww dddd | dddd dddd | CCCC CCCC | ? message 2 TFA 30.3251.10 # i: 20 bit random id (changes on power-loss) # b: 1 bit battery indicator (0=>OK, 1=>LOW) # c: 2 bit channel valid channels are (always 00 stands for channel 1) @@ -597,14 +660,14 @@ sub SD_WS_Parse { # t: 12 bit unsigned temperature, offset 500, scaled by 10 - if message 1 # h: 8 bit relative humidity percentage - if message 1 # w: 12 bit unsigned windspeed, scaled by 10 - if message 2 + # d: 12 bit unsigned winddirection - only TFA 30.3251.10 + # C: 8 bit CRC of the preceding 7 bytes (Polynomial 0x31, Initial value 0x00, Input not reflected, Result not reflected) # ?: unknown # The sensor sends at intervals of about 30 seconds - - sensortype => 'TFA 30.3222.02', + sensortype => 'TFA 30.3222.02, TFA 30.3251.10, LaCrosse TX141W', model => 'SD_WS_85_THW', prematch => sub {my $msg = shift; return 1 if ($msg =~ /^[0-9A-F]{16}/); }, # min 16 nibbles - crcok => sub {return 1;}, # crc test method is so far unknown - id => sub {my (undef,$bitData) = @_; return substr($rawData,1,5); }, # 0952CF012B1021DF0 + id => sub {my ($rawData,undef) = @_; return substr($rawData,1,5); }, # 0952CF012B1021DF0 bat => sub {my (undef,$bitData) = @_; return substr($bitData,24,1) eq "0" ? "ok" : "low";}, channel => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,26,27) + 1 ); }, # unknown temp => sub {my (undef,$bitData) = @_; @@ -628,6 +691,34 @@ sub SD_WS_Parse { return; } }, + winddir => sub {my (undef,$bitData) = @_; + if (substr($bitData,30,2) eq "10") { # message 2 winddirection + $winddir = SD_WS_binaryToNumber($bitData,44,55); + return ($winddir * 1, $winddirtxtar[round(($winddir / 22.5),0)]); + } else { + return; + } + }, + crcok => sub {my ($rawData,undef) = @_; + my $rc = eval { + require Digest::CRC; + Digest::CRC->import(); + 1; + }; + if ($rc) { + my $datacheck1 = pack( 'H*', substr($rawData,0,14) ); + my $crcmein1 = Digest::CRC->new(width => 8, poly => 0x31); + my $rr3 = $crcmein1->add($datacheck1)->hexdigest; + if (hex($rr3) != hex(substr($rawData,14,2))) { + Log3 $name, 3, "$name: SD_WS_85 Parse msg $rawData - ERROR CRC8"; + return 0; + } + } else { + Log3 $name, 1, "$name: SD_WS_85 Parse msg $rawData - ERROR CRC not load, please install modul Digest::CRC"; + return 0; + } + return 1; + } } , 89 => { @@ -986,64 +1077,92 @@ sub SD_WS_Parse { # 0123456789012345678901234567890123456789 # ---------------------------------------- # 3DA820B00C1618FFFFFF1808152294FFF01E0000 Msg 1, 40 Nibble from SIGNALduino, T: 15.2 H: 94 G:0 W: 0 D:180 - # CCCCIIIIIIIIFFGGGWWWDDD?TTT?HH????SS Msg 1, 36 Nibble - # CCCCIIIIIIIIFFGGGWWWDDD?ffRRRRVVV?SS Msg 2, 36 Nibble + # CCCCIIIIIIIIFFGGGWWWDDD?TTTFHHVVVXSS Msg 1, 36 Nibble, wind, temperature, humidity, uv + # CCCCIIIIIIIIFFGGGWWWDDD?RRRRRR???XSS Msg 2, 36 Nibble, wind, rain # C = CRC16 # I = station ID - # F = flags, 4 bit (1: weather station, 2: indoor?, 4: soil probe), 1 bit battery (1=ok, 0=low), 3 bit channel + # F = flags, 8 bit (nibble 12 1: weather station, 2: indoor, 4: soil probe, nibble 13 1 bit battery change, 3 bit channel) # G = wind gust in 1/10 m/s, inverted, BCD coded, GGG = FE6 =~ 019 => 1.9 m/s. # W = wind speed in 1/10 m/s, inverted, BCD coded, LSB first nibble, MSB last two nibble, WWW = EFE =~ 101 => 1.1 m/s. # D = wind direction in grad, BCD coded, DDD = 158 => 158 ° - # ? = unknown, always 0x8 - # T = temperature in 1/10 °C, only if byte 12 ne 0xFF, BCD coded, TTT = 312 => 31.2 °C - # ? = unknown - # H = humidity in percent, only if byte 12 ne 0xFF, BCD coded, HH = 23 => 23 % - # R = rain counter, only if byte 12 eq 0xFF, inverted, BCD coded - # V = uv, only if byte 12 eq 0xFF and byte 15/16 not 0xFF01, inverted, BCD coded - # ? = unknown + # ? = unknown, 0x0, 0x8 or 0xE + # T = temperature in 1/10 °C, TTT = 312 => 31.2 °C + # F = flags, 4 bit - bit 3 temperature (0=positive, 1=negative), bit 2 ?, bit 1 battery (1=ok, 0=low), bit 0 ? + # H = humidity in percent, BCD coded, HH = 23 => 23 % + # R = rain counter, inverted, BCD coded + # V = uv, inverted, BCD coded + # X = message type, 0 = temp, hum, wind, uv, 1 = wind, rain # S = checksum (sum over byte 2 - 17 must be 255) sensortype => 'Bresser_6in1, new Bresser_5in1', model => 'SD_WS_115', prematch => sub { return 1; }, # no precheck known id => sub {my ($rawData,undef) = @_; return substr($rawData,4,8); }, - bat => sub {my (undef,$bitData) = @_; return substr($bitData,52,1) eq '1' ? 'ok' : 'low';}, - channel => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,53,55));}, + bat => sub {my ($rawData,$bitData) = @_; + return if (substr($rawData,12,1) eq '1' && substr($rawData,33,1) eq '1'); # not by weather station & rain + return substr($bitData,110,1) eq '1' ? 'ok' : 'low'; + }, + batChange => sub {my (undef,$bitData) = @_; return substr($bitData,52,1);}, + channel => sub {my ($rawData,$bitData) = @_; + $channel = ''; + $channel = substr($rawData,12,1) if (substr($rawData,12,1) ne '1'); # not weather station + return ($channel . SD_WS_binaryToNumber($bitData,53,55)); + }, windgust => sub {my ($rawData,undef) = @_; + return if (substr($rawData,12,1) ne '1'); # only weather station $windgust = substr($rawData,14,3); $windgust =~ tr/0123456789ABCDEF/FEDCBA9876543210/; + # uncoverable branch true return if ($windgust !~ m/^\d+$/xms); return $windgust * 0.1; }, windspeed => sub {my ($rawData,undef) = @_; + return if (substr($rawData,12,1) ne '1'); # only weather station $windspeed = substr($rawData,18,2) . substr($rawData,17,1); $windspeed =~ tr/0123456789ABCDEF/FEDCBA9876543210/; + # uncoverable branch true return if ($windspeed !~ m/^\d+$/xms); return $windspeed * 0.1; }, winddir => sub {my ($rawData,undef) = @_; + return if (substr($rawData,12,1) ne '1' || substr($rawData,20,3) !~ m/^\d+$/xms); # only weather station $winddir = substr($rawData,20,3); - return if ($winddir !~ m/^\d+$/xms); return ($winddir * 1, $winddirtxtar[round(($winddir / 22.5),0)]); }, - temp => sub {my ($rawData,undef) = @_; - return if (substr($rawData,24,2) eq 'FF'); - $rawTemp = (substr($rawData,24,1) . substr($rawData,25,1) . substr($rawData,26,1)) * 0.1; - if ($rawTemp > 60) {$rawTemp -= 100}; - return $rawTemp; + temp => sub {my ($rawData,$bitData) = @_; + # uncoverable condition right + return if ((substr($rawData,12,1) eq '1' && substr($rawData,33,1) eq '1') || substr($rawData,24,3) !~ m/^\d+$/xms); # not by weather station & rain + $rawTemp = substr($rawData,24,3) * 0.1; + if (substr($bitData,108,1) eq '1') { + if ($rawTemp > 60) { + $rawTemp -= 100; # Bresser 6in1 + } else { + $rawTemp *= -1; # Bresser 3in1 + } + } + return round($rawTemp,1); }, hum => sub {my ($rawData,undef) = @_; - return if (substr($rawData,24,2) eq 'FF'); - return substr($rawData,28,2) + 0; + # uncoverable condition right + return if ((substr($rawData,12,1) eq '1' && substr($rawData,33,1) eq '1') || substr($rawData,28,2) !~ m/^\d+$/xms); # not by weather station & rain + $hum = substr($rawData,28,2) * 1; + if (substr($rawData,12,1) eq '4' && $hum >= 1 && $hum <= 16) { # Soil Moisture + return $moisture_map[$hum - 1]; + } + return $hum; }, rain => sub {my ($rawData,undef) = @_; - return if (substr($rawData,24,2) ne 'FF'); - $rain = substr($rawData,26,4); + return if (substr($rawData,33,1) ne '1' || substr($rawData,12,1) ne '1'); # message type || weather station + $rain = substr($rawData,24,6); $rain =~ tr/0123456789ABCDEF/FEDCBA9876543210/; + # uncoverable branch true + return if ($rain !~ m/^\d+$/xms); return $rain * 0.1; }, uv => sub {my ($rawData,undef) = @_; - return if (substr($rawData,24,2) ne 'FF' || substr($rawData,30,3) !~ m/^\d+$/xms); + return if (substr($rawData,33,1) ne '0' || substr($rawData,12,1) ne '1'); # message type || weather station $uv = substr($rawData,30,3); + $uv =~ tr/0123456789ABCDEF/FEDCBA9876543210/; + # uncoverable branch true return if ($uv !~ m/^\d+$/xms); return $uv * 0.1; }, @@ -1116,6 +1235,187 @@ sub SD_WS_Parse { return 1; } } , + 117 => { + # https://github.com/merbanan/rtl_433/blob/master/src/devices/bresser_7in1.c + # The compact 7-in-1 multifunction outdoor sensor transmits the data on 868.3 MHz. + # The device uses FSK-PCM encoding, the device sends a transmission every 12 seconds. + # A transmission starts with a preamble of 0xAA. + # Preamble: aa aa aa aa aa 2d d4 + # + # 1 2 3 4 + # 01234567890123456789012345678901234567890123456789 + # -------------------------------------------------- + # 0CF0A6F5B98A10AAAAAAAAAAAAAABABC3EAABBFCAAAAAAAAAA000000 original message + # A65A0C5F1320BA000000000000001016940011560000000000AAAAAA message after all nibbles xor 0xA + # CCCCIIIIDDD??FGGGWWWRRRRRR??TTTBHHbbbbbbVVVttttttt + # C = LFSR-16 digest, generator 0x8810 key 0xba95 with a final xor 0x6df1, which likely means we got that wrong. + # I = station ID + # D = wind direction in degree, BCD coded, DDD = 158 => 158 ° + # F = flags, 4 bit + # Bit: 0123 + # 1010 + # r??? + # r: 1 bit device reset, 1 after inserting battery + # ???: always 010 + # G = wind gust in 1/10 m/s, BCD coded, GGG = 123 => 12.3 m/s. + # W = wind speed in 1/10 m/s, BCD coded, WWW = 123 => 12.3 m/s. + # R = rain counter, in 0.1 mm, BCD coded RRRRRR = 000084 => 8.4 mm + # T = temperature in 1/10 °C, BCD coded, TTT = 312 => 31.2 °C + # B = battery. 0=Ok, 6=Low + # H = humidity in percent, BCD coded, HH = 23 => 23 % + # b = brightness, BCD coded, BBBBBB = 005584 => 5.584 klx + # V = uv, BCD coded, VVV = 012 => 1.2 + # ? = unknown + # t = trailer + sensortype => 'Bresser_7in1', + model => 'SD_WS_117', + prematch => sub {my $rawData = shift; return 1 if ($rawData =~ /^[0-9A-F]{8}[0-9]{3}[0-9A-F]{3}[0-9]{12}[0-9A-F]{2}[0-9]{15}/); }, + id => sub {my ($rawData,undef) = @_; return substr($rawData,4,4); }, + winddir => sub {my ($rawData,undef) = @_; + $winddir = substr($rawData,8,3); + return ($winddir * 1, $winddirtxtar[round(($winddir / 22.5),0)]); + }, + batChange => sub {my (undef,$bitData) = @_; return substr($bitData,52,1) eq '0' ? '1' : '0';}, + windgust => sub {my ($rawData,undef) = @_; return substr($rawData,14,3) * 0.1;}, + windspeed => sub {my ($rawData,undef) = @_; return substr($rawData,17,3) * 0.1;}, + rain => sub {my ($rawData,undef) = @_; return substr($rawData,20,6) * 0.1;}, + temp => sub {my ($rawData,undef) = @_; + $rawTemp = substr($rawData,28,3) * 0.1; + if ($rawTemp > 60) {$rawTemp -= 100}; + return $rawTemp; + }, + bat => sub {my ($rawData,undef) = @_; return substr($rawData,31,1) eq '0' ? 'ok' : 'low';}, + hum => sub {my ($rawData,undef) = @_; return substr($rawData,32,2) * 1;}, + brightness => sub {my ($rawData,undef) = @_; return substr($rawData,34,6) * 0.001;}, + uv => sub {my ($rawData,undef) = @_; return substr($rawData,40,3) * 0.1;}, + crcok => sub {return 1;}, # checks are in SD_Protocols.pm sub ConvBresser_7in1 + }, + 120 => { + # Weather station TFA 35.1077.54.S2 with 30.3151 (T/H-transmitter), 30.3152 (rain gauge), 30.3153 (anemometer) + # ------------------------------------------------------------------------------------------------------------ + # https://forum.fhem.de/index.php/topic,119335.msg1221926.html#msg1221926 + # 0 4 | 8 12 | 16 20 | 24 28 | 32 36 | 40 44 | 48 52 | 56 60 | 64 68 | 72 76 + # 1111 1110 | 1010 1011 | 0000 0100 | 1001 1110 | 1010 1010 | 0000 1000 | 0000 1100 | 0000 1100 | 0101 0011 | 0000 0000 - T: 19.1 H: 85 W: 1.3 R: 473.1 + # PPPP PPPW | WWWI IIII | IIIF BTTT | TTTT TTTH | HHHH HHHS | SSSS SSSG | GGGG GGGR | RRRR RRRR | RRRR RRR? | CCCC CCCC + # 1111 1110 | 1100 1011 | 0001 0101 | 0011 0000 | 0000 0110 | 0000 1100 | 0100 0100 | 1000 1100 | 0100 1111 | 1011 1000 - 2022-06-27 18:03:06 + # PPPP PPPW | WWWI IIII | IIIF ???? | ?hhh hhh? | mmmm mmm? | ssss sssY | YYYY YYY? | ??MM MMM? | ?DDD DDD? | CCCC CCCC + # P - 7 bit preamble + # W - 4 bit whid, 0101=weather, 0110=time + # I - 8 bit ident + # F - 1 bit flag, 0=weather, 1=time + # B - 1 bit battery + # T - 10 bit temperature in 1/10 °C, offset 40 + # H - 8 bit humidity in percent + # S - 8 bit windspeed in 1/10 m/s, resolution 0.33333 + # G - 8 bit windgust in 1/10 m/s, resolution 0.33333 + # R - 16 bit rain counter, resolution 0.3 mm + # C - 8 bit CRC8 of byte 1-10 bytes, result must be 0 (Polynomial 0x31) + # h - 6 bit hour, BCD coded + # m - 7 bit minute, BCD coded + # s - 7 bit second, BCD coded + # Y - 8 bit year, BCD coded + # M - 5 bit month, BCD coded + # D - 6 bit day, BCD coded + # ? - x bit unknown + sensortype => 'TFA_35.1077', + model => 'SD_WS_120', + prematch => sub {return 1;}, # no precheck known + id => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,11,18);}, + bat => sub {my (undef,$bitData) = @_; return substr($bitData,20,1) eq '0' ? 'ok' : 'low';}, + temp => sub {my (undef,$bitData) = @_; + return if (substr($bitData,19,1) eq '1'); + return SD_WS_binaryToNumber($bitData,21,30) * 0.1 - 40; + }, + hum => sub {my (undef,$bitData) = @_; + return if (substr($bitData,19,1) eq '1'); + return SD_WS_binaryToNumber($bitData,31,38); + }, + windspeed => sub {my (undef,$bitData) = @_; + return if (substr($bitData,19,1) eq '1'); + return round((SD_WS_binaryToNumber($bitData,39,46) / 3.0),1); + }, + windgust => sub {my (undef,$bitData) = @_; + return if (substr($bitData,19,1) eq '1'); + return round((SD_WS_binaryToNumber($bitData,47,54) / 3.0),1); + }, + rawRainCounter => sub {my (undef,$bitData) = @_; + return if (substr($bitData,19,1) eq '1'); + return SD_WS_binaryToNumber($bitData,55,70); + }, + rain => sub {my (undef,$bitData) = @_; + return if (substr($bitData,19,1) eq '1'); + return SD_WS_binaryToNumber($bitData,55,70) * 0.3; + }, + dcf => sub {my (undef,$bitData) = @_; + return if (substr($bitData,19,1) eq '0'); + return '20' . SD_WS_binaryToNumber($bitData,47,50) . SD_WS_binaryToNumber($bitData,51,54) . '-' # year + . substr($bitData,58,1) . SD_WS_binaryToNumber($bitData,59,62) . '-' # month + . SD_WS_binaryToNumber($bitData,65,66) . SD_WS_binaryToNumber($bitData,67,70) . ' ' # day + . SD_WS_binaryToNumber($bitData,25,26) . SD_WS_binaryToNumber($bitData,27,30) . ':' # hour + . SD_WS_binaryToNumber($bitData,32,34) . SD_WS_binaryToNumber($bitData,35,38) . ':' # minute + . SD_WS_binaryToNumber($bitData,40,42) . SD_WS_binaryToNumber($bitData,43,46) # second + }, + crcok => sub {my $rawData = shift; + my $rc = eval { + require Digest::CRC; + Digest::CRC->import(); + 1; + }; + if ($rc) { + my $datacheck1 = pack( 'H*', substr($rawData,2,length($rawData)-2) ); + my $crcmein1 = Digest::CRC->new(width => 8, poly => 0x31); + my $rr3 = $crcmein1->add($datacheck1)->hexdigest; + if (hex($rr3) != 0) { + Log3 $name, 3, "$name: SD_WS_120 Parse msg $rawData - ERROR CRC8"; + return 0; + } + } else { + Log3 $name, 1, "$name: SD_WS_120 Parse msg $rawData - ERROR CRC not load, please install modul Digest::CRC"; + return 0; + } + return 1; + } + }, + 122 => { + # TM40, Wireless Grill-, Meat-, Roasting-Thermometer with 4 Temperature Sensors + # ----------------------------------------------------------------------------- + # 0 4 | 8 12 | 16 20 | 24 28 | 32 36 | 40 44 | 48 52 | 56 60 | 64 68 | 72 76 | 80 84 | 88 92 | 96 100 | 104 + # 1001 0010 | 0110 0011 | 0000 0001 | 0011 0110 | 0000 0001 | 0011 0110 | 0000 0001 | 0100 0000 | 0000 0001 | 0110 1000 | 0000 0000 | 0000 0000 | 1011 1000 | 1000 + # iiii iiii | iiii iiii | 4444 4444 | 4444 4444 | 3333 3333 | 3333 3333 | 2222 2222 | 2222 2222 | tttt tttt | tttt tttt | ???? ???? | b??? p??? | CCCC CCCC | 1 + # i: 16 bit id, changes when the batteries are inserted + # 4: 16 bit unsigned temperature 4, 65235 = sensor not connected + # 3: 16 bit unsigned temperature 3, 65235 = sensor not connected + # 2: 16 bit unsigned temperature 2, 65235 = sensor not connected + # t: 16 bit unsigned temperature 1, 65235 = sensor not connected + # b: 1 bit battery. 0 = Ok, 1 = Low + # p: 1 bit transmitter power. 0 = On, 1 = off + # ?: 16 bit flags, 4 bits per sensor, meaning of the lower 3 bits 101 = not connected, 000 = connected + # C: 8 bit check???, always changes bit 1-7, bit 0 always 0 + # Sensor transmits at intervals of about 3 to 4 seconds + sensortype => 'TM40', + model => 'SD_WS_122_T', + prematch => sub { return 1; }, # no precheck known + id => sub { my ($rawData,undef) = @_; return substr($rawData,0,4); }, + temp4 => sub { my (undef,$bitData) = @_; + return if (substr($bitData,81,3) ne '000'); + return SD_WS_binaryToNumber($bitData,16,31) / 10; + }, + temp3 => sub { my (undef,$bitData) = @_; + return if (substr($bitData,85,3) ne '000'); + return SD_WS_binaryToNumber($bitData,32,47) / 10; + }, + temp2 => sub { my (undef,$bitData) = @_; + return if (substr($bitData,89,3) ne '000'); + return SD_WS_binaryToNumber($bitData,48,63) / 10; + }, + temp => sub { my (undef,$bitData) = @_; + return if (substr($bitData,93,3) ne '000'); + return SD_WS_binaryToNumber($bitData,64,79) / 10; + }, + bat => sub { my (undef,$bitData) = @_; return substr($bitData,88,1) eq "0" ? "ok" : "low"; }, + transmitter => sub { my (undef,$bitData) = @_; return substr($bitData,92,1) eq "0" ? "on" : "off"; }, + crcok => sub {return 1;}, # Check could not be determined yet. + }, ); Log3 $name, 4, "$name: SD_WS_Parse protocol $protocol, rawData $rawData"; @@ -1430,6 +1730,8 @@ sub SD_WS_Parse { $id = $decodingSubs{$protocol}{id}->( $rawData,$bitData ); $temp = $decodingSubs{$protocol}{temp}->( $rawData,$bitData ) if (exists($decodingSubs{$protocol}{temp})); $temp2 = $decodingSubs{$protocol}{temp2}->( $rawData,$bitData ) if (exists($decodingSubs{$protocol}{temp2})); + $temp3 = $decodingSubs{$protocol}{temp3}->( $rawData,$bitData ) if (exists($decodingSubs{$protocol}{temp3})); + $temp4 = $decodingSubs{$protocol}{temp4}->( $rawData,$bitData ) if (exists($decodingSubs{$protocol}{temp4})); $hum = $decodingSubs{$protocol}{hum}->( $rawData,$bitData ) if (exists($decodingSubs{$protocol}{hum})); $windspeed = $decodingSubs{$protocol}{windspeed}->( $rawData,$bitData ) if (exists($decodingSubs{$protocol}{windspeed})); ($winddir,$winddirtxt) = $decodingSubs{$protocol}{winddir}->( $rawData,$bitData ) if (exists($decodingSubs{$protocol}{winddir})); @@ -1455,6 +1757,9 @@ sub SD_WS_Parse { $count = $decodingSubs{$protocol}{count}->( $rawData,$bitData ) if (exists($decodingSubs{$protocol}{count})); $identified = $decodingSubs{$protocol}{identified}->( $rawData,$bitData ) if (exists($decodingSubs{$protocol}{identified})); $uv = $decodingSubs{$protocol}{uv}->( $rawData,$bitData ) if (exists($decodingSubs{$protocol}{uv})); + $brightness = $decodingSubs{$protocol}{brightness}->( $rawData,$bitData ) if (exists($decodingSubs{$protocol}{brightness})); + $transmitter = $decodingSubs{$protocol}{transmitter}->( $rawData,$bitData ) if (exists($decodingSubs{$protocol}{transmitter})); + $dcf = $decodingSubs{$protocol}{dcf}->( $rawData,$bitData ) if (exists($decodingSubs{$protocol}{dcf})); Log3 $iohash, 4, "$name: SD_WS_Parse decoded protocol-id $protocol ($SensorTyp), sensor-id $id"; } else { @@ -1487,6 +1792,22 @@ sub SD_WS_Parse { $def = $modules{SD_WS}{defptr}{$deviceCode} if(!$def); if(!$def) { + if (($longids ne "0") && ($longids eq "1" || $longids eq "ALL" || (",$longids," =~ m/,$model,/))) { + my @found = devspec2array(q[TYPE=SD_WS:FILTER=i:replaceBattery>0]); + if (scalar(@found) > 0) { + my $rname = $found[0]; + my $rproto = InternalVal($rname, $ioname. q[_Protocol_ID], undef); + if (defined $rproto && $rproto eq $protocol) { + my $rhash = $defs{$rname}; + my $rdef = $rhash->{DEF}; + Log3 $name, 3, qq[$rname: SD_WS_Parse, change DEF from $rdef in $deviceCode]; + CommandModify(undef, qq[$rname $deviceCode]); + delete($rhash->{replaceBattery}); + RemoveInternalTimer($rhash); + return $rname; + } + } + } Log3 $iohash, 1, "$name: SD_WS_Parse UNDEFINED sensor $model detected, code $deviceCode"; return "UNDEFINED $deviceCode SD_WS $deviceCode"; } @@ -1496,7 +1817,7 @@ sub SD_WS_Parse { return "" if(IsIgnored($name)); if (defined $temp) { - if (($temp < -30 || $temp > 70) && $protocol ne '106' && $protocol ne '113') { # not forBBQ temperature sensor GT-TMBBQ-01s and Wireless Grill Thermometer GFGT 433 B1 + if (($temp < -30 || $temp > 70) && $protocol ne '106' && $protocol ne '113' && $protocol ne '122') { # not forBBQ temperature sensor GT-TMBBQ-01s, Wireless Grill Thermometer GFGT 433 B1 and TM40 Log3 $name, 3, "$ioname: SD_WS_Parse $deviceCode - ERROR temperature $temp"; return ""; } @@ -1509,18 +1830,14 @@ sub SD_WS_Parse { } # Sanity checks - if($def && $protocol ne '106' && $protocol ne '113') { # not forBBQ temperature sensor GT-TMBBQ-01s and Wireless Grill Thermometer GFGT 433 B1 - my $timeSinceLastUpdate = abs(ReadingsAge($name, "state", 0)); + if($def && $protocol ne '106' && $protocol ne '113' && $protocol ne '122') { # not forBBQ temperature sensor GT-TMBBQ-01s, Wireless Grill Thermometer GFGT 433 B1 and TM40 + my $timeSinceLastUpdate = abs(ReadingsAge($name, 'state', 0)); # temperature - if (defined($temp) && defined(ReadingsVal($name, "temperature", undef))) { - my $diffTemp = 0; - my $oldTemp = ReadingsVal($name, "temperature", undef); + my $oldTemp = ReadingsVal($name, 'temperature', undef); + if (defined $oldTemp && defined $temp) { + #my $oldTemp = ReadingsVal($name, "temperature", undef); my $maxdeviation = AttrVal($name, "max-deviation-temp", 1); # default 1 K - if ($temp > $oldTemp) { - $diffTemp = ($temp - $oldTemp); - } else { - $diffTemp = ($oldTemp - $temp); - } + my $diffTemp = abs($temp - $oldTemp); $diffTemp = sprintf("%.1f", $diffTemp); Log3 $name, 4, "$ioname: $name old temp $oldTemp, age $timeSinceLastUpdate, new temp $temp, diff temp $diffTemp"; my $maxDiffTemp = $timeSinceLastUpdate / 60 + $maxdeviation; # maxdeviation + 1.0 Kelvin/Minute @@ -1532,15 +1849,11 @@ sub SD_WS_Parse { } } # humidity - if (defined($hum) && defined(ReadingsVal($name, "humidity", undef))) { - my $diffHum = 0; - my $oldHum = ReadingsVal($name, "humidity", undef); + my $oldHum = ReadingsVal($name, 'humidity', undef); + if (defined $oldHum && defined $hum) { + #my $oldHum = ReadingsVal($name, "humidity", undef); my $maxdeviation = AttrVal($name, "max-deviation-hum", 1); # default 1 % - if ($hum > $oldHum) { - $diffHum = ($hum - $oldHum); - } else { - $diffHum = ($oldHum - $hum); - } + my $diffHum = abs($hum - $oldHum); $diffHum = sprintf("%.1f", $diffHum); Log3 $name, 4, "$ioname: $name old hum $oldHum, age $timeSinceLastUpdate, new hum $hum, diff hum $diffHum"; my $maxDiffHum = $timeSinceLastUpdate / 60 + $maxdeviation; # $maxdeviation + 1.0 %/Minute @@ -1566,12 +1879,20 @@ sub SD_WS_Parse { #my $state = (($temp > -60 && $temp < 70) ? "T: $temp":"T: xx") . (($hum > 0 && $hum < 100) ? " H: $hum":""); my $state = ""; if (defined($temp)) { - $state .= "T: $temp" + $state .= "T: $temp"; } if (defined($temp2)) { $state .= ' ' if (length($state) > 0); $state .= "T2: $temp2"; } + if (defined($temp3)) { + $state .= ' ' if (length($state) > 0); + $state .= "T3: $temp3"; + } + if (defined($temp4)) { + $state .= ' ' if (length($state) > 0); + $state .= "T4: $temp4"; + } if (defined($hum) && ($hum > 0 && $hum < 100)) { $state .= " H: $hum" } @@ -1595,6 +1916,11 @@ sub SD_WS_Parse { $state .= ' ' if (length($state) > 0); $state .= "D: $distance"; } + if (defined($brightness)) { + $state .= ' ' if (length($state) > 0); + $state .= "B: $brightness"; + } + ### protocol 33 has different bits per sensor type if ($protocol eq "33") { if (AttrVal($name,'model',0) eq "S522") { # Conrad S522 @@ -1611,10 +1937,20 @@ sub SD_WS_Parse { } } + ### Protocol 85 without wind direction + if ($protocol eq "85") { + if (AttrVal($name,'model',0) ne "TFA_30.3251.10") { + $winddir = undef; + $winddirtxt = undef; + } + } + readingsBeginUpdate($hash); readingsBulkUpdate($hash, "state", $state); - readingsBulkUpdate($hash, "temperature", $temp) if (defined($temp) && (($temp > -60 && $temp < 70 ) || $protocol eq '106' || $protocol eq '113')); - readingsBulkUpdate($hash, "temperature2", $temp2) if (defined($temp2) && (($temp2 > -60 && $temp < 70 ) || $protocol eq '113')); + readingsBulkUpdate($hash, "temperature", $temp) if (defined($temp) && (($temp > -60 && $temp < 70 ) || $protocol eq '106' || $protocol eq '113' || $protocol eq '122')); + readingsBulkUpdate($hash, "temperature2", $temp2) if (defined($temp2) && (($temp2 > -60 && $temp2 < 70 ) || $protocol eq '113' || $protocol eq '122')); + readingsBulkUpdate($hash, "temperature3", $temp3) if (defined($temp3) && (($temp3 > -60 && $temp3 < 70 ) || $protocol eq '122')); + readingsBulkUpdate($hash, "temperature4", $temp4) if (defined($temp4) && (($temp4 > -60 && $temp4 < 70 ) || $protocol eq '122')); readingsBulkUpdate($hash, "humidity", $hum) if (defined($hum) && ($hum > 0 && $hum < 100 )) ; readingsBulkUpdate($hash, 'windSpeed', $windspeed) if (defined($windspeed)) ; readingsBulkUpdate($hash, 'windDirectionDegree', $winddir) if (defined($winddir)) ; @@ -1640,10 +1976,12 @@ sub SD_WS_Parse { readingsBulkUpdate($hash, 'count', $count) if (defined($count)); readingsBulkUpdate($hash, 'identified', $identified) if (defined($identified)); readingsBulkUpdate($hash, "uv", $uv) if (defined($uv)); + readingsBulkUpdate($hash, 'brightness', $brightness) if (defined($brightness)); + readingsBulkUpdateIfChanged($hash, 'transmitter', $transmitter) if (defined($transmitter)); + readingsBulkUpdate($hash, 'dcf', $dcf) if (defined($dcf)); readingsEndUpdate($hash, 1); # Notify is done by Dispatch return $name; - } ############################# @@ -1737,7 +2075,7 @@ sub SD_WS_WH2SHIFT {
replaceBatteryForSec <seconds>
+ <secondes>
.
+ The first unknown address with the same protocol that is received replaces the current device address.
+ replaceBatteryForSec <Sekunden>
+ <Sekunden>
Sekunden in den Batteriewechselmodus.
+ Die erste unbekannte Adresse mit gleichem Protokoll, die empfangen wird, ersetzt die aktuelle Geräteadresse.
+