############################################## # $Id$ # # The purpose of this module is to support serval # weather sensors like WS-0101 (Sender 868MHz ASK Epmfänger RX868SH-DV elv) # # 2015 Sidey79, pejonp # 2019 Ralf9 # 2020 Sidey79, HomeAutoUser # # 20170922: rainTotal --> rain_total # 20170923: windDirAverage - https://forum.fhem.de/index.php/topic,75225.msg669950.html#msg669950 @SabineT # 20191003: UV/Solar Nachrichten WH2315 - https://forum.fhem.de/index.php/topic,67587.msg980092.html#msg980092 @Ralf # 20200126: PERL WARNING - https://forum.fhem.de/index.php/topic,67587.msg982425.html#msg982425 @rob # 20200127: Corrected line indents @HomeAutoUser # 20200127: fix, WindDirAverage return undef --> return $windDirection_old @HomeAutoUser # 20200127: revised commandref @HomeAutoUser # package main; use strict; use warnings; # werden benötigt, aber im Programm noch extra abgetestet #use Digest::CRC qw(crc); #use Math::Trig; sub SD_WS09_Initialize($) { my ($hash) = @_; $hash->{Match} = "^P9#F[A-Fa-f0-9]+"; ## pos 7 ist aktuell immer 0xF $hash->{DefFn} = "SD_WS09_Define"; $hash->{UndefFn} = "SD_WS09_Undef"; $hash->{ParseFn} = "SD_WS09_Parse"; $hash->{AttrFn} = "SD_WS09_Attr"; $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:0,1 showtime:1,0 " ."model:CTW600,WH1080 ignore:0,1 " ."windKorrektur:-3,-2,-1,0,1,2,3 " ."Unit_of_Wind:m/s,km/h,ft/s,mph,bft,knot " ."WindDirAverageTime " ."WindDirAverageMinSpeed " ."WindDirAverageDecay " ."$readingFnAttributes "; $hash->{AutoCreate} = { "SD_WS09.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.* windKorrektur:.*:0 verbose:5" , FILTER => "%NAME", GPLOT => "WH1080wind4:windSpeed/windGust,", autocreateThreshold => "2:180"} }; } ############################# sub SD_WS09_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); return "wrong syntax: define SD_WS09 ".int(@a) if(int(@a) < 3 ); $hash->{CODE} = $a[2]; $hash->{lastMSG} = ""; $hash->{bitMSG} = ""; $modules{SD_WS09}{defptr}{$a[2]} = $hash; $hash->{STATE} = "Defined"; my $model = $a[2]; $model =~ s/_.*$//; $hash->{MODEL} = $model; my $name= $hash->{NAME}; return ; } ##################################### sub SD_WS09_Undef($$) { my ($hash, $name) = @_; delete($modules{SD_WS09}{defptr}{$hash->{CODE}}) if(defined($hash->{CODE}) && defined($modules{SD_WS09}{defptr}{$hash->{CODE}})); return ; } ################################### sub SD_WS09_Parse($$) { my ($iohash, $msg) = @_; my $name = $iohash->{NAME}; my (undef ,$rawData) = split("#",$msg); my @winddir_name=("N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW"); my %uowind_unit= ("m/s",'1',"km/h",'3.6',"ft/s",'3.28',"bft",'-1',"mph",'2.24',"knot",'1.94'); my %uowind_index = ("m/s",'0',"km/h",'1',"ft/s",'2',"mph",'3',"knot",'4',"bft",'5'); my $hlen = length($rawData); my $blen = $hlen * 4; my $bitData = unpack("B$blen", pack("H$hlen", $rawData)); my $bitData2; my $bitData20; my $rain = 0; my $deviceCode = 0; my $model = "undef"; # 0xFFA -> WS0101/WH1080 alles andere -> CTW600 my $modelid; my $windSpeed = 0; my $windSpeed_kmh; my $windSpeed_fts; my $windSpeed_bft; my $windSpeed_mph; my $windSpeed_kn; my $windguest = 0; my $windguest_kmh; my $windguest_fts; my $windguest_bft; my $windguest_mph; my $windguest_kn; my $sensdata; my $id; my $bat = 0; my $temp = 0; my $hum = 1; my $windDirection = 1 ; my $windDirectionText = "N"; my $windDirectionDegree = 0; my $FOuvo ; # UV data nybble ? my $FOlux ; # Lux High byte (full scale = 4,000,000?) # Lux Middle byte # Lux Low byte, Unit = 0.1 Lux (binary) my $rr2 ; my $state; my $msg_vor = 'P9#'; my $minL1 = 70; my $minL2 = 60; my $whid; my $wh; my $rawData_merk; my $wfaktor = 1; my @windstat; my $syncpos; if ($hlen < 20) { # WH3080 UV/Solar $syncpos = index($bitData,"1111110111"); # FF7 UV/Solar if ($syncpos >= 0 && $syncpos < 3) { $model = "WH1080"; if ($syncpos < 2) { $rawData = SD_WS09_SHIFT($iohash, $rawData); Log3 $iohash, 4, "$name: SD_WS09_Parse_SHIFT_0 raw:$rawData"; } if ($syncpos == 0) { $rawData = SD_WS09_SHIFT($iohash, $rawData); Log3 $iohash, 4, "$name: SD_WS09_Parse_SHIFT_1 raw:$rawData"; } } else { Log3 $iohash, 4, "$name: SD_WS09_Parse WH1080 EXIT (sync no found): msg=$rawData length:".length($bitData); return ""; } } else { $syncpos= index($bitData,"11111110"); #7x1 1x0 preamble Log3 $iohash, 4, "$name: SD_WS09_Parse0 msg=$rawData Bin=$bitData syncp=$syncpos length:".length($bitData) ; if ($syncpos ==-1 || length($bitData)-$syncpos < $minL2) { Log3 $iohash, 4, "$name: SD_WS09_Parse EXIT: msg=$rawData syncp=$syncpos length:".length($bitData) ; return ; } } my $crcwh1080 = AttrVal($iohash->{NAME},'WS09_CRCAUS',0); Log3 $iohash, 4, "$name: SD_WS09_Parse CRC_AUS:$crcwh1080 " ; $rawData_merk = $rawData; my $rc = eval { require Digest::CRC; Digest::CRC->import(); 1; }; # test ob Digest::CRC geladen if($rc) { $rr2 = SD_WS09_CRCCHECK($rawData); if ($model eq "WH1080") { # FF7 UV/Solar if ($rr2 != 0) { Log3 $iohash, 4, "$name: SD_WS09_Parse WH1080: sensorTyp: 7 UV/Solar, CRC_Error Exit: raw:$rawData CRC=$rr2"; return ""; } } else { if ($rr2 == 0 || (($rr2 == 49) && ($crcwh1080 == 2)) ) { # 1. OK $model = "WH1080"; Log3 $iohash, 4, "$name: SD_WS09_Parse_SHIFT_0 OK raw:$rawData crc:$rr2"; } else { # 1. nok Log3 $iohash, 4, "$name: SD_WS09_Parse_SHIFT_1 NOK raw:$rawData crc:$rr2 -> shift"; $rawData = SD_WS09_SHIFT($iohash, $rawData); $rr2 = SD_WS09_CRCCHECK($rawData); if ($rr2 == 0 || (($rr2 == 49) && ($crcwh1080 == 2)) ) { # 2.ok $msg = $msg_vor.$rawData; $model = "WH1080"; Log3 $iohash, 4, "$name: SD_WS09_Parse_SHIFT_2 OK raw:$rawData msg:$msg crc:$rr2"; } else { # 2. nok Log3 $iohash, 4, "$name: SD_WS09_Parse_SHIFT_3 NOK raw:$rawData crc:$rr2 -> shift"; $rawData = SD_WS09_SHIFT($iohash, $rawData); $rr2 = SD_WS09_CRCCHECK($rawData); if ($rr2 == 0 || (($rr2 == 49) && ($crcwh1080 == 2)) ) { # 3. ok $msg = $msg_vor.$rawData; $model = "WH1080"; Log3 $iohash, 4, "$name: SD_WS09_Parse_SHIFT_4 OK raw:$rawData msg:$msg crc:$rr2"; } else { # 3. nok $rawData = $rawData_merk; $msg = $msg_vor.$rawData; Log3 $iohash, 4, "$name: SD_WS09_Parse_SHIFT_5 NOK raw:$rawData msg:$msg crc:$rr2"; } } } } } else { # Digest::CRC failed Log3 $iohash, 1, "$name: SD_WS09 CRC_not_load: Modul Digest::CRC fehlt: cpan install Digest::CRC or sudo apt-get install libdigest-crc-perl" ; return ""; } $hlen = length($rawData); $blen = $hlen * 4; $bitData = unpack("B$blen", pack("H$hlen", $rawData)); Log3 $iohash, 4, "$name: SD_WS09_CRC_test2 rwa:$rawData msg:$msg CRC:".SD_WS09_CRCCHECK($rawData) ; if( $model eq "WH1080") { $sensdata = substr($bitData,8); $whid = substr($sensdata,0,4); Log3 $iohash, 5, "$name: SD_WS09_Parse_0 whid=$whid"; # A Wettermeldungen if( $whid eq '1010' ){ Log3 $iohash, 4, "$name: SD_WS09_Parse_1 msg=$sensdata length:".length($sensdata) ; $model = "WH1080"; $id = SD_WS09_bin2dec(substr($sensdata,4,8)); $bat = (SD_WS09_bin2dec((substr($sensdata,64,4))) == 0) ? 'ok':'low' ; # decode battery = 0 --> ok $temp = (SD_WS09_bin2dec(substr($sensdata,12,12)) - 400)/10; $hum = SD_WS09_bin2dec(substr($sensdata,24,8)); $windDirection = SD_WS09_bin2dec(substr($sensdata,68,4)); $windDirectionText = $winddir_name[$windDirection]; $windDirectionDegree = $windDirection * 360 / 16; $windSpeed = round((SD_WS09_bin2dec(substr($sensdata,32,8))* 34)/100,01); Log3 $iohash, 4, "$name: SD_WS09_Parse_2 ".$model." id:$id, Windspeed bit: ".substr($sensdata,32,8)." Dec: " . $windSpeed ; $windguest = round((SD_WS09_bin2dec(substr($sensdata,40,8)) * 34)/100,01); Log3 $iohash, 4, "$name: SD_WS09_Parse_3 ".$model." id:$id, Windguest bit: ".substr($sensdata,40,8)." Dec: " . $windguest ; $rain = SD_WS09_bin2dec(substr($sensdata,52,12)) * 0.3; Log3 $iohash, 4, "$name: SD_WS09_Parse_4 ".$model." id:$id, Rain bit: ".substr($sensdata,52,12)." Dec: " . $rain ; Log3 $iohash, 4, "$name: SD_WS09_Parse_5 ".$model." id:$id, bat:$bat, temp=$temp, hum=$hum, winddir=$windDirection:$windDirectionText wS=$windSpeed, wG=$windguest, rain=$rain"; # B DCF-77 Zeitmeldungen vom Sensor } elsif ( $whid eq '1011' ) { my $hrs1 = substr($sensdata,16,8); my $hrs; my $mins; my $sec; my $mday; my $month; my $year; $id = SD_WS09_bin2dec(substr($sensdata,4,8)); Log3 $iohash, 4, "$name: SD_WS09_Parse_6 Zeitmeldung0: HRS1=$hrs1 id:$id" ; $hrs = sprintf "%02d" , SD_WS09_BCD2bin(substr($sensdata,18,6) ) ; # Stunde $mins = sprintf "%02d" , SD_WS09_BCD2bin(substr($sensdata,24,8)); # Minute $sec = sprintf "%02d" ,SD_WS09_BCD2bin(substr($sensdata,32,8)); # Sekunde #day month year $year = SD_WS09_BCD2bin(substr($sensdata,40,8)); # Jahr $month = sprintf "%02d" ,SD_WS09_BCD2bin(substr($sensdata,51,5)); # Monat $mday = sprintf "%02d" ,SD_WS09_BCD2bin(substr($sensdata,56,8)); # Tag Log3 $iohash, 4, "$name: SD_WS09_Parse_7 Zeitmeldung1: id:$id, msg=$rawData length:".length($bitData) ; Log3 $iohash, 4, "$name: SD_WS09_Parse_8 Zeitmeldung2: id:$id, HH:mm:ss - ".$hrs.":".$mins.":".$sec ; Log3 $iohash, 4, "$name: SD_WS09_Parse_9 Zeitmeldung3: id:$id, dd.mm.yy - ".$mday.".".$month.".".$year ; return $name; # 7 UV/Solar Meldungen vom Sensor } elsif ( $whid eq '0111' ) { # Fine Offset (Solar Data) message BYTE offsets (within receive buffer) # Examples= FF 75 B0 55 00 97 8E 0E *CRC*OK* # =FF 75 B0 55 00 8F BE 92 *CRC*OK* # symbol FOrunio = 0 ; Fine Offset Runin byte = FF # symbol FOsaddo = 1 ; Solar Pod address word # symbol FOuvo = 3 ; UV data nybble ? # symbol FOluxHo = 4 ; Lux High byte (full scale = 4,000,000?) # symbol FOluxMo = 5 ; Lux Middle byte # symbol FOluxLo = 6 ; Lux Low byte, Unit = 0.1 Lux (binary) # symbol FOcksumo= 7 ; CRC checksum (CRC-8 shifting left) $id = SD_WS09_bin2dec(substr($sensdata,4,8)); $FOuvo = SD_WS09_bin2dec(substr($sensdata,12,4)); $FOlux = SD_WS09_bin2dec(substr($sensdata,24,24))/10; Log3 $iohash, 4, "$name: SD_WS09_Parse_10 UV-Solar1: id:$id, UV:".$FOuvo." Lux:".$FOlux ; } else { Log3 $iohash, 4, "$name: SD_WS09_Parse_Ex Exit: msg=$rawData length:".length($sensdata) ; Log3 $iohash, 4, "$name: SD_WS09_WH10 Exit: Model=$model " ; return ; } } else { # es wird eine CTW600 angenommen $syncpos= index($bitData,'11111110'); #7x1 1x0 preamble $wh = substr($bitData,0,8); if ( $wh eq '11111110' && length($bitData) > $minL1 ) { Log3 $iohash, 4, "$name: SD_WS09_Parse_11 CTW600 EXIT: msg=$bitData wh:$wh length:".length($bitData) ; $sensdata = substr($bitData,$syncpos+8); Log3 $iohash, 4, "$name: SD_WS09_Parse_12 CTW WH=$wh msg=$sensdata syncp=$syncpos length:".length($sensdata) ; $model = "CTW600"; $whid = "0000"; my $nn1 = substr($sensdata,10,2); # Keine Bedeutung my $nn2 = substr($sensdata,62,4); # Keine Bedeutung $modelid = substr($sensdata,0,4); Log3 $iohash, 4, "$name: SD_WS09_Parse_13 Id: ".$modelid." NN1:$nn1 NN2:$nn2" ; Log3 $iohash, 4, "$name: SD_WS09_Parse_14 Id: ".$modelid." Bin-Sync=$sensdata syncp=$syncpos length:".length($sensdata) ; $bat = SD_WS09_bin2dec((substr($sensdata,0,3))) ; $id = SD_WS09_bin2dec(substr($sensdata,4,6)); $temp = (SD_WS09_bin2dec(substr($sensdata,12,10)) - 400)/10; $hum = SD_WS09_bin2dec(substr($sensdata,22,8)); $windDirection = SD_WS09_bin2dec(substr($sensdata,66,4)); $windDirectionText = $winddir_name[$windDirection]; $windDirectionDegree = $windDirection * 360 / 16; $windSpeed = round(SD_WS09_bin2dec(substr($sensdata,30,16))/240,01); Log3 $iohash, 4, "$name: SD_WS09_Parse_15 ".$model." Windspeed bit: ".substr($sensdata,32,8)." Dec: " . $windSpeed ; $windguest = round((SD_WS09_bin2dec(substr($sensdata,40,8)) * 34)/100,01); Log3 $iohash, 4, "$name: SD_WS09_Parse_16 ".$model." Windguest bit: ".substr($sensdata,40,8)." Dec: " . $windguest ; $rain = round(SD_WS09_bin2dec(substr($sensdata,46,16)) * 0.3,01); Log3 $iohash, 4, "$name: SD_WS09_Parse_17 ".$model." Rain bit: ".substr($sensdata,46,16)." Dec: " . $rain ; } else { Log3 $iohash, 4, "$name: SD_WS09_Parse_18 CTW600 EXIT: msg=$bitData length:".length($bitData) ; return ; } } Log3 $iohash, 4, "$name: SD_WS09_Parse_19 ".$model." id:$id :$sensdata "; if($hum > 100 || $hum < 0) { Log3 $iohash, 4, "$name: SD_WS09_Parse HUM: hum=$hum msg=$rawData " ; return ; } if($temp > 60 || $temp < -40) { Log3 $iohash, 4, "$name: SD_WS09_Parse TEMP: Temp=$temp msg=$rawData " ; return ; } my $longids = AttrVal($iohash->{NAME},'longids',0); if ( ($longids ne "0") && ($longids eq "1" || $longids eq "ALL" || (",$longids," =~ m/,$model,/))) { $deviceCode=$model."_".$id; Log3 $iohash,4, "$name: SD_WS09_Parse using longid: $longids model: $model"; } else { $deviceCode = $model; } my $def = $modules{SD_WS09}{defptr}{$iohash->{NAME} . "." . $deviceCode}; $def = $modules{SD_WS09}{defptr}{$deviceCode} if(!$def); if(!$def) { Log3 $iohash, 1, 'SD_WS09_Parse UNDEFINED sensor ' . $model . ' detected, code ' . $deviceCode; return "UNDEFINED $deviceCode SD_WS09 $deviceCode"; } my $hash = $def; $name = $hash->{NAME}; Log3 $name, 4, "SD_WS09_Parse_20: $name ($rawData)"; if (!defined(AttrVal($name,"event-min-interval",undef))) { my $minsecs = AttrVal($iohash->{NAME},'minsecs',0); if($hash->{lastReceive} && (time() - $hash->{lastReceive} < $minsecs)) { Log3 $hash, 4, "SD_WS09_Parse_End $deviceCode Dropped due to short time. minsecs=$minsecs"; return ; } } my $windkorr = AttrVal($name,'windKorrektur',0); if ($windkorr != 0 ) { my $oldwinddir = $windDirection; $windDirection = $windDirection + $windkorr; $windDirectionText = $winddir_name[$windDirection]; Log3 $hash, 4, "SD_WS09_Parse_WK ".$model." Faktor:$windkorr wD:$oldwinddir Korrektur wD:$windDirection:$windDirectionText" ; } # "Unit_of_Wind:m/s,km/h,ft/s,bft,knot " # my %uowind_unit= ("m/s",'1',"km/h",'3.6',"ft/s",'3.28',"bft",'-1',"mph",'2.24',"knot",'1.94'); # B = Wurzel aus ( 9 + 6 * V ) - 3 # V = 17 Meter pro Sekunde ergibt: B = Wurzel aus( 9 + 6 * 17 ) - 3 # Das ergibt : 7,53 Beaufort $windstat[0]= " Ws:$windSpeed Wg:$windguest m/s"; Log3 $hash, 4, "SD_WS09_Wind $windstat[0] : Faktor:$wfaktor" ; $wfaktor = $uowind_unit{"km/h"}; $windguest_kmh = round ($windguest * $wfaktor,01); $windSpeed_kmh = round ($windSpeed * $wfaktor,01); $windstat[1]= " Ws:$windSpeed_kmh Wg:$windguest_kmh km/h"; Log3 $hash, 4, "SD_WS09_Wind $windstat[1] : Faktor:$wfaktor" ; $wfaktor = $uowind_unit{"ft/s"}; $windguest_fts = round ($windguest * $wfaktor,01); $windSpeed_fts = round ($windSpeed * $wfaktor,01); $windstat[2]= " Ws:$windSpeed_fts Wg:$windguest_fts ft/s"; Log3 $hash, 4, "SD_WS09_Wind $windstat[2] : Faktor:$wfaktor" ; $wfaktor = $uowind_unit{"mph"}; $windguest_mph = round ($windguest * $wfaktor,01); $windSpeed_mph = round ($windSpeed * $wfaktor,01); $windstat[3]= " Ws:$windSpeed_mph Wg:$windguest_mph mph"; Log3 $hash, 4, "SD_WS09_Wind $windstat[3] : Faktor:$wfaktor" ; $wfaktor = $uowind_unit{"knot"}; $windguest_kn = round ($windguest * $wfaktor,01); $windSpeed_kn = round ($windSpeed * $wfaktor,01); $windstat[4]= " Ws:$windSpeed_kn Wg:$windguest_kn kn" ; Log3 $hash, 4, "SD_WS09_Wind $windstat[4] : Faktor:$wfaktor" ; $windguest_bft = round(sqrt( 9 + (6 * $windguest)) - 3,0) ; $windSpeed_bft = round(sqrt( 9 + (6 * $windSpeed)) - 3,0) ; $windstat[5]= " Ws:$windSpeed_bft Wg:$windguest_bft bft"; Log3 $hash, 4, "SD_WS09_Wind $windstat[5] " ; # Resets des rain counters abfangen: # wenn der aktuelle Wert < letzter Wert ist, dann fand ein reset statt # die Differenz "letzer Wert - aktueller Wert" wird dann als offset für zukünftige Ausgaben zu rain addiert # offset wird auch im Reading ".rain_offset" gespeichert my $last_rain = ReadingsVal($name, "rain", 0); my $rain_offset = ReadingsVal($name, ".rainOffset", 0); $rain_offset += $last_rain if($rain < $last_rain); my $rain_total = $rain + $rain_offset; Log3 $hash, 4, "SD_WS09_Parse_rain_offset ".$model." rain:$rain raintotal:$rain_total rainoffset:$rain_offset " ; # windDirectionAverage berechnen my $average = SD_WS09_WindDirAverage($hash, $windSpeed, $windDirectionDegree); $hash->{lastReceive} = time(); $def->{lastMSG} = $rawData; readingsBeginUpdate($hash); if($whid ne "0111") { #my $uowind = AttrVal($hash->{NAME},'Unit_of_Wind',0) ; my $uowind = AttrVal($name,'Unit_of_Wind',0) ; my $windex = $uowind_index{$uowind}; if (!defined $windex) { $windex = 0; } $state = "T: $temp ". ($hum>0 ? " H: $hum ":" "). $windstat[$windex]." Wd: $windDirectionText "." R: $rain"; readingsBulkUpdate($hash, "id", $id) if ($id ne ""); readingsBulkUpdate($hash, "state", $state); readingsBulkUpdate($hash, "temperature", $temp) if ($temp ne""); readingsBulkUpdate($hash, "humidity", $hum) if ($hum ne "" && $hum != 0 ); readingsBulkUpdate($hash, "battery", $bat) if ($bat ne ""); readingsBulkUpdate($hash, "batteryState", $bat) if ($bat ne ""); #zusätzlich Daten für Wetterstation readingsBulkUpdate($hash, "rain", $rain ); readingsBulkUpdate($hash, ".rainOffset", $rain_offset ); # Zwischenspeicher für den offset readingsBulkUpdate($hash, "rain_total", $rain_total ); # monoton steigender Wert von rain readingsBulkUpdate($hash, "windGust", $windguest ); readingsBulkUpdate($hash, "windSpeed", $windSpeed ); readingsBulkUpdate($hash, "windGust_kmh", $windguest_kmh ); readingsBulkUpdate($hash, "windSpeed_kmh", $windSpeed_kmh ); readingsBulkUpdate($hash, "windGust_fts", $windguest_fts ); readingsBulkUpdate($hash, "windSpeed_fts", $windSpeed_fts ); readingsBulkUpdate($hash, "windGust_mph", $windguest_mph ); readingsBulkUpdate($hash, "windSpeed_mph", $windSpeed_mph ); readingsBulkUpdate($hash, "windGust_kn", $windguest_kn ); readingsBulkUpdate($hash, "windSpeed_kn", $windSpeed_kn ); readingsBulkUpdate($hash, "windDirectionAverage", $average ); readingsBulkUpdate($hash, "windDirection", $windDirection ); readingsBulkUpdate($hash, "windDirectionDegree", $windDirectionDegree); readingsBulkUpdate($hash, "windDirectionText", $windDirectionText ); } if (($whid eq "0111") && ($model eq "WH1080")) { $state = "UV: $FOuvo Lux: $FOlux "; readingsBulkUpdate($hash, "id", $id) if ($id ne ""); readingsBulkUpdate($hash, "state", $state); #zusätzliche Daten UV + Lux readingsBulkUpdate($hash, "UV", $FOuvo ); readingsBulkUpdate($hash, "Lux", $FOlux ); } readingsEndUpdate($hash, 1); # Notify is done by Dispatch return $name; } ################################### sub SD_WS09_Attr(@) { my @a = @_; # Make possible to use the same code for different logical devices when they # are received through different physical devices. return if($a[0] ne "set" || $a[2] ne "IODev"); my $hash = $defs{$a[1]}; my $iohash = $defs{$a[3]}; my $cde = $hash->{CODE}; delete($modules{SD_WS09}{defptr}{$cde}); $modules{SD_WS09}{defptr}{$iohash->{NAME} . "." . $cde} = $hash; return ; } ################################### sub SD_WS09_WindDirAverage { ############################################################################### # übernommen von SabineT https://forum.fhem.de/index.php/topic,75225.msg669950.html#msg669950 # WindDirAverage # z.B.: myWindDirAverage('WH1080','windSpeed','windDirectionDegree',900,0.75,0.5) # avtime ist optional, default ist 600 s Zeitspanne, die berücksichtig werden soll # decay ist optional, default ist 1 Parameter, um ältere Werte geringer zu gewichten # minspeed ist optional, default ist 0 m/s # # Als Ergebnis wird die Windrichtung zurück geliefert, die aus dem aktuellen und # vergangenen Werten über eine Art exponentiellen Mittelwert berechnet werden. # Dabei wird zusätzlich die jeweilige Windgeschwindigkeit mit berücksichtigt (höhere Geschwindigkeit # bedeutet höhere Gewichtung). # # decay: 1 -> alle Werte werden gleich gewichtet # 0 -> nur der aktuelle Wert wird verwendet. # in der Praxis wird man Werte so um 0.75 nehmen # # minspeed: da bei sehr geringer Windgeschwindigkeit die Windrichtung üblicherweise nicht # eindeutig ist, kann mit minspeed ein Schwellwert angegeben werden # Ist die (gewichtetete) mittlere Geschwindigkeit < minspeed wird undef zurück geliefert # ############################################################################### my ($hash, $ws, $wd) = @_; return if ref($hash) ne 'HASH'; return if !defined $ws; return if !defined $wd; my $name = $hash->{NAME}; Log3 $hash, 4, "SD_WS09_WindDirAverage --- OK ----" ; my $rc = eval { require Math::Trig; Math::Trig->import(); 1; }; if($rc) # test ob Math::Trig geladen wurde { Log3 $hash, 4, "SD_WS09_WindDirAverage Math::Trig:OK" ; } else { Log3 $hash, 1, "SD_WS09_WindDirAverage Math::Trig:fehlt : cpan install Math::Trig" ; return ""; } my $avtime = AttrVal($name,'WindDirAverageTime',600); my $decay = AttrVal($name,'WindDirAverageDecay',0); my $minspeed = AttrVal($name,'WindDirAverageMinSpeed',0); my $windDirection_old = $wd; # default Werte für die optionalen Parameter, falls nicht beim Aufruf mit angegeben #$avtime = 600 if (!(defined $avtime) || $avtime == 0 ); $decay = 1 if (!(defined $decay)); $decay = 1 if ($decay > 1); # darf nicht >1 sein $decay = 0 if ($decay < 0); # darf nicht <0 sein #$minspeed = 0 if (!(defined $minspeed)); $wd = deg2rad($wd); my $ctime = CORE::time; my $time = FmtDateTime($ctime); my @new = ($ws,$wd,$time); Log3 $hash, 4,"SD_WS09_WindDirAverage_01 $name :Speed=".$ws." DirR=".round($wd,2)." Time=".$time; Log3 $hash, 4,"SD_WS09_WindDirAverage_02 $name :avtime=".$avtime." decay=".$decay." minspeed=".$minspeed; my $num; my $arr; #-- initialize if requested if( ($avtime eq "-1") ){ $hash->{helper}{history}=undef; } #-- test for existence if(!$hash->{helper}{history}){ Log3 $hash, 4,"SD_WS09_WindDirAverage_03 $name :ARRAY CREATED"; push(@{$hash->{helper}{history}},\@new); $num = 1; $arr=\@{$hash->{helper}{history}}; } else { $num = int(@{$hash->{helper}{history}}); $arr=\@{$hash->{helper}{history}}; my $stime = time_str2num($arr->[0][2]); # Zeitpunkt des ältesten Eintrags my $ltime = time_str2num($arr->[$num-1][2]); # Zeitpunkt des letzten Eintrags Log3 $hash,4,"SD_WS09_WindDirAverage_04 $name :Speed=".$ws." Dir=".round($wd,2)." Time=".$time." minspeed=".$minspeed." ctime=".$ctime." ltime=".$ltime." stime=".$stime." num=".$num; if((($ctime - $ltime) > 10) || ($num == 0)) { if(($num < 25) && (($ctime-$stime) < $avtime)){ Log3 $hash,4,"SD_WS09_WindDirAverage_05 $name :Speed=".$ws." Dir=".round($wd,2)." Time=".$time." minspeed=".$minspeed." num=".$num; push(@{$hash->{helper}{history}},\@new); } else { shift(@{$hash->{helper}{history}}); push(@{$hash->{helper}{history}},\@new); Log3 $hash,4,"SD_WS09_WindDirAverage_06 $name :Speed=".$ws." Dir=".round($wd,2)." Time=".$time." minspeed=".$minspeed." num=".$num; } } else { return $windDirection_old; # old undef | different behavior dispatch <-> UnitTest´s ($ctime - $ltime) } } #-- output and average my ($anz, $sanz) = 0; $num = int(@{$hash->{helper}{history}}); my ($sumSin, $sumCos, $sumSpeed, $age, $maxage, $weight) = 0; for(my $i=0; $i<$num; $i++) { ($ws, $wd, $time) = @{ $arr->[$i] }; $age = $ctime - time_str2num($time); if (($time eq "") || ($age > $avtime)) { #-- zu alte Einträge entfernen Log3 $hash,4,"SD_WS09_WindDirAverage_07 $name i=".$i." Speed=".round($ws,2)." Dir=".round($wd,2)." Time=".substr($time,11)." ctime=".$ctime." akt.=".time_str2num($time); shift(@{$hash->{helper}{history}}); $i--; $num--; } else { #-- Werte aufsummieren, Windrichtung gewichtet über Geschwindigkeit und decay/"alter" $weight = $decay ** ($age / $avtime); #-- für die Mittelwertsbildung der Geschwindigkeit wird nur ein 10tel von avtime genommen if ($age < ($avtime / 10)) { $sumSpeed += $ws * $weight ; $sanz++; } $sumSin += sin($wd) * $ws * $weight; $sumCos += cos($wd) * $ws * $weight; $anz++; Log3 $hash,4,"SD_WS09_WindDirAverage_08 $name i=".$i." Speed=".round($ws,2)." Dir=".round($wd,2)." Time=".substr($time,11)." vec=".round($sumSin,2)."/".round($sumCos,2)." age=".$age." ".round($weight,2); } } my $average = int((rad2deg(atan2($sumSin, $sumCos)) + 360) % 360); Log3 $hash,4,"SD_WS09_WindDirAverage_09 $name Mittelwert über $anz Werte ist $average, avspeed=".round($sumSpeed/$num,1) if ($num > 0); #-- undef zurückliefern, wenn die durchschnittliche Geschwindigkeit zu gering oder gar keine Werte verfügbar return if (($anz == 0) || ($sanz == 0)); return if (($sumSpeed / $sanz) < $minspeed); Log3 $hash,4,"SD_WS09_WindDirAverage_END $name Mittelwert=$average"; return $average; } ################################### sub SD_WS09_bin2dec($) { my $h = shift; my $int = unpack("N", pack("B32",substr("0" x 32 . $h, -32))); return sprintf("%d", $int); } ################################### sub SD_WS09_binflip($) { my $h = shift; my $hlen = length($h); my $i = 0; my $flip = ""; for ($i=$hlen-1; $i >= 0; $i--) { $flip = $flip.substr($h,$i,1); } return $flip; } ################################### sub SD_WS09_BCD2bin($) { my $binary = shift; my $int = unpack("N", pack("B32", substr("0" x 32 . $binary, -32))); my $BCD = sprintf("%x", $int ); return $BCD; } ################################### sub SD_WS09_SHIFT($$){ my ($hash, $rawData) = @_; my $name = $hash->{NAME}; my $hlen = length($rawData); my $blen = $hlen * 4; my $bitData = unpack("B$blen", pack("H$hlen", $rawData)); my $bitData2 = '1'.$bitData; my $bitData20 = substr($bitData2,0,length($bitData2)-1); $blen = length($bitData20); $hlen = $blen / 4; $rawData = uc(unpack("H$hlen", pack("B$blen", $bitData20))); $bitData = $bitData20; #Log3 $hash, 4, "$name: SD_WS09_SHIFT_0 raw: $rawData length:".length($bitData); Log3 $hash, 5, "$name: SD_WS09_SHIFT_1 bitdata: $bitData length:".length($bitData); return $rawData; } ################################### sub SD_WS09_CRCCHECK($) { my $rawData = shift; 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; $rr3 = sprintf("%d", hex($rr3)); Log3 "SD_WS09_CRCCHECK", 4, "SD_WS09_CRCCHECK : raw:$rawData CRC=$rr3 " ; return $rr3 ; } 1; =pod =item summary Supports weather sensors (WH1080/3080/CTW-600) protocl 9 from SIGNALduino =item summary_DE Unterstuetzt Wettersensoren (WH1080/3080/CTW-600) / Protokol 9 vom SIGNALduino =begin html

Wether Sensors protocol #9

=end html =begin html_DE

SD_WS09

=end html_DE =cut