2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-01-31 06:39:11 +00:00

14_SD_WS.pm: serval features and fixes

git-svn-id: https://svn.fhem.de/fhem/trunk@27955 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
sidey79 2023-09-12 20:15:44 +00:00
parent 9512babddc
commit 12affd16ff
2 changed files with 286 additions and 26 deletions

View File

@ -1,8 +1,16 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # 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. # Do not insert empty lines here, update check depends on it.
- bugfix: 14_SD_WS.pm: Corrected protocol 115 battery bit,
added batChange bit
corrected calculation for negative temperatures
- feature: 14_SD_WS.pm: add TFA 30.3151 (T/H-transmitter)
TFA 30.3251.10
30.3152 (rain gauge)
30.3153 (anemometer)
- feature: 14_SD_WS.pm: Protocol 115 for Bresser 6-in-1 Comfort Wetter Center
- feature: 14_SD_WS.pm: new set command replaceBatteryForSec
- feature: 14_SD_WS.pm: BRESSER 7-in-1 Weather Center
- bugfix: 14_SD_WS09.pm: wrong calculation of averagewindDirection - bugfix: 14_SD_WS09.pm: wrong calculation of averagewindDirection
- bugifix: 14_Hideki.pm: winddirection fixed (#1099) - bugifix: 14_Hideki.pm: winddirection fixed (#1099)
- change: 14_Hideki.pm: Update {Match} regex for Hideki Module (#1071) - change: 14_Hideki.pm: Update {Match} regex for Hideki Module (#1071)
- feature: SD_ProtocolData.pm: Protocol 122 for Temola TM 40 thermometer - feature: SD_ProtocolData.pm: Protocol 122 for Temola TM 40 thermometer

View File

@ -6,6 +6,7 @@
# Sidey79 & Ralf9 2016 - 2017 # Sidey79 & Ralf9 2016 - 2017
# Joerg 2017 # Joerg 2017
# elektron-bbs 2018 - # elektron-bbs 2018 -
# sidey 2017 -
# #
# 17.04.2017 WH2 (TFA 30.3157 nur Temp, Hum = 255),es wird das Perlmodul Digest:CRC benoetigt fuer CRC-Pruefung benoetigt # 17.04.2017 WH2 (TFA 30.3157 nur Temp, Hum = 255),es wird das Perlmodul Digest:CRC benoetigt fuer CRC-Pruefung benoetigt
# 29.05.2017 Test ob Digest::CRC installiert # 29.05.2017 Test ob Digest::CRC installiert
@ -46,6 +47,11 @@
# 11.04.2022 Protokoll 85: neuer Sensor Windmesser TFA 30.3251.10 mit Windrichtung, Pruefung CRC8 eingearbeitet # 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) # 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 # 11.06.2022 neues Protokoll 122: TM40, Wireless Grill-, Meat-, Roasting-Thermometer with 4 Temperature Sensors
# 06.01.2023 neues Protokoll 123: Inkbird IBS-P01R Pool Thermometer, Inkbird ITH-20R (not tested)
# 21.01.2023 use round from package FHEM::Core::Utils::Math;
# 01.04.2023 Added protocol 125: ecowitt WH31 support
# 06.05.2023 Added protocol 126: ecowitt WH40 support
# 21.08.2023 neues Protokoll 129: Sainlogic weather station FT-0835
package main; package main;
@ -55,6 +61,8 @@ use strict;
use warnings; use warnings;
use Carp qw(carp); use Carp qw(carp);
use FHEM::Meta; use FHEM::Meta;
use FHEM::Core::Utils::Math;
use constant HAS_DigestCRC => defined eval { require Digest::CRC; };
# Forward declarations # Forward declarations
sub SD_WS_LFSR_digest8_reflect; sub SD_WS_LFSR_digest8_reflect;
@ -67,7 +75,8 @@ sub SD_WS_Initialize {
my $hash = shift // return; my $hash = shift // return;
$hash->{Match} = '^W\d+x{0,1}#.*'; $hash->{Match} = '^W\d+x{0,1}#.*';
$hash->{DefFn} = \&SD_WS_Define; $hash->{DefFn} = \&SD_WS_Define;
$hash->{UndefFn} = \&SD_WS_Undef;$hash->{SetFn} = \&SD_WS_Set; $hash->{UndefFn} = \&SD_WS_Undef;
$hash->{SetFn} = \&SD_WS_Set;
$hash->{ParseFn} = \&SD_WS_Parse; $hash->{ParseFn} = \&SD_WS_Parse;
$hash->{AttrList} = "do_not_notify:1,0 ignore:0,1 showtime:1,0 " . $hash->{AttrList} = "do_not_notify:1,0 ignore:0,1 showtime:1,0 " .
"model:E0001PA,S522,TFA_30.3251.10,TX-EZ6,other " . "model:E0001PA,S522,TFA_30.3251.10,TX-EZ6,other " .
@ -95,7 +104,7 @@ sub SD_WS_Initialize {
"SD_WS_85_THW_.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "4:120"}, "SD_WS_85_THW_.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "4:120"},
"SD_WS_89_TH.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "3:180"}, "SD_WS_89_TH.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "3:180"},
"SD_WS_94_T.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4:Temp,", autocreateThreshold => "3:180"}, "SD_WS_94_T.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4:Temp,", autocreateThreshold => "3:180"},
'SD_WS_107_H.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => q{}, autocreateThreshold => '2:180'}, 'SD_WS_107_H.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => q{}, autocreateThreshold => '2:300'},
'SD_WS_108.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => 'temp4:Temp,', autocreateThreshold => '5:120'}, '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_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'},
@ -105,6 +114,10 @@ sub SD_WS_Initialize {
'SD_WS_117.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => 'temp4hum4:Temp/Hum,', autocreateThreshold => '5:120'}, '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_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'}, 'SD_WS_122_T.*' => { ATTR => 'event-min-interval:.*:60 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => 'temp4:Temp,', autocreateThreshold => '10:180'},
'SD_WS_123_T.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => 'temp4:Temp,', autocreateThreshold => '2:180'},
'SD_WS_125_.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => 'temp4hum4:Temp/Hum,', autocreateThreshold => '2:300'},
'SD_WS_126_R.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => 'rain4:Rain,', autocreateThreshold => "2:180"},
'SD_WS_129.*' => { ATTR => 'event-min-interval:.*:300 event-on-change-reading:.*', FILTER => '%NAME', GPLOT => 'temp4hum4:Temp/Hum,', autocreateThreshold => '3:180'},
}; };
return FHEM::Meta::InitMod( __FILE__, $hash ); return FHEM::Meta::InitMod( __FILE__, $hash );
} }
@ -318,7 +331,7 @@ sub SD_WS_Parse {
return 0; return 0;
} }
} else { } else {
Log3 $name, 1, "$name: SD_WS_27 Parse msg $rawData - ERROR CRC not load, please install modul Digest::CRC"; Log3 $name, 1, "$name: SD_WS_27 Parse msg $rawData - ERROR CRC not checked, please install module Digest::CRC";
return 0; return 0;
} }
} }
@ -383,7 +396,7 @@ sub SD_WS_Parse {
} }
}, },
id => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,0,9); }, # id id => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,0,9); }, # id
temp => sub {my (undef,$bitData) = @_; return round(((SD_WS_binaryToNumber($bitData,22,25)*256 + SD_WS_binaryToNumber($bitData,18,21)*16 + SD_WS_binaryToNumber($bitData,14,17)) - 1220) * 5 / 90.0 , 1); }, #temp temp => sub {my (undef,$bitData) = @_; return FHEM::Core::Utils::Math::round(((SD_WS_binaryToNumber($bitData,22,25)*256 + SD_WS_binaryToNumber($bitData,18,21)*16 + SD_WS_binaryToNumber($bitData,14,17)) - 1220) * 5 / 90.0 , 1); }, #temp
hum => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,30,33)*16 + SD_WS_binaryToNumber($bitData,26,29)); }, #hum hum => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,30,33)*16 + SD_WS_binaryToNumber($bitData,26,29)); }, #hum
channel => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,12,13)+1 ); }, # channel channel => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,12,13)+1 ); }, # channel
bat => sub {my (undef,$bitData) = @_; return substr($bitData,34,1) eq "0" ? "ok" : "low";}, # other or modul orginal bat => sub {my (undef,$bitData) = @_; return substr($bitData,34,1) eq "0" ? "ok" : "low";}, # other or modul orginal
@ -449,7 +462,7 @@ sub SD_WS_Parse {
sendmode => sub {my (undef,$bitData) = @_; return substr($bitData,12,1) eq "1" ? "manual" : "auto";}, sendmode => sub {my (undef,$bitData) = @_; return substr($bitData,12,1) eq "1" ? "manual" : "auto";},
bat => sub {my (undef,$bitData) = @_; return substr($bitData,13,1) eq "1" ? "low" : "ok";}, bat => sub {my (undef,$bitData) = @_; return substr($bitData,13,1) eq "1" ? "low" : "ok";},
trend => sub {my (undef,$bitData) = @_; return ('consistent', 'rising', 'falling', 'unknown')[SD_WS_binaryToNumber($bitData,14,15)];}, trend => sub {my (undef,$bitData) = @_; return ('consistent', 'rising', 'falling', 'unknown')[SD_WS_binaryToNumber($bitData,14,15)];},
temp => sub {my (undef,$bitData) = @_; return round(((SD_WS_binaryToNumber($bitData,16,27)) - 1220) * 5 / 90.0 , 1); }, temp => sub {my (undef,$bitData) = @_; return FHEM::Core::Utils::Math::round(((SD_WS_binaryToNumber($bitData,16,27)) - 1220) * 5 / 90.0 , 1); },
hum => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,28,31) * 10) + (SD_WS_binaryToNumber($bitData,32,35));}, hum => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,28,31) * 10) + (SD_WS_binaryToNumber($bitData,32,35));},
channel => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,38,39) );}, channel => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,38,39) );},
}, },
@ -609,7 +622,7 @@ sub SD_WS_Parse {
id => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,8,15); }, # random id id => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,8,15); }, # random id
bat => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,16) eq "1" ? "low" : "ok";}, # bat? bat => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,16) eq "1" ? "low" : "ok";}, # bat?
channel => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,17,19) + 1 ); }, # channel channel => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,17,19) + 1 ); }, # channel
temp => sub {my (undef,$bitData) = @_; return round((SD_WS_binaryToNumber($bitData,20,31)-720)*0.0556,1); }, # temp temp => sub {my (undef,$bitData) = @_; return FHEM::Core::Utils::Math::round((SD_WS_binaryToNumber($bitData,20,31)-720)*0.0556,1); }, # temp
hum => sub {my ($rawData,$bitData) = @_; return substr($rawData,1,1) eq "5" ? (SD_WS_binaryToNumber($bitData,32,39)) : 0;}, # hum hum => sub {my ($rawData,$bitData) = @_; return substr($rawData,1,1) eq "5" ? (SD_WS_binaryToNumber($bitData,32,39)) : 0;}, # hum
} , } ,
84 => 84 =>
@ -694,7 +707,7 @@ sub SD_WS_Parse {
winddir => sub {my (undef,$bitData) = @_; winddir => sub {my (undef,$bitData) = @_;
if (substr($bitData,30,2) eq "10") { # message 2 winddirection if (substr($bitData,30,2) eq "10") { # message 2 winddirection
$winddir = SD_WS_binaryToNumber($bitData,44,55); $winddir = SD_WS_binaryToNumber($bitData,44,55);
return ($winddir * 1, $winddirtxtar[round(($winddir / 22.5),0)]); return ($winddir * 1, $winddirtxtar[FHEM::Core::Utils::Math::round(($winddir / 22.5),0)]);
} else { } else {
return; return;
} }
@ -714,7 +727,7 @@ sub SD_WS_Parse {
return 0; return 0;
} }
} else { } else {
Log3 $name, 1, "$name: SD_WS_85 Parse msg $rawData - ERROR CRC not load, please install modul Digest::CRC"; Log3 $name, 1, "$name: SD_WS_85 Parse msg $rawData - ERROR CRC not loaded, please install module Digest::CRC";
return 0; return 0;
} }
return 1; return 1;
@ -795,7 +808,7 @@ sub SD_WS_Parse {
$rawTemp = SD_WS_binaryToNumber($bitData,8,21); $rawTemp = SD_WS_binaryToNumber($bitData,8,21);
my $tempFh = $rawTemp / 20 - 90; # Grad Fahrenheit my $tempFh = $rawTemp / 20 - 90; # Grad Fahrenheit
Log3 $name, 4, "$name: SD_WS_106_T tempraw = $rawTemp, temp = $tempFh Fahrenheit"; Log3 $name, 4, "$name: SD_WS_106_T tempraw = $rawTemp, temp = $tempFh Fahrenheit";
return (round((($tempFh - 32) * 5 / 9) , 1)); # Grad Celsius return (FHEM::Core::Utils::Math::round((($tempFh - 32) * 5 / 9) , 1)); # Grad Celsius
}, },
crcok => sub {return 1;}, # CRC test method does not exist crcok => sub {return 1;}, # CRC test method does not exist
} , } ,
@ -815,14 +828,14 @@ sub SD_WS_Parse {
# MM: Moisture percentage 0%-100% (0x00-0x64) MM = (AD - 70) / (450 - 70) # MM: Moisture percentage 0%-100% (0x00-0x64) MM = (AD - 70) / (450 - 70)
# Z: ? Fixed: leftmost 7 bit 1111 100 # Z: ? Fixed: leftmost 7 bit 1111 100
# AAA: 9 bit AD value MSB byte[07] & 0x01, LSB byte[08] ??? 10 bit ??? WH51 Manual.pdf says: 100%AD setting range:0%AD+10~1000 # AAA: 9 bit AD value MSB byte[07] & 0x01, LSB byte[08] ??? 10 bit ??? WH51 Manual.pdf says: 100%AD setting range:0%AD+10~1000
# XXXXXX: ? Fixed: 0xff 0xff 0xff # XXXXXX: ? Fixed: 0xff 0xff 0xff or 0x00 0x00 0x00
# CC: CRC of the preceding 12 bytes (Polynomial 0x31, Initial value 0x00, Input not reflected, Result not reflected) # CC: CRC of the preceding 12 bytes (Polynomial 0x31, Initial value 0x00, Input not reflected, Result not reflected)
# SS: Sum of the preceding 13 bytes % 256 # SS: Sum of the preceding 13 bytes % 256
sensortype => 'WH51, DP100, MISOL/1', sensortype => 'WH51, DP100, MISOL/1',
model => 'SD_WS_107_H', model => 'SD_WS_107_H',
prematch => sub { ($rawData,undef) = @_; return 1 if ($rawData =~ /^51[0-9A-F]{16}[F]{6}/); }, prematch => sub { ($rawData,undef) = @_; return 1 if ($rawData =~ /^51[0-9A-F]{26}/); },
id => sub { my ($rawData,undef) = @_; return substr($rawData,2,6); }, id => sub { my ($rawData,undef) = @_; return substr($rawData,2,6); },
batVoltage => sub { my (undef,$bitData) = @_; return round(SD_WS_binaryToNumber($bitData,35,39) / 10 , 1); }, batVoltage => sub { my (undef,$bitData) = @_; return FHEM::Core::Utils::Math::round(SD_WS_binaryToNumber($bitData,35,39) / 10 , 1); },
adc => sub { my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,62,71); }, adc => sub { my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,62,71); },
hum => sub { my ($rawData,undef) = @_; return hex(substr($rawData,12,2)); }, hum => sub { my ($rawData,undef) = @_; return hex(substr($rawData,12,2)); },
crcok => sub { my $rawData = shift; crcok => sub { my $rawData = shift;
@ -978,7 +991,7 @@ sub SD_WS_Parse {
return ($rawRainCounterMessage + 10) * 0.1; return ($rawRainCounterMessage + 10) * 0.1;
} }
}, },
temp => sub { my (undef,$bitData) = @_; return round(((SD_WS_binaryToNumber($bitData,48,55) * 256 + SD_WS_binaryToNumber($bitData,40,47)) - 1220) * 5 / 90.0 , 1); }, temp => sub { my (undef,$bitData) = @_; return FHEM::Core::Utils::Math::round(((SD_WS_binaryToNumber($bitData,48,55) * 256 + SD_WS_binaryToNumber($bitData,40,47)) - 1220) * 5 / 90.0 , 1); },
crcok => sub { my (undef,$bitData) = @_; crcok => sub { my (undef,$bitData) = @_;
my $sum = 0; my $sum = 0;
for (my $n = 0; $n < 56; $n += 8) { for (my $n = 0; $n < 56; $n += 8) {
@ -1053,13 +1066,13 @@ sub SD_WS_Parse {
$rawTemp = SD_WS_binaryToNumber($bitData,12,13) * 256 + SD_WS_binaryToNumber($bitData,16,23); $rawTemp = SD_WS_binaryToNumber($bitData,12,13) * 256 + SD_WS_binaryToNumber($bitData,16,23);
my $tempFh = $rawTemp - 90; # Grad Fahrenheit my $tempFh = $rawTemp - 90; # Grad Fahrenheit
Log3 $name, 4, "$name: SD_WS_113_T tempraw1 = $rawTemp, temp1 = $tempFh Grad Fahrenheit"; Log3 $name, 4, "$name: SD_WS_113_T tempraw1 = $rawTemp, temp1 = $tempFh Grad Fahrenheit";
return (round((($tempFh - 32) * 5 / 9) , 0)); # Grad Celsius return (FHEM::Core::Utils::Math::round((($tempFh - 32) * 5 / 9) , 0)); # Grad Celsius
}, },
temp2 => sub { my (undef,$bitData) = @_; temp2 => sub { my (undef,$bitData) = @_;
$rawTemp = SD_WS_binaryToNumber($bitData,14,15) * 256 + SD_WS_binaryToNumber($bitData,24,31); $rawTemp = SD_WS_binaryToNumber($bitData,14,15) * 256 + SD_WS_binaryToNumber($bitData,24,31);
my $tempFh = $rawTemp - 90; # Grad Fahrenheit my $tempFh = $rawTemp - 90; # Grad Fahrenheit
Log3 $name, 4, "$name: SD_WS_113_T tempraw2 = $rawTemp, temp2 = $tempFh Grad Fahrenheit"; Log3 $name, 4, "$name: SD_WS_113_T tempraw2 = $rawTemp, temp2 = $tempFh Grad Fahrenheit";
return (round((($tempFh - 32) * 5 / 9) , 0)); # Grad Celsius return (FHEM::Core::Utils::Math::round((($tempFh - 32) * 5 / 9) , 0)); # Grad Celsius
}, },
crcok => sub {return 1;}, # Check could not be determined yet. crcok => sub {return 1;}, # Check could not be determined yet.
} , } ,
@ -1126,7 +1139,7 @@ sub SD_WS_Parse {
winddir => sub {my ($rawData,undef) = @_; winddir => sub {my ($rawData,undef) = @_;
return if (substr($rawData,12,1) ne '1' || substr($rawData,20,3) !~ m/^\d+$/xms); # only weather station return if (substr($rawData,12,1) ne '1' || substr($rawData,20,3) !~ m/^\d+$/xms); # only weather station
$winddir = substr($rawData,20,3); $winddir = substr($rawData,20,3);
return ($winddir * 1, $winddirtxtar[round(($winddir / 22.5),0)]); return ($winddir * 1, $winddirtxtar[FHEM::Core::Utils::Math::round(($winddir / 22.5),0)]);
}, },
temp => sub {my ($rawData,$bitData) = @_; temp => sub {my ($rawData,$bitData) = @_;
# uncoverable condition right # uncoverable condition right
@ -1139,7 +1152,7 @@ sub SD_WS_Parse {
$rawTemp *= -1; # Bresser 3in1 $rawTemp *= -1; # Bresser 3in1
} }
} }
return round($rawTemp,1); return FHEM::Core::Utils::Math::round($rawTemp,1);
}, },
hum => sub {my ($rawData,undef) = @_; hum => sub {my ($rawData,undef) = @_;
# uncoverable condition right # uncoverable condition right
@ -1235,6 +1248,7 @@ sub SD_WS_Parse {
return 1; return 1;
} }
} , } ,
117 => { 117 => {
# https://github.com/merbanan/rtl_433/blob/master/src/devices/bresser_7in1.c # 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 compact 7-in-1 multifunction outdoor sensor transmits the data on 868.3 MHz.
@ -1273,7 +1287,7 @@ sub SD_WS_Parse {
id => sub {my ($rawData,undef) = @_; return substr($rawData,4,4); }, id => sub {my ($rawData,undef) = @_; return substr($rawData,4,4); },
winddir => sub {my ($rawData,undef) = @_; winddir => sub {my ($rawData,undef) = @_;
$winddir = substr($rawData,8,3); $winddir = substr($rawData,8,3);
return ($winddir * 1, $winddirtxtar[round(($winddir / 22.5),0)]); return ($winddir * 1, $winddirtxtar[FHEM::Core::Utils::Math::round(($winddir / 22.5),0)]);
}, },
batChange => sub {my (undef,$bitData) = @_; return substr($bitData,52,1) eq '0' ? '1' : '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;}, windgust => sub {my ($rawData,undef) = @_; return substr($rawData,14,3) * 0.1;},
@ -1332,11 +1346,11 @@ sub SD_WS_Parse {
}, },
windspeed => sub {my (undef,$bitData) = @_; windspeed => sub {my (undef,$bitData) = @_;
return if (substr($bitData,19,1) eq '1'); return if (substr($bitData,19,1) eq '1');
return round((SD_WS_binaryToNumber($bitData,39,46) / 3.0),1); return FHEM::Core::Utils::Math::round((SD_WS_binaryToNumber($bitData,39,46) / 3.0),1);
}, },
windgust => sub {my (undef,$bitData) = @_; windgust => sub {my (undef,$bitData) = @_;
return if (substr($bitData,19,1) eq '1'); return if (substr($bitData,19,1) eq '1');
return round((SD_WS_binaryToNumber($bitData,47,54) / 3.0),1); return FHEM::Core::Utils::Math::round((SD_WS_binaryToNumber($bitData,47,54) / 3.0),1);
}, },
rawRainCounter => sub {my (undef,$bitData) = @_; rawRainCounter => sub {my (undef,$bitData) = @_;
return if (substr($bitData,19,1) eq '1'); return if (substr($bitData,19,1) eq '1');
@ -1416,6 +1430,217 @@ sub SD_WS_Parse {
transmitter => sub { my (undef,$bitData) = @_; return substr($bitData,92,1) eq "0" ? "on" : "off"; }, transmitter => sub { my (undef,$bitData) = @_; return substr($bitData,92,1) eq "0" ? "on" : "off"; },
crcok => sub {return 1;}, # Check could not be determined yet. crcok => sub {return 1;}, # Check could not be determined yet.
}, },
123 => {
# Inkbird IBS-P01R Pool Thermometer, Inkbird ITH-20R (not tested)
# ---------------------------------------------------------------
# Nibble 0 4 | 8 12 | 16 20 | 24 28 | 32
# D391 0F80 | 0301 005A | 0655 FA00 | 1405 1405 | 35F6 - IBS-P01R
# D391 0F00 | 0103 0120 | 7E43 FF00 | 1405 3F02 | 5CCB - unknown sensor with humidity
# SSSS LL33 | 4455 66BB | IIII TTTT | tttt HHHH | CCCC
# S: 2 Byte, Sync 2 ???
# L: 1 Byte, Number of bytes from byte 3 to the end ???
# 3: 1 Byte, Flags, IBS-P01R always 0x80, in inkbird_ith20r.c - 00 - normal work , 40 - unlink sensor (button pressed 5s), 80 - battery replaced
# 4: 1 Byte, Flags, IBS-P01R always 0x03, in inkbird_ith20r.c - changes from 1 to 2 if external sensor present
# 5: 1 Byte, Flags, IBS-P01R always 0x01, in inkbird_ith20r.c - unknown (also seen 0201), sw version? Seen 0x0001 on IBS-P01R
# 6: 1 Byte, Flags, IBS-P01R always 0x00, in inkbird_ith20r.c - unknown (also seen 0201), sw version? Seen 0x0001 on IBS-P01R
# B: 1 Byte, Battery Percent, IBS-P01R (0, 30, 60, 90), in inkbird_ith20r.c - Battery % 0-100
# I: 2 Byte, Ident, always the same for a sensor but each sensor is different
# T: 2 Byte, Temperature in C * 10, little endian, so 0xD200 is 210, 21.0C
# t: 2 Byte, Temperature for the external sensor, 0x1405 if not connected
# H: 2 Byte, Relative humidity % * 10, little endian, so 0xC501 is 453 or 45.3%
# C: 2 Byte, CRC16 over bytes 0-15, poly=0x8005 (0xA001 reflected), init=0x2f61 (0x86F4 reflected)
# 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 108 | 112 116 | 120 128 | 132 136 | 140 144
# 1101 0011 | 1001 0001 | 0000 1111 | 1000 0000 | 0000 0011 | 0000 0001 | 0000 0000 | 0001 1110 | 0000 0110 | 0101 0101 | 0001 0100 | 0000 0001 | 0001 0100 | 0000 0101 | 0001 0100 | 0000 0101 | 0001 1100 | 0111 1011
# 1101 0011 | 1001 0001 | 0000 1111 | 0000 0000 | 0000 0001 | 0000 0011 | 0000 0011 | 0010 0001 | 0111 1110 | 0100 0011 | 1010 0101 | 0000 0000 | 0001 0100 | 0000 0101 | 0100 0110 | 0000 0010 | 0111 1111 | 0101 0001
# SSSS SSSS | SSSS SSSS | LLLL LLLL | 3333 3333 | 4444 4444 | 5555 5555 | 6666 6666 | BBBB BBBB | IIII IIII | IIII IIII | TTTT TTTT | TTTT TTTT | tttt tttt | tttt tttt | HHHH HHHH | HHHH HHHH | CCCC CCCC | CCCC CCCC
sensortype => 'IBS-P01R, ITH-20R',
model => 'SD_WS_123_T',
prematch => sub { return 1; }, # no precheck known
batChange => sub { my (undef,$bitData) = @_; return substr($bitData,24,1) eq '0' ? '1' : '0'; },
batteryPercent => sub { my ($rawData,undef) = @_; return hex(substr($rawData,14,2)); },
id => sub { my ($rawData,undef) = @_; return substr($rawData,16,4); },
temp => sub { my ($rawData,undef) = @_; return ((((hex(substr($rawData,20,2)) + hex(substr($rawData,22,2)) * 256) ^ 0x8000) - 0x8000) / 10); },
temp2 => sub { my ($rawData,undef) = @_;
return if (substr($rawData,24,4) eq '1405');
return ((((hex(substr($rawData,24,2)) + hex(substr($rawData,26,2)) * 256) ^ 0x8000) - 0x8000) / 10);
},
hum => sub { my ($rawData,undef) = @_;
return if (substr($rawData,28,4) eq '1405');
return ( (hex(substr($rawData,28,2)) + hex(substr($rawData,30,2)) * 256) / 10 );
},
crcok => sub { my ($rawData,undef) = @_;
my $calcsum = SD_WS_crc16lsb(16, 0xA001, 0x86F4, $rawData);
my $checksum = hex(substr($rawData,32,2)) + hex(substr($rawData,34,2)) * 256;
if ($checksum == $calcsum) {
return 1;
} else {
Log3 $name, 3, "$name: SD_WS_123 Parse msg $msg - ERROR CRC16 $checksum != $calcsum";
return 0;
}
},
},
125 => {
# Temperature and humidity sensor Fine Offset WH31, aka Ambient Weather, aka ecowitt
# ------------------------------------------------------------------------------------------
# Byte: 00 01 02 03 04 05 06 07 08 09 10
# Nibble: 01 23 45 67 89 01 23 45 67 89 01
# aa aa aa 2d d4 30 02 82 62 37 04 51 6C 00 02 00
# MN;D= 30 02 82 62 37 04 51 6C 00 02 00 ;R=63; Temp: 21.0 C Hum: 55%, Battery: ok, ID: 0x02
# FF II CT TT HH XX SS ?? ?? ?? ??
# FF: Family code 0x31 = WH31e 0x37 = wh31b
# II: ID (1 byte)
# C : 3bit Channel Number Bit 17-19, 1 Bit Battery bit 20
# TT: 10 bits Temperature in C, scaled by 10, offset 400. Start at Bit 22
# HH: Humidity in percent as two diget hex
# XX: CRC8 of the preceding 5 bytes (Polynomial 0x31, Initial value 0x00, Input not reflected, Result not reflected)
# SS: Sum-8 of the preceding 5 bytes
# ??: Unknown Data
sensortype => 'WH31e, WH31b, DP50',
model => 'SD_WS_125_TH',
prematch => sub {my ($rawData,undef) = @_; return 1 if ($rawData =~ /^(30|37)/); },
id => sub {my ($rawData,undef) = @_; return (substr($rawData,2,2));},
channel => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,17,19) + 1);},
bat => sub {my (undef,$bitData) = @_; return substr($bitData,20,1) eq '0' ? 'ok' : 'low';},
temp => sub {my (undef,$bitData) = @_;
my $temp = SD_WS_binaryToNumber($bitData,22,31);
return FHEM::Core::Utils::Math::round(($temp - 400) / 10, 1);
},
hum => sub { my ($rawData,undef) = @_; return hex(substr($rawData,8,2)); },
crcok => sub { my ($rawData,undef) = @_;
if (HAS_DigestCRC) {
my $calc_crc8 = Digest::CRC->new(width => 8, poly=>0x31);
my $crc_digest = $calc_crc8->add( pack 'H*', substr( $rawData, 0, 12 ) )->digest;
if ($crc_digest)
{
Log3 $name, 3, qq[$name: SD_WS_125 Parse msg $rawData - ERROR CRC8 $crc_digest shoud be 0];
return 0;
}
} else {
Log3 $name, 1, qq[$name: SD_WS_125 Parse msg $rawData - ERROR CRC not loaded, please install module Digest::CRC];
}
my $checksum = 0;
for (my $i=0; $i < 11; $i += 2) {
$checksum += hex(substr($rawData,$i,2));
}
$checksum -= hex(substr($rawData,12,2));
$checksum &= 0xFF;
if ($checksum) {
Log3 $name, 3, qq[$name: SD_WS_125 Parse msg $rawData - ERROR checksum $checksum != 0];
return 0;
}
return 1;
},
},
126 => {
# rain gauge Fine Offset WH40, aka Ambient Weather, aka ecowitt
# ------------------------------------------------------------------------------------------
# Byte: 00 01 02 03 04 05 06 07 08 09 10 11 12 13
# Nibble: 01 23 45 67 89 01 23 45 67 89 01 23 45 67
# aa aa aa 2d d4 40 01 1C DF 8F 00 00 97 62 20 A6 80 28 01 -> ID 0x11cdf
# 40 01 3E 3C 90 00 00 10 5B A0 2A -> ID: 0x13e3c
# FF II II II BB RR RR XX AA ?? ?? ?? ?? ?? ?? ??
# FF: Family code always 0x40
# II: ID (1 byte)
# BB: Voltage of battery is representey by last 5 bits; voltage / 10 => 0F = 15 = 1.5v Not all models have battery reporting. Firest seen in late 2022
# RR: the rain bucket tip count => 0.1mm increments
# XX: CRC8 of the preceding 5 bytes (Polynomial 0x31, Initial value 0x00, Input not reflected, Result not reflected)
# SS: Sum-8 of the preceding 5 bytes
# ??: Unknown Data
sensortype => 'WH40',
model => 'SD_WS_126_R',
prematch => sub {my ($rawData,undef) = @_; return 1 if ($rawData =~ /^40/); },
id => sub {my ($rawData,undef) = @_; return (substr($rawData,2,6));},
rain_total => sub {my ($rawData,undef) = @_; return 0.1 * hex(substr($rawData,10,4)); },
rawRainCounter => sub {my ($rawData,undef) = @_; return hex(substr($rawData,10,4)); },
bat => sub { my (undef,$bitData) = @_;
my $v = oct(q[0b].substr($bitData,35,5));
return $v ne '0' ? $v > 11 ? 'ok' : 'low' : undef; },
batVoltage => sub { my (undef,$bitData) = @_;
my $v = oct(q[0b].substr($bitData,35,5));
return $v ne '0' ? $v / 10 : undef; },
crcok => sub { my ($rawData,undef) = @_;
if (HAS_DigestCRC) {
my $calc_crc8 = Digest::CRC->new(width => 8, poly=>0x31);
my $crc_digest = $calc_crc8->add( pack 'H*', substr( $rawData, 0, 16 ) )->digest;
if ($crc_digest)
{
Log3 $name, 3, qq[$name: SD_WS_126 Parse msg $rawData - ERROR CRC8 $crc_digest shoud be 0];
return 0;
}
} else {
Log3 $name, 1, qq[$name: SD_WS_126 Parse msg $rawData - ERROR CRC not loaded, please install module Digest::CRC];
}
my $checksum = 0;
for (my $i=0; $i < 15; $i += 2) {
$checksum += hex(substr($rawData,$i,2));
}
$checksum -= hex(substr($rawData,16,2));
$checksum &= 0xFF;
if ($checksum) {
Log3 $name, 3, qq[$name: SD_WS_126 Parse msg $rawData - ERROR checksum $checksum != 0];
return 0;
}
return 1;
},
},
129 => {
# Sainlogic weather station FT-0835
# ---------------------------------------------------------------
# Byte: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
# Nibble: 01 23 45 67 89 01 23 45 67 89 01 23 45 67 89 01
# aa aa aa 2d d4 FF D4 C0 E4 00 00 5F 00 00 83 FD 51 FF FB FB 6B
# PP PP PI IF SS GG DD RR RR FT TT HH BB BB UU CC
# P: 20 bit Preamble always 0xFFD4C
# I: 8 bit Ident
# F: 4 bit Flags: battery, MSB wind direction, MSB wind gust, MSB wind speed
# S: 8 bit LSB wind speed, in 1/10 m/s, resolution 0.1
# G: 8 bit LSB wind gust, in 1/10 m/s, resolution 0.1
# D: 8 bit LSB wind direction, in degree
# R: 16 bit rain counter, in 1/10 l/m², resolution 0.1
# F: 4 bit Flags: Sensors with brightness MSB brightness, 3 bit unknown, sensors without brightness always 0x8
# I: 12 bit Temperature, unsigned fahrenheit, offset by 400 and scaled by 10
# D: 8 bit Humidity, in percent
# R: 16 bit Brightness, sensors without brightness always 0xFFFB
# U: 8 bit UV, sensors without brightness always 0xFB
# C: 8 bit CRC8 over all 16 bytes must be 0
sensortype => 'FT-0835, FT0300, FT-0310, FT020T, WS019T',
model => 'SD_WS_129',
prematch => sub {my ($rawData,undef) = @_; return 1 if ($rawData =~ /^FFD4/);},
id => sub {my ($rawData,undef) = @_; return (substr($rawData,5,2));},
bat => sub {my (undef,$bitData) = @_; return substr($bitData,28,1) eq "0" ? "ok" : "low";},
windspeed => sub {my (undef,$bitData) = @_; return FHEM::Core::Utils::Math::round(((substr($bitData,31,1) * 256 + SD_WS_binaryToNumber($bitData,32,39)) / 10.0),1);},
windgust => sub {my (undef,$bitData) = @_; return FHEM::Core::Utils::Math::round(((substr($bitData,30,1) * 256 + SD_WS_binaryToNumber($bitData,40,47)) / 10.0),1);},
winddir => sub {my (undef,$bitData) = @_;
$winddir = substr($bitData,29,1) * 256 + SD_WS_binaryToNumber($bitData,48,55);
return ($winddir * 1, $winddirtxtar[FHEM::Core::Utils::Math::round(($winddir / 22.5),0)]);
},
rain => sub {my ($rawData,undef) = @_; return 0.1 * hex(substr($rawData,14,4));},
temp => sub {my (undef,$bitData) = @_; return FHEM::Core::Utils::Math::round(((SD_WS_binaryToNumber($bitData,76,87)) -320 - 400) * 5 / 90.0 , 1);},
hum => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,88,95);},
brightness => sub {my ($rawData,$bitData) = @_;
return if (substr($rawData,28,2) eq 'FB');
return (substr($bitData,72,1) * 65536 + SD_WS_binaryToNumber($bitData,96,111));
},
uv => sub {my ($rawData,undef) = @_;
return if (substr($rawData,28,2) eq 'FB');
return 0.1 * hex(substr($rawData,28,2));
},
crcok => sub { my ($rawData,undef) = @_;
if (HAS_DigestCRC) {
my $calc_crc8 = Digest::CRC->new(width => 8, init=>0xC0, poly=>0x31);
my $crc_digest = $calc_crc8->add( pack 'H*', substr( $rawData, 4, 28 ) )->digest;
if ($crc_digest) {
Log3 $name, 3, qq[$name: SD_WS_129 Parse msg $rawData - ERROR CRC8 $crc_digest shoud be 0];
return 0;
}
} else {
Log3 $name, 1, qq[$name: SD_WS_129 Parse msg - ERROR CRC not loaded, please install module Digest::CRC];
}
return 1;
},
},
); );
Log3 $name, 4, "$name: SD_WS_Parse protocol $protocol, rawData $rawData"; Log3 $name, 4, "$name: SD_WS_Parse protocol $protocol, rawData $rawData";
@ -1835,7 +2060,6 @@ sub SD_WS_Parse {
# temperature # temperature
my $oldTemp = ReadingsVal($name, 'temperature', undef); my $oldTemp = ReadingsVal($name, 'temperature', undef);
if (defined $oldTemp && defined $temp) { if (defined $oldTemp && defined $temp) {
#my $oldTemp = ReadingsVal($name, "temperature", undef);
my $maxdeviation = AttrVal($name, "max-deviation-temp", 1); # default 1 K my $maxdeviation = AttrVal($name, "max-deviation-temp", 1); # default 1 K
my $diffTemp = abs($temp - $oldTemp); my $diffTemp = abs($temp - $oldTemp);
$diffTemp = sprintf("%.1f", $diffTemp); $diffTemp = sprintf("%.1f", $diffTemp);
@ -1851,7 +2075,6 @@ sub SD_WS_Parse {
# humidity # humidity
my $oldHum = ReadingsVal($name, 'humidity', undef); my $oldHum = ReadingsVal($name, 'humidity', undef);
if (defined $oldHum && defined $hum) { if (defined $oldHum && defined $hum) {
#my $oldHum = ReadingsVal($name, "humidity", undef);
my $maxdeviation = AttrVal($name, "max-deviation-hum", 1); # default 1 % my $maxdeviation = AttrVal($name, "max-deviation-hum", 1); # default 1 %
my $diffHum = abs($hum - $oldHum); my $diffHum = abs($hum - $oldHum);
$diffHum = sprintf("%.1f", $diffHum); $diffHum = sprintf("%.1f", $diffHum);
@ -1965,7 +2188,7 @@ sub SD_WS_Parse {
readingsBulkUpdate($hash, "temperatureTrend", $trendTemp) if (defined($trendTemp) && length($trendTemp) > 0); readingsBulkUpdate($hash, "temperatureTrend", $trendTemp) if (defined($trendTemp) && length($trendTemp) > 0);
readingsBulkUpdate($hash, "humidityTrend", $trendHum) if (defined($trendHum) && length($trendHum) > 0); readingsBulkUpdate($hash, "humidityTrend", $trendHum) if (defined($trendHum) && length($trendHum) > 0);
readingsBulkUpdate($hash, "sendmode", $sendmode) if (defined($sendmode) && length($sendmode) > 0); readingsBulkUpdate($hash, "sendmode", $sendmode) if (defined($sendmode) && length($sendmode) > 0);
readingsBulkUpdate($hash, "type", $SensorTyp, 0) if (defined($SensorTyp)); readingsBulkUpdateIfChanged($hash, "type", $SensorTyp, 0) if (defined($SensorTyp));
readingsBulkUpdate($hash, "beep", $beep) if (defined($beep)); readingsBulkUpdate($hash, "beep", $beep) if (defined($beep));
readingsBulkUpdate($hash, "adc", $adc) if (defined($adc)); readingsBulkUpdate($hash, "adc", $adc) if (defined($adc));
readingsBulkUpdate($hash, 'rain', $rain) if (defined($rain)); readingsBulkUpdate($hash, 'rain', $rain) if (defined($rain));
@ -2013,6 +2236,27 @@ sub SD_WS_LFSR_digest8_reflect {
return $sum; return $sum;
} }
sub SD_WS_crc16lsb {
my ($nBytes, $polynomial, $init, $rawData) = @_;
carp "SD_WS_crc16lsb, too few arguments ($nBytes, $polynomial, $init, $rawData)" if @_ < 4;
my $remainder = $init;
my $byte;
my $bit;
my $data;
for ($byte = 0; $byte < $nBytes; ++$byte) {
$data = hex(substr($rawData, $byte * 2, 2));
$remainder ^= $data;
for ($bit = 0; $bit < 8; ++$bit) {
if ($remainder & 1) {
$remainder = ($remainder >> 1) ^ $polynomial;
} else {
$remainder = ($remainder >> 1);
}
}
}
return $remainder;
}
############################# #############################
sub SD_WS_bin2dec { sub SD_WS_bin2dec {
my $h = shift // return; my $h = shift // return;
@ -2080,13 +2324,17 @@ sub SD_WS_WH2SHIFT {
<li>EuroChron EFTH-800, EFS-3110A (temperature and humidity sensor)</li> <li>EuroChron EFTH-800, EFS-3110A (temperature and humidity sensor)</li>
<li>Fine Offset WH51, aka ECOWITT WH51, aka Froggit DP100, aka MISOL/1 (soil moisture sensor)</li> <li>Fine Offset WH51, aka ECOWITT WH51, aka Froggit DP100, aka MISOL/1 (soil moisture sensor)</li>
<li>Fine Offset WH57, aka Froggit DP60, aka Ambient Weather WH31L (thunder and lightning sensor)</li> <li>Fine Offset WH57, aka Froggit DP60, aka Ambient Weather WH31L (thunder and lightning sensor)</li>
<li>Fine Offset WH31, aka Ambient Weather WH31E, aka ecowitt WH31 (temperature and humidity sensor)</li>
<li>Fine Offset WH40, aka Ambient Weather WH40, aka ecowitt WH40 (Regen sensor)</li>
<li>Fody E42 (temperature and humidity sensor)</li> <li>Fody E42 (temperature and humidity sensor)</li>
<li>Inkbird IBS-P01R pool thermometer, ITH-20R</li>
<li>NC-3911, NC-3912 refrigerator thermometer</li> <li>NC-3911, NC-3912 refrigerator thermometer</li>
<li>Opus XT300</li> <li>Opus XT300</li>
<li>PV-8644 infactory Poolthermometer</li> <li>PV-8644 infactory Poolthermometer</li>
<li>Renkforce E0001PA</li> <li>Renkforce E0001PA</li>
<li>Rain gauge DROP TFA 47.3005.01 with rain sensor TFA 30.3233.01</li> <li>Rain gauge DROP TFA 47.3005.01 with rain sensor TFA 30.3233.01</li>
<li>TECVANCE TV-4848</li> <li>TECVANCE TV-4848</li>
<li>Sainlogic Weather stations FT-0835, FT0300, FT-0310, FT020T, WS019T</li>
<li>Thermometer FT007T, FT007TP, F007T, F007TP</li> <li>Thermometer FT007T, FT007TP, F007T, F007TP</li>
<li>Thermo-Hygrometer FT007TH, F007TH</li> <li>Thermo-Hygrometer FT007TH, F007TH</li>
<li>TS-FT002 Water tank level monitor with temperature</li> <li>TS-FT002 Water tank level monitor with temperature</li>
@ -2220,13 +2468,17 @@ sub SD_WS_WH2SHIFT {
<li>EuroChron EFTH-800, EFS-3110A (Temperatur- und Feuchtigkeitssensor)</li> <li>EuroChron EFTH-800, EFS-3110A (Temperatur- und Feuchtigkeitssensor)</li>
<li>Fine Offset WH51, aka ECOWITT WH51, aka Froggit DP100, aka MISOL/1 (Bodenfeuchtesensor)</li> <li>Fine Offset WH51, aka ECOWITT WH51, aka Froggit DP100, aka MISOL/1 (Bodenfeuchtesensor)</li>
<li>Fine Offset WH57, aka Froggit DP60, aka Ambient Weather WH31L (Gewittersensor)</li> <li>Fine Offset WH57, aka Froggit DP60, aka Ambient Weather WH31L (Gewittersensor)</li>
<li>Fine Offset WH31, aka Ambient Weather WH31E Thermo-Hygrometer Sensor (Temperatur- und Feuchtemsser)</li>
<li>Fine Offset WH40, aka Ambient Weather WH40, aka ecowitt WH40 (rain sensor)</li>
<li>Fody E42 (Temperatur- und Feuchtigkeitssensor)</li> <li>Fody E42 (Temperatur- und Feuchtigkeitssensor)</li>
<li>Inkbird IBS-P01R Pool Thermometer, ITH-20R</li>
<li>Kabelloses Grillthermometer, Modellname: GFGT 433 B1</li> <li>Kabelloses Grillthermometer, Modellname: GFGT 433 B1</li>
<li>NC-3911, NC-3912 digitales Kuehl- und Gefrierschrank-Thermometer</li> <li>NC-3911, NC-3912 digitales Kuehl- und Gefrierschrank-Thermometer</li>
<li>Opus XT300</li> <li>Opus XT300</li>
<li>PV-8644 infactory Poolthermometer</li> <li>PV-8644 infactory Poolthermometer</li>
<li>Regenmesser DROP TFA 47.3005.01 mit Regensensor TFA 30.3233.01</li> <li>Regenmesser DROP TFA 47.3005.01 mit Regensensor TFA 30.3233.01</li>
<li>Renkforce E0001PA</li> <li>Renkforce E0001PA</li>
<li>Sainlogic Wetterstationen FT-0835, FT0300, FT-0310, FT020T, WS019T</li>
<li>TECVANCE TV-4848</li> <li>TECVANCE TV-4848</li>
<li>Temperatur-Sensor FT007T, FT007TP, F007T, F007TP</li> <li>Temperatur-Sensor FT007T, FT007TP, F007T, F007TP</li>
<li>Temperatur/Feuchte-Sensor FT007TH, F007TH</li> <li>Temperatur/Feuchte-Sensor FT007TH, F007TH</li>